Mega Code Archive

 
Categories / Java Tutorial / Generics
 

Create a typesafe view over an underlying raw map

/*  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.  *  * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.  *  * The contents of this file are subject to the terms of either the GNU  * General Public License Version 2 only ("GPL") or the Common  * Development and Distribution License("CDDL") (collectively, the  * "License"). You may not use this file except in compliance with the  * License. You can obtain a copy of the License at  * http://www.netbeans.org/cddl-gplv2.html  * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the  * specific language governing permissions and limitations under the  * License.  When distributing the software, include this License Header  * Notice in each file and include the License file at  * nbbuild/licenses/CDDL-GPL-2-CP.  Sun designates this  * particular file as subject to the "Classpath" exception as provided  * by Sun in the GPL Version 2 section of the License file that  * accompanied this code. If applicable, add the following below the  * License Header, with the fields enclosed by brackets [] replaced by  * your own identifying information:  * "Portions Copyrighted [year] [name of copyright owner]"  *  * Contributor(s):  *  * The Original Software is NetBeans. The Initial Developer of the Original  * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun  * Microsystems, Inc. All Rights Reserved.  *  * If you wish your version of this file to be governed by only the CDDL  * or only the GPL Version 2, indicate your decision by adding  * "[Contributor] elects to include this software in this distribution  * under the [CDDL or GPL Version 2] license." If you do not indicate a  * single choice of license, a recipient has the option to distribute  * your version of this file under either the CDDL, the GPL Version 2 or  * to extend the choice of license to its licensees as provided above.  * However, if you add GPL Version 2 code and therefore, elected the GPL  * Version 2 license, then the option applies only if the new code is  * made subject to such option by the copyright holder.  */ import java.io.Serializable; import java.util.AbstractMap; import java.util.AbstractSet; import java.util.Iterator; import java.util.Map; import java.util.NoSuchElementException; import java.util.Set; /**  * @since 4.37  * @author Jaroslav Tulach  */ public class Utils {   /**    * Create a typesafe view over an underlying raw map.    * Mutations affect the underlying map (this is not a copy).    * {@link Map#clear} will make the view empty but may not clear the underlying map.    * You may add entries only of the requested type pair.    * {@link Map#get}, {@link Map#containsKey}, and {@link Map#containsValue} also perform a type check    * and will throw {@link ClassCastException} for an illegal argument.    * The view is serializable if the underlying map is.    * @param rawMap an unchecked map    * @param keyType the desired entry key type    * @param valueType the desired entry value type    * @param strict if false, entries in the underlying map for which the key is not null but not assignable    *               to the requested key type, and/or the value is not null but not assignable to    *               the requested value type, are omitted from the view;    *               if true, a {@link ClassCastException} may arise during some map operation    * @return a view over the raw map guaranteed to match the specified type    */   public static <K,V> Map<K,V> checkedMapByFilter(Map rawMap, Class<K> keyType, Class<V> valueType, boolean strict) {       return new CheckedMap<K,V>(rawMap, keyType, valueType, strict);   }   private static final class CheckedMap<K,V> extends AbstractMap<K,V> implements Serializable {       private static final long serialVersionUID = 1L;       private final Map rawMap;       private final Class<K> keyType;       private final Class<V> valueType;       private final boolean strict;       public CheckedMap(Map rawMap, Class<K> keyType, Class<V> valueType, boolean strict) {           this.rawMap = rawMap;           this.keyType = keyType;           this.valueType = valueType;           this.strict = strict;       }       private boolean acceptKey(Object o) {           if (o == null) {               return true;           } else if (keyType.isInstance(o)) {               return true;           } else if (strict) {               throw new ClassCastException(o + " was not a " + keyType.getName()); // NOI18N           } else {               return false;           }       }       private boolean acceptValue(Object o) {           if (o == null) {               return true;           } else if (valueType.isInstance(o)) {               return true;           } else if (strict) {               throw new ClassCastException(o + " was not a " + valueType.getName()); // NOI18N           } else {               return false;           }       }       private boolean acceptEntry(Map.Entry e) {           return acceptKey(e.getKey()) && acceptValue(e.getValue());       }       private final class EntrySet extends AbstractSet<Map.Entry<K, V>> {           @Override           public Iterator<Map.Entry<K,V>> iterator() {               return new CheckedIterator<Map.Entry<K,V>>(rawMap.entrySet().iterator()) {                   @Override                   protected boolean accept(Object o) {                       return acceptEntry((Map.Entry) o);                   }               };           }           @Override           public int size() {               int c = 0;               Iterator it = rawMap.entrySet().iterator();               while (it.hasNext()) {                   if (acceptEntry((Map.Entry) it.next())) {                       c++;                   }               }               return c;           }       }       @Override       public Set<Map.Entry<K, V>> entrySet() {           return new EntrySet();       }       @Override       public V get(Object key) {           Object o = rawMap.get(keyType.cast(key));           if (acceptValue(o)) {               @SuppressWarnings("unchecked")               V v = (V) o;               return v;           } else {               return null;           }       }       @SuppressWarnings("unchecked")       @Override       public V put(K key, V value) {           Object old = rawMap.put(keyType.cast(key), valueType.cast(value));           if (acceptValue(old)) {               return (V) old;           } else {               return null;           }       }       @Override       public V remove(Object key) {           Object old = rawMap.remove(keyType.cast(key));           if (acceptValue(old)) {               @SuppressWarnings("unchecked")               V v = (V) old;               return v;           } else {               return null;           }       }       @Override       public boolean containsKey(Object key) {           return rawMap.containsKey(keyType.cast(key)) &&                   acceptValue(rawMap.get(key));       }       @Override       public boolean containsValue(Object value) {           // Cannot just ask rawMap since we could not check type of key.           return super.containsValue(valueType.cast(value));       }       @Override       public int size() {           int c = 0;           Iterator it = rawMap.entrySet().iterator();           while (it.hasNext()) {               if (acceptEntry((Map.Entry) it.next())) {                   c++;               }           }           return c;       }       // keySet, values cannot be so easily overridden because we type-check whole entries   } } abstract class CheckedIterator<E> implements Iterator<E> {   static final Object WAITING = new Object();   private final Iterator it;   private Object next = WAITING;   public CheckedIterator(Iterator it) {     this.it = it;   }   protected abstract boolean accept(Object o);   public boolean hasNext() {     if (next != WAITING) {       return true;     }     while (it.hasNext()) {       next = it.next();       if (accept(next)) {         return true;       }     }     next = WAITING;     return false;   }   public E next() {     if (next == WAITING && !hasNext()) {       throw new NoSuchElementException();     }     assert next != WAITING;     @SuppressWarnings("unchecked")     // type-checking is done by accept()     E x = (E) next;     next = WAITING;     return x;   }   public void remove() {     it.remove();   } }