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