1 package be
.nikiroo
.utils
.serial
;
3 import java
.io
.NotSerializableException
;
4 import java
.lang
.reflect
.Array
;
5 import java
.lang
.reflect
.Constructor
;
6 import java
.lang
.reflect
.Field
;
7 import java
.util
.ArrayList
;
8 import java
.util
.HashMap
;
10 import java
.util
.UnknownFormatConversionException
;
13 * Small class to help with serialisation.
15 * Note that we do not support inner classes (but we do support nested classes)
16 * and all objects require an empty constructor to be deserialised.
20 public class SerialUtils
{
21 private static Map
<String
, CustomSerializer
> customTypes
;
24 customTypes
= new HashMap
<String
, CustomSerializer
>();
25 // TODO: add "default" custom serialisers if any (Bitmap?)
28 customTypes
.put("[]", new CustomSerializer() {
30 protected String
toString(Object value
) {
31 String type
= value
.getClass().getCanonicalName();
32 type
= type
.substring(0, type
.length() - 2); // remove the []
34 StringBuilder builder
= new StringBuilder();
35 builder
.append(type
).append("\n");
37 for (int i
= 0; true; i
++) {
38 Object item
= Array
.get(value
, i
);
39 // encode it normally if direct value
40 if (!SerialUtils
.encode(builder
, item
)) {
43 builder
.append(new Exporter().append(item
)
45 } catch (NotSerializableException e
) {
46 throw new UnknownFormatConversionException(e
52 } catch (ArrayIndexOutOfBoundsException e
) {
56 return builder
.toString();
60 protected String
getType() {
65 protected Object
fromString(String content
) {
66 String
[] tab
= content
.split("\n");
69 Object array
= Array
.newInstance(
70 SerialUtils
.getClass(tab
[0]), tab
.length
- 1);
71 for (int i
= 1; i
< tab
.length
; i
++) {
72 Object value
= new Importer().read(tab
[i
]).getValue();
73 Array
.set(array
, i
- 1, value
);
77 } catch (Exception e
) {
78 throw new UnknownFormatConversionException(e
.getMessage());
85 * Create an empty object of the given type.
88 * the object type (its class name)
90 * @return the new object
92 * @throws ClassNotFoundException
93 * if the class cannot be found
94 * @throws NoSuchMethodException
95 * if the given class is not compatible with this code
97 public static Object
createObject(String type
)
98 throws ClassNotFoundException
, NoSuchMethodException
{
101 Class
<?
> clazz
= getClass(type
);
102 String className
= clazz
.getName();
103 Object
[] args
= null;
104 Constructor
<?
> ctor
= null;
105 if (className
.contains("$")) {
106 Object javaParent
= createObject(className
.substring(0,
107 className
.lastIndexOf('$')));
108 args
= new Object
[] { javaParent
};
109 ctor
= clazz
.getDeclaredConstructor(new Class
[] { javaParent
112 args
= new Object
[] {};
113 ctor
= clazz
.getDeclaredConstructor();
116 ctor
.setAccessible(true);
117 return ctor
.newInstance(args
);
118 } catch (ClassNotFoundException e
) {
120 } catch (NoSuchMethodException e
) {
122 } catch (Exception e
) {
123 throw new NoSuchMethodException("Cannot instantiate: " + type
);
128 * Insert a custom serialiser that will take precedence over the default one
129 * or the target class.
132 * the custom serialiser
134 static public void addCustomSerializer(CustomSerializer serializer
) {
135 customTypes
.put(serializer
.getType(), serializer
);
139 * Serialise the given object into this {@link StringBuilder}.
141 * <b>Important: </b>If the operation fails (with a
142 * {@link NotSerializableException}), the {@link StringBuilder} will be
143 * corrupted (will contain bad, most probably not importable data).
146 * the output {@link StringBuilder} to serialise to
148 * the object to serialise
150 * the map of already serialised objects (if the given object or
151 * one of its descendant is already present in it, only an ID
152 * will be serialised)
154 * @throws NotSerializableException
155 * if the object cannot be serialised (in this case, the
156 * {@link StringBuilder} can contain bad, most probably not
159 static void append(StringBuilder builder
, Object o
, Map
<Integer
, Object
> map
)
160 throws NotSerializableException
{
162 Field
[] fields
= new Field
[] {};
167 int hash
= System
.identityHashCode(o
);
168 fields
= o
.getClass().getDeclaredFields();
169 type
= o
.getClass().getCanonicalName();
171 throw new NotSerializableException(
173 "Cannot find the class for this object: %s (it could be an inner class, which is not supported)",
176 id
= Integer
.toString(hash
);
177 if (map
.containsKey(hash
)) {
178 fields
= new Field
[] {};
184 builder
.append("{\nREF ").append(type
).append("@").append(id
)
186 if (!encode(builder
, o
)) { // check if direct value
188 for (Field field
: fields
) {
189 field
.setAccessible(true);
191 if (field
.getName().startsWith("this$")) {
192 // Do not keep this links of nested classes
196 builder
.append("\n");
197 builder
.append(field
.getName());
201 value
= field
.get(o
);
203 if (!encode(builder
, value
)) {
204 builder
.append("\n");
205 append(builder
, value
, map
);
208 } catch (IllegalArgumentException e
) {
209 e
.printStackTrace(); // should not happen (see
211 } catch (IllegalAccessException e
) {
212 e
.printStackTrace(); // should not happen (see
216 builder
.append("\n}");
219 // return true if encoded (supported)
220 static boolean encode(StringBuilder builder
, Object value
) {
222 builder
.append("NULL");
223 } else if (value
.getClass().getCanonicalName().endsWith("[]")) {
224 customTypes
.get("[]").encode(builder
, value
);
225 } else if (customTypes
.containsKey(value
.getClass().getCanonicalName())) {
226 customTypes
.get(value
.getClass().getCanonicalName())//
227 .encode(builder
, value
);
228 } else if (value
instanceof String
) {
229 encodeString(builder
, (String
) value
);
230 } else if (value
instanceof Boolean
) {
231 builder
.append(value
);
232 } else if (value
instanceof Byte
) {
233 builder
.append(value
).append('b');
234 } else if (value
instanceof Character
) {
235 encodeString(builder
, "" + value
);
237 } else if (value
instanceof Short
) {
238 builder
.append(value
).append('s');
239 } else if (value
instanceof Integer
) {
240 builder
.append(value
);
241 } else if (value
instanceof Long
) {
242 builder
.append(value
).append('L');
243 } else if (value
instanceof Float
) {
244 builder
.append(value
).append('F');
245 } else if (value
instanceof Double
) {
246 builder
.append(value
).append('d');
254 static Object
decode(String encodedValue
) {
256 if (encodedValue
.length() > 1) {
257 cut
= encodedValue
.substring(0, encodedValue
.length() - 1);
260 if (CustomSerializer
.isCustom(encodedValue
)) {
261 // custom:TYPE_NAME:"content is String-encoded"
262 String type
= CustomSerializer
.typeOf(encodedValue
);
263 if (customTypes
.containsKey(type
)) {
264 return customTypes
.get(type
).decode(encodedValue
);
266 throw new UnknownFormatConversionException(
267 "Unknown custom type: " + type
);
269 } else if (encodedValue
.equals("NULL") || encodedValue
.equals("null")) {
271 } else if (encodedValue
.endsWith("\"")) {
272 return decodeString(encodedValue
);
273 } else if (encodedValue
.equals("true")) {
275 } else if (encodedValue
.equals("false")) {
277 } else if (encodedValue
.endsWith("b")) {
278 return Byte
.parseByte(cut
);
279 } else if (encodedValue
.endsWith("c")) {
280 return decodeString(cut
).charAt(0);
281 } else if (encodedValue
.endsWith("s")) {
282 return Short
.parseShort(cut
);
283 } else if (encodedValue
.endsWith("L")) {
284 return Long
.parseLong(cut
);
285 } else if (encodedValue
.endsWith("F")) {
286 return Float
.parseFloat(cut
);
287 } else if (encodedValue
.endsWith("d")) {
288 return Double
.parseDouble(cut
);
290 return Integer
.parseInt(encodedValue
);
295 * Return the corresponding class or throw an {@link Exception} if it
299 * the class name to look for
301 * @return the class (will never be NULL)
303 * @throws ClassNotFoundException
304 * if the class cannot be found
305 * @throws NoSuchMethodException
306 * if the class cannot be created (usually because it or its
307 * enclosing class doesn't have an empty constructor)
309 static private Class
<?
> getClass(String type
)
310 throws ClassNotFoundException
, NoSuchMethodException
{
311 Class
<?
> clazz
= null;
313 clazz
= Class
.forName(type
);
314 } catch (ClassNotFoundException e
) {
315 int pos
= type
.length();
316 pos
= type
.lastIndexOf(".", pos
);
318 String parentType
= type
.substring(0, pos
);
319 String nestedType
= type
.substring(pos
+ 1);
320 Class
<?
> javaParent
= null;
322 javaParent
= getClass(parentType
);
323 parentType
= javaParent
.getName();
324 clazz
= Class
.forName(parentType
+ "$" + nestedType
);
325 } catch (Exception ee
) {
328 if (javaParent
== null) {
329 throw new NoSuchMethodException(
332 + " (the enclosing class cannot be created: maybe it doesn't have an empty constructor?)");
338 throw new ClassNotFoundException("Class not found: " + type
);
345 private static void encodeString(StringBuilder builder
, String raw
) {
346 builder
.append('\"');
347 for (char car
: raw
.toCharArray()) {
350 builder
.append("\\\\");
353 builder
.append("\\r");
356 builder
.append("\\n");
359 builder
.append("\\\"");
366 builder
.append('\"');
370 private static String
decodeString(String escaped
) {
371 StringBuilder builder
= new StringBuilder();
373 boolean escaping
= false;
374 for (char car
: escaped
.toCharArray()) {
384 builder
.append('\\');
387 builder
.append('\r');
390 builder
.append('\n');
400 return builder
.substring(1, builder
.length() - 1).toString();