org.metawidget.vaadin.ui.VaadinMetawidget.java Source code

Java tutorial

Introduction

Here is the source code for org.metawidget.vaadin.ui.VaadinMetawidget.java

Source

// Metawidget
//
// For historical reasons, this file is licensed under the LGPL
// (http://www.gnu.org/licenses/lgpl-2.1.html).
//
// Most other files in Metawidget are licensed under both the
// LGPL/EPL and a commercial license. See http://metawidget.org
// for details.

package org.metawidget.vaadin.ui;

import static org.metawidget.inspector.InspectionResultConstants.*;

import java.io.Serializable;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.MissingResourceException;
import java.util.ResourceBundle;

import org.metawidget.iface.MetawidgetException;
import org.metawidget.inspectionresultprocessor.iface.InspectionResultProcessor;
import org.metawidget.inspector.iface.Inspector;
import org.metawidget.layout.iface.Layout;
import org.metawidget.pipeline.w3c.W3CPipeline;
import org.metawidget.util.ArrayUtils;
import org.metawidget.util.ClassUtils;
import org.metawidget.util.CollectionUtils;
import org.metawidget.util.simple.PathUtils;
import org.metawidget.util.simple.PathUtils.TypeAndNames;
import org.metawidget.util.simple.StringUtils;
import org.metawidget.widgetbuilder.iface.WidgetBuilder;
import org.metawidget.widgetprocessor.iface.WidgetProcessor;
import org.w3c.dom.Element;

import com.vaadin.terminal.PaintException;
import com.vaadin.terminal.PaintTarget;
import com.vaadin.ui.AbstractComponent;
import com.vaadin.ui.Component;
import com.vaadin.ui.ComponentContainer;
import com.vaadin.ui.CustomComponent;

/**
 * Metawidget for Vaadin environments.
 *
 * @author Loghman Barari
 */

public class VaadinMetawidget extends CustomComponent {

    //
    // Private members
    //

    private Object mToInspect;

    private String mPath;

    private ResourceBundle mBundle;

    private boolean mNeedToBuildWidgets;

    private Element mLastInspection;

    private boolean mIgnoreAddRemove;

    /**
     * List of existing, manually added components.
     * <p>
     * This is a List, not a Set, so that mExistingUnusedComponents (which is initialized from it)
     * is consistent.
     */

    private List<AbstractComponent> mExistingComponents = CollectionUtils.newArrayList();

    /**
     * List of existing, manually added, but unused by Metawidget components.
     * <p>
     * This is a List, not a Set, for consistency during endBuild.
     */

    private List<AbstractComponent> mExistingUnusedComponents;

    private Map<Object, Facet> mFacets = CollectionUtils.newHashMap();

    private Map<Object, Object> mClientProperties;

    /* package private */Pipeline mPipeline;

    //
    // Constructor
    //

    public VaadinMetawidget() {

        mPipeline = newPipeline();
    }

    //
    // Public methods
    //

    /**
     * Sets the Object to inspect.
     * <p>
     * If <code>setPath</code> has not been set, or points to a previous <code>setToInspect</code>,
     * sets it to point to the given Object.
     */

    public void setToInspect(Object toInspect) {

        updateToInspectWithoutInvalidate(toInspect);
        invalidateInspection();
    }

    /**
     * Gets the Object being inspected.
     * <p>
     * Exposed for binding implementations.
     *
     * @return the object. Note this return type uses generics, so as to not require a cast by the
     *         caller (eg. <code>Person p = getToInspect()</code>)
     */

    @SuppressWarnings("unchecked")
    public <T> T getToInspect() {

        return (T) mToInspect;
    }

    /**
     * Sets the path to be inspected.
     */

    public void setPath(String path) {

        mPath = path;
        invalidateInspection();
    }

    public String getPath() {

        return mPath;
    }

    public void setConfig(String config) {

        mPipeline.setConfig(config);
        invalidateInspection();
    }

