Skip to content

Method: reduceToMostSpecificSubclasses(List)

1: /**
2: * Copyright (C) 2022 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.jsonld.deserialization.util;
16:
17: import cz.cvut.kbss.jopa.model.annotations.OWLClass;
18: import cz.cvut.kbss.jsonld.common.BeanAnnotationProcessor;
19: import cz.cvut.kbss.jsonld.exception.AmbiguousTargetTypeException;
20: import cz.cvut.kbss.jsonld.exception.TargetTypeException;
21: import org.slf4j.Logger;
22: import org.slf4j.LoggerFactory;
23:
24: import java.lang.reflect.Modifier;
25: import java.util.ArrayList;
26: import java.util.Collection;
27: import java.util.List;
28: import java.util.stream.Collectors;
29:
30: /**
31: * Resolves the type of instance into which a JSON-LD object will be deserialized.
32: */
33: public class TargetClassResolver {
34:
35: private static final Logger LOG = LoggerFactory.getLogger(TargetClassResolver.class);
36:
37: private final TypeMap typeMap;
38:
39: private final TargetClassResolverConfig config;
40:
41: public TargetClassResolver(TypeMap typeMap) {
42: this.typeMap = typeMap;
43: this.config = new TargetClassResolverConfig();
44: }
45:
46: public TargetClassResolver(TypeMap typeMap, TargetClassResolverConfig config) {
47: this.typeMap = typeMap;
48: this.config = config;
49: }
50:
51: /**
52: * Resolves object deserialization target class based on the specified type info.
53: *
54: * @param <T> The type of the expected target class
55: * @param expectedClass Expected class as specified by deserialization return type of field type
56: * @param types Types of the JSON-LD object to deserialize
57: * @return Resolved target class. It has to be a subtype of the {@code expectedClass}
58: * @throws TargetTypeException If the resulting candidate is not assignable to the expected class or it cannot be
59: * determined
60: */
61: public <T> Class<? extends T> getTargetClass(Class<T> expectedClass, Collection<String> types) {
62: if (types.isEmpty() && config.shouldAllowAssumingTargetType()) {
63: LOG.trace("Assuming target type to be " + expectedClass);
64: return expectedClass;
65: }
66: final List<Class<?>> candidates = getTargetClassCandidates(types);
67: final Class<?> targetCandidate;
68: reduceTargetClassCandidates(expectedClass, candidates);
69: final List<Class<?>> reducedCandidates = new ArrayList<>(candidates);
70: reduceToMostSpecificSubclasses(candidates);
71: if (candidates.isEmpty()) {
72: if (doesExpectedClassMatchesTypes(expectedClass, types)) {
73: targetCandidate = expectedClass;
74: } else {
75: throw new TargetTypeException(
76: "Neither " + expectedClass + " nor any of its subclasses matches the types " + types + ".");
77: }
78: } else {
79: targetCandidate = selectFinalTargetClass(candidates, reducedCandidates, types);
80: }
81: assert expectedClass.isAssignableFrom(targetCandidate);
82: return (Class<? extends T>) targetCandidate;
83: }
84:
85: private List<Class<?>> getTargetClassCandidates(Collection<String> types) {
86: return types.stream().flatMap(t -> typeMap.get(t).stream()).collect(Collectors.toList());
87: }
88:
89: private void reduceTargetClassCandidates(Class<?> expectedClass, List<Class<?>> candidates) {
90: candidates.removeIf(c -> !expectedClass.isAssignableFrom(c) || Modifier.isAbstract(c.getModifiers()));
91: }
92:
93: private void reduceToMostSpecificSubclasses(List<Class<?>> candidates) {
94: candidates.removeIf(cls -> candidates.stream().anyMatch(c -> !cls.equals(c) && cls.isAssignableFrom(c)));
95: }
96:
97: private Class<?> selectFinalTargetClass(List<Class<?>> mostSpecificCandidates, List<Class<?>> candidates,
98: Collection<String> types) {
99: assert mostSpecificCandidates.size() > 0;
100: if (mostSpecificCandidates.size() > 1) {
101: if (!config.isOptimisticTypeResolutionEnabled()) {
102: throw ambiguousTargetType(types, mostSpecificCandidates);
103: }
104: if (config.shouldPreferSuperclass()) {
105: return selectTargetClassWithSuperclassPreference(mostSpecificCandidates, candidates);
106: }
107: }
108: return pickOne(mostSpecificCandidates);
109: }
110:
111: private static Class<?> pickOne(List<Class<?>> candidates) {
112: return candidates.size() == 1 ? candidates.get(0) :
113: candidates.stream().filter(BeanAnnotationProcessor::hasPropertiesField).findFirst()
114: .orElse(candidates.get(0));
115: }
116:
117: private Class<?> selectTargetClassWithSuperclassPreference(List<Class<?>> mostSpecificCandidates,
118: List<Class<?>> candidates) {
119: candidates.removeAll(mostSpecificCandidates);
120: reduceToMostGeneralSuperclasses(candidates);
121: return pickOne(candidates);
122: }
123:
124: private void reduceToMostGeneralSuperclasses(List<Class<?>> candidates) {
125: candidates.removeIf(cls -> candidates.stream().anyMatch(c -> !cls.equals(c) && c.isAssignableFrom(cls)));
126: }
127:
128: private static AmbiguousTargetTypeException ambiguousTargetType(Collection<String> types,
129: List<Class<?>> candidates) {
130: return new AmbiguousTargetTypeException(
131: "Object with types " + types + " matches multiple equivalent target classes: " + candidates);
132: }
133:
134: private boolean doesExpectedClassMatchesTypes(Class<?> expectedClass, Collection<String> types) {
135: final OWLClass owlClass = expectedClass.getDeclaredAnnotation(OWLClass.class);
136: return owlClass != null && types.contains(owlClass.iri());
137: }
138: }