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

Java tutorial

Introduction

Here is the source code for de.betterform.xml.xforms.ui.AbstractFormControl.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.config.Config;
import de.betterform.xml.dom.DOMUtil;
import de.betterform.xml.events.DefaultAction;
import de.betterform.xml.events.XFormsEventNames;
import de.betterform.xml.ns.NamespaceConstants;
import de.betterform.xml.xforms.XFormsProcessorImpl;
import de.betterform.xml.xforms.action.UpdateHandler;
import de.betterform.xml.xforms.exception.XFormsException;
import de.betterform.xml.xforms.model.Model;
import de.betterform.xml.xforms.model.ModelItem;
import de.betterform.xml.xforms.ui.state.BoundElementState;
import de.betterform.xml.xforms.ui.state.UIElementStateUtil;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.events.Event;

import java.math.BigDecimal;
import java.text.*;
import java.util.Date;
import java.util.InputMismatchException;
import java.util.Locale;

/**
 * Base class for all form controls.
 *
 * @author Joern Turner
 * @author Ulrich Nicolas Lissé
 * @version $Id: AbstractFormControl.java 3506 2008-08-29 15:51:38Z lars $
 */
public abstract class AbstractFormControl extends BindingElement implements DefaultAction {
    private static Log LOGGER = LogFactory.getLog(AbstractFormControl.class);

    /**
     * Creates a new abstract form control.
     *
     * @param element the host document element.
     * @param model the context model.
     */
    public AbstractFormControl(Element element, Model model) {
        super(element, model);
    }

    // todo: extract interface
    // form control methods

    /**
     * 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 abstract void setValue(String value) throws XFormsException;

    /**
     * Returns the current value of this form control.
     *
     * @return the current value of this form control.
     */
    public Object getValue() {
        if (this.elementState != null) {
            return this.elementState.getValue();
        }

        return null;
    }

    public Object getSchemaValue() {
        if (this.elementState != null) {
            return this.elementState.getSchemaValue();
        }

        return null;

    }

    /**
     * Returns the datatype of the bound node.
     *
     * @return the datatype of the bound node.
     */
    public String getDatatype() throws XFormsException {
        if (hasBindingExpression()) {
            Node n = getInstanceNode();
            if (n == null) {
                getLogger().warn("Node for path '" + getBindingExpression() + "' does not exist:"
                        + DOMUtil.getCanonicalPath(this.element));
                //TODO: clarify what to do
                //throw new XFormsException("Node for path '" + getBindingExpression() + "' does not exist::" + DOMUtil.getCanonicalPath(this.element));
            }
            ModelItem modelItem = this.model.getInstance(getInstanceId()).getModelItem(n);
            if (modelItem != null) {
                return UIElementStateUtil.getDatatype(modelItem, this.element);
            } else {
                return "string";
            }
        }

        return null;
    }

    // lifecycle methods

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

    // lifecycle template methods

    /**
     * Initializes the default action.
     */
    protected void initializeDefaultAction() {
        this.container.getXMLEventService().registerDefaultAction(this.target, XFormsEventNames.BINDING_EXCEPTION,
                this);
        this.container.getXMLEventService().registerDefaultAction(this.target, XFormsEventNames.PREVIOUS, this);
        this.container.getXMLEventService().registerDefaultAction(this.target, XFormsEventNames.NEXT, this);
        this.container.getXMLEventService().registerDefaultAction(this.target, XFormsEventNames.FOCUS, this);
        this.container.getXMLEventService().registerDefaultAction(this.target, XFormsEventNames.HELP, this);
        this.container.getXMLEventService().registerDefaultAction(this.target, XFormsEventNames.HINT, this);
    }

