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
;
9 import be
.nikiroo
.utils
.IOUtils
;
10 import be
.nikiroo
.utils
.NextableInputStream
;
11 import be
.nikiroo
.utils
.NextableInputStreamStep
;
12 import be
.nikiroo
.utils
.StringUtils
;
15 * A simple class that can accept the output of {@link Exporter} to recreate
16 * objects as they were sent to said exporter.
18 * This class requires the objects (and their potential enclosing objects) to
19 * have an empty constructor, and does not support inner classes (it does
20 * support nested classes, though).
24 public class Importer
{
27 private Importer child
;
28 private Map
<String
, Object
> map
;
30 private String currentFieldName
;
33 * Create a new {@link Importer}.
36 map
= new HashMap
<String
, Object
>();
37 map
.put("NULL", null);
40 private Importer(Map
<String
, Object
> map
) {
45 * Read some data into this {@link Importer}: it can be the full serialised
46 * content, or a number of lines of it (any given line <b>MUST</b> be
47 * complete though) and accumulate it with the already present data.
52 * @return itself so it can be chained
54 * @throws NoSuchFieldException
55 * if the serialised data contains information about a field
56 * which does actually not exist in the class we know of
57 * @throws NoSuchMethodException
58 * if a class described in the serialised data cannot be created
59 * because it is not compatible with this code
60 * @throws ClassNotFoundException
61 * if a class described in the serialised data cannot be found
63 * if the content cannot be read (for instance, corrupt data)
64 * @throws NullPointerException
65 * if the stream is empty
67 public Importer
read(InputStream in
) throws NoSuchFieldException
,
68 NoSuchMethodException
, ClassNotFoundException
, IOException
,
69 NullPointerException
{
71 // TODO: fix NexInStream: next() MUST be called first time, too
72 // TODO: NexInStream: add getBytes() (size downloaded)
73 // TODO: public InputStrem open() (open/close do nothing)
74 // TODO: public boolean eof()
75 // TODO: public nextAll(): next, but disable separation of sub-streams
76 // TODO: close(alsoCloseIncludedField)
78 NextableInputStream stream
= new NextableInputStream(in
,
79 new NextableInputStreamStep('\n'));
81 if (in
== null || stream
.eof()) {
83 throw new NullPointerException("InputStream is null");
85 throw new NullPointerException("InputStream is empty");
88 while (stream
.next()) {
89 boolean zip
= stream
.startsWiths("ZIP:");
90 boolean b64
= stream
.startsWiths("B64:");
93 InputStream decoded
= StringUtils
.unbase64(stream
.open(), zip
);
108 * Read a single (whole) line of serialised data into this {@link Importer}
109 * and accumulate it with the already present data.
114 * @return TRUE if we are just done with one object or sub-object
116 * @throws NoSuchFieldException
117 * if the serialised data contains information about a field
118 * which does actually not exist in the class we know of
119 * @throws NoSuchMethodException
120 * if a class described in the serialised data cannot be created
121 * because it is not compatible with this code
122 * @throws ClassNotFoundException
123 * if a class described in the serialised data cannot be found
124 * @throws IOException
125 * if the content cannot be read (for instance, corrupt data)
127 private boolean processLine(InputStream in
) throws NoSuchFieldException
,
128 NoSuchMethodException
, ClassNotFoundException
, IOException
{
130 // Defer to latest child if any
132 if (child
.processLine(in
)) {
133 if (currentFieldName
!= null) {
134 setField(currentFieldName
, child
.getValue());
135 currentFieldName
= null;
143 // TODO use the stream, Luke
144 String line
= IOUtils
.readSmallStream(in
);
146 if (line
.equals("{")) { // START: new child if needed
148 child
= new Importer(map
);
150 } else if (line
.equals("}")) { // STOP: report self to parent
152 } else if (line
.startsWith("REF ")) { // REF: create/link self
153 String
[] tab
= line
.substring("REF ".length()).split("@");
154 String type
= tab
[0];
155 tab
= tab
[1].split(":");
158 link
= map
.containsKey(ref
);
162 if (line
.endsWith(":")) {
164 me
= SerialUtils
.createObject(type
);
167 int pos
= line
.indexOf(":");
168 String encodedValue
= line
.substring(pos
+ 1);
169 me
= SerialUtils
.decode(encodedValue
);
173 } else { // FIELD: new field *or* direct simple value
174 if (line
.endsWith(":")) {
175 // field value is compound
176 currentFieldName
= line
.substring(0, line
.length() - 1);
177 } else if (line
.startsWith(":") || !line
.contains(":")
178 || line
.startsWith("\"") || CustomSerializer
.isCustom(line
)) {
179 // not a field value but a direct value
180 me
= SerialUtils
.decode(line
);
182 // field value is direct
183 int pos
= line
.indexOf(":");
184 String fieldName
= line
.substring(0, pos
);
185 String encodedValue
= line
.substring(pos
+ 1);
187 value
= SerialUtils
.decode(encodedValue
);
189 // To support simple types directly:
193 setField(fieldName
, value
);
201 private void setField(String name
, Object value
)
202 throws NoSuchFieldException
{
205 Field field
= me
.getClass().getDeclaredField(name
);
207 field
.setAccessible(true);
208 field
.set(me
, value
);
209 } catch (NoSuchFieldException e
) {
210 throw new NoSuchFieldException(String
.format(
211 "Field \"%s\" was not found in object of type \"%s\".",
212 name
, me
.getClass().getCanonicalName()));
213 } catch (Exception e
) {
214 throw new NoSuchFieldException(String
.format(
215 "Internal error when setting \"%s.%s\": %s", me
.getClass()
216 .getCanonicalName(), name
, e
.getMessage()));
221 * Find the given needle in the data and return its position (or -1 if not
225 * the data to look through
227 * the offset at wich to start searching
231 * @return the position of the needle if found, -1 if not found
233 private int find(byte[] data
, int offset
, byte[] needle
) {
234 for (int i
= offset
; i
+ needle
.length
- 1 < data
.length
; i
++) {
236 for (int j
= 0; j
< needle
.length
; j
++) {
237 if (data
[i
+ j
] != needle
[j
]) {
252 * Return the current deserialised value.
254 * @return the current value
256 public Object
getValue() {