001/*
002// This software is subject to the terms of the Eclipse Public License v1.0
003// Agreement, available at the following URL:
004// http://www.eclipse.org/legal/epl-v10.html.
005// You must accept the terms of that agreement to use this software.
006//
007// Copyright (C) 1998-2005 Julian Hyde
008// Copyright (C) 2005-2009 Pentaho and others
009// All Rights Reserved.
010*/
011package mondrian.olap;
012
013import java.util.*;
014
015/**
016 * <code>EnumeratedValues</code> is a helper class for declaring a set of
017 * symbolic constants which have names, ordinals, and possibly descriptions.
018 * The ordinals do not have to be contiguous.
019 *
020 * <p>Typically, for a particular set of constants, you derive a class from this
021 * interface, and declare the constants as <code>public static final</code>
022 * members. Give it a private constructor, and a <code>public static final
023 * <i>ClassName</i> instance</code> member to hold the singleton instance.
024 * {@link Access} is a simple example of this.</p>
025 */
026public class EnumeratedValues<V extends EnumeratedValues.Value>
027    implements Cloneable
028{
029    /** Map symbol names to values */
030    private Map<String, V> valuesByName = new LinkedHashMap<String, V>();
031
032    /** the smallest ordinal value */
033    private int min = Integer.MAX_VALUE;
034
035    /** the largest ordinal value */
036    private int max = Integer.MIN_VALUE;
037
038    // the variables below are only set AFTER makeImmutable() has been called
039
040    /** An array mapping ordinals to {@link Value}s. It is biased by the
041     * min value. It is built by {@link #makeImmutable}. */
042    private Value[] ordinalToValueMap;
043    private static final String[] emptyStringArray = new String[0];
044
045    /**
046     * Creates a new empty, mutable enumeration.
047     */
048    public EnumeratedValues() {
049    }
050
051    /**
052     * Creates an enumeration, with an array of values, and freezes it.
053     */
054    public EnumeratedValues(V[] values) {
055        for (V value : values) {
056            register(value);
057        }
058        makeImmutable();
059    }
060
061    /**
062     * Creates an enumeration, initialize it with an array of strings, and
063     * freezes it.
064     */
065    public EnumeratedValues(String[] names) {
066        for (int i = 0; i < names.length; i++) {
067            register((V) new BasicValue(names[i], i, names[i]));
068        }
069        makeImmutable();
070    }
071
072    /**
073     * Create an enumeration, initializes it with arrays of code/name pairs,
074     * and freezes it.
075     */
076    public EnumeratedValues(String[] names, int[] codes) {
077        for (int i = 0; i < names.length; i++) {
078            register((V) new BasicValue(names[i], codes[i], names[i]));
079        }
080        makeImmutable();
081    }
082
083    /**
084     * Create an enumeration, initializes it with arrays of code/name pairs,
085     * and freezes it.
086     */
087    public EnumeratedValues(String[] names, int[] codes, String[] descriptions)
088    {
089        for (int i = 0; i < names.length; i++) {
090            register((V) new BasicValue(names[i], codes[i], descriptions[i]));
091        }
092        makeImmutable();
093    }
094
095    public EnumeratedValues(Class<? extends Enum> clazz) {
096        throw new UnsupportedOperationException();
097    }
098
099    public EnumeratedValues<V> clone() {
100        EnumeratedValues clone;
101        try {
102            clone = (EnumeratedValues) super.clone();
103        } catch (CloneNotSupportedException ex) {
104            throw Util.newInternal(ex, "error while cloning " + this);
105        }
106        clone.valuesByName = new HashMap<String, Value>(valuesByName);
107        clone.ordinalToValueMap = null;
108        return clone;
109    }
110
111    /**
112     * Creates a mutable enumeration from an existing enumeration, which may
113     * already be immutable.
114     */
115    public EnumeratedValues getMutableClone() {
116        return clone();
117    }
118
119    /**
120     * Associates a symbolic name with an ordinal value.
121     *
122     * @pre value != null
123     * @pre !isImmutable()
124     * @pre value.getName() != null
125     */
126    public void register(V value) {
127        assert value != null : "pre: value != null";
128        Util.assertPrecondition(!isImmutable(), "isImmutable()");
129        final String name = value.getName();
130        Util.assertPrecondition(name != null, "value.getName() != null");
131        Value old = valuesByName.put(name, value);
132        if (old != null) {
133            throw Util.newInternal(
134                "Enumeration already contained a value '" + old.getName()
135                + "'");
136        }
137        final int ordinal = value.getOrdinal();
138        min = Math.min(min, ordinal);
139        max = Math.max(max, ordinal);
140    }
141
142    /**
143     * Freezes the enumeration, preventing it from being further modified.
144     */
145    public void makeImmutable() {
146        ordinalToValueMap = new Value[1 + max - min];
147        for (Value value : valuesByName.values()) {
148            final int index = value.getOrdinal() - min;
149            if (ordinalToValueMap[index] != null) {
150                throw Util.newInternal(
151                    "Enumeration has more than one value with ordinal "
152                    + value.getOrdinal());
153            }
154            ordinalToValueMap[index] = value;
155        }
156    }
157
158    public final boolean isImmutable() {
159        return (ordinalToValueMap != null);
160    }
161
162    /**
163     * Returns the smallest ordinal defined by this enumeration.
164     */
165    public final int getMin() {
166        return min;
167    }
168
169    /**
170     * Returns the largest ordinal defined by this enumeration.
171     */
172    public final int getMax() {
173        return max;
174    }
175
176    /**
177     * Returns whether <code>ordinal</code> is valid for this enumeration.
178     * This method is particularly useful in pre- and post-conditions, for
179     * example
180     * <blockquote>
181     * <pre>&#64;param axisCode Axis code, must be a {&#64;link AxisCode} value
182     * &#64;pre AxisCode.instance.isValid(axisCode)</pre>
183     * </blockquote>
184     *
185     * @param ordinal Suspected ordinal from this enumeration.
186     * @return Whether <code>ordinal</code> is valid.
187     */
188    public final boolean isValid(int ordinal) {
189        if ((ordinal < min) || (ordinal > max)) {
190            return false;
191        }
192        if (getName(ordinal) == null) {
193            return false;
194        }
195        return true;
196    }
197
198    /**
199     * Returns the name associated with an ordinal; the return value
200     * is null if the ordinal is not a member of the enumeration.
201     *
202     * @pre isImmutable()
203     */
204    public final V getValue(int ordinal) {
205        Util.assertPrecondition(isImmutable());
206
207        return (V) ordinalToValueMap[ordinal - min];
208    }
209
210    /**
211     * Returns the name associated with an ordinal; the return value
212     * is null if the ordinal is not a member of the enumeration.
213     *
214     * @pre isImmutable()
215     */
216    public final String getName(int ordinal) {
217        Util.assertPrecondition(isImmutable());
218
219        final Value value = ordinalToValueMap[ordinal - min];
220        return (value == null) ? null : value.getName();
221    }
222
223    /**
224     * Returns the description associated with an ordinal; the return value
225     * is null if the ordinal is not a member of the enumeration.
226     *
227     * @pre isImmutable()
228     */
229    public final String getDescription(int ordinal)
230    {
231        Util.assertPrecondition(isImmutable());
232
233        final Value value = ordinalToValueMap[ordinal - min];
234        return (value == null) ? null : value.getDescription();
235    }
236
237    /**
238     * Returns the ordinal associated with a name
239     *
240     * @throws Error if the name is not a member of the enumeration
241     */
242    public final int getOrdinal(String name) {
243        return getValue(name, true).getOrdinal();
244    }
245
246    /**
247     * Returns the value associated with a name.
248     *
249     * @param name Name of enumerated value
250     * @param fail Whether to throw if not found
251     * @throws Error if the name is not a member of the enumeration and
252     *       <code>fail</code> is true
253     */
254    public V getValue(String name, final boolean fail) {
255        final V value = valuesByName.get(name);
256        if (value == null && fail) {
257            throw new Error("Unknown enum name:  " + name);
258        }
259        return value;
260    }
261
262    /**
263     * Returns the names in this enumeration, in declaration order.
264     */
265    public String[] getNames() {
266        return valuesByName.keySet().toArray(emptyStringArray);
267    }
268
269    /**
270     * Returns the members of this enumeration, sorted by name.
271     */
272    public List<V> getValuesSortedByName() {
273        List<V> list = new ArrayList<V>();
274        final String[] names = getNames();
275        Arrays.sort(names);
276        for (String name : names) {
277            list.add(getValue(name, true));
278        }
279        return list;
280    }
281
282    /**
283     * Returns an error indicating that the value is illegal. (The client needs
284     * to throw the error.)
285     */
286    public RuntimeException badValue(int ordinal) {
287        return Util.newInternal(
288            "bad value " + ordinal + "("
289            + getName(ordinal) + ") for enumeration '"
290            + getClass().getName() + "'");
291    }
292
293    /**
294     * Returns an exception indicating that we didn't expect to find this value
295     * here.
296     */
297    public RuntimeException unexpected(V value) {
298        return Util.newInternal(
299            "Was not expecting value '" + value
300            + "' for enumeration '" + getClass().getName()
301            + "' in this context");
302    }
303
304    /**
305     * A <code>Value</code> represents a member of an enumerated type. If an
306     * enumerated type is not based upon an explicit array of values, an
307     * array of {@link BasicValue}s will implicitly be created.
308     */
309    public interface Value {
310        String getName();
311        int getOrdinal();
312        String getDescription();
313    }
314
315    /**
316     * <code>BasicValue</code> is an obvious implementation of {@link
317     * EnumeratedValues.Value}.
318     */
319    public static class BasicValue implements Value {
320        public final String name;
321        public final int ordinal;
322        public final String description;
323
324        /**
325         * @pre name != null
326         */
327        public BasicValue(String name, int ordinal, String description) {
328            Util.assertPrecondition(name != null, "name != null");
329            this.name = name;
330            this.ordinal = ordinal;
331            this.description = description;
332        }
333
334        public String getName() {
335            return name;
336        }
337
338        public int getOrdinal() {
339            return ordinal;
340        }
341
342        public String getDescription() {
343            return description;
344        }
345
346        /**
347         * Returns the value's name.
348         */
349        public String toString() {
350            return name;
351        }
352
353        /**
354         * Returns whether this value is equal to a given string.
355         *
356         * @deprecated I bet you meant to write
357         *   <code>value.name_.equals(s)</code> rather than
358         *   <code>value.equals(s)</code>, didn't you?
359         */
360        public boolean equals(String s) {
361            return super.equals(s);
362        }
363
364        /**
365         * Returns an error indicating that we did not expect to find this
366         * value in this context. Typical use is in a <code>switch</code>
367         * statement:
368         *
369         * <blockquote><pre>
370         * switch (fruit) {
371         * case Fruit.AppleORDINAL:
372         *     return 1;
373         * case Fruir.OrangeORDINAL:
374         *     return 2;
375         * default:
376         *     throw fruit.unexpected();
377         * }</pre></blockquote>
378         */
379        public RuntimeException unexpected() {
380            return Util.newInternal(
381                "Value " + name + " of class "
382                + getClass() + " unexpected here");
383        }
384    }
385
386}
387
388// End EnumeratedValues.java