Package org.apache.myfaces.custom.tree2

Source Code of org.apache.myfaces.custom.tree2.UITreeData$SavedState

/*
* Copyright 2005 The Apache Software Foundation.
*
* 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 org.apache.myfaces.custom.tree2;

import java.io.IOException;
import java.io.Serializable;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import javax.faces.application.FacesMessage;
import javax.faces.component.EditableValueHolder;
import javax.faces.component.NamingContainer;
import javax.faces.component.UIComponent;
import javax.faces.component.UIComponentBase;
import javax.faces.context.FacesContext;
import javax.faces.el.ValueBinding;
import javax.faces.event.AbortProcessingException;
import javax.faces.event.FacesEvent;
import javax.faces.event.FacesListener;
import javax.faces.event.PhaseId;

import org.apache.myfaces.util.MessageUtils;

/**
* TreeData is a {@link UIComponent} that supports binding data stored in a tree represented
* by a {@link TreeNode} instance.  During iterative processing over the tree nodes in the
* data model, the object for the current node is exposed as a request attribute under the key
* specified by the <code>var</code> property.  {@link javax.faces.render.Renderer}s of this
* component should use the appropriate facet to assist in rendering.
*
* @author Sean Schofield
* @author Hans Bergsten (Some code taken from an example in his O'Reilly JavaServer Faces book. Copied with permission)
* @version $Revision: 291921 $ $Date: 2005-09-27 08:08:36 -0400 (Tue, 27 Sep 2005) $
*/
public class UITreeData extends UIComponentBase implements NamingContainer
{

    public static final String COMPONENT_TYPE = "org.apache.myfaces.Tree2";
    public static final String COMPONENT_FAMILY = "org.apache.myfaces.HtmlTree2";
    private static final String DEFAULT_RENDERER_TYPE = "org.apache.myfaces.Tree2";
    private static final String MISSING_NODE = "org.apache.myfaces.tree2.MISSING_NODE";
    private static final int PROCESS_DECODES = 1;
    private static final int PROCESS_VALIDATORS = 2;
    private static final int PROCESS_UPDATES = 3;

    private TreeModel _model;
    private Object _value;
    private String _var;
    private String _nodeId;
    private Map _saved = new HashMap();

    private TreeState _restoredState = null;

    /**
     * Constructor
     */
    public UITreeData()
    {
        setRendererType(DEFAULT_RENDERER_TYPE);
    }


    // see superclass for documentation
    public String getFamily()
    {
        return COMPONENT_FAMILY;
    }

    //  see superclass for documentation
    public Object saveState(FacesContext context)
    {
        Object values[] = new Object[3];
        values[0] = super.saveState(context);
        values[1] = _var;

        TreeState t = getDataModel().getTreeState();
        if ( t == null)
        {
            // the model supplier has forgotten to return a valid state manager, but we need one
            t = new TreeStateBase();
        }

        // save the state with the component, unless it should explicitly not saved eg. session-scoped model and state
        values[2] = (t.isTransient()) ? null : t;
        return ((Object) (values));
    }


    // see superclass for documentation
    public void restoreState(FacesContext context, Object state)
    {
        Object values[] = (Object[]) state;
        super.restoreState(context, values[0]);

        _var = (String)values[1];
        _restoredState = (TreeState) values[2];
    }


    public void queueEvent(FacesEvent event)
    {
        super.queueEvent(new FacesEventWrapper(event, getNodeId(), this));
    }


    public void broadcast(FacesEvent event) throws AbortProcessingException
    {
        if (event instanceof FacesEventWrapper)
        {
            FacesEventWrapper childEvent = (FacesEventWrapper) event;
            String currNodeId = getNodeId();
            setNodeId(childEvent.getNodeId());
            FacesEvent nodeEvent = childEvent.getFacesEvent();
            nodeEvent.getComponent().broadcast(nodeEvent);
            setNodeId(currNodeId);
            return;
        }
        else if(event instanceof ToggleExpandedEvent)
        {
            ToggleExpandedEvent toggleEvent = (ToggleExpandedEvent) event;
            String currentNodeId = getNodeId();
            setNodeId(toggleEvent.getNodeId());
            toggleExpanded();
            setNodeId(currentNodeId);
        }
        else
        {
            super.broadcast(event);
            return;
        }
    }


