001/*
002 * (C) Copyright 2014 Nuxeo SA (http://nuxeo.com/) and others.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *     http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 *
016 * Contributors:
017 *     Nicolas Chapurlat <nchapurlat@nuxeo.com>
018 */
019
020package org.nuxeo.ecm.core.schema.types.constraints;
021
022import java.io.Serializable;
023import java.util.ArrayList;
024import java.util.HashMap;
025import java.util.List;
026import java.util.Locale;
027import java.util.Map;
028
029import org.apache.commons.lang.StringUtils;
030
031/**
032 * This constraints checks whether an object's String representation size is in some interval.
033 * <p>
034 * This constraint's bounds are not strict (i.e. >= and <=).
035 * </p>
036 *
037 * @since 7.1
038 */
039public class LengthConstraint extends AbstractConstraint {
040
041    private static final long serialVersionUID = 3630463971175189087L;
042
043    private static final String NAME = "LengthConstraint";
044
045    private static final String PNAME_MIN_LENGTH = "Minimum";
046
047    private static final String PNAME_MAX_LENGTH = "Maximum";
048
049    private final Long min;
050
051    private final Long max;
052
053    /**
054     * For a fixed length, use min = max values.
055     * <p>
056     * Bounds could be any object having toString representating an integer.
057     * </p>
058     *
059     * @param min Minimum length for the validated String, use a null value to get unbounded length.
060     * @param max Maximum length for the validated String, use a null value to get unbounded length.
061     */
062    public LengthConstraint(Object min, Object max) {
063        this.min = ConstraintUtils.objectToPostiveLong(min);
064        this.max = ConstraintUtils.objectToPostiveLong(max);
065    }
066
067    @Override
068    public boolean validate(Object object) {
069        if (object == null) {
070            return true;
071        }
072        int len = object.toString().length();
073        if (min != null && len < min) {
074            return false;
075        }
076        if (max != null && len > max) {
077            return false;
078        }
079        return true;
080    }
081
082    /**
083     * Here, value is : <br>
084     * name = {@value #NAME} <br>
085     * parameters =
086     * <ul>
087     * <li>{@value #PNAME_MIN_LENGTH} : 5 // only is bounded</li>
088     * <li>{@value #PNAME_MAX_LENGTH} : 10 // only if bounded</li>
089     * </ul>
090     * </p>
091     */
092    @Override
093    public Description getDescription() {
094        Map<String, Serializable> params = new HashMap<String, Serializable>();
095        if (min != null) {
096            params.put(PNAME_MIN_LENGTH, min);
097        }
098        if (max != null) {
099            params.put(PNAME_MAX_LENGTH, max);
100        }
101        return new Description(LengthConstraint.NAME, params);
102    }
103
104    /**
105     * @return This constraints minimum length if bounded, null otherwise.
106     * @since 7.1
107     */
108    public Long getMin() {
109        return min;
110    }
111
112    /**
113     * @return This constraints maximum length if bounded, null otherwise.
114     * @since 7.1
115     */
116    public Long getMax() {
117        return max;
118    }
119
120    @Override
121    public String getErrorMessage(Object invalidValue, Locale locale) {
122        // test whether there's a custom translation for this field constraint specific translation
123        // the expected key is label.schema.constraint.violation.[ConstraintName].minmax ou
124        // the expected key is label.schema.constraint.violation.[ConstraintName].min ou
125        // the expected key is label.schema.constraint.violation.[ConstraintName].max
126        // follow the AbstractConstraint behavior otherwise
127        Object[] params;
128        String subKey;
129        if (min != null && max != null) {
130            params = new Object[] { min, max };
131            subKey = "minmax";
132        } else if (min != null) {
133            params = new Object[] { min };
134            subKey = "min";
135        } else {
136            params = new Object[] { max };
137            subKey = "max";
138        }
139        List<String> pathTokens = new ArrayList<String>();
140        pathTokens.add(MESSAGES_KEY);
141        pathTokens.add(LengthConstraint.NAME);
142        pathTokens.add(subKey);
143        String key = StringUtils.join(pathTokens, '.');
144        Locale computedLocale = locale != null ? locale : Constraint.MESSAGES_DEFAULT_LANG;
145        String message = getMessageString(MESSAGES_BUNDLE, key, params, computedLocale);
146        if (message != null && !message.trim().isEmpty() && !key.equals(message)) {
147            // use a custom constraint message if there's one
148            return message;
149        } else {
150            // follow AbstractConstraint behavior otherwise
151            return super.getErrorMessage(invalidValue, computedLocale);
152        }
153    }
154
155    @Override
156    public int hashCode() {
157        final int prime = 31;
158        int result = 1;
159        result = prime * result + ((max == null) ? 0 : max.hashCode());
160        result = prime * result + ((min == null) ? 0 : min.hashCode());
161        return result;
162    }
163
164    @Override
165    public boolean equals(Object obj) {
166        if (this == obj) {
167            return true;
168        }
169        if (obj == null) {
170            return false;
171        }
172        if (getClass() != obj.getClass()) {
173            return false;
174        }
175        LengthConstraint other = (LengthConstraint) obj;
176        if (max == null) {
177            if (other.max != null) {
178                return false;
179            }
180        } else if (!max.equals(other.max)) {
181            return false;
182        }
183        if (min == null) {
184            if (other.min != null) {
185                return false;
186            }
187        } else if (!min.equals(other.min)) {
188            return false;
189        }
190        return true;
191    }
192
193}