b3307fdc7220c4fa757b04e9038e720106ddc8a4
[nikiroo-utils.git] / src / be / nikiroo / utils / serial / Importer.java
1 package be.nikiroo.utils.serial;
2
3 import java.lang.reflect.Constructor;
4 import java.lang.reflect.Field;
5 import java.lang.reflect.InvocationTargetException;
6 import java.util.HashMap;
7 import java.util.Map;
8 import java.util.Scanner;
9
10 import be.nikiroo.utils.IOUtils;
11 import be.nikiroo.utils.StringUtils;
12
13 /**
14 * A simple class that can accept the output of {@link Exporter} to recreate
15 * objects as they were sent to said exporter.
16 * <p>
17 * This class requires the objects (and their potential enclosing objects) to
18 * have an empty constructor, and does not support inner classes (it does
19 * support nested classes, though).
20 *
21 * @author niki
22 */
23 public class Importer {
24 private Boolean link;
25 private Object me;
26 private Importer child;
27 private Map<String, Object> map;
28
29 private String currentFieldName;
30
31 public Importer() {
32 map = new HashMap<String, Object>();
33 map.put("NULL", null);
34 }
35
36 private Importer(Map<String, Object> map) {
37 this.map = map;
38 }
39
40 public Importer readLine(String line) {
41 try {
42 processLine(line);
43 } catch (Exception e) {
44 throw new IllegalArgumentException(e);
45 }
46 return this;
47 }
48
49 public Importer read(String data) {
50 try {
51 if (data.startsWith("ZIP:")) {
52 data = StringUtils.unzip64(data.substring("ZIP:".length()));
53 }
54 Scanner scan = new Scanner(data);
55 scan.useDelimiter("\n");
56 while (scan.hasNext()) {
57 processLine(scan.next());
58 }
59 scan.close();
60 } catch (Exception e) {
61 throw new IllegalArgumentException(e);
62 }
63 return this;
64 }
65
66 public boolean processLine(String line) throws IllegalArgumentException,
67 NoSuchFieldException, SecurityException, IllegalAccessException,
68 NoSuchMethodException, InstantiationException, ClassNotFoundException, InvocationTargetException {
69 // Defer to latest child if any
70 if (child != null) {
71 if (child.processLine(line)) {
72 if (currentFieldName != null) {
73 setField(currentFieldName, child.getValue());
74 currentFieldName = null;
75 }
76 child = null;
77 }
78
79 return false;
80 }
81
82 if (line.equals("{")) { // START: new child if needed
83 if (link != null) {
84 child = new Importer(map);
85 }
86 } else if (line.equals("}")) { // STOP: report self to parent
87 return true;
88 } else if (line.startsWith("REF ")) { // REF: create/link self
89 String ref = line.substring(4).split("@")[1];
90 link = map.containsKey(ref);
91 if (link) {
92 me = map.get(ref);
93 } else {
94 me = createSelf(line.substring(4).split("@")[0]);
95 map.put(ref, me);
96 }
97 } else { // FIELD: new field
98 if (line.endsWith(":")) {
99 // field value is compound
100 currentFieldName = line.substring(0, line.length() - 1);
101 } else {
102 // field value is direct
103 int pos = line.indexOf(":");
104 String fieldName = line.substring(0, pos);
105 String encodedValue = line.substring(pos + 1);
106 Object value = null;
107 value = SerialUtils.decode(encodedValue);
108
109 // To support simple types directly:
110 if (me == null) {
111 me = value;
112 } else {
113 setField(fieldName, value);
114 }
115 }
116 }
117
118 return false;
119 }
120
121 /**
122 * Create an empty object of the given type.
123 *
124 * @param type
125 * the object type
126 * @return the object
127 *
128 * @throws NoSuchMethodException
129 * @throws SecurityException
130 * @throws InstantiationException
131 * @throws IllegalAccessException
132 * @throws ClassNotFoundException
133 * @throws IllegalArgumentException
134 * @throws InvocationTargetException
135 */
136 private Object createSelf(String type) throws NoSuchMethodException,
137 SecurityException, InstantiationException, IllegalAccessException,
138 ClassNotFoundException, IllegalArgumentException,
139 InvocationTargetException {
140
141 try {
142 Class<?> clazz = getClass(type);
143 if (clazz == null) {
144 throw new ClassNotFoundException("Class not found: " + type);
145 }
146
147 String className = clazz.getName();
148 Object[] args = null;
149 Constructor<?> ctor = null;
150 if (className.contains("$")) {
151 Object javaParent = createSelf(className.substring(0,
152 className.lastIndexOf('$')));
153 args = new Object[] { javaParent };
154 ctor = clazz.getDeclaredConstructor(new Class[] { javaParent
155 .getClass() });
156 } else {
157 args = new Object[] {};
158 ctor = clazz.getDeclaredConstructor();
159 }
160
161 ctor.setAccessible(true);
162 return ctor.newInstance(args);
163 } catch (NoSuchMethodException e) {
164 throw new NoSuchMethodException(
165 String.format(
166 "Objects of type \"%s\" cannot be created by this code: maybe the class"
167 + " or its enclosing class doesn't have an empty constructor?",
168 type));
169
170 }
171 }
172
173 private Class<?> getClass(String type) throws ClassNotFoundException,
174 NoSuchMethodException {
175 Class<?> clazz = null;
176 try {
177 clazz = Class.forName(type);
178 } catch (ClassNotFoundException e) {
179 int pos = type.length();
180 pos = type.lastIndexOf(".", pos);
181 if (pos >= 0) {
182 String parentType = type.substring(0, pos);
183 String nestedType = type.substring(pos + 1);
184 Class<?> javaParent = null;
185 try {
186 javaParent = getClass(parentType);
187 parentType = javaParent.getName();
188 clazz = Class.forName(parentType + "$" + nestedType);
189 } catch (Exception ee) {
190 }
191
192 if (javaParent == null) {
193 throw new NoSuchMethodException(
194 "Class not found: "
195 + type
196 + " (the enclosing class cannot be created: maybe it doesn't have an empty constructor?)");
197 }
198 }
199 }
200
201 return clazz;
202 }
203
204 private void setField(String name, Object value)
205 throws NoSuchFieldException, SecurityException,
206 IllegalArgumentException, IllegalAccessException {
207
208 try {
209 Field field = me.getClass().getDeclaredField(name);
210
211 field.setAccessible(true);
212 field.set(me, value);
213 } catch (NoSuchFieldException e) {
214 throw new NoSuchFieldException(String.format(
215 "Field \"%s\" was not found in object of type \"%s\".",
216 name, me.getClass().getCanonicalName()));
217 }
218 }
219
220 public Object getValue() {
221 return me;
222 }
223 }