You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@struts.apache.org by mu...@apache.org on 2009/07/29 20:33:08 UTC

svn commit: r799003 [2/4] - in /struts/sandbox/trunk/struts2-json-plugin: ./ src/ src/main/ src/main/java/ src/main/java/org/ src/main/java/org/apache/ src/main/java/org/apache/struts2/ src/main/java/org/apache/struts2/json/ src/main/java/org/apache/st...

Added: struts/sandbox/trunk/struts2-json-plugin/src/main/java/org/apache/struts2/json/JSONUtil.java
URL: http://svn.apache.org/viewvc/struts/sandbox/trunk/struts2-json-plugin/src/main/java/org/apache/struts2/json/JSONUtil.java?rev=799003&view=auto
==============================================================================
--- struts/sandbox/trunk/struts2-json-plugin/src/main/java/org/apache/struts2/json/JSONUtil.java (added)
+++ struts/sandbox/trunk/struts2-json-plugin/src/main/java/org/apache/struts2/json/JSONUtil.java Wed Jul 29 18:33:05 2009
@@ -0,0 +1,395 @@
+/*
+ * $Id$
+ *
+ * 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.struts2.json;
+
+import java.io.BufferedReader;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.PrintWriter;
+import java.io.Reader;
+import java.io.Writer;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.regex.Pattern;
+import java.util.zip.GZIPOutputStream;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.commons.lang.xwork.StringUtils;
+import org.apache.struts2.json.annotations.SMDMethod;
+
+import com.opensymphony.xwork2.util.logging.Logger;
+import com.opensymphony.xwork2.util.logging.LoggerFactory;
+
+/**
+ * Wrapper for JSONWriter with some utility methods.
+ */
+public class JSONUtil {
+    final static String RFC3339_FORMAT = "yyyy-MM-dd'T'HH:mm:ss";
+    private static final Logger LOG = LoggerFactory.getLogger(JSONUtil.class);
+
+    /**
+     * Serializes an object into JSON.
+     * 
+     * @param object
+     *            to be serialized
+     * @return JSON string
+     * @throws JSONException
+     */
+    public static String serialize(Object object) throws JSONException {
+        JSONWriter writer = new JSONWriter();
+
+        return writer.write(object);
+    }
+
+    /**
+     * Serializes an object into JSON, excluding any properties matching any of
+     * the regular expressions in the given collection.
+     * 
+     * @param object
+     *            to be serialized
+     * @param excludeProperties
+     *            Patterns matching properties to exclude
+     * @param ignoreHierarchy
+     *            whether to ignore properties defined on base classes of the
+     *            root object
+     * @return JSON string
+     * @throws JSONException
+     */
+    public static String serialize(Object object, Collection<Pattern> excludeProperties,
+            Collection<Pattern> includeProperties, boolean ignoreHierarchy, boolean excludeNullProperties)
+            throws JSONException {
+        JSONWriter writer = new JSONWriter();
+        writer.setIgnoreHierarchy(ignoreHierarchy);
+        return writer.write(object, excludeProperties, includeProperties, excludeNullProperties);
+    }
+
+    /**
+     * Serializes an object into JSON, excluding any properties matching any of
+     * the regular expressions in the given collection.
+     * 
+     * @param object
+     *            to be serialized
+     * @param excludeProperties
+     *            Patterns matching properties to exclude
+     * @param ignoreHierarchy
+     *            whether to ignore properties defined on base classes of the
+     *            root object
+     * @param enumAsBean
+     *            whether to serialized enums a Bean or name=value pair
+     * @return JSON string
+     * @throws JSONException
+     */
+    public static String serialize(Object object, Collection<Pattern> excludeProperties,
+            Collection<Pattern> includeProperties, boolean ignoreHierarchy, boolean enumAsBean,
+            boolean excludeNullProperties) throws JSONException {
+        JSONWriter writer = new JSONWriter();
+        writer.setIgnoreHierarchy(ignoreHierarchy);
+        writer.setEnumAsBean(enumAsBean);
+        return writer.write(object, excludeProperties, includeProperties, excludeNullProperties);
+    }
+
+    /**
+     * Serializes an object into JSON to the given writer.
+     * 
+     * @param writer
+     *            Writer to serialize the object to
+     * @param object
+     *            object to be serialized
+     * @throws IOException
+     * @throws JSONException
+     */
+    public static void serialize(Writer writer, Object object) throws IOException, JSONException {
+        writer.write(serialize(object));
+    }
+
+    /**
+     * Serializes an object into JSON to the given writer, excluding any
+     * properties matching any of the regular expressions in the given
+     * collection.
+     * 
+     * @param writer
+     *            Writer to serialize the object to
+     * @param object
+     *            object to be serialized
+     * @param excludeProperties
+     *            Patterns matching properties to ignore
+     * @throws IOException
+     * @throws JSONException
+     */
+    public static void serialize(Writer writer, Object object, Collection<Pattern> excludeProperties,
+            Collection<Pattern> includeProperties, boolean excludeNullProperties) throws IOException,
+            JSONException {
+        writer.write(serialize(object, excludeProperties, includeProperties, true, excludeNullProperties));
+    }
+
+    /**
+     * Deserializes a object from JSON
+     * 
+     * @param json
+     *            string in JSON
+     * @return desrialized object
+     * @throws JSONException
+     */
+    public static Object deserialize(String json) throws JSONException {
+        JSONReader reader = new JSONReader();
+        return reader.read(json);
+    }
+
+    /**
+     * Deserializes a object from JSON
+     * 
+     * @param reader
+     *            Reader to read a JSON string from
+     * @return deserialized object
+     * @throws JSONException
+     *             when IOException happens
+     */
+    public static Object deserialize(Reader reader) throws JSONException {
+        // read content
+        BufferedReader bufferReader = new BufferedReader(reader);
+        String line = null;
+        StringBuilder buffer = new StringBuilder();
+
+        try {
+            while ((line = bufferReader.readLine()) != null) {
+                buffer.append(line);
+            }
+        } catch (IOException e) {
+            throw new JSONException(e);
+        }
+
+        return deserialize(buffer.toString());
+    }
+
+    public static void writeJSONToResponse(SerializationParams serializationParams) throws IOException {
+        StringBuilder stringBuilder = new StringBuilder();
+        if (StringUtils.isNotBlank(serializationParams.getSerializedJSON()))
+            stringBuilder.append(serializationParams.getSerializedJSON());
+
+        if (StringUtils.isNotBlank(serializationParams.getWrapPrefix()))
+            stringBuilder.insert(0, serializationParams.getWrapPrefix());
+        else if (serializationParams.isWrapWithComments()) {
+            stringBuilder.insert(0, "/* ");
+            stringBuilder.append(" */");
+        } else if (serializationParams.isPrefix())
+            stringBuilder.insert(0, "{}&& ");
+
+        if (StringUtils.isNotBlank(serializationParams.getWrapSuffix()))
+            stringBuilder.append(serializationParams.getWrapSuffix());
+
+        String json = stringBuilder.toString();
+
+        if (LOG.isDebugEnabled()) {
+            LOG.debug("[JSON]" + json);
+        }
+
+        HttpServletResponse response = serializationParams.getResponse();
+
+        // status or error code
+        if (serializationParams.getStatusCode() > 0)
+            response.setStatus(serializationParams.getStatusCode());
+        else if (serializationParams.getErrorCode() > 0)
+            response.sendError(serializationParams.getErrorCode());
+
+        // content type
+        if (serializationParams.isSmd())
+            response.setContentType("application/json-rpc;charset=" + serializationParams.getEncoding());
+        else
+            response.setContentType(serializationParams.getContentType() + ";charset="
+                    + serializationParams.getEncoding());
+
+        if (serializationParams.isNoCache()) {
+            response.setHeader("Cache-Control", "no-cache");
+            response.setHeader("Expires", "0");
+            response.setHeader("Pragma", "No-cache");
+        }
+
+        if (serializationParams.isGzip()) {
+            response.addHeader("Content-Encoding", "gzip");
+            GZIPOutputStream out = null;
+            InputStream in = null;
+            try {
+                out = new GZIPOutputStream(response.getOutputStream());
+                in = new ByteArrayInputStream(json.getBytes());
+                byte[] buf = new byte[1024];
+                int len;
+                while ((len = in.read(buf)) > 0) {
+                    out.write(buf, 0, len);
+                }
+            } finally {
+                if (in != null)
+                    in.close();
+                if (out != null) {
+                    out.finish();
+                    out.close();
+                }
+            }
+
+        } else {
+            response.setContentLength(json.getBytes(serializationParams.getEncoding()).length);
+            PrintWriter out = response.getWriter();
+            out.print(json);
+        }
+    }
+
+    public static List<String> asList(String commaDelim) {
+        if ((commaDelim == null) || (commaDelim.trim().length() == 0))
+            return null;
+        List<String> list = new ArrayList<String>();
+        String[] split = commaDelim.split(",");
+        for (int i = 0; i < split.length; i++) {
+            String trimmed = split[i].trim();
+            if (trimmed.length() > 0) {
+                list.add(trimmed);
+            }
+        }
+        return list;
+    }
+
+    /**
+     * List visible methods carrying the
+     * 
+     * @SMDMethod annotation
+     * 
+     * @param ignoreInterfaces
+     *            if true, only the methods of the class are examined. If false,
+     *            annotations on every interfaces' methods are examined.
+     */
+    @SuppressWarnings("unchecked")
+    public static Method[] listSMDMethods(Class clazz, boolean ignoreInterfaces) {
+        final List<Method> methods = new LinkedList<Method>();
+        if (ignoreInterfaces) {
+            for (Method method : clazz.getMethods()) {
+                SMDMethod smdMethodAnnotation = method.getAnnotation(SMDMethod.class);
+                if (smdMethodAnnotation != null) {
+                    methods.add(method);
+                }
+            }
+        } else {
+            // recurse the entire superclass/interface hierarchy and add in
+            // order encountered
+            JSONUtil.visitInterfaces(clazz, new JSONUtil.ClassVisitor() {
+                public boolean visit(Class aClass) {
+                    for (Method method : aClass.getMethods()) {
+                        SMDMethod smdMethodAnnotation = method.getAnnotation(SMDMethod.class);
+                        if ((smdMethodAnnotation != null) && !methods.contains(method)) {
+                            methods.add(method);
+                        }
+                    }
+                    return true;
+                }
+            });
+        }
+
+        Method[] methodResult = new Method[methods.size()];
+        return methods.toArray(methodResult);
+    }
+
+    /**
+     * Realizes the visit(Class) method called by vistInterfaces for all
+     * encountered classes/interfaces
+     */
+    public static interface ClassVisitor {
+
+        /**
+         * Called when a new interface/class is encountered
+         * 
+         * @param aClass
+         *            the encountered class/interface
+         * @return true if the recursion should continue, false to stop
+         *         recursion immediately
+         */
+        @SuppressWarnings("unchecked")
+        boolean visit(Class aClass);
+    }
+
+    /**
+     * Visit all the interfaces realized by the specified object, its
+     * superclasses and its interfaces <p/> Visitation is performed in the
+     * following order: aClass aClass' interfaces the interface's superclasses
+     * (interfaces) aClass' superclass superclass' interfaces superclass'
+     * interface's superclasses (interfaces) super-superclass and so on <p/> The
+     * Object base class is base excluded. Classes/interfaces are only visited
+     * once each
+     * 
+     * @param aClass
+     *            the class to start recursing upwards from
+     * @param visitor
+     *            this vistor is called for each class/interface encountered
+     * @return true if all classes/interfaces were visited, false if it was
+     *         exited early as specified by a ClassVisitor result
+     */
+    @SuppressWarnings("unchecked")
+    public static boolean visitInterfaces(Class aClass, ClassVisitor visitor) {
+        List<Class> classesVisited = new LinkedList<Class>();
+        return visitUniqueInterfaces(aClass, visitor, classesVisited);
+    }
+
+    /**
+     * Recursive method to visit all the interfaces of a class (and its
+     * superclasses and super-interfaces) if they haven't already been visited.
+     * <p/> Always visits itself if it hasn't already been visited
+     * 
+     * @param thisClass
+     *            the current class to visit (if not already done so)
+     * @param classesVisited
+     *            classes already visited
+     * @param visitor
+     *            this vistor is called for each class/interface encountered
+     * @return true if recursion can continue, false if it should be aborted
+     */
+    private static boolean visitUniqueInterfaces(Class thisClass, ClassVisitor visitor,
+            List<Class> classesVisited) {
+        boolean okayToContinue = true;
+
+        if (!classesVisited.contains(thisClass)) {
+            classesVisited.add(thisClass);
+            okayToContinue = visitor.visit(thisClass);
+
+            if (okayToContinue) {
+                Class[] interfaces = thisClass.getInterfaces();
+                int index = 0;
+                while ((index < interfaces.length) && (okayToContinue)) {
+                    okayToContinue = visitUniqueInterfaces(interfaces[index++], visitor, classesVisited);
+                }
+
+                if (okayToContinue) {
+                    Class superClass = thisClass.getSuperclass();
+                    if ((superClass != null) && (!Object.class.equals(superClass))) {
+                        okayToContinue = visitUniqueInterfaces(superClass, visitor, classesVisited);
+                    }
+                }
+            }
+        }
+        return okayToContinue;
+    }
+
+    public static boolean isGzipInRequest(HttpServletRequest request) {
+        String header = request.getHeader("Accept-Encoding");
+        return (header != null) && (header.indexOf("gzip") >= 0);
+    }
+}

