001/*
002 * CDDL HEADER START
003 *
004 * The contents of this file are subject to the terms of the
005 * Common Development and Distribution License, Version 1.0 only
006 * (the "License").  You may not use this file except in compliance
007 * with the License.
008 *
009 * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt
010 * or http://forgerock.org/license/CDDLv1.0.html.
011 * See the License for the specific language governing permissions
012 * and limitations under the License.
013 *
014 * When distributing Covered Code, include this CDDL HEADER in each
015 * file and include the License file at legal-notices/CDDLv1_0.txt.
016 * If applicable, add the following below this CDDL HEADER, with the
017 * fields enclosed by brackets "[]" replaced with your own identifying
018 * information:
019 *      Portions Copyright [yyyy] [name of copyright owner]
020 *
021 * CDDL HEADER END
022 *
023 *
024 *      Copyright 2008 Sun Microsystems, Inc.
025 */
026package org.forgerock.opendj.config;
027
028import org.forgerock.util.Reject;
029
030import java.util.EnumSet;
031
032/**
033 * Memory size property definition.
034 * <p>
035 * All memory size property values are represented in bytes using longs.
036 * <p>
037 * All values must be zero or positive and within the lower/upper limit
038 * constraints. Support is provided for "unlimited" memory sizes. These are
039 * represented using a negative memory size value or using the string
040 * "unlimited".
041 */
042public final class SizePropertyDefinition extends PropertyDefinition<Long> {
043
044    /** String used to represent unlimited memory sizes. */
045    private static final String UNLIMITED = "unlimited";
046
047    /** The lower limit of the property value in bytes. */
048    private final long lowerLimit;
049
050    /** The optional upper limit of the property value in bytes. */
051    private final Long upperLimit;
052
053    /**
054     * Indicates whether this property allows the use of the "unlimited" memory
055     * size value (represented using a -1L or the string "unlimited").
056     */
057    private final boolean allowUnlimited;
058
059    /**
060     * An interface for incrementally constructing memory size property
061     * definitions.
062     */
063    public static final class Builder extends AbstractBuilder<Long, SizePropertyDefinition> {
064
065        /** The lower limit of the property value in bytes. */
066        private long lowerLimit;
067
068        /** The optional upper limit of the property value in bytes. */
069        private Long upperLimit;
070
071        /**
072         * Indicates whether this property allows the use of the "unlimited" memory
073         * size value (represented using a -1L or the string "unlimited").
074         */
075        private boolean allowUnlimited;
076
077        /** Private constructor. */
078        private Builder(AbstractManagedObjectDefinition<?, ?> d, String propertyName) {
079            super(d, propertyName);
080        }
081
082        /**
083         * Set the lower limit in bytes.
084         *
085         * @param lowerLimit
086         *            The new lower limit (must be >= 0) in bytes.
087         * @throws IllegalArgumentException
088         *             If a negative lower limit was specified, or if the lower
089         *             limit is greater than the upper limit.
090         */
091        public final void setLowerLimit(long lowerLimit) {
092            if (lowerLimit < 0) {
093                throw new IllegalArgumentException("Negative lower limit");
094            }
095            if (upperLimit != null && lowerLimit > upperLimit) {
096                throw new IllegalArgumentException("Lower limit greater than upper limit");
097            }
098            this.lowerLimit = lowerLimit;
099        }
100
101        /**
102         * Set the lower limit using a string representation of the limit.
103         *
104         * @param lowerLimit
105         *            The string representation of the new lower limit.
106         * @throws IllegalArgumentException
107         *             If the lower limit could not be parsed, or if a negative
108         *             lower limit was specified, or the lower limit is greater
109         *             than the upper limit.
110         */
111        public final void setLowerLimit(String lowerLimit) {
112            setLowerLimit(SizeUnit.parseValue(lowerLimit, SizeUnit.BYTES));
113        }
114
115        /**
116         * Set the upper limit in bytes.
117         *
118         * @param upperLimit
119         *            The new upper limit in bytes or <code>null</code> if there
120         *            is no upper limit.
121         * @throws IllegalArgumentException
122         *             If the lower limit is greater than the upper limit.
123         */
124        public final void setUpperLimit(Long upperLimit) {
125            if (upperLimit != null) {
126                if (upperLimit < 0) {
127                    throw new IllegalArgumentException("Negative upper limit");
128                }
129                if (lowerLimit > upperLimit) {
130                    throw new IllegalArgumentException("Lower limit greater than upper limit");
131                }
132            }
133            this.upperLimit = upperLimit;
134        }
135
136        /**
137         * Set the upper limit using a string representation of the limit.
138         *
139         * @param upperLimit
140         *            The string representation of the new upper limit, or
141         *            <code>null</code> if there is no upper limit.
142         * @throws IllegalArgumentException
143         *             If the upper limit could not be parsed, or if the lower
144         *             limit is greater than the upper limit.
145         */
146        public final void setUpperLimit(String upperLimit) {
147            setUpperLimit(upperLimit != null ? SizeUnit.parseValue(upperLimit, SizeUnit.BYTES) : null);
148        }
149
150        /**
151         * Specify whether or not this property definition will allow unlimited
152         * values (default is false).
153         *
154         * @param allowUnlimited
155         *            <code>true</code> if the property will allow unlimited
156         *            values, or <code>false</code> otherwise.
157         */
158        public final void setAllowUnlimited(boolean allowUnlimited) {
159            this.allowUnlimited = allowUnlimited;
160        }
161
162        /** {@inheritDoc} */
163        @Override
164        protected SizePropertyDefinition buildInstance(AbstractManagedObjectDefinition<?, ?> d, String propertyName,
165            EnumSet<PropertyOption> options, AdministratorAction adminAction,
166            DefaultBehaviorProvider<Long> defaultBehavior) {
167            return new SizePropertyDefinition(d, propertyName, options, adminAction, defaultBehavior, lowerLimit,
168                upperLimit, allowUnlimited);
169        }
170
171    }
172
173    /**
174     * Create an memory size property definition builder.
175     *
176     * @param d
177     *            The managed object definition associated with this property
178     *            definition.
179     * @param propertyName
180     *            The property name.
181     * @return Returns the new integer property definition builder.
182     */
183    public static Builder createBuilder(AbstractManagedObjectDefinition<?, ?> d, String propertyName) {
184        return new Builder(d, propertyName);
185    }
186
187    /** Private constructor. */
188    private SizePropertyDefinition(AbstractManagedObjectDefinition<?, ?> d, String propertyName,
189        EnumSet<PropertyOption> options, AdministratorAction adminAction,
190        DefaultBehaviorProvider<Long> defaultBehavior, Long lowerLimit, Long upperLimit, boolean allowUnlimited) {
191        super(d, Long.class, propertyName, options, adminAction, defaultBehavior);
192        this.lowerLimit = lowerLimit;
193        this.upperLimit = upperLimit;
194        this.allowUnlimited = allowUnlimited;
195    }
196
197    /**
198     * Get the lower limit in bytes.
199     *
200     * @return Returns the lower limit in bytes.
201     */
202    public long getLowerLimit() {
203        return lowerLimit;
204    }
205
206    /**
207     * Get the upper limit in bytes.
208     *
209     * @return Returns the upper limit in bytes or <code>null</code> if there is
210     *         no upper limit.
211     */
212    public Long getUpperLimit() {
213        return upperLimit;
214    }
215
216    /**
217     * Determine whether this property allows unlimited memory sizes.
218     *
219     * @return Returns <code>true</code> if this this property allows unlimited
220     *         memory sizes.
221     */
222    public boolean isAllowUnlimited() {
223        return allowUnlimited;
224    }
225
226    /** {@inheritDoc} */
227    @Override
228    public void validateValue(Long value) {
229        Reject.ifNull(value);
230
231        if (!allowUnlimited && value < lowerLimit) {
232            throw PropertyException.illegalPropertyValueException(this, value);
233
234            // unlimited allowed
235        } else if (value >= 0 && value < lowerLimit) {
236            throw PropertyException.illegalPropertyValueException(this, value);
237        }
238
239        if (upperLimit != null && value > upperLimit) {
240            throw PropertyException.illegalPropertyValueException(this, value);
241        }
242    }
243
244    /** {@inheritDoc} */
245    @Override
246    public String encodeValue(Long value) {
247        Reject.ifNull(value);
248
249        // Make sure that we correctly encode negative values as "unlimited".
250        if (allowUnlimited && value < 0) {
251            return UNLIMITED;
252        }
253
254        // Encode the size value using the best-fit unit.
255        StringBuilder builder = new StringBuilder();
256        SizeUnit unit = SizeUnit.getBestFitUnitExact(value);
257
258        // Cast to a long to remove fractional part (which should not be there
259        // anyway as the best-fit unit should result in an exact conversion).
260        builder.append((long) unit.fromBytes(value));
261        builder.append(' ');
262        builder.append(unit);
263        return builder.toString();
264    }
265
266    /** {@inheritDoc} */
267    @Override
268    public Long decodeValue(String value) {
269        Reject.ifNull(value);
270
271        // First check for the special "unlimited" value when necessary.
272        if (allowUnlimited && UNLIMITED.equalsIgnoreCase(value.trim())) {
273            return -1L;
274        }
275
276        // Decode the value.
277        Long i;
278        try {
279            i = SizeUnit.parseValue(value, SizeUnit.BYTES);
280        } catch (NumberFormatException e) {
281            throw PropertyException.illegalPropertyValueException(this, value);
282        }
283
284        try {
285            validateValue(i);
286        } catch (PropertyException e) {
287            throw PropertyException.illegalPropertyValueException(this, value);
288        }
289        return i;
290    }
291
292    /** {@inheritDoc} */
293    @Override
294    public <R, P> R accept(PropertyDefinitionVisitor<R, P> v, P p) {
295        return v.visitSize(this, p);
296    }
297
298    /** {@inheritDoc} */
299    @Override
300    public <R, P> R accept(PropertyValueVisitor<R, P> v, Long value, P p) {
301        return v.visitSize(this, value, p);
302    }
303
304    /** {@inheritDoc} */
305    @Override
306    public void toString(StringBuilder builder) {
307        super.toString(builder);
308
309        builder.append(" lowerLimit=");
310        builder.append(lowerLimit);
311
312        if (upperLimit != null) {
313            builder.append(" upperLimit=");
314            builder.append(upperLimit);
315        }
316
317        builder.append(" allowUnlimited=");
318        builder.append(allowUnlimited);
319
320    }
321
322    /** {@inheritDoc} */
323    @Override
324    public int compare(Long o1, Long o2) {
325        return o1.compareTo(o2);
326    }
327
328}