    /**
     * Disposes the default action.
     */
    protected void disposeDefaultAction() {
        this.container.getXMLEventService().deregisterDefaultAction(this.target, XFormsEventNames.BINDING_EXCEPTION,
                this);
        this.container.getXMLEventService().deregisterDefaultAction(this.target, XFormsEventNames.PREVIOUS, this);
        this.container.getXMLEventService().deregisterDefaultAction(this.target, XFormsEventNames.NEXT, this);
        this.container.getXMLEventService().deregisterDefaultAction(this.target, XFormsEventNames.FOCUS, this);
        this.container.getXMLEventService().deregisterDefaultAction(this.target, XFormsEventNames.HELP, this);
        this.container.getXMLEventService().deregisterDefaultAction(this.target, XFormsEventNames.HINT, this);
    }

    // template methods

    /**
     * Factory method for the element state.
     *
     * @return an element state implementation or <code>null</code> if no state
     *         keeping is required.
     * @throws XFormsException if an error occurred during creation.
     */
    protected UIElementState createElementState() throws XFormsException {
        return hasBindingExpression() ? new BoundElementState() : null;
    }

    /**
     * Dispatches the '4.6.7 Value Change with Focus Change' event sequence.
     *
     * @throws XFormsException if an error occurred during event sequencing.
     */
    protected void dispatchValueChangeSequence() throws XFormsException {
        // prevent betterform internal value change dispatching on this control
        if (this.elementState != null) {
            this.elementState.setProperty("dispatchValueChange", Boolean.FALSE);
        }

        // update behaviour
        UpdateHandler updateHandler = this.model.getUpdateHandler();
        if (updateHandler == null) {
            this.container.dispatch(this.model.getTarget(), XFormsEventNames.RECALCULATE, null);
            this.container.dispatch(this.model.getTarget(), XFormsEventNames.REVALIDATE, null);
            this.container.dispatch(this.model.getTarget(), XFormsEventNames.REFRESH, null);
        } else {
            updateHandler.doRecalculate(true);
            updateHandler.doRevalidate(true);
            updateHandler.doRefresh(true);
        }

        // reset betterform internal value change dispatching on this control
        if (this.elementState != null) {
            this.elementState.setProperty("dispatchValueChange", Boolean.TRUE);
        }
    }

    // implementation of 'de.betterform.xml.events.DefaultAction'

    /**
     * Performs the implementation specific default action for this event.
     *
     * @param event the event.
     */
    public void performDefault(Event event) {
        super.performDefault(event);
        if (isCancelled(event)) {
            getLogger().debug(this + " event " + event.getType() + " cancelled");
            return;
        }
        if (event.getType().equals(XFormsEventNames.PREVIOUS)) {
            // todo
            getLogger().warn(this + " default action for " + event.getType() + " is not implemented yet");
            return;
        }
        if (event.getType().equals(XFormsEventNames.NEXT)) {
            // todo
            getLogger().warn(this + " default action for " + event.getType() + " is not implemented yet");
            return;
        }
        if (event.getType().equals(XFormsEventNames.FOCUS)) {
            /*
                        //giving focus if we're relevant
                        try{
            if(UIElementStateUtil.getModelItem(this) != null && UIElementStateUtil.getModelItem(this).isRelevant()){
                String currentFocussedControl = this.container.getFocussedControlId();
                if (currentFocussedControl != null){
                    this.container.dispatch(this.container.getFocussedControlId() , DOMEventNames.FOCUS_OUT, null);
                }
                this.container.setFocussedControlId(this.id);
                this.container.dispatch(this.target , DOMEventNames.FOCUS_IN, null);
            }
                        }catch (XFormsException e){
            getLogger().warn("Exeption occured during dispatch of FOCUS_IN event");
                        }
            */
            return;
        }
        if (event.getType().equals(XFormsEventNames.HELP)) {
            // todo
            getLogger().warn(this + " default action for " + event.getType() + " is not implemented yet");
            return;
        }
        if (event.getType().equals(XFormsEventNames.HINT)) {
            // todo
            getLogger().warn(this + " default action for " + event.getType() + " is not implemented yet");
            return;
        }
    }