Added: struts/sandbox/trunk/struts2-json-plugin/src/main/java/org/apache/struts2/json/JSONWriter.java
URL: http://svn.apache.org/viewvc/struts/sandbox/trunk/struts2-json-plugin/src/main/java/org/apache/struts2/json/JSONWriter.java?rev=799003&view=auto
==============================================================================
--- struts/sandbox/trunk/struts2-json-plugin/src/main/java/org/apache/struts2/json/JSONWriter.java (added)
+++ struts/sandbox/trunk/struts2-json-plugin/src/main/java/org/apache/struts2/json/JSONWriter.java Wed Jul 29 18:33:05 2009
@@ -0,0 +1,549 @@
+/*
+ * $Id$
+ *
+ * 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.struts2.json;
+
+import java.beans.BeanInfo;
+import java.beans.Introspector;
+import java.beans.PropertyDescriptor;
+import java.lang.reflect.Array;
+import java.lang.reflect.Method;
+import java.text.CharacterIterator;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.text.StringCharacterIterator;
+import java.util.Calendar;
+import java.util.Collection;
+import java.util.Date;
+import java.util.Iterator;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Stack;
+import java.util.regex.Pattern;
+
+import org.apache.struts2.json.annotations.JSON;
+
+import com.opensymphony.xwork2.util.logging.Logger;
+import com.opensymphony.xwork2.util.logging.LoggerFactory;
+
+/**
+ * <p>
+ * Serializes an object into JavaScript Object Notation (JSON). If cyclic
+ * references are detected they will be nulled out.
+ * </p>
+ */
+class JSONWriter {
+    private static final Logger LOG = LoggerFactory.getLogger(JSONWriter.class);
+
+    /**
+     * By default, enums are serialzied as name=value pairs
+     */
+    public static final boolean ENUM_AS_BEAN_DEFAULT = false;
+
+    static char[] hex = "0123456789ABCDEF".toCharArray();
+    private StringBuilder buf = new StringBuilder();
+    private Stack stack = new Stack();
+    private boolean ignoreHierarchy = true;
+    private Object root;
+    private boolean buildExpr = true;
+    private String exprStack = "";
+    private Collection<Pattern> excludeProperties;
+    private Collection<Pattern> includeProperties;
+    private DateFormat formatter;
+    private boolean enumAsBean = ENUM_AS_BEAN_DEFAULT;
+    private boolean excludeNullProperties;
+
+    /**
+     * @param object
+     *            Object to be serialized into JSON
+     * @return JSON string for object
+     * @throws JSONException
+     */
+    public String write(Object object) throws JSONException {
+        return this.write(object, null, null, false);
+    }
+
+    /**
+     * @param object
+     *            Object to be serialized into JSON
+     * @return JSON string for object
+     * @throws JSONException
+     */
+    public String write(Object object, Collection<Pattern> excludeProperties,
+            Collection<Pattern> includeProperties, boolean excludeNullProperties) throws JSONException {
+        this.excludeNullProperties = excludeNullProperties;
+        this.buf.setLength(0);
+        this.root = object;
+        this.exprStack = "";
+        this.buildExpr = ((excludeProperties != null) && !excludeProperties.isEmpty())
+                || ((includeProperties != null) && !includeProperties.isEmpty());
+        this.excludeProperties = excludeProperties;
+        this.includeProperties = includeProperties;
+        this.value(object, null);
+
+        return this.buf.toString();
+    }
+
+    /**
+     * Detect cyclic references
+     */
+    private void value(Object object, Method method) throws JSONException {
+        if (object == null) {
+            this.add("null");
+
+            return;
+        }
+
+        if (this.stack.contains(object)) {
+            Class clazz = object.getClass();
+
+            // cyclic reference
+            if (clazz.isPrimitive() || clazz.equals(String.class)) {
+                this.process(object, method);
+            } else {
+                if (LOG.isDebugEnabled()) {
+                    LOG.debug("Cyclic reference detected on " + object);
+                }
+
+                this.add("null");
+            }
+
+            return;
+        }
+
+        this.process(object, method);
+    }
+
+    /**
+     * Serialize object into json
+     */
+    private void process(Object object, Method method) throws JSONException {
+        this.stack.push(object);
+
+        if (object instanceof Class) {
+            this.string(object);
+        } else if (object instanceof Boolean) {
+            this.bool(((Boolean) object).booleanValue());
+        } else if (object instanceof Number) {
+            this.add(object);
+        } else if (object instanceof String) {
+            this.string(object);
+        } else if (object instanceof Character) {
+            this.string(object);
+        } else if (object instanceof Map) {
+            this.map((Map) object, method);
+        } else if (object.getClass().isArray()) {
+            this.array(object, method);
+        } else if (object instanceof Iterable) {
+            this.array(((Iterable) object).iterator(), method);
+        } else if (object instanceof Date) {
+            this.date((Date) object, method);
+        } else if (object instanceof Calendar) {
+            this.date(((Calendar) object).getTime(), method);
+        } else if (object instanceof Locale) {
+            this.string(object);
+        } else if (object instanceof Enum) {
+            this.enumeration((Enum) object);
+        } else {
+            this.bean(object);
+        }
+
+        this.stack.pop();
+    }
+
+    /**
+     * Instrospect bean and serialize its properties
+     */
+    private void bean(Object object) throws JSONException {
+        this.add("{");
+
+        BeanInfo info;
+
+        try {
+            Class clazz = object.getClass();
+
+            info = ((object == this.root) && this.ignoreHierarchy) ? Introspector.getBeanInfo(clazz, clazz
+                    .getSuperclass()) : Introspector.getBeanInfo(clazz);
+
+            PropertyDescriptor[] props = info.getPropertyDescriptors();
+
+            boolean hasData = false;
+            for (int i = 0; i < props.length; ++i) {
+                PropertyDescriptor prop = props[i];
+                String name = prop.getName();
+                Method accessor = prop.getReadMethod();
+                Method baseAccessor = null;
+                if (clazz.getName().indexOf("$$EnhancerByCGLIB$$") > -1) {
+                    try {
+                        baseAccessor = Class.forName(
+                                clazz.getName().substring(0, clazz.getName().indexOf("$$"))).getMethod(
+                                accessor.getName(), accessor.getParameterTypes());
+                    } catch (Exception ex) {
+                        LOG.debug(ex.getMessage(), ex);
+                    }
+                } else
+                    baseAccessor = accessor;
+
+                if (baseAccessor != null) {
+
+                    JSON json = baseAccessor.getAnnotation(JSON.class);
+                    if (json != null) {
+                        if (!json.serialize())
+                            continue;
+                        else if (json.name().length() > 0)
+                            name = json.name();
+                    }
+
+                    // ignore "class" and others
+                    if (this.shouldExcludeProperty(clazz, prop)) {
+                        continue;
+                    }
+                    String expr = null;
+                    if (this.buildExpr) {
+                        expr = this.expandExpr(name);
+                        if (this.shouldExcludeProperty(expr)) {
+                            continue;
+                        }
+                        expr = this.setExprStack(expr);
+                    }
+
+                    Object value = accessor.invoke(object, new Object[0]);
+                    boolean propertyPrinted = this.add(name, value, accessor, hasData);
+                    hasData = hasData || propertyPrinted;
+                    if (this.buildExpr) {
+                        this.setExprStack(expr);
+                    }
+                }
+            }
+
+            // special-case handling for an Enumeration - include the name() as
+            // a property */
+            if (object instanceof Enum) {
+                Object value = ((Enum) object).name();
+                this.add("_name", value, object.getClass().getMethod("name"), hasData);
+            }
+        } catch (Exception e) {
+            throw new JSONException(e);
+        }
+
+        this.add("}");
+    }
+
+    /**
+     * Instrospect an Enum and serialize it as a name/value pair or as a bean
+     * including all its own properties
+     */
+    private void enumeration(Enum enumeration) throws JSONException {
+        if (enumAsBean) {
+            this.bean(enumeration);
+        } else {
+            this.string(enumeration.name());
+        }
+    }
+
+    /**
+     * Ignore "class" field
+     */
+    private boolean shouldExcludeProperty(Class clazz, PropertyDescriptor prop) throws SecurityException,
+            NoSuchFieldException {
+        String name = prop.getName();
+
+        if (name.equals("class") || name.equals("declaringClass") || name.equals("cachedSuperClass")
+                || name.equals("metaClass")) {
+            return true;
+        }
+
+        return false;
+    }
+
+    private String expandExpr(int i) {
+        return this.exprStack + "[" + i + "]";
+    }
+
+    private String expandExpr(String property) {
+        if (this.exprStack.length() == 0)
+            return property;
+        return this.exprStack + "." + property;
+    }
+
+    private String setExprStack(String expr) {
+        String s = this.exprStack;
+        this.exprStack = expr;
+        return s;
+    }
+
+    private boolean shouldExcludeProperty(String expr) {
+        if (this.excludeProperties != null) {
+            for (Pattern pattern : this.excludeProperties) {
+                if (pattern.matcher(expr).matches()) {
+                    if (LOG.isDebugEnabled())
+                        LOG.debug("Ignoring property because of exclude rule: " + expr);
+                    return true;
+                }
+            }
+        }
+
+        if (this.includeProperties != null) {
+            for (Pattern pattern : this.includeProperties) {
+                if (pattern.matcher(expr).matches()) {
+                    return false;
+                }
+            }
+
+            if (LOG.isDebugEnabled())
+                LOG.debug("Ignoring property because of include rule:  " + expr);
+            return true;
+        }
+
+        return false;
+    }
+
+    /**
+     * Add name/value pair to buffer
+     */
+    private boolean add(String name, Object value, Method method, boolean hasData) throws JSONException {
+        if (!excludeNullProperties || (value != null)) {
+            if (hasData) {
+                this.add(',');
+            }
+            this.add('"');
+            this.add(name);
+            this.add("\":");
+            this.value(value, method);
+            return true;
+        }
+
+        return false;
+    }
+
+    /**
+     * Add map to buffer
+     */
+    private void map(Map map, Method method) throws JSONException {
+        this.add("{");
+
+        Iterator it = map.entrySet().iterator();
+
+        boolean warnedNonString = false; // one report per map
+        boolean hasData = false;
+        while (it.hasNext()) {
+            Map.Entry entry = (Map.Entry) it.next();
+            Object key = entry.getKey();
+            String expr = null;
+            if (this.buildExpr) {
+                if (key == null) {
+                    LOG.error("Cannot build expression for null key in " + this.exprStack);
+                    continue;
+                } else {
+                    expr = this.expandExpr(key.toString());
+                    if (this.shouldExcludeProperty(expr)) {
+                        continue;
+                    }
+                    expr = this.setExprStack(expr);
+                }
+            }
+            if (hasData) {
+                this.add(',');
+            }
+            hasData = true;
+            if (!warnedNonString && !(key instanceof String)) {
+                LOG.warn("JavaScript doesn't support non-String keys, using toString() on "
+                        + key.getClass().getName());
+                warnedNonString = true;
+            }
+            this.value(key.toString(), method);
+            this.add(":");
+            this.value(entry.getValue(), method);
+            if (this.buildExpr) {
+                this.setExprStack(expr);
+            }
+        }
+
+        this.add("}");
+    }
+
+    /**
+     * Add date to buffer
+     */
+    private void date(Date date, Method method) {
+        JSON json = null;
+        if (method != null)
+            json = method.getAnnotation(JSON.class);
+        if (this.formatter == null)
+            this.formatter = new SimpleDateFormat(JSONUtil.RFC3339_FORMAT);
+
+        DateFormat formatter = (json != null) && (json.format().length() > 0) ? new SimpleDateFormat(json
+                .format()) : this.formatter;
+        this.string(formatter.format(date));
+    }
+
+    /**
+     * Add array to buffer
+     */
+    private void array(Iterator it, Method method) throws JSONException {
+        this.add("[");
+
+        boolean hasData = false;
+        for (int i = 0; it.hasNext(); i++) {
+            String expr = null;
+            if (this.buildExpr) {
+                expr = this.expandExpr(i);
+                if (this.shouldExcludeProperty(expr)) {
+                    it.next();
+                    continue;
+                }
+                expr = this.setExprStack(expr);
+            }
+            if (hasData) {
+                this.add(',');
+            }
+            hasData = true;
+            this.value(it.next(), method);
+            if (this.buildExpr) {
+                this.setExprStack(expr);
+            }
+        }
+
+        this.add("]");
+    }
+
+    /**
+     * Add array to buffer
+     */
+    private void array(Object object, Method method) throws JSONException {
+        this.add("[");
+
+        int length = Array.getLength(object);
+
+        boolean hasData = false;
+        for (int i = 0; i < length; ++i) {
+            String expr = null;
+            if (this.buildExpr) {
+                expr = this.expandExpr(i);
+                if (this.shouldExcludeProperty(expr)) {
+                    continue;
+                }
+                expr = this.setExprStack(expr);
+            }
+            if (hasData) {
+                this.add(',');
+            }
+            hasData = true;
+            this.value(Array.get(object, i), method);
+            if (this.buildExpr) {
+                this.setExprStack(expr);
+            }
+        }
+
+        this.add("]");
+    }
+
+    /**
+     * Add boolean to buffer
+     */
+    private void bool(boolean b) {
+        this.add(b ? "true" : "false");
+    }
+
+    /**
+     * escape characters
+     */
+    private void string(Object obj) {
+        this.add('"');
+
+        CharacterIterator it = new StringCharacterIterator(obj.toString());
+
+        for (char c = it.first(); c != CharacterIterator.DONE; c = it.next()) {
+            if (c == '"') {
+                this.add("\\\"");
+            } else if (c == '\\') {
+                this.add("\\\\");
+            } else if (c == '/') {
+                this.add("\\/");
+            } else if (c == '\b') {
+                this.add("\\b");
+            } else if (c == '\f') {
+                this.add("\\f");
+            } else if (c == '\n') {
+                this.add("\\n");
+            } else if (c == '\r') {
+                this.add("\\r");
+            } else if (c == '\t') {
+                this.add("\\t");
+            } else if (Character.isISOControl(c)) {
+                this.unicode(c);
+            } else {
+                this.add(c);
+            }
+        }
+
+        this.add('"');
+    }
+
+    /**
+     * Add object to buffer
+     */
+    private void add(Object obj) {
+        this.buf.append(obj);
+    }
+
+    /**
+     * Add char to buffer
+     */
+    private void add(char c) {
+        this.buf.append(c);
+    }
+
+    /**
+     * Represent as unicode
+     * 
+     * @param c
+     *            character to be encoded
+     */
+    private void unicode(char c) {
+        this.add("\\u");
+
+        int n = c;
+
+        for (int i = 0; i < 4; ++i) {
+            int digit = (n & 0xf000) >> 12;
+
+            this.add(hex[digit]);
+            n <<= 4;
+        }
+    }
+
+    public void setIgnoreHierarchy(boolean ignoreHierarchy) {
+        this.ignoreHierarchy = ignoreHierarchy;
+    }
+
+    /**
+     * If true, an Enum is serialized as a bean with a special property
+     * _name=name() as all as all other properties defined within the enum.<br/>
+     * If false, an Enum is serialized as a name=value pair (name=name())
+     * 
+     * @param enumAsBean
+     *            true to serialize an enum as a bean instead of as a name=value
+     *            pair (default=false)
+     */
+    public void setEnumAsBean(boolean enumAsBean) {
+        this.enumAsBean = enumAsBean;
+    }
+}

