Skip to content

Method: removeVisitor()

1: /**
2: * Copyright (C) 2022 Czech Technical University in Prague
3: * <p>
4: * This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
5: * License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later
6: * version.
7: * <p>
8: * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
9: * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
10: * details. You should have received a copy of the GNU General Public License along with this program. If not, see
11: * <http://www.gnu.org/licenses/>.
12: */
13: package cz.cvut.kbss.jsonld.serialization.traversal;
14:
15: import cz.cvut.kbss.jsonld.common.BeanAnnotationProcessor;
16: import cz.cvut.kbss.jsonld.common.BeanClassProcessor;
17: import cz.cvut.kbss.jsonld.common.IdentifierUtil;
18: import cz.cvut.kbss.jsonld.exception.MissingIdentifierException;
19:
20: import java.lang.reflect.Field;
21: import java.util.*;
22:
23: /**
24: * Traverses the provided object graph, visiting each instance and its fields, notifying visitors of these encounters.
25: * <p>
26: * Each object is visited only once, so circular references are not a problem.
27: * <p>
28: * The traversal algorithm is depth-first in nature.
29: */
30: public class ObjectGraphTraverser {
31:
32: private final SerializationContextFactory serializationContextFactory;
33:
34: private InstanceVisitor visitor;
35:
36: private final InstanceTypeResolver typeResolver = new InstanceTypeResolver();
37:
38: private boolean requireId = false;
39:
40: private final Map<Object, String> knownInstances = new IdentityHashMap<>();
41:
42: public ObjectGraphTraverser(SerializationContextFactory serializationContextFactory) {
43: this.serializationContextFactory = serializationContextFactory;
44: }
45:
46: public void setVisitor(InstanceVisitor visitor) {
47: this.visitor = Objects.requireNonNull(visitor);
48: }
49:
50: public void removeVisitor() {
51: this.visitor = null;
52: }
53:
54: public void traverse(Object instance) {
55: Objects.requireNonNull(instance);
56: traverse(serializationContextFactory.create(instance));
57: }
58:
59: public void traverse(SerializationContext<?> ctx) {
60: Objects.requireNonNull(ctx);
61: assert visitor != null;
62: if (ctx.getValue() instanceof Collection) {
63: traverseCollection((SerializationContext<? extends Collection<?>>) ctx);
64: } else {
65: traverseSingular(ctx);
66: }
67: }
68:
69: private void traverseCollection(SerializationContext<? extends Collection<?>> ctx) {
70: openCollection(ctx);
71: for (Object item : ctx.getValue()) {
72: if (item == null) {
73: continue;
74: }
75: traverseSingular(serializationContextFactory.create(item, ctx));
76: }
77: closeCollection(ctx);
78: }
79:
80: void traverseSingular(SerializationContext<?> ctx) {
81: if (ctx.getValue() == null) {
82: return;
83: }
84: final boolean firstEncounter = !knownInstances.containsKey(ctx.getValue());
85: final boolean shouldTraverse = visitInstance(ctx);
86: if (!shouldTraverse) {
87: return;
88: }
89: openInstance(ctx);
90: visitIdentifier(ctx);
91: if (!BeanClassProcessor.isIdentifierType(ctx.getValue().getClass()) && firstEncounter) {
92: visitTypes(ctx);
93: serializeFields(ctx);
94: serializePropertiesField(ctx);
95: }
96: closeInstance(ctx);
97: }
98:
99: private void serializeFields(SerializationContext<?> ctx) {
100: final Object instance = ctx.getValue();
101: final List<Field> fieldsToSerialize =
102: orderAttributesForSerialization(BeanAnnotationProcessor.getSerializableFields(instance),
103: BeanAnnotationProcessor.getAttributeOrder(instance.getClass()));
104: for (Field f : fieldsToSerialize) {
105: if (shouldSkipFieldSerialization(f)) {
106: continue;
107: }
108: Object value = BeanClassProcessor.getFieldValue(f, instance);
109: final SerializationContext<?> fieldCtx = serializationContextFactory.createForAttribute(f, value, ctx);
110: visitAttribute(fieldCtx);
111: }
112: }
113:
114: private boolean shouldSkipFieldSerialization(Field f) {
115: return BeanAnnotationProcessor.isInstanceIdentifier(f) || BeanAnnotationProcessor.isPropertiesField(
116: f) || BeanAnnotationProcessor.isTypesField(f);
117: }
118:
119: private List<Field> orderAttributesForSerialization(List<Field> fields, String[] ordering) {
120: final List<Field> result = new ArrayList<>(fields.size());
121: for (String item : ordering) {
122: final Iterator<Field> it = fields.iterator();
123: while (it.hasNext()) {
124: final Field f = it.next();
125: if (f.getName().equals(item)) {
126: it.remove();
127: result.add(f);
128: break;
129: }
130: }
131: }
132: result.addAll(fields);
133: return result;
134: }
135:
136: private void serializePropertiesField(SerializationContext<?> ctx) {
137: final Object instance = ctx.getValue();
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(
149: serializationContextFactory.createForProperties(propertiesField, (Map<?, ?>) value, ctx));
150: }
151:
152: public boolean visitInstance(SerializationContext<?> ctx) {
153: return visitor.visitObject(ctx);
154: }
155:
156: public void openInstance(SerializationContext<?> ctx) {
157: if (!BeanClassProcessor.isIdentifierType(ctx.getValue().getClass())) {
158: final String identifier = resolveIdentifier(ctx.getValue());
159: knownInstances.put(ctx.getValue(), identifier);
160: }
161: visitor.openObject(ctx);
162: }
163:
164: private String resolveIdentifier(Object instance) {
165: final Optional<Object> extractedId = BeanAnnotationProcessor.getInstanceIdentifier(instance);
166: if (!extractedId.isPresent() && requireId) {
167: throw MissingIdentifierException.create(instance);
168: }
169: return extractedId.orElseGet(() -> knownInstances.containsKey(instance) ? knownInstances.get(instance) :
170: IdentifierUtil.generateBlankNodeId()).toString();
171: }
172:
173: public void closeInstance(SerializationContext<?> ctx) {
174: visitor.closeObject(ctx);
175: }
176:
177: public void visitIdentifier(SerializationContext<?> ctx) {
178: final Object identifier = ctx.getValue();
179: final String id;
180: final SerializationContext<String> idContext;
181: if (BeanClassProcessor.isIdentifierType(identifier.getClass())) {
182: id = identifier.toString();
183: idContext = serializationContextFactory.createForIdentifier(null, id, ctx);
184: } else {
185: id = resolveIdentifier(identifier);
186: idContext = serializationContextFactory.createForIdentifier(
187: BeanAnnotationProcessor.getIdentifierField(identifier.getClass()).orElse(null), id, ctx);
188: knownInstances.put(identifier, id);
189: }
190: visitor.visitIdentifier(idContext);
191: }
192:
193: public void visitTypes(SerializationContext<?> ctx) {
194: final Object instance = ctx.getValue();
195: final Set<String> resolvedTypes = typeResolver.resolveTypes(instance);
196: assert !resolvedTypes.isEmpty();
197: final SerializationContext<Set<String>> typesContext = serializationContextFactory.createForTypes(
198: BeanAnnotationProcessor.getTypesField(instance.getClass()).orElse(null), resolvedTypes, ctx);
199: visitor.visitTypes(typesContext);
200: }
201:
202: public void visitAttribute(SerializationContext<?> ctx) {
203: visitor.visitAttribute(ctx);
204: }
205:
206: public void openCollection(SerializationContext<? extends Collection<?>> ctx) {
207: visitor.openCollection(ctx);
208: }
209:
210: public void closeCollection(SerializationContext<?> ctx) {
211: visitor.closeCollection(ctx);
212: }
213:
214: public void setRequireId(boolean requireId) {
215: this.requireId = requireId;
216: }
217: }