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