    /**
     * convert a localized value into its XML Schema datatype representation. If the value given cannot be parsed with
     * the locale in betterForm context the default locale (US) will be used as fallback. This can be convenient for
     * user-agents that do not pass a localized value back.
     *
     * @param value the value to convert
     * @return converted value that can be used to update instance data and match the Schema datatype lexical space
     * @throws java.text.ParseException in case the incoming string cannot be converted into a Schema datatype representation
     */
    protected String delocaliseValue(String value) throws XFormsException, ParseException {
        if (value == null || value.equals("")) {
            return value;
        }
        if (Config.getInstance().getProperty(XFormsProcessorImpl.BETTERFORM_ENABLE_L10N).equals("true")) {
            Locale locale = (Locale) getModel().getContainer().getProcessor().getContext()
                    .get(XFormsProcessorImpl.BETTERFORM_LOCALE);
            XFormsProcessorImpl processor = this.model.getContainer().getProcessor();

            if (processor.hasControlType(this.id, NamespaceConstants.XMLSCHEMA_PREFIX + ":float")
                    || processor.hasControlType(this.id, NamespaceConstants.XMLSCHEMA_PREFIX + ":decimal")
                    || processor.hasControlType(this.id, NamespaceConstants.XMLSCHEMA_PREFIX + ":double")) {

                NumberFormat formatter = NumberFormat.getNumberInstance(locale);
                formatter.setMaximumFractionDigits(Double.SIZE);
                BigDecimal number;

                try {
                    number = strictParse(value, locale);
                } catch (ParseException e) {
                    LOGGER.warn("value: '" + value + "' could not be parsed for locale: " + locale);
                    return value;
                } catch (NumberFormatException nfe) {
                    LOGGER.warn("value: '" + value + "' could not be parsed for locale: " + locale);
                    return value;
                } catch (InputMismatchException ime) {
                    LOGGER.warn("value: '" + value + "' could not be parsed for locale: " + locale);
                    return value;
                }
                return number.toPlainString();
            } else if (processor.hasControlType(this.id, NamespaceConstants.XMLSCHEMA_PREFIX + ":date")) {
                DateFormat df = DateFormat.getDateInstance(DateFormat.DEFAULT, locale);
                Date d = null;
                try {
                    d = df.parse(value);
                } catch (ParseException e) {
                    //try the default locale - else fail with ParseException
                    df = new SimpleDateFormat("yyyy-MM-dd");
                    df.setLenient(false);
                    d = df.parse(value);
                }
                df = new SimpleDateFormat("yyyy-MM-dd");
                return df.format(d);
            } else if (processor.hasControlType(this.id, NamespaceConstants.XMLSCHEMA_PREFIX + ":dateTime")) {
                String timezone = "";
                // int position = ;
                if (value.contains("GMT")) {
                    timezone = value.substring(value.indexOf("GMT") + 3, value.length());
                } else if (value.contains("+")) {
                    timezone = value.substring(value.indexOf("+"), value.length());

                } else if (value.contains("Z")) {
                    timezone = "Z";
                }

                DateFormat sf = DateFormat.getDateTimeInstance(DateFormat.DEFAULT, DateFormat.DEFAULT, locale);
                Date d = null;
                try {
                    d = sf.parse(value);
                } catch (ParseException e) {
                    //try the default locale - else fail with ParseException
                    sf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
                    d = null;
                    d = sf.parse(value);
                }
                sf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
                String converted = sf.format(d);
                if (!timezone.equals("")) {
                    return converted + timezone;
                }
                return converted;
            }
        }
        return value;
    }

    private BigDecimal strictParse(String value, Locale locale) throws ParseException {
        DecimalFormat format = (DecimalFormat) NumberFormat.getInstance(locale);
        format.setParseBigDecimal(true);
        value = value.trim();
        ParsePosition pos = new ParsePosition(0);
        BigDecimal number = (BigDecimal) format.parse(value, pos);
        boolean okay = pos.getIndex() == value.length() && pos.getErrorIndex() == -1;
        if (!okay)
            throw new ParseException("Could not parse '" + value + "' as a number", pos.getErrorIndex());
        return number;
    }

}

// end of class