    // see superclass for documentation
    public void processDecodes(FacesContext context)
    {
        if (context == null) throw new NullPointerException("context");
        if (!isRendered()) return;

        _model = null;
        _saved = new HashMap();
       
        setNodeId(null);
        decode(context);

        processNodes(context, PROCESS_DECODES, null, 0);

    }

    // see superclass for documentation
    public void processValidators(FacesContext context)
    {
        if (context == null) throw new NullPointerException("context");
        if (!isRendered()) return;

        processNodes(context, PROCESS_VALIDATORS, null, 0);

        setNodeId(null);
    }


    // see superclass for documentation
    public void processUpdates(FacesContext context)
    {
        if (context == null) throw new NullPointerException("context");
        if (!isRendered()) return;

        processNodes(context, PROCESS_UPDATES, null, 0);

        setNodeId(null);
    }

    // see superclass for documentation
    public String getClientId(FacesContext context)
    {
        String ownClientId = super.getClientId(context);
        if (_nodeId != null)
        {
            return ownClientId + NamingContainer.SEPARATOR_CHAR + _nodeId;
        } else
        {
            return ownClientId;
        }
    }

    // see superclass for documentation
    public void setValueBinding(String name, ValueBinding binding)
    {
        if ("value".equals(name))
        {
            _model = null;
        } else if ("nodeVar".equals(name) || "nodeId".equals(name) || "treeVar".equals(name))
        {
            throw new IllegalArgumentException("name " + name);
        }
        super.setValueBinding(name, binding);
    }

    // see superclass for documentation
    public void encodeBegin(FacesContext context) throws IOException
    {
        /**
         * The renderer will handle most of the encoding, but if there are any
         * error messages queued for the components (validation errors), we
         * do want to keep the saved state so that we can render the node with
         * the invalid value.
         */

        if (!keepSaved(context))
        {
            _saved = new HashMap();
        }

        // FIX for MYFACES-404
        // do not use the cached model the render phase
        _model = null;

        super.encodeBegin(context);
    }

    /**
     * Sets the value of the TreeData.
     *
     * @param value The new value
     */
    public void setValue(Object value)
    {
        _model = null;
        _value = value;
    }


    /**
     * Gets the value of the TreeData.
     *
     * @return The value
     */
    public Object getValue()
    {
        if (_value != null) return _value;
        ValueBinding vb = getValueBinding("value");
        return vb != null ? vb.getValue(getFacesContext()) : null;
    }

    /**
     * Set the request-scope attribute under which the data object for the current node wil be exposed
     * when iterating.
     *
     * @param var The new request-scope attribute name
     */
    public void setVar(String var)
    {
        _var = var;
    }


    /**
     * Return the request-scope attribute under which the data object for the current node will be exposed
     * when iterating. This property is not enabled for value binding expressions.
     *
     * @return The iterrator attribute
     */
    public String getVar()
    {
        return _var;
    }

    /**
     * Calls through to the {@link TreeModel} and returns the current {@link TreeNode} or <code>null</code>.
     *
     * @return The current node
     */
    public TreeNode getNode()
    {
        TreeModel model = getDataModel();

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

        return model.getNode();
    }


    public String getNodeId()
    {
        return _nodeId;
    }


