Skip to content

Method: ObjectGraphTraverser()

1: /**
2: * Copyright (C) 2020 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.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 implements InstanceVisitor {
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((Collection<?>) instance);
55: } else {
56: traverseImpl(instance);
57: }
58: } catch (IllegalAccessException e) {
59: throw new JsonLdSerializationException("Unable to extract field value.", e);
60: }
61: }
62:
63: private void traverseCollection(Collection<?> col) throws IllegalAccessException {
64: openCollection(col);
65: for (Object item : col) {
66: traverseImpl(item);
67: }
68: closeCollection(col);
69: }
70:
71: private void traverseImpl(Object instance) throws IllegalAccessException {
72: if (instance == null) {
73: return;
74: }
75: if (knownInstances.containsKey(instance)) {
76: visitKnownInstance(knownInstances.get(instance), instance);
77: return;
78: }
79: openInstance(instance);
80: visitIdentifier(null, instance);
81: if (!BeanClassProcessor.isIdentifierType(instance.getClass())) {
82: visitTypes(null, instance);
83: serializeFields(instance);
84: }
85: closeInstance(instance);
86: }
87:
88: private void serializeFields(Object instance) throws IllegalAccessException {
89: final List<Field> fieldsToSerialize =
90: orderAttributesForSerialization(BeanAnnotationProcessor.getSerializableFields(instance),
91: BeanAnnotationProcessor.getAttributeOrder(instance.getClass()));
92: for (Field f : fieldsToSerialize) {
93: if (BeanAnnotationProcessor.isInstanceIdentifier(f)) {
94: continue;
95: }
96: Object value = BeanClassProcessor.getFieldValue(f, instance);
97: visitField(f, value);
98: if (value != null && BeanAnnotationProcessor.isObjectProperty(f)) {
99: traverseObjectPropertyValue(value);
100: }
101: }
102: }
103:
104: private List<Field> orderAttributesForSerialization(List<Field> fields, String[] ordering) {
105: final List<Field> result = new ArrayList<>(fields.size());
106: for (String item : ordering) {
107: final Iterator<Field> it = fields.iterator();
108: while (it.hasNext()) {
109: final Field f = it.next();
110: if (f.getName().equals(item)) {
111: it.remove();
112: result.add(f);
113: break;
114: }
115: }
116: }
117: result.addAll(fields);
118: return result;
119: }
120:
121: private void traverseObjectPropertyValue(Object value) throws IllegalAccessException {
122: if (value instanceof Collection) {
123: final Collection<?> col = (Collection<?>) value;
124: openCollection(col);
125: for (Object elem : col) {
126: traverseImpl(elem);
127: }
128: closeCollection(col);
129: } else if (value.getClass().isArray()) {
130: throw new JsonLdSerializationException("Arrays are not supported, yet.");
131: } else {
132: traverseImpl(value);
133: }
134: }
135:
136: @Override
137: public void openInstance(Object instance) {
138: if (!BeanClassProcessor.isIdentifierType(instance.getClass())) {
139: final Optional<Object> identifier = BeanAnnotationProcessor.getInstanceIdentifier(instance);
140: knownInstances.put(instance, identifier.orElse(IdentifierUtil.generateBlankNodeId()).toString());
141: }
142: visitors.forEach(v -> v.openInstance(instance));
143: }
144:
145: @Override
146: public void closeInstance(Object instance) {
147: visitors.forEach(v -> v.closeInstance(instance));
148: }
149:
150: @Override
151: public void visitKnownInstance(String id, Object instance) {
152: visitors.forEach(v -> v.visitKnownInstance(id, instance));
153: }
154:
155: @Override
156: public void visitIdentifier(String identifier, Object instance) {
157: final String id;
158: if (BeanClassProcessor.isIdentifierType(instance.getClass())) {
159: id = instance.toString();
160: } else {
161: final Optional<Object> extractedId = BeanAnnotationProcessor.getInstanceIdentifier(instance);
162: if (!extractedId.isPresent() && requireId) {
163: throw MissingIdentifierException.create(instance);
164: }
165: id = extractedId.orElse(IdentifierUtil.generateBlankNodeId()).toString();
166: knownInstances.put(instance, id);
167: }
168: visitors.forEach(v -> v.visitIdentifier(id, instance));
169: }
170:
171: @Override
172: public void visitTypes(Collection<String> types, Object instance) {
173: final Set<String> resolvedTypes = typeResolver.resolveTypes(instance);
174: assert !resolvedTypes.isEmpty();
175: visitors.forEach(v -> v.visitTypes(resolvedTypes, instance));
176: }
177:
178: @Override
179: public void visitField(Field field, Object value) {
180: visitors.forEach(v -> v.visitField(field, value));
181: }
182:
183: @Override
184: public void openCollection(Collection<?> collection) {
185: visitors.forEach(v -> v.openCollection(collection));
186: }
187:
188: @Override
189: public void closeCollection(Collection<?> collection) {
190: visitors.forEach(v -> v.closeCollection(collection));
191: }
192:
193: public void setRequireId(boolean requireId) {
194: this.requireId = requireId;
195: }
196: }