001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common
008: * Development and Distribution License("CDDL") (collectively, the
009: * "License"). You may not use this file except in compliance with the
010: * License. You can obtain a copy of the License at
011: * http://www.netbeans.org/cddl-gplv2.html
012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
013: * specific language governing permissions and limitations under the
014: * License. When distributing the software, include this License Header
015: * Notice in each file and include the License file at
016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
017: * particular file as subject to the "Classpath" exception as provided
018: * by Sun in the GPL Version 2 section of the License file that
019: * accompanied this code. If applicable, add the following below the
020: * License Header, with the fields enclosed by brackets [] replaced by
021: * your own identifying information:
022: * "Portions Copyrighted [year] [name of copyright owner]"
023: *
024: * Contributor(s):
025: *
026: * The Original Software is NetBeans. The Initial Developer of the Original
027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
028: * Microsystems, Inc. All Rights Reserved.
029: *
030: * If you wish your version of this file to be governed by only the CDDL
031: * or only the GPL Version 2, indicate your decision by adding
032: * "[Contributor] elects to include this software in this distribution
033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
034: * single choice of license, a recipient has the option to distribute
035: * your version of this file under either the CDDL, the GPL Version 2 or
036: * to extend the choice of license to its licensees as provided above.
037: * However, if you add GPL Version 2 code and therefore, elected the GPL
038: * Version 2 license, then the option applies only if the new code is
039: * made subject to such option by the copyright holder.
040: */
041:
042: package org.openide.util;
043:
044: import java.io.Serializable;
045: import java.util.AbstractMap;
046: import java.util.AbstractSet;
047: import java.util.ArrayList;
048: import java.util.Collection;
049: import java.util.Enumeration;
050: import java.util.HashMap;
051: import java.util.HashSet;
052: import java.util.Iterator;
053: import java.util.LinkedList;
054: import java.util.List;
055: import java.util.Map;
056: import java.util.NoSuchElementException;
057: import java.util.RandomAccess;
058: import java.util.Set;
059: import java.util.logging.Level;
060: import java.util.logging.Logger;
061:
062: /**
063: * Utilities for working with generics.
064: * <p>Note that there is no <code>checkedListByFilter</code> method currently.
065: * If constant-time operation is important (e.g. your raw list is large and {@link RandomAccess})
066: * you can use {@link #checkedListByCopy}, assuming you do not need to modify the underlying list.
067: * If you are only interested in an iterator anyway, try {@link #checkedIteratorByFilter}.
068: * @author Jesse Glick
069: * @since org.openide.util 7.1
070: */
071: public class NbCollections {
072:
073: private NbCollections() {
074: }
075:
076: private static final Logger LOG = Logger
077: .getLogger(NbCollections.class.getName());
078:
079: /**
080: * Create a typesafe copy of a raw set.
081: * @param rawSet an unchecked set
082: * @param type the desired supertype of the entries
083: * @param strict true to throw a <code>ClassCastException</code> if the raw set has an invalid entry,
084: * false to skip over such entries (warnings may be logged)
085: * @return a typed set guaranteed to contain only entries assignable
086: * to the named type (or they may be null)
087: * @throws ClassCastException if some entry in the raw set was not well-typed, and only if <code>strict</code> was true
088: */
089: public static <E> Set<E> checkedSetByCopy(Set rawSet,
090: Class<E> type, boolean strict) throws ClassCastException {
091: Set<E> s = new HashSet<E>(rawSet.size() * 4 / 3 + 1);
092: Iterator it = rawSet.iterator();
093: while (it.hasNext()) {
094: Object e = it.next();
095: try {
096: s.add(type.cast(e));
097: } catch (ClassCastException x) {
098: if (strict) {
099: throw x;
100: } else {
101: LOG.log(Level.WARNING,
102: "Element {0} not assignable to {1}",
103: new Object[] { e, type });
104: }
105: }
106: }
107: return s;
108: }
109:
110: /**
111: * Create a typesafe copy of a raw list.
112: * @param rawList an unchecked list
113: * @param type the desired supertype of the entries
114: * @param strict true to throw a <code>ClassCastException</code> if the raw list has an invalid entry,
115: * false to skip over such entries (warnings may be logged)
116: * @return a typed list guaranteed to contain only entries assignable
117: * to the named type (or they may be null)
118: * @throws ClassCastException if some entry in the raw list was not well-typed, and only if <code>strict</code> was true
119: */
120: public static <E> List<E> checkedListByCopy(List rawList,
121: Class<E> type, boolean strict) throws ClassCastException {
122: List<E> l = (rawList instanceof RandomAccess) ? new ArrayList<E>(
123: rawList.size())
124: : new LinkedList<E>();
125: Iterator it = rawList.iterator();
126: while (it.hasNext()) {
127: Object e = it.next();
128: try {
129: l.add(type.cast(e));
130: } catch (ClassCastException x) {
131: if (strict) {
132: throw x;
133: } else {
134: LOG.log(Level.WARNING,
135: "Element {0} not assignable to {1}",
136: new Object[] { e, type });
137: }
138: }
139: }
140: return l;
141: }
142:
143: /**
144: * Create a typesafe copy of a raw map.
145: * @param rawMap an unchecked map
146: * @param keyType the desired supertype of the keys
147: * @param valueType the desired supertype of the values
148: * @param strict true to throw a <code>ClassCastException</code> if the raw map has an invalid key or value,
149: * false to skip over such map entries (warnings may be logged)
150: * @return a typed map guaranteed to contain only keys and values assignable
151: * to the named types (or they may be null)
152: * @throws ClassCastException if some key or value in the raw map was not well-typed, and only if <code>strict</code> was true
153: */
154: public static <K, V> Map<K, V> checkedMapByCopy(Map rawMap,
155: Class<K> keyType, Class<V> valueType, boolean strict)
156: throws ClassCastException {
157: Map<K, V> m2 = new HashMap<K, V>(rawMap.size() * 4 / 3 + 1);
158: Iterator it = rawMap.entrySet().iterator();
159: while (it.hasNext()) {
160: Map.Entry e = (Map.Entry) it.next();
161: try {
162: m2.put(keyType.cast(e.getKey()), valueType.cast(e
163: .getValue()));
164: } catch (ClassCastException x) {
165: if (strict) {
166: throw x;
167: } else {
168: LOG.log(Level.WARNING,
169: "Entry {0} not assignable to <{1},{2}>",
170: new Object[] { e, keyType, valueType });
171: }
172: }
173: }
174: return m2;
175: }
176:
177: private static abstract class CheckedIterator<E> implements
178: Iterator<E> {
179:
180: private static final Object WAITING = new Object();
181:
182: private final Iterator it;
183: private Object next = WAITING;
184:
185: public CheckedIterator(Iterator it) {
186: this .it = it;
187: }
188:
189: protected abstract boolean accept(Object o);
190:
191: public boolean hasNext() {
192: if (next != WAITING) {
193: return true;
194: }
195: while (it.hasNext()) {
196: next = it.next();
197: if (accept(next)) {
198: return true;
199: }
200: }
201: next = WAITING;
202: return false;
203: }
204:
205: public E next() {
206: if (next == WAITING && !hasNext()) {
207: throw new NoSuchElementException();
208: }
209: assert next != WAITING;
210: @SuppressWarnings("unchecked")
211: // type-checking is done by accept()
212: E x = (E) next;
213: next = WAITING;
214: return x;
215: }
216:
217: public void remove() {
218: it.remove();
219: }
220:
221: }
222:
223: /**
224: * Create a typesafe filter of an unchecked iterator.
225: * {@link Iterator#remove} will work if it does in the unchecked iterator.
226: * @param rawIterator an unchecked iterator
227: * @param type the desired enumeration type
228: * @param strict if false, elements which are not null but not assignable to the requested type are omitted;
229: * if true, {@link ClassCastException} may be thrown from an iterator operation
230: * @return an iterator guaranteed to contain only objects of the requested type (or null)
231: */
232: public static <E> Iterator<E> checkedIteratorByFilter(
233: Iterator rawIterator, final Class<E> type,
234: final boolean strict) {
235: return new CheckedIterator<E>(rawIterator) {
236: protected boolean accept(Object o) {
237: if (o == null) {
238: return true;
239: } else if (type.isInstance(o)) {
240: return true;
241: } else if (strict) {
242: throw new ClassCastException(o + " was not a "
243: + type.getName()); // NOI18N
244: } else {
245: return false;
246: }
247: }
248: };
249: }
250:
251: /**
252: * Create a typesafe view over an underlying raw set.
253: * Mutations affect the underlying set (this is not a copy).
254: * {@link Set#clear} will make the view empty but may not clear the underlying set.
255: * You may add elements only of the requested type.
256: * {@link Set#contains} also performs a type check and will throw {@link ClassCastException}
257: * for an illegal argument.
258: * The view is serializable if the underlying set is.
259: * @param rawSet an unchecked set
260: * @param type the desired element type
261: * @param strict if false, elements in the underlying set which are not null and which are not assignable
262: * to the requested type are omitted from the view;
263: * if true, a {@link ClassCastException} may arise during some set operation
264: * @return a view over the raw set guaranteed to match the specified type
265: */
266: public static <E> Set<E> checkedSetByFilter(Set rawSet,
267: Class<E> type, boolean strict) {
268: return new CheckedSet<E>(rawSet, type, strict);
269: }
270:
271: private static final class CheckedSet<E> extends AbstractSet<E>
272: implements Serializable {
273:
274: private static final long serialVersionUID = 1L;
275:
276: private final Set rawSet;
277: private final Class<E> type;
278: private final boolean strict;
279:
280: public CheckedSet(Set rawSet, Class<E> type, boolean strict) {
281: this .rawSet = rawSet;
282: this .type = type;
283: this .strict = strict;
284: }
285:
286: private boolean acceptEntry(Object o) {
287: if (o == null) {
288: return true;
289: } else if (type.isInstance(o)) {
290: return true;
291: } else if (strict) {
292: throw new ClassCastException(o + " was not a "
293: + type.getName()); // NOI18N
294: } else {
295: return false;
296: }
297: }
298:
299: @Override
300: public Iterator<E> iterator() {
301: return new CheckedIterator<E>(rawSet.iterator()) {
302: @Override
303: protected boolean accept(Object o) {
304: return acceptEntry(o);
305: }
306: };
307: }
308:
309: @Override
310: public int size() {
311: int c = 0;
312: Iterator it = rawSet.iterator();
313: while (it.hasNext()) {
314: if (acceptEntry(it.next())) {
315: c++;
316: }
317: }
318: return c;
319: }
320:
321: @Override
322: @SuppressWarnings("unchecked")
323: // complains about usage of raw set
324: public boolean add(E o) {
325: return rawSet.add(type.cast(o));
326: }
327:
328: @Override
329: public boolean contains(Object o) {
330: return rawSet.contains(type.cast(o));
331: }
332:
333: }
334:
335: /**
336: * Create a typesafe view over an underlying raw map.
337: * Mutations affect the underlying map (this is not a copy).
338: * {@link Map#clear} will make the view empty but may not clear the underlying map.
339: * You may add entries only of the requested type pair.
340: * {@link Map#get}, {@link Map#containsKey}, and {@link Map#containsValue} also perform a type check
341: * and will throw {@link ClassCastException} for an illegal argument.
342: * The view is serializable if the underlying map is.
343: * @param rawMap an unchecked map
344: * @param keyType the desired entry key type
345: * @param valueType the desired entry value type
346: * @param strict if false, entries in the underlying map for which the key is not null but not assignable
347: * to the requested key type, and/or the value is not null but not assignable to
348: * the requested value type, are omitted from the view;
349: * if true, a {@link ClassCastException} may arise during some map operation
350: * @return a view over the raw map guaranteed to match the specified type
351: */
352: public static <K, V> Map<K, V> checkedMapByFilter(Map rawMap,
353: Class<K> keyType, Class<V> valueType, boolean strict) {
354: return new CheckedMap<K, V>(rawMap, keyType, valueType, strict);
355: }
356:
357: private static final class CheckedMap<K, V> extends
358: AbstractMap<K, V> implements Serializable {
359:
360: private static final long serialVersionUID = 1L;
361:
362: private final Map rawMap;
363: private final Class<K> keyType;
364: private final Class<V> valueType;
365: private final boolean strict;
366:
367: public CheckedMap(Map rawMap, Class<K> keyType,
368: Class<V> valueType, boolean strict) {
369: this .rawMap = rawMap;
370: this .keyType = keyType;
371: this .valueType = valueType;
372: this .strict = strict;
373: }
374:
375: private boolean acceptKey(Object o) {
376: if (o == null) {
377: return true;
378: } else if (keyType.isInstance(o)) {
379: return true;
380: } else if (strict) {
381: throw new ClassCastException(o + " was not a "
382: + keyType.getName()); // NOI18N
383: } else {
384: return false;
385: }
386: }
387:
388: private boolean acceptValue(Object o) {
389: if (o == null) {
390: return true;
391: } else if (valueType.isInstance(o)) {
392: return true;
393: } else if (strict) {
394: throw new ClassCastException(o + " was not a "
395: + valueType.getName()); // NOI18N
396: } else {
397: return false;
398: }
399: }
400:
401: private boolean acceptEntry(Map.Entry e) {
402: return acceptKey(e.getKey()) && acceptValue(e.getValue());
403: }
404:
405: private final class EntrySet extends
406: AbstractSet<Map.Entry<K, V>> {
407:
408: @Override
409: public Iterator<Map.Entry<K, V>> iterator() {
410: return new CheckedIterator<Map.Entry<K, V>>(rawMap
411: .entrySet().iterator()) {
412: @Override
413: protected boolean accept(Object o) {
414: return acceptEntry((Map.Entry) o);
415: }
416: };
417: }
418:
419: @Override
420: public int size() {
421: int c = 0;
422: Iterator it = rawMap.entrySet().iterator();
423: while (it.hasNext()) {
424: if (acceptEntry((Map.Entry) it.next())) {
425: c++;
426: }
427: }
428: return c;
429: }
430:
431: }
432:
433: @Override
434: public Set<Map.Entry<K, V>> entrySet() {
435: return new EntrySet();
436: }
437:
438: @Override
439: public V get(Object key) {
440: Object o = rawMap.get(keyType.cast(key));
441: if (acceptValue(o)) {
442: @SuppressWarnings("unchecked")
443: V v = (V) o;
444: return v;
445: } else {
446: return null;
447: }
448: }
449:
450: @SuppressWarnings("unchecked")
451: @Override
452: public V put(K key, V value) {
453: Object old = rawMap.put(keyType.cast(key), valueType
454: .cast(value));
455: if (acceptValue(old)) {
456: return (V) old;
457: } else {
458: return null;
459: }
460: }
461:
462: @Override
463: public V remove(Object key) {
464: Object old = rawMap.remove(keyType.cast(key));
465: if (acceptValue(old)) {
466: @SuppressWarnings("unchecked")
467: V v = (V) old;
468: return v;
469: } else {
470: return null;
471: }
472: }
473:
474: @Override
475: public boolean containsKey(Object key) {
476: return rawMap.containsKey(keyType.cast(key))
477: && acceptValue(rawMap.get(key));
478: }
479:
480: @Override
481: public boolean containsValue(Object value) {
482: // Cannot just ask rawMap since we could not check type of key.
483: return super .containsValue(valueType.cast(value));
484: }
485:
486: @Override
487: public int size() {
488: int c = 0;
489: Iterator it = rawMap.entrySet().iterator();
490: while (it.hasNext()) {
491: if (acceptEntry((Map.Entry) it.next())) {
492: c++;
493: }
494: }
495: return c;
496: }
497:
498: // keySet, values cannot be so easily overridden because we type-check whole entries
499:
500: }
501:
502: /**
503: * Create a typesafe filter of an unchecked enumeration.
504: * @param rawEnum an unchecked enumeration
505: * @param type the desired enumeration type
506: * @param strict if false, elements which are not null but not assignable to the requested type are omitted;
507: * if true, {@link ClassCastException} may be thrown from an enumeration operation
508: * @return an enumeration guaranteed to contain only objects of the requested type (or null)
509: */
510: public static <E> Enumeration<E> checkedEnumerationByFilter(
511: Enumeration rawEnum, final Class<E> type,
512: final boolean strict) {
513: @SuppressWarnings("unchecked")
514: Enumeration<Object> _rawEnum = rawEnum;
515: return Enumerations.<Object, E> filter(_rawEnum,
516: new Enumerations.Processor<Object, E>() {
517: public E process(Object o, Collection<Object> ignore) {
518: if (o == null) {
519: return null;
520: } else {
521: try {
522: return type.cast(o);
523: } catch (ClassCastException x) {
524: if (strict) {
525: throw x;
526: } else {
527: return null;
528: }
529: }
530: }
531: }
532: });
533: }
534:
535: /**
536: * Treat an {@link Iterator} as an {@link Iterable} so it can be used in an enhanced for-loop.
537: * Bear in mind that the iterator is "consumed" by the loop and so should be used only once.
538: * Generally it is best to put the code which obtains the iterator inside the loop header.
539: * <div class="nonnormative">
540: * <p>Example of correct usage:</p>
541: * <pre>
542: * String text = ...;
543: * for (String token : NbCollections.iterable(new {@link java.util.Scanner}(text))) {
544: * // ...
545: * }
546: * </pre>
547: * </div>
548: * @param iterator an iterator
549: * @return an iterable wrapper which will traverse the iterator once
550: * @throws NullPointerException if the iterator is null
551: * @see <a href="http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6312085">Java bug #6312085</a>
552: * @see <a href="http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6360734">Java bug #6360734</a>
553: * @see <a href="http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4988624">Java bug #4988624</a>
554: * @since org.openide.util 7.5
555: */
556: public static <E> Iterable<E> iterable(final Iterator<E> iterator) {
557: if (iterator == null) {
558: throw new NullPointerException();
559: }
560: return new Iterable<E>() {
561: public Iterator<E> iterator() {
562: return iterator;
563: }
564: };
565: }
566:
567: /**
568: * Treat an {@link Enumeration} as an {@link Iterable} so it can be used in an enhanced for-loop.
569: * Bear in mind that the enumeration is "consumed" by the loop and so should be used only once.
570: * Generally it is best to put the code which obtains the enumeration inside the loop header.
571: * <div class="nonnormative">
572: * <p>Example of correct usage:</p>
573: * <pre>
574: * ClassLoader loader = ...;
575: * String name = ...;
576: * for (URL resource : NbCollections.iterable(loader.{@link ClassLoader#getResources getResources}(name))) {
577: * // ...
578: * }
579: * </pre>
580: * </div>
581: * @param enumeration an enumeration
582: * @return an iterable wrapper which will traverse the enumeration once
583: * ({@link Iterator#remove} is not supported)
584: * @throws NullPointerException if the enumeration is null
585: * @see <a href="http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6349852">Java bug #6349852</a>
586: * @since org.openide.util 7.5
587: */
588: public static <E> Iterable<E> iterable(
589: final Enumeration<E> enumeration) {
590: if (enumeration == null) {
591: throw new NullPointerException();
592: }
593: return new Iterable<E>() {
594: public Iterator<E> iterator() {
595: return new Iterator<E>() {
596: public boolean hasNext() {
597: return enumeration.hasMoreElements();
598: }
599:
600: public E next() {
601: return enumeration.nextElement();
602: }
603:
604: public void remove() {
605: throw new UnsupportedOperationException();
606: }
607: };
608: }
609: };
610: }
611:
612: }
|