Package com.vaadin.terminal.gwt.server

Source Code of com.vaadin.terminal.gwt.server.JsonPaintTarget$Variable

/*
* Copyright 2011 Vaadin Ltd.
*
* Licensed 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 com.vaadin.terminal.gwt.server;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.io.Serializable;
import java.io.StringWriter;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Map;
import java.util.Set;
import java.util.Stack;
import java.util.Vector;
import java.util.logging.Level;
import java.util.logging.Logger;

import com.vaadin.Application;
import com.vaadin.terminal.ApplicationResource;
import com.vaadin.terminal.ExternalResource;
import com.vaadin.terminal.PaintException;
import com.vaadin.terminal.PaintTarget;
import com.vaadin.terminal.Paintable;
import com.vaadin.terminal.Resource;
import com.vaadin.terminal.StreamVariable;
import com.vaadin.terminal.ThemeResource;
import com.vaadin.terminal.VariableOwner;
import com.vaadin.ui.Alignment;
import com.vaadin.ui.ClientWidget;
import com.vaadin.ui.Component;
import com.vaadin.ui.CustomLayout;

/**
* User Interface Description Language Target.
*
* TODO document better: role of this class, UIDL format, attributes, variables,
* etc.
*
* @author IT Mill Ltd.
* @version
* 6.7.1
* @since 5.0
*/
@SuppressWarnings("serial")
public class JsonPaintTarget implements PaintTarget {

    private static final Logger logger = Logger.getLogger(JsonPaintTarget.class
            .getName());

    /* Document type declarations */

    private final static String UIDL_ARG_NAME = "name";

    private final Stack<String> mOpenTags;

    private final Stack<JsonTag> openJsonTags;

    private final PrintWriter uidlBuffer;

    private boolean closed = false;

    private final AbstractCommunicationManager manager;

    private int changes = 0;

    private final Set<Object> usedResources = new HashSet<Object>();

    private boolean customLayoutArgumentsOpen = false;

    private JsonTag tag;

    private int errorsOpen;

    private boolean cacheEnabled = false;

    private final Collection<Paintable> paintedComponents = new HashSet<Paintable>();

    private Collection<Paintable> identifiersCreatedDueRefPaint;

    private final Collection<Class<? extends Paintable>> usedPaintableTypes = new LinkedList<Class<? extends Paintable>>();

    /**
     * Creates a new XMLPrintWriter, without automatic line flushing.
     *
     * @param variableMap
     * @param manager
     * @param outWriter
     *            A character-output stream.
     * @param cachingRequired
     *            true if this is not a full repaint, i.e. caches are to be
     *            used.
     * @throws PaintException
     *             if the paint operation failed.
     */
    public JsonPaintTarget(AbstractCommunicationManager manager,
            PrintWriter outWriter, boolean cachingRequired)
            throws PaintException {

        this.manager = manager;

        // Sets the target for UIDL writing
        uidlBuffer = outWriter;

        // Initialize tag-writing
        mOpenTags = new Stack<String>();
        openJsonTags = new Stack<JsonTag>();
        cacheEnabled = cachingRequired;
    }

    public void startTag(String tagName) throws PaintException {
        startTag(tagName, false);
    }

    /**
     * Prints the element start tag.
     *
     * <pre class='code'>
     *   Todo:
     *    Checking of input values
     *
     * </pre>
     *
     * @param tagName
     *            the name of the start tag.
     * @throws PaintException
     *             if the paint operation failed.
     *
     */
    public void startTag(String tagName, boolean isChildNode)
            throws PaintException {
        // In case of null data output nothing:
        if (tagName == null) {
            throw new NullPointerException();
        }

        // Ensures that the target is open
        if (closed) {
            throw new PaintException(
                    "Attempted to write to a closed PaintTarget.");
        }

        if (tag != null) {
            openJsonTags.push(tag);
        }
        // Checks tagName and attributes here
        mOpenTags.push(tagName);

        tag = new JsonTag(tagName);

        if ("error".equals(tagName)) {
            errorsOpen++;
        }

        customLayoutArgumentsOpen = false;

    }

