de.betterform.xml.xforms.ui.Selector.java Source code

Java tutorial

Introduction

Here is the source code for de.betterform.xml.xforms.ui.Selector.java

Source

/*
 * Copyright (c) 2012. betterFORM Project - http://www.betterform.de
 * Licensed under the terms of BSD License
 */

package de.betterform.xml.xforms.ui;

import de.betterform.xml.dom.DOMComparator;
import de.betterform.xml.dom.DOMUtil;
import de.betterform.xml.events.BetterFormEventNames;
import de.betterform.xml.events.DOMEventNames;
import de.betterform.xml.events.XFormsEventNames;
import de.betterform.xml.events.XMLEvent;
import de.betterform.xml.ns.NamespaceConstants;
import de.betterform.xml.xforms.XFormsConstants;
import de.betterform.xml.xforms.exception.XFormsBindingException;
import de.betterform.xml.xforms.exception.XFormsException;
import de.betterform.xml.xforms.model.Instance;
import de.betterform.xml.xforms.model.Model;
import de.betterform.xml.xpath.impl.saxon.XPathCache;
import net.sf.saxon.om.NodeInfo;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.w3c.dom.Attr;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.events.Event;
import org.w3c.dom.events.EventTarget;

import java.util.*;

/**
 * Implementation of both <b>8.1.10 The select Element</b> and <b>8.1.11 The
 * select1 Element</b>.
 *
 * @author Ulrich Nicolas Liss&eacute;
 * @author Lars Windauer
 * @version $Id: Selector.java 3510 2008-08-31 14:39:56Z lars $
 */
public class Selector extends AbstractFormControl {
    private static final Log LOGGER = LogFactory.getLog(Selector.class);
    private boolean copySkeleton = false;
    private boolean multiple;
    private final Map extendedPrefixMapping;
    private boolean selectHasCopyChilds = false;
    private boolean isOutOfRange = false;

    /**
     * Creates a new selector element handler.
     *
     * @param element the host document element.
     * @param model the context model.
     */
    public Selector(Element element, Model model) {
        super(element, model);

        extendedPrefixMapping = new HashMap(prefixMapping);
        extendedPrefixMapping.put(NamespaceConstants.BETTERFORM_PREFIX, NamespaceConstants.BETTERFORM_NS);
        extendedPrefixMapping.put(NamespaceConstants.XFORMS_PREFIX, NamespaceConstants.XFORMS_NS);
        this.container.getXMLEventService().registerDefaultAction(this.target, DOMEventNames.ACTIVATE, this);
    }

    @Override
    public void dispose() throws XFormsException {
        this.container.getXMLEventService().deregisterDefaultAction(this.target, DOMEventNames.ACTIVATE, this);
        super.dispose();
    }

    @Override
    public void performDefault(Event event) {
        super.performDefault(event);
        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug("Selector: perform default event:" + event.getType());
        }
        XMLEvent ev = (XMLEvent) event;
        String contextInfo = (String) ev.getContextInfo().get("context-info");
        ArrayList list = new ArrayList();
        if (contextInfo != null) {
            StringTokenizer tokens = new StringTokenizer(contextInfo, ";");
            while (tokens.hasMoreTokens()) {
                list.add(tokens.nextElement());
            }
        }
        String itemId;
        Item item;

        Iterator iterator = null;
        List elemContext = de.betterform.xml.xpath.impl.saxon.XPathUtil.getElementContext(getElement(),
                container.getProcessor().getBaseURI());
        String xformsPrefix = null;
        if (this.xformsPrefix != null) {
            xformsPrefix = this.xformsPrefix;
        } else {
            xformsPrefix = NamespaceConstants.XFORMS_PREFIX;
        }

        String xpath = "(" + xformsPrefix + ":" + XFormsConstants.ITEM + " | *//" + xformsPrefix + ":"
                + XFormsConstants.ITEM + "[not(ancestor::" + NamespaceConstants.BETTERFORM_PREFIX + ":data)])/@id";

