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