    public void setNodeId(String nodeId)
    {
        saveDescendantState();

        _nodeId = nodeId;

        TreeModel model = getDataModel();
        if (model == null)
        {
            return;
        }

        try
        {
            model.setNodeId(nodeId);
        }
        catch (IndexOutOfBoundsException aob)
        {
            /**
             * This might happen if we are trying to process a commandLink for a node that node that no longer
             * exists.  Instead of allowing a RuntimeException to crash the application, we will add a warning
             * message so the user can optionally display the warning.  Also, we will allow the user to provide
             * their own value binding method to be called so they can handle it how they see fit.
             */
            FacesMessage message = MessageUtils.getMessage(MISSING_NODE, new String[] {nodeId});
            message.setSeverity(FacesMessage.SEVERITY_WARN);
            FacesContext.getCurrentInstance().addMessage(getId(), message);

            /** @todo call hook */
            /** @todo figure out whether or not to abort this method gracefully */
        }

        restoreDescendantState();

        Map requestMap = getFacesContext().getExternalContext().getRequestMap();

        if (_var != null)
        {
            if (nodeId == null)
            {
                requestMap.remove(_var);
            } else
            {
                requestMap.put(_var, getNode());
            }
        }
    }

    /**
     * Gets an array of String containing the ID's of all of the {@link TreeNode}s in the path to
     * the specified node.  The path information will be an array of <code>String</code> objects
     * representing node ID's. The array will starting with the ID of the root node and end with
     * the ID of the specified node.
     *
     * @param nodeId The id of the node for whom the path information is needed.
     * @return String[]
     */
    public String[] getPathInformation(String nodeId)
    {
        return getDataModel().getPathInformation(nodeId);
    }

    /**
     * Indicates whether or not the specified {@link TreeNode} is the last child in the <code>List</code>
     * of children.  If the node id provided corresponds to the root node, this returns <code>true</code>.
     *
     * @param nodeId The ID of the node to check
     * @return boolean
     */
    public boolean isLastChild(String nodeId)
    {
        return getDataModel().isLastChild(nodeId);
    }

    /**
     * Returns a previously cached {@link TreeModel}, if any, or sets the cache variable to either the
     * current value (if its a {@link TreeModel}) or to a new instance of {@link TreeModel} (if it's a
     * {@link TreeNode}) with the provided value object as the root node.
     *
     * @return TreeModel
     */
    public TreeModel getDataModel()
    {
        if (_model != null)
        {
            return _model;
        }

        Object value = getValue();
        if (value != null)
        {
            if (value instanceof TreeModel)
            {
                _model = (TreeModel) value;
            }
            else if (value instanceof TreeNode)
            {
              _model = new TreeModelBase((TreeNode) value);
            } else
            {
                throw new IllegalArgumentException("Value must be a TreeModel or TreeNode");
            }
        }

        if (_restoredState != null)
            _model.setTreeState(_restoredState); // set the restored state (if there is one) on the model

        return _model;
    }

    /**
     * Epands all nodes by default.
     * @param expandAll boolean
     */
    public void expandAll()
    {
        String currentNodeId = _nodeId;

        int kidId = 0;
        expandEverything(null, kidId++);
        setNodeId(currentNodeId);
    }

    /**
     * Expands all of the nodes in the specfied path.
     * @param nodePath The path to expand.
     */
    public void expandPath(String[] nodePath)
    {
        getDataModel().getTreeState().expandPath(nodePath);
    }

    /**
     * Expands all of the nodes in the specfied path.
     * @param nodePath The path to expand.
     */
    public void collapsePath(String[] nodePath)
    {
        getDataModel().getTreeState().collapsePath(nodePath);
    }

    /**
     * Private helper method that recursviely expands all of the nodes.
     *
     * @param parentId The id of the parent node (if applicable)
     * @param childCount The child number of the node to expand (will be incremented as you recurse.)
     */
    private void expandEverything(String parentId, int childCount)
    {
        String nodeId = (parentId != null) ? parentId + TreeModel.SEPARATOR + childCount : "0";
        setNodeId(nodeId);

        TreeNode node = getDataModel().getNode();

        if (!node.isLeaf() && !isNodeExpanded())
        {
            getDataModel().getTreeState().toggleExpanded(nodeId);
        }

        List children = getNode().getChildren();
        for (int kount=0; kount < children.size(); kount++)
        {
            expandEverything(nodeId, kount);
        }
    }