Added: struts/sandbox/trunk/struts2-json-plugin/src/main/java/org/apache/struts2/json/SerializationParams.java
URL: http://svn.apache.org/viewvc/struts/sandbox/trunk/struts2-json-plugin/src/main/java/org/apache/struts2/json/SerializationParams.java?rev=799003&view=auto
==============================================================================
--- struts/sandbox/trunk/struts2-json-plugin/src/main/java/org/apache/struts2/json/SerializationParams.java (added)
+++ struts/sandbox/trunk/struts2-json-plugin/src/main/java/org/apache/struts2/json/SerializationParams.java Wed Jul 29 18:33:05 2009
@@ -0,0 +1,100 @@
+package org.apache.struts2.json;
+
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.commons.lang.xwork.StringUtils;
+
+public class SerializationParams {
+    private static final String DEFAULT_CONTENT_TYPE = "application/json";
+
+    private final HttpServletResponse response;
+    private final String encoding;
+    private final boolean wrapWithComments;
+    private final String serializedJSON;
+    private final boolean smd;
+    private final boolean gzip;
+    private final boolean noCache;
+    private final int statusCode;
+    private final int errorCode;
+    private final boolean prefix;
+    private String contentType = DEFAULT_CONTENT_TYPE;
+    private String wrapPrefix;
+    private String wrapSuffix;
+
+    public SerializationParams(HttpServletResponse response, String encoding, boolean wrapWithComments,
+            String serializedJSON, boolean smd, boolean gzip, boolean noCache, int statusCode, int errorCode,
+            boolean prefix, String contentType, String wrapPrefix, String wrapSuffix) {
+        this.response = response;
+        this.encoding = encoding;
+        this.wrapWithComments = wrapWithComments;
+        this.serializedJSON = serializedJSON;
+        this.smd = smd;
+        this.gzip = gzip;
+        this.noCache = noCache;
+        this.statusCode = statusCode;
+        this.errorCode = errorCode;
+        this.prefix = prefix;
+        this.contentType = StringUtils.defaultString(contentType, DEFAULT_CONTENT_TYPE);
+        this.wrapPrefix = wrapPrefix;
+        this.wrapSuffix = wrapSuffix;
+    }
+
+    public SerializationParams(HttpServletResponse response, String defaultEncoding,
+            boolean wrapWithComments, String json, boolean b, boolean b1, boolean noCache, int i, int i1,
+            boolean prefix, String contentType) {
+        this(response, defaultEncoding, wrapWithComments, json, b, b1, noCache, i, i1, prefix, contentType,
+                null, null);
+    }
+
+    public String getWrapSuffix() {
+        return wrapSuffix;
+    }
+
+    public String getWrapPrefix() {
+        return wrapPrefix;
+    }
+
+    public HttpServletResponse getResponse() {
+        return response;
+    }
+
+    public String getEncoding() {
+        return encoding;
+    }
+
+    public boolean isWrapWithComments() {
+        return wrapWithComments;
+    }
+
+    public String getSerializedJSON() {
+        return serializedJSON;
+    }
+
+    public boolean isSmd() {
+        return smd;
+    }
+
+    public boolean isGzip() {
+        return gzip;
+    }
+
+    public boolean isNoCache() {
+        return noCache;
+    }
+
+    public int getStatusCode() {
+        return statusCode;
+    }
+
+    public int getErrorCode() {
+        return errorCode;
+    }
+
+    public boolean isPrefix() {
+        return prefix;
+    }
+
+    public String getContentType() {
+        return contentType;
+    }
+}

