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