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;
026
027import org.apache.commons.lang.StringUtils;
028
029/**
030 * This constraints checks whether an object's String representation size is in some interval.
031 * <p>
032 * This constraint's bounds are not strict (i.e. >= and <=).
033 * </p>
034 *
035 * @since 7.1
036 */
037public class LengthConstraint extends AbstractConstraint {
038
039    private static final long serialVersionUID = 3630463971175189087L;
040
041    private static final String NAME = "LengthConstraint";
042
043    private static final String PNAME_MIN_LENGTH = "Minimum";
044
045    private static final String PNAME_MAX_LENGTH = "Maximum";
046
047    private final Long min;
048
049    private final Long max;
050
051    /**
052     * For a fixed length, use min = max values.
053     * <p>
054     * Bounds could be any object having toString representating an integer.
055     * </p>
056     *
057     * @param min Minimum length for the validated String, use a null value to get unbounded length.
058     * @param max Maximum length for the validated String, use a null value to get unbounded length.
059     */
060    public LengthConstraint(Object min, Object max) {
061        this.min = ConstraintUtils.objectToPostiveLong(min);
062        this.max = ConstraintUtils.objectToPostiveLong(max);
063    }
064
065    @Override
066    public boolean validate(Object object) {
067        if (object == null) {
068            return true;
069        }
070        int len = object.toString().length();
071        if (min != null && len < min) {
072            return false;
073        }
074        if (max != null && len > max) {
075            return false;
076        }
077        return true;
078    }
079
080    /**
081     * Here, value is : <br>
082     * name = {@value #NAME} <br>
083     * parameters =
084     * <ul>
085     * <li>{@value #PNAME_MIN_LENGTH} : 5 // only is bounded</li>
086     * <li>{@value #PNAME_MAX_LENGTH} : 10 // only if bounded</li>
087     * </ul>
088     * </p>
089     */
090    @Override
091    public Description getDescription() {
092        Map<String, Serializable> params = new HashMap<String, Serializable>();
093        if (min != null) {
094            params.put(PNAME_MIN_LENGTH, min);
095        }
096        if (max != null) {
097            params.put(PNAME_MAX_LENGTH, max);
098        }
099        return new Description(LengthConstraint.NAME, params);
100    }
101
102    /**
103     * @return This constraints minimum length if bounded, null otherwise.
104     * @since 7.1
105     */
106    public Long getMin() {
107        return min;
108    }
109
110    /**
111     * @return This constraints maximum length if bounded, null otherwise.
112     * @since 7.1
113     */
114    public Long getMax() {
115        return max;
116    }
117
118    @Override
119    public String getErrorMessage(Object invalidValue, Locale locale) {
120        // test whether there's a custom translation for this field constraint specific translation
121        // the expected key is label.schema.constraint.violation.[ConstraintName].minmax ou
122        // the expected key is label.schema.constraint.violation.[ConstraintName].min ou
123        // the expected key is label.schema.constraint.violation.[ConstraintName].max
124        // follow the AbstractConstraint behavior otherwise
125        Object[] params;
126        String subKey;
127        if (min != null && max != null) {
128            params = new Object[] { min, max };
129            subKey = "minmax";
130        } else if (min != null) {
131            params = new Object[] { min };
132            subKey = "min";
133        } else {
134            params = new Object[] { max };
135            subKey = "max";
136        }
137        List<String> pathTokens = new ArrayList<String>();
138        pathTokens.add(MESSAGES_KEY);
139        pathTokens.add(LengthConstraint.NAME);
140        pathTokens.add(subKey);
141        String key = StringUtils.join(pathTokens, '.');
142        Locale computedLocale = locale != null ? locale : Constraint.MESSAGES_DEFAULT_LANG;
143        String message = getMessageString(MESSAGES_BUNDLE, key, params, computedLocale);
144        if (message != null && !message.trim().isEmpty() && !key.equals(message)) {
145            // use a custom constraint message if there's one
146            return message;
147        } else {
148            // follow AbstractConstraint behavior otherwise
149            return super.getErrorMessage(invalidValue, computedLocale);
150        }
151    }
152
153    @Override
154    public int hashCode() {
155        final int prime = 31;
156        int result = 1;
157        result = prime * result + ((max == null) ? 0 : max.hashCode());
158        result = prime * result + ((min == null) ? 0 : min.hashCode());
159        return result;
160    }
161
162    @Override
163    public boolean equals(Object obj) {
164        if (this == obj) {
165            return true;
166        }
167        if (obj == null) {
168            return false;
169        }
170        if (getClass() != obj.getClass()) {
171            return false;
172        }
173        LengthConstraint other = (LengthConstraint) obj;
174        if (max == null) {
175            if (other.max != null) {
176                return false;
177            }
178        } else if (!max.equals(other.max)) {
179            return false;
180        }
181        if (min == null) {
182            if (other.min != null) {
183                return false;
184            }
185        } else if (!min.equals(other.min)) {
186            return false;
187        }
188        return true;
189    }
190
191}