mirror of
https://github.com/Alfresco/alfresco-community-repo.git
synced 2025-10-08 14:51:49 +00:00
125606 rmunteanu: Merged 5.1.1 (5.1.1) to 5.1.N (5.1.2) 125515 slanglois: MNT-16155 Update source headers - add new Copyrights for Java and JSP source files + automatic check in the build git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/BRANCHES/DEV/5.2.N/root@125788 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
451 lines
13 KiB
Java
451 lines
13 KiB
Java
/*
|
|
* #%L
|
|
* Alfresco Repository
|
|
* %%
|
|
* Copyright (C) 2005 - 2016 Alfresco Software Limited
|
|
* %%
|
|
* This file is part of the Alfresco software.
|
|
* If the software was purchased under a paid Alfresco license, the terms of
|
|
* the paid license agreement will prevail. Otherwise, the software is
|
|
* provided under the following open source license terms:
|
|
*
|
|
* Alfresco 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 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* Alfresco 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 Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
|
* #L%
|
|
*/
|
|
package org.alfresco.util;
|
|
|
|
import java.io.Serializable;
|
|
import java.math.BigDecimal;
|
|
import java.math.BigInteger;
|
|
import java.util.Collection;
|
|
import java.util.Collections;
|
|
import java.util.Date;
|
|
import java.util.HashMap;
|
|
import java.util.HashSet;
|
|
import java.util.Locale;
|
|
import java.util.Map;
|
|
import java.util.Set;
|
|
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
|
|
|
/**
|
|
* A map that protects keys and values from accidental modification.
|
|
* <p/>
|
|
* Use this map when keys or values need to be protected against client modification.
|
|
* For example, when a component pulls a map from a common resource it can wrap
|
|
* the map with this class to prevent any accidental modification of the shared
|
|
* resource.
|
|
* <p/>
|
|
* Upon first write to this map , the underlying map will be copied (selectively cloned),
|
|
* the original map handle will be discarded and the copied map will be used. Note that
|
|
* the map copy process will also occur if any mutable value is in danger of being
|
|
* exposed to client modification. Therefore, methods that iterate and retrieve values
|
|
* will also trigger the copy if any values are mutable.
|
|
*
|
|
* @param <K> the map key type (must extend {@link Serializable})
|
|
* @param <V> the map value type (must extend {@link Serializable})
|
|
*
|
|
* @author Derek Hulley
|
|
* @since 3.4.9
|
|
* @since 4.0.1
|
|
*/
|
|
public class ValueProtectingMap<K extends Serializable, V extends Serializable> implements Map<K, V>, Serializable
|
|
{
|
|
private static final long serialVersionUID = -9073485393875357605L;
|
|
|
|
/**
|
|
* Default immutable classes:
|
|
* <li>String</li>
|
|
* <li>BigDecimal</li>
|
|
* <li>BigInteger</li>
|
|
* <li>Byte</li>
|
|
* <li>Double</li>
|
|
* <li>Float</li>
|
|
* <li>Integer</li>
|
|
* <li>Long</li>
|
|
* <li>Short</li>
|
|
* <li>Boolean</li>
|
|
* <li>Date</li>
|
|
* <li>Locale</li>
|
|
*/
|
|
public static final Set<Class<?>> DEFAULT_IMMUTABLE_CLASSES;
|
|
static
|
|
{
|
|
DEFAULT_IMMUTABLE_CLASSES = new HashSet<Class<?>>(13);
|
|
DEFAULT_IMMUTABLE_CLASSES.add(String.class);
|
|
DEFAULT_IMMUTABLE_CLASSES.add(BigDecimal.class);
|
|
DEFAULT_IMMUTABLE_CLASSES.add(BigInteger.class);
|
|
DEFAULT_IMMUTABLE_CLASSES.add(Byte.class);
|
|
DEFAULT_IMMUTABLE_CLASSES.add(Double.class);
|
|
DEFAULT_IMMUTABLE_CLASSES.add(Float.class);
|
|
DEFAULT_IMMUTABLE_CLASSES.add(Integer.class);
|
|
DEFAULT_IMMUTABLE_CLASSES.add(Long.class);
|
|
DEFAULT_IMMUTABLE_CLASSES.add(Short.class);
|
|
DEFAULT_IMMUTABLE_CLASSES.add(Boolean.class);
|
|
DEFAULT_IMMUTABLE_CLASSES.add(Date.class);
|
|
DEFAULT_IMMUTABLE_CLASSES.add(Locale.class);
|
|
}
|
|
|
|
/**
|
|
* Protect a specific value if it is considered mutable
|
|
*
|
|
* @param <S> the type of the value, which must be {@link Serializable}
|
|
* @param value the value to protect if it is mutable (may be <tt>null</tt>)
|
|
* @param immutableClasses a set of classes that can be considered immutable
|
|
* over and above the {@link #DEFAULT_IMMUTABLE_CLASSES default set}
|
|
* @return a cloned instance (via serialization) or the instance itself, if immutable
|
|
*/
|
|
@SuppressWarnings("unchecked")
|
|
public static <S extends Serializable> S protectValue(S value, Set<Class<?>> immutableClasses)
|
|
{
|
|
if (!mustProtectValue(value, immutableClasses))
|
|
{
|
|
return value;
|
|
}
|
|
// We have to clone it
|
|
// No worries about the return type; it has to be the same as we put into the serializer
|
|
return (S) SerializationUtils.deserialize(SerializationUtils.serialize(value));
|
|
}
|
|
|
|
/**
|
|
* Utility method to check if values need to be cloned or not
|
|
*
|
|
* @param <S> the type of the value, which must be {@link Serializable}
|
|
* @param value the value to check
|
|
* @param immutableClasses a set of classes that can be considered immutable
|
|
* over and above the {@link #DEFAULT_IMMUTABLE_CLASSES default set}
|
|
* @return <tt>true</tt> if the value must <b>NOT</b> be given
|
|
* to the calling clients
|
|
*/
|
|
public static <S extends Serializable> boolean mustProtectValue(S value, Set<Class<?>> immutableClasses)
|
|
{
|
|
if (value == null)
|
|
{
|
|
return false;
|
|
}
|
|
Class<?> clazz = value.getClass();
|
|
return (
|
|
DEFAULT_IMMUTABLE_CLASSES.contains(clazz) == false &&
|
|
immutableClasses.contains(clazz) == false);
|
|
}
|
|
|
|
/**
|
|
* Utility method to clone a map, preserving immutable instances
|
|
*
|
|
* @param <K> the map key type, which must be {@link Serializable}
|
|
* @param <V> the map value type, which must be {@link Serializable}
|
|
* @param map the map to copy
|
|
* @param immutableClasses a set of classes that can be considered immutable
|
|
* over and above the {@link #DEFAULT_IMMUTABLE_CLASSES default set}
|
|
*/
|
|
public static <K extends Serializable, V extends Serializable> Map<K, V> cloneMap(Map<K, V> map, Set<Class<?>> immutableClasses)
|
|
{
|
|
Map<K, V> copy = new HashMap<K, V>((int)(map.size() * 1.3));
|
|
for (Map.Entry<K, V> element : map.entrySet())
|
|
{
|
|
K key = element.getKey();
|
|
V value = element.getValue();
|
|
// Clone as necessary
|
|
key = ValueProtectingMap.protectValue(key, immutableClasses);
|
|
value = ValueProtectingMap.protectValue(value, immutableClasses);
|
|
copy.put(key, value);
|
|
}
|
|
return copy;
|
|
}
|
|
|
|
private ReentrantReadWriteLock.ReadLock readLock;
|
|
private ReentrantReadWriteLock.WriteLock writeLock;
|
|
|
|
private boolean cloned = false;
|
|
private Map<K, V> map;
|
|
private Set<Class<?>> immutableClasses;
|
|
|
|
/**
|
|
* Construct providing a protected map and using only the
|
|
* {@link #DEFAULT_IMMUTABLE_CLASSES default immutable classes}
|
|
*
|
|
* @param protectedMap the map to safeguard
|
|
*/
|
|
public ValueProtectingMap(Map<K, V> protectedMap)
|
|
{
|
|
this (protectedMap, null);
|
|
}
|
|
|
|
/**
|
|
* Construct providing a protected map, complementing the set of
|
|
* {@link #DEFAULT_IMMUTABLE_CLASSES default immutable classes}
|
|
*
|
|
* @param protectedMap the map to safeguard
|
|
* @param immutableClasses additional immutable classes
|
|
* over and above the {@link #DEFAULT_IMMUTABLE_CLASSES default set}
|
|
* (may be <tt>null</tt>
|
|
*/
|
|
public ValueProtectingMap(Map<K, V> protectedMap, Set<Class<?>> immutableClasses)
|
|
{
|
|
// Unwrap any internal maps if given a value protecting map
|
|
if (protectedMap instanceof ValueProtectingMap)
|
|
{
|
|
ValueProtectingMap<K, V> mapTemp = (ValueProtectingMap<K, V>) protectedMap;
|
|
this.map = mapTemp.map;
|
|
}
|
|
else
|
|
{
|
|
this.map = protectedMap;
|
|
}
|
|
|
|
this.cloned = false;
|
|
if (immutableClasses == null)
|
|
{
|
|
this.immutableClasses = Collections.emptySet();
|
|
}
|
|
else
|
|
{
|
|
this.immutableClasses = new HashSet<Class<?>>(immutableClasses);
|
|
}
|
|
// Construct locks
|
|
ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
|
|
this.readLock = lock.readLock();
|
|
this.writeLock = lock.writeLock();
|
|
}
|
|
|
|
/**
|
|
* An unsafe method to use for anything except tests.
|
|
*
|
|
* @return the map that this instance is protecting
|
|
*/
|
|
/* protected */ Map<K, V> getProtectedMap()
|
|
{
|
|
return map;
|
|
}
|
|
|
|
/**
|
|
* Called by methods that need to force the map into a safe state.
|
|
* <p/>
|
|
* This method can be called without any locks being active.
|
|
*/
|
|
private void cloneMap()
|
|
{
|
|
readLock.lock();
|
|
try
|
|
{
|
|
// Check that it hasn't been copied already
|
|
if (cloned)
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
readLock.unlock();
|
|
}
|
|
/*
|
|
* Note: This space here is a window during which some code could have made
|
|
* a copy. Therefore we will do a cautious double-check.
|
|
*/
|
|
// Put in a write lock before cloning the map
|
|
writeLock.lock();
|
|
try
|
|
{
|
|
// Check that it hasn't been copied already
|
|
if (cloned)
|
|
{
|
|
return;
|
|
}
|
|
|
|
Map<K, V> copy = ValueProtectingMap.cloneMap(map, immutableClasses);
|
|
// Discard the original
|
|
this.map = copy;
|
|
this.cloned = true;
|
|
}
|
|
finally
|
|
{
|
|
writeLock.unlock();
|
|
}
|
|
}
|
|
|
|
/*
|
|
* READ-ONLY METHODS
|
|
*/
|
|
|
|
@Override
|
|
public int size()
|
|
{
|
|
readLock.lock();
|
|
try
|
|
{
|
|
return map.size();
|
|
}
|
|
finally
|
|
{
|
|
readLock.unlock();
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public boolean isEmpty()
|
|
{
|
|
readLock.lock();
|
|
try
|
|
{
|
|
return map.isEmpty();
|
|
}
|
|
finally
|
|
{
|
|
readLock.unlock();
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public boolean containsKey(Object key)
|
|
{
|
|
readLock.lock();
|
|
try
|
|
{
|
|
return map.containsKey(key);
|
|
}
|
|
finally
|
|
{
|
|
readLock.unlock();
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public boolean containsValue(Object value)
|
|
{
|
|
readLock.lock();
|
|
try
|
|
{
|
|
return map.containsValue(value);
|
|
}
|
|
finally
|
|
{
|
|
readLock.unlock();
|
|
}
|
|
|
|
}
|
|
|
|
@Override
|
|
public int hashCode()
|
|
{
|
|
readLock.lock();
|
|
try
|
|
{
|
|
return map.hashCode();
|
|
}
|
|
finally
|
|
{
|
|
readLock.unlock();
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public boolean equals(Object obj)
|
|
{
|
|
readLock.lock();
|
|
try
|
|
{
|
|
return map.equals(obj);
|
|
}
|
|
finally
|
|
{
|
|
readLock.unlock();
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public String toString()
|
|
{
|
|
readLock.lock();
|
|
try
|
|
{
|
|
return map.toString();
|
|
}
|
|
finally
|
|
{
|
|
readLock.unlock();
|
|
}
|
|
}
|
|
|
|
/*
|
|
* METHODS THAT *MIGHT* REQUIRE COPY
|
|
*/
|
|
|
|
@Override
|
|
public V get(Object key)
|
|
{
|
|
readLock.lock();
|
|
try
|
|
{
|
|
V value = map.get(key);
|
|
return ValueProtectingMap.protectValue(value, immutableClasses);
|
|
}
|
|
finally
|
|
{
|
|
readLock.unlock();
|
|
}
|
|
}
|
|
|
|
/*
|
|
* METHODS THAT REQUIRE COPY
|
|
*/
|
|
|
|
@Override
|
|
public V put(K key, V value)
|
|
{
|
|
cloneMap();
|
|
return map.put(key, value);
|
|
}
|
|
|
|
@Override
|
|
public V remove(Object key)
|
|
{
|
|
cloneMap();
|
|
return map.remove(key);
|
|
}
|
|
|
|
@Override
|
|
public void putAll(Map<? extends K, ? extends V> m)
|
|
{
|
|
cloneMap();
|
|
map.putAll(m);
|
|
}
|
|
|
|
@Override
|
|
public void clear()
|
|
{
|
|
cloneMap();
|
|
map.clear();
|
|
}
|
|
|
|
@Override
|
|
public Set<K> keySet()
|
|
{
|
|
cloneMap();
|
|
return map.keySet();
|
|
}
|
|
|
|
@Override
|
|
public Collection<V> values()
|
|
{
|
|
cloneMap();
|
|
return map.values();
|
|
}
|
|
|
|
@Override
|
|
public Set<Entry<K, V>> entrySet()
|
|
{
|
|
cloneMap();
|
|
return map.entrySet();
|
|
}
|
|
}
|