Skip to content

Method: ObjectGraphTraverser()

1: /**
2: * Copyright (C) 2020 Czech Technical University in Prague
3: * <p>
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: * <p>
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.serialization.traversal;
16:
17: import cz.cvut.kbss.jsonld.common.BeanAnnotationProcessor;
18: import cz.cvut.kbss.jsonld.common.BeanClassProcessor;
19: import cz.cvut.kbss.jsonld.common.IdentifierUtil;
20: import cz.cvut.kbss.jsonld.exception.JsonLdSerializationException;
21: import cz.cvut.kbss.jsonld.exception.MissingIdentifierException;
22:
23: import java.lang.reflect.Field;
24: import java.util.*;
25:
26: public class ObjectGraphTraverser {
27:
28: private final Set<InstanceVisitor> visitors = new HashSet<>(4);
29:
30: private final InstanceTypeResolver typeResolver = new InstanceTypeResolver();
31:
32: private boolean requireId = false;
33:
34: private Map<Object, String> knownInstances;
35:
36: public void addVisitor(InstanceVisitor visitor) {
37: Objects.requireNonNull(visitor);
38: visitors.add(visitor);
39: }
40:
41: public void removeVisitor(InstanceVisitor visitor) {
42: visitors.remove(visitor);
43: }
44:
45: private void resetKnownInstances() {
46: this.knownInstances = new IdentityHashMap<>();
47: }
48:
49: public void traverse(Object instance) {
50: Objects.requireNonNull(instance);
51: resetKnownInstances();
52: try {
53: if (instance instanceof Collection) {
54: traverseCollection(new SerializationContext<>((Collection<?>) instance));
55: } else {
56: traverseSingular(new SerializationContext<>(instance));
57: }
58: } catch (IllegalAccessException e) {
59: throw new JsonLdSerializationException("Unable to extract field value.", e);
60: }
61: }
62:
63: private void traverseCollection(SerializationContext<? extends Collection<?>> ctx) throws IllegalAccessException {
64: openCollection(ctx);
65: for (Object item : ctx.getValue()) {
66: traverseSingular(new SerializationContext<>(item));
67: }
68: closeCollection(ctx);
69: }
70:
71: void traverseSingular(SerializationContext<?> ctx) throws IllegalAccessException {
72: if (ctx.getValue() == null) {
73: return;
74: }
75: final boolean firstEncounter = !knownInstances.containsKey(ctx.getValue());
76: openInstance(ctx);
77: visitIdentifier(ctx.getValue());
78: if (!BeanClassProcessor.isIdentifierType(ctx.getValue().getClass()) && firstEncounter) {
79: visitTypes(ctx.getValue());
80: serializeFields(ctx.getValue());
81: serializePropertiesField(ctx.getValue());
82: }
83: closeInstance(ctx);
84: }
85:
86: private void serializeFields(Object instance) throws IllegalAccessException {
87: final List<Field> fieldsToSerialize =
88: orderAttributesForSerialization(BeanAnnotationProcessor.getSerializableFields(instance),
89: BeanAnnotationProcessor.getAttributeOrder(instance.getClass()));
90: for (Field f : fieldsToSerialize) {
91: if (BeanAnnotationProcessor.isInstanceIdentifier(f) || BeanAnnotationProcessor
92: .isPropertiesField(f) || BeanAnnotationProcessor.isTypesField(f)) {
93: continue;
94: }
95: Object value = BeanClassProcessor.getFieldValue(f, instance);
96: final SerializationContext<?> ctx =
97: new SerializationContext<>(BeanAnnotationProcessor.getAttributeIdentifier(f), f, value);
98: visitAttribute(ctx);
99: if (value != null && BeanAnnotationProcessor.isObjectProperty(f)) {
100: traverseObjectPropertyValue(ctx);
101: }
102: }
103: }
104:
105: private List<Field> orderAttributesForSerialization(List<Field> fields, String[] ordering) {
106: final List<Field> result = new ArrayList<>(fields.size());
107: for (String item : ordering) {
108: final Iterator<Field> it = fields.iterator();
109: while (it.hasNext()) {
110: final Field f = it.next();
111: if (f.getName().equals(item)) {
112: it.remove();
113: result.add(f);
114: break;
115: }
116: }
117: }
118: result.addAll(fields);
119: return result;
120: }
121:
122: private void traverseObjectPropertyValue(SerializationContext<?> ctx) throws IllegalAccessException {
123: if (ctx.getValue() instanceof Collection) {
124: final SerializationContext<Collection<?>> colContext = (SerializationContext<Collection<?>>) ctx;
125: openCollection(colContext);
126: for (Object elem : colContext.getValue()) {
127: traverseSingular(new SerializationContext<>(elem));
128: }
129: closeCollection(colContext);
130: } else if (ctx.getValue().getClass().isArray()) {
131: throw new JsonLdSerializationException("Arrays are not supported, yet.");
132: } else {
133: traverseSingular(ctx);
134: }
135: }
136:
137: private void serializePropertiesField(Object instance) {
138: if (!BeanAnnotationProcessor.hasPropertiesField(instance.getClass())) {
139: return;
140: }
141: final Field propertiesField = BeanAnnotationProcessor.getPropertiesField(instance.getClass());
142: final Object value = BeanClassProcessor.getFieldValue(propertiesField, instance);
143: if (value == null) {
144: return;
145: }
146: assert value instanceof Map;
147: new PropertiesTraverser(this)
148: .traverseProperties(new SerializationContext<>(propertiesField, (Map<?, ?>) value));
149: }
150:
151: public void openInstance(SerializationContext<?> ctx) {
152: if (!BeanClassProcessor.isIdentifierType(ctx.getValue().getClass())) {
153: final String identifier = resolveIdentifier(ctx.getValue());
154: knownInstances.put(ctx.getValue(), identifier);
155: }
156: visitors.forEach(v -> v.openObject(ctx));
157: }
158:
159: private String resolveIdentifier(Object instance) {
160: final Optional<Object> extractedId = BeanAnnotationProcessor.getInstanceIdentifier(instance);
161: if (!extractedId.isPresent() && requireId) {
162: throw MissingIdentifierException.create(instance);
163: }
164: return extractedId.orElseGet(() -> knownInstances.containsKey(instance) ? knownInstances.get(instance) :
165: IdentifierUtil.generateBlankNodeId()).toString();
166: }
167:
168: public void closeInstance(SerializationContext<?> ctx) {
169: visitors.forEach(v -> v.closeObject(ctx));
170: }
171:
172: public void visitIdentifier(Object instance) {
173: final String id;
174: if (BeanClassProcessor.isIdentifierType(instance.getClass())) {
175: id = instance.toString();
176: } else {
177: id = resolveIdentifier(instance);
178: knownInstances.put(instance, id);
179: }
180: final SerializationContext<String> idContext = new SerializationContext<>(id);
181: visitors.forEach(v -> v.visitIdentifier(idContext));
182: }
183:
184: public void visitTypes(Object instance) {
185: final Set<String> resolvedTypes = typeResolver.resolveTypes(instance);
186: assert !resolvedTypes.isEmpty();
187: final SerializationContext<Collection<String>> typesContext = new SerializationContext<>(resolvedTypes);
188: visitors.forEach(v -> v.visitTypes(typesContext));
189: }
190:
191: public void visitAttribute(SerializationContext<?> ctx) {
192: visitors.forEach(v -> v.visitAttribute(ctx));
193: }
194:
195: public void openCollection(SerializationContext<? extends Collection<?>> ctx) {
196: visitors.forEach(v -> v.openCollection(ctx));
197: }
198:
199: public void closeCollection(SerializationContext<?> ctx) {
200: visitors.forEach(v -> v.closeCollection(ctx));
201: }
202:
203: public void setRequireId(boolean requireId) {
204: this.requireId = requireId;
205: }
206: }