/* * JBoss, Home of Professional Open Source * Copyright 2009 Red Hat Inc. and/or its affiliates and other * contributors as indicated by the @author tags. All rights reserved. * See the copyright.txt in the distribution for a full listing of * individual contributors. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package org.infinispan.util; import org.infinispan.container.DataContainer; import org.infinispan.container.entries.InternalCacheEntry; import org.infinispan.container.entries.InternalCacheValue; import org.infinispan.container.versioning.EntryVersion; import org.infinispan.marshall.AbstractExternalizer; import org.infinispan.marshall.Ids; import org.infinispan.marshall.MarshallUtil; import java.io.IOException; import java.io.InputStream; import java.io.ObjectInput; import java.io.ObjectOutput; import java.io.Reader; import java.io.Serializable; import java.lang.reflect.Array; import java.util.*; import java.util.Map.Entry; /** * Factory for generating immutable type wrappers. * * @author Jason T. Greene * @author Galder ZamarreƱo * @since 4.0 */ public class Immutables { /** * Whether or not this collection type is immutable * * @param o a Collection, Set, List, or Map * @return true if immutable, false if not */ public static boolean isImmutable(Object o) { return o instanceof Immutable; } /** * Converts a Collection to an immutable List by copying it. * * @param source the collection to convert * @return a copied/converted immutable list */ public static <T> List<T> immutableListConvert(Collection<? extends T> source) { return new ImmutableListCopy<T>(source); } /** * * Creates an immutable copy of the list. * * @param list the list to copy * @return the immutable copy */ public static <T> List<T> immutableListCopy(List<T> list) { if (list == null) return null; if (list.isEmpty()) return Collections.emptyList(); if (list.size() == 1) return Collections.singletonList(list.get(0)); return new ImmutableListCopy<T>(list); } /** * Creates an immutable copy of the properties. * * @param properties the TypedProperties to copy * @return the immutable copy */ public static TypedProperties immutableTypedPropreties(TypedProperties properties) { if (properties == null) return null; return new ImmutableTypedProperties(properties); } /** * Wraps an array with an immutable list. There is no copying involved. * * @param <T> * @param array the array to wrap * @return a list containing the array */ public static <T> List<T> immutableListWrap(T... array) { return new ImmutableListCopy<T>(array); } /** * Creates a new immutable list containing the union (combined entries) of both lists. * * @param list1 contains the first elements of the new list * @param list2 contains the successor elements of the new list * @return a new immutable merged copy of list1 and list2 */ public static <T> List<T> immutableListMerge(List<? extends T> list1, List<? extends T> list2) { return new ImmutableListCopy<T>(list1, list2); } /** * Converts a Collections into an immutable Set by copying it. * * @param collection the collection to convert/copy * @return a new immutable set containing the elements in collection */ public static <T> Set<T> immutableSetConvert(Collection<? extends T> collection) { return immutableSetWrap(new HashSet<T>(collection)); } /** * Wraps a set with an immutable set. There is no copying involved. * * @param set the set to wrap * @return an immutable set wrapper that delegates to the original set */ public static <T> Set<T> immutableSetWrap(Set<? extends T> set) { return new ImmutableSetWrapper<T>(set); } /** * Creates an immutable copy of the specified set. * * @param set the set to copy from * @return an immutable set copy */ public static <T> Set<T> immutableSetCopy(Set<T> set) { if (set == null) return null; if (set.isEmpty()) return Collections.emptySet(); if (set.size() == 1) return Collections.singleton(set.iterator().next()); Set<? extends T> copy = ObjectDuplicator.duplicateSet(set); if (copy == null) // Set uses Collection copy-ctor copy = attemptCopyConstructor(set, Collection.class); if (copy == null) copy = new HashSet<T>(set); return new ImmutableSetWrapper<T>(copy); } /** * Wraps a map with an immutable map. There is no copying involved. * * @param map the map to wrap * @return an immutable map wrapper that delegates to the original map */ public static <K, V> Map<K, V> immutableMapWrap(Map<? extends K, ? extends V> map) { return new ImmutableMapWrapper<K, V>(map); } /** * Creates an immutable copy of the specified map. * * @param map the map to copy from * @return an immutable map copy */ public static <K, V> Map<K, V> immutableMapCopy(Map<K, V> map) { if (map == null) return null; if (map.isEmpty()) return Collections.emptyMap(); if (map.size() == 1) { Map.Entry<K, V> me = map.entrySet().iterator().next(); return Collections.singletonMap(me.getKey(), me.getValue()); } Map<? extends K, ? extends V> copy = ObjectDuplicator.duplicateMap(map); if (copy == null) copy = attemptCopyConstructor(map, Map.class); if (copy == null) copy = new HashMap<K, V>(map); return new ImmutableMapWrapper<K, V>(copy); } /** * Creates a new immutable copy of the specified Collection. * * @param collection the collection to copy * @return an immutable copy */ public static <T> Collection<T> immutableCollectionCopy(Collection<T> collection) { if (collection == null) return null; if (collection.isEmpty()) return Collections.emptySet(); if (collection.size() == 1) return Collections.singleton(collection.iterator().next()); Collection<? extends T> copy = ObjectDuplicator.duplicateCollection(collection); if (copy == null) copy = attemptCopyConstructor(collection, Collection.class); if (copy == null) copy = new ArrayList<T>(collection); return new ImmutableCollectionWrapper<T>(copy); } /** * Wraps a collection with an immutable collection. There is no copying involved. * * @param collection the collection to wrap * @return an immutable collection wrapper that delegates to the original collection */ public static <T> Collection<T> immutableCollectionWrap(Collection<? extends T> collection) { return new ImmutableCollectionWrapper<T>(collection); } @SuppressWarnings("unchecked") private static <T> T attemptCopyConstructor(T source, Class<? super T> clazz) { try { return (T) source.getClass().getConstructor(clazz).newInstance(source); } catch (Exception e) { } return null; } /** * Wraps a {@link Map.Entry}} with an immutable {@link Map.Entry}}. There is no copying involved. * * @param entry the mapping to wrap. * @return an immutable {@link Map.Entry}} wrapper that delegates to the original mapping. */ public static <K, V> Map.Entry<K, V> immutableEntry(Map.Entry<K, V> entry) { return new ImmutableEntry<K, V>(entry); } /** * Wraps a {@link InternalCacheEntry}} with an immutable {@link InternalCacheEntry}}. There is no copying involved. * * @param entry the internal cache entry to wrap. * @return an immutable {@link InternalCacheEntry}} wrapper that delegates to the original entry. */ public static InternalCacheEntry immutableInternalCacheEntry(InternalCacheEntry entry) { return new ImmutableInternalCacheEntry(entry); } public interface Immutable { } /* * Immutable wrapper types. * * We have to re-implement Collections.unmodifiableXXX, since it is not * simple to detect them (the class names are JDK dependent). */ private static class ImmutableIteratorWrapper<E> implements Iterator<E> { private Iterator<? extends E> iterator; public ImmutableIteratorWrapper(Iterator<? extends E> iterator) { this.iterator = iterator; } @Override public boolean hasNext() { return iterator.hasNext(); } @Override public E next() { return iterator.next(); } @Override public void remove() { throw new UnsupportedOperationException(); } } private static class ImmutableCollectionWrapper<E> implements Collection<E>, Serializable, Immutable { private static final long serialVersionUID = 6777564328198393535L; Collection<? extends E> collection; public ImmutableCollectionWrapper(Collection<? extends E> collection) { this.collection = collection; } @Override public boolean add(E o) { throw new UnsupportedOperationException(); } @Override public boolean addAll(Collection<? extends E> c) { throw new UnsupportedOperationException(); } @Override public void clear() { throw new UnsupportedOperationException(); } @Override public boolean contains(Object o) { return collection.contains(o); } @Override public boolean containsAll(Collection<?> c) { return collection.containsAll(c); } public boolean equals(Object o) { return collection.equals(o); } public int hashCode() { return collection.hashCode(); } @Override public boolean isEmpty() { return collection.isEmpty(); } @Override public Iterator<E> iterator() { return new ImmutableIteratorWrapper<E>(collection.iterator()); } @Override public boolean remove(Object o) { throw new UnsupportedOperationException(); } @Override public boolean removeAll(Collection<?> c) { throw new UnsupportedOperationException(); } @Override public boolean retainAll(Collection<?> c) { throw new UnsupportedOperationException(); } @Override public int size() { return collection.size(); } @Override public Object[] toArray() { return collection.toArray(); } @Override public <T> T[] toArray(T[] a) { return collection.toArray(a); } public String toString() { return collection.toString(); } } private static class ImmutableSetWrapper<E> extends ImmutableCollectionWrapper<E> implements Set<E>, Serializable, Immutable { private static final long serialVersionUID = 7991492805176142615L; public ImmutableSetWrapper(Set<? extends E> set) { super(set); } } private static class ImmutableReversibleOrderedSetWrapper<E> extends ImmutableCollectionWrapper<E> implements ReversibleOrderedSet<E>, Serializable, Immutable { private static final long serialVersionUID = 7991492805176142615L; public ImmutableReversibleOrderedSetWrapper(Set<? extends E> set) { super(set); } @Override public Iterator<E> reverseIterator() { return new ImmutableIteratorWrapper<E>(((ReversibleOrderedSet<? extends E>) collection).reverseIterator()); } } /** * Immutable version of Map.Entry for traversing immutable collections. */ private static class ImmutableEntry<K, V> implements Entry<K, V>, Immutable { private K key; private V value; private int hash; ImmutableEntry(Entry<? extends K, ? extends V> entry) { this.key = entry.getKey(); this.value = entry.getValue(); this.hash = entry.hashCode(); } @Override public K getKey() { return key; } @Override public V getValue() { return value; } @Override public V setValue(V value) { throw new UnsupportedOperationException(); } private static boolean eq(Object o1, Object o2) { return o1 == o2 || (o1 != null && o1.equals(o2)); } @SuppressWarnings("unchecked") public boolean equals(Object o) { if (!(o instanceof Entry)) return false; Entry<K, V> entry = (Entry<K, V>) o; return eq(entry.getKey(), key) && eq(entry.getValue(), value); } public int hashCode() { return hash; } public String toString() { return getKey() + "=" + getValue(); } } /** * Immutable version of InternalCacheEntry for traversing data containers. */ private static class ImmutableInternalCacheEntry implements InternalCacheEntry, Immutable { private final InternalCacheEntry entry; private final int hash; ImmutableInternalCacheEntry(InternalCacheEntry entry) { this.entry = entry; this.hash = entry.hashCode(); } @Override public Object getKey() { return entry.getKey(); } @Override public Object getValue() { return entry.getValue(); } @Override public Object setValue(Object value) { throw new UnsupportedOperationException(); } @Override public void commit(DataContainer container, EntryVersion newVersion) { throw new UnsupportedOperationException(); } @SuppressWarnings("unchecked") public boolean equals(Object o) { if (!(o instanceof InternalCacheEntry)) return false; InternalCacheEntry entry = (InternalCacheEntry) o; return entry.equals(this.entry); } public int hashCode() { return hash; } public String toString() { return getKey() + "=" + getValue(); } @Override public boolean canExpire() { return entry.canExpire(); } @Override public long getCreated() { return entry.getCreated(); } @Override public long getExpiryTime() { return entry.getExpiryTime(); } @Override public long getLastUsed() { return entry.getLastUsed(); } @Override public boolean isExpired(long now) { return entry.isExpired(now); } @Override public boolean isExpired() { return entry.isExpired(); } @Override public void setLifespan(long lifespan) { throw new UnsupportedOperationException(); } @Override public void setMaxIdle(long maxIdle) { throw new UnsupportedOperationException(); } @Override public InternalCacheValue toInternalCacheValue() { return new ImmutableInternalCacheValue(this); } @Override public void setChanged(boolean b) { throw new UnsupportedOperationException(); } @Override public void touch() { throw new UnsupportedOperationException(); } @Override public void touch(long currentTimeMillis) { throw new UnsupportedOperationException(); } @Override public boolean undelete(boolean doUndelete) { throw new UnsupportedOperationException(); } @Override public void reincarnate() { throw new UnsupportedOperationException(); } @Override public void setVersion(EntryVersion version) { throw new UnsupportedOperationException(); } @Override public long getLifespan() { return entry.getLifespan(); } @Override public long getMaxIdle() { return entry.getMaxIdle(); } @Override public boolean isChanged() { return entry.isChanged(); } @Override public boolean isCreated() { return entry.isCreated(); } @Override public boolean isNull() { return entry.isNull(); } @Override public boolean isRemoved() { return entry.isRemoved(); } @Override public boolean isEvicted() { return entry.isEvicted(); } @Override public boolean isValid() { return entry.isValid(); } @Override public void rollback() { throw new UnsupportedOperationException(); } @Override public void setCreated(boolean created) { throw new UnsupportedOperationException(); } @Override public void setRemoved(boolean removed) { throw new UnsupportedOperationException(); } @Override public void setEvicted(boolean evicted) { entry.setEvicted(evicted); } @Override public void setValid(boolean valid) { throw new UnsupportedOperationException(); } @Override public boolean isLockPlaceholder() { return entry.isLockPlaceholder(); } @Override public EntryVersion getVersion() { return entry.getVersion(); } @Override public InternalCacheEntry clone() { return new ImmutableInternalCacheEntry(entry.clone()); } } private static class ImmutableInternalCacheValue implements InternalCacheValue, Immutable { private final ImmutableInternalCacheEntry entry; ImmutableInternalCacheValue(ImmutableInternalCacheEntry entry) { this.entry = entry; } @Override public boolean canExpire() { return entry.canExpire(); } @Override public long getCreated() { return entry.getCreated(); } @Override public long getLastUsed() { return entry.getLastUsed(); } @Override public long getLifespan() { return entry.getLifespan(); } @Override public long getMaxIdle() { return entry.getMaxIdle(); } @Override public Object getValue() { return entry.getValue(); } @Override public boolean isExpired(long now) { return entry.isExpired(now); } @Override public boolean isExpired() { return entry.isExpired(); } @Override public InternalCacheEntry toInternalCacheEntry(Object key) { return entry; } } private static class ImmutableEntrySetWrapper<K, V> extends ImmutableSetWrapper<Entry<K, V>> { private static final long serialVersionUID = 6378667653889667692L; @SuppressWarnings("unchecked") public ImmutableEntrySetWrapper(Set<? extends Entry<? extends K, ? extends V>> set) { super((Set<Entry<K, V>>) set); } @Override public Object[] toArray() { Object[] array = new Object[collection.size()]; int i = 0; for (Entry<K, V> entry : this) array[i++] = entry; return array; } @Override @SuppressWarnings("unchecked") public <T> T[] toArray(T[] array) { int size = collection.size(); if (array.length < size) array = (T[]) Array.newInstance(array.getClass().getComponentType(), size); int i = 0; Object[] result = array; for (Entry<K, V> entry : this) result[i++] = entry; return array; } @Override public Iterator<Entry<K, V>> iterator() { return new ImmutableIteratorWrapper<Entry<K, V>>(collection.iterator()) { @Override public Entry<K, V> next() { return new ImmutableEntry<K, V>(super.next()); } }; } } private static class ImmutableMapWrapper<K, V> implements Map<K, V>, Serializable, Immutable { private static final long serialVersionUID = 708144227046742221L; private Map<? extends K, ? extends V> map; public ImmutableMapWrapper(Map<? extends K, ? extends V> map) { this.map = map; } @Override public void clear() { throw new UnsupportedOperationException(); } @Override public boolean containsKey(Object key) { return map.containsKey(key); } @Override public boolean containsValue(Object value) { return map.containsValue(value); } @Override public Set<Entry<K, V>> entrySet() { return new ImmutableEntrySetWrapper<K, V>(map.entrySet()); } public boolean equals(Object o) { return map.equals(o); } @Override public V get(Object key) { return map.get(key); } public int hashCode() { return map.hashCode(); } @Override public boolean isEmpty() { return map.isEmpty(); } @Override public Set<K> keySet() { return new ImmutableSetWrapper<K>(map.keySet()); } @Override public V put(K key, V value) { throw new UnsupportedOperationException(); } @Override public void putAll(Map<? extends K, ? extends V> t) { throw new UnsupportedOperationException(); } @Override public V remove(Object key) { throw new UnsupportedOperationException(); } @Override public int size() { return map.size(); } @Override public Collection<V> values() { return new ImmutableCollectionWrapper<V>(map.values()); } public String toString() { return map.toString(); } } public static class ImmutableMapWrapperExternalizer extends AbstractExternalizer<Map> { @Override public void writeObject(ObjectOutput output, Map map) throws IOException { MarshallUtil.marshallMap(map, output); } @Override public Map readObject(ObjectInput input) throws IOException, ClassNotFoundException { Map<Object, Object> map = new HashMap<Object, Object>(); MarshallUtil.unmarshallMap(map, input); return Immutables.immutableMapWrap(map); } @Override public Integer getId() { return Ids.IMMUTABLE_MAP; } @Override public Set<Class<? extends Map>> getTypeClasses() { return Util.<Class<? extends Map>>asSet(ImmutableMapWrapper.class); } } private static class ImmutableTypedProperties extends TypedProperties { ImmutableTypedProperties(TypedProperties properties) { super(); if (properties != null && !properties.isEmpty()) { for (Map.Entry<Object, Object> e: properties.entrySet()) super.put(e.getKey(), e.getValue()); } } @Override public synchronized void clear() { throw new UnsupportedOperationException(); } @Override public Set<java.util.Map.Entry<Object, Object>> entrySet() { return new ImmutableEntrySetWrapper<Object, Object>(super.entrySet()); } @Override public Set<Object> keySet() { return new ImmutableSetWrapper<Object>(super.keySet()); } @Override public synchronized void load(InputStream inStream) throws IOException { throw new UnsupportedOperationException(); } @Override public synchronized void load(Reader reader) throws IOException { throw new UnsupportedOperationException(); } @Override public synchronized void loadFromXML(InputStream in) throws IOException, InvalidPropertiesFormatException { throw new UnsupportedOperationException(); } @Override public synchronized Object put(Object key, Object value) { throw new UnsupportedOperationException(); } @Override public synchronized void putAll(Map<?, ?> t) { throw new UnsupportedOperationException(); } @Override public synchronized Object remove(Object key) { throw new UnsupportedOperationException(); } @Override public synchronized TypedProperties setProperty(String key, String value) { throw new UnsupportedOperationException(); } @Override public Set<String> stringPropertyNames() { return new ImmutableSetWrapper<String>(super.stringPropertyNames()); } @Override public Collection<Object> values() { return new ImmutableCollectionWrapper<Object>(super.values()); } } }