fix changelog headers
[fanfix.git] / serial / Importer.java
1 package be.nikiroo.utils.serial;
2
3 import java.io.IOException;
4 import java.lang.reflect.Field;
5 import java.util.HashMap;
6 import java.util.Map;
7 import java.util.Scanner;
8
9 import be.nikiroo.utils.StringUtils;
10
11 /**
12 * A simple class that can accept the output of {@link Exporter} to recreate
13 * objects as they were sent to said exporter.
14 * <p>
15 * This class requires the objects (and their potential enclosing objects) to
16 * have an empty constructor, and does not support inner classes (it does
17 * support nested classes, though).
18 *
19 * @author niki
20 */
21 public class Importer {
22 private Boolean link;
23 private Object me;
24 private Importer child;
25 private Map<String, Object> map;
26
27 private String currentFieldName;
28
29 /**
30 * Create a new {@link Importer}.
31 */
32 public Importer() {
33 map = new HashMap<String, Object>();
34 map.put("NULL", null);
35 }
36
37 private Importer(Map<String, Object> map) {
38 this.map = map;
39 }
40
41 /**
42 * Read some data into this {@link Importer}: it can be the full serialised
43 * content, or a number of lines of it (any given line <b>MUST</b> be
44 * complete though) and accumulate it with the already present data.
45 *
46 * @param data
47 * the data to parse
48 *
49 * @return itself so it can be chained
50 *
51 * @throws NoSuchFieldException
52 * if the serialised data contains information about a field
53 * which does actually not exist in the class we know of
54 * @throws NoSuchMethodException
55 * if a class described in the serialised data cannot be created
56 * because it is not compatible with this code
57 * @throws ClassNotFoundException
58 * if a class described in the serialised data cannot be found
59 */
60 public Importer read(String data) throws NoSuchFieldException,
61 NoSuchMethodException, ClassNotFoundException {
62
63 try {
64 Scanner scan = new Scanner(data);
65 scan.useDelimiter("\n");
66 while (scan.hasNext()) {
67 String line = scan.next();
68
69 if (line.startsWith("ZIP:")) {
70 line = StringUtils.unzip64(line.substring("ZIP:".length()));
71 read(line);
72 } else {
73 processLine(line);
74 }
75 }
76 scan.close();
77 } catch (IOException e) {
78 throw new NoSuchMethodException(
79 "Internal error when decoding ZIP content: input may be corrupt");
80 }
81
82 return this;
83 }
84
85 /**
86 * Read a single (whole) line of serialised data into this {@link Importer}
87 * and accumulate it with the already present data.
88 *
89 * @param line
90 * the line to parse
91 *
92 * @return TRUE if we are just done with one object or sub-object
93 *
94 * @throws NoSuchFieldException
95 * if the serialised data contains information about a field
96 * which does actually not exist in the class we know of
97 * @throws NoSuchMethodException
98 * if a class described in the serialised data cannot be created
99 * because it is not compatible with this code
100 * @throws ClassNotFoundException
101 * if a class described in the serialised data cannot be found
102 */
103 private boolean processLine(String line) throws NoSuchFieldException,
104 NoSuchMethodException, ClassNotFoundException {
105 // Defer to latest child if any
106 if (child != null) {
107 if (child.processLine(line)) {
108 if (currentFieldName != null) {
109 setField(currentFieldName, child.getValue());
110 currentFieldName = null;
111 }
112 child = null;
113 }
114
115 return false;
116 }
117
118 if (line.equals("{")) { // START: new child if needed
119 if (link != null) {
120 child = new Importer(map);
121 }
122 } else if (line.equals("}")) { // STOP: report self to parent
123 return true;
124 } else if (line.startsWith("REF ")) { // REF: create/link self
125 String[] tab = line.substring("REF ".length()).split("@");
126 String type = tab[0];
127 tab = tab[1].split(":");
128 String ref = tab[0];
129
130 link = map.containsKey(ref);
131 if (link) {
132 me = map.get(ref);
133 } else {
134 if (line.endsWith(":")) {
135 // construct
136 me = SerialUtils.createObject(type);
137 } else {
138 // direct value
139 int pos = line.indexOf(":");
140 String encodedValue = line.substring(pos + 1);
141 me = SerialUtils.decode(encodedValue);
142 }
143 map.put(ref, me);
144 }
145 } else { // FIELD: new field *or* direct simple value
146 if (line.endsWith(":")) {
147 // field value is compound
148 currentFieldName = line.substring(0, line.length() - 1);
149 } else if (line.startsWith(":") || !line.contains(":")) {
150 // not a field value but a direct value
151 me = SerialUtils.decode(line);
152 } else {
153 // field value is direct
154 int pos = line.indexOf(":");
155 String fieldName = line.substring(0, pos);
156 String encodedValue = line.substring(pos + 1);
157 Object value = null;
158 value = SerialUtils.decode(encodedValue);
159
160 // To support simple types directly:
161 if (me == null) {
162 me = value;
163 } else {
164 setField(fieldName, value);
165 }
166 }
167 }
168
169 return false;
170 }
171
172 private void setField(String name, Object value)
173 throws NoSuchFieldException {
174
175 try {
176 Field field = me.getClass().getDeclaredField(name);
177
178 field.setAccessible(true);
179 field.set(me, value);
180 } catch (NoSuchFieldException e) {
181 throw new NoSuchFieldException(String.format(
182 "Field \"%s\" was not found in object of type \"%s\".",
183 name, me.getClass().getCanonicalName()));
184 } catch (Exception e) {
185 throw new NoSuchFieldException(String.format(
186 "Internal error when setting \"%s.%s\": %s", me.getClass()
187 .getCanonicalName(), name, e.getMessage()));
188 }
189 }
190
191 /**
192 * Return the current deserialised value.
193 *
194 * @return the current value
195 */
196 public Object getValue() {
197 return me;
198 }
199 }