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    }