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 }