    private void processNodes(FacesContext context, int processAction, String parentId, int childLevel)
    {
        UIComponent facet = null;
        setNodeId(parentId != null ? parentId + NamingContainer.SEPARATOR_CHAR + childLevel : "0");
        TreeNode node = getNode();

        facet = getFacet(node.getType());

        if (facet == null)
        {
            throw new IllegalArgumentException("Unable to locate facet with the name: " + node.getType());
        }

        switch (processAction)
        {
            case PROCESS_DECODES:

                facet.processDecodes(context);
                break;

            case PROCESS_VALIDATORS:

                facet.processValidators(context);
                break;

            case PROCESS_UPDATES:

                facet.processUpdates(context);
                break;
        }
        if(isNodeExpanded())
        {
            processChildNodes(context, node, processAction);
        }
    }

    /**
     * Process the child nodes of the supplied parent {@link TreeNode}.  This method is protected so that
     * it can be overriden by a subclass that may want to control how child nodes are processed.
     *
     * @param context       FacesContext
     * @param parentNode    The parent node whose children are to be processed
     * @param processAction An <code>int</code> representing the type of action to process
     */
    protected void processChildNodes(FacesContext context, TreeNode parentNode, int processAction)
    {
        int kidId = 0;
        String currId = getNodeId();

        List children = parentNode.getChildren();

        for (int i = 0; i < children.size(); i++)
        {
            processNodes(context, processAction, currId, kidId++);
        }
    }

    /**
     * To support using input components for the nodes (e.g., input fields, checkboxes, and selection
     * lists) while still only using one set of components for all nodes, the state held by the components
     * for the current node must be saved for a new node is selected.
     */
    private void saveDescendantState()
    {
        FacesContext context = getFacesContext();
        Iterator i = getFacets().values().iterator();
        while (i.hasNext())
        {
            UIComponent facet = (UIComponent) i.next();
            saveDescendantState(facet, context);
        }
    }

    /**
     * Overloaded helper method for the no argument version of this method.
     *
     * @param component The component whose state needs to be saved
     * @param context   FacesContext
     */
    private void saveDescendantState(UIComponent component, FacesContext context)
    {
        if (component instanceof EditableValueHolder)
        {
            EditableValueHolder input = (EditableValueHolder) component;
            String clientId = component.getClientId(context);
            SavedState state = (SavedState) _saved.get(clientId);
            if (state == null)
            {
                state = new SavedState();
                _saved.put(clientId, state);
            }
            state.setValue(input.getLocalValue());
            state.setValid(input.isValid());
            state.setSubmittedValue(input.getSubmittedValue());
            state.setLocalValueSet(input.isLocalValueSet());
        }

        List kids = component.getChildren();
        for (int i = 0; i < kids.size(); i++)
        {
            saveDescendantState((UIComponent) kids.get(i), context);
        }
    }


    /**
     * Used to configure a new node with the state stored previously.
     */
    private void restoreDescendantState()
    {
        FacesContext context = getFacesContext();
        Iterator i = getFacets().values().iterator();
        while (i.hasNext())
        {
            UIComponent facet = (UIComponent) i.next();
            restoreDescendantState(facet, context);
        }
    }

    /**
     * Overloaded helper method for the no argument version of this method.
     *
     * @param component The component whose state needs to be restored
     * @param context   FacesContext
     */
    private void restoreDescendantState(UIComponent component, FacesContext context)
    {
        String id = component.getId();
        component.setId(id); // forces the cilent id to be reset

        if (component instanceof EditableValueHolder)
        {
            EditableValueHolder input = (EditableValueHolder) component;
            String clientId = component.getClientId(context);
            SavedState state = (SavedState) _saved.get(clientId);
            if (state == null)
            {
                state = new SavedState();
            }
            input.setValue(state.getValue());
            input.setValid(state.isValid());
            input.setSubmittedValue(state.getSubmittedValue());
            input.setLocalValueSet(state.isLocalValueSet());
        }

        List kids = component.getChildren();
        for (int i = 0; i < kids.size(); i++)
        {
            restoreDescendantState((UIComponent)kids.get(i), context);
        }
    }

