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>@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