1 package be
.nikiroo
.utils
.serial
;
3 import java
.io
.IOException
;
4 import java
.io
.InputStream
;
5 import java
.lang
.reflect
.Field
;
6 import java
.util
.HashMap
;
8 import java
.util
.zip
.GZIPInputStream
;
10 import be
.nikiroo
.utils
.IOUtils
;
11 import be
.nikiroo
.utils
.streams
.Base64InputStream
;
12 import be
.nikiroo
.utils
.streams
.BufferedInputStream
;
13 import be
.nikiroo
.utils
.streams
.NextableInputStream
;
14 import be
.nikiroo
.utils
.streams
.NextableInputStreamStep
;
17 * A simple class that can accept the output of {@link Exporter} to recreate
18 * objects as they were sent to said exporter.
20 * This class requires the objects (and their potential enclosing objects) to
21 * have an empty constructor, and does not support inner classes (it does
22 * support nested classes, though).
26 public class Importer
{
29 private Importer child
;
30 private Map
<String
, Object
> map
;
32 private String currentFieldName
;
35 * Create a new {@link Importer}.
38 map
= new HashMap
<String
, Object
>();
39 map
.put("NULL", null);
42 private Importer(Map
<String
, Object
> map
) {
47 * Read some data into this {@link Importer}: it can be the full serialised
48 * content, or a number of lines of it (any given line <b>MUST</b> be
49 * complete though) and accumulate it with the already present data.
54 * @return itself so it can be chained
56 * @throws NoSuchFieldException
57 * if the serialised data contains information about a field
58 * which does actually not exist in the class we know of
59 * @throws NoSuchMethodException
60 * if a class described in the serialised data cannot be created
61 * because it is not compatible with this code
62 * @throws ClassNotFoundException
63 * if a class described in the serialised data cannot be found
65 * if the content cannot be read (for instance, corrupt data)
66 * @throws NullPointerException
67 * if the stream is empty
69 public Importer
read(InputStream in
) throws NoSuchFieldException
,
70 NoSuchMethodException
, ClassNotFoundException
, IOException
,
71 NullPointerException
{
73 NextableInputStream stream
= new NextableInputStream(in
,
74 new NextableInputStreamStep('\n'));
78 throw new NullPointerException("InputStream is null");
82 while (stream
.next()) {
85 throw new NullPointerException(
86 "InputStream empty, normal termination");
92 boolean zip
= stream
.startsWith("ZIP:");
93 boolean b64
= stream
.startsWith("B64:");
96 stream
.skip("XXX:".length());
98 InputStream decoded
= stream
.open();
100 decoded
= new GZIPInputStream(decoded
);
102 decoded
= new Base64InputStream(decoded
, false);
121 * Read a single (whole) line of serialised data into this {@link Importer}
122 * and accumulate it with the already present data.
127 * @return TRUE if we are just done with one object or sub-object
129 * @throws NoSuchFieldException
130 * if the serialised data contains information about a field
131 * which does actually not exist in the class we know of
132 * @throws NoSuchMethodException
133 * if a class described in the serialised data cannot be created
134 * because it is not compatible with this code
135 * @throws ClassNotFoundException
136 * if a class described in the serialised data cannot be found
137 * @throws IOException
138 * if the content cannot be read (for instance, corrupt data)
140 private boolean processLine(BufferedInputStream in
)
141 throws NoSuchFieldException
, NoSuchMethodException
,
142 ClassNotFoundException
, IOException
{
144 // Defer to latest child if any
146 if (child
.processLine(in
)) {
147 if (currentFieldName
!= null) {
148 setField(currentFieldName
, child
.getValue());
149 currentFieldName
= null;
158 if (in
.is("{")) { // START: new child if needed
160 child
= new Importer(map
);
164 } else if (in
.is("}")) { // STOP: report self to parent
170 if (CustomSerializer
.isCustom(in
)) {
171 // not a field value but a direct value
172 String line
= IOUtils
.readSmallStream(in
);
173 me
= SerialUtils
.decode(line
);
177 // TODO use the stream, Luke
178 // .. at least for REF
179 String line
= IOUtils
.readSmallStream(in
);
181 if (line
.startsWith("REF ")) { // REF: create/link self
182 // TODO: here, line is REF type@999:xxx
184 // note: use .end() when containsKey(ref)
185 String
[] tab
= line
.substring("REF ".length()).split("@");
186 String type
= tab
[0];
187 tab
= tab
[1].split(":");
190 link
= map
.containsKey(ref
);
194 if (line
.endsWith(":")) {
196 me
= SerialUtils
.createObject(type
);
199 int pos
= line
.indexOf(":");
200 String encodedValue
= line
.substring(pos
+ 1);
201 me
= SerialUtils
.decode(encodedValue
);
205 } else { // FIELD: new field *or* direct simple value
206 if (line
.endsWith(":")) {
207 // field value is compound
208 currentFieldName
= line
.substring(0, line
.length() - 1);
209 } else if (line
.startsWith(":") || !line
.contains(":")
210 || line
.startsWith("\"")) {
211 // not a field value but a direct value
212 me
= SerialUtils
.decode(line
);
214 // field value is direct
215 int pos
= line
.indexOf(":");
216 String fieldName
= line
.substring(0, pos
);
217 String encodedValue
= line
.substring(pos
+ 1);
219 value
= SerialUtils
.decode(encodedValue
);
221 // To support simple types directly:
225 setField(fieldName
, value
);
233 private void setField(String name
, Object value
)
234 throws NoSuchFieldException
{
237 Field field
= me
.getClass().getDeclaredField(name
);
239 field
.setAccessible(true);
240 field
.set(me
, value
);
241 } catch (NoSuchFieldException e
) {
242 throw new NoSuchFieldException(String
.format(
243 "Field \"%s\" was not found in object of type \"%s\".",
244 name
, me
.getClass().getCanonicalName()));
245 } catch (Exception e
) {
246 throw new NoSuchFieldException(String
.format(
247 "Internal error when setting \"%s.%s\": %s", me
.getClass()
248 .getCanonicalName(), name
, e
.getMessage()));
253 * Return the current deserialised value.
255 * @return the current value
257 public Object
getValue() {