    /**
     * Prints the element end tag.
     *
     * If the parent tag is closed before every child tag is closed an
     * PaintException is raised.
     *
     * @param tag
     *            the name of the end tag.
     * @throws Paintexception
     *             if the paint operation failed.
     */
    public void endTag(String tagName) throws PaintException {
        // In case of null data output nothing:
        if (tagName == null) {
            throw new NullPointerException();
        }

        // Ensure that the target is open
        if (closed) {
            throw new PaintException(
                    "Attempted to write to a closed PaintTarget.");
        }

        if (openJsonTags.size() > 0) {
            final JsonTag parent = openJsonTags.pop();

            String lastTag = "";

            lastTag = mOpenTags.pop();
            if (!tagName.equalsIgnoreCase(lastTag)) {
                throw new PaintException("Invalid UIDL: wrong ending tag: '"
                        + tagName + "' expected: '" + lastTag + "'.");
            }

            // simple hack which writes error uidl structure into attribute
            if ("error".equals(lastTag)) {
                if (errorsOpen == 1) {
                    parent.addAttribute("\"error\":[\"error\",{}"
                            + tag.getData() + "]");
                } else {
                    // sub error
                    parent.addData(tag.getJSON());
                }
                errorsOpen--;
            } else {
                parent.addData(tag.getJSON());
            }

            tag = parent;
        } else {
            changes++;
            uidlBuffer.print(((changes > 1) ? "," : "") + tag.getJSON());
            tag = null;
        }
    }

    /**
     * Substitutes the XML sensitive characters with predefined XML entities.
     *
     * @param xml
     *            the String to be substituted.
     * @return A new string instance where all occurrences of XML sensitive
     *         characters are substituted with entities.
     */
    static public String escapeXML(String xml) {
        if (xml == null || xml.length() <= 0) {
            return "";
        }
        return escapeXML(new StringBuilder(xml)).toString();
    }

    /**
     * Substitutes the XML sensitive characters with predefined XML entities.
     *
     * @param xml
     *            the String to be substituted.
     * @return A new StringBuilder instance where all occurrences of XML
     *         sensitive characters are substituted with entities.
     *
     */
    static StringBuilder escapeXML(StringBuilder xml) {
        if (xml == null || xml.length() <= 0) {
            return new StringBuilder("");
        }

        final StringBuilder result = new StringBuilder(xml.length() * 2);

        for (int i = 0; i < xml.length(); i++) {
            final char c = xml.charAt(i);
            final String s = toXmlChar(c);
            if (s != null) {
                result.append(s);
            } else {
                result.append(c);
            }
        }
        return result;
    }

    /**
     * Escapes the given string so it can safely be used as a JSON string.
     *
     * @param s
     *            The string to escape
     * @return Escaped version of the string
     */
    static public String escapeJSON(String s) {
        // FIXME: Move this method to another class as other classes use it
        // also.
        if (s == null) {
            return "";
        }
        final StringBuilder sb = new StringBuilder();
        for (int i = 0; i < s.length(); i++) {
            final char ch = s.charAt(i);
            switch (ch) {
            case '"':
                sb.append("\\\"");
                break;
            case '\\':
                sb.append("\\\\");
                break;
            case '\b':
                sb.append("\\b");
                break;
            case '\f':
                sb.append("\\f");
                break;
            case '\n':
                sb.append("\\n");
                break;
            case '\r':
                sb.append("\\r");
                break;
            case '\t':
                sb.append("\\t");
                break;
            case '/':
                sb.append("\\/");
                break;
            default:
                if (ch >= '\u0000' && ch <= '\u001F') {
                    final String ss = Integer.toHexString(ch);
                    sb.append("\\u");
                    for (int k = 0; k < 4 - ss.length(); k++) {
                        sb.append('0');
                    }
                    sb.append(ss.toUpperCase());
                } else {
                    sb.append(ch);
                }
            }
        }
        return sb.toString();
    }

    /**
     * Substitutes a XML sensitive character with predefined XML entity.
     *
     * @param c
     *            the Character to be replaced with an entity.
     * @return String of the entity or null if character is not to be replaced
     *         with an entity.
     */
    private static String toXmlChar(char c) {
        switch (c) {
        case '&':
            return "&amp;"; // & => &amp;
        case '>':
            return "&gt;"; // > => &gt;
        case '<':
            return "&lt;"; // < => &lt;
        case '"':
            return "&quot;"; // " => &quot;
        case '\'':
            return "&apos;"; // ' => &apos;
        default:
            return null;
        }
    }