    public void setInspector(Inspector inspector) {

        mPipeline.setInspector(inspector);
        invalidateInspection();
    }

    /**
     * Useful for WidgetBuilders to perform nested inspections (eg. for
     * Collections).
     */

    public String inspect(Object toInspect, String type, String... names) {

        return mPipeline.inspect(toInspect, type, names);
    }

    public void addInspectionResultProcessor(
            InspectionResultProcessor<VaadinMetawidget> inspectionResultProcessor) {

        mPipeline.addInspectionResultProcessor(inspectionResultProcessor);
        invalidateInspection();
    }

    public void removeInspectionResultProcessor(
            InspectionResultProcessor<VaadinMetawidget> inspectionResultProcessor) {

        mPipeline.removeInspectionResultProcessor(inspectionResultProcessor);
        invalidateInspection();
    }

    public void setInspectionResultProcessors(
            InspectionResultProcessor<VaadinMetawidget>... inspectionResultProcessors) {

        mPipeline.setInspectionResultProcessors(inspectionResultProcessors);
        invalidateInspection();
    }

    public void setWidgetBuilder(WidgetBuilder<Component, VaadinMetawidget> widgetBuilder) {

        mPipeline.setWidgetBuilder(widgetBuilder);
        invalidateWidgets();
    }

    public void addWidgetProcessor(WidgetProcessor<Component, VaadinMetawidget> widgetProcessor) {

        mPipeline.addWidgetProcessor(widgetProcessor);
        invalidateWidgets();
    }

    public void removeWidgetProcessor(WidgetProcessor<Component, VaadinMetawidget> widgetProcessor) {

        mPipeline.removeWidgetProcessor(widgetProcessor);
        invalidateWidgets();
    }

    public void setWidgetProcessors(WidgetProcessor<Component, VaadinMetawidget>... widgetProcessors) {

        mPipeline.setWidgetProcessors(widgetProcessors);
        invalidateWidgets();
    }

    public <T> T getWidgetProcessor(Class<T> widgetProcessorClass) {

        buildWidgets();
        return mPipeline.getWidgetProcessor(widgetProcessorClass);
    }

    /**
     * Set the layout for this Metawidget.
     */

    public void setLayout(Layout<Component, ComponentContainer, VaadinMetawidget> layout) {

        mPipeline.setLayout(layout);
        invalidateWidgets();
    }

    public ResourceBundle getBundle() {

        return mBundle;
    }

    public void setBundle(ResourceBundle bundle) {

        mBundle = bundle;
        invalidateWidgets();
    }

    /**
     * Returns a label for the given set of attributes.
     * <p>
     * The label is determined using the following algorithm:
     * <p>
     * <ul>
     * <li>if <tt>attributes.get( "label" )</tt> exists...
     * <ul>
     * <li><tt>attributes.get( "label" )</tt> is camel-cased and used as a lookup into
     * <tt>getLocalizedKey( camelCasedLabel )</tt>. This means developers can initially build their
     * UIs without worrying about localization, then turn it on later</li>
     * <li>if no such lookup exists, return <tt>attributes.get( "label" )</tt>
     * </ul>
     * </li>
     * <li>if <tt>attributes.get( "label" )</tt> does not exist...
     * <ul>
     * <li><tt>attributes.get( "name" )</tt> is used as a lookup into
     * <tt>getLocalizedKey( name )</tt></li>
     * <li>if no such lookup exists, return <tt>attributes.get( "name" )</tt>
     * </ul>
     * </li>
     * </ul>
     */

    public String getLabelString(Map<String, String> attributes) {

        if (attributes == null) {
            return "";
        }

        // Explicit label

        String label = attributes.get(LABEL);

        if (label != null) {
            // (may be forced blank)

            if ("".equals(label)) {
                return null;
            }

            // (localize if possible)

            String localized = getLocalizedKey(StringUtils.camelCase(label));

            if (localized != null) {
                return localized.trim();
            }

            return label.trim();
        }

        // Default name

        String name = attributes.get(NAME);

        if (name != null) {

            // (localize if possible)

            String localized = getLocalizedKey(name);

            if (localized != null) {
                return localized.trim();
            }

            return StringUtils.uncamelCase(name);
        }

        return "";
    }

