001/*
002 * (C) Copyright 2014 Nuxeo SA (http://nuxeo.com/) and contributors.
003 *
004 * All rights reserved. This program and the accompanying materials
005 * are made available under the terms of the GNU Lesser General Public License
006 * (LGPL) version 2.1 which accompanies this distribution, and is available at
007 * http://www.gnu.org/licenses/lgpl-2.1.html
008 *
009 * This library is distributed in the hope that it will be useful,
010 * but WITHOUT ANY WARRANTY; without even the implied warranty of
011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
012 * Lesser General Public License for more details.
013 *
014 * Contributors:
015 *     Nicolas Chapurlat <nchapurlat@nuxeo.com>
016 */
017
018package org.nuxeo.ecm.core.schema.types.constraints;
019
020import java.io.Serializable;
021import java.util.ArrayList;
022import java.util.HashMap;
023import java.util.List;
024import java.util.Locale;
025import java.util.Map;
026import java.util.regex.Pattern;
027
028import org.apache.commons.lang.StringUtils;
029
030/**
031 * This constraint ensures some object's String representation match a pattern.
032 *
033 * @since 7.1
034 */
035public class PatternConstraint extends AbstractConstraint {
036
037    private static final long serialVersionUID = 1L;
038
039    private static final String NAME = "PatternConstraint";
040
041    private static final String PNAME_PATTERN = "Pattern";
042
043    protected final Pattern pattern;
044
045    public PatternConstraint(String pattern) {
046        this.pattern = Pattern.compile(pattern);
047    }
048
049    @Override
050    public boolean validate(Object object) {
051        if (object == null) {
052            return true;
053        }
054        return pattern.matcher(object.toString()).matches();
055    }
056
057    /**
058     * <p>
059     * Here, value is : <br>
060     * name = {@value #NAME} <br>
061     * parameters =
062     * <ul>
063     * <li>{@value #PNAME_PATTERN} : [0-9]+</li>
064     * </ul>
065     * </p>
066     */
067    @Override
068    public Description getDescription() {
069        Map<String, Serializable> params = new HashMap<String, Serializable>();
070        params.put(PNAME_PATTERN, pattern.pattern());
071        return new Description(PatternConstraint.NAME, params);
072    }
073
074    /**
075     * @return The pattern used by this constraint to validate.
076     * @since 7.1
077     */
078    public String getPattern() {
079        return pattern.pattern();
080    }
081
082    @Override
083    public String getErrorMessage(Object invalidValue, Locale locale) {
084        // test whether there's a custom translation for this field constraint specific translation
085        // the expected key is label.schema.constraint.violation.[ConstraintName]
086        // follow the AbstractConstraint behavior otherwise
087        List<String> pathTokens = new ArrayList<String>();
088        pathTokens.add(MESSAGES_KEY);
089        pathTokens.add(PatternConstraint.NAME);
090        String key = StringUtils.join(pathTokens, '.');
091        Object[] params = new Object[] { getPattern() };
092        Locale computedLocale = locale != null ? locale : Constraint.MESSAGES_DEFAULT_LANG;
093        String message = getMessageString(MESSAGES_BUNDLE, key, params, computedLocale);
094        if (message != null && !message.trim().isEmpty() && !key.equals(message)) {
095            // use a custom constraint message if there's one
096            return message;
097        } else {
098            // follow AbstractConstraint behavior otherwise
099            return super.getErrorMessage(invalidValue, computedLocale);
100        }
101    }
102
103    @Override
104    public int hashCode() {
105        final int prime = 31;
106        int result = 1;
107        result = prime * result + ((pattern == null) ? 0 : pattern.pattern().hashCode());
108        return result;
109    }
110
111    @Override
112    public boolean equals(Object obj) {
113        if (this == obj) {
114            return true;
115        }
116        if (obj == null) {
117            return false;
118        }
119        if (getClass() != obj.getClass()) {
120            return false;
121        }
122        PatternConstraint other = (PatternConstraint) obj;
123        if (pattern == null) {
124            if (other.pattern != null) {
125                return false;
126            }
127        } else if (!pattern.pattern().equals(other.pattern.pattern())) {
128            return false;
129        }
130        return true;
131    }
132
133}