Skip to contentMethod: traverse(Object)
1: /*
2: * JB4JSON-LD
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.jsonld.serialization.traversal;
19:
20: import cz.cvut.kbss.jsonld.common.BeanAnnotationProcessor;
21: import cz.cvut.kbss.jsonld.common.BeanClassProcessor;
22: import cz.cvut.kbss.jsonld.common.IdentifierUtil;
23: import cz.cvut.kbss.jsonld.exception.MissingIdentifierException;
24:
25: import java.lang.reflect.Field;
26: import java.util.*;
27:
28: /**
29: * Traverses the provided object graph, visiting each instance and its fields, notifying visitors of these encounters.
30: * <p>
31: * Each object is visited only once, so circular references are not a problem.
32: * <p>
33: * The traversal algorithm is depth-first in nature.
34: */
35: public class ObjectGraphTraverser {
36:
37: private final SerializationContextFactory serializationContextFactory;
38:
39: private InstanceVisitor visitor;
40:
41: private final InstanceTypeResolver typeResolver = new InstanceTypeResolver();
42:
43: private boolean requireId = false;
44:
45: private final Map<Object, String> knownInstances = new IdentityHashMap<>();
46:
47: public ObjectGraphTraverser(SerializationContextFactory serializationContextFactory) {
48: this.serializationContextFactory = serializationContextFactory;
49: }
50:
51: public void setVisitor(InstanceVisitor visitor) {
52: this.visitor = Objects.requireNonNull(visitor);
53: }
54:
55: public void removeVisitor() {
56: this.visitor = null;
57: }
58:
59: public void traverse(Object instance) {
60: Objects.requireNonNull(instance);
61: traverse(serializationContextFactory.create(instance));
62: }
63:
64: public void traverse(SerializationContext<?> ctx) {
65: Objects.requireNonNull(ctx);
66: assert visitor != null;
67: if (ctx.getValue() instanceof Collection) {
68: traverseCollection((SerializationContext<? extends Collection<?>>) ctx);
69: } else {
70: traverseSingular(ctx);
71: }
72: }
73:
74: private void traverseCollection(SerializationContext<? extends Collection<?>> ctx) {
75: openCollection(ctx);
76: for (Object item : ctx.getValue()) {
77: if (item == null) {
78: continue;
79: }
80: traverseSingular(serializationContextFactory.create(item, ctx));
81: }
82: closeCollection(ctx);
83: }
84:
85: void traverseSingular(SerializationContext<?> ctx) {
86: if (ctx.getValue() == null) {
87: return;
88: }
89: final boolean shouldTraverse = visitInstance(ctx);
90: if (!shouldTraverse) {
91: return;
92: }
93: if (BeanClassProcessor.isIndividualType(ctx.getValue().getClass())) {
94: visitIndividual(ctx);
95: return;
96: }
97: final boolean firstEncounter = !knownInstances.containsKey(ctx.getValue());
98: openInstance(ctx);
99: visitIdentifier(ctx);
100: if (firstEncounter) {
101: visitTypes(ctx);
102: serializeFields(ctx);
103: serializePropertiesField(ctx);
104: }
105: closeInstance(ctx);
106: }
107:
108: private void serializeFields(SerializationContext<?> ctx) {
109: final Object instance = ctx.getValue();
110: final List<Field> fieldsToSerialize =
111: orderAttributesForSerialization(BeanAnnotationProcessor.getSerializableFields(instance),
112: BeanAnnotationProcessor.getAttributeOrder(instance.getClass()));
113: for (Field f : fieldsToSerialize) {
114: if (shouldSkipFieldSerialization(f)) {
115: continue;
116: }
117: Object value = BeanClassProcessor.getFieldValue(f, instance);
118: final SerializationContext<?> fieldCtx = serializationContextFactory.createForAttribute(f, value, ctx);
119: visitAttribute(fieldCtx);
120: }
121: }
122:
123: private boolean shouldSkipFieldSerialization(Field f) {
124: return BeanAnnotationProcessor.isInstanceIdentifier(f) || BeanAnnotationProcessor.isPropertiesField(
125: f) || BeanAnnotationProcessor.isTypesField(f);
126: }
127:
128: private List<Field> orderAttributesForSerialization(List<Field> fields, String[] ordering) {
129: final List<Field> result = new ArrayList<>(fields.size());
130: for (String item : ordering) {
131: final Iterator<Field> it = fields.iterator();
132: while (it.hasNext()) {
133: final Field f = it.next();
134: if (f.getName().equals(item)) {
135: it.remove();
136: result.add(f);
137: break;
138: }
139: }
140: }
141: result.addAll(fields);
142: return result;
143: }
144:
145: private void serializePropertiesField(SerializationContext<?> ctx) {
146: final Object instance = ctx.getValue();
147: if (!BeanAnnotationProcessor.hasPropertiesField(instance.getClass())) {
148: return;
149: }
150: final Field propertiesField = BeanAnnotationProcessor.getPropertiesField(instance.getClass());
151: final Object value = BeanClassProcessor.getFieldValue(propertiesField, instance);
152: if (value == null) {
153: return;
154: }
155: assert value instanceof Map;
156: new PropertiesTraverser(this)
157: .traverseProperties(
158: serializationContextFactory.createForProperties(propertiesField, (Map<?, ?>) value, ctx));
159: }
160:
161: public boolean visitInstance(SerializationContext<?> ctx) {
162: return visitor.visitObject(ctx);
163: }
164:
165: public void visitIndividual(SerializationContext<?> ctx) {
166: visitor.visitIndividual(ctx);
167: }
168:
169: public void openInstance(SerializationContext<?> ctx) {
170: if (!BeanClassProcessor.isIdentifierType(ctx.getValue().getClass())) {
171: final String identifier = resolveIdentifier(ctx.getValue());
172: knownInstances.put(ctx.getValue(), identifier);
173: }
174: visitor.openObject(ctx);
175: }
176:
177: private String resolveIdentifier(Object instance) {
178: final Optional<Object> extractedId = BeanAnnotationProcessor.getInstanceIdentifier(instance);
179: if (!extractedId.isPresent() && requireId) {
180: throw MissingIdentifierException.create(instance);
181: }
182: return extractedId.orElseGet(() -> knownInstances.containsKey(instance) ? knownInstances.get(instance) :
183: IdentifierUtil.generateBlankNodeId()).toString();
184: }
185:
186: public void closeInstance(SerializationContext<?> ctx) {
187: visitor.closeObject(ctx);
188: }
189:
190: public void visitIdentifier(SerializationContext<?> ctx) {
191: final Object identifier = ctx.getValue();
192: final Class<?> idCls = identifier.getClass();
193: final String id;
194: final SerializationContext<String> idContext;
195: id = resolveIdentifier(identifier);
196: idContext = serializationContextFactory.createForIdentifier(
197: BeanAnnotationProcessor.getIdentifierField(idCls).orElse(null), id, ctx);
198: knownInstances.put(identifier, id);
199: visitor.visitIdentifier(idContext);
200: }
201:
202: public void visitTypes(SerializationContext<?> ctx) {
203: final Object instance = ctx.getValue();
204: final Set<String> resolvedTypes = typeResolver.resolveTypes(instance);
205: assert !resolvedTypes.isEmpty();
206: final SerializationContext<Set<String>> typesContext = serializationContextFactory.createForTypes(
207: BeanAnnotationProcessor.getTypesField(instance.getClass()).orElse(null), resolvedTypes, ctx);
208: visitor.visitTypes(typesContext);
209: }
210:
211: public void visitAttribute(SerializationContext<?> ctx) {
212: visitor.visitAttribute(ctx);
213: }
214:
215: public void openCollection(SerializationContext<? extends Collection<?>> ctx) {
216: visitor.openCollection(ctx);
217: }
218:
219: public void closeCollection(SerializationContext<?> ctx) {
220: visitor.closeCollection(ctx);
221: }
222:
223: public void setRequireId(boolean requireId) {
224: this.requireId = requireId;
225: }
226: }