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