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