/******************************************************************************* * Copyright (c) 2007, 2014 compeople AG and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * compeople AG - initial API and implementation *******************************************************************************/ package org.eclipse.riena.ui.ridgets.validation; import java.text.ParsePosition; import java.text.SimpleDateFormat; import org.eclipse.core.databinding.validation.IValidator; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IConfigurationElement; import org.eclipse.core.runtime.IExecutableExtension; import org.eclipse.core.runtime.IStatus; import org.eclipse.osgi.util.NLS; import org.eclipse.riena.core.util.PropertiesUtils; import org.eclipse.riena.ui.ridgets.IDateTextRidget; import org.eclipse.riena.ui.ridgets.nls.Messages; /** * Validator checking that a String matches a given pattern for a Date. */ public abstract class AbstractValidDate implements IValidator, IExecutableExtension { private static final String DAY_COMPONENT_LETTER = "d"; //$NON-NLS-1$ private static final String MONTH_COMPONENT_LETTER = "M"; //$NON-NLS-1$ private static final String YEAR_COMPONENT_LETTER = "y"; //$NON-NLS-1$ private static final String A_LEAP_YEAR = "2004"; //$NON-NLS-1$ /** * the separator key used in the format for this date validation rule */ private static final char SEPERATOR_KEY = '.'; // default is the dot-separator // placeholderValid to use in completion, if no input occurs between two // separators private static final String PLACEHOLDER_VALID = "1"; //$NON-NLS-1$ // placeholderInvalid to use in completion to mark invalid input. May be // used, if input occurs for // some part of the format pattern, which should be marked as invalid. // Example: yyyy is the part of the format pattern to be validated and the // input is 0xx and should be // marked invalid, so 0xx is replaced by ?. private static final String PLACEHOLDER_INVALID = "?"; //$NON-NLS-1$ private String pattern; private boolean checkValidIntermediate; /** * Creates a StringToDateValidator. */ protected AbstractValidDate(final boolean checkValidIntermediate) { this("", checkValidIntermediate); //$NON-NLS-1$ } /** * Creates a StringToDateValidator. * * @param pattern * The pattern to match e.g. MM/dd/yyyy. */ public AbstractValidDate(final String pattern, final boolean checkValidIntermediate) { this.pattern = pattern; this.checkValidIntermediate = checkValidIntermediate; } /** * @see org.eclipse.core.databinding.validation.IValidator#validate(java.lang.Object) */ public IStatus validate(final Object value) { if (value != null) { if (!(value instanceof String)) { throw new ValidationFailure("ValidCharacters can only validate objects of type String."); //$NON-NLS-1$ } final String string = (String) value; // an empty String should not create an ErrorMarker if (Utils.isEmptyDate(string)) { return ValidationRuleStatus.ok(); } if (string.length() > 0 && !isDateValid(string, pattern)) { final String message = NLS.bind(Messages.AbstractValidDate_error_invalidDate, pattern); return ValidationRuleStatus.error(false, message); } } return ValidationRuleStatus.ok(); } private boolean isDateValid(String value, String datePattern) { // fix for problem report #651 // workaround for org.apache.commons.validator.GenericValidator bug #221 // https://issues.apache.org/jira/browse/VALIDATOR-221 if (datePattern.contains(DAY_COMPONENT_LETTER) && datePattern.contains(MONTH_COMPONENT_LETTER) && !datePattern.contains(YEAR_COMPONENT_LETTER)) { datePattern += SEPERATOR_KEY + YEAR_COMPONENT_LETTER + YEAR_COMPONENT_LETTER + YEAR_COMPONENT_LETTER + YEAR_COMPONENT_LETTER; value += SEPERATOR_KEY + A_LEAP_YEAR; } if (checkValidIntermediate) { return isDate(complete(value, datePattern), datePattern, false); } return isDate(value, datePattern, true); // if (checkValidIntermediate) { // return GenericValidator.isDate(complete(value, datePattern), // datePattern, false); // } // return GenericValidator.isDate(value, datePattern, true); } private boolean isDate(final String value, final String pattern, final boolean strict) { if (value == null || pattern == null || pattern.length() <= 0) { return false; } if (strict && value.length() != pattern.length()) { return false; } final SimpleDateFormat formatter = new SimpleDateFormat(pattern); formatter.setLenient(false); final ParsePosition pos = new ParsePosition(0); formatter.parse(value, pos); if (pos.getErrorIndex() != -1) { return false; } if (strict) { if (pos.getIndex() < value.length()) { return false; } } return true; } private String complete(final String value, final String datePattern) { final StringBuilder completedValue = new StringBuilder(); String valueToCheck = value; String datePatternToCheck = datePattern; int nextSeparatorIndex = nextSeparatorIndex(datePatternToCheck); while (nextSeparatorIndex != -1) { final int nextSeparatorIndexOfValueToCheck = valueToCheck.indexOf(datePatternToCheck .charAt(nextSeparatorIndex)); if (nextSeparatorIndexOfValueToCheck == -1) { break; } if (nextSeparatorIndexOfValueToCheck == 0 || isPlaceholderInvalidSubstitutionNeeded(valueToCheck.substring(0, nextSeparatorIndexOfValueToCheck))) { completedValue.append(PLACEHOLDER_VALID); completedValue.append(valueToCheck.charAt(nextSeparatorIndexOfValueToCheck)); } else if (isNoPossibleCompletionToAllowedYear(valueToCheck.substring(0, nextSeparatorIndexOfValueToCheck), datePatternToCheck)) { completedValue.append(PLACEHOLDER_INVALID); } else { completedValue.append(valueToCheck.substring(0, nextSeparatorIndexOfValueToCheck + 1)); } datePatternToCheck = datePatternToCheck.substring(nextSeparatorIndex + 1); valueToCheck = valueToCheck.substring(nextSeparatorIndexOfValueToCheck + 1); nextSeparatorIndex = nextSeparatorIndex(datePatternToCheck); } if (("".equals(valueToCheck) && !"".equals(datePatternToCheck)) //$NON-NLS-1$//$NON-NLS-2$ || isPlaceholderInvalidSubstitutionNeeded(valueToCheck)) { completedValue.append(PLACEHOLDER_VALID); } else if (isNoPossibleCompletionToAllowedYear(valueToCheck, datePatternToCheck)) { completedValue.append(PLACEHOLDER_INVALID); } else { completedValue.append(valueToCheck); } // System.out.println("completed= " + completedValue.toString()); return completedValue.toString(); } private boolean isPlaceholderInvalidSubstitutionNeeded(final String value) { return "0".equals(value); //$NON-NLS-1$ } private boolean isNoPossibleCompletionToAllowedYear(final String value, final String datePattern) { boolean result = false; if (datePattern.startsWith(IDateTextRidget.FORMAT_YYYY)) { final String trimedValue = value.trim(); result = trimedValue.length() < 2 || (trimedValue.length() > 2 && trimedValue.startsWith("0")); //$NON-NLS-1$ // System.out.println(String.format("%s '%s' %b", datePattern, trimedValue, result)); } return result; } private int nextSeparatorIndex(final String value) { for (int i = 0; i < value.length(); i++) { if (!Character.isLetter(value.charAt(i))) { return i; } } return -1; } protected void setPattern(final String pattern) { this.pattern = pattern; } protected void setCheckValidIntermediate(final boolean checkValidIntermediate) { this.checkValidIntermediate = checkValidIntermediate; } /** * This method is called on a newly constructed extension for validation. * After creating a new instance of {@code AbstractValidDate} this method is * called to initialize the instance. The argument for initialization is in * the parameter {@code data}. Is the data a string the argument is the * initial value of {@code pattern}. * * @see org.eclipse.core.runtime.IExecutableExtension#setInitializationData(org.eclipse.core.runtime.IConfigurationElement, * java.lang.String, java.lang.Object) */ public void setInitializationData(final IConfigurationElement config, final String propertyName, final Object data) throws CoreException { if (data instanceof String) { final String[] args = PropertiesUtils.asArray(data); setPattern(args[0]); } } }