com.haulmont.cuba.web.gui.components.WebDateField.java Source code

Java tutorial

Introduction

Here is the source code for com.haulmont.cuba.web.gui.components.WebDateField.java

Source

/*
 * Copyright (c) 2008-2016 Haulmont.
 *
 * 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.haulmont.cuba.web.gui.components;

import com.haulmont.chile.core.datatypes.Datatypes;
import com.haulmont.chile.core.model.MetaClass;
import com.haulmont.chile.core.model.MetaProperty;
import com.haulmont.chile.core.model.utils.InstanceUtils;
import com.haulmont.cuba.core.entity.Entity;
import com.haulmont.cuba.core.entity.annotation.IgnoreUserTimeZone;
import com.haulmont.cuba.core.global.*;
import com.haulmont.cuba.gui.TestIdManager;
import com.haulmont.cuba.gui.components.Component;
import com.haulmont.cuba.gui.components.DateField;
import com.haulmont.cuba.gui.components.Frame;
import com.haulmont.cuba.gui.data.Datasource;
import com.haulmont.cuba.gui.data.impl.WeakItemChangeListener;
import com.haulmont.cuba.gui.data.impl.WeakItemPropertyChangeListener;
import com.haulmont.cuba.security.global.UserSession;
import com.haulmont.cuba.web.AppUI;
import com.haulmont.cuba.web.toolkit.ui.CubaDateField;
import com.haulmont.cuba.web.toolkit.ui.CubaDateFieldWrapper;
import com.haulmont.cuba.web.toolkit.ui.CubaMaskedTextField;
import com.vaadin.data.Property;
import com.vaadin.ui.Layout;
import org.apache.commons.lang.StringUtils;

import javax.persistence.TemporalType;
import javax.validation.constraints.Future;
import javax.validation.constraints.Past;
import java.sql.Time;
import java.util.*;

public class WebDateField extends WebAbstractField<CubaDateFieldWrapper> implements DateField {

    protected Resolution resolution;

    protected boolean updatingInstance;

    protected CubaDateField dateField;
    protected WebTimeField timeField;

    protected Layout innerLayout;

    protected String dateTimeFormat;
    protected String dateFormat;
    protected String timeFormat;

    protected TimeZone timeZone;

    protected UserSession userSession;

    protected Datasource.ItemPropertyChangeListener itemPropertyChangeListener;
    protected WeakItemPropertyChangeListener weakItemPropertyChangeListener;

    protected Datasource.ItemChangeListener itemChangeListener;
    protected WeakItemChangeListener weakItemChangeListener;

    protected boolean buffered = false;
    protected boolean updateTimeFieldResolution = false;

    public WebDateField() {
        innerLayout = new com.vaadin.ui.CssLayout();
        innerLayout.setPrimaryStyleName("c-datefield-layout");

        dateField = new CubaDateField();
        dateField.setValidationVisible(false);
        dateField.setImmediate(true);
        dateField.setInvalidAllowed(true);

        UserSessionSource sessionSource = AppBeans.get(UserSessionSource.NAME);
        userSession = sessionSource.getUserSession();

        Locale locale = userSession.getLocale();
        dateField.setDateFormat(Datatypes.getFormatStringsNN(locale).getDateFormat());
        dateField.setResolution(com.vaadin.shared.ui.datefield.Resolution.DAY);

        timeField = new WebTimeField();

        CubaMaskedTextField vTimeField = (CubaMaskedTextField) timeField.getComponent();
        vTimeField.setImmediate(true);
        vTimeField.setInvalidAllowed(false);
        vTimeField.setInvalidCommitted(true);

        dateField.addValueChangeListener(createDateValueChangeListener());
        timeField.addValueChangeListener(createTimeValueChangeListener());
        setResolution(Resolution.MIN);

        component = new CubaDateFieldWrapper(this, innerLayout);
    }

    protected Property.ValueChangeListener createDateValueChangeListener() {
        return e -> {
            if (!checkRange(constructDate())) {
                return;
            }

            updateInstance();

            if (component != null) {
                // Repaint error state
                component.markAsDirty();
            }
        };
    }

    protected Component.ValueChangeListener createTimeValueChangeListener() {
        return event -> {
            if (!checkRange(constructDate())) {
                return;
            }
            if (!updateTimeFieldResolution) {
                updateInstance();
            }
        };
    }

    public CubaDateField getDateField() {
        return dateField;
    }

    public WebTimeField getTimeField() {
        return timeField;
    }

    @Override
    public Resolution getResolution() {
        return resolution;
    }

    @Override
    public void setResolution(Resolution resolution) {
        this.resolution = resolution;
        __setResolution(resolution);
        updateLayout();
    }

    @Override
    public void setRangeStart(Date value) {
        dateField.setRangeStart(value);
    }

    @Override
    public Date getRangeStart() {
        return dateField.getRangeStart();
    }

    @Override
    public void setRangeEnd(Date value) {
        dateField.setRangeEnd(value);
    }

    @Override
    public Date getRangeEnd() {
        return dateField.getRangeEnd();
    }

    protected boolean checkRange(Date value) {
        if (updatingInstance) {
            return true;
        }

        if (value != null) {
            Date rangeStart = getRangeStart();
            if (rangeStart != null && value.before(rangeStart)) {
                handleDateOutOfRange(value);
                return false;
            }

            Date rangeEnd = getRangeEnd();
            if (rangeEnd != null && value.after(rangeEnd)) {
                handleDateOutOfRange(value);
                return false;
            }
        }

        return true;
    }

    protected void handleDateOutOfRange(Date value) {
        if (getFrame() != null) {
            Messages messages = AppBeans.get(Messages.NAME);
            getFrame().showNotification(messages.getMainMessage("datePicker.dateOutOfRangeMessage"),
                    Frame.NotificationType.TRAY);
        }

        updatingInstance = true;
        try {
            dateField.setValue((Date) prevValue);
            if (prevValue == null) {
                timeField.setValue(null);
            } else {
                timeField.setValue(extractTime((Date) prevValue));
            }
        } finally {
            updatingInstance = false;
        }
    }

    @Override
    public String getDateFormat() {
        return dateTimeFormat;
    }

    @Override
    public void setDateFormat(String dateFormat) {
        dateTimeFormat = dateFormat;
        StringBuilder date = new StringBuilder(dateFormat);
        StringBuilder time = new StringBuilder(dateFormat);
        int timeStartPos = findTimeStartPos(dateFormat);
        if (timeStartPos >= 0) {
            time.delete(0, timeStartPos);
            date.delete(timeStartPos, dateFormat.length());
            timeFormat = StringUtils.trimToEmpty(time.toString());
            timeField.setFormat(timeFormat);
            setResolution(resolution);
        } else if (resolution.ordinal() < Resolution.DAY.ordinal()) {
            setResolution(Resolution.DAY);
        }

        this.dateFormat = StringUtils.trimToEmpty(date.toString());
        dateField.setDateFormat(this.dateFormat);
    }

    @Override
    public TimeZone getTimeZone() {
        return timeZone;
    }

    @Override
    public void setTimeZone(TimeZone timeZone) {
        TimeZone prevTimeZone = this.timeZone;
        Date value = getValue();
        this.timeZone = timeZone;
        dateField.setTimeZone(timeZone);
        if (value != null && !Objects.equals(prevTimeZone, timeZone)) {
            setValueToFields(value);
        }
    }

    public void updateLayout() {
        innerLayout.removeAllComponents();
        innerLayout.addComponent(dateField);

        if (resolution.ordinal() < Resolution.DAY.ordinal()) {
            innerLayout.addComponent(timeField.<com.vaadin.ui.Component>getComponent());
            innerLayout.addStyleName("c-datefield-withtime");
        } else {
            innerLayout.removeStyleName("c-datefield-withtime");
        }
    }

    @Override
    protected void attachListener(CubaDateFieldWrapper component) {
        // do nothing
    }

    protected int findTimeStartPos(String dateTimeFormat) {
        List<Integer> positions = new ArrayList<>();

        char[] signs = new char[] { 'H', 'h', 'm', 's' };
        for (char sign : signs) {
            int pos = dateTimeFormat.indexOf(sign);
            if (pos > -1) {
                positions.add(pos);
            }
        }
        return positions.isEmpty() ? -1 : Collections.min(positions);
    }

    protected void __setResolution(Resolution resolution) {
        if (resolution.ordinal() < Resolution.DAY.ordinal()) {
            timeField.setResolution(resolution);
            // while changing resolution, timeField loses its value, so we need to set it again
            updateTimeFieldResolution = true;
            Date value = dateField.getValue();
            if (value == null) {
                timeField.setValue(null);
            } else {
                timeField.setValue(extractTime(value));
            }
            updateTimeFieldResolution = false;
        } else {
            dateField.setResolution(WebComponentsHelper.convertDateFieldResolution(resolution));
        }
    }

    @SuppressWarnings("unchecked")
    @Override
    public Date getValue() {
        return constructDate();
    }

    @Override
    public void setValue(Object value) {
        setValueToFields((Date) value);
        updateInstance();
    }

    @Override
    public void commit() {
        if (updatingInstance) {
            return;
        }

        updatingInstance = true;
        try {
            if (datasource != null && metaPropertyPath != null) {
                Date value = constructDate();

                if (datasource.getItem() != null) {
                    InstanceUtils.setValueEx(datasource.getItem(), metaPropertyPath.getPath(), value);
                    setModified(false);
                }
            }
        } finally {
            updatingInstance = false;
        }

        Object newValue = getValue();
        fireValueChanged(newValue);
    }

    @Override
    public void discard() {
        if (datasource != null && datasource.getItem() != null) {
            Date value = getEntityValue(datasource.getItem());
            setValueToFields(value);
            fireValueChanged(value);
        }
    }

    @Override
    public boolean isBuffered() {
        return buffered;
    }

    @Override
    public void setBuffered(boolean buffered) {
        this.buffered = buffered;
    }

    @Override
    public boolean isModified() {
        return dateField.isModified();
    }

    protected void setModified(boolean modified) {
        dateField.setModified(modified);
    }

    protected void setValueToFields(Date value) {
        updatingInstance = true;
        try {
            dateField.setValueIgnoreReadOnly(value);
            if (value == null) {
                timeField.setValue(null);
            } else {
                timeField.setValue(extractTime(value));
            }
        } finally {
            updatingInstance = false;
        }
    }

    @Override
    public void setDebugId(String id) {
        super.setDebugId(id);

        if (id != null) {
            TestIdManager testIdManager = AppUI.getCurrent().getTestIdManager();
            timeField.setDebugId(testIdManager.getTestId(id + "_time"));
            dateField.setId(testIdManager.getTestId(id + "_date"));
        }
    }

    @Override
    public void setId(String id) {
        super.setId(id);

        if (id != null && AppUI.getCurrent().isTestMode()) {
            timeField.setId("timepart");
            dateField.setCubaId("datepart");
        }
    }

    protected boolean isHourUsed() {
        return resolution != null && resolution.ordinal() <= Resolution.HOUR.ordinal();
    }

    protected boolean isMinUsed() {
        return resolution != null && resolution.ordinal() <= Resolution.MIN.ordinal();
    }

    protected void updateInstance() {
        if (updatingInstance) {
            return;
        }

        updatingInstance = true;
        try {
            if (datasource != null && metaPropertyPath != null) {
                Date value = constructDate();

                if (!isBuffered()) {
                    if (datasource.getItem() != null) {
                        InstanceUtils.setValueEx(datasource.getItem(), metaPropertyPath.getPath(), value);
                        setModified(false);
                    }
                } else {
                    setModified(true);
                }
            }
        } finally {
            updatingInstance = false;
        }

        Object newValue = getValue();
        fireValueChanged(newValue);
    }

    @Override
    public void setDatasource(Datasource datasource, String property) {
        if ((datasource == null && property != null) || (datasource != null && property == null))
            throw new IllegalArgumentException(
                    "Datasource and property should be either null or not null at the same time");

        if (datasource == this.datasource
                && ((metaPropertyPath != null && metaPropertyPath.toString().equals(property))
                        || (metaPropertyPath == null && property == null)))
            return;

        if (this.datasource != null) {
            metaProperty = null;
            metaPropertyPath = null;

            component.setPropertyDataSource(null);

            //noinspection unchecked
            this.datasource.removeItemChangeListener(weakItemChangeListener);
            weakItemChangeListener = null;

            //noinspection unchecked
            this.datasource.removeItemPropertyChangeListener(weakItemPropertyChangeListener);
            weakItemPropertyChangeListener = null;

            this.datasource = null;

            if (itemWrapper != null) {
                itemWrapper.unsubscribe();
            }

            timeZone = null;

            disableBeanValidator();
        }

        if (datasource != null) {
            //noinspection unchecked
            this.datasource = datasource;

            MetaClass metaClass = datasource.getMetaClass();
            resolveMetaPropertyPath(metaClass, property);

            if (metaProperty.getRange().isDatatype()
                    && metaProperty.getRange().asDatatype().getJavaClass().equals(Date.class) && timeZone == null) {
                MetadataTools metadataTools = AppBeans.get(MetadataTools.class);
                Boolean ignoreUserTimeZone = metadataTools.getMetaAnnotationValue(metaProperty,
                        IgnoreUserTimeZone.class);
                if (!Boolean.TRUE.equals(ignoreUserTimeZone)) {
                    timeZone = userSession.getTimeZone();
                    dateField.setTimeZone(timeZone);
                }
            }

            itemChangeListener = e -> {
                if (updatingInstance) {
                    return;
                }
                Date value = getEntityValue(e.getItem());
                setValueToFields(value);
                fireValueChanged(value);
            };
            weakItemChangeListener = new WeakItemChangeListener(datasource, itemChangeListener);
            //noinspection unchecked
            datasource.addItemChangeListener(weakItemChangeListener);

            itemPropertyChangeListener = e -> {
                if (updatingInstance) {
                    return;
                }
                if (!isBuffered() && e.getProperty().equals(metaPropertyPath.toString())) {
                    setValueToFields((Date) e.getValue());
                    fireValueChanged(e.getValue());
                }
            };

            weakItemPropertyChangeListener = new WeakItemPropertyChangeListener(datasource,
                    itemPropertyChangeListener);
            //noinspection unchecked
            datasource.addItemPropertyChangeListener(weakItemPropertyChangeListener);

            if (datasource.getState() == Datasource.State.VALID && datasource.getItem() != null) {
                if (property.equals(metaPropertyPath.toString())) {
                    Date value = getEntityValue(datasource.getItem());
                    setValueToFields(value);
                    fireValueChanged(value);
                }
            }

            initRequired(metaPropertyPath);
            initDateFormat(metaProperty);

            if (metaProperty.isReadOnly()) {
                setEditable(false);
            }

            initBeanValidator();
            setDateRangeByProperty(metaProperty);
        }
    }

    protected void initDateFormat(MetaProperty metaProperty) {
        TemporalType tt = null;
        if (metaProperty.getRange().asDatatype().getJavaClass().equals(java.sql.Date.class)) {
            tt = TemporalType.DATE;
        } else if (metaProperty.getAnnotations() != null) {
            tt = (TemporalType) metaProperty.getAnnotations().get(MetadataTools.TEMPORAL_ANN_NAME);
        }

        setResolution(tt == TemporalType.DATE ? DateField.Resolution.DAY : Resolution.MIN);

        MessageTools messageTools = AppBeans.get(MessageTools.NAME);
        String formatStr = messageTools.getDefaultDateFormat(tt);
        setDateFormat(formatStr);
    }

    protected void setDateRangeByProperty(MetaProperty metaProperty) {
        if (metaProperty.getAnnotations().get(Past.class.getName()) != null) {
            TimeSource timeSource = AppBeans.get(TimeSource.NAME);
            Date currentTimestamp = timeSource.currentTimestamp();

            Calendar calendar = Calendar.getInstance(userSession.getLocale());
            calendar.setTime(currentTimestamp);
            calendar.set(Calendar.HOUR_OF_DAY, 23);
            calendar.set(Calendar.MINUTE, 59);
            calendar.set(Calendar.SECOND, 59);
            calendar.set(Calendar.MILLISECOND, 999);

            setRangeEnd(calendar.getTime());
        } else if (metaProperty.getAnnotations().get(Future.class.getName()) != null) {
            TimeSource timeSource = AppBeans.get(TimeSource.NAME);
            Date currentTimestamp = timeSource.currentTimestamp();

            Calendar calendar = Calendar.getInstance(userSession.getLocale());
            calendar.setTime(currentTimestamp);
            calendar.set(Calendar.HOUR_OF_DAY, 0);
            calendar.set(Calendar.MINUTE, 0);
            calendar.set(Calendar.SECOND, 0);
            calendar.set(Calendar.MILLISECOND, 0);
            if (metaProperty.getRange().asDatatype().getJavaClass().equals(java.sql.Date.class)) {
                calendar.add(Calendar.DATE, 1);
            }

            setRangeStart(calendar.getTime());
        }
    }

    protected Date getEntityValue(Entity item) {
        return InstanceUtils.getValueEx(item, metaPropertyPath.getPath());
    }

    protected void fireValueChanged(Object value) {
        Object oldValue = prevValue;

        if (!Objects.equals(oldValue, value)) {
            prevValue = value;

            if (hasValidationError()) {
                setValidationError(null);
            }

            ValueChangeEvent event = new ValueChangeEvent(this, oldValue, value);
            getEventRouter().fireEvent(ValueChangeListener.class, ValueChangeListener::valueChanged, event);
        }
    }

    protected Date constructDate() {
        final Date datePickerDate = dateField.getValue();
        if (datePickerDate == null) {
            return null;
        }

        Calendar dateCalendar = Calendar.getInstance(userSession.getLocale());
        if (timeZone != null) {
            dateCalendar.setTimeZone(timeZone);
        }
        dateCalendar.setTime(datePickerDate);
        if (timeField.getValue() == null) {
            dateCalendar.set(Calendar.HOUR_OF_DAY, 0);
            dateCalendar.set(Calendar.MINUTE, 0);
            dateCalendar.set(Calendar.SECOND, 0);
        } else {
            Calendar timeCalendar = Calendar.getInstance(userSession.getLocale());
            timeCalendar.setTime(timeField.<Date>getValue());

            dateCalendar.set(Calendar.HOUR_OF_DAY, timeCalendar.get(Calendar.HOUR_OF_DAY));
            dateCalendar.set(Calendar.MINUTE, timeCalendar.get(Calendar.MINUTE));
            dateCalendar.set(Calendar.SECOND, timeCalendar.get(Calendar.SECOND));
        }

        Date resultDate = dateCalendar.getTime();

        if (metaProperty != null) {
            Class javaClass = metaProperty.getRange().asDatatype().getJavaClass();
            if (javaClass.equals(java.sql.Date.class)) {
                return new java.sql.Date(resultDate.getTime());
            } else if (javaClass.equals(Time.class)) {
                return new Time(resultDate.getTime());
            } else {
                return resultDate;
            }
        } else {
            return resultDate;
        }
    }

    protected Date extractTime(Date date) {
        Calendar dateCalendar = Calendar.getInstance(userSession.getLocale());
        if (timeZone != null) {
            dateCalendar.setTimeZone(timeZone);
        }
        dateCalendar.setTime(date);

        Calendar timeCalendar = Calendar.getInstance(userSession.getLocale());
        timeCalendar.setTimeInMillis(0);

        timeCalendar.set(Calendar.HOUR_OF_DAY, dateCalendar.get(Calendar.HOUR_OF_DAY));
        timeCalendar.set(Calendar.MINUTE, dateCalendar.get(Calendar.MINUTE));
        timeCalendar.set(Calendar.SECOND, dateCalendar.get(Calendar.SECOND));

        return timeCalendar.getTime();
    }

    @Override
    protected void setEditableToComponent(boolean editable) {
        timeField.setEditable(editable);
        dateField.setReadOnly(!editable);

        component.setCompositionReadOnly(!editable);
    }

    @Override
    public int getTabIndex() {
        return dateField.getTabIndex();
    }

    @Override
    public void setTabIndex(int tabIndex) {
        dateField.setTabIndex(tabIndex);
        timeField.setTabIndex(tabIndex);
    }
}