496fcb16baceaa3123386f612feaa899aff52f6a
[nikiroo-utils.git] / src / be / nikiroo / utils / serial / CustomSerializer.java
1 package be.nikiroo.utils.serial;
2
3 import java.io.IOException;
4 import java.io.InputStream;
5 import java.io.OutputStream;
6
7 import be.nikiroo.utils.IOUtils;
8 import be.nikiroo.utils.streams.BufferedInputStream;
9 import be.nikiroo.utils.streams.NextableInputStream;
10 import be.nikiroo.utils.streams.NextableInputStreamStep;
11 import be.nikiroo.utils.streams.ReplaceInputStream;
12 import be.nikiroo.utils.streams.ReplaceOutputStream;
13
14 /**
15 * A {@link CustomSerializer} supports and generates values in the form:
16 * <ul>
17 * <li><tt>custom^<i>TYPE</i>^<i>ENCODED_VALUE</i></tt></li>
18 * </ul>
19 * <p>
20 * In this scheme, the values are:
21 * <ul>
22 * <li><tt>custom</tt>: a fixed keyword</li>
23 * <li><tt>^</tt>: a fixed separator character (the
24 * <tt><i>ENCODED_VALUE</i></tt> can still use it inside its content, though</li>
25 * <li><tt><i>TYPE</i></tt>: the object type of this value</li>
26 * <li><tt><i>ENCODED_VALUE</i></tt>: the custom encoded value</li>
27 * </ul>
28 * <p>
29 * To create a new {@link CustomSerializer}, you are expected to implement the
30 * abstract methods of this class. The rest should be taken care of bythe
31 * system.
32 *
33 * @author niki
34 */
35 public abstract class CustomSerializer {
36 /**
37 * Generate the custom <tt><i>ENCODED_VALUE</i></tt> from this
38 * <tt>value</tt>.
39 * <p>
40 * The <tt>value</tt> will always be of the supported type.
41 *
42 * @param out
43 * the {@link OutputStream} to write the value to
44 * @param value
45 * the value to serialize
46 *
47 * @throws IOException
48 * in case of I/O error
49 */
50 protected abstract void toStream(OutputStream out, Object value)
51 throws IOException;
52
53 /**
54 * Regenerate the value from the custom <tt><i>ENCODED_VALUE</i></tt>.
55 * <p>
56 * The value in the {@link InputStream} <tt>in</tt> will always be of the
57 * supported type.
58 *
59 * @param in
60 * the {@link InputStream} containing the
61 * <tt><i>ENCODED_VALUE</i></tt>
62 *
63 * @return the regenerated object
64 *
65 * @throws IOException
66 * in case of I/O error
67 */
68 protected abstract Object fromStream(InputStream in) throws IOException;
69
70 /**
71 * Return the supported type name.
72 * <p>
73 * It <b>must</b> be the name returned by {@link Object#getClass()
74 * #getCanonicalName()}.
75 *
76 * @return the supported class name
77 */
78 protected abstract String getType();
79
80 /**
81 * Encode the object into the given {@link OutputStream}.
82 *
83 * @param out
84 * the builder to append to
85 * @param value
86 * the object to encode
87 *
88 * @throws IOException
89 * in case of I/O error
90 */
91 public void encode(OutputStream out, Object value) throws IOException {
92 ReplaceOutputStream replace = new ReplaceOutputStream(out, //
93 new String[] { "\\", "\n" }, //
94 new String[] { "\\\\", "\\n" });
95
96 try {
97 SerialUtils.write(replace, "custom^");
98 SerialUtils.write(replace, getType());
99 SerialUtils.write(replace, "^");
100 toStream(replace, value);
101 } finally {
102 replace.close(false);
103 }
104 }
105
106 /**
107 * Decode the value back into the supported object type.
108 *
109 * @param in
110 * the encoded value
111 *
112 * @return the object
113 *
114 * @throws IOException
115 * in case of I/O error
116 */
117 public Object decode(InputStream in) throws IOException {
118 ReplaceInputStream replace = new ReplaceInputStream(in, //
119 new String[] { "\\\\", "\\n" }, //
120 new String[] { "\\", "\n" });
121
122 try {
123 NextableInputStream stream = new NextableInputStream(
124 replace.open(), new NextableInputStreamStep('^'));
125 try {
126 if (!stream.next()) {
127 throw new IOException(
128 "Cannot find the first custom^ element");
129 }
130
131 String custom = IOUtils.readSmallStream(stream);
132 if (!"custom".equals(custom)) {
133 throw new IOException(
134 "Cannot find the first custom^ element, it is: "
135 + custom + "^");
136 }
137
138 if (!stream.next()) {
139 throw new IOException("Cannot find the second custom^"
140 + getType() + " element");
141 }
142
143 String type = IOUtils.readSmallStream(stream);
144 if (!getType().equals(type)) {
145 throw new IOException("Cannot find the second custom^"
146 + getType() + " element, it is: custom^" + type
147 + "^");
148 }
149
150 if (!stream.nextAll()) {
151 throw new IOException("Cannot find the third custom^"
152 + getType() + "^value element");
153 }
154
155 return fromStream(stream);
156 } finally {
157 stream.close();
158 }
159 } finally {
160 replace.close(false);
161 }
162 }
163
164 /** Use {@link CustomSerializer#isCustom(BufferedInputStream)}. */
165 @Deprecated
166 public static boolean isCustom(String encodedValue) {
167 int pos1 = encodedValue.indexOf('^');
168 int pos2 = encodedValue.indexOf('^', pos1 + 1);
169
170 return pos1 >= 0 && pos2 >= 0 && encodedValue.startsWith("custom^");
171 }
172
173 public static boolean isCustom(BufferedInputStream in) throws IOException {
174 return in.startsWith("custom^");
175 }
176
177 public static String typeOf(String encodedValue) {
178 int pos1 = encodedValue.indexOf('^');
179 int pos2 = encodedValue.indexOf('^', pos1 + 1);
180 String type = encodedValue.substring(pos1 + 1, pos2);
181
182 return type;
183 }
184 }