Skip to content

Package: ClassFieldMetamodelProcessor$InferenceInfo

ClassFieldMetamodelProcessor$InferenceInfo

nameinstructionbranchcomplexitylinemethod
ClassFieldMetamodelProcessor.InferenceInfo(Inferred)
M: 1 C: 19
95%
M: 1 C: 5
83%
M: 1 C: 3
75%
M: 0 C: 4
100%
M: 0 C: 1
100%

Coverage

1: /*
2: * JOPA
3: * Copyright (C) 2024 Czech Technical University in Prague
4: *
5: * This library is free software; you can redistribute it and/or
6: * modify it under the terms of the GNU Lesser General Public
7: * License as published by the Free Software Foundation; either
8: * version 3.0 of the License, or (at your option) any later version.
9: *
10: * This library is distributed in the hope that it will be useful,
11: * but WITHOUT ANY WARRANTY; without even the implied warranty of
12: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13: * Lesser General Public License for more details.
14: *
15: * You should have received a copy of the GNU Lesser General Public
16: * License along with this library.
17: */
18: package cz.cvut.kbss.jopa.model.metamodel;
19:
20: import cz.cvut.kbss.jopa.exception.MetamodelInitializationException;
21: import cz.cvut.kbss.jopa.exceptions.OWLPersistenceException;
22: import cz.cvut.kbss.jopa.model.IRI;
23: import cz.cvut.kbss.jopa.model.annotations.Convert;
24: import cz.cvut.kbss.jopa.model.annotations.Enumerated;
25: import cz.cvut.kbss.jopa.model.annotations.FetchType;
26: import cz.cvut.kbss.jopa.model.annotations.Id;
27: import cz.cvut.kbss.jopa.model.annotations.Inferred;
28: import cz.cvut.kbss.jopa.model.annotations.OWLAnnotationProperty;
29: import cz.cvut.kbss.jopa.model.annotations.OWLDataProperty;
30: import cz.cvut.kbss.jopa.model.annotations.OWLObjectProperty;
31: import cz.cvut.kbss.jopa.model.annotations.ParticipationConstraint;
32: import cz.cvut.kbss.jopa.model.annotations.ParticipationConstraints;
33: import cz.cvut.kbss.jopa.model.annotations.Properties;
34: import cz.cvut.kbss.jopa.model.annotations.RDFCollection;
35: import cz.cvut.kbss.jopa.model.annotations.Sequence;
36: import cz.cvut.kbss.jopa.model.annotations.Sparql;
37: import cz.cvut.kbss.jopa.model.annotations.Types;
38: import cz.cvut.kbss.jopa.oom.converter.ConverterWrapper;
39: import cz.cvut.kbss.jopa.utils.EntityPropertiesUtils;
40: import org.slf4j.Logger;
41: import org.slf4j.LoggerFactory;
42:
43: import java.lang.reflect.Field;
44: import java.lang.reflect.Method;
45: import java.lang.reflect.ParameterizedType;
46: import java.lang.reflect.Type;
47: import java.util.ArrayDeque;
48: import java.util.Collection;
49: import java.util.Deque;
50: import java.util.List;
51: import java.util.Map;
52: import java.util.Objects;
53: import java.util.Optional;
54: import java.util.Set;
55:
56: class ClassFieldMetamodelProcessor<X> {
57:
58: private static final Logger LOG = LoggerFactory.getLogger(ClassFieldMetamodelProcessor.class);
59:
60: private final FieldMappingValidator mappingValidator = new FieldMappingValidator();
61:
62: private final Class<X> cls;
63: private final AbstractIdentifiableType<X> et;
64: private final TypeBuilderContext<X> context;
65: private final MetamodelBuilder metamodelBuilder;
66:
67: ClassFieldMetamodelProcessor(TypeBuilderContext<X> context, MetamodelBuilder metamodelBuilder) {
68: this.cls = context.getType().getJavaType();
69: this.context = context;
70: this.et = context.getType();
71: this.metamodelBuilder = metamodelBuilder;
72: }
73:
74: void processField(Field field) {
75: LOG.trace("processing field: {}.", field);
76: if (EntityPropertiesUtils.isFieldTransient(field)) {
77: // Do not log static fields
78: if (!EntityPropertiesUtils.isFieldStatic(field)) {
79: LOG.trace("Skipping transient field {}.", field);
80: }
81: return;
82: }
83: if (field.getType().isPrimitive()) {
84: throw new MetamodelInitializationException("Primitive types cannot be used for entity fields. Field " + field + " in class " + cls);
85: }
86:
87: final Class<?> fieldValueCls = getFieldValueType(field);
88: field.setAccessible(true);
89: final InferenceInfo inference = processInferenceInfo(field);
90:
91: if (isTypesField(field)) {
92: processTypesField(field, fieldValueCls, inference);
93: return;
94: }
95: if (isPropertiesField(field)) {
96: processPropertiesField(field, fieldValueCls, inference);
97: return;
98: }
99:
100: if (isQueryAttribute(field)) {
101: createQueryAttribute(field, fieldValueCls);
102: return;
103: }
104:
105: PropertyInfo propertyInfo = PropertyInfo.from(field);
106:
107: final PropertyAttributes propertyAtt = PropertyAttributes.create(propertyInfo, mappingValidator, context);
108: propertyAtt.resolve(propertyInfo, metamodelBuilder, fieldValueCls);
109:
110: if (propertyAtt.isKnownOwlProperty()) {
111: final AbstractAttribute<X, ?> a = createAttribute(propertyInfo, inference, propertyAtt);
112: registerTypeReference(a);
113: return;
114: }
115:
116: if (isIdentifierField(field)) {
117: processIdentifierField(field);
118: return;
119: }
120:
121: AnnotatedAccessor fieldAccessor = findAnnotatedMethodBelongingToField(field);
122:
123: if (fieldAccessor != null) {
124: createAndRegisterAttribute(field, inference, fieldValueCls, fieldAccessor);
125: return;
126: }
127:
128: throw new MetamodelInitializationException("Unable to process field " + field + ". It is not transient but has no mapping information.");
129: }
130:
131: /**
132: * Do a bottom top search of hierarchy in order to find annotated accessor belonging to given field. This is used
133: * when a property annotation (
134: * {@link OWLDataProperty,OWLObjectProperty,OWLAnnotationProperty) is declared on method, not on field.
135: */
136: private AnnotatedAccessor findAnnotatedMethodBelongingToField(Field field) {
137: LOG.debug("finding property definition to field {} ", field);
138:
139: Deque<IdentifiableType<?>> toVisit = new ArrayDeque<>();
140: toVisit.add(et);
141:
142: boolean found = false;
143: AnnotatedAccessor foundAccessor = null;
144:
145: while (!toVisit.isEmpty()) {
146: IdentifiableType<?> top = toVisit.peek();
147: Collection<AnnotatedAccessor> accessorsInParent = metamodelBuilder.getAnnotatedAccessorsForClass(top);
148:
149: for (AnnotatedAccessor annotatedAccessor : accessorsInParent) {
150:
151: if (!propertyBelongsToMethod(field, annotatedAccessor)) {
152: continue;
153: }
154:
155: LOG.debug("Found annotated method belonging to field - {} - {}", annotatedAccessor.getMethod()
156: .getName(), field.getName());
157: if (!found) { // first belonging method
158: found = true;
159: foundAccessor = annotatedAccessor;
160: } else if (methodsAnnotationsEqual(annotatedAccessor.getMethod(), foundAccessor.getMethod())) {
161: LOG.debug("Methods are equal, skipping");
162: } else { /// Two non-equal methods that could belong to the field - ambiguous
163: throw new MetamodelInitializationException("Ambiguous hierarchy - fields can inherit only from multiple methods if their property mapping annotations equal. However for field " + field + " two non-compatible methods were found - " + foundAccessor.getMethod() + " and " + annotatedAccessor.getMethod());
164: }
165:
166: }
167: /// remove top
168: toVisit.pop();
169: /// add all parents
170: toVisit.addAll(top.getSupertypes());
171: }
172:
173: return foundAccessor;
174: }
175:
176: private void createAndRegisterAttribute(Field field, InferenceInfo inference, Class<?> fieldValueCls,
177: AnnotatedAccessor annotatedAccessor) {
178: final PropertyInfo info = PropertyInfo.from(annotatedAccessor.getMethod(), field);
179:
180: final PropertyAttributes propertyAtt = PropertyAttributes.create(info, mappingValidator, context);
181: propertyAtt.resolve(info, metamodelBuilder, fieldValueCls);
182:
183: final AbstractAttribute<X, ?> a = createAttribute(info, inference, propertyAtt);
184: registerTypeReference(a);
185: }
186:
187: private boolean methodsAnnotationsEqual(Method newMethod, Method foundMethod) {
188:
189: return Objects.equals(newMethod.getAnnotation(OWLObjectProperty.class), foundMethod.getAnnotation(OWLObjectProperty.class)) &&
190: Objects.equals(newMethod.getAnnotation(OWLDataProperty.class), foundMethod.getAnnotation(OWLDataProperty.class)) &&
191: Objects.equals(newMethod.getAnnotation(OWLAnnotationProperty.class), foundMethod.getAnnotation(OWLAnnotationProperty.class)) &&
192: Objects.equals(newMethod.getAnnotation(Convert.class), foundMethod.getAnnotation(Convert.class)) &&
193: Objects.equals(newMethod.getAnnotation(Sequence.class), foundMethod.getAnnotation(Sequence.class)) &&
194: Objects.equals(newMethod.getAnnotation(Enumerated.class), foundMethod.getAnnotation(Enumerated.class)) &&
195: Objects.equals(newMethod.getAnnotation(ParticipationConstraints.class), foundMethod.getAnnotation(ParticipationConstraints.class));
196:
197: }
198:
199:
200: private boolean propertyBelongsToMethod(Field property, AnnotatedAccessor accessor) {
201: if (!property.getName().equals(accessor.getPropertyName())) {
202: return false;
203: }
204: if (!property.getType().isAssignableFrom(accessor.getPropertyType())) {
205: throw new MetamodelInitializationException("Non-compatible types between method " + accessor + " and field " + property.getName() + ". Method is accessor to type " + accessor.getPropertyType() + ", field type is " + property.getType());
206: }
207:
208: return true;
209: }
210:
211: private static Class<?> getFieldValueType(Field field) {
212: if (Collection.class.isAssignableFrom(field.getType())) {
213: return getSetOrListErasureType((ParameterizedType) field.getGenericType());
214: } else if (field.getType().isArray()) {
215: throw new MetamodelInitializationException("Array persistent attributes are not supported.");
216: } else {
217: return field.getType();
218: }
219: }
220:
221: private static Class<?> getSetOrListErasureType(final ParameterizedType cls) {
222: final Type[] t = cls.getActualTypeArguments();
223:
224: if (t.length != 1) {
225: throw new OWLPersistenceException("Only collections with a single generic parameter are supported.");
226: }
227: Type type = t[0];
228: if (type instanceof Class<?>) {
229: return (Class<?>) type;
230: } else if (type instanceof ParameterizedType) {
231: final Type rawType = ((ParameterizedType) type).getRawType();
232: return (Class<?>) rawType;
233: }
234: throw new OWLPersistenceException("Unsupported collection element type " + type);
235: }
236:
237: private InferenceInfo processInferenceInfo(Field field) {
238: final Inferred inferred = field.getAnnotation(Inferred.class);
239:
240: final InferenceInfo inference = new InferenceInfo(inferred);
241: if (inference.inferred) {
242: metamodelBuilder.addInferredClass(cls);
243: }
244: return inference;
245: }
246:
247: private static boolean isTypesField(Field field) {
248: return field.getAnnotation(Types.class) != null;
249: }
250:
251: private void processTypesField(Field field, Class<?> fieldValueCls, InferenceInfo inference) {
252: mappingValidator.validateTypesField(field);
253: // Always use eager for Types, they are fetched eagerly anyway to ensure entity is of correct type
254: final FetchType fetchType = FetchType.EAGER;
255: et.addDirectTypes(new TypesSpecificationImpl<>(et, fetchType, field, fieldValueCls, inference.inferred));
256: }
257:
258: private static boolean isPropertiesField(Field field) {
259: return field.getAnnotation(Properties.class) != null;
260: }
261:
262: private void processPropertiesField(Field field, Class<?> fieldValueCls, InferenceInfo inference) {
263: Properties properties = field.getAnnotation(Properties.class);
264: mappingValidator.validatePropertiesField(field);
265: final PropertiesParametersResolver paramsResolver = new PropertiesParametersResolver(field);
266: final FetchType fetchType = inference.inferred ? FetchType.EAGER : properties.fetchType();
267: et.addOtherProperties(
268: PropertiesSpecificationImpl.declaringType(et).fetchType(fetchType).javaField(field)
269: .javaType(fieldValueCls).inferred(inference.inferred)
270: .propertyIdType(paramsResolver.getPropertyIdentifierType())
271: .propertyValueType(paramsResolver.getPropertyValueType()).build());
272: }
273:
274: private static boolean isQueryAttribute(Field field) {
275: return field.getAnnotation(Sparql.class) != null;
276: }
277:
278: private void createQueryAttribute(Field field, Class<?> fieldValueCls) {
279: final Sparql sparqlAnnotation = field.getAnnotation(Sparql.class);
280: final String query = sparqlAnnotation.query();
281: final FetchType fetchType = sparqlAnnotation.fetchType();
282:
283: ParticipationConstraint[] participationConstraints = field.getAnnotationsByType(ParticipationConstraint.class);
284:
285: final AbstractQueryAttribute<X, ?> a;
286:
287: cz.cvut.kbss.jopa.model.metamodel.Type<?> type;
288:
289: if (ManagedClassProcessor.isManagedType(fieldValueCls)) {
290: type = metamodelBuilder.getEntityClass(fieldValueCls);
291: } else {
292: type = BasicTypeImpl.get(fieldValueCls);
293: }
294:
295: Optional<ConverterWrapper<?, ?>> optionalConverterWrapper = context.getConverterResolver()
296: .resolveConverter(type);
297: ConverterWrapper<?, ?> converterWrapper = null;
298:
299: if (optionalConverterWrapper.isPresent()) {
300: converterWrapper = optionalConverterWrapper.get();
301: }
302:
303: if (Collection.class.isAssignableFrom(field.getType())) {
304: a = new PluralQueryAttributeImpl<>(query, sparqlAnnotation.enableReferencingAttributes(), field, et, fetchType, participationConstraints, type, field.getType(), converterWrapper);
305: } else if (Map.class.isAssignableFrom(field.getType())) {
306: throw new IllegalArgumentException("NOT YET SUPPORTED");
307: } else {
308: a = new SingularQueryAttributeImpl<>(query, sparqlAnnotation.enableReferencingAttributes(), field, et, fetchType, type, participationConstraints, converterWrapper);
309: }
310:
311: et.addDeclaredQueryAttribute(field.getName(), a);
312: }
313:
314: private AbstractAttribute<X, ?> createAttribute(PropertyInfo property, InferenceInfo
315: inference, PropertyAttributes propertyAttributes) {
316: final AbstractAttribute<X, ?> a;
317: if (property.getType().isAssignableFrom(Collection.class)) {
318: final AbstractPluralAttribute.PluralAttributeBuilder builder = CollectionAttributeImpl.builder(propertyAttributes)
319: .declaringType(et)
320: .propertyInfo(property)
321: .inferred(inference.inferred)
322: .includeExplicit(inference.includeExplicit);
323: context.getConverterResolver().resolveConverter(property, propertyAttributes).ifPresent(builder::converter);
324: a = builder.build();
325: } else if (property.getType().isAssignableFrom(List.class)) {
326: a = createListAttribute(property, inference, propertyAttributes);
327: } else if (property.getType().isAssignableFrom(Set.class)) {
328: final AbstractPluralAttribute.PluralAttributeBuilder builder = SetAttributeImpl.builder(propertyAttributes)
329: .declaringType(et)
330: .propertyInfo(property)
331: .inferred(inference.inferred)
332: .includeExplicit(inference.includeExplicit);
333: context.getConverterResolver().resolveConverter(property, propertyAttributes).ifPresent(builder::converter);
334: a = builder.build();
335: } else if (property.getType().isAssignableFrom(Map.class)) {
336: throw new IllegalArgumentException("NOT YET SUPPORTED");
337: } else {
338: final SingularAttributeImpl.SingularAttributeBuilder builder = SingularAttributeImpl.builder(propertyAttributes)
339: .declaringType(et)
340: .propertyInfo(property)
341: .inferred(inference.inferred)
342: .includeExplicit(inference.includeExplicit);
343: context.getConverterResolver().resolveConverter(property, propertyAttributes).ifPresent(builder::converter);
344: a = builder.build();
345: }
346: et.addDeclaredAttribute(property.getName(), a);
347: return a;
348: }
349:
350: private AbstractAttribute<X, ?> createListAttribute(PropertyInfo property, InferenceInfo
351: inference, PropertyAttributes propertyAttributes) {
352: if (property.getAnnotation(RDFCollection.class) != null) {
353: return createRdfCollectionAttribute(property, inference, propertyAttributes);
354: }
355: final Sequence os = property.getAnnotation(Sequence.class);
356: if (os == null) {
357: throw new MetamodelInitializationException("Expected Sequence annotation.");
358: }
359: final ListAttributeImpl.ListAttributeBuilder builder = ListAttributeImpl.builder(propertyAttributes)
360: .declaringType(et)
361: .propertyInfo(property)
362: .inferred(inference.inferred)
363: .includeExplicit(inference.includeExplicit)
364: .owlListClass(IRI.create(resolvePrefix(os.listClassIRI())))
365: .hasNextProperty(IRI.create(resolvePrefix(os.hasNextPropertyIRI())))
366: .hasContentsProperty(IRI.create(resolvePrefix(os.hasContentsPropertyIRI())))
367: .sequenceType(os.type());
368: context.getConverterResolver().resolveConverter(property, propertyAttributes).ifPresent(builder::converter);
369: return builder.build();
370: }
371:
372: private AbstractAttribute<X, ?> createRdfCollectionAttribute(PropertyInfo property, InferenceInfo inference,
373: PropertyAttributes propertyAttributes) {
374: final RDFCollectionAttribute.RDFCollectionAttributeBuilder builder = RDFCollectionAttribute.builder(propertyAttributes)
375: .declaringType(et)
376: .propertyInfo(property)
377: .inferred(inference.inferred)
378: .includeExplicit(inference.includeExplicit);
379: context.getConverterResolver().resolveConverter(property, propertyAttributes).ifPresent(builder::converter);
380: return builder.build();
381: }
382:
383: private String resolvePrefix(String value) {
384: return context.resolveNamespace(value);
385: }
386:
387: private void registerTypeReference(Attribute<X, ?> attribute) {
388: final Class<?> type = attribute.isCollection() ? ((PluralAttribute<X, ?, ?>) attribute).getBindableJavaType() : attribute.getJavaType();
389: if (metamodelBuilder.hasManagedType(type)) {
390: metamodelBuilder.registerTypeReference(type, et.getJavaType());
391: }
392: }
393:
394: private void processIdentifierField(Field field) {
395: final Id id = field.getAnnotation(Id.class);
396: assert id != null;
397:
398: mappingValidator.validateIdentifierType(field.getType());
399: et.setIdentifier(new IRIIdentifierImpl<>(et, field, id.generated()));
400: }
401:
402: private static boolean isIdentifierField(Field field) {
403: return field.getAnnotation(Id.class) != null;
404: }
405:
406: private static class InferenceInfo {
407: private final boolean inferred;
408: private final boolean includeExplicit;
409:
410: InferenceInfo(Inferred inferredAnnotation) {
411:• this.inferred = inferredAnnotation != null;
412:• this.includeExplicit = inferredAnnotation == null || inferredAnnotation.includeExplicit();
413: }
414: }
415: }