Skip to content

Method: createVocabularyClass(JCodeModel)

1: /**
2: * Copyright (C) 2020 Czech Technical University in Prague
3: * <p>
4: * This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
5: * License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later
6: * version.
7: * <p>
8: * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
9: * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
10: * details. You should have received a copy of the GNU General Public License along with this program. If not, see
11: * <http://www.gnu.org/licenses/>.
12: */
13: package cz.cvut.kbss.jopa.owl2java;
14:
15: import com.sun.codemodel.*;
16: import cz.cvut.kbss.jopa.ic.api.AtomicSubClassConstraint;
17: import cz.cvut.kbss.jopa.ic.api.DataParticipationConstraint;
18: import cz.cvut.kbss.jopa.ic.api.ObjectParticipationConstraint;
19: import cz.cvut.kbss.jopa.model.MultilingualString;
20: import cz.cvut.kbss.jopa.model.annotations.OWLAnnotationProperty;
21: import cz.cvut.kbss.jopa.model.annotations.OWLDataProperty;
22: import cz.cvut.kbss.jopa.model.annotations.OWLObjectProperty;
23: import cz.cvut.kbss.jopa.model.annotations.Properties;
24: import cz.cvut.kbss.jopa.model.annotations.*;
25: import cz.cvut.kbss.jopa.owl2java.cli.Option;
26: import cz.cvut.kbss.jopa.owl2java.cli.PropertiesType;
27: import cz.cvut.kbss.jopa.owl2java.config.TransformationConfiguration;
28: import cz.cvut.kbss.jopa.owl2java.exception.OWL2JavaException;
29: import cz.cvut.kbss.jopa.owlapi.DatatypeTransformer;
30: import cz.cvut.kbss.jopa.vocabulary.DC;
31: import cz.cvut.kbss.jopa.vocabulary.RDFS;
32: import org.semanticweb.owlapi.model.OWLClass;
33: import org.semanticweb.owlapi.model.*;
34: import org.semanticweb.owlapi.search.EntitySearcher;
35: import org.slf4j.Logger;
36: import org.slf4j.LoggerFactory;
37:
38: import java.io.Serializable;
39: import java.text.Normalizer;
40: import java.util.*;
41: import java.util.concurrent.atomic.AtomicBoolean;
42: import java.util.stream.Collectors;
43:
44: import static cz.cvut.kbss.jopa.owl2java.Constants.*;
45:
46: public class JavaTransformer {
47:
48: private static final Logger LOG = LoggerFactory.getLogger(OWL2JavaTransformer.class);
49:
50: private static final String[] KEYWORDS = {"abstract",
51: "assert",
52: "boolean",
53: "break",
54: "byte",
55: "case",
56: "catch",
57: "char",
58: "class",
59: "const",
60: "continue",
61: "default",
62: "do",
63: "double",
64: "else",
65: "enum",
66: "extends",
67: "final",
68: "finally",
69: "float",
70: "for",
71: "goto",
72: "if",
73: "implements",
74: "import",
75: "instanceof",
76: "int",
77: "interface",
78: "long",
79: "native",
80: "new",
81: "package",
82: "private",
83: "protected",
84: "public",
85: "return",
86: "short",
87: "static",
88: "strictfp",
89: "super",
90: "switch",
91: "synchronized",
92: "this",
93: "throw",
94: "throws",
95: "transient",
96: "try",
97: "void",
98: "volatile",
99: "while"};
100:
101: private static final String PREFIX_STRING = "s_";
102: private static final String PREFIX_CLASS = "c_";
103: private static final String PREFIX_PROPERTY = "p_";
104: private static final String PREFIX_INDIVIDUAL = "i_";
105: private static final String PREFIX_DATATYPE = "d_";
106:
107: private JDefinedClass voc;
108: private final Map<OWLEntity, JFieldRef> entities = new HashMap<>();
109:
110: private final Map<OWLClass, JDefinedClass> classes = new HashMap<>();
111:
112: private final TransformationConfiguration configuration;
113:
114: JavaTransformer(TransformationConfiguration configuration) {
115: this.configuration = configuration;
116: }
117:
118: private static String validJavaIDForIRI(final IRI iri) {
119: if (iri.getFragment() != null) {
120: return validJavaID(iri.getFragment());
121: } else {
122: int x = iri.toString().lastIndexOf("/");
123: return validJavaID(iri.toString().substring(x + 1));
124: }
125: }
126:
127: private static String validJavaID(final String s) {
128: String res = s.trim().replace("-", "_").replace("'", "_quote_").replace(".", "_dot_").replace(',', '_');
129: // Replace non-ASCII characters with ASCII ones
130: res = Normalizer.normalize(res, Normalizer.Form.NFD).replaceAll("[^\\p{ASCII}]", "");
131: if (Arrays.binarySearch(KEYWORDS, res) >= 0) {
132: res = "_" + res;
133: }
134: return res;
135: }
136:
137: private JFieldVar addField(final String name, final JDefinedClass cls,
138: final JType fieldType) {
139: String newName = name;
140:
141: int i = 0;
142: while (cls.fields().containsKey(newName)) {
143: newName = name + "" + (++i);
144: }
145:
146: final JFieldVar fvId = cls.field(JMod.PROTECTED, fieldType, newName);
147: final String fieldName = fvId.name().substring(0, 1).toUpperCase() + fvId.name().substring(1);
148: final JMethod mSetId = cls.method(JMod.PUBLIC, void.class, "set" + fieldName);
149: final JVar v = mSetId.param(fieldType, fvId.name());
150: mSetId.body().assign(JExpr._this().ref(fvId), v);
151: final JMethod mGetId = cls.method(JMod.PUBLIC, fieldType, "get" + fieldName);
152: mGetId.body()._return(fvId);
153: return fvId;
154: }
155:
156: /**
157: * Generates an object model consisting of JOPA entity classes and a vocabulary file from the specified ontology and
158: * context definition.
159: *
160: * @param ontology Ontology from which the model is generated
161: * @param context Context information
162: * @return The generated object model
163: */
164: public ObjectModel generateModel(final OWLOntology ontology, final ContextDefinition context) {
165: try {
166: final JCodeModel cm = new JCodeModel();
167: voc = createVocabularyClass(cm);
168: generateVocabulary(ontology, cm, context);
169: _generateModel(ontology, cm, context, modelPackageName());
170: return new ObjectModel(cm);
171: } catch (JClassAlreadyExistsException e) {
172: throw new OWL2JavaException("Transformation FAILED.", e);
173: }
174: }
175:
176: private String modelPackageName() {
177: final String packageConfig = configuration.getPackageName();
178: final StringBuilder sb = new StringBuilder(packageConfig);
179: if (!packageConfig.isEmpty()) {
180: sb.append(PACKAGE_SEPARATOR);
181: }
182: sb.append(MODEL_PACKAGE);
183: return sb.toString();
184: }
185:
186: private JDefinedClass createVocabularyClass(JCodeModel codeModel)
187: throws JClassAlreadyExistsException {
188: final String packageName = configuration.getPackageName();
189: final String className =
190:• packageName.isEmpty() ? VOCABULARY_CLASS : packageName + PACKAGE_SEPARATOR + VOCABULARY_CLASS;
191: final JDefinedClass cls = codeModel._class(className);
192: generateAuthorshipDoc(cls);
193: return cls;
194: }
195:
196: private void generateAuthorshipDoc(JDocCommentable javaElem) {
197: javaElem.javadoc().add("This class was generated by OWL2Java " + VERSION);
198: }
199:
200: /**
201: * Generates only vocabulary of the loaded ontology.
202: *
203: * @param ontology Ontology from which the vocabulary should be generated
204: * @param context Integrity constraints context, if null is supplied, the whole ontology is interpreted as
205: * integrity constraints.
206: * @return The generated object model
207: */
208: public ObjectModel generateVocabulary(final OWLOntology ontology, ContextDefinition context) {
209: try {
210: final JCodeModel cm = new JCodeModel();
211: this.voc = createVocabularyClass(cm);
212: generateVocabulary(ontology, cm, context);
213: return new ObjectModel(cm);
214: } catch (JClassAlreadyExistsException e) {
215: throw new OWL2JavaException("Vocabulary generation FAILED, because the Vocabulary class already exists.",
216: e);
217: }
218: }
219:
220: private void generateObjectProperty(final OWLOntology ontology,
221: final JCodeModel cm,
222: final ContextDefinition context,
223: final String pkg,
224: final OWLClass clazz,
225: final JDefinedClass subj,
226: final org.semanticweb.owlapi.model.OWLObjectProperty prop) {
227: final ClassObjectPropertyComputer comp = new ClassObjectPropertyComputer(
228: clazz,
229: prop,
230: context.set,
231: ontology
232: );
233:
234: if (Card.NO != comp.getCard()) {
235: JClass filler = ensureCreated(pkg, cm, comp.getFiller(), ontology);
236: final String fieldName = validJavaIDForIRI(prop.getIRI());
237:
238: switch (comp.getCard()) {
239: case MULTIPLE:
240: filler = cm.ref(java.util.Set.class).narrow(filler);
241: break;
242: case SIMPLELIST:
243: case LIST:
244: filler = cm.ref(java.util.List.class).narrow(filler);
245: break;
246: default:
247: break;
248: }
249:
250: final JFieldVar fv = addField(fieldName, subj, filler);
251: generateJavadoc(ontology, prop, fv);
252:
253: if (comp.getCard().equals(Card.SIMPLELIST)) {
254: fv.annotate(Sequence.class).param("type", SequenceType.simple);
255: }
256:
257:
258: fv.annotate(OWLObjectProperty.class).param("iri", entities.get(prop));
259:
260: JAnnotationArrayMember use = null;
261: for (ObjectParticipationConstraint ic : comp.getParticipationConstraints()) {
262: if (use == null) {
263: use = fv.annotate(ParticipationConstraints.class).paramArray("value");
264: }
265: JAnnotationUse u = use.annotate(ParticipationConstraint.class)
266: .param("owlObjectIRI", entities.get(ic.getObject()));
267: setParticipationConstraintCardinality(u, ic);
268: }
269: }
270: }
271:
272: private void setParticipationConstraintCardinality(JAnnotationUse u,
273: cz.cvut.kbss.jopa.ic.api.ParticipationConstraint<?, ?> ic) {
274: if (ic.getMin() != 0) {
275: u.param("min", ic.getMin());
276: }
277: if (ic.getMin() != -1) {
278: u.param("max", ic.getMax());
279: }
280: }
281:
282: private void generateDataProperty(final OWLOntology ontology,
283: final JCodeModel cm,
284: final ContextDefinition context,
285: final OWLClass clazz,
286: final JDefinedClass subj,
287: final org.semanticweb.owlapi.model.OWLDataProperty prop) {
288: final ClassDataPropertyComputer comp = new ClassDataPropertyComputer(
289: clazz,
290: prop,
291: context.set,
292: ontology
293: );
294:
295: if (Card.NO != comp.getCard()) {
296:
297: final JType obj = cm._ref(resolveFieldType(comp.getFiller()));
298:
299: final String fieldName = validJavaIDForIRI(prop.getIRI());
300:
301: JFieldVar fv;
302:
303: switch (comp.getCard()) {
304: case MULTIPLE:
305: fv = addField(fieldName, subj, cm.ref(java.util.Set.class).narrow(obj));
306: break;
307: case ONE:
308: fv = addField(fieldName, subj, obj);
309: break;
310: default:
311: throw new OWL2JavaException("Unsupported data property cardinality type " + comp.getCard());
312: }
313: generateJavadoc(ontology, prop, fv);
314:
315: fv.annotate(OWLDataProperty.class).param("iri", entities.get(prop));
316:
317: JAnnotationArrayMember use = null;
318: for (DataParticipationConstraint ic : comp.getParticipationConstraints()) {
319: if (use == null) {
320: use = fv.annotate(ParticipationConstraints.class).paramArray("value");
321: }
322:
323: JAnnotationUse u = use.annotate(ParticipationConstraint.class)
324: .param("owlObjectIRI", comp.getFiller().getIRI().toString());
325:
326: setParticipationConstraintCardinality(u, ic);
327: }
328: }
329: }
330:
331: private Class<?> resolveFieldType(OWLDatatype datatype) {
332: final Class<?> cls = DatatypeTransformer.transformOWLType(datatype);
333: if (MultilingualString.class.equals(cls) && !configuration.shouldPreferMultilingualStrings()) {
334: return String.class;
335: }
336: return cls;
337: }
338:
339: private void _generateModel(final OWLOntology ontology, final JCodeModel cm,
340: final ContextDefinition context, final String pkg) {
341: LOG.info("Generating model ...");
342: final PropertiesType propertiesType = configuration.getPropertiesType();
343:
344: context.classes.add(ontology.getOWLOntologyManager().getOWLDataFactory().getOWLThing());
345:
346: for (final OWLClass clazz : context.classes) {
347: LOG.info(" Generating class '{}'.", clazz);
348: final JDefinedClass subj = ensureCreated(pkg, cm, clazz, ontology);
349:
350: final AtomicBoolean extendClass = new AtomicBoolean(false);
351: context.set.getClassIntegrityConstraints(clazz).stream()
352: .filter(ic -> ic instanceof AtomicSubClassConstraint).forEach(ic -> {
353: final AtomicSubClassConstraint icc = (AtomicSubClassConstraint) ic;
354: subj._extends(ensureCreated(pkg, cm, icc.getSupClass(), ontology
355: ));
356: extendClass.set(true);
357: });
358:
359: if (!extendClass.get())
360: addCommonClassFields(cm, subj, propertiesType);
361: for (final org.semanticweb.owlapi.model.OWLObjectProperty prop : context.objectProperties) {
362: generateObjectProperty(ontology, cm, context, pkg, clazz, subj, prop);
363: }
364:
365: for (org.semanticweb.owlapi.model.OWLDataProperty prop : context.dataProperties) {
366: generateDataProperty(ontology, cm, context, clazz, subj, prop);
367: }
368: }
369: }
370:
371: private void generateVocabulary(final OWLOntology o, final JCodeModel cm, ContextDefinition context) {
372: final Collection<OWLEntity> col = new LinkedHashSet<>();
373: col.add(o.getOWLOntologyManager().getOWLDataFactory().getOWLThing());
374: col.addAll(context.classes);
375: col.addAll(context.objectProperties);
376: col.addAll(context.dataProperties);
377: col.addAll(context.annotationProperties);
378: col.addAll(context.individuals);
379:
380: generateOntologyIrisConstants(o.getOWLOntologyManager());
381:
382: final Set<IRI> visitedProperties = new HashSet<>(col.size());
383:
384: for (final OWLEntity c : col) {
385: final Optional<String> prefix = resolveFieldPrefix(c, visitedProperties);
386: if (!prefix.isPresent()) {
387: continue;
388: }
389: final String sFieldName = ensureUniqueIdentifier(
390: PREFIX_STRING + prefix.get() + validJavaIDForIRI(c.getIRI()));
391:
392: final JFieldVar fv1 = voc.field(JMod.PUBLIC | JMod.STATIC
393: | JMod.FINAL, String.class, sFieldName, JExpr.lit(c.getIRI().toString()));
394: if (configuration.shouldGenerateOwlapiIris()) {
395: voc.field(JMod.PUBLIC | JMod.STATIC | JMod.FINAL, IRI.class,
396: sFieldName.substring(PREFIX_STRING.length()),
397: cm.ref(IRI.class).staticInvoke("create").arg(fv1));
398: }
399: generateJavadoc(o, c, fv1);
400: entities.put(c, voc.staticRef(fv1));
401: }
402: }
403:
404: private void generateOntologyIrisConstants(OWLOntologyManager ontologyManager) {
405: // Get only unique ontology IRIs sorted
406: final List<IRI> ontologyIris = ontologyManager.ontologies().map(o -> o.getOntologyID().getOntologyIRI())
407: .filter(Optional::isPresent).map(Optional::get).distinct()
408: .sorted(Comparator.comparing(IRI::getIRIString))
409: .collect(Collectors.toList());
410: ontologyIris.forEach(iri -> {
411: final String fieldName = ensureUniqueIdentifier("ONTOLOGY_IRI_" + validJavaIDForIRI(iri));
412: voc.field(JMod.PUBLIC | JMod.STATIC | JMod.FINAL, String.class, fieldName, JExpr.lit(iri.toString()));
413: });
414: }
415:
416: private static Optional<String> resolveFieldPrefix(OWLEntity c, Set<IRI> visitedProperties) {
417: if (c.isOWLClass()) {
418: return Optional.of(PREFIX_CLASS);
419: } else if (c.isOWLDatatype()) {
420: return Optional.of(PREFIX_DATATYPE);
421: } else if (c.isOWLDataProperty() || c.isOWLObjectProperty() || c.isOWLAnnotationProperty()) {
422: if (visitedProperties.contains(c.getIRI())) {
423: LOG.debug("Property with IRI {} already processed. Skipping.", c.getIRI());
424: return Optional.empty();
425: }
426: visitedProperties.add(c.getIRI());
427: return Optional.of(PREFIX_PROPERTY);
428: } else if (c.isOWLNamedIndividual()) {
429: return Optional.of(PREFIX_INDIVIDUAL);
430: }
431: return Optional.of("");
432: }
433:
434: private String ensureUniqueIdentifier(String id) {
435: final StringBuilder sb = new StringBuilder(id);
436: while (voc.fields().containsKey(sb.toString())) {
437: sb.append("_A");
438: }
439: return sb.toString();
440: }
441:
442: /**
443: * Generates Javadoc from rdfs:comment annotation (if present).
444: *
445: * @param ontology Ontology from which the model/vocabulary is being generated
446: * @param owlEntity Annotated entity
447: * @param javaElem Element to document with Javadoc
448: */
449: private boolean generateJavadoc(OWLOntology ontology, OWLEntity owlEntity, JDocCommentable javaElem) {
450: if (!configuration.shouldGenerateJavadoc()) {
451: return false;
452: }
453: final Optional<OWLAnnotation> ann = EntitySearcher.getAnnotations(owlEntity, ontology)
454: .filter(a -> a.getProperty().isComment()).findFirst();
455: ann.flatMap(a -> a.getValue().asLiteral()).ifPresent(lit -> javaElem.javadoc().add(lit.getLiteral()));
456: return ann.isPresent() && ann.get().getValue().isLiteral();
457: }
458:
459: private JDefinedClass create(final String pkg, final JCodeModel cm, final OWLClass clazz,
460: final OWLOntology ontology) {
461: JDefinedClass cls;
462:
463: String name = pkg + PACKAGE_SEPARATOR + javaClassId(ontology, clazz);
464:
465: try {
466: cls = cm._class(name);
467:
468: cls.annotate(cz.cvut.kbss.jopa.model.annotations.OWLClass.class).param("iri", entities.get(clazz));
469: cls._implements(Serializable.class);
470:
471: generateClassJavadoc(ontology, clazz, cls);
472: } catch (JClassAlreadyExistsException e) {
473: LOG.trace("Class already exists. Using the existing version. {}", e.getMessage());
474: cls = cm._getClass(name);
475: }
476: return cls;
477: }
478:
479: /**
480: * Add common properties such as id and type
481: */
482: private void addCommonClassFields(final JCodeModel cm, final JDefinedClass cls,
483: final PropertiesType propertiesType) {
484: // @Id(generated = true) protected String id;
485: final JClass ftId = cm.ref(String.class);
486: final JFieldVar fvId = addField("id", cls, ftId);
487: JAnnotationUse a = fvId.annotate(Id.class);
488: a.param("generated", true);
489:
490: // RDFS label
491: final JClass ftLabel = cm.ref(String.class);
492: final JFieldVar fvLabel = addField("name", cls, ftLabel);
493: fvLabel.annotate(OWLAnnotationProperty.class).param("iri", cm.ref(RDFS.class).staticRef("LABEL"));
494:
495: // DC description
496: final JClass ftDescription = cm.ref(String.class);
497: final JFieldVar fvDescription = addField("description", cls, ftDescription);
498: fvDescription.annotate(OWLAnnotationProperty.class)
499: .param("iri", cm.ref(DC.Elements.class).staticRef("DESCRIPTION"));
500:
501: // @Types Set<String> types;
502: final JClass ftTypes = cm.ref(Set.class).narrow(String.class);
503: final JFieldVar fvTypes = addField("types", cls, ftTypes);
504: fvTypes.annotate(Types.class);
505:
506: // @Properties public final Map<String,Set<String>> properties;
507: final Class propertiesTypeC = (propertiesType == PropertiesType.object ? Object.class : String.class);
508: final JClass ftProperties = cm.ref(Map.class)
509: .narrow(cm.ref(String.class), cm.ref(Set.class).narrow(propertiesTypeC));
510: final JFieldVar fvProperties = addField("properties", cls, ftProperties);
511: fvProperties.annotate(Properties.class);
512:
513: generateToStringMethod(cls, fvId.name(), fvLabel.name());
514: }
515:
516: private String javaClassId(OWLOntology ontology, OWLClass owlClass) {
517: final Optional<OWLAnnotation> res = EntitySearcher.getAnnotations(owlClass, ontology)
518: .filter(a -> isJavaClassNameAnnotation(a) &&
519: a.getValue().isLiteral()).findFirst();
520: if (res.isPresent()) {
521: return res.get().getValue().asLiteral().get().getLiteral();
522: } else {
523: return toJavaNotation(validJavaIDForIRI(owlClass.getIRI()));
524: }
525: }
526:
527: private void generateClassJavadoc(OWLOntology ontology, OWLEntity owlEntity, JDocCommentable javaElem) {
528: final boolean generated = generateJavadoc(ontology, owlEntity, javaElem);
529: if (generated) {
530: javaElem.javadoc().add("\n\n");
531: }
532: generateAuthorshipDoc(javaElem);
533: }
534:
535: private void generateToStringMethod(JDefinedClass cls, String idFieldName, String labelFieldName) {
536: final JMethod toString = cls.method(JMod.PUBLIC, String.class, "toString");
537: toString.annotate(Override.class);
538: final JBlock body = toString.body();
539: JExpression expression = JExpr.lit(cls.name() + " {");
540: expression = expression.plus(JExpr.ref(labelFieldName));
541: expression = expression.plus(JExpr.lit("<")).plus(JExpr.ref(idFieldName)).plus(JExpr.lit(">"));
542: expression = expression.plus(JExpr.lit("}"));
543:
544: body._return(expression);
545: }
546:
547: private JDefinedClass ensureCreated(final String pkg, final JCodeModel cm, final OWLClass clazz,
548: final OWLOntology ontology) {
549: if (!classes.containsKey(clazz)) {
550: classes.put(clazz, create(pkg, cm, clazz, ontology));
551: }
552: return classes.get(clazz);
553: }
554:
555: private boolean isJavaClassNameAnnotation(OWLAnnotation a) {
556: final String classNameProperty = (String) configuration.getCliParams()
557: .valueOf(Option.JAVA_CLASSNAME_ANNOTATION.arg);
558: return a.getProperty().getIRI()
559: .equals(IRI.create(classNameProperty != null ? classNameProperty : Constants.P_CLASS_NAME));
560: }
561:
562: /**
563: * Converts a class name to the Java camel case notation
564: *
565: * @param className Generated class name
566: * @return Converted class name
567: */
568: private static String toJavaNotation(String className) {
569: StringBuilder result = new StringBuilder();
570: for (String w : className.split("_")) {
571: if (!w.isEmpty()) {
572: result.append(w.substring(0, 1).toUpperCase()).append(w.substring(1));
573: }
574: }
575: return result.toString();
576: }
577: }