        try {
            iterator = XPathCache.getInstance()
                    .evaluate(elemContext, 1, xpath, extendedPrefixMapping, xpathFunctionContext).iterator();
        } catch (XFormsException e) {
            LOGGER.error("Error evaluating xpath " + xpath);
        }

        List<EventTarget> selectList = new ArrayList<EventTarget>();
        List<EventTarget> deselectList = new ArrayList<EventTarget>();

        while (iterator.hasNext()) {
            itemId = ((NodeInfo) iterator.next()).getStringValue();
            item = (Item) this.container.lookup(itemId);

            if (list.contains(item.getId()) && !item.isSelected()) {
                item.select();
                selectList.add(item.getTarget());
            } else if (!list.contains(item.getId()) && item.isSelected()) {
                item.deselect();
                deselectList.add(item.getTarget());
            }
        }

        for (EventTarget evt : deselectList) {
            try {
                this.container.dispatch(evt, XFormsEventNames.DESELECT, null);
            } catch (XFormsException e) {
                LOGGER.error("Error dispatching " + XFormsEventNames.DESELECT + "  to target " + evt.toString());
            }
        }
        deselectList.clear();

        for (EventTarget evt : selectList) {
            try {
                this.container.dispatch(evt, XFormsEventNames.SELECT, null);
            } catch (XFormsException e) {
                LOGGER.error("Error dispatching " + XFormsEventNames.SELECT + "  to target " + evt.toString());
            }
        }
        selectList.clear();
    }

    /**
     * Checks wether this is a multiple selector or not.
     *
     * @return <code>true</code> if this is a multiple selector, otherwise
     *         <code>false</code>.
     */
    public boolean isMultiple() {
        return this.multiple;
    }

    /**
     * Specifies the behaviour of this selector.
     *
     * @param multiple specifies wether to allow multiple selections.
     */
    public void setMultiple(boolean multiple) {
        this.multiple = multiple;
    }

    public List getNodeset() {
        try {
            updateXPathContext();
        } catch (XFormsException e) {
            LOGGER.error("Could not update XPathContext for Selector " + this.getBindingId());
        }
        return nodeset;
    }

    /**
     * Sets the value of this form control.
     * <p/>
     * The bound instance data is updated and the event sequence for this
     * control is executed. Event sequences are described in Chapter 4.6 of
     * XForms 1.0 Recommendation.
     *
     * @param value the value to be set.
     */
    public void setValue(String value) throws XFormsException {
        if (hasBindingExpression()) {
            //test for an xforms:copy id as the value
            String values[] = value.split(" ");
            de.betterform.xml.xforms.XFormsElement xfe = null;
            if (values.length == 1) {
                xfe = container.lookup(value);
            } else {
                xfe = container.lookup(values[0]);
            }

            if (xfe != null) {
                if (xfe instanceof Copy) {

                    //found an xforms:copy
                    DOMUtil.removeAllChildren(
                            (Node) model.getInstance(getInstanceId()).getModelItem(getInstanceNode()).getNode());

                    //copy the node from each selected xforms:copy into the instance
                    for (int i = 0; i < values.length; i++) {
                        Copy xfCopy = (Copy) xfe;
                        Object xfCopyInstanceNode = xfCopy.getInstanceNode();
                        Instance instance = model.getInstance(getInstanceId());
                        Node instanceNode = getInstanceNode();
                        if (instanceNode != null && xfCopyInstanceNode != null) {
                            try {
                                if (xfCopyInstanceNode instanceof Element) {
                                    instance.setNode(instanceNode, (Element) xfCopyInstanceNode);
                                } else if (xfCopyInstanceNode instanceof Attr) {
                                    instance.insertNode(instanceNode, (Attr) xfCopyInstanceNode, null);
                                } else {

                                }
                            } catch (Exception e) {
                                this.container.dispatch(this.getTarget(), XFormsEventNames.BINDING_EXCEPTION, null);
                                throw new XFormsBindingException("Node to copy does not exist", e, this.getTarget(),
                                        null);

                            }
                        } else {
                            throw new XFormsException("error during Selector.setValue: instance: " + instance
                                    + " node to copy: " + xfCopyInstanceNode);
                        }

                        dispatchSelectionWithoutValueChange(xfCopyInstanceNode);

                        if (i + 1 < values.length) {
                            xfe = container.lookup(values[i + 1]);
                            if (!(xfe instanceof Copy)) {
                                throw new XFormsException(
                                        "Control with the id = '" + value + "' is not an xforms:copy");
                            }
                        }
                    }
                } else {
                    setControlValue(value);
                    LOGGER.warn("Control with the id = '" + value + "' is not an xforms:copy");
                }
            } else {

                if (isMultiple() && this.selectHasCopyChilds) {
                    Node instanceNode = getInstanceNode();
                    DOMUtil.removeAllChildren(instanceNode);
                } else {
                    setControlValue(value);
                }
            }
            dispatchValueChangeSequence();
        }
    }

    private void setControlValue(String value) throws XFormsException {
        //standard string value selection
        dispatchSelectionWithoutValueChange(value);
        setNodeValue(value);

    }

    // lifecycle methods

    /**
     * Performs element init.
     *
     * @throws XFormsException if any error occurred during init.
     */
    public void init() throws XFormsException {
        if (getLogger().isTraceEnabled()) {
            getLogger().trace(this + " init");
        }
        // DOMUtil.prettyPrintDOM(this.element);
        if (this.element.hasAttribute("copySkeleton")) {
            this.copySkeleton = true;
        }
        super.init();
        initializeSelection();
        // initializeActions();
    }

    /**
     * Performs element update.
     *
     * @throws XFormsException if any error occurred during update.
     */
    public void refresh() throws XFormsException {
        super.refresh();
        updateSelection();
    }

    // lifecycle template methods

    /**
     * Initializes all items' selection state.
     * <p/>
     * The selection state of all items is set according to the bound instance
     * value of this select.
     */
    protected void initializeSelection() throws XFormsException {
        if (hasBindingExpression()) {
            setSelection(true, false, getValue());
        }
    }

    /**
     * Updates all items' selection state.
     * <p/>
     * The selection state of any items which is not set according to the bound
     * instance value of this select is updated.
     */
    protected void updateSelection() throws XFormsException {
        if (hasBindingExpression()) {
            setSelection(false, false, getValue());
        }
    }

    /**
     * Updates all items' selection state and dispatches the appropriate
     * <code>xforms-select</code> and <code>xforms-deselect events</code>.
     * <p/>
     * The selection state of any items which is not set according to the bound
     * instance value of this select is updated.
     *
     * @param value the value used to compute selection states.
     */
    protected void dispatchSelectionWithoutValueChange(Object value) throws XFormsException {
        if (hasBindingExpression()) {
            setSelection(false, true, value);
        }
    }

    // template methods

    /**
     * Returns the logger object.
     *
     * @return the logger object.
     */
    protected Log getLogger() {
        return LOGGER;
    }

    // helper

    private void setSelection(boolean force, boolean dispatch, Object value) throws XFormsException {

        Item item;
        String itemId;
        boolean selectable = true;
        List elemContext = de.betterform.xml.xpath.impl.saxon.XPathUtil.getElementContext(getElement(),
                container.getProcessor().getBaseURI());

        String xfNamespacePrefix = this.xformsPrefix;
        if (xfNamespacePrefix == null) {
            xfNamespacePrefix = NamespaceConstants.XFORMS_PREFIX;
        }
        String xPath = "(" + xfNamespacePrefix + ":" + XFormsConstants.ITEM + " | *//" + xfNamespacePrefix + ":"
                + XFormsConstants.ITEM + "[not(ancestor::" + NamespaceConstants.BETTERFORM_PREFIX + ":data)])/@id";
        List items = XPathCache.getInstance().evaluate(elemContext, 1, xPath, extendedPrefixMapping,
                xpathFunctionContext);
        Iterator iterator = items.iterator();
        Boolean inRange = false;

        while (iterator.hasNext()) {
            itemId = ((NodeInfo) iterator.next()).getStringValue();
            item = (Item) this.container.lookup(itemId);

            if (item.hasCopyChild()) {
                this.selectHasCopyChilds = true;
            }
            if (selectable && isInRange(value, item.getValue())) {
                if (force || !item.isSelected()) {
                    if (getLogger().isTraceEnabled()) {
                        getLogger().trace(this + " selecting item " + itemId);
                    }

                    item.select();

                    if (item.hasCopyChild()) {
                        Map map = new HashMap();
                        map.put("copyItem", "true");
                        map.put("selectedItem", item.getId());
                        this.container.dispatch(this.getTarget(), BetterFormEventNames.STATE_CHANGED, map);
                    }
                }
                inRange = true;
                // allow only one first selection for non-multiple selectors
                selectable = isMultiple();
            } else {
                if (force || item.isSelected()) {
                    if (getLogger().isTraceEnabled()) {
                        getLogger().trace(this + " deselecting item " + itemId);
                    }
                    item.deselect();
                }
            }

        }

        if (!inRange) {
            this.isOutOfRange = true;
            this.container.dispatch(this.target, XFormsEventNames.OUT_OF_RANGE, null);
        } else if (this.isOutOfRange) {
            this.isOutOfRange = false;
            this.container.dispatch(this.target, XFormsEventNames.IN_RANGE, null);
        }
    }

    private boolean isInRange(Object boundValue, Object itemValue) {
        //todo: Joern suggests that this function could be simplified using DOMComparator - Adam

        if (boundValue == null || itemValue == null) {
            return false;
        }

        if (boundValue == itemValue) {
            return true;
        }

        if (boundValue instanceof String && itemValue instanceof String) {

            if (isMultiple()) {
                String bound = " " + (String) boundValue + " ";
                String item = " " + (String) itemValue + " ";
                return bound.indexOf(item) > -1;
            }
            return boundValue.equals(itemValue);
        }

        if (boundValue instanceof Element && itemValue instanceof Element) {
            DOMComparator domComparator = new DOMComparator();

            if (DOMUtil.hasElementChildren((Element) boundValue)) {
                List boundChildElements = DOMUtil.getChildElements((Element) boundValue);

                for (Iterator itBoundChildElements = boundChildElements.iterator(); itBoundChildElements
                        .hasNext();) {
                    Element childElement = (Element) itBoundChildElements.next();
                    if (domComparator.compare(childElement, (Element) itemValue)) {
                        return true;
                    }
                }
            } else {
                if (domComparator.compare((Element) boundValue, (Element) itemValue)) {
                    return true;
                }
            }
            if (copySkeleton) {
                return compareSkeletons((Element) itemValue, (Element) boundValue);
            }
        }
        return false;
    }

    private boolean compareSkeletons(Element itemValue, Element boundValue) {
        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug("Compare Selector Skeleton itemValue: " + itemValue + " boundValue: " + boundValue);
            /*
                        LOGGER.debug("Compare Selector Skeleton START");
                        DOMUtil.prettyPrintDOM(itemValue);
                        LOGGER.debug("\nPrototype Skeleton END");
                
                        LOGGER.debug("\nData Prototype: START");
                        DOMUtil.prettyPrintDOM(boundValue);
                        LOGGER.debug("\nData Prototype: END");
            */

        }
        DOMComparator comparator = new DOMComparator();

        List boundChildElements = DOMUtil.getChildElements(boundValue);
        for (Iterator itBoundChildElements = boundChildElements.iterator(); itBoundChildElements.hasNext();) {
            Element childElement = (Element) itBoundChildElements.next();
            if (comparator.compareSkeleton(itemValue, childElement)) {
                return true;
            }
        }
        return false;
    }
}
// end of class