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