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