    /**
     * A regular bean with accessor methods for all state variables.
     *
     * @author Sean Schofield
     * @author Hans Bergsten (Some code taken from an example in his O'Reilly JavaServer Faces book. Copied with permission)
     * @version $Revision: 291921 $ $Date: 2005-09-27 08:08:36 -0400 (Tue, 27 Sep 2005) $
     */
    private static class SavedState implements Serializable
    {
        private static final long serialVersionUID = 273343276957070557L;
        private Object submittedValue;
        private boolean valid = true;
        private Object value;
        private boolean localValueSet;

        Object getSubmittedValue()
        {
            return submittedValue;
        }

        void setSubmittedValue(Object submittedValue)
        {
            this.submittedValue = submittedValue;
        }

        boolean isValid()
        {
            return valid;
        }

        void setValid(boolean valid)
        {
            this.valid = valid;
        }

        Object getValue()
        {
            return value;
        }

        void setValue(Object value)
        {
            this.value = value;
        }

        boolean isLocalValueSet()
        {
            return localValueSet;
        }

        void setLocalValueSet(boolean localValueSet)
        {
            this.localValueSet = localValueSet;
        }
    }

    /**
     * Inner class used to wrap the original events produced by child components in the tree.
     * This will allow the tree to find the appropriate component later when its time to
     * broadcast the events to registered listeners.  Code is based on a similar private
     * class for UIData.
     */
    private static class FacesEventWrapper extends FacesEvent
    {
        private static final long serialVersionUID = -3056153249469828447L;
        private FacesEvent _wrappedFacesEvent;
        private String _nodeId;


        public FacesEventWrapper(FacesEvent facesEvent, String nodeId, UIComponent component)
        {
            super(component);
            _wrappedFacesEvent = facesEvent;
            _nodeId = nodeId;
        }


        public PhaseId getPhaseId()
        {
            return _wrappedFacesEvent.getPhaseId();
        }


        public void setPhaseId(PhaseId phaseId)
        {
            _wrappedFacesEvent.setPhaseId(phaseId);
        }


        public void queue()
        {
            _wrappedFacesEvent.queue();
        }


        public String toString()
        {
            return _wrappedFacesEvent.toString();
        }


        public boolean isAppropriateListener(FacesListener faceslistener)
        {
            // this event type is only intended for wrapping a real event
            return false;
        }


        public void processListener(FacesListener faceslistener)
        {
            throw new UnsupportedOperationException("This event type is only intended for wrapping a real event");
        }


        public FacesEvent getFacesEvent()
        {
            return _wrappedFacesEvent;
        }


        public String getNodeId()
        {
            return _nodeId;
        }
    }

    /**
     * Returns true if there is an error message queued for at least one of the nodes.
     *
     * @param context FacesContext
     * @return whether an error message is present
     */
    private boolean keepSaved(FacesContext context)
    {
        Iterator clientIds = _saved.keySet().iterator();
        while (clientIds.hasNext())
        {
            String clientId = (String) clientIds.next();
            Iterator messages = context.getMessages(clientId);
            while (messages.hasNext())
            {
                FacesMessage message = (FacesMessage) messages.next();
                if (message.getSeverity().compareTo(FacesMessage.SEVERITY_ERROR) >= 0)
                {
                    return true;
                }
            }
        }

        return false;
    }

    /**
     * Toggle the expanded state of the current node.
     */
    public void toggleExpanded()
    {
        getDataModel().getTreeState().toggleExpanded(getNodeId());
    }

    /**
     * Indicates whether or not the current {@link TreeNode} is expanded.
     * @return boolean
     */
    public boolean isNodeExpanded()
    {
        return getDataModel().getTreeState().isNodeExpanded(getNodeId());
    }

}
TOP

Related Classes of org.apache.myfaces.custom.tree2.UITreeData$SavedState

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.