001 /*
002 // $Id: //open/mondrian-release/3.0/src/main/mondrian/olap/EnumeratedValues.java#2 $
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>@param axisCode Axis code, must be a {@link AxisCode} value
181 * @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