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
.NextableInputStream
;
13 import be
.nikiroo
.utils
.streams
.NextableInputStreamStep
;
16 * A simple class that can accept the output of {@link Exporter} to recreate
17 * objects as they were sent to said exporter.
19 * This class requires the objects (and their potential enclosing objects) to
20 * have an empty constructor, and does not support inner classes (it does
21 * support nested classes, though).
25 public class Importer
{
28 private Importer child
;
29 private Map
<String
, Object
> map
;
31 private String currentFieldName
;
34 * Create a new {@link Importer}.
37 map
= new HashMap
<String
, Object
>();
38 map
.put("NULL", null);
41 private Importer(Map
<String
, Object
> map
) {
46 * Read some data into this {@link Importer}: it can be the full serialised
47 * content, or a number of lines of it (any given line <b>MUST</b> be
48 * complete though) and accumulate it with the already present data.
53 * @return itself so it can be chained
55 * @throws NoSuchFieldException
56 * if the serialised data contains information about a field
57 * which does actually not exist in the class we know of
58 * @throws NoSuchMethodException
59 * if a class described in the serialised data cannot be created
60 * because it is not compatible with this code
61 * @throws ClassNotFoundException
62 * if a class described in the serialised data cannot be found
64 * if the content cannot be read (for instance, corrupt data)
65 * @throws NullPointerException
66 * if the stream is empty
68 public Importer
read(InputStream in
) throws NoSuchFieldException
,
69 NoSuchMethodException
, ClassNotFoundException
, IOException
,
70 NullPointerException
{
72 NextableInputStream stream
= new NextableInputStream(in
,
73 new NextableInputStreamStep('\n'));
77 throw new NullPointerException("InputStream is null");
81 while (stream
.next()) {
84 throw new NullPointerException(
85 "InputStream empty, normal termination");
91 boolean zip
= stream
.startsWiths("ZIP:");
92 boolean b64
= stream
.startsWiths("B64:");
95 stream
.skip("XXX:".length());
97 InputStream decoded
= stream
.open();
99 decoded
= new GZIPInputStream(decoded
);
101 decoded
= new Base64InputStream(decoded
, false);
120 * Read a single (whole) line of serialised data into this {@link Importer}
121 * and accumulate it with the already present data.
126 * @return TRUE if we are just done with one object or sub-object
128 * @throws NoSuchFieldException
129 * if the serialised data contains information about a field
130 * which does actually not exist in the class we know of
131 * @throws NoSuchMethodException
132 * if a class described in the serialised data cannot be created
133 * because it is not compatible with this code
134 * @throws ClassNotFoundException
135 * if a class described in the serialised data cannot be found
136 * @throws IOException
137 * if the content cannot be read (for instance, corrupt data)
139 private boolean processLine(InputStream in
) throws NoSuchFieldException
,
140 NoSuchMethodException
, ClassNotFoundException
, IOException
{
142 // Defer to latest child if any
144 if (child
.processLine(in
)) {
145 if (currentFieldName
!= null) {
146 setField(currentFieldName
, child
.getValue());
147 currentFieldName
= null;
155 // TODO use the stream, Luke
156 String line
= IOUtils
.readSmallStream(in
);
158 if (line
.isEmpty()) {
162 if (line
.equals("{")) { // START: new child if needed
164 child
= new Importer(map
);
166 } else if (line
.equals("}")) { // STOP: report self to parent
168 } else if (line
.startsWith("REF ")) { // REF: create/link self
169 String
[] tab
= line
.substring("REF ".length()).split("@");
170 String type
= tab
[0];
171 tab
= tab
[1].split(":");
174 link
= map
.containsKey(ref
);
178 if (line
.endsWith(":")) {
180 me
= SerialUtils
.createObject(type
);
183 int pos
= line
.indexOf(":");
184 String encodedValue
= line
.substring(pos
+ 1);
185 me
= SerialUtils
.decode(encodedValue
);
189 } else { // FIELD: new field *or* direct simple value
190 if (line
.endsWith(":")) {
191 // field value is compound
192 currentFieldName
= line
.substring(0, line
.length() - 1);
193 } else if (line
.startsWith(":") || !line
.contains(":")
194 || line
.startsWith("\"") || CustomSerializer
.isCustom(line
)) {
195 // not a field value but a direct value
196 me
= SerialUtils
.decode(line
);
198 // field value is direct
199 int pos
= line
.indexOf(":");
200 String fieldName
= line
.substring(0, pos
);
201 String encodedValue
= line
.substring(pos
+ 1);
203 value
= SerialUtils
.decode(encodedValue
);
205 // To support simple types directly:
209 setField(fieldName
, value
);
217 private void setField(String name
, Object value
)
218 throws NoSuchFieldException
{
221 Field field
= me
.getClass().getDeclaredField(name
);
223 field
.setAccessible(true);
224 field
.set(me
, value
);
225 } catch (NoSuchFieldException e
) {
226 throw new NoSuchFieldException(String
.format(
227 "Field \"%s\" was not found in object of type \"%s\".",
228 name
, me
.getClass().getCanonicalName()));
229 } catch (Exception e
) {
230 throw new NoSuchFieldException(String
.format(
231 "Internal error when setting \"%s.%s\": %s", me
.getClass()
232 .getCanonicalName(), name
, e
.getMessage()));
237 * Return the current deserialised value.
239 * @return the current value
241 public Object
getValue() {