    /**
     * @return null if no bundle, ???key??? if bundle is missing a key
     */

    public String getLocalizedKey(String key) {

        if (mBundle == null) {
            return null;
        }

        try {
            return mBundle.getString(key);
        } catch (MissingResourceException e) {
            return StringUtils.RESOURCE_KEY_NOT_FOUND_PREFIX + key + StringUtils.RESOURCE_KEY_NOT_FOUND_SUFFIX;
        }
    }

    @Override
    public boolean isReadOnly() {

        return mPipeline.isReadOnly();
    }

    @Override
    public void setReadOnly(boolean readOnly) {

        if (mPipeline.isReadOnly() == readOnly) {
            return;
        }

        mPipeline.setReadOnly(readOnly);
        invalidateWidgets();
    }

    public int getMaximumInspectionDepth() {

        return mPipeline.getMaximumInspectionDepth();
    }

    public void setMaximumInspectionDepth(int maximumInspectionDepth) {

        mPipeline.setMaximumInspectionDepth(maximumInspectionDepth);
        invalidateWidgets();
    }

    /**
     * Fetch a list of <code>AbstractComponents</code> that were added manually,
     * and have so far not been used.
     * <p>
     * <strong>This is an internal API exposed for OverriddenWidgetBuilder. Clients should not call
     * it directly.</strong>
     */

    public List<AbstractComponent> fetchExistingUnusedComponents() {

        return mExistingUnusedComponents;
    }

    //
    // The following methods all kick off buildWidgets() if necessary
    //

    @Override
    public int getComponentCount() {

        buildWidgets();
        return super.getComponentCount();
    }

    @Override
    public Iterator<Component> getComponentIterator() {

        buildWidgets();
        return super.getComponentIterator();
    }

    /**
     * Finds the Component with the given name.
     */

    @SuppressWarnings("unchecked")
    public <T extends Component> T getComponent(String... names) {

        if (names == null || names.length == 0) {
            return null;
        }

        Component topComponent = this;

        for (int loop = 0, length = names.length; loop < length; loop++) {
            String name = names[loop];

            // May need building 'just in time' if we are calling getComponent
            // immediately after a 'setToInspect'. See
            // VaadinMetawidgetTest.testNestedWithManualInspector

            if (topComponent instanceof VaadinMetawidget) {
                ((VaadinMetawidget) topComponent).buildWidgets();
            }

            // Try to find a component

            if (topComponent instanceof ComponentContainer) {
                topComponent = getComponent((ComponentContainer) topComponent, name);
            } else {
                topComponent = null;
            }

            if (loop == length - 1) {
                return (T) topComponent;
            }

            if (topComponent == null) {
                throw MetawidgetException.newException(
                        "No such component '" + name + "' of '" + ArrayUtils.toString(names, "', '") + "'");
            }
        }

        return (T) topComponent;
    }

    public Facet getFacet(String name) {

        buildWidgets();

        return mFacets.get(name);
    }

    /**
     * Named after <code>Panel.getContent</code>.
     */

    @SuppressWarnings("unchecked")
    public <C extends Component> C getContent() {

        buildWidgets();

        return (C) getCompositionRoot();
    }

    @Override
    public void addComponent(Component component) {

        if (!mIgnoreAddRemove) {
            invalidateWidgets();

            // Don't fall through to super.addImpl for facets. Tuck them away
            // in mFacets instead. Some layouts may never use them, and
            // others (eg. MigLayout) don't like adding components
            // without constraints

            AbstractComponent abstractComponent = (AbstractComponent) component;

            if (component instanceof Facet) {
                mFacets.put(abstractComponent.getData(), (Facet) component);
                return;
            }

            mExistingComponents.add(abstractComponent);

        } else {
            setCompositionRoot(component);
        }
    }

