1 package be
.nikiroo
.utils
.serial
;
3 import java
.io
.NotSerializableException
;
4 import java
.lang
.reflect
.Constructor
;
5 import java
.lang
.reflect
.Field
;
6 import java
.util
.HashMap
;
10 * Small class to help with serialisation.
12 * Note that we do not support inner classes (but we do support nested classes)
13 * and all objects require an empty constructor to be deserialised.
17 public class SerialUtils
{
18 private static Map
<String
, CustomSerializer
> customTypes
;
21 customTypes
= new HashMap
<String
, CustomSerializer
>();
22 // TODO: add "default" custom serialisers if any (Bitmap?)
26 * Create an empty object of the given type.
29 * the object type (its class name)
31 * @return the new object
33 * @throws ClassNotFoundException
34 * if the class cannot be found
35 * @throws NoSuchMethodException
36 * if the given class is not compatible with this code
38 public static Object
createObject(String type
)
39 throws ClassNotFoundException
, NoSuchMethodException
{
42 Class
<?
> clazz
= getClass(type
);
43 String className
= clazz
.getName();
45 Constructor
<?
> ctor
= null;
46 if (className
.contains("$")) {
47 Object javaParent
= createObject(className
.substring(0,
48 className
.lastIndexOf('$')));
49 args
= new Object
[] { javaParent
};
50 ctor
= clazz
.getDeclaredConstructor(new Class
[] { javaParent
53 args
= new Object
[] {};
54 ctor
= clazz
.getDeclaredConstructor();
57 ctor
.setAccessible(true);
58 return ctor
.newInstance(args
);
59 } catch (ClassNotFoundException e
) {
61 } catch (NoSuchMethodException e
) {
63 } catch (Exception e
) {
64 throw new NoSuchMethodException("Cannot instantiate: " + type
);
69 * Insert a custom serialiser that will take precedence over the default one
70 * or the target class.
73 * the custom serialiser
75 static public void addCustomSerializer(CustomSerializer serializer
) {
76 customTypes
.put(serializer
.getType(), serializer
);
80 * Serialise the given object into this {@link StringBuilder}.
82 * <b>Important: </b>If the operation fails (with a
83 * {@link NotSerializableException}), the {@link StringBuilder} will be
84 * corrupted (will contain bad, most probably not importable data).
87 * the output {@link StringBuilder} to serialise to
89 * the object to serialise
91 * the map of already serialised objects (if the given object or
92 * one of its descendant is already present in it, only an ID
95 * @throws NotSerializableException
96 * if the object cannot be serialised (in this case, the
97 * {@link StringBuilder} can contain bad, most probably not
100 static void append(StringBuilder builder
, Object o
, Map
<Integer
, Object
> map
)
101 throws NotSerializableException
{
103 Field
[] fields
= new Field
[] {};
108 int hash
= System
.identityHashCode(o
);
109 fields
= o
.getClass().getDeclaredFields();
110 type
= o
.getClass().getCanonicalName();
112 throw new NotSerializableException(
114 "Cannot find the class for this object: %s (it could be an inner class, which is not supported)",
117 id
= Integer
.toString(hash
);
118 if (map
.containsKey(hash
)) {
119 fields
= new Field
[] {};
125 builder
.append("{\nREF ").append(type
).append("@").append(id
);
127 for (Field field
: fields
) {
128 field
.setAccessible(true);
130 if (field
.getName().startsWith("this$")) {
131 // Do not keep this links of nested classes
135 builder
.append("\n");
136 builder
.append(field
.getName());
140 value
= field
.get(o
);
142 if (!encode(builder
, value
)) {
143 builder
.append("\n");
144 append(builder
, value
, map
);
147 } catch (IllegalArgumentException e
) {
148 e
.printStackTrace(); // should not happen (see setAccessible)
149 } catch (IllegalAccessException e
) {
150 e
.printStackTrace(); // should not happen (see setAccessible)
152 builder
.append("\n}");
155 // return true if encoded (supported)
156 static boolean encode(StringBuilder builder
, Object value
) {
158 builder
.append("NULL");
159 } else if (customTypes
.containsKey(value
.getClass().getCanonicalName())) {
160 customTypes
.get(value
.getClass().getCanonicalName())//
161 .encode(builder
, value
);
162 } else if (value
instanceof String
) {
163 encodeString(builder
, (String
) value
);
164 } else if (value
instanceof Boolean
) {
165 builder
.append(value
);
166 } else if (value
instanceof Byte
) {
167 builder
.append(value
).append('b');
168 } else if (value
instanceof Character
) {
169 encodeString(builder
, (String
) value
);
171 } else if (value
instanceof Short
) {
172 builder
.append(value
).append('s');
173 } else if (value
instanceof Integer
) {
174 builder
.append(value
);
175 } else if (value
instanceof Long
) {
176 builder
.append(value
).append('L');
177 } else if (value
instanceof Float
) {
178 builder
.append(value
).append('F');
179 } else if (value
instanceof Double
) {
180 builder
.append(value
).append('d');
188 static Object
decode(String encodedValue
) {
190 if (encodedValue
.length() > 1) {
191 cut
= encodedValue
.substring(0, encodedValue
.length() - 1);
194 if (CustomSerializer
.isCustom(encodedValue
)) {
195 // custom:TYPE_NAME:"content is String-encoded"
196 String type
= CustomSerializer
.typeOf(encodedValue
);
197 if (customTypes
.containsKey(type
)) {
198 return customTypes
.get(type
).decode(encodedValue
);
200 throw new java
.util
.UnknownFormatConversionException(
201 "Unknown custom type: " + type
);
203 } else if (encodedValue
.equals("NULL") || encodedValue
.equals("null")) {
205 } else if (encodedValue
.endsWith("\"")) {
206 return decodeString(encodedValue
);
207 } else if (encodedValue
.equals("true")) {
209 } else if (encodedValue
.equals("false")) {
211 } else if (encodedValue
.endsWith("b")) {
212 return Byte
.parseByte(cut
);
213 } else if (encodedValue
.endsWith("c")) {
214 return decodeString(cut
).charAt(0);
215 } else if (encodedValue
.endsWith("s")) {
216 return Short
.parseShort(cut
);
217 } else if (encodedValue
.endsWith("L")) {
218 return Long
.parseLong(cut
);
219 } else if (encodedValue
.endsWith("F")) {
220 return Float
.parseFloat(cut
);
221 } else if (encodedValue
.endsWith("d")) {
222 return Double
.parseDouble(cut
);
224 return Integer
.parseInt(encodedValue
);
229 * Return the corresponding class or throw an {@link Exception} if it
233 * the class name to look for
235 * @return the class (will never be NULL)
237 * @throws ClassNotFoundException
238 * if the class cannot be found
239 * @throws NoSuchMethodException
240 * if the class cannot be created (usually because it or its
241 * enclosing class doesn't have an empty constructor)
243 static private Class
<?
> getClass(String type
)
244 throws ClassNotFoundException
, NoSuchMethodException
{
245 Class
<?
> clazz
= null;
247 clazz
= Class
.forName(type
);
248 } catch (ClassNotFoundException e
) {
249 int pos
= type
.length();
250 pos
= type
.lastIndexOf(".", pos
);
252 String parentType
= type
.substring(0, pos
);
253 String nestedType
= type
.substring(pos
+ 1);
254 Class
<?
> javaParent
= null;
256 javaParent
= getClass(parentType
);
257 parentType
= javaParent
.getName();
258 clazz
= Class
.forName(parentType
+ "$" + nestedType
);
259 } catch (Exception ee
) {
262 if (javaParent
== null) {
263 throw new NoSuchMethodException(
266 + " (the enclosing class cannot be created: maybe it doesn't have an empty constructor?)");
272 throw new ClassNotFoundException("Class not found: " + type
);
279 private static void encodeString(StringBuilder builder
, String raw
) {
280 builder
.append('\"');
281 for (char car
: raw
.toCharArray()) {
284 builder
.append("\\\\");
287 builder
.append("\\r");
290 builder
.append("\\n");
293 builder
.append("\\\"");
300 builder
.append('\"');
304 private static String
decodeString(String escaped
) {
305 StringBuilder builder
= new StringBuilder();
307 boolean escaping
= false;
308 for (char car
: escaped
.toCharArray()) {
318 builder
.append('\\');
321 builder
.append('\r');
324 builder
.append('\n');
334 return builder
.substring(1, builder
.length() - 1).toString();