Skip to contentMethod: propertyDiff(Map, Map)
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.exceptions.InvalidAssertionIdentifierException;
21: import cz.cvut.kbss.jopa.model.IRI;
22: import cz.cvut.kbss.jopa.model.descriptors.Descriptor;
23: import cz.cvut.kbss.jopa.model.metamodel.Attribute;
24: import cz.cvut.kbss.jopa.model.metamodel.EntityType;
25: import cz.cvut.kbss.jopa.model.metamodel.PropertiesSpecification;
26: import cz.cvut.kbss.jopa.utils.EntityPropertiesUtils;
27: import cz.cvut.kbss.jopa.utils.IdentifierTransformer;
28: import cz.cvut.kbss.jopa.vocabulary.RDF;
29: import cz.cvut.kbss.ontodriver.model.*;
30:
31: import java.net.URI;
32: import java.util.*;
33: import java.util.Map.Entry;
34: import java.util.stream.Collectors;
35:
36: class PropertiesFieldStrategy<X> extends FieldStrategy<PropertiesSpecification<? super X, ?, ?, ?>, X> {
37:
38: private final PropertiesValueHolder value = new PropertiesValueHolder();
39:
40: PropertiesFieldStrategy(EntityType<X> et, PropertiesSpecification<? super X, ?, ?, ?> att,
41: Descriptor descriptor, EntityMappingHelper mapper) {
42: super(et, att, descriptor, mapper);
43: }
44:
45: @Override
46: void addAxiomValue(Axiom<?> ax) {
47: if (shouldSkipAxiom(ax)) {
48: return;
49: }
50: value.addValue(ax);
51: }
52:
53: private boolean shouldSkipAxiom(Axiom<?> ax) {
54: final String property = ax.getAssertion().getIdentifier().toString();
55: // This is class assertion for entities without types
56: return property.equals(RDF.TYPE) || isMappedAttribute(ax);
57: }
58:
59: private boolean isMappedAttribute(Axiom<?> ax) {
60: // TODO This is too simple, in case the assertion corresponds to a mapped attribute,
61: // we also have to check whether the map is suitable for the attribute. If not, it belongs to properties
62: final IRI propertyAsIri = IRI.create(ax.getAssertion().getIdentifier().toString());
63: for (Attribute<?, ?> att : et.getAttributes()) {
64: if (att.getIRI().equals(propertyAsIri)) {
65: return true;
66: }
67: }
68: return false;
69: }
70:
71: @Override
72: boolean hasValue() {
73: return !value.map.isEmpty();
74: }
75:
76: @Override
77: void buildInstanceFieldValue(Object instance) {
78: setValueOnInstance(instance, value.getValue());
79: }
80:
81: @Override
82: void buildAxiomValuesFromInstance(X instance, AxiomValueGatherer valueBuilder) {
83: final Object val = extractFieldValueFromInstance(instance);
84: final X original = mapper.getOriginalInstance(instance);
85: if (val == null) {
86: if (original == null) {
87: return;
88: }
89: final Map<?, Set<?>> origProps = (Map<?, Set<?>>) extractFieldValueFromInstance(original);
90: if (origProps == null || origProps.isEmpty()) {
91: return;
92: }
93: valueBuilder.removeProperties(prepareProperties(origProps), getAttributeWriteContext());
94: return;
95: }
96: assert val instanceof Map;
97: final Map<?, Set<?>> props = (Map<?, Set<?>>) val;
98: if (original == null) {
99: valueBuilder.addProperties(prepareProperties(props), getAttributeWriteContext());
100: } else {
101: final Map<?, Set<?>> origProps = (Map<?, Set<?>>) extractFieldValueFromInstance(original);
102: final Map<Assertion, Set<Value<?>>> toRemove = resolvePropertiesToRemove(props, origProps);
103: if (!toRemove.isEmpty()) {
104: valueBuilder.removeProperties(toRemove, getAttributeWriteContext());
105: }
106: final Map<Assertion, Set<Value<?>>> toAdd = resolvePropertiesToAdd(props, origProps);
107: if (!toAdd.isEmpty()) {
108: valueBuilder.addProperties(toAdd, getAttributeWriteContext());
109: }
110: }
111: }
112:
113: private Map<Assertion, Set<Value<?>>> prepareProperties(Map<?, Set<?>> props) {
114: final Map<Assertion, Set<Value<?>>> result = new HashMap<>(props.size());
115: props.entrySet().stream().filter(e -> e.getKey() != null && e.getValue() != null)
116: .forEach(e -> result.put(propertyToAssertion(e.getKey()), objectsToValues(e.getValue())));
117: return result;
118: }
119:
120: private Assertion propertyToAssertion(Object property) {
121: try {
122: return Assertion
123: .createPropertyAssertion(EntityPropertiesUtils.getValueAsURI(property), attribute.isInferred());
124: } catch (IllegalArgumentException e) {
125: throw new InvalidAssertionIdentifierException(property + " is not a valid identifier.", e);
126: }
127: }
128:
129: private static Set<Value<?>> objectsToValues(Collection<?> strValues) {
130: final Set<Value<?>> ontoValues = new HashSet<>(strValues.size());
131: ontoValues.addAll(strValues.stream().filter(Objects::nonNull).map(Value::new).collect(Collectors.toList()));
132: return ontoValues;
133: }
134:
135: private Map<Assertion, Set<Value<?>>> resolvePropertiesToRemove(Map<?, Set<?>> current, Map<?, Set<?>> original) {
136: return propertyDiff(original, current);
137: }
138:
139: /**
140: * The difference is counted as the properties and values which were in base but are not in the updated version.
141: */
142: private Map<Assertion, Set<Value<?>>> propertyDiff(Map<?, Set<?>> base, Map<?, Set<?>> updated) {
143:• if (base == null || base.isEmpty()) {
144: return Collections.emptyMap();
145: }
146: final Map<Assertion, Set<Value<?>>> diff = new HashMap<>();
147:• if (updated == null || updated.isEmpty()) {
148: diff.putAll(createAssertionsForAll(base));
149: } else {
150:• for (Entry<?, Set<?>> entry : base.entrySet()) {
151: final Object key = entry.getKey();
152:• if (!updated.containsKey(key) || updated.get(key) == null || updated.get(key).isEmpty()) {
153: // All values of the property are missing
154: diff.put(propertyToAssertion(key), objectsToValues(entry.getValue()));
155: } else {
156: final Set<?> currentValues = updated.get(key);
157: // Check which property values are missing
158: final List<?> removed =
159: entry.getValue().stream().filter(origVal -> !currentValues.contains(origVal))
160: .collect(Collectors.toList());
161:• if (!removed.isEmpty()) {
162: diff.put(propertyToAssertion(key), objectsToValues(removed));
163: }
164: }
165: }
166: }
167: return diff;
168: }
169:
170: private Map<Assertion, Set<Value<?>>> createAssertionsForAll(Map<?, Set<?>> map) {
171: final Map<Assertion, Set<Value<?>>> diff = new HashMap<>(map.size());
172: map.forEach((key, val) -> diff.put(propertyToAssertion(key), objectsToValues(val)));
173: return diff;
174: }
175:
176: private Map<Assertion, Set<Value<?>>> resolvePropertiesToAdd(Map<?, Set<?>> current, Map<?, Set<?>> original) {
177: return propertyDiff(current, original);
178: }
179:
180: @Override
181: Set<Axiom<?>> buildAxiomsFromInstance(X instance) {
182: final Object val = extractFieldValueFromInstance(instance);
183: assert val instanceof Map || val == null;
184: final Map<?, Set<?>> values = (Map<?, Set<?>>) val;
185: if (values == null || values.isEmpty()) {
186: return Collections.emptySet();
187: }
188: final Set<Axiom<?>> result = new HashSet<>();
189: final NamedResource subject = NamedResource.create(EntityPropertiesUtils.getIdentifier(instance, et));
190: for (Entry<?, Set<?>> e : values.entrySet()) {
191: final Assertion assertion = propertyToAssertion(e.getKey());
192: e.getValue().stream().filter(Objects::nonNull).map(Value::new)
193: .forEach(v -> result.add(new AxiomImpl<>(subject, assertion, v)));
194: }
195: return result;
196: }
197:
198: @Override
199: Assertion createAssertion() {
200: return Assertion.createUnspecifiedPropertyAssertion(attribute.isInferred());
201: }
202:
203: @Override
204: Collection<Value<?>> toAxiomValue(Object value) {
205: return Collections.singleton(new Value<>(value));
206: }
207:
208: private class PropertiesValueHolder {
209:
210: private final Map<Object, Set<Object>> map = new HashMap<>();
211:
212: void addValue(Axiom<?> ax) {
213: final Object property = mapPropertyIdentifier(ax.getAssertion());
214: final Object val = mapPropertyValue(ax.getValue());
215: if (!map.containsKey(property)) {
216: map.put(property, new HashSet<>());
217: }
218: map.get(property).add(val);
219: }
220:
221: private Object mapPropertyIdentifier(Assertion a) {
222: final URI id = a.getIdentifier();
223: final Class<?> propertyIdType = PropertiesFieldStrategy.this.attribute.getPropertyIdentifierType();
224: assert IdentifierTransformer.isValidIdentifierType(propertyIdType);
225:
226: return IdentifierTransformer.transformToIdentifier(id, propertyIdType);
227: }
228:
229: private Object mapPropertyValue(Value<?> value) {
230: final Class<?> propertyValueType = PropertiesFieldStrategy.this.attribute.getPropertyValueType();
231: Object val = value.getValue();
232: if (val.getClass().equals(NamedResource.class)) {
233: // Value is an object property value
234: val = ((NamedResource) val).getIdentifier();
235: }
236: if (propertyValueType.isAssignableFrom(val.getClass())) {
237: return val;
238: } else {
239: // String value type was always default
240: if (propertyValueType.equals(String.class)) {
241: return val.toString();
242: } else {
243: throw new IllegalArgumentException("Cannot return value " + val + " as type " + propertyValueType);
244: }
245: }
246: }
247:
248: Map<Object, Set<Object>> getValue() {
249: return map;
250: }
251: }
252: }