1 package be
.nikiroo
.utils
.serial
;
3 import java
.awt
.image
.BufferedImage
;
4 import java
.io
.IOException
;
5 import java
.io
.NotSerializableException
;
6 import java
.lang
.reflect
.Array
;
7 import java
.lang
.reflect
.Constructor
;
8 import java
.lang
.reflect
.Field
;
9 import java
.lang
.reflect
.Modifier
;
10 import java
.util
.HashMap
;
12 import java
.util
.UnknownFormatConversionException
;
14 import be
.nikiroo
.utils
.StringUtils
;
17 * Small class to help with serialisation.
19 * Note that we do not support inner classes (but we do support nested classes)
20 * and all objects require an empty constructor to be deserialised.
24 public class SerialUtils
{
25 private static Map
<String
, CustomSerializer
> customTypes
;
28 customTypes
= new HashMap
<String
, CustomSerializer
>();
31 customTypes
.put("[]", new CustomSerializer() {
33 protected String
toString(Object value
) {
34 String type
= value
.getClass().getCanonicalName();
35 type
= type
.substring(0, type
.length() - 2); // remove the []
37 StringBuilder builder
= new StringBuilder();
38 builder
.append(type
).append("\n");
40 for (int i
= 0; true; i
++) {
41 Object item
= Array
.get(value
, i
);
42 // encode it normally if direct value
43 if (!SerialUtils
.encode(builder
, item
)) {
46 builder
.append(new Exporter().append(item
)
48 } catch (NotSerializableException e
) {
49 throw new UnknownFormatConversionException(e
55 } catch (ArrayIndexOutOfBoundsException e
) {
59 return builder
.toString();
63 protected String
getType() {
68 protected Object
fromString(String content
) {
69 String
[] tab
= content
.split("\n");
72 Object array
= Array
.newInstance(
73 SerialUtils
.getClass(tab
[0]), tab
.length
- 1);
74 for (int i
= 1; i
< tab
.length
; i
++) {
75 Object value
= new Importer().read(tab
[i
]).getValue();
76 Array
.set(array
, i
- 1, value
);
80 } catch (Exception e
) {
81 throw new UnknownFormatConversionException(e
.getMessage());
86 // Images (this is currently the only supported image type by default)
87 customTypes
.put("java.awt.image.BufferedImage", new CustomSerializer() {
89 protected String
toString(Object value
) {
91 return StringUtils
.fromImage((BufferedImage
) value
);
92 } catch (IOException e
) {
93 throw new UnknownFormatConversionException(e
.getMessage());
98 protected String
getType() {
99 return "java.awt.image.BufferedImage";
103 protected Object
fromString(String content
) {
105 return StringUtils
.toImage(content
);
106 } catch (IOException e
) {
107 throw new UnknownFormatConversionException(e
.getMessage());
114 * Create an empty object of the given type.
117 * the object type (its class name)
119 * @return the new object
121 * @throws ClassNotFoundException
122 * if the class cannot be found
123 * @throws NoSuchMethodException
124 * if the given class is not compatible with this code
126 public static Object
createObject(String type
)
127 throws ClassNotFoundException
, NoSuchMethodException
{
130 Class
<?
> clazz
= getClass(type
);
131 String className
= clazz
.getName();
132 Object
[] args
= null;
133 Constructor
<?
> ctor
= null;
134 if (className
.contains("$")) {
135 Object javaParent
= createObject(className
.substring(0,
136 className
.lastIndexOf('$')));
137 args
= new Object
[] { javaParent
};
138 ctor
= clazz
.getDeclaredConstructor(new Class
[] { javaParent
141 args
= new Object
[] {};
142 ctor
= clazz
.getDeclaredConstructor();
145 ctor
.setAccessible(true);
146 return ctor
.newInstance(args
);
147 } catch (ClassNotFoundException e
) {
149 } catch (NoSuchMethodException e
) {
151 } catch (Exception e
) {
152 throw new NoSuchMethodException("Cannot instantiate: " + type
);
157 * Insert a custom serialiser that will take precedence over the default one
158 * or the target class.
161 * the custom serialiser
163 static public void addCustomSerializer(CustomSerializer serializer
) {
164 customTypes
.put(serializer
.getType(), serializer
);
168 * Serialise the given object into this {@link StringBuilder}.
170 * <b>Important: </b>If the operation fails (with a
171 * {@link NotSerializableException}), the {@link StringBuilder} will be
172 * corrupted (will contain bad, most probably not importable data).
175 * the output {@link StringBuilder} to serialise to
177 * the object to serialise
179 * the map of already serialised objects (if the given object or
180 * one of its descendant is already present in it, only an ID
181 * will be serialised)
183 * @throws NotSerializableException
184 * if the object cannot be serialised (in this case, the
185 * {@link StringBuilder} can contain bad, most probably not
188 static void append(StringBuilder builder
, Object o
, Map
<Integer
, Object
> map
)
189 throws NotSerializableException
{
191 Field
[] fields
= new Field
[] {};
196 int hash
= System
.identityHashCode(o
);
197 fields
= o
.getClass().getDeclaredFields();
198 type
= o
.getClass().getCanonicalName();
200 throw new NotSerializableException(
202 "Cannot find the class for this object: %s (it could be an inner class, which is not supported)",
205 id
= Integer
.toString(hash
);
206 if (map
.containsKey(hash
)) {
207 fields
= new Field
[] {};
213 builder
.append("{\nREF ").append(type
).append("@").append(id
)
215 if (!encode(builder
, o
)) { // check if direct value
217 for (Field field
: fields
) {
218 field
.setAccessible(true);
220 if (field
.getName().startsWith("this$")
221 || field
.isSynthetic()
222 || (field
.getModifiers() & Modifier
.STATIC
) == Modifier
.STATIC
) {
223 // Do not keep this links of nested classes
224 // Do not keep synthetic fields
225 // Do not keep final fields
229 builder
.append("\n");
230 builder
.append(field
.getName());
234 value
= field
.get(o
);
236 if (!encode(builder
, value
)) {
237 builder
.append("\n");
238 append(builder
, value
, map
);
241 } catch (IllegalArgumentException e
) {
242 e
.printStackTrace(); // should not happen (see
244 } catch (IllegalAccessException e
) {
245 e
.printStackTrace(); // should not happen (see
249 builder
.append("\n}");
252 // return true if encoded (supported)
253 static boolean encode(StringBuilder builder
, Object value
) {
255 builder
.append("NULL");
256 } else if (value
.getClass().getCanonicalName().endsWith("[]")) {
257 customTypes
.get("[]").encode(builder
, value
);
258 } else if (customTypes
.containsKey(value
.getClass().getCanonicalName())) {
259 customTypes
.get(value
.getClass().getCanonicalName())//
260 .encode(builder
, value
);
261 } else if (value
instanceof String
) {
262 encodeString(builder
, (String
) value
);
263 } else if (value
instanceof Boolean
) {
264 builder
.append(value
);
265 } else if (value
instanceof Byte
) {
266 builder
.append(value
).append('b');
267 } else if (value
instanceof Character
) {
268 encodeString(builder
, "" + value
);
270 } else if (value
instanceof Short
) {
271 builder
.append(value
).append('s');
272 } else if (value
instanceof Integer
) {
273 builder
.append(value
);
274 } else if (value
instanceof Long
) {
275 builder
.append(value
).append('L');
276 } else if (value
instanceof Float
) {
277 builder
.append(value
).append('F');
278 } else if (value
instanceof Double
) {
279 builder
.append(value
).append('d');
280 } else if (value
instanceof Enum
) {
281 String type
= value
.getClass().getCanonicalName();
282 builder
.append(type
).append(".").append(((Enum
<?
>) value
).name())
291 static Object
decode(String encodedValue
) {
293 if (encodedValue
.length() > 1) {
294 cut
= encodedValue
.substring(0, encodedValue
.length() - 1);
297 if (CustomSerializer
.isCustom(encodedValue
)) {
298 // custom:TYPE_NAME:"content is String-encoded"
299 String type
= CustomSerializer
.typeOf(encodedValue
);
300 if (customTypes
.containsKey(type
)) {
301 return customTypes
.get(type
).decode(encodedValue
);
303 throw new UnknownFormatConversionException(
304 "Unknown custom type: " + type
);
306 } else if (encodedValue
.equals("NULL") || encodedValue
.equals("null")) {
308 } else if (encodedValue
.endsWith("\"")) {
309 return decodeString(encodedValue
);
310 } else if (encodedValue
.equals("true")) {
312 } else if (encodedValue
.equals("false")) {
314 } else if (encodedValue
.endsWith("b")) {
315 return Byte
.parseByte(cut
);
316 } else if (encodedValue
.endsWith("c")) {
317 return decodeString(cut
).charAt(0);
318 } else if (encodedValue
.endsWith("s")) {
319 return Short
.parseShort(cut
);
320 } else if (encodedValue
.endsWith("L")) {
321 return Long
.parseLong(cut
);
322 } else if (encodedValue
.endsWith("F")) {
323 return Float
.parseFloat(cut
);
324 } else if (encodedValue
.endsWith("d")) {
325 return Double
.parseDouble(cut
);
326 } else if (encodedValue
.endsWith(";")) {
327 return decodeEnum(encodedValue
);
329 return Integer
.parseInt(encodedValue
);
334 * Return the corresponding class or throw an {@link Exception} if it
338 * the class name to look for
340 * @return the class (will never be NULL)
342 * @throws ClassNotFoundException
343 * if the class cannot be found
344 * @throws NoSuchMethodException
345 * if the class cannot be created (usually because it or its
346 * enclosing class doesn't have an empty constructor)
348 static private Class
<?
> getClass(String type
)
349 throws ClassNotFoundException
, NoSuchMethodException
{
350 Class
<?
> clazz
= null;
352 clazz
= Class
.forName(type
);
353 } catch (ClassNotFoundException e
) {
354 int pos
= type
.length();
355 pos
= type
.lastIndexOf(".", pos
);
357 String parentType
= type
.substring(0, pos
);
358 String nestedType
= type
.substring(pos
+ 1);
359 Class
<?
> javaParent
= null;
361 javaParent
= getClass(parentType
);
362 parentType
= javaParent
.getName();
363 clazz
= Class
.forName(parentType
+ "$" + nestedType
);
364 } catch (Exception ee
) {
367 if (javaParent
== null) {
368 throw new NoSuchMethodException(
371 + " (the enclosing class cannot be created: maybe it doesn't have an empty constructor?)");
377 throw new ClassNotFoundException("Class not found: " + type
);
383 @SuppressWarnings({ "unchecked", "rawtypes" })
384 private static Enum
<?
> decodeEnum(String escaped
) {
385 // escaped: be.xxx.EnumType.VALUE;
386 int pos
= escaped
.lastIndexOf(".");
387 String type
= escaped
.substring(0, pos
);
388 String name
= escaped
.substring(pos
+ 1, escaped
.length() - 1);
391 return Enum
.valueOf((Class
<Enum
>) getClass(type
), name
);
392 } catch (Exception e
) {
394 throw new UnknownFormatConversionException("Unknown enum: <" + type
400 private static void encodeString(StringBuilder builder
, String raw
) {
401 builder
.append('\"');
402 for (char car
: raw
.toCharArray()) {
405 builder
.append("\\\\");
408 builder
.append("\\r");
411 builder
.append("\\n");
414 builder
.append("\\\"");
421 builder
.append('\"');
425 private static String
decodeString(String escaped
) {
426 StringBuilder builder
= new StringBuilder();
428 boolean escaping
= false;
429 for (char car
: escaped
.toCharArray()) {
439 builder
.append('\\');
442 builder
.append('\r');
445 builder
.append('\n');
455 return builder
.substring(1, builder
.length() - 1).toString();