Added: struts/sandbox/trunk/struts2-json-plugin/src/main/java/org/apache/struts2/json/annotations/JSON.java
URL: http://svn.apache.org/viewvc/struts/sandbox/trunk/struts2-json-plugin/src/main/java/org/apache/struts2/json/annotations/JSON.java?rev=799003&view=auto
==============================================================================
--- struts/sandbox/trunk/struts2-json-plugin/src/main/java/org/apache/struts2/json/annotations/JSON.java (added)
+++ struts/sandbox/trunk/struts2-json-plugin/src/main/java/org/apache/struts2/json/annotations/JSON.java Wed Jul 29 18:33:05 2009
@@ -0,0 +1,18 @@
+package org.apache.struts2.json.annotations;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface JSON {
+    String name() default "";
+
+    boolean serialize() default true;
+
+    boolean deserialize() default true;
+
+    String format() default "";
+}

Added: struts/sandbox/trunk/struts2-json-plugin/src/main/java/org/apache/struts2/json/annotations/SMD.java
URL: http://svn.apache.org/viewvc/struts/sandbox/trunk/struts2-json-plugin/src/main/java/org/apache/struts2/json/annotations/SMD.java?rev=799003&view=auto
==============================================================================
--- struts/sandbox/trunk/struts2-json-plugin/src/main/java/org/apache/struts2/json/annotations/SMD.java (added)
+++ struts/sandbox/trunk/struts2-json-plugin/src/main/java/org/apache/struts2/json/annotations/SMD.java Wed Jul 29 18:33:05 2009
@@ -0,0 +1,36 @@
+/*
+ * $Id$
+ *
+ * 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.struts2.json.annotations;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Target(ElementType.TYPE)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface SMD {
+    String version() default org.apache.struts2.json.smd.SMD.DEFAULT_VERSION;
+
+    String objectName();
+
+    String serviceType() default org.apache.struts2.json.smd.SMD.DEFAULT_SERVICE_TYPE;
+}

Added: struts/sandbox/trunk/struts2-json-plugin/src/main/java/org/apache/struts2/json/annotations/SMDMethod.java
URL: http://svn.apache.org/viewvc/struts/sandbox/trunk/struts2-json-plugin/src/main/java/org/apache/struts2/json/annotations/SMDMethod.java?rev=799003&view=auto
==============================================================================
--- struts/sandbox/trunk/struts2-json-plugin/src/main/java/org/apache/struts2/json/annotations/SMDMethod.java (added)
+++ struts/sandbox/trunk/struts2-json-plugin/src/main/java/org/apache/struts2/json/annotations/SMDMethod.java Wed Jul 29 18:33:05 2009
@@ -0,0 +1,32 @@
+/*
+ * $Id$
+ *
+ * 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.struts2.json.annotations;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface SMDMethod {
+    String name() default "";
+}

Added: struts/sandbox/trunk/struts2-json-plugin/src/main/java/org/apache/struts2/json/annotations/SMDMethodParameter.java
URL: http://svn.apache.org/viewvc/struts/sandbox/trunk/struts2-json-plugin/src/main/java/org/apache/struts2/json/annotations/SMDMethodParameter.java?rev=799003&view=auto
==============================================================================
--- struts/sandbox/trunk/struts2-json-plugin/src/main/java/org/apache/struts2/json/annotations/SMDMethodParameter.java (added)
+++ struts/sandbox/trunk/struts2-json-plugin/src/main/java/org/apache/struts2/json/annotations/SMDMethodParameter.java Wed Jul 29 18:33:05 2009
@@ -0,0 +1,12 @@
+package org.apache.struts2.json.annotations;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Target(ElementType.PARAMETER)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface SMDMethodParameter {
+    String name();
+}

Added: struts/sandbox/trunk/struts2-json-plugin/src/main/java/org/apache/struts2/json/rpc/RPCError.java
URL: http://svn.apache.org/viewvc/struts/sandbox/trunk/struts2-json-plugin/src/main/java/org/apache/struts2/json/rpc/RPCError.java?rev=799003&view=auto
==============================================================================
--- struts/sandbox/trunk/struts2-json-plugin/src/main/java/org/apache/struts2/json/rpc/RPCError.java (added)
+++ struts/sandbox/trunk/struts2-json-plugin/src/main/java/org/apache/struts2/json/rpc/RPCError.java Wed Jul 29 18:33:05 2009
@@ -0,0 +1,86 @@
+package org.apache.struts2.json.rpc;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+
+import com.opensymphony.xwork2.util.logging.Logger;
+import com.opensymphony.xwork2.util.logging.LoggerFactory;
+
+public class RPCError {
+    private static final Logger LOG = LoggerFactory.getLogger(RPCError.class);
+
+    private int code;
+    private String name;
+    private String message;
+    private String stack;
+
+    public RPCError() {
+    }
+
+    public RPCError(String message, int code) {
+        this.code = code;
+        this.message = message;
+
+        LOG.error(message);
+    }
+
+    public RPCError(String message, RPCErrorCode code) {
+        this(message, code.code());
+    }
+
+    public RPCError(Throwable t, int code, boolean debug) {
+        while (t.getCause() != null) {
+            t = t.getCause();
+        }
+
+        this.code = code;
+        this.message = t.getMessage();
+        this.name = t.getClass().getName();
+
+        if (debug) {
+            StringWriter s = new StringWriter();
+            PrintWriter w = new PrintWriter(s);
+            t.printStackTrace(w);
+            w.flush();
+            this.stack = s.toString();
+        }
+
+        LOG.error(t.getMessage(), t);
+    }
+
+    public RPCError(Throwable t, RPCErrorCode code, boolean debug) {
+        this(t, code.code(), debug);
+    }
+
+    public int getCode() {
+        return code;
+    }
+
+    public void setCode(int code) {
+        this.code = code;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public String getMessage() {
+        return message;
+    }
+
+    public void setMessage(String message) {
+        this.message = message;
+    }
+
+    public String getStack() {
+        return stack;
+    }
+
+    public void setStack(String stack) {
+        this.stack = stack;
+    }
+}

Added: struts/sandbox/trunk/struts2-json-plugin/src/main/java/org/apache/struts2/json/rpc/RPCErrorCode.java
URL: http://svn.apache.org/viewvc/struts/sandbox/trunk/struts2-json-plugin/src/main/java/org/apache/struts2/json/rpc/RPCErrorCode.java?rev=799003&view=auto
==============================================================================
--- struts/sandbox/trunk/struts2-json-plugin/src/main/java/org/apache/struts2/json/rpc/RPCErrorCode.java (added)
+++ struts/sandbox/trunk/struts2-json-plugin/src/main/java/org/apache/struts2/json/rpc/RPCErrorCode.java Wed Jul 29 18:33:05 2009
@@ -0,0 +1,25 @@
+package org.apache.struts2.json.rpc;
+
+public enum RPCErrorCode {
+    MISSING_METHOD(100, "'method' parameter is missing in request"), MISSING_ID(100,
+            "'id' parameter is missing in request"), INVALID_PROCEDURE_CALL(0, "Invalid procedure call"), METHOD_NOT_FOUND(
+            101, "Procedure not found"), PARAMETERS_MISMATCH(102,
+            "Parameters count in request does not patch parameters count on method"), EXCEPTION(103,
+            "An exception was thrown"), SMD_DISABLED(104, "SMD is disabled");
+
+    private int code;
+    private String message;
+
+    RPCErrorCode(int code, String message) {
+        this.code = code;
+        this.message = message;
+    }
+
+    public int code() {
+        return code;
+    }
+
+    public String message() {
+        return this.message;
+    }
+}

Added: struts/sandbox/trunk/struts2-json-plugin/src/main/java/org/apache/struts2/json/rpc/RPCResponse.java
URL: http://svn.apache.org/viewvc/struts/sandbox/trunk/struts2-json-plugin/src/main/java/org/apache/struts2/json/rpc/RPCResponse.java?rev=799003&view=auto
==============================================================================
--- struts/sandbox/trunk/struts2-json-plugin/src/main/java/org/apache/struts2/json/rpc/RPCResponse.java (added)
+++ struts/sandbox/trunk/struts2-json-plugin/src/main/java/org/apache/struts2/json/rpc/RPCResponse.java Wed Jul 29 18:33:05 2009
@@ -0,0 +1,34 @@
+package org.apache.struts2.json.rpc;
+
+/**
+ * Class that will be serialized as a response to an RPC call
+ */
+public class RPCResponse {
+    private String id;
+    private Object result;
+    private RPCError error;
+
+    public String getId() {
+        return id;
+    }
+
+    public void setId(String id) {
+        this.id = id;
+    }
+
+    public Object getResult() {
+        return result;
+    }
+
+    public void setResult(Object result) {
+        this.result = result;
+    }
+
+    public RPCError getError() {
+        return error;
+    }
+
+    public void setError(RPCError error) {
+        this.error = error;
+    }
+}

