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