Version 3.1.6: fix Bridge, Serialiser, Progress:
[nikiroo-utils.git] / src / be / nikiroo / utils / serial / Importer.java
CommitLineData
db31c358
NR
1package be.nikiroo.utils.serial;
2
8c8da42a
NR
3import java.io.IOException;
4import java.lang.reflect.Field;
db31c358
NR
5import java.util.HashMap;
6import java.util.Map;
7import java.util.Scanner;
8
db31c358
NR
9import 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 */
21public 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
f157aed8
NR
29 /**
30 * Create a new {@link Importer}.
31 */
db31c358
NR
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
8c8da42a
NR
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
452f38c8
NR
59 * @throws IOException
60 * if the content cannot be read (for instance, corrupt data)
8c8da42a
NR
61 */
62 public Importer read(String data) throws NoSuchFieldException,
452f38c8 63 NoSuchMethodException, ClassNotFoundException, IOException {
db31c358 64
452f38c8 65 Scanner scan = new Scanner(data);
db31c358 66 try {
db31c358
NR
67 scan.useDelimiter("\n");
68 while (scan.hasNext()) {
8c8da42a
NR
69 String line = scan.next();
70
71 if (line.startsWith("ZIP:")) {
452f38c8
NR
72 try {
73 line = StringUtils.unzip64(line.substring("ZIP:"
74 .length()));
75 } catch (IOException e) {
76 throw new IOException(
77 "Internal error when decoding ZIP content: input may be corrupt");
78 }
ce0974c4
NR
79 read(line);
80 } else {
81 processLine(line);
8c8da42a 82 }
db31c358 83 }
452f38c8 84 } finally {
db31c358 85 scan.close();
db31c358 86 }
8c8da42a 87
db31c358
NR
88 return this;
89 }
90
8c8da42a
NR
91 /**
92 * Read a single (whole) line of serialised data into this {@link Importer}
93 * and accumulate it with the already present data.
94 *
95 * @param line
96 * the line to parse
97 *
98 * @return TRUE if we are just done with one object or sub-object
99 *
100 * @throws NoSuchFieldException
101 * if the serialised data contains information about a field
102 * which does actually not exist in the class we know of
103 * @throws NoSuchMethodException
104 * if a class described in the serialised data cannot be created
105 * because it is not compatible with this code
106 * @throws ClassNotFoundException
107 * if a class described in the serialised data cannot be found
452f38c8
NR
108 * @throws IOException
109 * if the content cannot be read (for instance, corrupt data)
8c8da42a
NR
110 */
111 private boolean processLine(String line) throws NoSuchFieldException,
452f38c8 112 NoSuchMethodException, ClassNotFoundException, IOException {
db31c358
NR
113 // Defer to latest child if any
114 if (child != null) {
115 if (child.processLine(line)) {
116 if (currentFieldName != null) {
117 setField(currentFieldName, child.getValue());
118 currentFieldName = null;
119 }
120 child = null;
121 }
122
123 return false;
124 }
125
126 if (line.equals("{")) { // START: new child if needed
127 if (link != null) {
128 child = new Importer(map);
129 }
130 } else if (line.equals("}")) { // STOP: report self to parent
131 return true;
132 } else if (line.startsWith("REF ")) { // REF: create/link self
ce0974c4
NR
133 String[] tab = line.substring("REF ".length()).split("@");
134 String type = tab[0];
135 tab = tab[1].split(":");
136 String ref = tab[0];
137
db31c358
NR
138 link = map.containsKey(ref);
139 if (link) {
140 me = map.get(ref);
141 } else {
ce0974c4
NR
142 if (line.endsWith(":")) {
143 // construct
144 me = SerialUtils.createObject(type);
145 } else {
146 // direct value
147 int pos = line.indexOf(":");
148 String encodedValue = line.substring(pos + 1);
149 me = SerialUtils.decode(encodedValue);
150 }
db31c358
NR
151 map.put(ref, me);
152 }
ce0974c4 153 } else { // FIELD: new field *or* direct simple value
db31c358
NR
154 if (line.endsWith(":")) {
155 // field value is compound
156 currentFieldName = line.substring(0, line.length() - 1);
f4053377
NR
157 } else if (line.startsWith(":") || !line.contains(":")
158 || line.startsWith("\"") || CustomSerializer.isCustom(line)) {
ce0974c4
NR
159 // not a field value but a direct value
160 me = SerialUtils.decode(line);
db31c358
NR
161 } else {
162 // field value is direct
163 int pos = line.indexOf(":");
164 String fieldName = line.substring(0, pos);
165 String encodedValue = line.substring(pos + 1);
166 Object value = null;
167 value = SerialUtils.decode(encodedValue);
168
169 // To support simple types directly:
170 if (me == null) {
171 me = value;
172 } else {
173 setField(fieldName, value);
174 }
175 }
176 }
177
178 return false;
179 }
180
db31c358 181 private void setField(String name, Object value)
8c8da42a 182 throws NoSuchFieldException {
db31c358
NR
183
184 try {
185 Field field = me.getClass().getDeclaredField(name);
186
187 field.setAccessible(true);
188 field.set(me, value);
189 } catch (NoSuchFieldException e) {
190 throw new NoSuchFieldException(String.format(
191 "Field \"%s\" was not found in object of type \"%s\".",
192 name, me.getClass().getCanonicalName()));
8c8da42a
NR
193 } catch (Exception e) {
194 throw new NoSuchFieldException(String.format(
195 "Internal error when setting \"%s.%s\": %s", me.getClass()
196 .getCanonicalName(), name, e.getMessage()));
db31c358
NR
197 }
198 }
199
8c8da42a
NR
200 /**
201 * Return the current deserialised value.
202 *
203 * @return the current value
204 */
db31c358
NR
205 public Object getValue() {
206 return me;
207 }
208}