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>@param axisCode Axis code, must be a {@link AxisCode} value 182 * @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