    @Override
    public void removeComponent(Component component) {

        if (!mIgnoreAddRemove) {
            invalidateWidgets();

            if (component instanceof Facet) {
                mFacets.remove(((Facet) component).getData());
            } else {
                mExistingComponents.remove(component);
            }
        }
    }

    @Override
    public void paintContent(PaintTarget target) throws PaintException {

        buildWidgets();

        super.paintContent(target);
    }

    /**
     * Storage area for WidgetProcessors, Layouts, and other stateless clients.
     */

    public void putClientProperty(Object key, Object value) {

        if (mClientProperties == null) {
            mClientProperties = new HashMap<Object, Object>();
        }

        mClientProperties.put(key, value);
    }

    /**
     * Storage area for WidgetProcessors, Layouts, and other stateless clients.
     */

    @SuppressWarnings("unchecked")
    public <T> T getClientProperty(Object key) {

        if (mClientProperties == null) {
            return null;
        }

        return (T) mClientProperties.get(key);
    }

    //
    // Protected methods
    //

    /**
     * Instantiate the Pipeline used by this Metawidget.
     * <p>
     * Subclasses wishing to use their own Pipeline should override this method to instantiate their
     * version.
     */

    protected Pipeline newPipeline() {

        return new Pipeline();
    }

    protected String getDefaultConfiguration() {

        return ClassUtils.getPackagesAsFolderNames(VaadinMetawidget.class) + "/metawidget-vaadin-default.xml";
    }

    /**
     * Invalidates the current inspection result (if any) <em>and</em> invalidates the widgets.
     * <p>
     * As an optimisation we only invalidate the widgets, not the entire inspection result, for some
     * operations (such as adding/removing stubs, changing read-only etc.)
     */

    protected void invalidateInspection() {

        mLastInspection = null;
        invalidateWidgets();
    }

    /**
     * Invalidates the widgets.
     */

    protected void invalidateWidgets() {

        if (mNeedToBuildWidgets) {
            return;
        }

        // Prepare to build widgets

        mNeedToBuildWidgets = true;

        if (mClientProperties != null) {
            mClientProperties.clear();
        }

        // Call repaint here, rather than just 'invalidate', for scenarios like
        // doing a 'remove' of a button that masks a Metawidget

        this.requestRepaint();
    }

    protected void buildWidgets() {

        // No need to build?

        if (!mNeedToBuildWidgets) {
            return;
        }

        mPipeline.configureOnce();

        mNeedToBuildWidgets = false;
        mIgnoreAddRemove = true;

        try {
            if (mLastInspection == null) {
                mLastInspection = inspect();
            }

            if (mPath != null) {
                mPipeline.buildWidgets(mLastInspection);
            }
        } catch (Exception e) {
            throw MetawidgetException.newException(e);
        } finally {
            mIgnoreAddRemove = false;
        }
    }

    protected void startBuild() {

        mExistingUnusedComponents = CollectionUtils.newArrayList(mExistingComponents);
    }

    /**
     * @param elementName
     *            XML node name of the business field. Typically 'entity',
     *            'property' or 'action'. Never null
     */

    protected void layoutWidget(Component component, String elementName, Map<String, String> attributes) {

        // Set the name of the component.
        //
        // Note: we haven't split this out into a separate WidgetProcessor, because other methods
        // like getValue/setValue/getComponent( String... names ) rely on it

        ((AbstractComponent) component).setData(attributes.get(NAME));

        // Remove, then re-add to layout (to re-order the component)

        removeComponent(component);

        // Look up any additional attributes

        Map<String, String> additionalAttributes = mPipeline.getAdditionalAttributes(component);

        if (additionalAttributes != null) {
            attributes.putAll(additionalAttributes);
        }
    }

