Skip to content

Method: extractFieldValueFromInstance(Object)

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.oom;
19:
20: import cz.cvut.kbss.jopa.model.MultilingualString;
21: import cz.cvut.kbss.jopa.model.descriptors.Descriptor;
22: import cz.cvut.kbss.jopa.model.metamodel.AbstractAttribute;
23: import cz.cvut.kbss.jopa.model.metamodel.AbstractPluralAttribute;
24: import cz.cvut.kbss.jopa.model.metamodel.Attribute;
25: import cz.cvut.kbss.jopa.model.metamodel.CollectionType;
26: import cz.cvut.kbss.jopa.model.metamodel.EntityType;
27: import cz.cvut.kbss.jopa.model.metamodel.FieldSpecification;
28: import cz.cvut.kbss.jopa.model.metamodel.Identifier;
29: import cz.cvut.kbss.jopa.model.metamodel.ListAttribute;
30: import cz.cvut.kbss.jopa.model.metamodel.ListAttributeImpl;
31: import cz.cvut.kbss.jopa.model.metamodel.PropertiesSpecification;
32: import cz.cvut.kbss.jopa.model.metamodel.SingularAttribute;
33: import cz.cvut.kbss.jopa.model.metamodel.TypesSpecification;
34: import cz.cvut.kbss.jopa.utils.EntityPropertiesUtils;
35: import cz.cvut.kbss.ontodriver.model.Assertion;
36: import cz.cvut.kbss.ontodriver.model.Axiom;
37: import cz.cvut.kbss.ontodriver.model.AxiomImpl;
38: import cz.cvut.kbss.ontodriver.model.NamedResource;
39: import cz.cvut.kbss.ontodriver.model.Value;
40:
41: import java.net.URI;
42: import java.util.Collection;
43: import java.util.Set;
44: import java.util.stream.Collectors;
45:
46: /**
47: * @param <T> The attribute specification type, e.g. {@link SingularAttribute}, {@link ListAttribute}
48: * @param <X> Entity class
49: */
50: abstract class FieldStrategy<T extends FieldSpecification<? super X, ?>, X> {
51:
52: protected static final Object LAZILY_LOADED_REFERENCE_PLACEHOLDER = new Object();
53:
54: final EntityType<X> et;
55: final T attribute;
56: final Descriptor entityDescriptor;
57: final EntityMappingHelper mapper;
58: ReferenceSavingResolver referenceSavingResolver;
59:
60: FieldStrategy(EntityType<X> et, T att, Descriptor entityDescriptor, EntityMappingHelper mapper) {
61: this.et = et;
62: this.attribute = att;
63: this.entityDescriptor = entityDescriptor;
64: this.mapper = mapper;
65: }
66:
67: static <X> FieldStrategy<? extends FieldSpecification<? super X, ?>, X> createFieldStrategy(EntityType<X> et,
68: FieldSpecification<? super X, ?> att,
69: Descriptor entityDescriptor,
70: EntityMappingHelper mapper) {
71: if (att.equals(et.getIdentifier())) {
72: return new IdentifierFieldStrategy<>(et, (Identifier<? super X, ?>) att, entityDescriptor, mapper);
73: }
74: if (att instanceof TypesSpecification) {
75: return new TypesFieldStrategy<>(et, (TypesSpecification<? super X, ?>) att, entityDescriptor, mapper);
76: } else if (att instanceof PropertiesSpecification) {
77: return new PropertiesFieldStrategy<>(et, (PropertiesSpecification<? super X, ?, ?, ?>) att,
78: entityDescriptor, mapper);
79: }
80: final AbstractAttribute<? super X, ?> attribute = (AbstractAttribute<? super X, ?>) att;
81: if (attribute.isCollection()) {
82: switch (attribute.getPersistentAttributeType()) {
83: case ANNOTATION:
84: return createPluralAnnotationPropertyStrategy(et,
85: (AbstractPluralAttribute<? super X, ?, ?>) attribute,
86: entityDescriptor, mapper);
87: case DATA:
88: return createPluralDataPropertyStrategy(et, (AbstractPluralAttribute<? super X, ?, ?>) attribute,
89: entityDescriptor, mapper);
90: case OBJECT:
91: return createPluralObjectPropertyStrategy(et, (AbstractPluralAttribute<? super X, ?, ?>) attribute,
92: entityDescriptor, mapper);
93: default:
94: break;
95: }
96: } else {
97: switch (attribute.getPersistentAttributeType()) {
98: case ANNOTATION:
99: return createSingularAnnotationPropertyStrategy(et, attribute, entityDescriptor, mapper);
100: case DATA:
101: return createSingularDataPropertyStrategy(et, attribute, entityDescriptor, mapper);
102: case OBJECT:
103: return new SingularObjectPropertyStrategy<>(et, attribute, entityDescriptor, mapper);
104: default:
105: break;
106: }
107: }
108: // Shouldn't happen
109: throw new IllegalArgumentException();
110: }
111:
112: private static <Y> FieldStrategy<? extends FieldSpecification<? super Y, ?>, Y> createPluralAnnotationPropertyStrategy(
113: EntityType<Y> et, AbstractPluralAttribute<? super Y, ?, ?> attribute, Descriptor descriptor,
114: EntityMappingHelper mapper) {
115: if (MultilingualString.class.equals(attribute.getElementType().getJavaType())) {
116: return new PluralMultilingualStringFieldStrategy<>(et,
117: (AbstractPluralAttribute<? super Y, ?, MultilingualString>) attribute,
118: descriptor, mapper);
119: } else {
120: return new PluralAnnotationPropertyStrategy<>(et, attribute, descriptor, mapper);
121: }
122: }
123:
124: private static <Y> FieldStrategy<? extends FieldSpecification<? super Y, ?>, Y> createPluralDataPropertyStrategy(
125: EntityType<Y> et, AbstractPluralAttribute<? super Y, ?, ?> attribute, Descriptor descriptor,
126: EntityMappingHelper mapper) {
127: if (attribute.getCollectionType() == CollectionType.LIST) {
128: return createListPropertyStrategy(et, (ListAttributeImpl<? super Y, ?>) attribute, descriptor, mapper);
129: }
130: if (MultilingualString.class.equals(attribute.getElementType().getJavaType())) {
131: return new PluralMultilingualStringFieldStrategy<>(et,
132: (AbstractPluralAttribute<? super Y, ?, MultilingualString>) attribute,
133: descriptor, mapper);
134: } else {
135: return new PluralDataPropertyStrategy<>(et, attribute, descriptor, mapper);
136: }
137: }
138:
139: private static <Y> FieldStrategy<? extends FieldSpecification<? super Y, ?>, Y> createPluralObjectPropertyStrategy(
140: EntityType<Y> et, AbstractPluralAttribute<? super Y, ?, ?> attribute, Descriptor descriptor,
141: EntityMappingHelper mapper) {
142: return switch (attribute.getCollectionType()) {
143: case LIST -> createListPropertyStrategy(et, (ListAttributeImpl<? super Y, ?>) attribute, descriptor,
144: mapper);
145: case COLLECTION, SET -> new SimpleSetPropertyStrategy<>(et, attribute, descriptor, mapper);
146: default -> throw new UnsupportedOperationException(
147: "Unsupported plural attribute collection type " + attribute.getCollectionType());
148: };
149: }
150:
151: private static <Y> FieldStrategy<? extends FieldSpecification<? super Y, ?>, Y> createListPropertyStrategy(
152: EntityType<Y> et, ListAttributeImpl<? super Y, ?> attribute, Descriptor descriptor,
153: EntityMappingHelper mapper) {
154: switch (attribute.getSequenceType()) {
155: case referenced:
156: if (attribute.getPersistentAttributeType() == Attribute.PersistentAttributeType.OBJECT) {
157: return new ReferencedListPropertyStrategy<>(et, attribute, descriptor, mapper);
158: } else {
159: return new ReferencedListDataPropertyStrategy<>(et, attribute, descriptor, mapper);
160: }
161: case simple:
162: return new SimpleListPropertyStrategy<>(et, attribute, descriptor, mapper);
163: default:
164: throw new UnsupportedOperationException(
165: "Unsupported list attribute sequence type " + attribute.getSequenceType());
166: }
167: }
168:
169: private static <X> FieldStrategy<? extends FieldSpecification<? super X, ?>, X> createSingularDataPropertyStrategy(
170: EntityType<X> et, AbstractAttribute<? super X, ?> attribute, Descriptor descriptor,
171: EntityMappingHelper mapper) {
172: if (MultilingualString.class.equals(attribute.getJavaType())) {
173: return new SingularMultilingualStringFieldStrategy<>(et,
174: (AbstractAttribute<? super X, MultilingualString>) attribute,
175: descriptor, mapper);
176: } else {
177: return new SingularDataPropertyStrategy<>(et, attribute, descriptor, mapper);
178: }
179: }
180:
181: private static <X> FieldStrategy<? extends FieldSpecification<? super X, ?>, X> createSingularAnnotationPropertyStrategy(
182: EntityType<X> et, AbstractAttribute<? super X, ?> attribute, Descriptor descriptor,
183: EntityMappingHelper mapper) {
184: if (MultilingualString.class.equals(attribute.getJavaType())) {
185: return new SingularMultilingualStringFieldStrategy<>(et,
186: (AbstractAttribute<? super X, MultilingualString>) attribute,
187: descriptor, mapper);
188: } else {
189: return new SingularAnnotationPropertyStrategy<>(et, attribute, descriptor, mapper);
190: }
191: }
192:
193: void setReferenceSavingResolver(ReferenceSavingResolver referenceSavingResolver) {
194: this.referenceSavingResolver = referenceSavingResolver;
195: }
196:
197: /**
198: * Sets the specified value on the specified instance, the field is taken from the attribute represented by this
199: * strategy. </p>
200: * <p>
201: * Note that this method assumes the value and the field are of compatible types, no check is done here.
202: */
203: void setValueOnInstance(Object instance, Object value) {
204: EntityPropertiesUtils.setFieldValue(attribute.getJavaField(), instance, value);
205: }
206:
207: /**
208: * Extracts the attribute value from the specified instance. </p>
209: *
210: * @return Attribute value, possibly {@code null}
211: */
212: Object extractFieldValueFromInstance(Object instance) {
213: return EntityPropertiesUtils.getAttributeValue(attribute, instance);
214: }
215:
216: <E> URI resolveValueIdentifier(E instance, EntityType<E> valEt) {
217: URI id = EntityPropertiesUtils.getIdentifier(instance, valEt);
218: if (id == null) {
219: id = mapper.generateIdentifier(valEt);
220: EntityPropertiesUtils.setIdentifier(id, instance, valEt);
221: }
222: return id;
223: }
224:
225: /**
226: * Gets the context to which this attribute assertions are written.
227: * <p>
228: * Note that this may not (and in case of object properties usually won't) be the context containing the target
229: * object. Rather, it will be the context of the owner entity itself (depending on whether assertions are stored in
230: * subject context or not).
231: *
232: * @return Attribute assertion context
233: * @see Descriptor
234: */
235: URI getAttributeWriteContext() {
236: return entityDescriptor.getSingleAttributeContext(attribute).orElse(null);
237: }
238:
239: /**
240: * Adds value from the specified axioms to this strategy. </p>
241: * <p>
242: * The value(s) is/are then set on entity field using {@link #buildInstanceFieldValue(Object)}.
243: *
244: * @param ax Axiom to extract value from
245: */
246: abstract void addAxiomValue(Axiom<?> ax);
247:
248: /**
249: * Adds value from axiom in case the field is lazily loaded.
250: * <p>
251: * This allows skipping loading of references of lazily loaded attributes while providing info whether there
252: * actually are any values to load eventually.
253: *
254: * @param ax Axiom to extract value from
255: */
256: void lazilyAddAxiomValue(Axiom<?> ax) {
257: addAxiomValue(ax);
258: }
259:
260: /**
261: * Sets instance field from values gathered in this strategy.
262: * <p>
263: * Note that if this strategy represents a plural field and there were no values added from axioms, the field value
264: * should be set to an empty instance of the collection/map corresponding to the target field type.
265: *
266: * @param instance The instance to receive the field value
267: * @throws IllegalArgumentException Access error
268: */
269: abstract void buildInstanceFieldValue(Object instance);
270:
271: /**
272: * Checks whether any values have been added from axioms.
273: *
274: * @return {@code true} if this strategy holds values added from axioms, {@code false} otherwise
275: * @see #addAxiomValue(Axiom)
276: */
277: abstract boolean hasValue();
278:
279: /**
280: * Extracts values of field represented by this strategy from the specified instance and adds them to the specified
281: * value gatherer.
282: *
283: * @param instance The instance to extract values from
284: * @param valueBuilder Builder into which the attribute value(s) are extracted
285: * @throws IllegalArgumentException Access error
286: */
287: abstract void buildAxiomValuesFromInstance(X instance, AxiomValueGatherer valueBuilder);
288:
289: /**
290: * Extracts values of field represented by this strategy from the specified instance and returns axioms representing
291: * them.
292: *
293: * @param instance The instance to extract values from
294: * @return Set of axioms
295: * @throws IllegalArgumentException Access error
296: */
297: abstract Set<Axiom<?>> buildAxiomsFromInstance(X instance);
298:
299: /**
300: * Creates property assertion appropriate for the attribute represented by this strategy.
301: *
302: * @return Property assertion
303: */
304: abstract Assertion createAssertion();
305:
306: /**
307: * Transforms the specified attribute value to an axiom {@link Value}.
308: *
309: * @param value Value to transform
310: * @return Axiom value
311: */
312: abstract Collection<Value<?>> toAxiomValue(Object value);
313:
314: /**
315: * Returns only values that are not inferred in the repository for the specified subject.
316: * <p>
317: * This method goes through the specified values and if the property represented by this strategy can contain
318: * inferred values (i.e., {@link FieldSpecification#isInferred()} returns true), it returns only those values that
319: * are NOT inferred in the repository.
320: * <p>
321: * The reasoning of this method is that the current implementation of a property values update is done by removing
322: * all its values as asserting new ones extracted from the entity. However, if an attribute value is a mix of
323: * inferred and asserted values, this will basically assert values that are already inferred, which is not correct.
324: * This method thus removes inferred values from the values passed to the OntoDriver for saving. Note that there is
325: * a minor caveat in that one is not able to intentionally assert already inferred values this way, but it is
326: * considered not important to work resolve at the moment.
327: *
328: * @param subject Subject whose property values are examined
329: * @param values Values to filter
330: * @return Asserted values
331: */
332: protected Set<Value<?>> filterOutInferredValues(NamedResource subject, Set<Value<?>> values) {
333: if (!attribute.isInferred()) {
334: return values;
335: }
336: final Assertion assertion = createAssertion();
337: final URI ctx = getAttributeWriteContext();
338: return values.stream().filter(v -> !mapper.isInferred(new AxiomImpl<>(subject, assertion, v), ctx))
339: .collect(Collectors.toSet());
340: }
341:
342: @Override
343: public String toString() {
344: return getClass().getSimpleName() + "{" +
345: "attribute=" + attribute +
346: '}';
347: }
348: }