Added: struts/sandbox/trunk/struts2-json-plugin/src/main/java/org/apache/struts2/json/smd/SMD.java
URL: http://svn.apache.org/viewvc/struts/sandbox/trunk/struts2-json-plugin/src/main/java/org/apache/struts2/json/smd/SMD.java?rev=799003&view=auto
==============================================================================
--- struts/sandbox/trunk/struts2-json-plugin/src/main/java/org/apache/struts2/json/smd/SMD.java (added)
+++ struts/sandbox/trunk/struts2-json-plugin/src/main/java/org/apache/struts2/json/smd/SMD.java Wed Jul 29 18:33:05 2009
@@ -0,0 +1,75 @@
+/*
+ * $Id$
+ *
+ * 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.struts2.json.smd;
+
+import java.util.Set;
+import java.util.TreeSet;
+
+public class SMD {
+    public static final String DEFAULT_VERSION = ".1";
+    public static final String DEFAULT_SERVICE_TYPE = "JSON-RPC";
+
+    private String version = DEFAULT_VERSION;
+    private String objectName;
+    private String serviceType = DEFAULT_SERVICE_TYPE;
+    private String serviceUrl;
+    private Set<SMDMethod> methods = new TreeSet<SMDMethod>();
+
+    public void addSMDMethod(SMDMethod method) {
+        this.methods.add(method);
+    }
+
+    public String getVersion() {
+        return this.version;
+    }
+
+    public void setVersion(String version) {
+        this.version = version;
+    }
+
+    public String getObjectName() {
+        return this.objectName;
+    }
+
+    public void setObjectName(String objectName) {
+        this.objectName = objectName;
+    }
+
+    public String getServiceType() {
+        return this.serviceType;
+    }
+
+    public void setServiceType(String serviceType) {
+        this.serviceType = serviceType;
+    }
+
+    public String getServiceUrl() {
+        return this.serviceUrl;
+    }
+
+    public void setServiceUrl(String serviceUrl) {
+        this.serviceUrl = serviceUrl;
+    }
+
+    public Set<SMDMethod> getMethods() {
+        return this.methods;
+    }
+}

Added: struts/sandbox/trunk/struts2-json-plugin/src/main/java/org/apache/struts2/json/smd/SMDMethod.java
URL: http://svn.apache.org/viewvc/struts/sandbox/trunk/struts2-json-plugin/src/main/java/org/apache/struts2/json/smd/SMDMethod.java?rev=799003&view=auto
==============================================================================
--- struts/sandbox/trunk/struts2-json-plugin/src/main/java/org/apache/struts2/json/smd/SMDMethod.java (added)
+++ struts/sandbox/trunk/struts2-json-plugin/src/main/java/org/apache/struts2/json/smd/SMDMethod.java Wed Jul 29 18:33:05 2009
@@ -0,0 +1,75 @@
+/*
+ * $Id$
+ *
+ * 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.struts2.json.smd;
+
+import java.util.Set;
+import java.util.TreeSet;
+
+public class SMDMethod implements Comparable {
+    private String name;
+    private Set<SMDMethodParameter> parameters = new TreeSet<SMDMethodParameter>();
+
+    public SMDMethod(String name) {
+        this.name = name;
+    }
+
+    public void addSMDMethodParameter(SMDMethodParameter parameter) {
+        this.parameters.add(parameter);
+    }
+
+    public String getName() {
+        return this.name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public Set<SMDMethodParameter> getParameters() {
+        return this.parameters;
+    }
+
+    public int compareTo(Object o) {
+        if (!(o instanceof SMDMethod))
+            return 1;
+        if (o == null)
+            return 1;
+        SMDMethod other = (SMDMethod) o;
+        if ((name == null) && (other.name == null))
+            return 0;
+        if (name == null)
+            return -1;
+        if (name.equals(other.name))
+            return parameters.size() - other.parameters.size();
+
+        return name.compareTo(other.name);
+    }
+
+    public boolean equals(Object obj) {
+        if (!(obj instanceof SMDMethod))
+            return false;
+        SMDMethod toCompare = (SMDMethod) obj;
+        if ((name == null) && (toCompare.name == null))
+            return true;
+        return (name != null) && name.equals(toCompare.name)
+                && (parameters.size() == toCompare.parameters.size());
+    }
+}

Added: struts/sandbox/trunk/struts2-json-plugin/src/main/java/org/apache/struts2/json/smd/SMDMethodParameter.java
URL: http://svn.apache.org/viewvc/struts/sandbox/trunk/struts2-json-plugin/src/main/java/org/apache/struts2/json/smd/SMDMethodParameter.java?rev=799003&view=auto
==============================================================================
--- struts/sandbox/trunk/struts2-json-plugin/src/main/java/org/apache/struts2/json/smd/SMDMethodParameter.java (added)
+++ struts/sandbox/trunk/struts2-json-plugin/src/main/java/org/apache/struts2/json/smd/SMDMethodParameter.java Wed Jul 29 18:33:05 2009
@@ -0,0 +1,57 @@
+/*
+ * $Id$
+ *
+ * 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.struts2.json.smd;
+
+public class SMDMethodParameter implements Comparable {
+    private String name;
+
+    public SMDMethodParameter(String name) {
+        this.name = name;
+    }
+
+    public String getName() {
+        return this.name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public int compareTo(Object o) {
+        if (!(o instanceof SMDMethodParameter))
+            return 1;
+        if (o == null)
+            return 1;
+        if ((name == null) && (((SMDMethodParameter) o).name == null))
+            return 0;
+        if (name == null)
+            return -1;
+        return name.compareTo(((SMDMethodParameter) o).name);
+    }
+
+    public boolean equals(Object o) {
+        if (!(o instanceof SMDMethodParameter))
+            return false;
+        if ((name == null) && (((SMDMethodParameter) o).name == null))
+            return true;
+        return (name != null) && name.equals(((SMDMethodParameter) o).name);
+    }
+}

Added: struts/sandbox/trunk/struts2-json-plugin/src/main/resources/JsonPlugin.tld
URL: http://svn.apache.org/viewvc/struts/sandbox/trunk/struts2-json-plugin/src/main/resources/JsonPlugin.tld?rev=799003&view=auto
==============================================================================
--- struts/sandbox/trunk/struts2-json-plugin/src/main/resources/JsonPlugin.tld (added)
+++ struts/sandbox/trunk/struts2-json-plugin/src/main/resources/JsonPlugin.tld Wed Jul 29 18:33:05 2009
@@ -0,0 +1,25 @@
+<?xml version="1.0"?>
+<taglib xmlns="http://java.sun.com/xml/ns/j2ee"
+        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+        xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-jsptaglibrary_2_0.xsd"
+        version="2.0">
+
+    <description>
+        This exposes JSON util functions from the Struts JSON plugin
+    </description>
+
+    <tlib-version>1.0</tlib-version>
+
+    <short-name>json</short-name>
+
+    <uri>/struts-json-tags</uri>
+
+    <function>
+        <name>serialize</name>
+        <function-class>org.apache.struts2.json.JSONUtil</function-class>
+        <function-signature>
+            java.lang.String serialize(java.lang.Object)
+        </function-signature>
+    </function>
+
+</taglib>

Added: struts/sandbox/trunk/struts2-json-plugin/src/main/resources/struts-plugin.xml
URL: http://svn.apache.org/viewvc/struts/sandbox/trunk/struts2-json-plugin/src/main/resources/struts-plugin.xml?rev=799003&view=auto
==============================================================================
--- struts/sandbox/trunk/struts2-json-plugin/src/main/resources/struts-plugin.xml (added)
+++ struts/sandbox/trunk/struts2-json-plugin/src/main/resources/struts-plugin.xml Wed Jul 29 18:33:05 2009
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+
+<!DOCTYPE struts PUBLIC
+        "-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
+        "http://struts.apache.org/dtds/struts-2.0.dtd">
+
+<struts>
+    <package name="json-default" extends="struts-default">
+        <result-types>
+            <result-type name="json" class="org.apache.struts2.json.JSONUtil"/>
+        </result-types>
+        <interceptors>
+            <interceptor name="json" class="org.apache.struts2.json.JSONInterceptor"/>
+        </interceptors>
+    </package>
+</struts>

Added: struts/sandbox/trunk/struts2-json-plugin/src/test/java/org/apache/struts2/json/AnEnum.java
URL: http://svn.apache.org/viewvc/struts/sandbox/trunk/struts2-json-plugin/src/test/java/org/apache/struts2/json/AnEnum.java?rev=799003&view=auto
==============================================================================
--- struts/sandbox/trunk/struts2-json-plugin/src/test/java/org/apache/struts2/json/AnEnum.java (added)
+++ struts/sandbox/trunk/struts2-json-plugin/src/test/java/org/apache/struts2/json/AnEnum.java Wed Jul 29 18:33:05 2009
@@ -0,0 +1,8 @@
+package org.apache.struts2.json;
+
+/**
+ * An enumeration for JSON serialization testing
+ */
+public enum AnEnum {
+    ValueA, ValueB, ValueC
+}