    /**
     * Prints XML-escaped text.
     *
     * @param str
     * @throws PaintException
     *             if the paint operation failed.
     *
     */
    public void addText(String str) throws PaintException {
        tag.addData("\"" + escapeJSON(str) + "\"");
    }

    public void addAttribute(String name, boolean value) throws PaintException {
        tag.addAttribute("\"" + name + "\":" + (value ? "true" : "false"));
    }

    @SuppressWarnings("deprecation")
    public void addAttribute(String name, Resource value) throws PaintException {

        if (value instanceof ExternalResource) {
            addAttribute(name, ((ExternalResource) value).getURL());

        } else if (value instanceof ApplicationResource) {
            final ApplicationResource r = (ApplicationResource) value;
            final Application a = r.getApplication();
            if (a == null) {
                throw new PaintException(
                        "Application not specified for resorce "
                                + value.getClass().getName());
            }
            final String uri = a.getRelativeLocation(r);
            addAttribute(name, uri);

        } else if (value instanceof ThemeResource) {
            final String uri = "theme://"
                    + ((ThemeResource) value).getResourceId();
            addAttribute(name, uri);
        } else {
            throw new PaintException("Ajax adapter does not "
                    + "support resources of type: "
                    + value.getClass().getName());
        }

    }

    public void addAttribute(String name, int value) throws PaintException {
        tag.addAttribute("\"" + name + "\":" + String.valueOf(value));
    }

    public void addAttribute(String name, long value) throws PaintException {
        tag.addAttribute("\"" + name + "\":" + String.valueOf(value));
    }

    public void addAttribute(String name, float value) throws PaintException {
        tag.addAttribute("\"" + name + "\":" + String.valueOf(value));
    }

    public void addAttribute(String name, double value) throws PaintException {
        tag.addAttribute("\"" + name + "\":" + String.valueOf(value));
    }

    public void addAttribute(String name, String value) throws PaintException {
        // In case of null data output nothing:
        if ((value == null) || (name == null)) {
            throw new NullPointerException(
                    "Parameters must be non-null strings");
        }

        tag.addAttribute("\"" + name + "\": \"" + escapeJSON(value) + "\"");

        if (customLayoutArgumentsOpen && "template".equals(name)) {
            getUsedResources().add("layouts/" + value + ".html");
        }

        if (name.equals("locale")) {
            manager.requireLocale(value);
        }

    }

    public void addAttribute(String name, Paintable value)
            throws PaintException {
        final String id = getPaintIdentifier(value);
        addAttribute(name, id);
    }

    public void addAttribute(String name, Map<?, ?> value)
            throws PaintException {

        StringBuilder sb = new StringBuilder();
        sb.append("\"");
        sb.append(name);
        sb.append("\": ");
        sb.append("{");
        for (Iterator<?> it = value.keySet().iterator(); it.hasNext();) {
            Object key = it.next();
            Object mapValue = value.get(key);
            sb.append("\"");
            if (key instanceof Paintable) {
                Paintable paintable = (Paintable) key;
                sb.append(getPaintIdentifier(paintable));
            } else {
                sb.append(escapeJSON(key.toString()));
            }
            sb.append("\":");
            if (mapValue instanceof Float || mapValue instanceof Integer
                    || mapValue instanceof Double
                    || mapValue instanceof Boolean
                    || mapValue instanceof Alignment) {
                sb.append(mapValue);
            } else {
                sb.append("\"");
                sb.append(escapeJSON(mapValue.toString()));
                sb.append("\"");
            }
            if (it.hasNext()) {
                sb.append(",");
            }
        }
        sb.append("}");

        tag.addAttribute(sb.toString());
    }

    public void addAttribute(String name, Object[] values) {
        // In case of null data output nothing:
        if ((values == null) || (name == null)) {
            throw new NullPointerException(
                    "Parameters must be non-null strings");
        }
        final StringBuilder buf = new StringBuilder();
        buf.append("\"" + name + "\":[");
        for (int i = 0; i < values.length; i++) {
            if (i > 0) {
                buf.append(",");
            }
            buf.append("\"");
            buf.append(escapeJSON(values[i].toString()));
            buf.append("\"");
        }
        buf.append("]");
        tag.addAttribute(buf.toString());
    }

