http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7e4f63e6/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/urlencoding/UonParserReader.java ---------------------------------------------------------------------- diff --git a/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/urlencoding/UonParserReader.java b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/urlencoding/UonParserReader.java new file mode 100755 index 0000000..621164e --- /dev/null +++ b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/urlencoding/UonParserReader.java @@ -0,0 +1,195 @@ +/******************************************************************************* + * Licensed Materials - Property of IBM + * (c) Copyright IBM Corporation 2014, 2015. All Rights Reserved. + * + * The source code for this program is not published or otherwise + * divested of its trade secrets, irrespective of what has been + * deposited with the U.S. Copyright Office. + *******************************************************************************/ +package com.ibm.juno.core.urlencoding; + +import java.io.*; + +import com.ibm.juno.core.parser.*; + +/** + * Same functionality as {@link ParserReader} except automatically decoded <code>%xx</code> escape sequences. + * <p> + * Escape sequences are assumed to be encoded UTF-8. Extended Unicode (>\u10000) is supported. + * <p> + * If decoding is enabled, the following character replacements occur so that boundaries are not lost: + * <ul> + * <li><js>'&'</js> -> <js>'\u0001'</js> + * <li><js>'='</js> -> <js>'\u0002'</js> + * </ul> + * + * @author James Bognar ([email protected]) + */ +public final class UonParserReader extends ParserReader { + + private final boolean decodeChars; + private final char[] buff; + private int iCurrent, iEnd; + + /** + * Constructor for input from a {@link CharSequence}. + * + * @param in The character sequence being read from. + * @param decodeChars If <jk>true</jk>, decode <code>%xx</code> escape sequences. + */ + public UonParserReader(CharSequence in, boolean decodeChars) { + super(in); + this.decodeChars = decodeChars; + if (in == null || ! decodeChars) + this.buff = new char[0]; + else + this.buff = new char[in.length() < 1024 ? in.length() : 1024]; + } + + /** + * Constructor for input from a {@link Reader}). + * + * @param r The Reader being wrapped. + * @param buffSize Buffer size. + * @param decodeChars If <jk>true</jk>, decode <code>%xx</code> escape sequences. + */ + public UonParserReader(Reader r, int buffSize, boolean decodeChars) { + super(r, buffSize); + this.decodeChars = decodeChars; + buffSize = decodeChars ? (buffSize <= 0 ? 1024 : Math.max(buffSize, 20)) : 0; + this.buff = new char[buffSize]; + } + + @Override /* Reader */ + public final int read(char[] cbuf, int off, int len) throws IOException { + + if (! decodeChars) + return super.read(cbuf, off, len); + + // Copy any remainder to the beginning of the buffer. + int remainder = iEnd - iCurrent; + if (remainder > 0) + System.arraycopy(buff, iCurrent, buff, 0, remainder); + iCurrent = 0; + + int expected = buff.length - remainder; + + int x = super.read(buff, remainder, expected); + if (x == -1 && remainder == 0) + return -1; + + iEnd = remainder + (x == -1 ? 0 : x); + + int i = 0; + while (i < len) { + if (iCurrent >= iEnd) + return i; + char c = buff[iCurrent++]; + if (c == '+') { + cbuf[off + i++] = ' '; + } else if (c == '&') { + cbuf[off + i++] = '\u0001'; + } else if (c == '=') { + cbuf[off + i++] = '\u0002'; + } else if (c != '%') { + cbuf[off + i++] = c; + } else { + int iMark = iCurrent-1; // Keep track of current position. + + // Stop if there aren't at least two more characters following '%' in the buffer, + // or there aren't at least two more positions open in cbuf to handle double-char chars. + if (iMark+2 >= iEnd || i+2 > len) { + iCurrent--; + return i; + } + + int b0 = readEncodedByte(); + int cx; + + // 0xxxxxxx + if (b0 < 128) { + cx = b0; + + // 10xxxxxx + } else if (b0 < 192) { + throw new IOException("Invalid hex value for first escape pattern in UTF-8 sequence: " + b0); + + // 110xxxxx 10xxxxxx + // 11000000(192) - 11011111(223) + } else if (b0 < 224) { + cx = readUTF8(b0-192, 1); + if (cx == -1) { + iCurrent = iMark; + return i; + } + + // 1110xxxx 10xxxxxx 10xxxxxx + // 11100000(224) - 11101111(239) + } else if (b0 < 240) { + cx = readUTF8(b0-224, 2); + if (cx == -1) { + iCurrent = iMark; + return i; + } + + // 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx + // 11110000(240) - 11110111(247) + } else if (b0 < 248) { + cx = readUTF8(b0-240, 3); + if (cx == -1) { + iCurrent = iMark; + return i; + } + + } else + throw new IOException("Invalid hex value for first escape pattern in UTF-8 sequence: " + b0); + + if (cx < 0x10000) + cbuf[off + i++] = (char)cx; + else { + cx -= 0x10000; + cbuf[off + i++] = (char)(0xd800 + (cx >> 10)); + cbuf[off + i++] = (char)(0xdc00 + (cx & 0x3ff)); + } + } + } + return i; + } + + private final int readUTF8(int n, final int numBytes) throws IOException { + if (iCurrent + numBytes*3 > iEnd) + return -1; + for (int i = 0; i < numBytes; i++) { + n <<= 6; + n += readHex()-128; + } + return n; + } + + private final int readHex() throws IOException { + int c = buff[iCurrent++]; + if (c != '%') + throw new IOException("Did not find expected '%' character in UTF-8 sequence."); + return readEncodedByte(); + } + + private final int readEncodedByte() throws IOException { + if (iEnd <= iCurrent + 1) + throw new IOException("Incomplete trailing escape pattern"); + int h = buff[iCurrent++]; + int l = buff[iCurrent++]; + h = fromHexChar(h); + l = fromHexChar(l); + return (h << 4) + l; + } + + private final int fromHexChar(int c) throws IOException { + if (c >= '0' && c <= '9') + return c - '0'; + if (c >= 'a' && c <= 'f') + return 10 + c - 'a'; + if (c >= 'A' && c <= 'F') + return 10 + c - 'A'; + throw new IOException("Invalid hex character '"+c+"' found in escape pattern."); + } +}
http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7e4f63e6/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/urlencoding/UonSerializer$Encoding.class ---------------------------------------------------------------------- diff --git a/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/urlencoding/UonSerializer$Encoding.class b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/urlencoding/UonSerializer$Encoding.class new file mode 100755 index 0000000..5fca57c Binary files /dev/null and b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/urlencoding/UonSerializer$Encoding.class differ http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7e4f63e6/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/urlencoding/UonSerializer$Readable.class ---------------------------------------------------------------------- diff --git a/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/urlencoding/UonSerializer$Readable.class b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/urlencoding/UonSerializer$Readable.class new file mode 100755 index 0000000..c4e6b57 Binary files /dev/null and b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/urlencoding/UonSerializer$Readable.class differ http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7e4f63e6/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/urlencoding/UonSerializer$Simple.class ---------------------------------------------------------------------- diff --git a/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/urlencoding/UonSerializer$Simple.class b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/urlencoding/UonSerializer$Simple.class new file mode 100755 index 0000000..1ecf09c Binary files /dev/null and b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/urlencoding/UonSerializer$Simple.class differ http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7e4f63e6/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/urlencoding/UonSerializer$SimpleEncoding.class ---------------------------------------------------------------------- diff --git a/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/urlencoding/UonSerializer$SimpleEncoding.class b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/urlencoding/UonSerializer$SimpleEncoding.class new file mode 100755 index 0000000..2d0a383 Binary files /dev/null and b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/urlencoding/UonSerializer$SimpleEncoding.class differ http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7e4f63e6/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/urlencoding/UonSerializer.class ---------------------------------------------------------------------- diff --git a/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/urlencoding/UonSerializer.class b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/urlencoding/UonSerializer.class new file mode 100755 index 0000000..4ca7cf1 Binary files /dev/null and b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/urlencoding/UonSerializer.class differ http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7e4f63e6/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/urlencoding/UonSerializer.java ---------------------------------------------------------------------- diff --git a/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/urlencoding/UonSerializer.java b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/urlencoding/UonSerializer.java new file mode 100755 index 0000000..cb16038 --- /dev/null +++ b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/urlencoding/UonSerializer.java @@ -0,0 +1,532 @@ +/******************************************************************************* + * Licensed Materials - Property of IBM + * (c) Copyright IBM Corporation 2011, 2015. All Rights Reserved. + * + * The source code for this program is not published or otherwise + * divested of its trade secrets, irrespective of what has been + * deposited with the U.S. Copyright Office. + *******************************************************************************/ +package com.ibm.juno.core.urlencoding; + +import static com.ibm.juno.core.serializer.SerializerProperties.*; +import static com.ibm.juno.core.urlencoding.UonSerializerProperties.*; + +import java.io.*; +import java.lang.reflect.*; +import java.util.*; + +import com.ibm.juno.core.*; +import com.ibm.juno.core.annotation.*; +import com.ibm.juno.core.filter.*; +import com.ibm.juno.core.serializer.*; + +/** + * Serializes POJO models to UON (a notation for URL-encoded query parameter values). + * + * + * <h6 class='topic'>Media types</h6> + * <p> + * Handles <code>Accept</code> types: <code>text/uon</code> + * <p> + * Produces <code>Content-Type</code> types: <code>text/uon</code> + * + * + * <h6 class='topic'>Description</h6> + * <p> + * This serializer provides several serialization options. Typically, one of the predefined DEFAULT serializers will be sufficient. + * However, custom serializers can be constructed to fine-tune behavior. + * + * + * <h6 class='topic'>Configurable properties</h6> + * <p> + * This class has the following properties associated with it: + * <ul> + * <li>{@link UonSerializerProperties} + * <li>{@link SerializerProperties} + * <li>{@link BeanContextProperties} + * </ul> + * <p> + * The following shows a sample object defined in Javascript: + * </p> + * <p class='bcode'> + * { + * id: 1, + * name: <js>'John Smith'</js>, + * uri: <js>'http://sample/addressBook/person/1'</js>, + * addressBookUri: <js>'http://sample/addressBook'</js>, + * birthDate: <js>'1946-08-12T00:00:00Z'</js>, + * otherIds: <jk>null</jk>, + * addresses: [ + * { + * uri: <js>'http://sample/addressBook/address/1'</js>, + * personUri: <js>'http://sample/addressBook/person/1'</js>, + * id: 1, + * street: <js>'100 Main Street'</js>, + * city: <js>'Anywhereville'</js>, + * state: <js>'NY'</js>, + * zip: 12345, + * isCurrent: <jk>true</jk>, + * } + * ] + * } + * </p> + * <p> + * Using the "strict" syntax defined in this document, the equivalent + * UON notation would be as follows: + * </p> + * <p class='bcode'> + * $o( + * <xa>id</xa>=$n(<xs>1</xs>), + * <xa>name</xa>=<xs>John+Smith</xs>, + * <xa>uri</xa>=<xs>http://sample/addressBook/person/1</xs>, + * <xa>addressBookUri</xa>=<xs>http://sample/addressBook</xs>, + * <xa>birthDate</xa>=<xs>1946-08-12T00:00:00Z</xs>, + * <xa>otherIds</xa>=<xs>%00</xs>, + * <xa>addresses</xa>=$a( + * $o( + * <xa>uri</xa>=<xs>http://sample/addressBook/address/1</xs>, + * <xa>personUri</xa>=<xs>http://sample/addressBook/person/1</xs>, + * <xa>id</xa>=$n(<xs>1</xs>), + * <xa>street</xa>=<xs>100+Main+Street</xs>, + * <xa>city</xa>=<xs>Anywhereville</xs>, + * <xa>state</xa>=<xs>NY</xs>, + * <xa>zip</xa>=$n(<xs>12345</xs>), + * <xa>isCurrent</xa>=$b(<xs>true</xs>) + * ) + * ) + * ) + * </p> + * <p> + * A secondary "lax" syntax is available when the data type of the + * values are already known on the receiving end of the transmission: + * </p> + * <p class='bcode'> + * ( + * <xa>id</xa>=<xs>1</xs>, + * <xa>name</xa>=<xs>John+Smith</xs>, + * <xa>uri</xa>=<xs>http://sample/addressBook/person/1</xs>, + * <xa>addressBookUri</xa>=<xs>http://sample/addressBook</xs>, + * <xa>birthDate</xa>=<xs>1946-08-12T00:00:00Z</xs>, + * <xa>otherIds</xa>=<xs>%00</xs>, + * <xa>addresses</xa>=( + * ( + * <xa>uri</xa>=<xs>http://sample/addressBook/address/1</xs>, + * <xa>personUri</xa>=<xs>http://sample/addressBook/person/1</xs>, + * <xa>id</xa>=<xs>1</xs>, + * <xa>street</xa>=<xs>100+Main+Street</xs>, + * <xa>city</xa>=<xs>Anywhereville</xs>, + * <xa>state</xa>=<xs>NY</xs>, + * <xa>zip</xa>=<xs>12345</xs>, + * <xa>isCurrent</xa>=<xs>true</xs> + * ) + * ) + * ) + * </p> + * + * + * <h6 class='topic'>Examples</h6> + * <p class='bcode'> + * <jc>// Serialize a Map</jc> + * Map m = <jk>new</jk> ObjectMap(<js>"{a:'b',c:1,d:false,e:['f',1,false],g:{h:'i'}}"</js>); + * + * <jc>// Serialize to value equivalent to JSON.</jc> + * <jc>// Produces "$o(a=b,c=$n(1),d=$b(false),e=$a(f,$n(1),$b(false)),g=$o(h=i))"</jc> + * String s = UonSerializer.<jsf>DEFAULT</jsf>.serialize(s); + * + * <jc>// Serialize to simplified value (for when data type is already known by receiver).</jc> + * <jc>// Produces "(a=b,c=1,d=false,e=(f,1,false),g=(h=i))"</jc> + * String s = UonSerializer.<jsf>DEFAULT_SIMPLE</jsf>.serialize(s); + * + * <jc>// Serialize a bean</jc> + * <jk>public class</jk> Person { + * <jk>public</jk> Person(String s); + * <jk>public</jk> String getName(); + * <jk>public int</jk> getAge(); + * <jk>public</jk> Address getAddress(); + * <jk>public boolean</jk> deceased; + * } + * + * <jk>public class</jk> Address { + * <jk>public</jk> String getStreet(); + * <jk>public</jk> String getCity(); + * <jk>public</jk> String getState(); + * <jk>public int</jk> getZip(); + * } + * + * Person p = <jk>new</jk> Person(<js>"John Doe"</js>, 23, <js>"123 Main St"</js>, <js>"Anywhere"</js>, <js>"NY"</js>, 12345, <jk>false</jk>); + * + * <jc>// Produces "$o(name=John Doe,age=23,address=$o(street=123 Main St,city=Anywhere,state=NY,zip=$n(12345)),deceased=$b(false))"</jc> + * String s = UonSerializer.<jsf>DEFAULT</jsf>.serialize(s); + * + * <jc>// Produces "(name=John Doe,age=23,address=(street=123 Main St,city=Anywhere,state=NY,zip=12345),deceased=false)"</jc> + * String s = UonSerializer.<jsf>DEFAULT_SIMPLE</jsf>.serialize(s); + * </p> + * + * @author James Bognar ([email protected]) + */ +@Produces("text/uon") +public class UonSerializer extends WriterSerializer { + + /** Reusable instance of {@link UonSerializer}, all default settings. */ + public static final UonSerializer DEFAULT = new UonSerializer().lock(); + + /** Reusable instance of {@link UonSerializer.Simple}. */ + public static final UonSerializer DEFAULT_SIMPLE = new Simple().lock(); + + /** Reusable instance of {@link UonSerializer.Readable}. */ + public static final UonSerializer DEFAULT_READABLE = new Readable().lock(); + + /** Reusable instance of {@link UonSerializer.Encoding}. */ + public static final UonSerializer DEFAULT_ENCODING = new Encoding().lock(); + + /** Reusable instance of {@link UonSerializer.SimpleEncoding}. */ + public static final UonSerializer DEFAULT_SIMPLE_ENCODING = new SimpleEncoding().lock(); + + /** + * Equivalent to <code><jk>new</jk> UonSerializer().setProperty(UonSerializerProperties.<jsf>UON_simpleMode</jsf>,<jk>true</jk>);</code>. + */ + @Produces(value={"text/uon-simple"},contentType="text/uon") + public static class Simple extends UonSerializer { + /** Constructor */ + public Simple() { + setProperty(UON_simpleMode, true); + } + } + + /** + * Equivalent to <code><jk>new</jk> UonSerializer().setProperty(UonSerializerProperties.<jsf>UON_useWhitespace</jsf>,<jk>true</jk>);</code>. + */ + public static class Readable extends UonSerializer { + /** Constructor */ + public Readable() { + setProperty(UON_useWhitespace, true); + setProperty(SERIALIZER_useIndentation, true); + } + } + + /** + * Equivalent to <code><jk>new</jk> UonSerializer().setProperty(UonSerializerProperties.<jsf>UON_encodeChars</jsf>,<jk>true</jk>);</code>. + */ + public static class Encoding extends UonSerializer { + /** Constructor */ + public Encoding() { + setProperty(UON_encodeChars, true); + } + } + + /** + * Equivalent to <code><jk>new</jk> UonSerializer().setProperty(UonSerializerProperties.<jsf>UON_simpleMode</jsf>,<jk>true</jk>).setProperty(UonSerializerProperties.<jsf>UON_encodeChars</jsf>,<jk>true</jk>);</code>. + */ + @Produces(value={"text/uon-simple"},contentType="text/uon") + public static class SimpleEncoding extends UonSerializer { + /** Constructor */ + public SimpleEncoding() { + setProperty(UON_simpleMode, true); + setProperty(UON_encodeChars, true); + } + } + + /** UON serializer properties currently set on this serializer. */ + protected transient UonSerializerProperties usp = new UonSerializerProperties(); + + /** URL-Encoding properties currently set on this serializer. */ + protected transient UrlEncodingProperties uep = new UrlEncodingProperties(); + + + /** + * Workhorse method. Determines the type of object, and then calls the + * appropriate type-specific serialization method. + * + * @param out The writer to serialize to. + * @param o The object being serialized. + * @param eType The expected type of the object if this is a bean property. + * @param ctx The context that exist for the duration of a serialize. + * @param attrName The bean property name if this is a bean property. <jk>null</jk> if this isn't a bean property being serialized. + * @param pMeta The bean property metadata. + * @param quoteEmptyStrings <jk>true</jk> if this is the first entry in an array. + * @param isTop If we haven't recursively called this method. + * @return The same writer passed in. + * @throws SerializeException + */ + @SuppressWarnings({ "rawtypes", "unchecked" }) + protected SerializerWriter serializeAnything(UonSerializerWriter out, Object o, ClassMeta<?> eType, UonSerializerContext ctx, + String attrName, BeanPropertyMeta pMeta, boolean quoteEmptyStrings, boolean isTop) throws SerializeException { + try { + + BeanContext bc = ctx.getBeanContext(); + + if (o == null) { + out.appendObject(null, false, false, isTop); + return out; + } + + if (eType == null) + eType = object(); + + boolean addClassAttr; // Add "_class" attribute to element? + ClassMeta<?> aType; // The actual type + ClassMeta<?> gType; // The generic type + + aType = ctx.push(attrName, o, eType); + boolean isRecursion = aType == null; + + // Handle recursion + if (aType == null) { + o = null; + aType = object(); + } + + gType = aType.getFilteredClassMeta(); + addClassAttr = (ctx.isAddClassAttrs() && ! eType.equals(aType)); + + // Filter if necessary + PojoFilter filter = aType.getPojoFilter(); // The filter + if (filter != null) { + o = filter.filter(o); + + // If the filter's getFilteredClass() method returns Object, we need to figure out + // the actual type now. + if (gType.isObject()) + gType = bc.getClassMetaForObject(o); + } + + // '\0' characters are considered null. + if (o == null || (gType.isChar() && ((Character)o).charValue() == 0)) + out.appendObject(null, false, false, isTop); + else if (gType.hasToObjectMapMethod()) + serializeMap(out, gType.toObjectMap(o), eType, ctx); + else if (gType.isBean()) + serializeBeanMap(out, bc.forBean(o), addClassAttr, ctx); + else if (gType.isUri() || (pMeta != null && (pMeta.isUri() || pMeta.isBeanUri()))) + out.appendUri(o, isTop); + else if (gType.isMap()) { + if (o instanceof BeanMap) + serializeBeanMap(out, (BeanMap)o, addClassAttr, ctx); + else + serializeMap(out, (Map)o, eType, ctx); + } + else if (gType.isCollection()) { + if (addClassAttr) + serializeCollectionMap(out, (Collection)o, gType, ctx); + else + serializeCollection(out, (Collection) o, eType, ctx); + } + else if (gType.isArray()) { + if (addClassAttr) + serializeCollectionMap(out, toList(gType.getInnerClass(), o), gType, ctx); + else + serializeCollection(out, toList(gType.getInnerClass(), o), eType, ctx); + } + else { + out.appendObject(o, quoteEmptyStrings, false, isTop); + } + + if (! isRecursion) + ctx.pop(); + return out; + } catch (SerializeException e) { + throw e; + } catch (StackOverflowError e) { + throw e; + } catch (Throwable e) { + throw new SerializeException("Exception occured trying to process object of type ''{0}''", (o == null ? null : o.getClass().getName())).initCause(e); + } + } + + @SuppressWarnings({ "rawtypes", "unchecked" }) + private SerializerWriter serializeMap(UonSerializerWriter out, Map m, ClassMeta<?> type, UonSerializerContext ctx) throws IOException, SerializeException { + + m = sort(ctx, m); + + ClassMeta<?> keyType = type.getKeyType(), valueType = type.getValueType(); + + int depth = ctx.getIndent(); + out.startFlag('o'); + + Iterator mapEntries = m.entrySet().iterator(); + + while (mapEntries.hasNext()) { + Map.Entry e = (Map.Entry) mapEntries.next(); + Object value = e.getValue(); + Object key = generalize(ctx, e.getKey(), keyType); + out.cr(depth).appendObject(key, ctx.useWhitespace, false, false).append('='); + serializeAnything(out, value, valueType, ctx, (key == null ? null : key.toString()), null, ctx.useWhitespace, false); + if (mapEntries.hasNext()) + out.append(','); + } + + if (m.size() > 0) + out.cr(depth-1); + out.append(')'); + + return out; + } + + @SuppressWarnings({ "rawtypes" }) + private SerializerWriter serializeCollectionMap(UonSerializerWriter out, Collection o, ClassMeta<?> type, UonSerializerContext ctx) throws IOException, SerializeException { + int i = ctx.getIndent(); + out.startFlag('o').nl(); + out.append(i, "_class=").appendObject(type, false, false, false).append(',').nl(); + out.append(i, "items="); + ctx.indent++; + serializeCollection(out, o, type, ctx); + ctx.indent--; + + if (o.size() > 0) + out.cr(i-1); + out.append(')'); + + return out; + } + + @SuppressWarnings({ "rawtypes" }) + private SerializerWriter serializeBeanMap(UonSerializerWriter out, BeanMap m, boolean addClassAttr, UonSerializerContext ctx) throws IOException, SerializeException { + int depth = ctx.getIndent(); + + out.startFlag('o'); + + Iterator mapEntries = m.entrySet().iterator(); + + // Print out "_class" attribute on this bean if required. + if (addClassAttr) { + String attr = "_class"; + out.cr(depth).appendObject(attr, false, false, false).append('=').append(m.getClassMeta().getInnerClass().getName()); + if (mapEntries.hasNext()) + out.append(','); + } + + boolean addComma = false; + + while (mapEntries.hasNext()) { + BeanMapEntry p = (BeanMapEntry)mapEntries.next(); + BeanPropertyMeta pMeta = p.getMeta(); + + String key = p.getKey(); + Object value = null; + try { + value = p.getValue(); + } catch (StackOverflowError e) { + throw e; + } catch (Throwable t) { + ctx.addBeanGetterWarning(pMeta, t); + } + + if (canIgnoreValue(ctx, pMeta.getClassMeta(), key, value)) + continue; + + if (addComma) + out.append(','); + + out.cr(depth).appendObject(key, false, false, false).append('='); + + serializeAnything(out, value, pMeta.getClassMeta(), ctx, key, pMeta, false, false); + + addComma = true; + } + + if (m.size() > 0) + out.cr(depth-1); + out.append(')'); + + return out; + } + + @SuppressWarnings({ "rawtypes", "unchecked" }) + private SerializerWriter serializeCollection(UonSerializerWriter out, Collection c, ClassMeta<?> type, UonSerializerContext ctx) throws IOException, SerializeException { + + ClassMeta<?> elementType = type.getElementType(); + + c = sort(ctx, c); + + out.startFlag('a'); + + int depth = ctx.getIndent(); + boolean quoteEmptyString = (c.size() == 1 || ctx.useWhitespace); + + for (Iterator i = c.iterator(); i.hasNext();) { + out.cr(depth); + serializeAnything(out, i.next(), elementType, ctx, "<iterator>", null, quoteEmptyString, false); + if (i.hasNext()) + out.append(','); + } + + if (c.size() > 0) + out.cr(depth-1); + out.append(')'); + + return out; + } + + //-------------------------------------------------------------------------------- + // Overridden methods + //-------------------------------------------------------------------------------- + + @Override /* Serializer */ + public UonSerializerContext createContext(ObjectMap properties, Method javaMethod) { + return new UonSerializerContext(getBeanContext(), sp, usp, uep, properties, javaMethod); + } + + @Override /* Serializer */ + protected void doSerialize(Object o, Writer out, SerializerContext ctx) throws IOException, SerializeException { + UonSerializerContext uctx = (UonSerializerContext)ctx; + serializeAnything(uctx.getWriter(out), o, null, uctx, "root", null, false, true); + } + + @Override /* CoreApi */ + public UonSerializer setProperty(String property, Object value) throws LockedException { + checkLock(); + if (! usp.setProperty(property, value)) + if (! uep.setProperty(property, value)) + super.setProperty(property, value); + return this; + } + + @Override /* CoreApi */ + public UonSerializer setProperties(ObjectMap properties) throws LockedException { + for (Map.Entry<String,Object> e : properties.entrySet()) + setProperty(e.getKey(), e.getValue()); + return this; + } + + @Override /* CoreApi */ + public UonSerializer addNotBeanClasses(Class<?>...classes) throws LockedException { + super.addNotBeanClasses(classes); + return this; + } + + @Override /* CoreApi */ + public UonSerializer addFilters(Class<?>...classes) throws LockedException { + super.addFilters(classes); + return this; + } + + @Override /* CoreApi */ + public <T> UonSerializer addImplClass(Class<T> interfaceClass, Class<? extends T> implClass) throws LockedException { + super.addImplClass(interfaceClass, implClass); + return this; + } + + @Override /* CoreApi */ + public UonSerializer setClassLoader(ClassLoader classLoader) throws LockedException { + super.setClassLoader(classLoader); + return this; + } + + @Override /* Lockable */ + public UonSerializer lock() { + super.lock(); + return this; + } + + @Override /* Lockable */ + public UonSerializer clone() { + try { + UonSerializer c = (UonSerializer)super.clone(); + c.usp = usp.clone(); + c.uep = uep.clone(); + return c; + } catch (CloneNotSupportedException e) { + throw new RuntimeException(e); // Shouldn't happen + } + } +} http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7e4f63e6/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/urlencoding/UonSerializerContext.class ---------------------------------------------------------------------- diff --git a/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/urlencoding/UonSerializerContext.class b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/urlencoding/UonSerializerContext.class new file mode 100755 index 0000000..d1e9fd4 Binary files /dev/null and b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/urlencoding/UonSerializerContext.class differ http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7e4f63e6/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/urlencoding/UonSerializerContext.java ---------------------------------------------------------------------- diff --git a/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/urlencoding/UonSerializerContext.java b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/urlencoding/UonSerializerContext.java new file mode 100755 index 0000000..849afef --- /dev/null +++ b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/urlencoding/UonSerializerContext.java @@ -0,0 +1,97 @@ +/******************************************************************************* + * Licensed Materials - Property of IBM + * (c) Copyright IBM Corporation 2014, 2015. All Rights Reserved. + * + * The source code for this program is not published or otherwise + * divested of its trade secrets, irrespective of what has been + * deposited with the U.S. Copyright Office. + *******************************************************************************/ +package com.ibm.juno.core.urlencoding; + +import static com.ibm.juno.core.urlencoding.UonSerializerProperties.*; +import static com.ibm.juno.core.urlencoding.UrlEncodingProperties.*; + +import java.io.*; +import java.lang.reflect.*; + +import com.ibm.juno.core.*; +import com.ibm.juno.core.serializer.*; + +/** + * Context object that lives for the duration of a single serialization of {@link UonSerializer} and {@link UrlEncodingSerializer}. + * <p> + * See {@link SerializerContext} for details. + * + * @author James Bognar ([email protected]) + */ +public final class UonSerializerContext extends SerializerContext { + + boolean simpleMode, useWhitespace, encodeChars, expandedParams; + + /** + * Constructor. + * + * @param beanContext The bean context being used by the serializer. + * @param sp Default general serializer properties. + * @param usp Default UON serializer properties. + * @param uep Default URL-Encoding properties. + * @param op Override properties. + * @param javaMethod Java method that invoked this serializer. + * When using the REST API, this is the Java method invoked by the REST call. + * Can be used to access annotations defined on the method or class. + */ + protected UonSerializerContext(BeanContext beanContext, SerializerProperties sp, UonSerializerProperties usp, UrlEncodingProperties uep, ObjectMap op, Method javaMethod) { + super(beanContext, sp, op, javaMethod); + if (op == null || op.isEmpty()) { + simpleMode = usp.simpleMode; + useWhitespace = usp.useWhitespace; + encodeChars = usp.encodeChars; + expandedParams = uep.expandedParams; + } else { + simpleMode = op.getBoolean(UON_simpleMode, usp.simpleMode); + useWhitespace = op.getBoolean(UON_useWhitespace, usp.useWhitespace); + encodeChars = op.getBoolean(UON_encodeChars, usp.encodeChars); + expandedParams = op.getBoolean(URLENC_expandedParams, uep.expandedParams); + + } + } + + /** + * Returns the {@link UonSerializerProperties#UON_simpleMode} setting value in this context. + * + * @return The {@link UonSerializerProperties#UON_simpleMode} setting value in this context. + */ + public final boolean isSimpleMode() { + return simpleMode; + } + + /** + * Returns the {@link UonSerializerProperties#UON_encodeChars} setting value in this context. + * + * @return The {@link UonSerializerProperties#UON_encodeChars} setting value in this context. + */ + public final boolean isEncodeChars() { + return encodeChars; + } + + /** + * Returns the {@link UrlEncodingProperties#URLENC_expandedParams} setting value in this context. + * + * @return The {@link UrlEncodingProperties#URLENC_expandedParams} setting value in this context. + */ + public final boolean isExpandedParams() { + return expandedParams; + } + + /** + * Wraps the specified writer in a {@link UonSerializerWriter}. + * + * @param out The writer to wrap. + * @return The wrapped writer. + */ + protected UonSerializerWriter getWriter(Writer out) { + if (out instanceof UonSerializerWriter) + return (UonSerializerWriter)out; + return new UonSerializerWriter(out, useWhitespace, isSimpleMode(), isEncodeChars(), getRelativeUriBase(), getAbsolutePathUriBase()); + } +} http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7e4f63e6/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/urlencoding/UonSerializerProperties.class ---------------------------------------------------------------------- diff --git a/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/urlencoding/UonSerializerProperties.class b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/urlencoding/UonSerializerProperties.class new file mode 100755 index 0000000..d8f0914 Binary files /dev/null and b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/urlencoding/UonSerializerProperties.class differ http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7e4f63e6/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/urlencoding/UonSerializerProperties.java ---------------------------------------------------------------------- diff --git a/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/urlencoding/UonSerializerProperties.java b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/urlencoding/UonSerializerProperties.java new file mode 100755 index 0000000..e6912fb --- /dev/null +++ b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/urlencoding/UonSerializerProperties.java @@ -0,0 +1,135 @@ +/******************************************************************************* + * Licensed Materials - Property of IBM + * (c) Copyright IBM Corporation 2014, 2015. All Rights Reserved. + * + * The source code for this program is not published or otherwise + * divested of its trade secrets, irrespective of what has been + * deposited with the U.S. Copyright Office. + *******************************************************************************/ +package com.ibm.juno.core.urlencoding; + +import com.ibm.juno.core.*; +import com.ibm.juno.core.serializer.*; + +/** + * Configurable properties on the {@link UonSerializer} and {@link UrlEncodingSerializer} classes. + * <p> + * Use the {@link UonSerializer#setProperty(String, Object)} method to set property values. + * <p> + * In addition to these properties, the following properties are also applicable for {@link UonSerializer}. + * <ul> + * <li>{@link SerializerProperties} + * <li>{@link BeanContextProperties} + * </ul> + * + * @author James Bognar ([email protected]) + */ +public final class UonSerializerProperties implements Cloneable { + + /** + * Use simplified output ({@link Boolean}, default=<jk>false</jk>). + * <p> + * If <jk>true</jk>, type flags will not be prepended to values in most cases. + * <p> + * Use this setting if the data types of the values (e.g. object/array/boolean/number/string) + * is known on the receiving end. + * <p> + * It should be noted that the default behavior produces a data structure that can + * be losslessly converted into JSON, and any JSON can be losslessly represented + * in a URL-encoded value. However, this strict equivalency does not exist + * when simple mode is used. + * <p> + * <table class='styled'> + * <tr> + * <th>Input (in JSON)</th> + * <th>Normal mode output</th> + * <th>Simple mode output</th> + * </tr> + * <tr> + * <td class='code'>{foo:'bar',baz:'bing'}</td> + * <td class='code'>$o(foo=bar,baz=bing)</td> + * <td class='code'>(foo=bar,baz=bing)</td> + * </tr> + * <tr> + * <td class='code'>{foo:{bar:'baz'}}</td> + * <td class='code'>$o(foo=$o(bar=baz))</td> + * <td class='code'>(foo=(bar=baz))</td> + * </tr> + * <tr> + * <td class='code'>['foo','bar']</td> + * <td class='code'>$a(foo,bar)</td> + * <td class='code'>(foo,bar)</td> + * </tr> + * <tr> + * <td class='code'>['foo',['bar','baz']]</td> + * <td class='code'>$a(foo,$a(bar,baz))</td> + * <td class='code'>(foo,(bar,baz))</td> + * </tr> + * <tr> + * <td class='code'>true</td> + * <td class='code'>$b(true)</td> + * <td class='code'>true</td> + * </tr> + * <tr> + * <td class='code'>123</td> + * <td class='code'>$n(123)</td> + * <td class='code'>123</td> + * </tr> + * </table> + */ + public static final String UON_simpleMode = "UonSerializer.simpleMode"; + + /** + * Use whitespace in output ({@link Boolean}, default=<jk>false</jk>). + * <p> + * If <jk>true</jk>, whitespace is added to the output to improve readability. + */ + public static final String UON_useWhitespace = "UonSerializer.useWhitespace"; + + /** + * Encode non-valid URI characters to <js>"%xx"</js> constructs. ({@link Boolean}, default=<jk>false</jk> for {@link UonSerializer}, <jk>true</jk> for {@link UrlEncodingSerializer}). + * <p> + * If <jk>true</jk>, non-valid URI characters will be converted to <js>"%xx"</js> sequences. + * Set to <jk>false</jk> if parameter value is being passed to some other code that will already + * perform URL-encoding of non-valid URI characters. + */ + public static final String UON_encodeChars = "UonSerializer.encodeChars"; + + boolean + simpleMode = false, + useWhitespace = false, + encodeChars = false; + + /** + * Sets the specified property value. + * @param property The property name. + * @param value The property value. + * @return <jk>true</jk> if property name was valid and property was set. + */ + public boolean setProperty(String property, Object value) { + BeanContext bc = BeanContext.DEFAULT; + if (property.equals(UON_simpleMode)) + simpleMode = bc.convertToType(value, Boolean.class); + else if (property.equals(UON_useWhitespace)) + useWhitespace = bc.convertToType(value, Boolean.class); + else if (property.equals(UON_encodeChars)) + encodeChars = bc.convertToType(value, Boolean.class); + else + return false; + return true; + } + + + //-------------------------------------------------------------------------------- + // Overridden methods + //-------------------------------------------------------------------------------- + + @Override /* Cloneable */ + public UonSerializerProperties clone() { + try { + return (UonSerializerProperties)super.clone(); + } catch (CloneNotSupportedException e) { + throw new RuntimeException(e); // Shouldn't happen + } + } +} http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7e4f63e6/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/urlencoding/UonSerializerWriter.class ---------------------------------------------------------------------- diff --git a/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/urlencoding/UonSerializerWriter.class b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/urlencoding/UonSerializerWriter.class new file mode 100755 index 0000000..4fc3d48 Binary files /dev/null and b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/urlencoding/UonSerializerWriter.class differ http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7e4f63e6/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/urlencoding/UonSerializerWriter.java ---------------------------------------------------------------------- diff --git a/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/urlencoding/UonSerializerWriter.java b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/urlencoding/UonSerializerWriter.java new file mode 100755 index 0000000..3205234 --- /dev/null +++ b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/urlencoding/UonSerializerWriter.java @@ -0,0 +1,265 @@ +/******************************************************************************* + * Licensed Materials - Property of IBM + * (c) Copyright IBM Corporation 2014, 2015. All Rights Reserved. + * + * The source code for this program is not published or otherwise + * divested of its trade secrets, irrespective of what has been + * deposited with the U.S. Copyright Office. + *******************************************************************************/ +package com.ibm.juno.core.urlencoding; + +import java.io.*; + +import com.ibm.juno.core.serializer.*; +import com.ibm.juno.core.utils.*; + +/** + * Specialized writer for serializing UON-encoded text. + * <p> + * <b>Note: This class is not intended for external use.</b> + * + * @author James Bognar ([email protected]) + */ +public final class UonSerializerWriter extends SerializerWriter { + + private final boolean simpleMode, encodeChars; + + // Characters that do not need to be URL-encoded in strings. + private static final AsciiSet unencodedChars = new AsciiSet("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789;/?:@-_.!*'$(),~="); + + // Characters that do not need to be URL-encoded in attribute names. + // Identical to unencodedChars, but excludes '='. + private static final AsciiSet unencodedCharsAttrName = new AsciiSet("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789;/?:@-_.!*'$(),~"); + + // Characters that need to be preceeded with an escape character. + private static final AsciiSet escapedChars = new AsciiSet(",()~="); + + // AsciiSet that maps no characters. + private static final AsciiSet emptyCharSet = new AsciiSet(""); + + private static char[] hexArray = "0123456789ABCDEF".toCharArray(); + + /** + * Constructor. + * + * @param out The writer being wrapped. + * @param useIndentation If <jk>true</jk>, tabs will be used in output. + * @param simpleMode If <jk>true</jk>, type flags will not be generated in output. + * @param encodeChars If <jk>true</jk>, special characters should be encoded. + * @param relativeUriBase The base (e.g. <js>https://localhost:9443/contextPath"</js>) for relative URIs (e.g. <js>"my/path"</js>). + * @param absolutePathUriBase The base (e.g. <js>https://localhost:9443"</js>) for relative URIs with absolute paths (e.g. <js>"/contextPath/my/path"</js>). + */ + protected UonSerializerWriter(Writer out, boolean useIndentation, boolean simpleMode, boolean encodeChars, String relativeUriBase, String absolutePathUriBase) { + super(out, useIndentation, false, '\'', relativeUriBase, absolutePathUriBase); + this.simpleMode = simpleMode; + this.encodeChars = encodeChars; + } + + /** + * Serializes the specified simple object as a UON string value. + * + * @param o The object being serialized. + * @param quoteEmptyStrings Special case where we're serializing an array containing an empty string. + * @param isTopAttrName If this is a top-level attribute name we're serializing. + * @param isTop If this is a top-level value we're serializing. + * @return This object (for method chaining). + * @throws IOException Should never happen. + */ + protected UonSerializerWriter appendObject(Object o, boolean quoteEmptyStrings, boolean isTopAttrName, boolean isTop) throws IOException { + + char typeFlag = 0; + + if (o == null) + o = "\u0000"; + else if (o.equals("\u0000")) + typeFlag = 's'; + + String s = o.toString(); + if (s.isEmpty()) { + if (quoteEmptyStrings) + typeFlag = 's'; + } else if (s.charAt(0) == '(' || s.charAt(0) == '$') { + typeFlag = 's'; + } else if (useIndentation && (s.indexOf('\n') != -1 || (s.charAt(0) <= ' ' && s.charAt(0) != 0))) { + // Strings containing newline characters must always be quoted so that they're not confused with whitespace. + // Also, strings starting with whitespace must be quoted so that the contents are not ignored when whitespace is ignored. + typeFlag = 's'; + } else if (! simpleMode) { + if (o instanceof Boolean) + typeFlag = 'b'; + else if (o instanceof Number) + typeFlag = 'n'; + } + + if (typeFlag != 0) + startFlag(typeFlag); + + AsciiSet unenc = (isTopAttrName ? unencodedCharsAttrName : unencodedChars); + AsciiSet esc = (isTop && typeFlag == 0 ? emptyCharSet : escapedChars); + + for (int i = 0; i < s.length(); i++) { + char c = s.charAt(i); + if (esc.contains(c)) + append('~'); + if ((!encodeChars) || unenc.contains(c)) + append(c); + else { + if (c == ' ') + append('+'); + else { + int p = s.codePointAt(i); + if (p < 0x0080) + appendHex(p); + else if (p < 0x0800) { + int p1=p>>>6; + appendHex(p1+192).appendHex((p&63)+128); + } else if (p < 0x10000) { + int p1=p>>>6, p2=p1>>>6; + appendHex(p2+224).appendHex((p1&63)+128).appendHex((p&63)+128); + } else { + i++; // Two-byte codepoint...skip past surrogate pair lower byte. + int p1=p>>>6, p2=p1>>>6, p3=p2>>>6; + appendHex(p3+240).appendHex((p2&63)+128).appendHex((p1&63)+128).appendHex((p&63)+128); + } + } + } + } + + if (typeFlag != 0) + append(')'); + + return this; + } + + /** + * Prints <code>$f(</code> in normal mode, and <code>(</code> in simple mode. + * + * @param f The flag character. + * @return This object (for method chaining). + * @throws IOException + */ + protected UonSerializerWriter startFlag(char f) throws IOException { + if (f != 's' && ! simpleMode) + append('$').append(f); + append('('); + return this; + } + + /** + * Prints out a two-byte %xx sequence for the given byte value. + */ + private UonSerializerWriter appendHex(int b) throws IOException { + if (b > 255) + throw new IOException("Invalid value passed to appendHex. Must be in the range 0-255. Value=" + b); + append('%').append(hexArray[b>>>4]).append(hexArray[b&0x0F]); + return this; + } + + /** + * Appends a URI to the output. + * + * @param uri The URI to append to the output. + * @param isTop If this is a top-level value we're serializing. + * @return This object (for method chaining). + * @throws IOException + */ + public SerializerWriter appendUri(Object uri, boolean isTop) throws IOException { + String s = uri.toString(); + if (s.indexOf("://") == -1) { + if (StringUtils.startsWith(s, '/')) { + if (absolutePathUriBase != null) + append(absolutePathUriBase); + } else { + if (relativeUriBase != null) { + append(relativeUriBase); + if (! relativeUriBase.equals("/")) + append("/"); + + } + } + } + return appendObject(s, false, false, isTop); + } + + //-------------------------------------------------------------------------------- + // Overridden methods + //-------------------------------------------------------------------------------- + + @Override /* SerializerWriter */ + public UonSerializerWriter cr(int depth) throws IOException { + super.cr(depth); + return this; + } + + @Override /* SerializerWriter */ + public UonSerializerWriter appendln(int indent, String text) throws IOException { + super.appendln(indent, text); + return this; + } + + @Override /* SerializerWriter */ + public UonSerializerWriter appendln(String text) throws IOException { + super.appendln(text); + return this; + } + + @Override /* SerializerWriter */ + public UonSerializerWriter append(int indent, String text) throws IOException { + super.append(indent, text); + return this; + } + + @Override /* SerializerWriter */ + public UonSerializerWriter append(int indent, char c) throws IOException { + super.append(indent, c); + return this; + } + + @Override /* SerializerWriter */ + public UonSerializerWriter q() throws IOException { + super.q(); + return this; + } + + @Override /* SerializerWriter */ + public UonSerializerWriter i(int indent) throws IOException { + super.i(indent); + return this; + } + + @Override /* SerializerWriter */ + public UonSerializerWriter nl() throws IOException { + super.nl(); + return this; + } + + @Override /* SerializerWriter */ + public UonSerializerWriter append(Object text) throws IOException { + super.append(text); + return this; + } + + @Override /* SerializerWriter */ + public UonSerializerWriter append(String text) throws IOException { + super.append(text); + return this; + } + + @Override /* SerializerWriter */ + public UonSerializerWriter appendIf(boolean b, String text) throws IOException { + super.appendIf(b, text); + return this; + } + + @Override /* SerializerWriter */ + public UonSerializerWriter appendIf(boolean b, char c) throws IOException { + super.appendIf(b, c); + return this; + } + + @Override /* SerializerWriter */ + public UonSerializerWriter append(char c) throws IOException { + super.append(c); + return this; + } +} http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7e4f63e6/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/urlencoding/UrlEncodingClassMeta.class ---------------------------------------------------------------------- diff --git a/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/urlencoding/UrlEncodingClassMeta.class b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/urlencoding/UrlEncodingClassMeta.class new file mode 100755 index 0000000..307849c Binary files /dev/null and b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/urlencoding/UrlEncodingClassMeta.class differ http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7e4f63e6/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/urlencoding/UrlEncodingClassMeta.java ---------------------------------------------------------------------- diff --git a/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/urlencoding/UrlEncodingClassMeta.java b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/urlencoding/UrlEncodingClassMeta.java new file mode 100755 index 0000000..884dd1c --- /dev/null +++ b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/urlencoding/UrlEncodingClassMeta.java @@ -0,0 +1,55 @@ +/******************************************************************************* + * Licensed Materials - Property of IBM + * (c) Copyright IBM Corporation 2015. All Rights Reserved. + * + * The source code for this program is not published or otherwise + * divested of its trade secrets, irrespective of what has been + * deposited with the U.S. Copyright Office. + *******************************************************************************/ +package com.ibm.juno.core.urlencoding; + +import com.ibm.juno.core.urlencoding.annotation.*; +import com.ibm.juno.core.utils.*; + +/** + * Metadata on classes specific to the URL-Encoding serializers and parsers pulled from the {@link UrlEncoding @UrlEncoding} annotation on the class. + * + * @author James Bognar ([email protected]) + */ +public class UrlEncodingClassMeta { + + private final UrlEncoding urlEncoding; + private final boolean expandedParams; + + /** + * Constructor. + * + * @param c The class that this annotation is defined on. + */ + public UrlEncodingClassMeta(Class<?> c) { + this.urlEncoding = ReflectionUtils.getAnnotation(UrlEncoding.class, c); + if (urlEncoding != null) { + expandedParams = urlEncoding.expandedParams(); + } else { + expandedParams = false; + } + } + + /** + * Returns the {@link UrlEncoding} annotation defined on the class. + * + * @return The value of the {@link UrlEncoding} annotation, or <jk>null</jk> if annotation is not specified. + */ + protected UrlEncoding getAnnotation() { + return urlEncoding; + } + + /** + * Returns the {@link UrlEncoding#expandedParams()} annotation defined on the class. + * + * @return The value of the {@link UrlEncoding#expandedParams()} annotation. + */ + protected boolean isExpandedParams() { + return expandedParams; + } +} http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7e4f63e6/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/urlencoding/UrlEncodingParser.class ---------------------------------------------------------------------- diff --git a/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/urlencoding/UrlEncodingParser.class b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/urlencoding/UrlEncodingParser.class new file mode 100755 index 0000000..9b91ca9 Binary files /dev/null and b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/urlencoding/UrlEncodingParser.class differ http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7e4f63e6/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/urlencoding/UrlEncodingParser.java ---------------------------------------------------------------------- diff --git a/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/urlencoding/UrlEncodingParser.java b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/urlencoding/UrlEncodingParser.java new file mode 100755 index 0000000..feb4548 --- /dev/null +++ b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/urlencoding/UrlEncodingParser.java @@ -0,0 +1,568 @@ +/******************************************************************************* + * Licensed Materials - Property of IBM + * (c) Copyright IBM Corporation 2013, 2015. All Rights Reserved. + * + * The source code for this program is not published or otherwise + * divested of its trade secrets, irrespective of what has been + * deposited with the U.S. Copyright Office. + *******************************************************************************/ +package com.ibm.juno.core.urlencoding; + +import static com.ibm.juno.core.urlencoding.UonParserProperties.*; + +import java.io.*; +import java.lang.reflect.*; +import java.util.*; + +import com.ibm.juno.core.*; +import com.ibm.juno.core.annotation.*; +import com.ibm.juno.core.filter.*; +import com.ibm.juno.core.parser.*; +import com.ibm.juno.core.utils.*; + +/** + * Parses URL-encoded text into POJO models. + * + * + * <h6 class='topic'>Media types</h6> + * <p> + * Handles <code>Content-Type</code> types: <code>application/x-www-form-urlencoded</code> + * + * + * <h6 class='topic'>Description</h6> + * <p> + * Parses URL-Encoded text (e.g. <js>"foo=bar&baz=bing"</js>) into POJOs. + * <p> + * Expects parameter values to be in UON notation. + * <p> + * This parser uses a state machine, which makes it very fast and efficient. + * + * + * <h6 class='topic'>Configurable properties</h6> + * <p> + * This class has the following properties associated with it: + * <ul> + * <li>{@link UonParserProperties} + * <li>{@link ParserProperties} + * <li>{@link BeanContextProperties} + * </ul> + * + * + * @author James Bognar ([email protected]) + */ +@SuppressWarnings({ "rawtypes", "unchecked", "hiding" }) +@Consumes("application/x-www-form-urlencoded") +public class UrlEncodingParser extends UonParser { + + /** Reusable instance of {@link UrlEncodingParser}. */ + public static final UrlEncodingParser DEFAULT = new UrlEncodingParser().lock(); + + /** Reusable instance of {@link UrlEncodingParser}. */ + public static final UrlEncodingParser DEFAULT_WS_AWARE = new UrlEncodingParser().setProperty(UON_whitespaceAware, true).lock(); + + /** + * Constructor. + */ + public UrlEncodingParser() { + setProperty(UON_decodeChars, true); + } + + private <T> T parseAnything(ClassMeta<T> nt, UonParserContext ctx, ParserReader r, Object outer, Object name) throws ParseException { + + BeanContext bc = ctx.getBeanContext(); + if (nt == null) + nt = (ClassMeta<T>)object(); + PojoFilter<T,Object> filter = (PojoFilter<T,Object>)nt.getPojoFilter(); + ClassMeta<?> ft = nt.getFilteredClassMeta(); + + try { + int c = r.peek(); + if (c == '?') + r.read(); + + Object o; + + if (ft.isObject()) { + ObjectMap m = new ObjectMap(bc); + parseIntoMap(ctx, r, m, bc.string(), bc.object()); + o = m.cast(); + } else if (ft.isMap()) { + Map m = (ft.canCreateNewInstance() ? (Map)ft.newInstance() : new ObjectMap(bc)); + o = parseIntoMap(ctx, r, m, ft.getKeyType(), ft.getValueType()); + } else if (ft.canCreateNewInstanceFromObjectMap(outer)) { + ObjectMap m = new ObjectMap(bc); + parseIntoMap(ctx, r, m, string(), object()); + o = ft.newInstanceFromObjectMap(outer, m); + } else if (ft.canCreateNewBean(outer)) { + BeanMap m = bc.newBeanMap(outer, ft.getInnerClass()); + m = parseIntoBeanMap(ctx, r, m); + o = m == null ? null : m.getBean(); + } else { + // It could be a non-bean with _class attribute. + ObjectMap m = new ObjectMap(bc); + ClassMeta<Object> valueType = object(); + parseIntoMap(ctx, r, m, string(), valueType); + if (m.containsKey("_class")) + o = m.cast(); + else if (m.containsKey("_value")) + o = ctx.getBeanContext().convertToType(m.get("_value"), ft); + else if (ft.isCollection()) { + // ?1=foo&2=bar... + Collection c2 = ft.canCreateNewInstance() ? (Collection)ft.newInstance() : new ObjectList(bc); + Map<Integer,Object> t = new TreeMap<Integer,Object>(); + for (Map.Entry<String,Object> e : m.entrySet()) { + String k = e.getKey(); + if (StringUtils.isNumeric(k)) + t.put(Integer.valueOf(k), bc.convertToType(e.getValue(), ft.getElementType())); + } + c2.addAll(t.values()); + o = c2; + } else { + if (ft.getNotABeanReason() != null) + throw new ParseException("Class ''{0}'' could not be instantiated as application/x-www-form-urlencoded. Reason: ''{1}''", ft, ft.getNotABeanReason()); + throw new ParseException("Malformed application/x-www-form-urlencoded input for class ''{0}''.", ft); + } + } + + if (filter != null && o != null) + o = filter.unfilter(o, nt); + + if (outer != null) + setParent(nt, o, outer); + + if (name != null) + setName(nt, o, name); + + return (T)o; + + } catch (RuntimeException e) { + throw e; + } catch (Exception e) { + throw new ParseException("Error occurred trying to parse into class ''{0}''", ft).initCause(e); + } + } + + private <K,V> Map<K,V> parseIntoMap(UonParserContext ctx, ParserReader r, Map<K,V> m, ClassMeta<K> keyType, ClassMeta<V> valueType) throws ParseException, IOException { + + if (keyType == null) + keyType = (ClassMeta<K>)string(); + + int c = r.peek(); + if (c == -1) + return m; + + final int S1=1; // Looking for attrName start. + final int S2=2; // Found attrName end, looking for =. + final int S3=3; // Found =, looking for valStart. + final int S4=4; // Looking for & or end. + boolean isInEscape = false; + + int state = S1; + K currAttr = null; + while (c != -1) { + c = r.read(); + if (! isInEscape) { + if (state == S1) { + if (c == -1) + return m; + r.unread(); + Object attr = parseAttr(r, true, ctx); + currAttr = ctx.getBeanContext().convertToType(attr, keyType); + state = S2; + c = 0; // Avoid isInEscape if c was '\' + } else if (state == S2) { + if (c == '\u0002') + state = S3; + else if (c == -1 || c == '\u0001') { + m.put(currAttr, null); + if (c == -1) + return m; + state = S1; + } + } else if (state == S3) { + if (c == -1 || c == '\u0001') { + V value = convertAttrToType(m, "", valueType); + m.put(currAttr, value); + if (c == -1) + return m; + state = S1; + } else { + // For performance, we bypass parseAnything for string values. + V value = (V)(valueType.isString() ? super.parseString(r.unread(), true, ctx) : super.parseAnything(valueType, ctx, r.unread(), null, m, true, null)); + + // If we already encountered this parameter, turn it into a list. + if (m.containsKey(currAttr) && valueType.isObject()) { + Object v2 = m.get(currAttr); + if (! (v2 instanceof ObjectList)) { + v2 = new ObjectList(v2); + m.put(currAttr, (V)v2); + } + ((ObjectList)v2).add(value); + } else { + m.put(currAttr, value); + } + state = S4; + c = 0; // Avoid isInEscape if c was '\' + } + } else if (state == S4) { + if (c == '\u0001') + state = S1; + else if (c == -1) { + return m; + } + } + } + isInEscape = (c == '\\' && ! isInEscape); + } + if (state == S1) + throw new ParseException("Could not find attribute name on object."); + if (state == S2) + throw new ParseException("Could not find '=' following attribute name on object."); + if (state == S3) + throw new ParseException("Dangling '=' found in object entry"); + if (state == S4) + throw new ParseException("Could not find end of object."); + + return null; // Unreachable. + } + + private <T> BeanMap<T> parseIntoBeanMap(UonParserContext ctx, ParserReader r, BeanMap<T> m) throws ParseException, IOException { + int line = r.getLine(); + int column = r.getColumn(); + + int c = r.peek(); + if (c == -1) + return m; + + final int S1=1; // Looking for attrName start. + final int S2=2; // Found attrName end, looking for =. + final int S3=3; // Found =, looking for valStart. + final int S4=4; // Looking for , or } + boolean isInEscape = false; + + int state = S1; + String currAttr = ""; + int currAttrLine = -1, currAttrCol = -1; + while (c != -1) { + c = r.read(); + if (! isInEscape) { + if (state == S1) { + if (c == -1) { + return m; + } + r.unread(); + currAttrLine= r.getLine(); + currAttrCol = r.getColumn(); + currAttr = parseAttrName(r, true); + if (currAttr == null) // Value was '%00' + return null; + state = S2; + } else if (state == S2) { + if (c == '\u0002') + state = S3; + else if (c == -1 || c == '\u0001') { + m.put(currAttr, null); + if (c == -1) + return m; + state = S1; + } + } else if (state == S3) { + if (c == -1 || c == '\u0001') { + if (! currAttr.equals("_class")) { + BeanPropertyMeta pMeta = m.getPropertyMeta(currAttr); + if (pMeta == null) { + if (m.getMeta().isSubTyped()) { + m.put(currAttr, ""); + } else { + onUnknownProperty(ctx, currAttr, m, currAttrLine, currAttrCol); + } + } else { + try { + // In cases of "&foo=", create an empty instance of the value if createable. + // Otherwise, leave it null. + ClassMeta<?> cm = pMeta.getClassMeta(); + if (cm.canCreateNewInstance()) + pMeta.set(m, cm.newInstance()); + } catch (Exception e) { + throw new ParseException(e); + } + } + } + if (c == -1) + return m; + state = S1; + } else { + if (! currAttr.equals("_class")) { + BeanPropertyMeta pMeta = m.getPropertyMeta(currAttr); + if (pMeta == null) { + if (m.getMeta().isSubTyped()) { + m.put(currAttr, parseAnything(object(), ctx, r.unread(), null, m.getBean(false), true, currAttr)); + } else { + onUnknownProperty(ctx, currAttr, m, currAttrLine, currAttrCol); + parseAnything(object(), ctx, r.unread(), null, m.getBean(false), true, null); // Read content anyway to ignore it + } + } else { + if (shouldUseExpandedParams(pMeta, ctx)) { + ClassMeta cm = pMeta.getClassMeta(); + Object value = parseAnything(cm.getElementType(), ctx, r.unread(), pMeta, m.getBean(false), true, currAttr); + pMeta.add(m, value); + } else { + Object value = parseAnything(pMeta.getClassMeta(), ctx, r.unread(), pMeta, m.getBean(false), true, currAttr); + pMeta.set(m, value); + } + } + } + state = S4; + } + } else if (state == S4) { + if (c == '\u0001') + state = S1; + else if (c == -1) { + return m; + } + } + } + isInEscape = (c == '\\' && ! isInEscape); + } + if (state == S1) + throw new ParseException(line, column, "Could not find attribute name on object."); + if (state == S2) + throw new ParseException(line, column, "Could not find '=' following attribute name on object."); + if (state == S3) + throw new ParseException(line, column, "Could not find value following '=' on object."); + if (state == S4) + throw new ParseException(line, column, "Could not find end of object."); + + return null; // Unreachable. + } + + /** + * Returns true if the specified bean property should be expanded as multiple key-value pairs. + */ + private final boolean shouldUseExpandedParams(BeanPropertyMeta<?> pMeta, UonParserContext ctx) { + ClassMeta cm = pMeta.getClassMeta(); + if (cm.isArray() || cm.isCollection()) { + if (ctx.isExpandedParams()) + return true; + if (pMeta.getBeanMeta().getClassMeta().getUrlEncodingMeta().isExpandedParams()) + return true; + } + return false; + } + + /** + * Parse a URL query string into a simple map of key/value pairs. + * + * @param qs The query string to parse. + * @return A sorted {@link TreeMap} of query string entries. + * @throws IOException + */ + public Map<String,String[]> parseIntoSimpleMap(String qs) throws IOException { + + Map<String,String[]> m = new TreeMap<String,String[]>(); + + if (StringUtils.isEmpty(qs)) + return m; + + UonParserReader r = new UonParserReader(qs, true); + + final int S1=1; // Looking for attrName start. + final int S2=2; // Found attrName start, looking for = or & or end. + final int S3=3; // Found =, looking for valStart. + final int S4=4; // Found valStart, looking for & or end. + + try { + int c = r.peek(); + if (c == '?') + r.read(); + + int state = S1; + String currAttr = null; + while (c != -1) { + c = r.read(); + if (state == S1) { + if (c != -1) { + r.unread(); + r.mark(); + state = S2; + } + } else if (state == S2) { + if (c == -1) { + add(m, r.getMarked(), null); + } else if (c == '\u0001') { + m.put(r.getMarked(0,-1), null); + state = S1; + } else if (c == '\u0002') { + currAttr = r.getMarked(0,-1); + state = S3; + } + } else if (state == S3) { + if (c == -1 || c == '\u0001') { + add(m, currAttr, ""); + } else { + if (c == '\u0002') + r.replace('='); + r.unread(); + r.mark(); + state = S4; + } + } else if (state == S4) { + if (c == -1) { + add(m, currAttr, r.getMarked()); + } else if (c == '\u0001') { + add(m, currAttr, r.getMarked(0,-1)); + state = S1; + } else if (c == '\u0002') { + r.replace('='); + } + } + } + } finally { + r.close(); + } + + return m; + } + + private static void add(Map<String,String[]> m, String key, String val) { + boolean b = m.containsKey(key); + if (val == null) { + if (! b) + m.put(key, null); + } else if (b && m.get(key) != null) { + m.put(key, ArrayUtils.append(m.get(key), val)); + } else { + m.put(key, new String[]{val}); + } + } + + private Object[] parseArgs(UonParserContext ctx, ParserReader r, ClassMeta<?>[] argTypes) throws ParseException { + // TODO - This can be made more efficient. + BeanContext bc = ctx.getBeanContext(); + ClassMeta<TreeMap<Integer,String>> cm = bc.getMapClassMeta(TreeMap.class, Integer.class, String.class); + TreeMap<Integer,String> m = parseAnything(cm, ctx, r, ctx.getOuter(), null); + Object[] vals = m.values().toArray(new Object[m.size()]); + if (vals.length != argTypes.length) + throw new ParseException("Argument lengths don't match. vals={0}, argTypes={1}", vals.length, argTypes.length); + for (int i = 0; i < vals.length; i++) { + String s = String.valueOf(vals[i]); + vals[i] = super.parseAnything(argTypes[i], ctx, ctx.getUrlEncodingParserReader(new StringReader(s), s.length()), null, ctx.getOuter(), true, null); + } + + return vals; + } + + /** + * Parses a single query parameter value into the specified class type. + * + * @param in The input query string value. + * @param type The class type of the object to create. + * @return A new instance of the specified type. + * @throws ParseException + */ + public <T> T parseParameter(CharSequence in, ClassMeta<T> type) throws ParseException { + if (in == null) + return null; + UonParserContext uctx = (UonParserContext)createContext(); + uctx.decodeChars = false; + UonParserReader r = uctx.getUrlEncodingParserReader(wrapReader(in), in.length()); + return super.parseAnything(type, uctx, r, null, null, true, null); + } + + /** + * Parses a single query parameter value into the specified class type. + * + * @param in The input query string value. + * @param type The class type of the object to create. + * @return A new instance of the specified type. + * @throws ParseException + */ + public <T> T parseParameter(CharSequence in, Class<T> type) throws ParseException { + return parseParameter(in, getBeanContext().getClassMeta(type)); + } + + //-------------------------------------------------------------------------------- + // Overridden methods + //-------------------------------------------------------------------------------- + + @Override /* Parser */ + protected <T> T doParse(Reader in, int estimatedSize, ClassMeta<T> type, ParserContext ctx) throws ParseException, IOException { + UonParserContext uctx = (UonParserContext)ctx; + type = ctx.getBeanContext().normalizeClassMeta(type); + UonParserReader r = uctx.getUrlEncodingParserReader(in, estimatedSize); + T o = parseAnything(type, uctx, r, ctx.getOuter(), null); + return o; + } + + @Override /* ReaderParser */ + protected Object[] doParseArgs(Reader in, int estimatedSize, ClassMeta<?>[] argTypes, ParserContext ctx) throws ParseException, IOException { + UonParserContext uctx = (UonParserContext)ctx; + UonParserReader r = uctx.getUrlEncodingParserReader(in, estimatedSize); + Object[] a = parseArgs(uctx, r, argTypes); + return a; + } + + @Override /* ReaderParser */ + protected <K,V> Map<K,V> doParseIntoMap(Reader in, int estimatedSize, Map<K,V> m, Type keyType, Type valueType, ParserContext ctx) throws ParseException, IOException { + UonParserContext uctx = (UonParserContext)ctx; + UonParserReader r = uctx.getUrlEncodingParserReader(in, estimatedSize); + if (r.peek() == '?') + r.read(); + m = parseIntoMap(uctx, r, m, ctx.getBeanContext().getClassMeta(keyType), ctx.getBeanContext().getClassMeta(valueType)); + return m; + } + + @Override /* Parser */ + public UrlEncodingParser setProperty(String property, Object value) throws LockedException { + checkLock(); + if (! upp.setProperty(property, value)) + if (! uep.setProperty(property, value)) + super.setProperty(property, value); + return this; + } + + @Override /* CoreApi */ + public UrlEncodingParser setProperties(ObjectMap properties) throws LockedException { + for (Map.Entry<String,Object> e : properties.entrySet()) + setProperty(e.getKey(), e.getValue()); + return this; + } + + @Override /* CoreApi */ + public UrlEncodingParser addNotBeanClasses(Class<?>...classes) throws LockedException { + super.addNotBeanClasses(classes); + return this; + } + + @Override /* CoreApi */ + public UrlEncodingParser addFilters(Class<?>...classes) throws LockedException { + super.addFilters(classes); + return this; + } + + @Override /* CoreApi */ + public <T> UrlEncodingParser addImplClass(Class<T> interfaceClass, Class<? extends T> implClass) throws LockedException { + super.addImplClass(interfaceClass, implClass); + return this; + } + + @Override /* CoreApi */ + public UrlEncodingParser setClassLoader(ClassLoader classLoader) throws LockedException { + super.setClassLoader(classLoader); + return this; + } + + @Override /* Lockable */ + public UrlEncodingParser lock() { + super.lock(); + return this; + } + + @Override /* Lockable */ + public UrlEncodingParser clone() { + UrlEncodingParser c = (UrlEncodingParser)super.clone(); + c.upp = upp.clone(); + c.uep = uep.clone(); + return c; + } +} http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7e4f63e6/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/urlencoding/UrlEncodingProperties.class ---------------------------------------------------------------------- diff --git a/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/urlencoding/UrlEncodingProperties.class b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/urlencoding/UrlEncodingProperties.class new file mode 100755 index 0000000..2e6d92e Binary files /dev/null and b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/urlencoding/UrlEncodingProperties.class differ http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7e4f63e6/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/urlencoding/UrlEncodingProperties.java ---------------------------------------------------------------------- diff --git a/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/urlencoding/UrlEncodingProperties.java b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/urlencoding/UrlEncodingProperties.java new file mode 100755 index 0000000..4c65132 --- /dev/null +++ b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/urlencoding/UrlEncodingProperties.java @@ -0,0 +1,82 @@ +/******************************************************************************* + * Licensed Materials - Property of IBM + * (c) Copyright IBM Corporation 2014, 2015. All Rights Reserved. + * + * The source code for this program is not published or otherwise + * divested of its trade secrets, irrespective of what has been + * deposited with the U.S. Copyright Office. + *******************************************************************************/ +package com.ibm.juno.core.urlencoding; + +import com.ibm.juno.core.*; + +/** + * Configurable properties on the {@link UrlEncodingSerializer} and {@link UrlEncodingParser} classes. + * <p> + * Use the {@link UrlEncodingSerializer#setProperty(String, Object)} and + * {@link UrlEncodingParser#setProperty(String, Object)} methods to set property values. + * + * @author James Bognar ([email protected]) + */ +public final class UrlEncodingProperties implements Cloneable { + + /** + * Serialize bean property collections/arrays as separate key/value pairs ({@link Boolean}, default=<jk>false</jk>). + * <p> + * If <jk>false</jk>, serializing the array <code>[1,2,3]</code> results in <code>?key=$a(1,2,3)</code>. + * If <jk>true</jk>, serializing the same array results in <code>?key=1&key=2&key=3</code>. + * <p> + * Example: + * <p class='bcode'> + * <jk>public class</jk> A { + * <jk>public</jk> String[] f1 = {<js>"a"</js>,<js>"b"</js>}; + * <jk>public</jk> List<String> f2 = <jk>new</jk> LinkedList<String>(Arrays.<jsm>asList</jsm>(<jk>new</jk> String[]{<js>"c"</js>,<js>"d"</js>})); + * } + * + * UrlEncodingSerializer s1 = <jk>new</jk> UrlEncodingParser(); + * UrlEncodingSerializer s2 = <jk>new</jk> UrlEncodingParser().setProperty(UrlEncodingProperties.<jsf>URLENC_expandedParams</jsf>, <jk>true</jk>); + * + * String s1 = p1.serialize(<jk>new</jk> A()); <jc>// Produces "f1=(a,b)&f2=(c,d)"</jc> + * String s2 = p2.serialize(<jk>new</jk> A()); <jc>// Produces "f1=a&f1=b&f2=c&f2=d"</jc> + * </p> + * <p> + * <b>Important note:</b> If parsing multi-part parameters, it's highly recommended to use Collections or Lists + * as bean property types instead of arrays since arrays have to be recreated from scratch every time a value + * is added to it. + * <p> + * This option only applies to beans. + */ + public static final String URLENC_expandedParams = "UrlEncoding.expandedParams"; + + boolean + expandedParams = false; + + /** + * Sets the specified property value. + * @param property The property name. + * @param value The property value. + * @return <jk>true</jk> if property name was valid and property was set. + */ + public boolean setProperty(String property, Object value) { + BeanContext bc = BeanContext.DEFAULT; + if (property.equals(URLENC_expandedParams)) + expandedParams = bc.convertToType(value, Boolean.class); + else + return false; + return true; + } + + + //-------------------------------------------------------------------------------- + // Overridden methods + //-------------------------------------------------------------------------------- + + @Override /* Cloneable */ + public UrlEncodingProperties clone() { + try { + return (UrlEncodingProperties)super.clone(); + } catch (CloneNotSupportedException e) { + throw new RuntimeException(e); // Shouldn't happen + } + } +} http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7e4f63e6/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/urlencoding/UrlEncodingSerializer$Readable.class ---------------------------------------------------------------------- diff --git a/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/urlencoding/UrlEncodingSerializer$Readable.class b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/urlencoding/UrlEncodingSerializer$Readable.class new file mode 100755 index 0000000..37a40a0 Binary files /dev/null and b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/urlencoding/UrlEncodingSerializer$Readable.class differ http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7e4f63e6/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/urlencoding/UrlEncodingSerializer$Simple.class ---------------------------------------------------------------------- diff --git a/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/urlencoding/UrlEncodingSerializer$Simple.class b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/urlencoding/UrlEncodingSerializer$Simple.class new file mode 100755 index 0000000..51f3f69 Binary files /dev/null and b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/urlencoding/UrlEncodingSerializer$Simple.class differ http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7e4f63e6/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/urlencoding/UrlEncodingSerializer$SimpleExpanded.class ---------------------------------------------------------------------- diff --git a/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/urlencoding/UrlEncodingSerializer$SimpleExpanded.class b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/urlencoding/UrlEncodingSerializer$SimpleExpanded.class new file mode 100755 index 0000000..e11f2ac Binary files /dev/null and b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/urlencoding/UrlEncodingSerializer$SimpleExpanded.class differ http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7e4f63e6/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/urlencoding/UrlEncodingSerializer.class ---------------------------------------------------------------------- diff --git a/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/urlencoding/UrlEncodingSerializer.class b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/urlencoding/UrlEncodingSerializer.class new file mode 100755 index 0000000..83bb958 Binary files /dev/null and b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/urlencoding/UrlEncodingSerializer.class differ
