001/**
002 * Copyright (c) 2011 - 2015, Lunifera GmbH (Gross Enzersdorf), Loetz GmbH&Co.KG (Heidelberg)
003 * All rights reserved. This program and the accompanying materials
004 * are made available under the terms of the Eclipse Public License v1.0
005 * which accompanies this distribution, and is available at
006 * http://www.eclipse.org/legal/epl-v10.html
007 *
008 * Contributors:
009 *         Florian Pirchner - Initial implementation
010 */
011package org.eclipse.osbp.runtime.web.ecview.presentation.vaadin.internal;
012
013import java.text.DateFormat;
014import java.text.SimpleDateFormat;
015import java.util.Arrays;
016import java.util.Date;
017import java.util.List;
018import java.util.Locale;
019
020import org.eclipse.core.databinding.Binding;
021import org.eclipse.core.databinding.observable.IObservable;
022import org.eclipse.core.databinding.observable.value.IObservableValue;
023import org.eclipse.emf.databinding.EMFObservables;
024import org.eclipse.osbp.ecview.core.common.editpart.IElementEditpart;
025import org.eclipse.osbp.ecview.core.common.model.core.YEmbeddableBindingEndpoint;
026import org.eclipse.osbp.ecview.core.common.model.core.YEmbeddableValueEndpoint;
027import org.eclipse.osbp.ecview.core.common.model.datatypes.YDatatype;
028import org.eclipse.osbp.ecview.core.emf.api.IDateFormatProvider;
029import org.eclipse.osbp.ecview.core.emf.api.IDateFormatProvider.Info;
030import org.eclipse.osbp.ecview.core.extension.model.datatypes.YDateTimeDatatype;
031import org.eclipse.osbp.ecview.core.extension.model.datatypes.YDateTimeFormat;
032import org.eclipse.osbp.ecview.core.extension.model.datatypes.YDateTimeResolution;
033import org.eclipse.osbp.ecview.core.extension.model.extension.ExtensionModelPackage;
034import org.eclipse.osbp.ecview.core.extension.model.extension.YDateTime;
035import org.eclipse.osbp.ecview.core.ui.core.editparts.extension.IDateTimeEditpart;
036import org.eclipse.osbp.runtime.web.ecview.presentation.vaadin.common.AbstractFieldWidgetPresenter;
037import org.eclipse.osbp.runtime.web.ecview.presentation.vaadin.internal.util.Util;
038
039import com.vaadin.data.util.ObjectProperty;
040import com.vaadin.server.ErrorMessage;
041import com.vaadin.shared.ui.datefield.Resolution;
042import com.vaadin.ui.Component;
043import com.vaadin.ui.ComponentContainer;
044import com.vaadin.ui.DateField;
045import com.vaadin.ui.Field;
046import com.vaadin.ui.UI;
047
048// TODO: Auto-generated Javadoc
049/**
050 * This presenter is responsible to render a text area on the given layout.
051 */
052public class DateTimePresentation extends
053        AbstractFieldWidgetPresenter<Component> {
054
055    /** The model access. */
056    private final ModelAccess modelAccess;
057    
058    /** The date field. */
059    private CustomField dateField;
060    
061    /** The binding_value to ui. */
062    private Binding binding_valueToUI;
063    
064    /** The property. */
065    private ObjectProperty<Date> property;
066    
067    /** The format info. */
068    private Info formatInfo;
069
070    /**
071     * Constructor.
072     * 
073     * @param editpart
074     *            The editpart of that presenter
075     */
076    public DateTimePresentation(IElementEditpart editpart) {
077        super((IDateTimeEditpart) editpart);
078        this.modelAccess = new ModelAccess((YDateTime) editpart.getModel());
079    }
080
081    /**
082     * {@inheritDoc}
083     */
084    @Override
085    public Component doCreateWidget(Object parent) {
086        if (dateField == null) {
087
088            dateField = new CustomField();
089            dateField.addStyleName(CSS_CLASS_CONTROL);
090            dateField.setImmediate(true);
091            setupComponent(dateField, getCastedModel());
092
093            associateWidget(dateField, modelAccess.yField);
094
095            if (modelAccess.isCssIdValid()) {
096                dateField.setId(modelAccess.getCssID());
097            } else {
098                dateField.setId(getEditpart().getId());
099            }
100
101            property = new ObjectProperty<Date>(null, Date.class);
102            dateField.setPropertyDataSource(property);
103
104            // creates the binding for the field
105            createBindings(modelAccess.yField, dateField);
106
107            if (modelAccess.isCssClassValid()) {
108                dateField.addStyleName(modelAccess.getCssClass());
109            }
110
111            doApplyDatatype(modelAccess.yField.getDatatype());
112
113            applyCaptions();
114
115            initializeField(dateField);
116        }
117        return dateField;
118    }
119
120    /**
121     * Applies the datatype options to the field.
122     *
123     * @param yDt
124     *            the y dt
125     */
126    protected void doApplyDatatype(YDatatype yDt) {
127        if (dateField == null) {
128            return;
129        }
130
131        IDateFormatProvider service = getViewContext().getService(
132                IDateFormatProvider.class.getName());
133        YDateTimeDatatype yDatatype = (YDateTimeDatatype) yDt;
134        if (service != null) {
135            formatInfo = service
136                    .getInfo(yDatatype, UI.getCurrent().getLocale());
137        } else {
138            formatInfo = new DefaultDateFormatProvider().getInfo(yDatatype, UI
139                    .getCurrent().getLocale());
140        }
141
142        dateField.setDateFormat(formatInfo.getDateFormat());
143        dateField.setResolution(mapToVaadin(formatInfo.getResolution()));
144    }
145
146    /**
147     * Map to vaadin.
148     *
149     * @param resolution
150     *            the resolution
151     * @return the resolution
152     */
153    private Resolution mapToVaadin(YDateTimeResolution resolution) {
154        switch (resolution) {
155        case YEAR:
156            return Resolution.YEAR;
157        case MONTH:
158            return Resolution.MONTH;
159        case DAY:
160            return Resolution.DAY;
161        case HOUR:
162            return Resolution.HOUR;
163        case MINUTE:
164            return Resolution.MINUTE;
165        case SECOND:
166            return Resolution.SECOND;
167        case UNDEFINED:
168            return Resolution.DAY;
169        }
170
171        return Resolution.DAY;
172    }
173
174    /* (non-Javadoc)
175     * @see org.eclipse.osbp.runtime.web.ecview.presentation.vaadin.common.AbstractVaadinWidgetPresenter#doUpdateLocale(java.util.Locale)
176     */
177    @Override
178    protected void doUpdateLocale(Locale locale) {
179        // need to refresh the locale datetime pattern
180        doApplyDatatype(modelAccess.yField.getDatatype());
181        // update the captions
182        applyCaptions();
183    }
184
185    /**
186     * Applies the labels to the widgets.
187     */
188    protected void applyCaptions() {
189        Util.applyCaptions(getI18nService(), modelAccess.getLabel(),
190                modelAccess.getLabelI18nKey(), getLocale(), dateField);
191    }
192
193    /* (non-Javadoc)
194     * @see org.eclipse.osbp.runtime.web.ecview.presentation.vaadin.common.AbstractFieldWidgetPresenter#doGetField()
195     */
196    @Override
197    protected Field<?> doGetField() {
198        return dateField;
199    }
200
201    /* (non-Javadoc)
202     * @see org.eclipse.osbp.runtime.web.ecview.presentation.vaadin.common.AbstractVaadinWidgetPresenter#internalGetObservableEndpoint(org.eclipse.osbp.ecview.core.common.model.core.YEmbeddableBindingEndpoint)
203     */
204    @Override
205    protected IObservable internalGetObservableEndpoint(
206            YEmbeddableBindingEndpoint bindableValue) {
207        if (bindableValue == null) {
208            throw new IllegalArgumentException(
209                    "BindableValue must not be null!");
210        }
211
212        if (bindableValue instanceof YEmbeddableValueEndpoint) {
213            return internalGetValueEndpoint();
214        }
215        throw new IllegalArgumentException("Not a valid input: "
216                + bindableValue);
217    }
218
219    /**
220     * Returns the observable to observe value.
221     *
222     * @return the i observable value
223     */
224    protected IObservableValue internalGetValueEndpoint() {
225        // return the observable value for text
226        return EMFObservables.observeValue(castEObject(getModel()),
227                ExtensionModelPackage.Literals.YDATE_TIME__VALUE);
228    }
229
230    /**
231     * Creates the bindings for the given values.
232     *
233     * @param yField
234     *            the y field
235     * @param field
236     *            the field
237     */
238    protected void createBindings(YDateTime yField, DateField field) {
239        // create the model binding from widget to ECView-model
240        binding_valueToUI = createBindingsValue(castEObject(getModel()),
241                ExtensionModelPackage.Literals.YDATE_TIME__VALUE, field, null,
242                null);
243        registerBinding(binding_valueToUI);
244
245        super.createBindings(yField, field, null);
246    }
247
248    /* (non-Javadoc)
249     * @see org.eclipse.osbp.ecview.core.common.presentation.IWidgetPresentation#getWidget()
250     */
251    @Override
252    public Component getWidget() {
253        return dateField;
254    }
255
256    /* (non-Javadoc)
257     * @see org.eclipse.osbp.ecview.core.common.presentation.IWidgetPresentation#isRendered()
258     */
259    @Override
260    public boolean isRendered() {
261        return dateField != null;
262    }
263
264    /**
265     * {@inheritDoc}
266     */
267    @Override
268    public void doUnrender() {
269        if (dateField != null) {
270
271            // unbind all active bindings
272            unbind();
273
274            Component parent = ((Component) dateField.getParent());
275            if (parent != null && parent instanceof ComponentContainer) {
276                ((ComponentContainer) parent).removeComponent(dateField);
277            }
278
279            // remove assocations
280            unassociateWidget(dateField);
281
282            dateField = null;
283        }
284    }
285
286    /**
287     * {@inheritDoc}
288     */
289    @Override
290    protected void internalDispose() {
291        try {
292            unrender();
293        } finally {
294            super.internalDispose();
295        }
296
297        if (binding_valueToUI != null) {
298            binding_valueToUI.dispose();
299            binding_valueToUI = null;
300        }
301    }
302
303    /**
304     * A helper class.
305     */
306    private static class ModelAccess {
307        
308        /** The y field. */
309        private final YDateTime yField;
310
311        /**
312         * Instantiates a new model access.
313         *
314         * @param yField
315         *            the y field
316         */
317        public ModelAccess(YDateTime yField) {
318            super();
319            this.yField = yField;
320        }
321
322        /**
323         * Gets the css class.
324         *
325         * @return the css class
326         * @see org.eclipse.osbp.ecview.core.ui.core.model.core.YCssAble#getCssClass()
327         */
328        public String getCssClass() {
329            return yField.getCssClass();
330        }
331
332        /**
333         * Returns true, if the css class is not null and not empty.
334         *
335         * @return true, if is css class valid
336         */
337        public boolean isCssClassValid() {
338            return getCssClass() != null && !getCssClass().equals("");
339        }
340
341        /**
342         * Gets the css id.
343         *
344         * @return the css id
345         * @see org.eclipse.osbp.ecview.core.ui.core.model.core.YCssAble#getCssID()
346         */
347        public String getCssID() {
348            return yField.getCssID();
349        }
350
351        /**
352         * Returns true, if the css id is not null and not empty.
353         *
354         * @return true, if is css id valid
355         */
356        public boolean isCssIdValid() {
357            return getCssID() != null && !getCssID().equals("");
358        }
359
360        /**
361         * Returns the label.
362         *
363         * @return the label
364         */
365        public String getLabel() {
366            return yField.getDatadescription() != null ? yField
367                    .getDatadescription().getLabel() : null;
368        }
369
370        /**
371         * Returns the label.
372         *
373         * @return the label i18n key
374         */
375        public String getLabelI18nKey() {
376            return yField.getDatadescription() != null ? yField
377                    .getDatadescription().getLabelI18nKey() : null;
378        }
379
380        /**
381         * Returns true, if the date format is valid.
382         *
383         * @return true, if is dateformat valid
384         */
385        @SuppressWarnings("unused")
386        public boolean isDateformatValid() {
387            return yField.getDatadescription() != null
388                    && yField.getDatatype().getFormat() != null;
389        }
390    }
391
392    /**
393     * The Class CustomField.
394     */
395    @SuppressWarnings("serial")
396    private class CustomField extends DateField {
397
398        /* (non-Javadoc)
399         * @see com.vaadin.ui.AbstractField#getErrorMessage()
400         */
401        @Override
402        public ErrorMessage getErrorMessage() {
403            if (isDisposed()) {
404                // after disposal, Vaadin will call this method once.
405                return null;
406            }
407
408            ErrorMessage message = super.getErrorMessage();
409            reportValidationError(message);
410            return message;
411        }
412    }
413
414    /**
415     * The Class DefaultDateFormatProvider.
416     */
417    private static class DefaultDateFormatProvider implements
418            IDateFormatProvider {
419
420        /* (non-Javadoc)
421         * @see org.eclipse.osbp.ecview.core.emf.api.IDateFormatProvider#getInfo(org.eclipse.osbp.ecview.core.extension.model.datatypes.YDateTimeDatatype, java.util.Locale)
422         */
423        @Override
424        public Info getInfo(YDateTimeDatatype yDt, Locale locale) {
425            
426            if (yDt == null) {
427                DateFormat formatter = DateFormat.getDateTimeInstance(DateFormat.DEFAULT, DateFormat.SHORT, locale);
428                return new IDateFormatProvider.Info(((SimpleDateFormat)formatter).toPattern(),
429                        YDateTimeResolution.MINUTE);
430            }
431
432            DateFormat formatter = DateFormat.getDateTimeInstance(DateFormat.DEFAULT, DateFormat.DEFAULT, locale);
433            String pattern  = ((SimpleDateFormat)formatter).toPattern();
434
435            String dateFormat = null;
436            YDateTimeResolution resolution = calcResolution(yDt);
437            YDateTimeFormat yFormat = yDt.getFormat();
438            if (yFormat != null) {
439                switch (yFormat) {
440                case DATE:
441                    switch (resolution) {
442                    case YEAR:
443                        dateFormat = filterFormat(pattern, "yyyy");
444                        break;
445                    case MONTH:
446                        dateFormat = filterFormat(pattern, "yyyy.MM");
447                        break;
448                    case DAY:
449                        dateFormat = filterFormat(pattern, "yyyy.MM.dd");
450                        break;
451                    default:
452                        throw new IllegalArgumentException(resolution
453                                + " is not a valid resolution for " + yFormat);
454                    }
455                    break;
456                case DATE_TIME:
457                    switch (resolution) {
458                    case YEAR:
459                        dateFormat = filterFormat(pattern, "yyyy");
460                        break;
461                    case MONTH:
462                        dateFormat = filterFormat(pattern, "yyyy.MM");
463                        break;
464                    case DAY:
465                        dateFormat = filterFormat(pattern, "yyyy.MM.dd");
466                        break;
467                    case HOUR:
468                        dateFormat = filterFormat(pattern, "yyyy.MM.dd HH");
469                        break;
470                    case MINUTE:
471                        dateFormat = filterFormat(pattern, "yyyy.MM.dd HH:mm");
472                        break;
473                    case SECOND:
474                        dateFormat = filterFormat(pattern, "yyyy.MM.dd HH:mm:ss");
475                        break;
476                    default:
477                        throw new IllegalArgumentException(resolution
478                                + " is not a valid resolution for " + yFormat);
479                    }
480                    break;
481                case TIME:
482                    switch (resolution) {
483                    case HOUR:
484                        dateFormat = filterFormat(pattern, "HH");
485                        break;
486                    case MINUTE:
487                        dateFormat = filterFormat(pattern, "HH:mm");
488                        break;
489                    case SECOND:
490                        dateFormat = filterFormat(pattern, "HH:mm:ss");
491                        break;
492                    default:
493                        throw new IllegalArgumentException(resolution
494                                + " is not a valid resolution for " + yFormat);
495                    }
496                    break;
497                }
498            }
499
500            return new IDateFormatProvider.Info(dateFormat, resolution);
501        }
502        
503        /**
504         * filters from any localized date-time pattern the desired subset
505         * defined by filterPattern without destroying the original localized
506         * pattern .
507         *
508         * @param localizedPattern
509         *            the localized full date-time pattern
510         * @param filterPattern
511         *            the subset of desired date-time formatter patterns
512         * @return the string
513         */
514        private String filterFormat(String localizedPattern, String filterPattern) {
515            // remove any multiple characters sequences and remove all separator signs from filterPattern 
516            String filter = filterPattern.replaceAll("(.)\\1+", "$1").replaceAll("[^\\w\\s]", "")+",";
517            // create a replacement pattern to remove unnecessary blanks disturbing the recognition of orphaned separators
518            // rule: each blank must be surrounded by any filter-letter to be valid
519            String invalidBlanks = "(?!["+filter+"])( )(?!["+filter+"])";
520            // create a replacement pattern to remove remaining separators without formatting function
521            // rule: each separator must be surrounded by any filter-letter or blank to be valid
522            String invalidSeparators = "(?!["+filter+" ])([.:])(?!["+filter+" ])";
523            return localizedPattern.replaceAll("[^"+filter+",.: ]", "").replaceAll(invalidBlanks, "").replaceAll(invalidSeparators, "");
524        }
525
526        /**
527         * Calc resolution.
528         *
529         * @param yDt
530         *            the y dt
531         * @return the y date time resolution
532         */
533        private YDateTimeResolution calcResolution(YDateTimeDatatype yDt) {
534            YDateTimeFormat yFormat = yDt.getFormat();
535            YDateTimeResolution resolution = null;
536            if (yFormat != null) {
537                YDateTimeResolution yResolution = yDt.getResolution();
538                switch (yFormat) {
539                case DATE:
540                    if (yResolution == YDateTimeResolution.UNDEFINED
541                            || yResolution == YDateTimeResolution.SECOND
542                            || yResolution == YDateTimeResolution.MINUTE
543                            || yResolution == YDateTimeResolution.HOUR) {
544                        resolution = YDateTimeResolution.DAY;
545                    }
546                    break;
547                case DATE_TIME:
548                    if (yResolution == YDateTimeResolution.UNDEFINED
549                            || yResolution == YDateTimeResolution.DAY
550                            || yResolution == YDateTimeResolution.MONTH
551                            || yResolution == YDateTimeResolution.YEAR) {
552                        resolution = YDateTimeResolution.MINUTE;
553                    }
554                    break;
555                case TIME:
556                    if (yResolution == YDateTimeResolution.UNDEFINED
557                            || yResolution == YDateTimeResolution.DAY
558                            || yResolution == YDateTimeResolution.MONTH
559                            || yResolution == YDateTimeResolution.YEAR) {
560                        resolution = YDateTimeResolution.MINUTE;
561                    }
562                    break;
563                }
564            }
565
566            if (resolution == null) {
567                switch (yDt.getResolution()) {
568                case SECOND:
569                    resolution = YDateTimeResolution.SECOND;
570                    break;
571                case MINUTE:
572                    resolution = YDateTimeResolution.MINUTE;
573                    break;
574                case HOUR:
575                    resolution = YDateTimeResolution.HOUR;
576                    break;
577                case DAY:
578                    resolution = YDateTimeResolution.DAY;
579                    break;
580                case MONTH:
581                    resolution = YDateTimeResolution.MONTH;
582                    break;
583                case YEAR:
584                    resolution = YDateTimeResolution.YEAR;
585                    break;
586                case UNDEFINED:
587                    resolution = YDateTimeResolution.MINUTE;
588                    break;
589                default:
590                    resolution = YDateTimeResolution.MINUTE;
591                }
592            }
593
594            return resolution;
595        }
596
597    }
598}