    public void addVariable(VariableOwner owner, String name, String value)
            throws PaintException {
        tag.addVariable(new StringVariable(owner, name, escapeJSON(value)));
    }

    public void addVariable(VariableOwner owner, String name, Paintable value)
            throws PaintException {
        tag.addVariable(new StringVariable(owner, name,
                getPaintIdentifier(value)));
    }

    public void addVariable(VariableOwner owner, String name, int value)
            throws PaintException {
        tag.addVariable(new IntVariable(owner, name, value));
    }

    public void addVariable(VariableOwner owner, String name, long value)
            throws PaintException {
        tag.addVariable(new LongVariable(owner, name, value));
    }

    public void addVariable(VariableOwner owner, String name, float value)
            throws PaintException {
        tag.addVariable(new FloatVariable(owner, name, value));
    }

    public void addVariable(VariableOwner owner, String name, double value)
            throws PaintException {
        tag.addVariable(new DoubleVariable(owner, name, value));
    }

    public void addVariable(VariableOwner owner, String name, boolean value)
            throws PaintException {
        tag.addVariable(new BooleanVariable(owner, name, value));
    }

    public void addVariable(VariableOwner owner, String name, String[] value)
            throws PaintException {
        tag.addVariable(new ArrayVariable(owner, name, value));
    }

    /**
     * Adds a upload stream type variable.
     *
     * TODO not converted for JSON
     *
     * @param owner
     *            the Listener for variable changes.
     * @param name
     *            the Variable name.
     *
     * @throws PaintException
     *             if the paint operation failed.
     */
    public void addUploadStreamVariable(VariableOwner owner, String name)
            throws PaintException {
        startTag("uploadstream");
        addAttribute(UIDL_ARG_NAME, name);
        endTag("uploadstream");
    }

    /**
     * Prints the single text section.
     *
     * Prints full text section. The section data is escaped
     *
     * @param sectionTagName
     *            the name of the tag.
     * @param sectionData
     *            the section data to be printed.
     * @throws PaintException
     *             if the paint operation failed.
     */
    public void addSection(String sectionTagName, String sectionData)
            throws PaintException {
        tag.addData("{\"" + sectionTagName + "\":\"" + escapeJSON(sectionData)
                + "\"}");
    }

    /**
     * Adds XML directly to UIDL.
     *
     * @param xml
     *            the Xml to be added.
     * @throws PaintException
     *             if the paint operation failed.
     */
    public void addUIDL(String xml) throws PaintException {

        // Ensure that the target is open
        if (closed) {
            throw new PaintException(
                    "Attempted to write to a closed PaintTarget.");
        }

        // Make sure that the open start tag is closed before
        // anything is written.

        // Escape and write what was given
        if (xml != null) {
            tag.addData("\"" + escapeJSON(xml) + "\"");
        }

    }

    /**
     * Adds XML section with namespace.
     *
     * @param sectionTagName
     *            the name of the tag.
     * @param sectionData
     *            the section data.
     * @param namespace
     *            the namespace to be added.
     * @throws PaintException
     *             if the paint operation failed.
     *
     * @see com.vaadin.terminal.PaintTarget#addXMLSection(String, String,
     *      String)
     */
    public void addXMLSection(String sectionTagName, String sectionData,
            String namespace) throws PaintException {

        // Ensure that the target is open
        if (closed) {
            throw new PaintException(
                    "Attempted to write to a closed PaintTarget.");
        }

        startTag(sectionTagName);
        if (namespace != null) {
            addAttribute("xmlns", namespace);
        }

        if (sectionData != null) {
            tag.addData("\"" + escapeJSON(sectionData) + "\"");
        }
        endTag(sectionTagName);
    }

    /**
     * Gets the UIDL already printed to stream. Paint target must be closed
     * before the <code>getUIDL</code> can be called.
     *
     * @return the UIDL.
     */
    public String getUIDL() {
        if (closed) {
            return uidlBuffer.toString();
        }
        throw new IllegalStateException(
                "Tried to read UIDL from open PaintTarget");
    }

