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