1 package be
.nikiroo
.utils
.serial
;
3 import java
.lang
.reflect
.Constructor
;
4 import java
.lang
.reflect
.Field
;
5 import java
.lang
.reflect
.InvocationTargetException
;
6 import java
.io
.NotSerializableException
;
7 import java
.lang
.reflect
.Field
;
8 import java
.util
.HashMap
;
12 * Small class to help serialise/deserialise objects.
14 * Note that we do not support inner classes (but we do support nested classes)
15 * and all objects require an empty constructor to be deserialised.
19 public class SerialUtils
{
20 private static Map
<String
, CustomSerializer
> customTypes
;
23 customTypes
= new HashMap
<String
, CustomSerializer
>();
24 // TODO: add "default" custom serialisers
28 * Create an empty object of the given type.
31 * the object type (its class name)
33 * @return the new object
35 * @throws NoSuchMethodException if the given class is not compatible with this code
36 * @throws ClassNotFoundException if the class cannot be found or created
38 public static Object
createObject(String type
) throws NoSuchMethodException
,
39 ClassNotFoundException
{
42 Class
<?
> clazz
= getClass(type
);
44 throw new ClassNotFoundException("Class not found: " + type
);
47 String className
= clazz
.getName();
49 Constructor
<?
> ctor
= null;
50 if (className
.contains("$")) {
51 Object javaParent
= createObject(className
.substring(0,
52 className
.lastIndexOf('$')));
53 args
= new Object
[] { javaParent
};
54 ctor
= clazz
.getDeclaredConstructor(new Class
[] { javaParent
57 args
= new Object
[] {};
58 ctor
= clazz
.getDeclaredConstructor();
61 ctor
.setAccessible(true);
62 return ctor
.newInstance(args
);
63 } catch (NoSuchMethodException e
) {
64 throw new NoSuchMethodException(
66 "Objects of type \"%s\" cannot be created by this code: maybe the class"
67 + " or its enclosing class doesn't have an empty constructor?",
71 catch (SecurityException e
) { throw new ClassNotFoundException("Cannot instantiate: " + type
, e
); }
72 catch (InstantiationException e
) { throw new ClassNotFoundException("Cannot instantiate: " + type
, e
); }
73 catch (IllegalAccessException e
) { throw new ClassNotFoundException("Cannot instantiate: " + type
, e
); }
74 catch (IllegalArgumentException e
) { throw new ClassNotFoundException("Cannot instantiate: " + type
, e
); }
75 catch (InvocationTargetException e
) { throw new ClassNotFoundException("Cannot instantiate: " + type
, e
); }
78 static public void addCustomSerializer(CustomSerializer serializer
) {
79 customTypes
.put(serializer
.getType(), serializer
);
82 static void append(StringBuilder builder
, Object o
, Map
<Integer
, Object
> map
)
83 throws NotSerializableException
{
85 Field
[] fields
= new Field
[] {};
90 int hash
= System
.identityHashCode(o
);
91 fields
= o
.getClass().getDeclaredFields();
92 type
= o
.getClass().getCanonicalName();
94 throw new NotSerializableException(
96 "Cannot find the class for this object: %s (it could be an inner class, which is not supported)",
99 id
= Integer
.toString(hash
);
100 if (map
.containsKey(hash
)) {
101 fields
= new Field
[] {};
107 builder
.append("{\nREF ").append(type
).append("@").append(id
);
109 for (Field field
: fields
) {
110 field
.setAccessible(true);
112 if (field
.getName().startsWith("this$")) {
113 // Do not keep this links of nested classes
117 builder
.append("\n");
118 builder
.append(field
.getName());
122 value
= field
.get(o
);
124 if (!encode(builder
, value
)) {
125 builder
.append("\n");
126 append(builder
, value
, map
);
129 } catch (IllegalArgumentException e
) {
130 e
.printStackTrace(); // should not happen (see setAccessible)
131 } catch (IllegalAccessException e
) {
132 e
.printStackTrace(); // should not happen (see setAccessible)
134 builder
.append("\n}");
137 // return true if encoded (supported)
138 static boolean encode(StringBuilder builder
, Object value
) {
140 builder
.append("NULL");
141 } else if (customTypes
.containsKey(value
.getClass().getCanonicalName())) {
142 customTypes
.get(value
.getClass().getCanonicalName())//
143 .encode(builder
, value
);
144 } else if (value
instanceof String
) {
145 encodeString(builder
, (String
) value
);
146 } else if (value
instanceof Boolean
) {
147 builder
.append(value
);
148 } else if (value
instanceof Byte
) {
149 builder
.append(value
).append('b');
150 } else if (value
instanceof Character
) {
151 encodeString(builder
, (String
) value
);
153 } else if (value
instanceof Short
) {
154 builder
.append(value
).append('s');
155 } else if (value
instanceof Integer
) {
156 builder
.append(value
);
157 } else if (value
instanceof Long
) {
158 builder
.append(value
).append('L');
159 } else if (value
instanceof Float
) {
160 builder
.append(value
).append('F');
161 } else if (value
instanceof Double
) {
162 builder
.append(value
).append('d');
170 static Object
decode(String encodedValue
) {
172 if (encodedValue
.length() > 1) {
173 cut
= encodedValue
.substring(0, encodedValue
.length() - 1);
176 if (CustomSerializer
.isCustom(encodedValue
)) {
177 // custom:TYPE_NAME:"content is String-encoded"
178 String type
= CustomSerializer
.typeOf(encodedValue
);
179 if (customTypes
.containsKey(type
)) {
180 return customTypes
.get(type
).decode(encodedValue
);
182 throw new java
.util
.UnknownFormatConversionException(
183 "Unknown custom type: " + type
);
185 } else if (encodedValue
.equals("NULL") || encodedValue
.equals("null")) {
187 } else if (encodedValue
.endsWith("\"")) {
188 return decodeString(encodedValue
);
189 } else if (encodedValue
.equals("true")) {
191 } else if (encodedValue
.equals("false")) {
193 } else if (encodedValue
.endsWith("b")) {
194 return Byte
.parseByte(cut
);
195 } else if (encodedValue
.endsWith("c")) {
196 return decodeString(cut
).charAt(0);
197 } else if (encodedValue
.endsWith("s")) {
198 return Short
.parseShort(cut
);
199 } else if (encodedValue
.endsWith("L")) {
200 return Long
.parseLong(cut
);
201 } else if (encodedValue
.endsWith("F")) {
202 return Float
.parseFloat(cut
);
203 } else if (encodedValue
.endsWith("d")) {
204 return Double
.parseDouble(cut
);
206 return Integer
.parseInt(encodedValue
);
210 static private Class
<?
> getClass(String type
) throws ClassNotFoundException
,
211 NoSuchMethodException
{
212 Class
<?
> clazz
= null;
214 clazz
= Class
.forName(type
);
215 } catch (ClassNotFoundException e
) {
216 int pos
= type
.length();
217 pos
= type
.lastIndexOf(".", pos
);
219 String parentType
= type
.substring(0, pos
);
220 String nestedType
= type
.substring(pos
+ 1);
221 Class
<?
> javaParent
= null;
223 javaParent
= getClass(parentType
);
224 parentType
= javaParent
.getName();
225 clazz
= Class
.forName(parentType
+ "$" + nestedType
);
226 } catch (Exception ee
) {
229 if (javaParent
== null) {
230 throw new NoSuchMethodException(
233 + " (the enclosing class cannot be created: maybe it doesn't have an empty constructor?)");
242 private static void encodeString(StringBuilder builder
, String raw
) {
243 builder
.append('\"');
244 for (char car
: raw
.toCharArray()) {
247 builder
.append("\\\\");
250 builder
.append("\\r");
253 builder
.append("\\n");
256 builder
.append("\\\"");
263 builder
.append('\"');
267 private static String
decodeString(String escaped
) {
268 StringBuilder builder
= new StringBuilder();
270 boolean escaping
= false;
271 for (char car
: escaped
.toCharArray()) {
281 builder
.append('\\');
284 builder
.append('\r');
287 builder
.append('\n');
297 return builder
.substring(1, builder
.length() - 1).toString();