    /**
     * Closes the paint target. Paint target must be closed before the
     * <code>getUIDL</code> can be called. Subsequent attempts to write to paint
     * target. If the target was already closed, call to this function is
     * ignored. will generate an exception.
     *
     * @throws PaintException
     *             if the paint operation failed.
     */
    public void close() throws PaintException {
        if (tag != null) {
            uidlBuffer.write(tag.getJSON());
        }
        flush();
        closed = true;
    }

    /**
     * Method flush.
     */
    private void flush() {
        uidlBuffer.flush();
    }

    /*
     * (non-Javadoc)
     *
     * @see com.vaadin.terminal.PaintTarget#startTag(com.vaadin.terminal
     * .Paintable, java.lang.String)
     */
    public boolean startTag(Paintable paintable, String tagName)
            throws PaintException {
        startTag(tagName, true);
        final boolean isPreviouslyPainted = manager.hasPaintableId(paintable)
                && (identifiersCreatedDueRefPaint == null || !identifiersCreatedDueRefPaint
                        .contains(paintable));
        final String id = manager.getPaintableId(paintable);
        paintable.addListener(manager);
        addAttribute("id", id);
        paintedComponents.add(paintable);

        if (paintable instanceof CustomLayout) {
            customLayoutArgumentsOpen = true;
        }

        return cacheEnabled && isPreviouslyPainted;
    }

    @Deprecated
    public void paintReference(Paintable paintable, String referenceName)
            throws PaintException {
        addAttribute(referenceName, paintable);
    }

    public String getPaintIdentifier(Paintable paintable) throws PaintException {
        if (!manager.hasPaintableId(paintable)) {
            if (identifiersCreatedDueRefPaint == null) {
                identifiersCreatedDueRefPaint = new HashSet<Paintable>();
            }
            identifiersCreatedDueRefPaint.add(paintable);
        }
        return manager.getPaintableId(paintable);
    }

    /*
     * (non-Javadoc)
     *
     * @see com.vaadin.terminal.PaintTarget#addCharacterData(java.lang.String )
     */
    public void addCharacterData(String text) throws PaintException {
        if (text != null) {
            tag.addData(text);
        }
    }

    /**
     * This is basically a container for UI components variables, that will be
     * added at the end of JSON object.
     *
     * @author mattitahvonen
     *
     */
    class JsonTag implements Serializable {
        boolean firstField = false;

        Vector<Object> variables = new Vector<Object>();

        Vector<Object> children = new Vector<Object>();

        Vector<Object> attr = new Vector<Object>();

        StringBuilder data = new StringBuilder();

        public boolean childrenArrayOpen = false;

        private boolean childNode = false;

        private boolean tagClosed = false;

        public JsonTag(String tagName) {
            data.append("[\"" + tagName + "\"");
        }

        private void closeTag() {
            if (!tagClosed) {
                data.append(attributesAsJsonObject());
                data.append(getData());
                // Writes the end (closing) tag
                data.append("]");
                tagClosed = true;
            }
        }

        public String getJSON() {
            if (!tagClosed) {
                closeTag();
            }
            return data.toString();
        }

        public void openChildrenArray() {
            if (!childrenArrayOpen) {
                // append("c : [");
                childrenArrayOpen = true;
                // firstField = true;
            }
        }

        public void closeChildrenArray() {
            // append("]");
            // firstField = false;
        }

        public void setChildNode(boolean b) {
            childNode = b;
        }

        public boolean isChildNode() {
            return childNode;
        }

        public String startField() {
            if (firstField) {
                firstField = false;
                return "";
            } else {
                return ",";
            }
        }

        /**
         *
         * @param s
         *            json string, object or array
         */
        public void addData(String s) {
            children.add(s);
        }

        public String getData() {
            final StringBuilder buf = new StringBuilder();
            final Iterator<Object> it = children.iterator();
            while (it.hasNext()) {
                buf.append(startField());
                buf.append(it.next());
            }
            return buf.toString();
        }

        public void addAttribute(String jsonNode) {
            attr.add(jsonNode);
        }