Added: struts/sandbox/trunk/struts2-json-plugin/src/test/java/org/apache/struts2/json/AnEnumBean.java
URL: http://svn.apache.org/viewvc/struts/sandbox/trunk/struts2-json-plugin/src/test/java/org/apache/struts2/json/AnEnumBean.java?rev=799003&view=auto
==============================================================================
--- struts/sandbox/trunk/struts2-json-plugin/src/test/java/org/apache/struts2/json/AnEnumBean.java (added)
+++ struts/sandbox/trunk/struts2-json-plugin/src/test/java/org/apache/struts2/json/AnEnumBean.java Wed Jul 29 18:33:05 2009
@@ -0,0 +1,25 @@
+package org.apache.struts2.json;
+
+/**
+ * A more complicated Enum with additional properties.
+ */
+public enum AnEnumBean {
+
+    One("A", "B"), Two("C", "D"), Three("E", "F");
+
+    private String propA;
+    private String propB;
+
+    AnEnumBean(String propA, String propB) {
+        this.propA = propA;
+        this.propB = propB;
+    }
+
+    public String getPropA() {
+        return propA;
+    }
+
+    public String getPropB() {
+        return propB;
+    }
+}

Added: struts/sandbox/trunk/struts2-json-plugin/src/test/java/org/apache/struts2/json/Bean.java
URL: http://svn.apache.org/viewvc/struts/sandbox/trunk/struts2-json-plugin/src/test/java/org/apache/struts2/json/Bean.java?rev=799003&view=auto
==============================================================================
--- struts/sandbox/trunk/struts2-json-plugin/src/test/java/org/apache/struts2/json/Bean.java (added)
+++ struts/sandbox/trunk/struts2-json-plugin/src/test/java/org/apache/struts2/json/Bean.java Wed Jul 29 18:33:05 2009
@@ -0,0 +1,131 @@
+package org.apache.struts2.json;
+
+import java.math.BigDecimal;
+import java.math.BigInteger;
+
+public class Bean {
+    private String stringField;
+    private int intField;
+    private boolean booleanField;
+    private char charField;
+    private long longField;
+    private float floatField;
+    private double doubleField;
+    private Object objectField;
+    private byte byteField;
+    private AnEnum enumField;
+    private AnEnumBean enumBean;
+    private BigDecimal bigDecimal;
+    private BigInteger bigInteger;
+
+    /**
+     * @return the byteField
+     */
+    public byte getByteField() {
+        return this.byteField;
+    }
+
+    /**
+     * @param byteField
+     *            the byteField to set
+     */
+    public void setByteField(byte byteField) {
+        this.byteField = byteField;
+    }
+
+    public boolean isBooleanField() {
+        return this.booleanField;
+    }
+
+    public void setBooleanField(boolean booleanField) {
+        this.booleanField = booleanField;
+    }
+
+    public char getCharField() {
+        return this.charField;
+    }
+
+    public void setCharField(char charField) {
+        this.charField = charField;
+    }
+
+    public double getDoubleField() {
+        return this.doubleField;
+    }
+
+    public void setDoubleField(double doubleField) {
+        this.doubleField = doubleField;
+    }
+
+    public float getFloatField() {
+        return this.floatField;
+    }
+
+    public void setFloatField(float floatField) {
+        this.floatField = floatField;
+    }
+
+    public int getIntField() {
+        return this.intField;
+    }
+
+    public void setIntField(int intField) {
+        this.intField = intField;
+    }
+
+    public long getLongField() {
+        return this.longField;
+    }
+
+    public void setLongField(long longField) {
+        this.longField = longField;
+    }
+
+    public Object getObjectField() {
+        return this.objectField;
+    }
+
+    public void setObjectField(Object objectField) {
+        this.objectField = objectField;
+    }
+
+    public String getStringField() {
+        return this.stringField;
+    }
+
+    public void setStringField(String stringField) {
+        this.stringField = stringField;
+    }
+
+    public AnEnum getEnumField() {
+        return enumField;
+    }
+
+    public void setEnumField(AnEnum enumField) {
+        this.enumField = enumField;
+    }
+
+    public AnEnumBean getEnumBean() {
+        return enumBean;
+    }
+
+    public void setEnumBean(AnEnumBean enumBean) {
+        this.enumBean = enumBean;
+    }
+
+    public BigInteger getBigInteger() {
+        return bigInteger;
+    }
+
+    public void setBigInteger(BigInteger bigInteger) {
+        this.bigInteger = bigInteger;
+    }
+
+    public BigDecimal getBigDecimal() {
+        return bigDecimal;
+    }
+
+    public void setBigDecimal(BigDecimal bigDecimal) {
+        this.bigDecimal = bigDecimal;
+    }
+}

