001 // Copyright 2007-2008 Waterken Inc. under the terms of the MIT X license
002 // found at http://www.opensource.org/licenses/mit-license.html
003 package org.waterken.syntax.json;
004
005 import java.io.BufferedWriter;
006 import java.io.Serializable;
007 import java.io.Writer;
008 import java.lang.reflect.Array;
009 import java.lang.reflect.Field;
010 import java.lang.reflect.GenericArrayType;
011 import java.lang.reflect.Modifier;
012 import java.lang.reflect.Type;
013 import java.lang.reflect.TypeVariable;
014 import java.math.BigDecimal;
015 import java.math.BigInteger;
016
017 import org.joe_e.Struct;
018 import org.joe_e.array.ByteArray;
019 import org.joe_e.array.ConstArray;
020 import org.joe_e.array.PowerlessArray;
021 import org.joe_e.charset.UTF8;
022 import org.joe_e.reflect.Reflection;
023 import org.ref_send.Record;
024 import org.ref_send.promise.Eventual;
025 import org.ref_send.promise.Promise;
026 import org.ref_send.scope.Layout;
027 import org.ref_send.scope.Scope;
028 import org.ref_send.type.Typedef;
029 import org.waterken.syntax.Export;
030 import org.waterken.syntax.Exporter;
031 import org.waterken.syntax.NonFinalRecordField;
032 import org.waterken.syntax.Serializer;
033
034 /**
035 * Serializes an array of Java objects to a JSON byte stream.
036 */
037 public final class
038 JSONSerializer extends Struct implements Serializer, Record, Serializable {
039 static private final long serialVersionUID = 1L;
040
041 public ByteArray
042 serialize(final Exporter export,
043 final Type type, final Object value) throws Exception {
044 /*
045 * SECURITY CLAIM: Only the immutable root of the application object
046 * tree provided by the values argument is serialized. The Exporter is
047 * used to assign a URL to each mutable sub-tree. This constraint
048 * ensures that application objects cannot cause repeated serialization
049 * of an object tree to result in different JSON texts. If the behavior
050 * of the provided Exporter is deterministic, always producing the same
051 * URL for the same object, then repeated serialization of an object
052 * tree produces identical JSON text.
053 *
054 * SECURITY CLAIM: Iteration of the immutable root is done without
055 * causing execution of application code. This constraint ensures that
056 * serialization has no effect on the application object tree.
057 *
058 * SECURITY DEPENDENCY: Application code cannot extend ConstArray, so
059 * iteration of the values array will not transfer control to
060 * application code.
061 */
062 final ByteArray.BuilderOutputStream buffer =
063 ByteArray.builder(512).asOutputStream();
064 write(export, type, value, new BufferedWriter(UTF8.output(buffer)));
065 return buffer.snapshot();
066 }
067
068 public ByteArray
069 serializeTuple(final Exporter export, final ConstArray<Type> types,
070 final ConstArray<?> values) throws Exception {
071 final ByteArray.BuilderOutputStream buffer =
072 ByteArray.builder(512).asOutputStream();
073 final Writer text = new BufferedWriter(UTF8.output(buffer));
074 final JSONWriter top = JSONWriter.make(text);
075 final JSONWriter.ArrayWriter aout = top.startArray();
076 for (int i = 0; i != values.length(); ++i) {
077 final Type type = i < types.length() ? types.get(i) : Object.class;
078 final Object value = values.get(i);
079 if (i + 1 == values.length() && type instanceof GenericArrayType &&
080 null != value && value.getClass().isArray()) {
081 final Type vparam =
082 ((GenericArrayType)type).getGenericComponentType();
083 final int length = Array.getLength(value);
084 for (int j = 0; j != length; ++j) {
085 serialize(export, vparam, Array.get(value, j),
086 aout.startElement());
087 }
088 break;
089 }
090 serialize(export, type, value, aout.startElement());
091 }
092 aout.finish();
093 if (!top.isWritten()) { throw new RuntimeException(); }
094 text.flush();
095 text.close();
096 return buffer.snapshot();
097 }
098
099 /**
100 * Serializes a stream of Java objects to a JSON text stream.
101 * @param export reference exporter
102 * @param type implicit type for <code>value</code>
103 * @param value value to serialize
104 * @param text UTF-8 text output, will be flushed and closed
105 */
106 static public void
107 write(final Exporter export, final Type type, final Object value,
108 final Writer text) throws Exception {
109 final JSONWriter top = JSONWriter.make(text);
110 serialize(export, type, value, top);
111 if (!top.isWritten()) { throw new RuntimeException(); }
112 text.flush();
113 text.close();
114 }
115
116 static private final TypeVariable<?> R = Typedef.var(Promise.class, "T");
117 static private final TypeVariable<?> T = Typedef.var(Iterable.class, "T");
118 static private final Class<?> Rejected =
119 Eventual.reject(new Exception()).getClass();
120
121 /**
122 * encoding of a rejected promise
123 */
124 static private final Layout<?> JSONerror = Layout.define("!");
125
126 static private void
127 serialize(final Exporter export, final Type implicit,
128 final Object value, final JSONWriter out) throws Exception {
129 if (null == value) {
130 out.writeNull();
131 return;
132 }
133 final Class<?> actual = value.getClass();
134 if (String.class == actual) {
135 out.writeString((String)value);
136 } else if (Integer.class == actual) {
137 out.writeInt((Integer)value);
138 } else if (Boolean.class == actual) {
139 out.writeBoolean((Boolean)value);
140 } else if (Long.class == actual) {
141 try {
142 out.writeLong((Long)value);
143 } catch (final ArithmeticException e) {
144 serialize(export, implicit, JSONerror.make(e), out);
145 }
146 } else if (Double.class == actual) {
147 try {
148 out.writeDouble((Double)value);
149 } catch (final ArithmeticException e) {
150 serialize(export, implicit, JSONerror.make(e), out);
151 }
152 } else if (Float.class == actual) {
153 try {
154 out.writeFloat((Float)value);
155 } catch (final ArithmeticException e) {
156 serialize(export, implicit, JSONerror.make(e), out);
157 }
158 } else if (Byte.class == actual) {
159 out.writeInt((Byte)value);
160 } else if (Short.class == actual) {
161 out.writeInt((Short)value);
162 } else if (Character.class == actual) {
163 out.writeString(((Character)value).toString());
164 } else if (BigInteger.class == actual) {
165 final BigInteger num = (BigInteger)value;
166 if (num.bitLength() < Integer.SIZE) {
167 serialize(export, implicit, num.intValue(), out);
168 } else if (num.bitLength() < Long.SIZE) {
169 serialize(export, implicit, num.longValue(), out);
170 } else {
171 serialize(export, implicit, JSONerror.make(JSONWriter.NaN),out);
172 }
173 } else if (BigDecimal.class == actual) {
174 serialize(export, implicit, ((BigDecimal)value).doubleValue(), out);
175 } else if (value instanceof ConstArray<?>) {
176 /*
177 * SECURITY DEPENDENCY: Application code cannot extend ConstArray,
178 * so iteration of the value array will not transfer control to
179 * application code.
180 */
181 final Type promised = Typedef.value(R, implicit);
182 final Type expected = null != promised ? promised : implicit;
183 final Type elementType = Typedef.bound(T, expected);
184 final JSONWriter.ArrayWriter aout = out.startArray();
185 for (final Object element : (ConstArray<?>)value) {
186 serialize(export, elementType, element, aout.startElement());
187 }
188 aout.finish();
189 } else if (Scope.class == actual) {
190 final Scope<?> scope = (Scope<?>)value;
191 /*
192 * SECURITY DEPENDENCY: Application code cannot extend ConstArray,
193 * so iteration of the scope arrays will not transfer control to
194 * application code.
195 */
196 final JSONWriter.ObjectWriter oout = out.startObject();
197 final int length = scope.values.length();
198 for (int i = 0; i != length; ++i) {
199 serialize(export, Object.class, scope.values.get(i),
200 oout.startMember(scope.meta.names.get(i)));
201 }
202 oout.finish();
203 } else if (value instanceof Record || value instanceof Throwable) {
204 final JSONWriter.ObjectWriter oout = out.startObject();
205 final Type promised = Typedef.value(R, implicit);
206 final Type expected = null != promised ? promised : implicit;
207 final PowerlessArray<String> types =
208 JSON.upto(actual, Typedef.raw(expected));
209 if (0 != types.length()) {
210 serialize(export, PowerlessArray.class,
211 types, oout.startMember("class"));
212 }
213 for (final Field f : Reflection.fields(actual)) {
214 if (!Modifier.isStatic(f.getModifiers()) &&
215 Modifier.isPublic(f.getDeclaringClass().getModifiers())) {
216 final Object member;
217 if (Modifier.isFinal(f.getModifiers())) {
218 member = Reflection.get(f, value);
219 } else {
220 member = Eventual.reject(new NonFinalRecordField());
221 }
222 if (null != member) {
223 serialize(export,
224 Typedef.bound(f.getGenericType(), actual),
225 member, oout.startMember(f.getName()));
226 }
227 }
228 }
229 oout.finish();
230 } else {
231 final Promise<?> pValue = Eventual.ref(value);
232 if (Rejected == pValue.getClass()) {
233 Exception reason;
234 try {
235 pValue.call();
236 throw new AssertionError();
237 } catch (final Exception e) {
238 reason = e;
239 }
240 serialize(export, implicit, JSONerror.make(reason), out);
241 } else {
242 final Export link = export.apply(value);
243 if (null != link.href) {
244 final Type promised = Typedef.value(R, implicit);
245 final Type expected= null != promised ? promised : implicit;
246 final PowerlessArray<String> types =
247 JSON.upto(actual, Typedef.raw(expected));
248 if (0 == types.length()) {
249 out.writeLink(link.href);
250 } else {
251 final JSONWriter.ObjectWriter oout = out.startObject();
252 serialize(export, PowerlessArray.class,
253 types, oout.startMember("class"));
254 oout.startMember("@").writeString(link.href);
255 oout.finish();
256 }
257 } else {
258 serialize(export, implicit, link.replacement, out);
259 }
260 }
261 }
262 }
263 }