        private String attributesAsJsonObject() {
            final StringBuilder buf = new StringBuilder();
            buf.append(startField());
            buf.append("{");
            for (final Iterator<Object> iter = attr.iterator(); iter.hasNext();) {
                final String element = (String) iter.next();
                buf.append(element);
                if (iter.hasNext()) {
                    buf.append(",");
                }
            }
            buf.append(tag.variablesAsJsonObject());
            buf.append("}");
            return buf.toString();
        }

        public void addVariable(Variable v) {
            variables.add(v);
        }

        private String variablesAsJsonObject() {
            if (variables.size() == 0) {
                return "";
            }
            final StringBuilder buf = new StringBuilder();
            buf.append(startField());
            buf.append("\"v\":{");
            final Iterator<Object> iter = variables.iterator();
            while (iter.hasNext()) {
                final Variable element = (Variable) iter.next();
                buf.append(element.getJsonPresentation());
                if (iter.hasNext()) {
                    buf.append(",");
                }
            }
            buf.append("}");
            return buf.toString();
        }
    }

    abstract class Variable implements Serializable {

        String name;

        public abstract String getJsonPresentation();
    }

    class BooleanVariable extends Variable implements Serializable {
        boolean value;

        public BooleanVariable(VariableOwner owner, String name, boolean v) {
            value = v;
            this.name = name;
        }

        @Override
        public String getJsonPresentation() {
            return "\"" + name + "\":" + (value == true ? "true" : "false");
        }

    }

    class StringVariable extends Variable implements Serializable {
        String value;

        public StringVariable(VariableOwner owner, String name, String v) {
            value = v;
            this.name = name;
        }

        @Override
        public String getJsonPresentation() {
            return "\"" + name + "\":\"" + value + "\"";
        }

    }

    class IntVariable extends Variable implements Serializable {
        int value;

        public IntVariable(VariableOwner owner, String name, int v) {
            value = v;
            this.name = name;
        }

        @Override
        public String getJsonPresentation() {
            return "\"" + name + "\":" + value;
        }
    }

    class LongVariable extends Variable implements Serializable {
        long value;

        public LongVariable(VariableOwner owner, String name, long v) {
            value = v;
            this.name = name;
        }

        @Override
        public String getJsonPresentation() {
            return "\"" + name + "\":" + value;
        }
    }

    class FloatVariable extends Variable implements Serializable {
        float value;

        public FloatVariable(VariableOwner owner, String name, float v) {
            value = v;
            this.name = name;
        }

        @Override
        public String getJsonPresentation() {
            return "\"" + name + "\":" + value;
        }
    }

    class DoubleVariable extends Variable implements Serializable {
        double value;

        public DoubleVariable(VariableOwner owner, String name, double v) {
            value = v;
            this.name = name;
        }

        @Override
        public String getJsonPresentation() {
            return "\"" + name + "\":" + value;
        }
    }

    class ArrayVariable extends Variable implements Serializable {
        String[] value;

        public ArrayVariable(VariableOwner owner, String name, String[] v) {
            value = v;
            this.name = name;
        }

        @Override
        public String getJsonPresentation() {
            StringBuilder sb = new StringBuilder();
            sb.append("\"");
            sb.append(name);
            sb.append("\":[");
            for (int i = 0; i < value.length;) {
                sb.append("\"");
                sb.append(escapeJSON(value[i]));
                sb.append("\"");
                i++;
                if (i < value.length) {
                    sb.append(",");
                }
            }
            sb.append("]");
            return sb.toString();
        }
    }

    public Set<Object> getUsedResources() {
        return usedResources;
    }

    /**
     * Method to check if paintable is already painted into this target.
     *
     * @param p
     * @return true if is not yet painted into this target and is connected to
     *         app
     */
    public boolean needsToBePainted(Paintable p) {
        if (paintedComponents.contains(p)) {
            return false;
        } else if (((Component) p).getApplication() == null) {
            return false;
        } else {
            return true;
        }
    }

    private static final Map<Class<? extends Paintable>, Class<? extends Paintable>> widgetMappingCache = new HashMap<Class<? extends Paintable>, Class<? extends Paintable>>();