    protected void endBuild() {

        if (mExistingUnusedComponents != null) {
            for (Component componentExisting : mExistingUnusedComponents) {
                // Unused facets don't count

                if (componentExisting instanceof Facet) {
                    continue;
                }

                // Manually created components default to no section

                Map<String, String> attributes = CollectionUtils.newHashMap();
                attributes.put(SECTION, "");

                mPipeline.layoutWidget(componentExisting, PROPERTY, attributes);
            }
        }
    }

    protected void initNestedMetawidget(VaadinMetawidget nestedMetawidget, Map<String, String> attributes) {

        // Don't copy setConfig(). Instead, copy runtime values

        mPipeline.initNestedPipeline(nestedMetawidget.mPipeline, attributes);

        nestedMetawidget.setPath(mPath + StringUtils.SEPARATOR_FORWARD_SLASH_CHAR + attributes.get(NAME));
        nestedMetawidget.setBundle(mBundle);
        nestedMetawidget.setToInspect(mToInspect);
    }

    //
    // Private methods
    //

    /**
     * Updates the Object to inspect, without invalidating the previous
     * inspection results.
     */

    private void updateToInspectWithoutInvalidate(Object toInspect) {

        if (mToInspect == null) {
            if (mPath == null && toInspect != null) {
                mPath = toInspect.getClass().getName();
            }
        } else if (mToInspect.getClass().getName().equals(mPath)) {
            if (toInspect == null) {
                mPath = null;
            } else {
                mPath = toInspect.getClass().getName();
            }
        }

        mToInspect = toInspect;
    }

    private Element inspect() {

        if (mPath == null) {
            return null;
        }

        TypeAndNames typeAndNames = PathUtils.parsePath(mPath);

        return mPipeline.inspectAsDom(mToInspect, typeAndNames.getType(), typeAndNames.getNamesAsArray());
    }

    private Component getComponent(ComponentContainer container, String name) {

        Iterator<Component> iterator = container.getComponentIterator();

        while (iterator.hasNext()) {

            AbstractComponent childComponent = (AbstractComponent) iterator.next();

            // Drill into unnamed containers

            if (childComponent.getData() == null && childComponent instanceof ComponentContainer) {
                childComponent = (AbstractComponent) getComponent((ComponentContainer) childComponent, name);

                if (childComponent != null) {
                    return childComponent;
                }

                continue;
            }

            // Match by name

            if (name.equals(childComponent.getData())) {
                return childComponent;
            }
        }

        // Not found

        return null;
    }

    //
    // Inner class
    //

    protected class Pipeline extends W3CPipeline<Component, ComponentContainer, VaadinMetawidget>
            implements Serializable {

        //
        // Protected methods
        //

        @Override
        protected VaadinMetawidget getPipelineOwner() {

            return VaadinMetawidget.this;
        }

        @Override
        protected String getDefaultConfiguration() {

            return VaadinMetawidget.this.getDefaultConfiguration();
        }

        @Override
        protected void startBuild() {

            VaadinMetawidget.this.startBuild();
            super.startBuild();
        }

        @Override
        protected void layoutWidget(Component component, String elementName, Map<String, String> attributes) {

            VaadinMetawidget.this.layoutWidget(component, elementName, attributes);
            super.layoutWidget(component, elementName, attributes);
        }

        @Override
        protected Map<String, String> getAdditionalAttributes(Component component) {

            if (component instanceof Stub) {
                return ((Stub) component).getAttributes();
            }

            return null;
        }

        @Override
        public VaadinMetawidget buildNestedMetawidget(Map<String, String> attributes) throws Exception {

            if (TRUE.equals(attributes.get(HIDDEN))) {
                return null;
            }

            VaadinMetawidget nestedMetawidget = VaadinMetawidget.this.getClass().newInstance();
            VaadinMetawidget.this.initNestedMetawidget(nestedMetawidget, attributes);

            return nestedMetawidget;
        }

        @Override
        protected void endBuild() {

            VaadinMetawidget.this.endBuild();
            super.endBuild();
        }
    }
}