+
+ // Array types:
+ customTypes.put("[]", new CustomSerializer() {
+ @Override
+ protected void toStream(OutputStream out, Object value)
+ throws IOException {
+
+ // TODO: we use \n to separate, and b64 to un-\n
+ // -- but we could use \\n ?
+ String type = value.getClass().getCanonicalName();
+ type = type.substring(0, type.length() - 2); // remove the []
+
+ write(out, type);
+ try {
+ for (int i = 0; true; i++) {
+ Object item = Array.get(value, i);
+
+ // encode it normally if direct value
+ write(out, "\r");
+ if (!SerialUtils.encode(out, item)) {
+ try {
+ // TODO: bad escaping?
+ write(out, "B64:");
+ OutputStream bout = StringUtils.base64(out,
+ false, false);
+ new Exporter(bout).append(item);
+ } catch (NotSerializableException e) {
+ throw new UnknownFormatConversionException(e
+ .getMessage());
+ }
+ }
+ }
+ } catch (ArrayIndexOutOfBoundsException e) {
+ // Done.
+ }
+ }
+
+ @Override
+ protected Object fromStream(InputStream in) throws IOException {
+ NextableInputStream stream = new NextableInputStream(in,
+ new NextableInputStreamStep('\r'));
+
+ try {
+ List<Object> list = new ArrayList<Object>();
+ stream.next();
+ String type = IOUtils.readSmallStream(stream);
+
+ while (stream.next()) {
+ Object value = new Importer().read(stream).getValue();
+ list.add(value);
+ }
+
+ Object array = Array.newInstance(
+ SerialUtils.getClass(type), list.size());
+ for (int i = 0; i < list.size(); i++) {
+ Array.set(array, i, list.get(i));
+ }
+
+ return array;
+ } catch (Exception e) {
+ if (e instanceof IOException) {
+ throw (IOException) e;
+ }
+ throw new IOException(e.getMessage());
+ }
+ }
+
+ @Override
+ protected String getType() {
+ return "[]";
+ }
+ });
+
+ // URL:
+ customTypes.put("java.net.URL", new CustomSerializer() {
+ @Override
+ protected void toStream(OutputStream out, Object value)
+ throws IOException {
+ String val = "";
+ if (value != null) {
+ val = ((URL) value).toString();
+ }
+
+ out.write(val.getBytes("UTF-8"));
+ }
+
+ @Override
+ protected Object fromStream(InputStream in) throws IOException {
+ String val = IOUtils.readSmallStream(in);
+ if (!val.isEmpty()) {
+ return new URL(val);
+ }
+
+ return null;
+ }
+
+ @Override
+ protected String getType() {
+ return "java.net.URL";
+ }
+ });
+
+ // Images (this is currently the only supported image type by default)
+ customTypes.put("be.nikiroo.utils.Image", new CustomSerializer() {
+ @Override
+ protected void toStream(OutputStream out, Object value)
+ throws IOException {
+ Image img = (Image) value;
+ OutputStream encoded = StringUtils.base64(out, false, false);
+ try {
+ InputStream in = img.newInputStream();
+ try {
+ IOUtils.write(in, encoded);
+ } finally {
+ in.close();
+ }
+ } finally {
+ encoded.flush();
+ // Cannot close!
+ }
+ }
+
+ @Override
+ protected String getType() {
+ return "be.nikiroo.utils.Image";
+ }
+
+ @Override
+ protected Object fromStream(InputStream in) throws IOException {
+ try {
+ // Cannot close it!
+ InputStream decoded = StringUtils.unbase64(in, false);
+ return new Image(decoded);
+ } catch (IOException e) {
+ throw new UnknownFormatConversionException(e.getMessage());
+ }
+ }
+ });
+ }
+
+ /**
+ * Create an empty object of the given type.
+ *
+ * @param type
+ * the object type (its class name)
+ *
+ * @return the new object
+ *
+ * @throws ClassNotFoundException
+ * if the class cannot be found
+ * @throws NoSuchMethodException
+ * if the given class is not compatible with this code
+ */
+ public static Object createObject(String type)
+ throws ClassNotFoundException, NoSuchMethodException {
+
+ String desc = null;
+ try {
+ Class<?> clazz = getClass(type);
+ String className = clazz.getName();
+ List<Object> args = new ArrayList<Object>();
+ List<Class<?>> classes = new ArrayList<Class<?>>();
+ Constructor<?> ctor = null;
+ if (className.contains("$")) {
+ for (String parentName = className.substring(0,
+ className.lastIndexOf('$'));; parentName = parentName
+ .substring(0, parentName.lastIndexOf('$'))) {
+ Object parent = createObject(parentName);
+ args.add(parent);
+ classes.add(parent.getClass());
+
+ if (!parentName.contains("$")) {
+ break;
+ }
+ }
+
+ // Better error description in case there is no empty
+ // constructor:
+ desc = "";
+ String end = "";
+ for (Class<?> parent = clazz; parent != null
+ && !parent.equals(Object.class); parent = parent
+ .getSuperclass()) {
+ if (!desc.isEmpty()) {
+ desc += " [:";
+ end += "]";
+ }
+ desc += parent;
+ }
+ desc += end;
+ //
+
+ try {
+ ctor = clazz.getDeclaredConstructor(classes
+ .toArray(new Class[] {}));
+ } catch (NoSuchMethodException nsme) {
+ // TODO: it seems we do not always need a parameter for each
+ // level, so we currently try "ALL" levels or "FIRST" level
+ // only -> we should check the actual rule and use it
+ ctor = clazz.getDeclaredConstructor(classes.get(0));
+ Object firstParent = args.get(0);
+ args.clear();
+ args.add(firstParent);
+ }
+ desc = null;
+ } else {
+ ctor = clazz.getDeclaredConstructor();
+ }
+
+ ctor.setAccessible(true);
+ return ctor.newInstance(args.toArray());
+ } catch (ClassNotFoundException e) {
+ throw e;
+ } catch (NoSuchMethodException e) {
+ if (desc != null) {
+ throw new NoSuchMethodException("Empty constructor not found: "
+ + desc);
+ }
+ throw e;
+ } catch (Exception e) {
+ throw new NoSuchMethodException("Cannot instantiate: " + type);
+ }