001: /*
002: * Janino - An embedded Java[TM] compiler
003: *
004: * Copyright (c) 2006, Arno Unkrig
005: * All rights reserved.
006: *
007: * Redistribution and use in source and binary forms, with or without
008: * modification, are permitted provided that the following conditions
009: * are met:
010: *
011: * 1. Redistributions of source code must retain the above copyright
012: * notice, this list of conditions and the following disclaimer.
013: * 2. Redistributions in binary form must reproduce the above
014: * copyright notice, this list of conditions and the following
015: * disclaimer in the documentation and/or other materials
016: * provided with the distribution.
017: * 3. The name of the author may not be used to endorse or promote
018: * products derived from this software without specific prior
019: * written permission.
020: *
021: * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
022: * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
023: * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
024: * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
025: * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
026: * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
027: * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
028: * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
029: * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
030: * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
031: * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
032: */
033:
034: package org.codehaus.janino.util.enumerator;
035:
036: import java.util.*;
037:
038: /**
039: * A class that represents an immutable set of {@link Enumerator}s.
040: * <p>
041: * Its main features are its constructor, which initializes the object from a clear-text string,
042: * and its {@link #toString()} method, which reconstructs the clear text values.
043: * <p>
044: * Sample code can be found in the documentation of {@link Enumerator}.
045: */
046: public class EnumeratorSet {
047: private final Class enumeratorClass;
048: private final Set values; // Enumerator-derived class
049: /*package*/String optionalName = null;
050:
051: private EnumeratorSet(Class enumeratorClass, Set values) {
052: this .enumeratorClass = enumeratorClass;
053: this .values = values;
054: }
055:
056: /**
057: * Construct an empty set for values of the given {@link Enumerator}-derived type.
058: */
059: public EnumeratorSet(Class enumeratorClass) {
060: this (enumeratorClass, new HashSet());
061: }
062:
063: /**
064: * Construct a set for values of the given {@link Enumerator}-derived type. If the
065: * <code>full</code> flag is <code>true</code>, all possible values are added to the set.
066: */
067: public EnumeratorSet(Class enumeratorClass, boolean full) {
068: this (enumeratorClass, new HashSet());
069: if (full)
070: this .values.addAll(Enumerator.getInstances(enumeratorClass)
071: .values());
072: }
073:
074: /**
075: * Construct a set for values of the given {@link Enumerator}-derived type and initialize it
076: * from a string.
077: * <p>
078: * Equivalent to <code>EnumeratorSet(enumeratorClass, s, ",")</code>.
079: */
080: public EnumeratorSet(Class enumeratorClass, String s)
081: throws EnumeratorFormatException {
082: this (enumeratorClass, s, ",");
083: }
084:
085: /**
086: * Construct a set for values of the given {@link Enumerator}-derived type and initialize it
087: * from a string.
088: * <p>
089: * The given string is parsed into tokens; each token is converted into a value as
090: * {@link Enumerator#fromString(String, Class)} does and added to this set. Named {@link EnumeratorSet}s
091: * declared in the <code>enumeratorClass</code> are also recognized and added. If the string names exactly one
092: * of those {@link EnumeratorSet}s declared in the <code>enumeratorClass</code>, then the resulting set
093: * inherits the name of theat {@link EnumeratorSet}.
094: *
095: * @throws EnumeratorFormatException if a token cannot be identified
096: */
097: public EnumeratorSet(Class enumeratorClass, String s,
098: String delimiter) throws EnumeratorFormatException {
099: Set vs = new HashSet();
100:
101: Map des = Enumerator.getInstances(enumeratorClass);
102: Map dess = EnumeratorSet
103: .getNamedEnumeratorSets(enumeratorClass);
104:
105: StringTokenizer st = new StringTokenizer(s, delimiter);
106: while (st.hasMoreTokens()) {
107: String name = st.nextToken();
108: Enumerator value = (Enumerator) des.get(name);
109: if (value != null) {
110: vs.add(value);
111: continue;
112: }
113:
114: EnumeratorSet es = (EnumeratorSet) dess.get(name);
115: if (es != null) {
116: if (vs.isEmpty()) {
117: vs = es.values;
118: this .optionalName = es.optionalName;
119: } else {
120: vs.addAll(es.values);
121: }
122: continue;
123: }
124:
125: throw new EnumeratorFormatException(name);
126: }
127:
128: this .enumeratorClass = enumeratorClass;
129: this .values = vs;
130: }
131:
132: /**
133: * Construct a copy of the given set.
134: */
135: public EnumeratorSet(EnumeratorSet that) {
136: this (that.enumeratorClass, that.values);
137: }
138:
139: /**
140: * Add the given value to the set.
141: *
142: * @throws EnumeratorSetTypeException if this set was constructed for a different {@link Enumerator}-derived type
143: */
144: public EnumeratorSet add(Enumerator value) {
145: if (value.getClass() != this .enumeratorClass)
146: throw new EnumeratorSetTypeException(
147: "Cannot add value of type \"" + value.getClass()
148: + "\" to set of different type \""
149: + this .enumeratorClass + "\"");
150: Set vs = new HashSet(this .values);
151: vs.add(value);
152: return new EnumeratorSet(this .enumeratorClass, vs);
153: }
154:
155: /**
156: * Add the values of the given set to this set.
157: *
158: * @throws EnumeratorSetTypeException if this set was constructed for a different {@link Enumerator}-derived type
159: */
160: public EnumeratorSet add(EnumeratorSet that) {
161: if (that.enumeratorClass != this .enumeratorClass)
162: throw new EnumeratorSetTypeException(
163: "Cannot add set of type \"" + that.enumeratorClass
164: + "\" to set of different type \""
165: + this .enumeratorClass + "\"");
166: Set vs;
167: if (this .values.isEmpty()) {
168: vs = that.values;
169: } else if (that.values.isEmpty()) {
170: vs = this .values;
171: } else {
172: vs = new HashSet(this .values);
173: vs.addAll(that.values);
174: }
175: return new EnumeratorSet(this .enumeratorClass, vs);
176: }
177:
178: /**
179: * If this {@link EnumeratorSet} contains the given <code>value</code>, return an
180: * {@link EnumeratorSet} that lacks the <code>value</code>. Otherwise, return this
181: * {@link EnumeratorSet}.
182: *
183: * @return the reduced set
184: *
185: * @throws EnumeratorSetTypeException if this set was constructed for a different {@link Enumerator}-derived type
186: */
187: public EnumeratorSet remove(Enumerator value) {
188: if (value.getClass() != this .enumeratorClass)
189: throw new EnumeratorSetTypeException(
190: "Cannot remove value of type \"" + value.getClass()
191: + "\" from set of different type \""
192: + this .enumeratorClass + "\"");
193: if (!this .values.contains(value))
194: return this ;
195: Set vs = new HashSet(this .values);
196: vs.remove(value);
197: return new EnumeratorSet(this .enumeratorClass, vs);
198: }
199:
200: /**
201: * Return this {@link EnumeratorSet} less <code>that</code> {@link EnumeratorSet}.
202: *
203: * @return the reduced set
204: *
205: * @throws EnumeratorSetTypeException if this set was constructed for a different {@link Enumerator}-derived type
206: */
207: public EnumeratorSet remove(EnumeratorSet that) {
208: if (that.enumeratorClass != this .enumeratorClass)
209: throw new EnumeratorSetTypeException(
210: "Cannot remove set of type \""
211: + that.enumeratorClass
212: + "\" from set of different type \""
213: + this .enumeratorClass + "\"");
214: Set vs = new HashSet(this .values);
215: vs.removeAll(that.values);
216: return new EnumeratorSet(this .enumeratorClass, vs);
217: }
218:
219: /**
220: * Check whether this set contains the given value
221: *
222: * @throws EnumeratorSetTypeException if this set was constructed for a different {@link Enumerator}-derived type
223: */
224: public boolean contains(Enumerator value) {
225: if (value.getClass() != this .enumeratorClass)
226: throw new EnumeratorSetTypeException(
227: "Cannot check value of type \"" + value.getClass()
228: + "\" within set of different type \""
229: + this .enumeratorClass + "\"");
230: return this .values.contains(value);
231: }
232:
233: /**
234: * Check if this set contains any of the values of the given set.
235: * <p>
236: * Returns <code>false</code> if either of the two sets is empty.
237: *
238: * @throws EnumeratorSetTypeException if this set was constructed for a different {@link Enumerator}-derived type
239: */
240: public boolean containsAnyOf(EnumeratorSet that) {
241: if (that.enumeratorClass != this .enumeratorClass)
242: throw new EnumeratorSetTypeException(
243: "Cannot compare set of type \""
244: + that.enumeratorClass
245: + "\" with set of different type \""
246: + this .enumeratorClass + "\"");
247: for (Iterator it = that.values.iterator(); it.hasNext();) {
248: if (this .values.contains(it.next()))
249: return true;
250: }
251: return false;
252: }
253:
254: /**
255: * Check if this set contains all values of the given set.
256: *
257: * @throws EnumeratorSetTypeException if this set was constructed for a different {@link Enumerator}-derived type
258: */
259: public boolean containsAllOf(EnumeratorSet that) {
260: if (that.enumeratorClass != this .enumeratorClass)
261: throw new EnumeratorSetTypeException(
262: "Cannot compare set of type \""
263: + that.enumeratorClass
264: + "\" with set of different type \""
265: + this .enumeratorClass + "\"");
266: return this .values.containsAll(that.values);
267: }
268:
269: /**
270: * An {@link EnumeratorSet} can optionally be assigned a name, which is used by
271: * {@link #toString()}.
272: *
273: * @return this object
274: */
275: public EnumeratorSet setName(String optionalName) {
276:
277: // Check for non-change.
278: if ((this .optionalName == optionalName)
279: || (this .optionalName != null && this .optionalName
280: .equals(optionalName)))
281: return this ;
282:
283: // Track all named EnumeratorSet instances.
284: Map namedEnumeratorSets = EnumeratorSet
285: .getNamedEnumeratorSets(this .enumeratorClass);
286: if (this .optionalName != null)
287: namedEnumeratorSets.remove(this .optionalName);
288: this .optionalName = optionalName;
289: if (this .optionalName != null)
290: namedEnumeratorSets.put(this .optionalName, this );
291:
292: return this ;
293: }
294:
295: /**
296: * Returns a map of all {@link EnumeratorSet}s instantiated for the given <code>enumeratorClass</code>.
297: *
298: * @return String name => EnumeratorSet
299: */
300: private static Map getNamedEnumeratorSets(Class enumeratorClass) {
301: Map m = (Map) EnumeratorSet.namedEnumeratorSets
302: .get(enumeratorClass);
303: if (m == null) {
304: m = new HashMap();
305: EnumeratorSet.namedEnumeratorSets.put(enumeratorClass, m);
306: }
307: return m;
308: }
309:
310: private static final Map namedEnumeratorSets = new HashMap(); // Class enumeratorClass => String name => EnumeratorSet
311:
312: /**
313: * Check the values' identity. Notice that the objects' names (see {@link #setName(String)} is
314: * not considered.
315: */
316: public boolean equals(Object that) {
317: return that instanceof EnumeratorSet
318: && this .values.equals(((EnumeratorSet) that).values);
319: }
320:
321: public int hashCode() {
322: return this .values.hashCode();
323: }
324:
325: /**
326: * Convert an {@link EnumeratorSet} to a clear-text string.
327: * <p>
328: * Identical with <code>toString(",")</code>.
329: */
330: public String toString() {
331: return this .toString(",");
332: }
333:
334: /**
335: * Convert an {@link EnumeratorSet} into a clear-text string.
336: * <p>
337: * If this {@link EnumeratorSet} has a name (see {@link #setName(String)}, then this name is
338: * returned.
339: * <p>
340: * Otherwise, if this {@link EnumeratorSet} is empty, an empty {@link String} is returned.
341: * <p>
342: * Otherwise, the values' names are concatenated, separated by the given delimiter, and returned.
343: */
344: public String toString(String delimiter) {
345:
346: // If this EnumeratorSet has a name, then everything is very simple.
347: if (this .optionalName != null)
348: return this .optionalName;
349:
350: // Return "" for an empty set.
351: Iterator it = this .values.iterator();
352: if (!it.hasNext())
353: return "";
354:
355: // Concatenate the enumerators' names.
356: StringBuffer sb = new StringBuffer(
357: ((Enumerator) it.next()).name);
358: while (it.hasNext()) {
359: sb.append(delimiter).append(((Enumerator) it.next()).name);
360: }
361: return sb.toString();
362: }
363: }
|