http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/2a37f310/juneau-core/src/main/java/org/apache/juneau/json/JsonParserSession.java ---------------------------------------------------------------------- diff --git a/juneau-core/src/main/java/org/apache/juneau/json/JsonParserSession.java b/juneau-core/src/main/java/org/apache/juneau/json/JsonParserSession.java index 17571c0..6cb332a 100644 --- a/juneau-core/src/main/java/org/apache/juneau/json/JsonParserSession.java +++ b/juneau-core/src/main/java/org/apache/juneau/json/JsonParserSession.java @@ -12,23 +12,28 @@ // *************************************************************************************************************************** package org.apache.juneau.json; +import static org.apache.juneau.internal.StringUtils.*; + import java.io.*; import java.lang.reflect.*; import java.util.*; import org.apache.juneau.*; -import org.apache.juneau.http.*; +import org.apache.juneau.internal.*; import org.apache.juneau.parser.*; +import org.apache.juneau.transform.*; /** * Session object that lives for the duration of a single use of {@link JsonParser}. * * <p> - * This class is NOT thread safe. It is meant to be discarded after one-time use. + * This class is NOT thread safe. + * It is typically discarded after one-time use although it can be reused against multiple inputs. */ -public final class JsonParserSession extends ParserSession { +@SuppressWarnings({ "unchecked", "rawtypes" }) +public final class JsonParserSession extends ReaderParserSession { - private ParserReader reader; + private static final AsciiSet decChars = new AsciiSet("0123456789"); /** * Create a new session using properties specified in the context. @@ -36,46 +41,11 @@ public final class JsonParserSession extends ParserSession { * @param ctx * The context creating this session object. * The context contains all the configuration settings for this object. - * @param input - * The input. - * Can be any of the following types: - * <ul> - * <li><jk>null</jk> - * <li>{@link Reader} - * <li>{@link CharSequence} - * <li>{@link InputStream} containing UTF-8 encoded text. - * <li>{@link File} containing system encoded text. - * </ul> - * @param op - * The override properties. - * These override any context properties defined in the context. - * @param javaMethod The java method that called this parser, usually the method in a REST servlet. - * @param outer The outer object for instantiating top-level non-static inner classes. - * @param locale - * The session locale. - * If <jk>null</jk>, then the locale defined on the context is used. - * @param timeZone - * The session timezone. - * If <jk>null</jk>, then the timezone defined on the context is used. - * @param mediaType The session media type (e.g. <js>"application/json"</js>). + * @param args + * Runtime session arguments. */ - public JsonParserSession(JsonParserContext ctx, ObjectMap op, Object input, Method javaMethod, Object outer, - Locale locale, TimeZone timeZone, MediaType mediaType) { - super(ctx, op, input, javaMethod, outer, locale, timeZone, mediaType); - } - - @Override /* ParserSession */ - public ParserReader getReader() throws Exception { - if (reader == null) { - Object input = getInput(); - if (input == null) - return null; - if (input instanceof CharSequence) - reader = new ParserReader((CharSequence)input); - else - reader = new ParserReader(super.getReader()); - } - return reader; + protected JsonParserSession(JsonParserContext ctx, ParserSessionArgs args) { + super(ctx, args); } /** @@ -89,7 +59,7 @@ public final class JsonParserSession extends ParserSession { * @param cp The codepoint. * @return <jk>true</jk> if the specified character is whitespace. */ - public final boolean isWhitespace(int cp) { + protected final boolean isWhitespace(int cp) { if (isStrict()) return cp <= 0x20 && (cp == 0x09 || cp == 0x0A || cp == 0x0D || cp == 0x20); return Character.isWhitespace(cp); @@ -101,21 +71,677 @@ public final class JsonParserSession extends ParserSession { * @param cp The codepoint. * @return <jk>true</jk> if the specified character is whitespace or '/'. */ - public final boolean isCommentOrWhitespace(int cp) { + protected final boolean isCommentOrWhitespace(int cp) { if (cp == '/') return true; if (isStrict()) return cp <= 0x20 && (cp == 0x09 || cp == 0x0A || cp == 0x0D || cp == 0x20); - return Character.isWhitespace(cp); + return Character.isWhitespace(cp); } @Override /* ParserSession */ - public Map<String,Object> getLastLocation() { - Map<String,Object> m = super.getLastLocation(); - if (reader != null) { - m.put("line", reader.getLine()); - m.put("column", reader.getColumn()); - } + protected <T> T doParse(ParserPipe pipe, ClassMeta<T> type) throws Exception { + ParserReader r = pipe.getParserReader(); + if (r == null) + return null; + T o = parseAnything(type, r, getOuter(), null); + validateEnd(r); + return o; + } + + @Override /* ReaderParserSession */ + protected <K,V> Map<K,V> doParseIntoMap(ParserPipe pipe, Map<K,V> m, Type keyType, Type valueType) throws Exception { + ParserReader r = pipe.getParserReader(); + m = parseIntoMap2(r, m, (ClassMeta<K>)getClassMeta(keyType), (ClassMeta<V>)getClassMeta(valueType), null); + validateEnd(r); return m; } + + @Override /* ReaderParserSession */ + protected <E> Collection<E> doParseIntoCollection(ParserPipe pipe, Collection<E> c, Type elementType) throws Exception { + ParserReader r = pipe.getParserReader(); + c = parseIntoCollection2(r, c, getClassMeta(elementType), null); + validateEnd(r); + return c; + } + + private <T> T parseAnything(ClassMeta<T> eType, ParserReader r, Object outer, BeanPropertyMeta pMeta) throws Exception { + + if (eType == null) + eType = (ClassMeta<T>)object(); + PojoSwap<T,Object> transform = (PojoSwap<T,Object>)eType.getPojoSwap(); + ClassMeta<?> sType = eType.getSerializedClassMeta(); + setCurrentClass(sType); + String wrapperAttr = sType.getExtendedMeta(JsonClassMeta.class).getWrapperAttr(); + + Object o = null; + + skipCommentsAndSpace(r); + if (wrapperAttr != null) + skipWrapperAttrStart(r, wrapperAttr); + int c = r.peek(); + if (c == -1) { + if (isStrict()) + throw new ParseException(r.getLocation(this), "Empty input."); + // Let o be null. + } else if ((c == ',' || c == '}' || c == ']')) { + if (isStrict()) + throw new ParseException(r.getLocation(this), "Missing value detected."); + // Handle bug in Cognos 10.2.1 that can product non-existent values. + // Let o be null; + } else if (c == 'n') { + parseKeyword("null", r); + } else if (sType.isObject()) { + if (c == '{') { + ObjectMap m2 = new ObjectMap(this); + parseIntoMap2(r, m2, string(), object(), pMeta); + o = cast(m2, pMeta, eType); + } else if (c == '[') { + o = parseIntoCollection2(r, new ObjectList(this), object(), pMeta); + } else if (c == '\'' || c == '"') { + o = parseString(r); + if (sType.isChar()) + o = o.toString().charAt(0); + } else if (c >= '0' && c <= '9' || c == '-' || c == '.') { + o = parseNumber(r, null); + } else if (c == 't') { + parseKeyword("true", r); + o = Boolean.TRUE; + } else { + parseKeyword("false", r); + o = Boolean.FALSE; + } + } else if (sType.isBoolean()) { + o = parseBoolean(r); + } else if (sType.isCharSequence()) { + o = parseString(r); + } else if (sType.isChar()) { + o = parseString(r).charAt(0); + } else if (sType.isNumber()) { + o = parseNumber(r, (Class<? extends Number>)sType.getInnerClass()); + } else if (sType.isMap()) { + Map m = (sType.canCreateNewInstance(outer) ? (Map)sType.newInstance(outer) : new ObjectMap(this)); + o = parseIntoMap2(r, m, sType.getKeyType(), sType.getValueType(), pMeta); + } else if (sType.isCollection()) { + if (c == '{') { + ObjectMap m = new ObjectMap(this); + parseIntoMap2(r, m, string(), object(), pMeta); + o = cast(m, pMeta, eType); + } else { + Collection l = (sType.canCreateNewInstance(outer) ? (Collection)sType.newInstance() : new ObjectList(this)); + o = parseIntoCollection2(r, l, sType, pMeta); + } + } else if (sType.canCreateNewBean(outer)) { + BeanMap m = newBeanMap(outer, sType.getInnerClass()); + o = parseIntoBeanMap2(r, m).getBean(); + } else if (sType.canCreateNewInstanceFromString(outer) && (c == '\'' || c == '"')) { + o = sType.newInstanceFromString(outer, parseString(r)); + } else if (sType.canCreateNewInstanceFromNumber(outer) && isFirstNumberChar((char)c)) { + o = sType.newInstanceFromNumber(this, outer, parseNumber(r, sType.getNewInstanceFromNumberClass())); + } else if (sType.isArray() || sType.isArgs()) { + if (c == '{') { + ObjectMap m = new ObjectMap(this); + parseIntoMap2(r, m, string(), object(), pMeta); + o = cast(m, pMeta, eType); + } else { + ArrayList l = (ArrayList)parseIntoCollection2(r, new ArrayList(), sType, pMeta); + o = toArray(sType, l); + } + } else if (c == '{') { + Map m = new ObjectMap(this); + parseIntoMap2(r, m, sType.getKeyType(), sType.getValueType(), pMeta); + if (m.containsKey(getBeanTypePropertyName(eType))) + o = cast((ObjectMap)m, pMeta, eType); + else + throw new ParseException(r.getLocation(this), "Class ''{0}'' could not be instantiated. Reason: ''{1}''", + sType.getInnerClass().getName(), sType.getNotABeanReason()); + } else if (sType.canCreateNewInstanceFromString(outer) && ! isStrict()) { + o = sType.newInstanceFromString(outer, parseString(r)); + } else { + throw new ParseException(r.getLocation(this), "Unrecognized syntax for class type ''{0}'', starting character ''{1}''", + sType, (char)c); + } + + if (wrapperAttr != null) + skipWrapperAttrEnd(r); + + if (transform != null && o != null) + o = transform.unswap(this, o, eType); + + if (outer != null) + setParent(eType, o, outer); + + return (T)o; + } + + private Number parseNumber(ParserReader r, Class<? extends Number> type) throws Exception { + int c = r.peek(); + if (c == '\'' || c == '"') + return parseNumber(r, parseString(r), type); + return parseNumber(r, parseNumberString(r), type); + } + + private Number parseNumber(ParserReader r, String s, Class<? extends Number> type) throws Exception { + + // JSON has slightly different number rules from Java. + // Strict mode enforces these different rules, lax does not. + if (isStrict()) { + + // Lax allows blank strings to represent 0. + // Strict does not allow blank strings. + if (s.length() == 0) + throw new ParseException(r.getLocation(this), "Invalid JSON number: ''{0}''", s); + + // Need to weed out octal and hexadecimal formats: 0123,-0123,0x123,-0x123. + // Don't weed out 0 or -0. + boolean isNegative = false; + char c = s.charAt(0); + if (c == '-') { + isNegative = true; + c = (s.length() == 1 ? 'x' : s.charAt(1)); + } + + // JSON doesn't allow '.123' and '-.123'. + if (c == '.') + throw new ParseException(loc(r), "Invalid JSON number: ''{0}''", s); + + // '01' is not a valid number, but '0.1', '0e1', '0e+1' are valid. + if (c == '0' && s.length() > (isNegative ? 2 : 1)) { + char c2 = s.charAt((isNegative ? 2 : 1)); + if (c2 != '.' && c2 != 'e' && c2 != 'E') + throw new ParseException(loc(r), "Invalid JSON number: ''{0}''", s); + } + + // JSON doesn't allow '1.' or '0.e1'. + int i = s.indexOf('.'); + if (i != -1 && (s.length() == (i+1) || ! decChars.contains(s.charAt(i+1)))) + throw new ParseException(loc(r), "Invalid JSON number: ''{0}''", s); + + } + return StringUtils.parseNumber(s, type); + } + + private Boolean parseBoolean(ParserReader r) throws Exception { + int c = r.peek(); + if (c == '\'' || c == '"') + return Boolean.valueOf(parseString(r)); + if (c == 't') { + parseKeyword("true", r); + return Boolean.TRUE; + } + parseKeyword("false", r); + return Boolean.FALSE; + } + + + private <K,V> Map<K,V> parseIntoMap2(ParserReader r, Map<K,V> m, ClassMeta<K> keyType, + ClassMeta<V> valueType, BeanPropertyMeta pMeta) throws Exception { + + if (keyType == null) + keyType = (ClassMeta<K>)string(); + + int S0=0; // Looking for outer { + int S1=1; // Looking for attrName start. + int S3=3; // Found attrName end, looking for :. + int S4=4; // Found :, looking for valStart: { [ " ' LITERAL. + int S5=5; // Looking for , or } + int S6=6; // Found , looking for attr start. + + int state = S0; + String currAttr = null; + int c = 0; + while (c != -1) { + c = r.read(); + if (state == S0) { + if (c == '{') + state = S1; + } else if (state == S1) { + if (c == '}') { + return m; + } else if (isCommentOrWhitespace(c)) { + skipCommentsAndSpace(r.unread()); + } else { + currAttr = parseFieldName(r.unread()); + state = S3; + } + } else if (state == S3) { + if (c == ':') + state = S4; + } else if (state == S4) { + if (isCommentOrWhitespace(c)) { + skipCommentsAndSpace(r.unread()); + } else { + K key = convertAttrToType(m, currAttr, keyType); + V value = parseAnything(valueType, r.unread(), m, pMeta); + setName(valueType, value, key); + m.put(key, value); + state = S5; + } + } else if (state == S5) { + if (c == ',') + state = S6; + else if (isCommentOrWhitespace(c)) + skipCommentsAndSpace(r.unread()); + else if (c == '}') { + return m; + } else { + break; + } + } else if (state == S6) { + if (c == '}') { + break; + } else if (isCommentOrWhitespace(c)) { + skipCommentsAndSpace(r.unread()); + } else { + currAttr = parseFieldName(r.unread()); + state = S3; + } + } + } + if (state == S0) + throw new ParseException(loc(r), "Expected '{' at beginning of JSON object."); + if (state == S1) + throw new ParseException(loc(r), "Could not find attribute name on JSON object."); + if (state == S3) + throw new ParseException(loc(r), "Could not find ':' following attribute name on JSON object."); + if (state == S4) + throw new ParseException(loc(r), "Expected one of the following characters: {,[,',\",LITERAL."); + if (state == S5) + throw new ParseException(loc(r), "Could not find '}' marking end of JSON object."); + if (state == S6) + throw new ParseException(loc(r), "Unexpected '}' found in JSON object."); + + return null; // Unreachable. + } + + /* + * Parse a JSON attribute from the character array at the specified position, then + * set the position marker to the last character in the field name. + */ + private String parseFieldName(ParserReader r) throws Exception { + int c = r.peek(); + if (c == '\'' || c == '"') + return parseString(r); + if (isStrict()) + throw new ParseException(loc(r), "Unquoted attribute detected."); + r.mark(); + // Look for whitespace. + while (c != -1) { + c = r.read(); + if (c == ':' || isWhitespace(c) || c == '/') { + r.unread(); + String s = r.getMarked().intern(); + return s.equals("null") ? null : s; + } + } + throw new ParseException(loc(r), "Could not find the end of the field name."); + } + + private <E> Collection<E> parseIntoCollection2(ParserReader r, Collection<E> l, + ClassMeta<?> type, BeanPropertyMeta pMeta) throws Exception { + + int S0=0; // Looking for outermost [ + int S1=1; // Looking for starting [ or { or " or ' or LITERAL or ] + int S2=2; // Looking for , or ] + int S3=3; // Looking for starting [ or { or " or ' or LITERAL + + int argIndex = 0; + + int state = S0; + int c = 0; + while (c != -1) { + c = r.read(); + if (state == S0) { + if (c == '[') + state = S1; + } else if (state == S1) { + if (c == ']') { + return l; + } else if (isCommentOrWhitespace(c)) { + skipCommentsAndSpace(r.unread()); + } else if (c != -1) { + l.add((E)parseAnything(type.isArgs() ? type.getArg(argIndex++) : type.getElementType(), r.unread(), l, pMeta)); + state = S2; + } + } else if (state == S2) { + if (c == ',') { + state = S3; + } else if (isCommentOrWhitespace(c)) { + skipCommentsAndSpace(r.unread()); + } else if (c == ']') { + return l; + } else { + break; // Invalid character found. + } + } else if (state == S3) { + if (isCommentOrWhitespace(c)) { + skipCommentsAndSpace(r.unread()); + } else if (c == ']') { + break; + } else if (c != -1) { + l.add((E)parseAnything(type.isArgs() ? type.getArg(argIndex++) : type.getElementType(), r.unread(), l, pMeta)); + state = S2; + } + } + } + if (state == S0) + throw new ParseException(loc(r), "Expected '[' at beginning of JSON array."); + if (state == S1) + throw new ParseException(loc(r), "Expected one of the following characters: {,[,',\",LITERAL."); + if (state == S2) + throw new ParseException(loc(r), "Expected ',' or ']'."); + if (state == S3) + throw new ParseException(loc(r), "Unexpected trailing comma in array."); + + return null; // Unreachable. + } + + private <T> BeanMap<T> parseIntoBeanMap2(ParserReader r, BeanMap<T> m) throws Exception { + + int S0=0; // Looking for outer { + int S1=1; // Looking for attrName start. + int S3=3; // Found attrName end, looking for :. + int S4=4; // Found :, looking for valStart: { [ " ' LITERAL. + int S5=5; // Looking for , or } + + int state = S0; + String currAttr = ""; + int c = 0; + int currAttrLine = -1, currAttrCol = -1; + while (c != -1) { + c = r.read(); + if (state == S0) { + if (c == '{') + state = S1; + } else if (state == S1) { + if (c == '}') { + return m; + } else if (isCommentOrWhitespace(c)) { + skipCommentsAndSpace(r.unread()); + } else { + r.unread(); + currAttrLine= r.getLine(); + currAttrCol = r.getColumn(); + currAttr = parseFieldName(r); + state = S3; + } + } else if (state == S3) { + if (c == ':') + state = S4; + } else if (state == S4) { + if (isCommentOrWhitespace(c)) { + skipCommentsAndSpace(r.unread()); + } else { + if (! currAttr.equals(getBeanTypePropertyName(m.getClassMeta()))) { + BeanPropertyMeta pMeta = m.getPropertyMeta(currAttr); + setCurrentProperty(pMeta); + if (pMeta == null) { + onUnknownProperty(r.getPipe(), currAttr, m, currAttrLine, currAttrCol); + parseAnything(object(), r.unread(), m.getBean(false), null); // Read content anyway to ignore it + } else { + ClassMeta<?> cm = pMeta.getClassMeta(); + Object value = parseAnything(cm, r.unread(), m.getBean(false), pMeta); + setName(cm, value, currAttr); + pMeta.set(m, currAttr, value); + } + setCurrentProperty(null); + } + state = S5; + } + } else if (state == S5) { + if (c == ',') + state = S1; + else if (isCommentOrWhitespace(c)) + skipCommentsAndSpace(r.unread()); + else if (c == '}') { + return m; + } + } + } + if (state == S0) + throw new ParseException(loc(r), "Expected '{' at beginning of JSON object."); + if (state == S1) + throw new ParseException(loc(r), "Could not find attribute name on JSON object."); + if (state == S3) + throw new ParseException(loc(r), "Could not find ':' following attribute name on JSON object."); + if (state == S4) + throw new ParseException(loc(r), "Expected one of the following characters: {,[,',\",LITERAL."); + if (state == S5) + throw new ParseException(loc(r), "Could not find '}' marking end of JSON object."); + + return null; // Unreachable. + } + + /* + * Starting from the specified position in the character array, returns the + * position of the character " or '. + * If the string consists of a concatenation of strings (e.g. 'AAA' + "BBB"), this method + * will automatically concatenate the strings and return the result. + */ + private String parseString(ParserReader r) throws Exception { + r.mark(); + int qc = r.read(); // The quote character being used (" or ') + if (qc != '"' && isStrict()) { + String msg = ( + qc == '\'' + ? "Invalid quote character \"{0}\" being used." + : "Did not find quote character marking beginning of string. Character=\"{0}\"" + ); + throw new ParseException(loc(r), msg, (char)qc); + } + final boolean isQuoted = (qc == '\'' || qc == '"'); + String s = null; + boolean isInEscape = false; + int c = 0; + while (c != -1) { + c = r.read(); + // Strict syntax requires that all control characters be escaped. + if (isStrict() && c <= 0x1F) + throw new ParseException("Unescaped control character encountered: ''0x{0}''", String.format("%04X", c)); + if (isInEscape) { + switch (c) { + case 'n': r.replace('\n'); break; + case 'r': r.replace('\r'); break; + case 't': r.replace('\t'); break; + case 'f': r.replace('\f'); break; + case 'b': r.replace('\b'); break; + case '\\': r.replace('\\'); break; + case '/': r.replace('/'); break; + case '\'': r.replace('\''); break; + case '"': r.replace('"'); break; + case 'u': { + String n = r.read(4); + try { + r.replace(Integer.parseInt(n, 16), 6); + } catch (NumberFormatException e) { + throw new ParseException(loc(r), "Invalid Unicode escape sequence in string."); + } + break; + } + default: + throw new ParseException(loc(r), "Invalid escape sequence in string."); + } + isInEscape = false; + } else { + if (c == '\\') { + isInEscape = true; + r.delete(); + } else if (isQuoted) { + if (c == qc) { + s = r.getMarked(1, -1); + break; + } + } else { + if (c == ',' || c == '}' || c == ']' || isWhitespace(c)) { + s = r.getMarked(0, -1); + r.unread(); + break; + } else if (c == -1) { + s = r.getMarked(0, 0); + break; + } + } + } + } + if (s == null) + throw new ParseException(loc(r), "Could not find expected end character ''{0}''.", (char)qc); + + // Look for concatenated string (i.e. whitespace followed by +). + skipCommentsAndSpace(r); + if (r.peek() == '+') { + if (isStrict()) + throw new ParseException(loc(r), "String concatenation detected."); + r.read(); // Skip past '+' + skipCommentsAndSpace(r); + s += parseString(r); + } + return trim(s); // End of input reached. + } + + /* + * Looks for the keywords true, false, or null. + * Throws an exception if any of these keywords are not found at the specified position. + */ + private void parseKeyword(String keyword, ParserReader r) throws Exception { + try { + String s = r.read(keyword.length()); + if (s.equals(keyword)) + return; + throw new ParseException(loc(r), "Unrecognized syntax."); + } catch (IndexOutOfBoundsException e) { + throw new ParseException(loc(r), "Unrecognized syntax."); + } + } + + /* + * Doesn't actually parse anything, but moves the position beyond any whitespace or comments. + * If positionOnNext is 'true', then the cursor will be set to the point immediately after + * the comments and whitespace. Otherwise, the cursor will be set to the last position of + * the comments and whitespace. + */ + private void skipCommentsAndSpace(ParserReader r) throws Exception { + int c = 0; + while ((c = r.read()) != -1) { + if (! isWhitespace(c)) { + if (c == '/') { + if (isStrict()) + throw new ParseException(loc(r), "Javascript comment detected."); + skipComments(r); + } else { + r.unread(); + return; + } + } + } + } + + /* + * Doesn't actually parse anything, but moves the position beyond the construct "{wrapperAttr:" when + * the @Json.wrapperAttr() annotation is used on a class. + */ + private void skipWrapperAttrStart(ParserReader r, String wrapperAttr) throws Exception { + + int S0=0; // Looking for outer { + int S1=1; // Looking for attrName start. + int S3=3; // Found attrName end, looking for :. + int S4=4; // Found :, looking for valStart: { [ " ' LITERAL. + + int state = S0; + String currAttr = null; + int c = 0; + while (c != -1) { + c = r.read(); + if (state == S0) { + if (c == '{') + state = S1; + } else if (state == S1) { + if (isCommentOrWhitespace(c)) { + skipCommentsAndSpace(r.unread()); + } else { + currAttr = parseFieldName(r.unread()); + if (! currAttr.equals(wrapperAttr)) + throw new ParseException(loc(r), + "Expected to find wrapper attribute ''{0}'' but found attribute ''{1}''", wrapperAttr, currAttr); + state = S3; + } + } else if (state == S3) { + if (c == ':') + state = S4; + } else if (state == S4) { + if (isCommentOrWhitespace(c)) { + skipCommentsAndSpace(r.unread()); + } else { + r.unread(); + return; + } + } + } + if (state == S0) + throw new ParseException(loc(r), "Expected '{' at beginning of JSON object."); + if (state == S1) + throw new ParseException(loc(r), "Could not find attribute name on JSON object."); + if (state == S3) + throw new ParseException(loc(r), "Could not find ':' following attribute name on JSON object."); + if (state == S4) + throw new ParseException(loc(r), "Expected one of the following characters: {,[,',\",LITERAL."); + } + + /* + * Doesn't actually parse anything, but moves the position beyond the construct "}" when + * the @Json.wrapperAttr() annotation is used on a class. + */ + private void skipWrapperAttrEnd(ParserReader r) throws ParseException, IOException { + int c = 0; + while ((c = r.read()) != -1) { + if (! isWhitespace(c)) { + if (c == '/') { + if (isStrict()) + throw new ParseException(loc(r), "Javascript comment detected."); + skipComments(r); + } else if (c == '}') { + return; + } else { + throw new ParseException(loc(r), "Could not find '}' at the end of JSON wrapper object."); + } + } + } + } + + /* + * Doesn't actually parse anything, but when positioned at the beginning of comment, + * it will move the pointer to the last character in the comment. + */ + private void skipComments(ParserReader r) throws ParseException, IOException { + int c = r.read(); + // "/* */" style comments + if (c == '*') { + while (c != -1) + if ((c = r.read()) == '*') + if ((c = r.read()) == '/') + return; + // "//" style comments + } else if (c == '/') { + while (c != -1) { + c = r.read(); + if (c == -1 || c == '\n') + return; + } + } + throw new ParseException(loc(r), "Open ended comment."); + } + + /* + * Call this method after you've finished a parsing a string to make sure that if there's any + * remainder in the input, that it consists only of whitespace and comments. + */ + private void validateEnd(ParserReader r) throws Exception { + skipCommentsAndSpace(r); + int c = r.read(); + if (c != -1 && c != ';') // var x = {...}; expressions can end with a semicolon. + throw new ParseException(loc(r), "Remainder after parse: ''{0}''.", (char)c); + } + + private ObjectMap loc(ParserReader r) { + return getLastLocation().append("line", r.getLine()).append("column", r.getColumn()); + } }
http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/2a37f310/juneau-core/src/main/java/org/apache/juneau/json/JsonSchemaSerializer.java ---------------------------------------------------------------------- diff --git a/juneau-core/src/main/java/org/apache/juneau/json/JsonSchemaSerializer.java b/juneau-core/src/main/java/org/apache/juneau/json/JsonSchemaSerializer.java index 9829016..66d6bae 100644 --- a/juneau-core/src/main/java/org/apache/juneau/json/JsonSchemaSerializer.java +++ b/juneau-core/src/main/java/org/apache/juneau/json/JsonSchemaSerializer.java @@ -12,17 +12,11 @@ // *************************************************************************************************************************** package org.apache.juneau.json; -import static org.apache.juneau.internal.ClassUtils.*; import static org.apache.juneau.serializer.SerializerContext.*; -import java.lang.reflect.*; -import java.util.*; - import org.apache.juneau.*; import org.apache.juneau.annotation.*; -import org.apache.juneau.http.*; import org.apache.juneau.serializer.*; -import org.apache.juneau.transform.*; /** * Serializes POJO metadata to HTTP responses as JSON. @@ -40,125 +34,21 @@ import org.apache.juneau.transform.*; @Produces(value="application/json+schema,text/json+schema",contentType="application/json") public final class JsonSchemaSerializer extends JsonSerializer { - private final JsonSerializerContext ctx; - /** * Constructor. * * @param propertyStore Initialize with the specified config property store. */ public JsonSchemaSerializer(PropertyStore propertyStore) { - this(propertyStore, null); - } - - /** - * Constructor. - * - * @param propertyStore Initialize with the specified config property store. - * @param overrideProperties - */ - public JsonSchemaSerializer(PropertyStore propertyStore, Map<String,Object> overrideProperties) { - super(propertyStore); - this.ctx = this.propertyStore.create(overrideProperties).getContext(JsonSerializerContext.class); - } - - @Override /* CoreObject */ - protected ObjectMap getOverrideProperties() { - return super.getOverrideProperties().append(SERIALIZER_detectRecursions, true) - .append(SERIALIZER_ignoreRecursions, true); + super( + propertyStore.copy() + .append(SERIALIZER_detectRecursions, true) + .append(SERIALIZER_ignoreRecursions, true) + ); } - - //-------------------------------------------------------------------------------- - // Entry point methods - //-------------------------------------------------------------------------------- - @Override /* Serializer */ - public JsonSerializerSession createSession(ObjectMap op, Method javaMethod, Locale locale, - TimeZone timeZone, MediaType mediaType, UriContext uriContext) { - return new JsonSerializerSession(ctx, op, javaMethod, locale, timeZone, mediaType, uriContext); - } - - @Override /* JsonSerializer */ - protected void doSerialize(SerializerSession session, SerializerOutput out, Object o) throws Exception { - JsonSerializerSession s = (JsonSerializerSession)session; - ObjectMap schema = getSchema(s, session.getClassMetaForObject(o), "root", null); - serializeAnything(s, s.getJsonWriter(out), schema, s.getExpectedRootType(o), "root", null); - } - - /* - * Creates a schema representation of the specified class type. - * - * @param eType The class type to get the schema of. - * @param ctx Serialize context used to prevent infinite loops. - * @param attrName The name of the current attribute. - * @return A schema representation of the specified class. - * @throws SerializeException If a problem occurred trying to convert the output. - */ - @SuppressWarnings({ "unchecked", "rawtypes" }) - private ObjectMap getSchema(JsonSerializerSession session, ClassMeta<?> eType, String attrName, String[] pNames) - throws Exception { - ObjectMap out = new ObjectMap(); - - if (eType == null) - eType = object(); - - ClassMeta<?> aType; // The actual type (will be null if recursion occurs) - ClassMeta<?> sType; // The serialized type - - aType = session.push(attrName, eType, null); - - sType = eType.getSerializedClassMeta(); - String type = null; - - if (sType.isEnum() || sType.isCharSequence() || sType.isChar()) - type = "string"; - else if (sType.isNumber()) - type = "number"; - else if (sType.isBoolean()) - type = "boolean"; - else if (sType.isMapOrBean()) - type = "object"; - else if (sType.isCollectionOrArray()) - type = "array"; - else - type = "any"; - - out.put("type", type); - out.put("description", eType.toString()); - PojoSwap f = eType.getPojoSwap(); - if (f != null) - out.put("transform", f); - - if (aType != null) { - if (sType.isEnum()) - out.put("enum", getEnumStrings((Class<Enum<?>>)sType.getInnerClass())); - else if (sType.isCollectionOrArray()) { - ClassMeta componentType = sType.getElementType(); - if (sType.isCollection() && isParentClass(Set.class, sType.getInnerClass())) - out.put("uniqueItems", true); - out.put("items", getSchema(session, componentType, "items", pNames)); - } else if (sType.isBean()) { - ObjectMap properties = new ObjectMap(); - BeanMeta bm = session.getBeanMeta(sType.getInnerClass()); - if (pNames != null) - bm = new BeanMetaFiltered(bm, pNames); - for (Iterator<BeanPropertyMeta> i = bm.getPropertyMetas().iterator(); i.hasNext();) { - BeanPropertyMeta p = i.next(); - properties.put(p.getName(), getSchema(session, p.getClassMeta(), p.getName(), p.getProperties())); - } - out.put("properties", properties); - } - } - session.pop(); - return out; - } - - @SuppressWarnings({ "unchecked", "rawtypes" }) - private static List<String> getEnumStrings(Class<? extends Enum> c) { - List<String> l = new LinkedList<String>(); - for (Object e : EnumSet.allOf(c)) - l.add(e.toString()); - return l; + public WriterSerializerSession createSession(SerializerSessionArgs args) { + return new JsonSchemaSerializerSession(ctx, args); } -} +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/2a37f310/juneau-core/src/main/java/org/apache/juneau/json/JsonSchemaSerializerSession.java ---------------------------------------------------------------------- diff --git a/juneau-core/src/main/java/org/apache/juneau/json/JsonSchemaSerializerSession.java b/juneau-core/src/main/java/org/apache/juneau/json/JsonSchemaSerializerSession.java new file mode 100644 index 0000000..f9e875d --- /dev/null +++ b/juneau-core/src/main/java/org/apache/juneau/json/JsonSchemaSerializerSession.java @@ -0,0 +1,129 @@ +// *************************************************************************************************************************** +// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file * +// * distributed with this work for additional information regarding copyright ownership. The ASF licenses this file * +// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance * +// * with the License. You may obtain a copy of the License at * +// * * +// * http://www.apache.org/licenses/LICENSE-2.0 * +// * * +// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an * +// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the * +// * specific language governing permissions and limitations under the License. * +// *************************************************************************************************************************** +package org.apache.juneau.json; + +import static org.apache.juneau.internal.ClassUtils.*; + +import java.util.*; + +import org.apache.juneau.*; +import org.apache.juneau.serializer.*; +import org.apache.juneau.transform.*; + +/** + * Session object that lives for the duration of a single use of {@link JsonSchemaSerializer}. + * + * <p> + * This class is NOT thread safe. + * It is typically discarded after one-time use although it can be reused within the same thread. + */ +public class JsonSchemaSerializerSession extends JsonSerializerSession { + + /** + * Create a new session using properties specified in the context. + * + * @param ctx + * The context creating this session object. + * The context contains all the configuration settings for this object. + * @param args + * Runtime arguments. + * These specify session-level information such as locale and URI context. + * It also include session-level properties that override the properties defined on the bean and + * serializer contexts. + * <br>If <jk>null</jk>, defaults to {@link SerializerSessionArgs#DEFAULT}. + */ + protected JsonSchemaSerializerSession(JsonSerializerContext ctx, SerializerSessionArgs args) { + super(ctx, args); + } + + @Override /* SerializerSession */ + protected void doSerialize(SerializerPipe out, Object o) throws Exception { + ObjectMap schema = getSchema(getClassMetaForObject(o), "root", null); + serializeAnything(getJsonWriter(out), schema, getExpectedRootType(o), "root", null); + } + + /* + * Creates a schema representation of the specified class type. + * + * @param eType The class type to get the schema of. + * @param ctx Serialize context used to prevent infinite loops. + * @param attrName The name of the current attribute. + * @return A schema representation of the specified class. + * @throws SerializeException If a problem occurred trying to convert the output. + */ + @SuppressWarnings({ "unchecked", "rawtypes" }) + private ObjectMap getSchema(ClassMeta<?> eType, String attrName, String[] pNames) throws Exception { + ObjectMap out = new ObjectMap(); + + if (eType == null) + eType = object(); + + ClassMeta<?> aType; // The actual type (will be null if recursion occurs) + ClassMeta<?> sType; // The serialized type + + aType = push(attrName, eType, null); + + sType = eType.getSerializedClassMeta(); + String type = null; + + if (sType.isEnum() || sType.isCharSequence() || sType.isChar()) + type = "string"; + else if (sType.isNumber()) + type = "number"; + else if (sType.isBoolean()) + type = "boolean"; + else if (sType.isMapOrBean()) + type = "object"; + else if (sType.isCollectionOrArray()) + type = "array"; + else + type = "any"; + + out.put("type", type); + out.put("description", eType.toString()); + PojoSwap f = eType.getPojoSwap(); + if (f != null) + out.put("transform", f); + + if (aType != null) { + if (sType.isEnum()) + out.put("enum", getEnumStrings((Class<Enum<?>>)sType.getInnerClass())); + else if (sType.isCollectionOrArray()) { + ClassMeta componentType = sType.getElementType(); + if (sType.isCollection() && isParentClass(Set.class, sType.getInnerClass())) + out.put("uniqueItems", true); + out.put("items", getSchema(componentType, "items", pNames)); + } else if (sType.isBean()) { + ObjectMap properties = new ObjectMap(); + BeanMeta bm = getBeanMeta(sType.getInnerClass()); + if (pNames != null) + bm = new BeanMetaFiltered(bm, pNames); + for (Iterator<BeanPropertyMeta> i = bm.getPropertyMetas().iterator(); i.hasNext();) { + BeanPropertyMeta p = i.next(); + properties.put(p.getName(), getSchema(p.getClassMeta(), p.getName(), p.getProperties())); + } + out.put("properties", properties); + } + } + pop(); + return out; + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + private static List<String> getEnumStrings(Class<? extends Enum> c) { + List<String> l = new LinkedList<String>(); + for (Object e : EnumSet.allOf(c)) + l.add(e.toString()); + return l; + } +} http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/2a37f310/juneau-core/src/main/java/org/apache/juneau/json/JsonSerializer.java ---------------------------------------------------------------------- diff --git a/juneau-core/src/main/java/org/apache/juneau/json/JsonSerializer.java b/juneau-core/src/main/java/org/apache/juneau/json/JsonSerializer.java index 82edd79..db0a78d 100644 --- a/juneau-core/src/main/java/org/apache/juneau/json/JsonSerializer.java +++ b/juneau-core/src/main/java/org/apache/juneau/json/JsonSerializer.java @@ -14,14 +14,11 @@ package org.apache.juneau.json; import static org.apache.juneau.json.JsonSerializerContext.*; -import java.lang.reflect.*; import java.util.*; import org.apache.juneau.*; import org.apache.juneau.annotation.*; -import org.apache.juneau.http.*; import org.apache.juneau.serializer.*; -import org.apache.juneau.transform.*; /** * Serializes POJO models to JSON. @@ -132,12 +129,10 @@ public class JsonSerializer extends WriterSerializer { * @param propertyStore The property store containing all the settings for this object. */ public Readable(PropertyStore propertyStore) { - super(propertyStore); - } - - @Override /* CoreObject */ - protected ObjectMap getOverrideProperties() { - return super.getOverrideProperties().append(SERIALIZER_useWhitespace, true); + super( + propertyStore.copy() + .append(SERIALIZER_useWhitespace, true) + ); } } @@ -151,12 +146,11 @@ public class JsonSerializer extends WriterSerializer { * @param propertyStore The property store containing all the settings for this object. */ public Simple(PropertyStore propertyStore) { - super(propertyStore); - } - - @Override /* CoreObject */ - protected ObjectMap getOverrideProperties() { - return super.getOverrideProperties().append(JSON_simpleMode, true).append(SERIALIZER_quoteChar, '\''); + super( + propertyStore.copy() + .append(JSON_simpleMode, true) + .append(SERIALIZER_quoteChar, '\'') + ); } } @@ -169,13 +163,12 @@ public class JsonSerializer extends WriterSerializer { * @param propertyStore The property store containing all the settings for this object. */ public SimpleReadable(PropertyStore propertyStore) { - super(propertyStore); - } - - @Override /* CoreObject */ - protected ObjectMap getOverrideProperties() { - return super.getOverrideProperties().append(JSON_simpleMode, true).append(SERIALIZER_quoteChar, '\'') - .append(SERIALIZER_useWhitespace, true); + super( + propertyStore.copy() + .append(JSON_simpleMode, true) + .append(SERIALIZER_quoteChar, '\'') + .append(SERIALIZER_useWhitespace, true) + ); } } @@ -191,18 +184,18 @@ public class JsonSerializer extends WriterSerializer { * @param propertyStore The property store containing all the settings for this object. */ public SimpleReadableSafe(PropertyStore propertyStore) { - super(propertyStore); - } - - @Override /* CoreObject */ - protected ObjectMap getOverrideProperties() { - return super.getOverrideProperties().append(JSON_simpleMode, true).append(SERIALIZER_quoteChar, '\'') - .append(SERIALIZER_useWhitespace, true).append(SERIALIZER_detectRecursions, true); + super( + propertyStore.copy() + .append(JSON_simpleMode, true) + .append(SERIALIZER_quoteChar, '\'') + .append(SERIALIZER_useWhitespace, true) + .append(SERIALIZER_detectRecursions, true) + ); } } - private final JsonSerializerContext ctx; + final JsonSerializerContext ctx; private volatile JsonSchemaSerializer schemaSerializer; /** @@ -221,186 +214,13 @@ public class JsonSerializer extends WriterSerializer { } /** - * Workhorse method. - * - * <p> - * Determines the type of object, and then calls the appropriate type-specific serialization method. - */ - @SuppressWarnings({ "rawtypes", "unchecked" }) - SerializerWriter serializeAnything(JsonSerializerSession session, JsonWriter out, Object o, ClassMeta<?> eType, - String attrName, BeanPropertyMeta pMeta) throws Exception { - - if (o == null) { - out.append("null"); - return out; - } - - if (eType == null) - eType = object(); - - ClassMeta<?> aType; // The actual type - ClassMeta<?> sType; // The serialized type - - aType = session.push(attrName, o, eType); - boolean isRecursion = aType == null; - - // Handle recursion - if (aType == null) { - o = null; - aType = object(); - } - - sType = aType.getSerializedClassMeta(); - String typeName = session.getBeanTypeName(eType, aType, pMeta); - - // Swap if necessary - PojoSwap swap = aType.getPojoSwap(); - if (swap != null) { - o = swap.swap(session, o); - - // If the getSwapClass() method returns Object, we need to figure out - // the actual type now. - if (sType.isObject()) - sType = session.getClassMetaForObject(o); - } - - String wrapperAttr = sType.getExtendedMeta(JsonClassMeta.class).getWrapperAttr(); - if (wrapperAttr != null) { - out.append('{').cr(session.indent).attr(wrapperAttr).append(':').s(session.indent); - session.indent++; - } - - // '\0' characters are considered null. - if (o == null || (sType.isChar() && ((Character)o).charValue() == 0)) - out.append("null"); - else if (sType.isNumber() || sType.isBoolean()) - out.append(o); - else if (sType.isBean()) - serializeBeanMap(session, out, session.toBeanMap(o), typeName); - else if (sType.isUri() || (pMeta != null && pMeta.isUri())) - out.uriValue(o); - else if (sType.isMap()) { - if (o instanceof BeanMap) - serializeBeanMap(session, out, (BeanMap)o, typeName); - else - serializeMap(session, out, (Map)o, eType); - } - else if (sType.isCollection()) { - serializeCollection(session, out, (Collection) o, eType); - } - else if (sType.isArray()) { - serializeCollection(session, out, toList(sType.getInnerClass(), o), eType); - } - else - out.stringValue(session.toString(o)); - - if (wrapperAttr != null) { - session.indent--; - out.cre(session.indent-1).append('}'); - } - - if (! isRecursion) - session.pop(); - return out; - } - - @SuppressWarnings({ "rawtypes", "unchecked" }) - private SerializerWriter serializeMap(JsonSerializerSession session, JsonWriter out, Map m, ClassMeta<?> type) - throws Exception { - - ClassMeta<?> keyType = type.getKeyType(), valueType = type.getValueType(); - - m = session.sort(m); - - int i = session.getIndent(); - out.append('{'); - - Iterator mapEntries = m.entrySet().iterator(); - - while (mapEntries.hasNext()) { - Map.Entry e = (Map.Entry) mapEntries.next(); - Object value = e.getValue(); - - Object key = session.generalize(e.getKey(), keyType); - - out.cr(i).attr(session.toString(key)).append(':').s(i); - - serializeAnything(session, out, value, valueType, (key == null ? null : session.toString(key)), null); - - if (mapEntries.hasNext()) - out.append(',').smi(i); - } - - out.cre(i-1).append('}'); - - return out; - } - - private SerializerWriter serializeBeanMap(JsonSerializerSession session, JsonWriter out, BeanMap<?> m, - String typeName) throws Exception { - int i = session.getIndent(); - out.append('{'); - - boolean addComma = false; - for (BeanPropertyValue p : m.getValues(session.isTrimNulls(), typeName != null ? session.createBeanTypeNameProperty(m, typeName) : null)) { - BeanPropertyMeta pMeta = p.getMeta(); - ClassMeta<?> cMeta = p.getClassMeta(); - String key = p.getName(); - Object value = p.getValue(); - Throwable t = p.getThrown(); - if (t != null) - session.onBeanGetterException(pMeta, t); - - if (session.canIgnoreValue(cMeta, key, value)) - continue; - - if (addComma) - out.append(',').smi(i); - - out.cr(i).attr(key).append(':').s(i); - - serializeAnything(session, out, value, cMeta, key, pMeta); - - addComma = true; - } - out.cre(i-1).append('}'); - return out; - } - - @SuppressWarnings({"rawtypes", "unchecked"}) - private SerializerWriter serializeCollection(JsonSerializerSession session, JsonWriter out, Collection c, - ClassMeta<?> type) throws Exception { - - ClassMeta<?> elementType = type.getElementType(); - - c = session.sort(c); - - out.append('['); - int depth = session.getIndent(); - - for (Iterator i = c.iterator(); i.hasNext();) { - - Object value = i.next(); - - out.cr(depth); - - serializeAnything(session, out, value, elementType, "<iterator>", null); - - if (i.hasNext()) - out.append(',').smi(depth); - } - out.cre(depth-1).append(']'); - return out; - } - - /** * Returns the schema serializer based on the settings of this serializer. * * @return The schema serializer. */ public JsonSchemaSerializer getSchemaSerializer() { if (schemaSerializer == null) - schemaSerializer = new JsonSchemaSerializer(propertyStore, getOverrideProperties()); + schemaSerializer = new JsonSchemaSerializer(propertyStore); return schemaSerializer; } @@ -409,14 +229,7 @@ public class JsonSerializer extends WriterSerializer { //-------------------------------------------------------------------------------- @Override /* Serializer */ - public JsonSerializerSession createSession(ObjectMap op, Method javaMethod, Locale locale, - TimeZone timeZone, MediaType mediaType, UriContext uriContext) { - return new JsonSerializerSession(ctx, op, javaMethod, locale, timeZone, mediaType, uriContext); - } - - @Override /* Serializer */ - protected void doSerialize(SerializerSession session, SerializerOutput out, Object o) throws Exception { - JsonSerializerSession s = (JsonSerializerSession)session; - serializeAnything(s, s.getJsonWriter(out), o, s.getExpectedRootType(o), "root", null); + public WriterSerializerSession createSession(SerializerSessionArgs args) { + return new JsonSerializerSession(ctx, args); } -} +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/2a37f310/juneau-core/src/main/java/org/apache/juneau/json/JsonSerializerSession.java ---------------------------------------------------------------------- diff --git a/juneau-core/src/main/java/org/apache/juneau/json/JsonSerializerSession.java b/juneau-core/src/main/java/org/apache/juneau/json/JsonSerializerSession.java index 2808f59..12c9433 100644 --- a/juneau-core/src/main/java/org/apache/juneau/json/JsonSerializerSession.java +++ b/juneau-core/src/main/java/org/apache/juneau/json/JsonSerializerSession.java @@ -14,20 +14,20 @@ package org.apache.juneau.json; import static org.apache.juneau.json.JsonSerializerContext.*; -import java.lang.reflect.*; import java.util.*; import org.apache.juneau.*; -import org.apache.juneau.http.*; import org.apache.juneau.serializer.*; +import org.apache.juneau.transform.*; /** * Session object that lives for the duration of a single use of {@link JsonSerializer}. * * <p> - * This class is NOT thread safe. It is meant to be discarded after one-time use. + * This class is NOT thread safe. + * It is typically discarded after one-time use although it can be reused within the same thread. */ -public final class JsonSerializerSession extends SerializerSession { +public class JsonSerializerSession extends WriterSerializerSession { private final boolean simpleMode, @@ -40,60 +40,203 @@ public final class JsonSerializerSession extends SerializerSession { * @param ctx * The context creating this session object. * The context contains all the configuration settings for this object. - * @param op - * The override properties. - * These override any context properties defined in the context. - * @param javaMethod The java method that called this serializer, usually the method in a REST servlet. - * @param locale - * The session locale. - * If <jk>null</jk>, then the locale defined on the context is used. - * @param timeZone - * The session timezone. - * If <jk>null</jk>, then the timezone defined on the context is used. - * @param mediaType The session media type (e.g. <js>"application/json"</js>). - * @param uriContext - * The URI context. - * Identifies the current request URI used for resolution of URIs to absolute or root-relative form. + * @param args + * Runtime arguments. + * These specify session-level information such as locale and URI context. + * It also include session-level properties that override the properties defined on the bean and + * serializer contexts. + * <br>If <jk>null</jk>, defaults to {@link SerializerSessionArgs#DEFAULT}. */ - protected JsonSerializerSession(JsonSerializerContext ctx, ObjectMap op, Method javaMethod, - Locale locale, TimeZone timeZone, MediaType mediaType, UriContext uriContext) { - super(ctx, op, javaMethod, locale, timeZone, mediaType, uriContext); - if (op == null || op.isEmpty()) { + protected JsonSerializerSession(JsonSerializerContext ctx, SerializerSessionArgs args) { + super(ctx, args); + ObjectMap p = getProperties(); + if (p.isEmpty()) { simpleMode = ctx.simpleMode; escapeSolidus = ctx.escapeSolidus; addBeanTypeProperties = ctx.addBeanTypeProperties; } else { - simpleMode = op.getBoolean(JSON_simpleMode, ctx.simpleMode); - escapeSolidus = op.getBoolean(JSON_escapeSolidus, ctx.escapeSolidus); - addBeanTypeProperties = op.getBoolean(JSON_addBeanTypeProperties, ctx.addBeanTypeProperties); + simpleMode = p.getBoolean(JSON_simpleMode, ctx.simpleMode); + escapeSolidus = p.getBoolean(JSON_escapeSolidus, ctx.escapeSolidus); + addBeanTypeProperties = p.getBoolean(JSON_addBeanTypeProperties, ctx.addBeanTypeProperties); } } - /** - * Returns the {@link JsonSerializerContext#JSON_simpleMode} setting value for this session. - * - * @return The {@link JsonSerializerContext#JSON_simpleMode} setting value for this session. - */ - public final boolean isSimpleMode() { - return simpleMode; + + @Override /* SerializerSesssion */ + protected void doSerialize(SerializerPipe out, Object o) throws Exception { + serializeAnything(getJsonWriter(out), o, getExpectedRootType(o), "root", null); } - /** - * Returns the {@link JsonSerializerContext#JSON_escapeSolidus} setting value for this session. - * - * @return The {@link JsonSerializerContext#JSON_escapeSolidus} setting value for this session. + /* + * Workhorse method. + * Determines the type of object, and then calls the appropriate type-specific serialization method. */ - public final boolean isEscapeSolidus() { - return escapeSolidus; + @SuppressWarnings({ "rawtypes", "unchecked" }) + SerializerWriter serializeAnything(JsonWriter out, Object o, ClassMeta<?> eType, String attrName, BeanPropertyMeta pMeta) throws Exception { + + if (o == null) { + out.append("null"); + return out; + } + + if (eType == null) + eType = object(); + + ClassMeta<?> aType; // The actual type + ClassMeta<?> sType; // The serialized type + + aType = push(attrName, o, eType); + boolean isRecursion = aType == null; + + // Handle recursion + if (aType == null) { + o = null; + aType = object(); + } + + sType = aType.getSerializedClassMeta(); + String typeName = getBeanTypeName(eType, aType, pMeta); + + // Swap if necessary + PojoSwap swap = aType.getPojoSwap(); + if (swap != null) { + o = swap.swap(this, o); + + // If the getSwapClass() method returns Object, we need to figure out + // the actual type now. + if (sType.isObject()) + sType = getClassMetaForObject(o); + } + + String wrapperAttr = sType.getExtendedMeta(JsonClassMeta.class).getWrapperAttr(); + if (wrapperAttr != null) { + out.append('{').cr(indent).attr(wrapperAttr).append(':').s(indent); + indent++; + } + + // '\0' characters are considered null. + if (o == null || (sType.isChar() && ((Character)o).charValue() == 0)) + out.append("null"); + else if (sType.isNumber() || sType.isBoolean()) + out.append(o); + else if (sType.isBean()) + serializeBeanMap(out, toBeanMap(o), typeName); + else if (sType.isUri() || (pMeta != null && pMeta.isUri())) + out.uriValue(o); + else if (sType.isMap()) { + if (o instanceof BeanMap) + serializeBeanMap(out, (BeanMap)o, typeName); + else + serializeMap(out, (Map)o, eType); + } + else if (sType.isCollection()) { + serializeCollection(out, (Collection) o, eType); + } + else if (sType.isArray()) { + serializeCollection(out, toList(sType.getInnerClass(), o), eType); + } + else + out.stringValue(toString(o)); + + if (wrapperAttr != null) { + indent--; + out.cre(indent-1).append('}'); + } + + if (! isRecursion) + pop(); + return out; } + @SuppressWarnings({ "rawtypes", "unchecked" }) + private SerializerWriter serializeMap(JsonWriter out, Map m, ClassMeta<?> type) throws Exception { + + ClassMeta<?> keyType = type.getKeyType(), valueType = type.getValueType(); + + m = sort(m); + + int i = indent; + out.append('{'); + + Iterator mapEntries = m.entrySet().iterator(); + + while (mapEntries.hasNext()) { + Map.Entry e = (Map.Entry) mapEntries.next(); + Object value = e.getValue(); + + Object key = generalize(e.getKey(), keyType); + + out.cr(i).attr(toString(key)).append(':').s(i); + + serializeAnything(out, value, valueType, (key == null ? null : toString(key)), null); + + if (mapEntries.hasNext()) + out.append(',').smi(i); + } + + out.cre(i-1).append('}'); + + return out; + } + + private SerializerWriter serializeBeanMap(JsonWriter out, BeanMap<?> m, String typeName) throws Exception { + int i = indent; + out.append('{'); + + boolean addComma = false; + for (BeanPropertyValue p : m.getValues(isTrimNulls(), typeName != null ? createBeanTypeNameProperty(m, typeName) : null)) { + BeanPropertyMeta pMeta = p.getMeta(); + ClassMeta<?> cMeta = p.getClassMeta(); + String key = p.getName(); + Object value = p.getValue(); + Throwable t = p.getThrown(); + if (t != null) + onBeanGetterException(pMeta, t); + + if (canIgnoreValue(cMeta, key, value)) + continue; + + if (addComma) + out.append(',').smi(i); + + out.cr(i).attr(key).append(':').s(i); + + serializeAnything(out, value, cMeta, key, pMeta); + + addComma = true; + } + out.cre(i-1).append('}'); + return out; + } + + @SuppressWarnings({"rawtypes", "unchecked"}) + private SerializerWriter serializeCollection(JsonWriter out, Collection c, ClassMeta<?> type) throws Exception { + + ClassMeta<?> elementType = type.getElementType(); + + c = sort(c); + + out.append('['); + + for (Iterator i = c.iterator(); i.hasNext();) { + Object value = i.next(); + out.cr(indent); + serializeAnything(out, value, elementType, "<iterator>", null); + if (i.hasNext()) + out.append(',').smi(indent); + } + out.cre(indent-1).append(']'); + return out; + } + + /** * Returns the {@link JsonSerializerContext#JSON_addBeanTypeProperties} setting value for this session. * * @return The {@link JsonSerializerContext#JSON_addBeanTypeProperties} setting value for this session. */ @Override /* SerializerSession */ - public final boolean isAddBeanTypeProperties() { + protected final boolean isAddBeanTypeProperties() { return addBeanTypeProperties; } @@ -104,11 +247,13 @@ public final class JsonSerializerSession extends SerializerSession { * @return The output target object wrapped in an {@link JsonWriter}. * @throws Exception */ - public JsonWriter getJsonWriter(SerializerOutput out) throws Exception { + protected final JsonWriter getJsonWriter(SerializerPipe out) throws Exception { Object output = out.getRawOutput(); if (output instanceof JsonWriter) return (JsonWriter)output; - return new JsonWriter(out.getWriter(), isUseWhitespace(), getMaxIndent(), isEscapeSolidus(), getQuoteChar(), - isSimpleMode(), isTrimStrings(), getUriResolver()); + JsonWriter w = new JsonWriter(out.getWriter(), isUseWhitespace(), getMaxIndent(), escapeSolidus, getQuoteChar(), + simpleMode, isTrimStrings(), getUriResolver()); + out.setWriter(w); + return w; } } http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/2a37f310/juneau-core/src/main/java/org/apache/juneau/msgpack/MsgPackInputStream.java ---------------------------------------------------------------------- diff --git a/juneau-core/src/main/java/org/apache/juneau/msgpack/MsgPackInputStream.java b/juneau-core/src/main/java/org/apache/juneau/msgpack/MsgPackInputStream.java index 40a03ec..2fdc81a 100644 --- a/juneau-core/src/main/java/org/apache/juneau/msgpack/MsgPackInputStream.java +++ b/juneau-core/src/main/java/org/apache/juneau/msgpack/MsgPackInputStream.java @@ -17,6 +17,8 @@ import static org.apache.juneau.internal.IOUtils.*; import java.io.*; +import org.apache.juneau.parser.*; + /** * Specialized input stream for parsing MessagePack streams. * @@ -27,6 +29,7 @@ import java.io.*; */ public final class MsgPackInputStream extends InputStream { + private final ParserPipe pipe; private final InputStream is; private DataType currentDataType; private long length; @@ -57,10 +60,12 @@ public final class MsgPackInputStream extends InputStream { /** * Constructor. * - * @param is The input stream being wrapped. + * @param pipe The parser input. + * @throws Exception */ - protected MsgPackInputStream(InputStream is) { - this.is = is; + protected MsgPackInputStream(ParserPipe pipe) throws Exception { + this.pipe = pipe; + this.is = pipe.getInputStream(); } @Override /* InputStream */ @@ -485,4 +490,13 @@ public final class MsgPackInputStream extends InputStream { int getPosition() { return pos; } + + /** + * Returns the pipe that was passed into the constructor. + * + * @return The pipe that was passed into the constructor. + */ + public ParserPipe getPipe() { + return pipe; + } } http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/2a37f310/juneau-core/src/main/java/org/apache/juneau/msgpack/MsgPackParser.java ---------------------------------------------------------------------- diff --git a/juneau-core/src/main/java/org/apache/juneau/msgpack/MsgPackParser.java b/juneau-core/src/main/java/org/apache/juneau/msgpack/MsgPackParser.java index 727b465..0d9ab31 100644 --- a/juneau-core/src/main/java/org/apache/juneau/msgpack/MsgPackParser.java +++ b/juneau-core/src/main/java/org/apache/juneau/msgpack/MsgPackParser.java @@ -12,16 +12,9 @@ // *************************************************************************************************************************** package org.apache.juneau.msgpack; -import static org.apache.juneau.msgpack.DataType.*; - -import java.lang.reflect.*; -import java.util.*; - import org.apache.juneau.*; import org.apache.juneau.annotation.*; -import org.apache.juneau.http.*; import org.apache.juneau.parser.*; -import org.apache.juneau.transform.*; /** * Parses a MessagePack stream into a POJO model. @@ -37,14 +30,13 @@ import org.apache.juneau.transform.*; * <li>{@link MsgPackParserContext} * </ul> */ -@SuppressWarnings({ "rawtypes", "unchecked" }) @Consumes("octal/msgpack") public class MsgPackParser extends InputStreamParser { /** Default parser, all default settings.*/ public static final MsgPackParser DEFAULT = new MsgPackParser(PropertyStore.create()); - + private final MsgPackParserContext ctx; /** @@ -62,168 +54,8 @@ public class MsgPackParser extends InputStreamParser { return new MsgPackParserBuilder(propertyStore); } - /** - * Workhorse method. - */ - private <T> T parseAnything(MsgPackParserSession session, ClassMeta<T> eType, MsgPackInputStream is, Object outer, - BeanPropertyMeta pMeta) throws Exception { - - if (eType == null) - eType = (ClassMeta<T>)object(); - PojoSwap<T,Object> transform = (PojoSwap<T,Object>)eType.getPojoSwap(); - ClassMeta<?> sType = eType.getSerializedClassMeta(); - session.setCurrentClass(sType); - - Object o = null; - DataType dt = is.readDataType(); - int length = (int)is.readLength(); - - if (dt != DataType.NULL) { - if (dt == BOOLEAN) - o = is.readBoolean(); - else if (dt == INT) - o = is.readInt(); - else if (dt == LONG) - o = is.readLong(); - else if (dt == FLOAT) - o = is.readFloat(); - else if (dt == DOUBLE) - o = is.readDouble(); - else if (dt == STRING) - o = session.trim(is.readString()); - else if (dt == BIN) - o = is.readBinary(); - else if (dt == ARRAY && sType.isObject()) { - ObjectList ol = new ObjectList(session); - for (int i = 0; i < length; i++) - ol.add(parseAnything(session, object(), is, outer, pMeta)); - o = ol; - } else if (dt == MAP && sType.isObject()) { - ObjectMap om = new ObjectMap(session); - for (int i = 0; i < length; i++) - om.put(parseAnything(session, string(), is, outer, pMeta), parseAnything(session, object(), is, om, pMeta)); - o = session.cast(om, pMeta, eType); - } - - if (sType.isObject()) { - // Do nothing. - } else if (sType.isBoolean() || sType.isCharSequence() || sType.isChar() || sType.isNumber()) { - o = session.convertToType(o, sType); - } else if (sType.isMap()) { - if (dt == MAP) { - Map m = (sType.canCreateNewInstance(outer) ? (Map)sType.newInstance(outer) : new ObjectMap(session)); - for (int i = 0; i < length; i++) { - Object key = parseAnything(session, sType.getKeyType(), is, outer, pMeta); - ClassMeta<?> vt = sType.getValueType(); - Object value = parseAnything(session, vt, is, m, pMeta); - setName(vt, value, key); - m.put(key, value); - } - o = m; - } else { - throw new ParseException(session, "Invalid data type {0} encountered for parse type {1}", dt, sType); - } - } else if (sType.canCreateNewBean(outer)) { - if (dt == MAP) { - BeanMap m = session.newBeanMap(outer, sType.getInnerClass()); - for (int i = 0; i < length; i++) { - String pName = parseAnything(session, string(), is, m.getBean(false), null); - BeanPropertyMeta bpm = m.getPropertyMeta(pName); - if (bpm == null) { - if (pName.equals(session.getBeanTypePropertyName(eType))) - parseAnything(session, session.string(), is, null, null); - else - session.onUnknownProperty(pName, m, 0, is.getPosition()); - } else { - ClassMeta<?> cm = bpm.getClassMeta(); - Object value = parseAnything(session, cm, is, m.getBean(false), bpm); - setName(cm, value, pName); - bpm.set(m, pName, value); - } - } - o = m.getBean(); - } else { - throw new ParseException(session, "Invalid data type {0} encountered for parse type {1}", dt, sType); - } - } else if (sType.canCreateNewInstanceFromString(outer) && dt == STRING) { - o = sType.newInstanceFromString(outer, o == null ? "" : o.toString()); - } else if (sType.canCreateNewInstanceFromNumber(outer) && dt.isOneOf(INT, LONG, FLOAT, DOUBLE)) { - o = sType.newInstanceFromNumber(session, outer, (Number)o); - } else if (sType.isCollection()) { - if (dt == MAP) { - ObjectMap m = new ObjectMap(session); - for (int i = 0; i < length; i++) - m.put(parseAnything(session, string(), is, outer, pMeta), parseAnything(session, object(), is, m, pMeta)); - o = session.cast(m, pMeta, eType); - } else if (dt == ARRAY) { - Collection l = ( - sType.canCreateNewInstance(outer) - ? (Collection)sType.newInstance() - : new ObjectList(session) - ); - for (int i = 0; i < length; i++) - l.add(parseAnything(session, sType.getElementType(), is, l, pMeta)); - o = l; - } else { - throw new ParseException(session, "Invalid data type {0} encountered for parse type {1}", dt, sType); - } - } else if (sType.isArray() || sType.isArgs()) { - if (dt == MAP) { - ObjectMap m = new ObjectMap(session); - for (int i = 0; i < length; i++) - m.put(parseAnything(session, string(), is, outer, pMeta), parseAnything(session, object(), is, m, pMeta)); - o = session.cast(m, pMeta, eType); - } else if (dt == ARRAY) { - Collection l = ( - sType.isCollection() && sType.canCreateNewInstance(outer) - ? (Collection)sType.newInstance() - : new ObjectList(session) - ); - for (int i = 0; i < length; i++) - l.add(parseAnything(session, sType.isArgs() ? sType.getArg(i) : sType.getElementType(), is, l, pMeta)); - o = session.toArray(sType, l); - } else { - throw new ParseException(session, "Invalid data type {0} encountered for parse type {1}", dt, sType); - } - } else if (dt == MAP) { - ObjectMap m = new ObjectMap(session); - for (int i = 0; i < length; i++) - m.put(parseAnything(session, string(), is, outer, pMeta), parseAnything(session, object(), is, m, pMeta)); - if (m.containsKey(session.getBeanTypePropertyName(eType))) - o = session.cast(m, pMeta, eType); - else - throw new ParseException(session, "Class ''{0}'' could not be instantiated. Reason: ''{1}''", - sType.getInnerClass().getName(), sType.getNotABeanReason()); - } else { - throw new ParseException(session, "Invalid data type {0} encountered for parse type {1}", dt, sType); - } - } - - if (transform != null && o != null) - o = transform.unswap(session, o, eType); - - if (outer != null) - setParent(eType, o, outer); - - return (T)o; - } - - - //-------------------------------------------------------------------------------- - // Entry point methods - //-------------------------------------------------------------------------------- - - @Override /* Parser */ - public MsgPackParserSession createSession(Object input, ObjectMap op, Method javaMethod, Object outer, - Locale locale, TimeZone timeZone, MediaType mediaType) { - return new MsgPackParserSession(ctx, op, input, javaMethod, outer, locale, timeZone, mediaType); - } - @Override /* Parser */ - protected <T> T doParse(ParserSession session, ClassMeta<T> type) throws Exception { - MsgPackParserSession s = (MsgPackParserSession)session; - MsgPackInputStream is = s.getInputStream(); - T o = parseAnything(s, type, is, s.getOuter(), null); - return o; + public MsgPackParserSession createSession(ParserSessionArgs args) { + return new MsgPackParserSession(ctx, args); } } http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/2a37f310/juneau-core/src/main/java/org/apache/juneau/msgpack/MsgPackParserSession.java ---------------------------------------------------------------------- diff --git a/juneau-core/src/main/java/org/apache/juneau/msgpack/MsgPackParserSession.java b/juneau-core/src/main/java/org/apache/juneau/msgpack/MsgPackParserSession.java index 93f16e6..8827370 100644 --- a/juneau-core/src/main/java/org/apache/juneau/msgpack/MsgPackParserSession.java +++ b/juneau-core/src/main/java/org/apache/juneau/msgpack/MsgPackParserSession.java @@ -12,23 +12,23 @@ // *************************************************************************************************************************** package org.apache.juneau.msgpack; -import java.io.*; -import java.lang.reflect.*; +import static org.apache.juneau.msgpack.DataType.*; + import java.util.*; import org.apache.juneau.*; -import org.apache.juneau.http.*; import org.apache.juneau.parser.*; +import org.apache.juneau.transform.*; /** * Session object that lives for the duration of a single use of {@link MsgPackParser}. * * <p> - * This class is NOT thread safe. It is meant to be discarded after one-time use. + * This class is NOT thread safe. + * It is typically discarded after one-time use although it can be reused against multiple inputs. */ -public final class MsgPackParserSession extends ParserSession { - - private MsgPackInputStream inputStream; +@SuppressWarnings({ "rawtypes", "unchecked" }) +public final class MsgPackParserSession extends InputStreamParserSession { /** * Create a new session using properties specified in the context. @@ -36,46 +36,166 @@ public final class MsgPackParserSession extends ParserSession { * @param ctx * The context creating this session object. * The context contains all the configuration settings for this object. - * @param input - * The input. - * Can be any of the following types: - * <ul> - * <li><jk>null</jk> - * <li>{@link Reader} - * <li>{@link CharSequence} - * <li>{@link InputStream} containing UTF-8 encoded text. - * <li>{@link File} containing system encoded text. - * </ul> - * @param op - * The override properties. - * These override any context properties defined in the context. - * @param javaMethod The java method that called this parser, usually the method in a REST servlet. - * @param outer The outer object for instantiating top-level non-static inner classes. - * @param locale - * The session locale. - * If <jk>null</jk>, then the locale defined on the context is used. - * @param timeZone - * The session timezone. - * If <jk>null</jk>, then the timezone defined on the context is used. - * @param mediaType The session media type (e.g. <js>"application/json"</js>). + * @param args + * Runtime session arguments. */ - public MsgPackParserSession(MsgPackParserContext ctx, ObjectMap op, Object input, Method javaMethod, Object outer, - Locale locale, TimeZone timeZone, MediaType mediaType) { - super(ctx, op, input, javaMethod, outer, locale, timeZone, mediaType); + protected MsgPackParserSession(MsgPackParserContext ctx, ParserSessionArgs args) { + super(ctx, args); } @Override /* ParserSession */ - public MsgPackInputStream getInputStream() throws ParseException { - if (inputStream == null) - inputStream = new MsgPackInputStream(super.getInputStream()); - return inputStream; + protected <T> T doParse(ParserPipe pipe, ClassMeta<T> type) throws Exception { + MsgPackInputStream is = new MsgPackInputStream(pipe); + T o = parseAnything(type, is, getOuter(), null); + return o; } - @Override /* ParserSession */ - public Map<String,Object> getLastLocation() { - Map<String,Object> m = super.getLastLocation(); - if (inputStream != null) - m.put("position", inputStream.getPosition()); - return m; + /* + * Workhorse method. + */ + private <T> T parseAnything(ClassMeta<T> eType, MsgPackInputStream is, Object outer, BeanPropertyMeta pMeta) throws Exception { + + if (eType == null) + eType = (ClassMeta<T>)object(); + PojoSwap<T,Object> transform = (PojoSwap<T,Object>)eType.getPojoSwap(); + ClassMeta<?> sType = eType.getSerializedClassMeta(); + setCurrentClass(sType); + + Object o = null; + DataType dt = is.readDataType(); + int length = (int)is.readLength(); + + if (dt != DataType.NULL) { + if (dt == BOOLEAN) + o = is.readBoolean(); + else if (dt == INT) + o = is.readInt(); + else if (dt == LONG) + o = is.readLong(); + else if (dt == FLOAT) + o = is.readFloat(); + else if (dt == DOUBLE) + o = is.readDouble(); + else if (dt == STRING) + o = trim(is.readString()); + else if (dt == BIN) + o = is.readBinary(); + else if (dt == ARRAY && sType.isObject()) { + ObjectList ol = new ObjectList(this); + for (int i = 0; i < length; i++) + ol.add(parseAnything(object(), is, outer, pMeta)); + o = ol; + } else if (dt == MAP && sType.isObject()) { + ObjectMap om = new ObjectMap(this); + for (int i = 0; i < length; i++) + om.put(parseAnything(string(), is, outer, pMeta), parseAnything(object(), is, om, pMeta)); + o = cast(om, pMeta, eType); + } + + if (sType.isObject()) { + // Do nothing. + } else if (sType.isBoolean() || sType.isCharSequence() || sType.isChar() || sType.isNumber()) { + o = convertToType(o, sType); + } else if (sType.isMap()) { + if (dt == MAP) { + Map m = (sType.canCreateNewInstance(outer) ? (Map)sType.newInstance(outer) : new ObjectMap(this)); + for (int i = 0; i < length; i++) { + Object key = parseAnything(sType.getKeyType(), is, outer, pMeta); + ClassMeta<?> vt = sType.getValueType(); + Object value = parseAnything(vt, is, m, pMeta); + setName(vt, value, key); + m.put(key, value); + } + o = m; + } else { + throw new ParseException(loc(is), "Invalid data type {0} encountered for parse type {1}", dt, sType); + } + } else if (sType.canCreateNewBean(outer)) { + if (dt == MAP) { + BeanMap m = newBeanMap(outer, sType.getInnerClass()); + for (int i = 0; i < length; i++) { + String pName = parseAnything(string(), is, m.getBean(false), null); + BeanPropertyMeta bpm = m.getPropertyMeta(pName); + if (bpm == null) { + if (pName.equals(getBeanTypePropertyName(eType))) + parseAnything(string(), is, null, null); + else + onUnknownProperty(is.getPipe(), pName, m, 0, is.getPosition()); + } else { + ClassMeta<?> cm = bpm.getClassMeta(); + Object value = parseAnything(cm, is, m.getBean(false), bpm); + setName(cm, value, pName); + bpm.set(m, pName, value); + } + } + o = m.getBean(); + } else { + throw new ParseException(loc(is), "Invalid data type {0} encountered for parse type {1}", dt, sType); + } + } else if (sType.canCreateNewInstanceFromString(outer) && dt == STRING) { + o = sType.newInstanceFromString(outer, o == null ? "" : o.toString()); + } else if (sType.canCreateNewInstanceFromNumber(outer) && dt.isOneOf(INT, LONG, FLOAT, DOUBLE)) { + o = sType.newInstanceFromNumber(this, outer, (Number)o); + } else if (sType.isCollection()) { + if (dt == MAP) { + ObjectMap m = new ObjectMap(this); + for (int i = 0; i < length; i++) + m.put(parseAnything(string(), is, outer, pMeta), parseAnything(object(), is, m, pMeta)); + o = cast(m, pMeta, eType); + } else if (dt == ARRAY) { + Collection l = ( + sType.canCreateNewInstance(outer) + ? (Collection)sType.newInstance() + : new ObjectList(this) + ); + for (int i = 0; i < length; i++) + l.add(parseAnything(sType.getElementType(), is, l, pMeta)); + o = l; + } else { + throw new ParseException(loc(is), "Invalid data type {0} encountered for parse type {1}", dt, sType); + } + } else if (sType.isArray() || sType.isArgs()) { + if (dt == MAP) { + ObjectMap m = new ObjectMap(this); + for (int i = 0; i < length; i++) + m.put(parseAnything(string(), is, outer, pMeta), parseAnything(object(), is, m, pMeta)); + o = cast(m, pMeta, eType); + } else if (dt == ARRAY) { + Collection l = ( + sType.isCollection() && sType.canCreateNewInstance(outer) + ? (Collection)sType.newInstance() + : new ObjectList(this) + ); + for (int i = 0; i < length; i++) + l.add(parseAnything(sType.isArgs() ? sType.getArg(i) : sType.getElementType(), is, l, pMeta)); + o = toArray(sType, l); + } else { + throw new ParseException(loc(is), "Invalid data type {0} encountered for parse type {1}", dt, sType); + } + } else if (dt == MAP) { + ObjectMap m = new ObjectMap(this); + for (int i = 0; i < length; i++) + m.put(parseAnything(string(), is, outer, pMeta), parseAnything(object(), is, m, pMeta)); + if (m.containsKey(getBeanTypePropertyName(eType))) + o = cast(m, pMeta, eType); + else + throw new ParseException(loc(is), "Class ''{0}'' could not be instantiated. Reason: ''{1}''", + sType.getInnerClass().getName(), sType.getNotABeanReason()); + } else { + throw new ParseException(loc(is), "Invalid data type {0} encountered for parse type {1}", dt, sType); + } + } + + if (transform != null && o != null) + o = transform.unswap(this, o, eType); + + if (outer != null) + setParent(eType, o, outer); + + return (T)o; + } + + private ObjectMap loc(MsgPackInputStream is) { + return getLastLocation().append("position", is.getPosition()); } } http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/2a37f310/juneau-core/src/main/java/org/apache/juneau/msgpack/MsgPackSerializer.java ---------------------------------------------------------------------- diff --git a/juneau-core/src/main/java/org/apache/juneau/msgpack/MsgPackSerializer.java b/juneau-core/src/main/java/org/apache/juneau/msgpack/MsgPackSerializer.java index ad5ed97..ad4dbce 100644 --- a/juneau-core/src/main/java/org/apache/juneau/msgpack/MsgPackSerializer.java +++ b/juneau-core/src/main/java/org/apache/juneau/msgpack/MsgPackSerializer.java @@ -12,14 +12,9 @@ // *************************************************************************************************************************** package org.apache.juneau.msgpack; -import java.lang.reflect.*; -import java.util.*; - import org.apache.juneau.*; import org.apache.juneau.annotation.*; -import org.apache.juneau.http.*; import org.apache.juneau.serializer.*; -import org.apache.juneau.transform.*; /** * Serializes POJO models to MessagePack. @@ -63,169 +58,8 @@ public class MsgPackSerializer extends OutputStreamSerializer { return new MsgPackSerializerBuilder(propertyStore); } - /** - * Workhorse method. - * - * <p> - * Determines the type of object, and then calls the appropriate type-specific serialization method. - */ - @SuppressWarnings({ "rawtypes", "unchecked" }) - MsgPackOutputStream serializeAnything(MsgPackSerializerSession session, MsgPackOutputStream out, Object o, - ClassMeta<?> eType, String attrName, BeanPropertyMeta pMeta) throws Exception { - - if (o == null) - return out.appendNull(); - - if (eType == null) - eType = object(); - - ClassMeta<?> aType; // The actual type - ClassMeta<?> sType; // The serialized type - - aType = session.push(attrName, o, eType); - boolean isRecursion = aType == null; - - // Handle recursion - if (aType == null) { - o = null; - aType = object(); - } - - sType = aType.getSerializedClassMeta(); - String typeName = session.getBeanTypeName(eType, aType, pMeta); - - // Swap if necessary - PojoSwap swap = aType.getPojoSwap(); - if (swap != null) { - o = swap.swap(session, o); - - // If the getSwapClass() method returns Object, we need to figure out - // the actual type now. - if (sType.isObject()) - sType = session.getClassMetaForObject(o); - } - - // '\0' characters are considered null. - if (o == null || (sType.isChar() && ((Character)o).charValue() == 0)) - out.appendNull(); - else if (sType.isBoolean()) - out.appendBoolean((Boolean)o); - else if (sType.isNumber()) - out.appendNumber((Number)o); - else if (sType.isBean()) - serializeBeanMap(session, out, session.toBeanMap(o), typeName); - else if (sType.isUri() || (pMeta != null && pMeta.isUri())) - out.appendString(session.resolveUri(o.toString())); - else if (sType.isMap()) { - if (o instanceof BeanMap) - serializeBeanMap(session, out, (BeanMap)o, typeName); - else - serializeMap(session, out, (Map)o, eType); - } - else if (sType.isCollection()) { - serializeCollection(session, out, (Collection) o, eType); - } - else if (sType.isArray()) { - serializeCollection(session, out, toList(sType.getInnerClass(), o), eType); - } else - out.appendString(session.toString(o)); - - if (! isRecursion) - session.pop(); - return out; - } - - @SuppressWarnings({ "rawtypes", "unchecked" }) - private void serializeMap(MsgPackSerializerSession session, MsgPackOutputStream out, Map m, ClassMeta<?> type) - throws Exception { - - ClassMeta<?> keyType = type.getKeyType(), valueType = type.getValueType(); - - m = session.sort(m); - - // The map size may change as we're iterating over it, so - // grab a snapshot of the entries in a separate list. - List<SimpleMapEntry> entries = new ArrayList<SimpleMapEntry>(m.size()); - for (Map.Entry e : (Set<Map.Entry>)m.entrySet()) - entries.add(new SimpleMapEntry(e.getKey(), e.getValue())); - - out.startMap(entries.size()); - - for (SimpleMapEntry e : entries) { - Object value = e.value; - Object key = session.generalize(e.key, keyType); - - serializeAnything(session, out, key, keyType, null, null); - serializeAnything(session, out, value, valueType, null, null); - } - } - - private void serializeBeanMap(MsgPackSerializerSession session, MsgPackOutputStream out, final BeanMap<?> m, - String typeName) throws Exception { - - List<BeanPropertyValue> values = m.getValues(session.isTrimNulls(), typeName != null ? session.createBeanTypeNameProperty(m, typeName) : null); - - int size = values.size(); - for (BeanPropertyValue p : values) - if (p.getThrown() != null) - size--; - out.startMap(size); - - for (BeanPropertyValue p : values) { - BeanPropertyMeta pMeta = p.getMeta(); - ClassMeta<?> cMeta = p.getClassMeta(); - String key = p.getName(); - Object value = p.getValue(); - Throwable t = p.getThrown(); - if (t != null) - session.onBeanGetterException(pMeta, t); - else { - serializeAnything(session, out, key, null, null, null); - serializeAnything(session, out, value, cMeta, key, pMeta); - } - } - } - - private static class SimpleMapEntry { - final Object key; - final Object value; - - private SimpleMapEntry(Object key, Object value) { - this.key = key; - this.value = value; - } - } - - @SuppressWarnings({"rawtypes", "unchecked"}) - private void serializeCollection(MsgPackSerializerSession session, MsgPackOutputStream out, Collection c, - ClassMeta<?> type) throws Exception { - - ClassMeta<?> elementType = type.getElementType(); - List<Object> l = new ArrayList<Object>(c.size()); - - c = session.sort(c); - l.addAll(c); - - out.startArray(l.size()); - - for (Object o : l) - serializeAnything(session, out, o, elementType, "<iterator>", null); - } - - - //-------------------------------------------------------------------------------- - // Entry point methods - //-------------------------------------------------------------------------------- - - @Override /* Serializer */ - public MsgPackSerializerSession createSession(ObjectMap op, Method javaMethod, Locale locale, - TimeZone timeZone, MediaType mediaType, UriContext uriContext) { - return new MsgPackSerializerSession(ctx, op, javaMethod, locale, timeZone, mediaType, uriContext); - } - @Override /* Serializer */ - protected void doSerialize(SerializerSession session, SerializerOutput out, Object o) throws Exception { - MsgPackSerializerSession s = (MsgPackSerializerSession)session; - serializeAnything(s, s.getMsgPackOutputStream(out), o, s.getExpectedRootType(o), "root", null); + public OutputStreamSerializerSession createSession(SerializerSessionArgs args) { + return new MsgPackSerializerSession(ctx, args); } }