Added: struts/sandbox/trunk/struts2-json-plugin/src/test/java/org/apache/struts2/json/JSONEnumTest.java
URL: http://svn.apache.org/viewvc/struts/sandbox/trunk/struts2-json-plugin/src/test/java/org/apache/struts2/json/JSONEnumTest.java?rev=799003&view=auto
==============================================================================
--- struts/sandbox/trunk/struts2-json-plugin/src/test/java/org/apache/struts2/json/JSONEnumTest.java (added)
+++ struts/sandbox/trunk/struts2-json-plugin/src/test/java/org/apache/struts2/json/JSONEnumTest.java Wed Jul 29 18:33:05 2009
@@ -0,0 +1,107 @@
+package org.apache.struts2.json;
+
+import java.util.Map;
+
+import junit.framework.TestCase;
+
+/**
+ * Test serialization of an Enum in the two supported modes: - Enum as a
+ * name=value pair; - Enum as a bean
+ */
+public class JSONEnumTest extends TestCase {
+
+    /**
+     * Asserts that a bean can be serialized to JSON and restored as a map
+     */
+    public void testEnumAsNameValue() throws Exception {
+        Bean bean1 = new Bean();
+
+        bean1.setStringField("str");
+        bean1.setBooleanField(true);
+        bean1.setCharField('s');
+        bean1.setDoubleField(10.1);
+        bean1.setFloatField(1.5f);
+        bean1.setIntField(10);
+        bean1.setLongField(100);
+        bean1.setEnumField(AnEnum.ValueA);
+        bean1.setEnumBean(AnEnumBean.Two);
+
+        JSONWriter jsonWriter = new JSONWriter();
+        jsonWriter.setEnumAsBean(false);
+        String json = jsonWriter.write(bean1);
+
+        Map result = (Map) JSONUtil.deserialize(json);
+        assertEquals("str", result.get("stringField"));
+        assertEquals(true, result.get("booleanField"));
+        assertEquals("s", result.get("charField")); // note: this is a
+                                                            // String
+        assertEquals(10.1, result.get("doubleField"));
+        assertEquals(1.5, result.get("floatField")); // note: this is a
+                                                            // Double
+        assertEquals(10L, result.get("intField")); // note: this is a
+                                                            // Long
+        assertEquals(AnEnum.ValueA, AnEnum.valueOf((String) result.get("enumField"))); // note:
+                                                                                        // this
+                                                                                        // is a
+                                                                                        // String
+        assertEquals(AnEnumBean.Two, AnEnumBean.valueOf((String) result.get("enumBean"))); // note:
+                                                                                            // this
+                                                                                            // is a
+                                                                                            // String
+    }
+
+    /**
+     * Asserts that a bean can be serialized to JSON and restored as a map <p/>
+     * In this case, the name of the enum is in _name and the two properties of
+     * AnEnumBean are also serialized
+     */
+    public void testEnumAsBean() throws Exception {
+        Bean bean1 = new Bean();
+
+        bean1.setStringField("str");
+        bean1.setBooleanField(true);
+        bean1.setCharField('s');
+        bean1.setDoubleField(10.1);
+        bean1.setFloatField(1.5f);
+        bean1.setIntField(10);
+        bean1.setLongField(100);
+        bean1.setEnumField(AnEnum.ValueA);
+        bean1.setEnumBean(AnEnumBean.Two);
+
+        JSONWriter jsonWriter = new JSONWriter();
+        jsonWriter.setEnumAsBean(true);
+        String json = jsonWriter.write(bean1);
+
+        Map result = (Map) JSONUtil.deserialize(json);
+        assertEquals("str", result.get("stringField"));
+        assertEquals(true, result.get("booleanField"));
+        assertEquals("s", result.get("charField")); // note: this is a
+                                                            // String
+        assertEquals(10.1, result.get("doubleField"));
+        assertEquals(1.5, result.get("floatField")); // note: this is a
+                                                            // Double
+        assertEquals(10L, result.get("intField")); // note: this is a
+                                                            // Long
+        Map enumBean1 = (Map) result.get("enumField");
+        assertNotNull(enumBean1);
+        assertEquals(AnEnum.ValueA, AnEnum.valueOf((String) enumBean1.get("_name"))); // get
+                                                                                        // the
+                                                                                        // special
+                                                                                        // name
+                                                                                        // property
+        Map enumBean2 = (Map) result.get("enumBean");
+        assertEquals(AnEnumBean.Two, AnEnumBean.valueOf((String) enumBean2.get("_name"))); // get
+                                                                                            // the
+                                                                                            // special
+                                                                                            // name
+                                                                                            // property
+        assertEquals(AnEnumBean.Two.getPropA(), (String) enumBean2.get("propA")); // get
+                                                                                    // the
+                                                                                    // propA
+                                                                                    // property
+        assertEquals(AnEnumBean.Two.getPropB(), (String) enumBean2.get("propB")); // get
+                                                                                    // the
+                                                                                    // propA
+                                                                                    // property
+    }
+}