    @SuppressWarnings("unchecked")
    public String getTag(Paintable paintable) {
        Class<? extends Paintable> class1;
        synchronized (widgetMappingCache) {
            class1 = widgetMappingCache.get(paintable.getClass());
        }
        if (class1 == null) {
            /*
             * Client widget annotation is searched from component hierarchy to
             * detect the component that presumably has client side
             * implementation. The server side name is used in the
             * transportation, but encoded into integer strings to optimized
             * transferred data.
             */
            class1 = paintable.getClass();
            while (!hasClientWidgetMapping(class1)) {
                Class<?> superclass = class1.getSuperclass();
                if (superclass != null
                        && Paintable.class.isAssignableFrom(superclass)) {
                    class1 = (Class<? extends Paintable>) superclass;
                } else {
                    logger.warning("No superclass of "
                            + paintable.getClass().getName()
                            + " has a @ClientWidget"
                            + " annotation. Component will not be mapped correctly on client side.");
                    break;
                }
            }
            synchronized (widgetMappingCache) {
                widgetMappingCache.put(paintable.getClass(), class1);
            }
        }

        usedPaintableTypes.add(class1);
        return manager.getTagForType(class1);

    }

    private boolean hasClientWidgetMapping(Class<? extends Paintable> class1) {
        try {
            return class1.isAnnotationPresent(ClientWidget.class);
        } catch (NoClassDefFoundError e) {
            StringWriter writer = new StringWriter();
            e.printStackTrace(new PrintWriter(writer));
            String stacktrace = writer.toString();
            if (stacktrace
                    .contains("com.ibm.oti.reflect.AnnotationParser.parseClass")) {
                // #7479 IBM JVM apparently tries to eagerly load the classes
                // referred to by annotations. Checking the annotation from byte
                // code to be sure that we are dealing the this case and not
                // some other class loading issue.
                if (bytecodeContainsClientWidgetAnnotation(class1)) {
                    return true;
                }
            } else {
                // throw exception forward
                throw e;
            }
        } catch (RuntimeException e) {
            if (e.getStackTrace()[0].getClassName().equals(
                    "org.glassfish.web.loader.WebappClassLoader")) {

                // See #3920
                // Glassfish 3 is darn eager to load the value class, even
                // though we just want to check if the annotation exists.

                // In some situations (depending on class loading order) it
                // would be enough to return true here, but it is safer to check
                // the annotation from byte code

                if (bytecodeContainsClientWidgetAnnotation(class1)) {
                    return true;
                }
            } else {
                // throw exception forward
                throw e;
            }
        }
        return false;
    }

    private boolean bytecodeContainsClientWidgetAnnotation(
            Class<? extends Paintable> class1) {

        try {
            String name = class1.getName().replace('.', '/') + ".class";

            InputStream stream = class1.getClassLoader().getResourceAsStream(
                    name);
            BufferedReader bufferedReader = new BufferedReader(
                    new InputStreamReader(stream));
            try {
                String line;
                boolean atSourcefile = false;
                while ((line = bufferedReader.readLine()) != null) {
                    if (line.startsWith("SourceFile")) {
                        atSourcefile = true;
                    }
                    if (atSourcefile) {
                        if (line.contains("ClientWidget")) {
                            return true;
                        }
                    }
                    // TODO could optimize to quit at the end attribute
                }
            } catch (IOException e1) {
                logger.log(Level.SEVERE,
                        "An error occurred while finding widget mapping.", e1);
            } finally {
                try {
                    bufferedReader.close();
                } catch (IOException e1) {
                    logger.log(Level.SEVERE, "Could not close reader.", e1);

                }
            }
        } catch (Throwable t) {
            logger.log(Level.SEVERE,
                    "An error occurred while finding widget mapping.", t);
        }

        return false;
    }

    Collection<Class<? extends Paintable>> getUsedPaintableTypes() {
        return usedPaintableTypes;
    }

    public void addVariable(VariableOwner owner, String name,
            StreamVariable value) throws PaintException {
        String url = manager.getStreamVariableTargetUrl(owner, name, value);
        if (url != null) {
            addVariable(owner, name, url);
        } // else { //NOP this was just a cleanup by component }

    }

    /*
     * (non-Javadoc)
     *
     * @see com.vaadin.terminal.PaintTarget#isFullRepaint()
     */
    public boolean isFullRepaint() {
        return !cacheEnabled;
    }

}
TOP

Related Classes of com.vaadin.terminal.gwt.server.JsonPaintTarget$Variable

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.