Commit | Line | Data |
---|---|---|
db31c358 NR |
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 | } |