001/* 002 * This library is part of OpenCms - 003 * the Open Source Content Management System 004 * 005 * Copyright (c) Alkacon Software GmbH & Co. KG (http://www.alkacon.com) 006 * 007 * This library is free software; you can redistribute it and/or 008 * modify it under the terms of the GNU Lesser General Public 009 * License as published by the Free Software Foundation; either 010 * version 2.1 of the License, or (at your option) any later version. 011 * 012 * This library is distributed in the hope that it will be useful, 013 * but WITHOUT ANY WARRANTY; without even the implied warranty of 014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 015 * Lesser General Public License for more details. 016 * 017 * For further information about Alkacon Software, please see the 018 * company website: http://www.alkacon.com 019 * 020 * For further information about OpenCms, please see the 021 * project website: http://www.opencms.org 022 * 023 * You should have received a copy of the GNU Lesser General Public 024 * License along with this library; if not, write to the Free Software 025 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 026 */ 027 028package org.opencms.util; 029 030import org.opencms.main.CmsLog; 031 032import java.util.Collection; 033import java.util.Map; 034import java.util.Set; 035import java.util.concurrent.ConcurrentHashMap; 036 037import org.apache.commons.logging.Log; 038 039/** 040 * Wrapper around ConcurrentHashMap which allows null values.<p> 041 * 042 * The point of this is the following: Often, HashMaps in older code are accessed concurrently by multiple threads. When these threads modify the 043 * map concurrently, an infinite loop may occur due to the standard HashMap implementation. But sometimes we can't just replace the HashMap with a 044 * ConcurrentHashMap because that class doesn't allow null values and we don't always know for certain whether null values are used or not. 045 * 046 * But if we don't care about the distinction about null values and entries not being present, we can use this map class which will just log an error 047 * and remove the entry when trying to set a null value. 048 * 049 * NOTE: Currently this wrapper does *not* check value modifications made to entries returned by entrySet! 050 * 051 * @param <K> the key type 052 * @param <V> the value type 053 */ 054public class CmsNullIgnoringConcurrentMap<K, V> implements Map<K, V> { 055 056 /** The logger for this class. */ 057 private static final Log LOG = CmsLog.getLog(CmsNullIgnoringConcurrentMap.class); 058 059 /** The wrapped map. */ 060 private Map<K, V> m_internalMap = new ConcurrentHashMap<K, V>(); 061 062 /** 063 * Creates a new instance.<p> 064 */ 065 public CmsNullIgnoringConcurrentMap() { 066 067 } 068 069 /** 070 * Creates a new instance from another map.<p> 071 * 072 * @param otherMap the other map 073 */ 074 public CmsNullIgnoringConcurrentMap(Map<K, V> otherMap) { 075 for (Map.Entry<K, V> entry : otherMap.entrySet()) { 076 put(entry.getKey(), entry.getValue()); 077 } 078 } 079 080 /** 081 * 082 * @see java.util.Map#clear() 083 */ 084 public void clear() { 085 086 m_internalMap.clear(); 087 } 088 089 /** 090 * @see java.util.Map#containsKey(java.lang.Object) 091 */ 092 public boolean containsKey(Object key) { 093 094 return m_internalMap.containsKey(key); 095 } 096 097 /** 098 * @see java.util.Map#containsValue(java.lang.Object) 099 */ 100 public boolean containsValue(Object value) { 101 102 return m_internalMap.containsValue(value); 103 } 104 105 /** 106 * @see java.util.Map#entrySet() 107 */ 108 public Set<Map.Entry<K, V>> entrySet() { 109 110 return m_internalMap.entrySet(); 111 } 112 113 /** 114 * @see java.util.Map#equals(java.lang.Object) 115 */ 116 @Override 117 public boolean equals(Object o) { 118 119 return m_internalMap.equals(o); 120 } 121 122 /** 123 * @see java.util.Map#get(java.lang.Object) 124 */ 125 public V get(Object key) { 126 127 return m_internalMap.get(key); 128 } 129 130 /** 131 * @see java.util.Map#hashCode() 132 */ 133 @Override 134 public int hashCode() { 135 136 return m_internalMap.hashCode(); 137 } 138 139 /** 140 * @see java.util.Map#isEmpty() 141 */ 142 public boolean isEmpty() { 143 144 return m_internalMap.isEmpty(); 145 } 146 147 /** 148 * @see java.util.Map#keySet() 149 */ 150 public Set<K> keySet() { 151 152 return m_internalMap.keySet(); 153 } 154 155 /** 156 * Sets the given map value for the given key, unless either of them is null.<p> 157 * 158 * If the value is null, 159 * 160 * @param key the key 161 * @param value the value 162 * 163 * @return the old value 164 * 165 * @see java.util.Map#put(java.lang.Object, java.lang.Object) 166 */ 167 public V put(K key, V value) { 168 169 if ((key != null) && (value != null)) { 170 return m_internalMap.put(key, value); 171 } 172 Exception e = new Exception(); 173 try { 174 // we want to print a stack trace when null is used as a key/value 175 throw e; 176 } catch (Exception e2) { 177 e = e2; 178 } 179 if (key == null) { 180 LOG.warn("Invalid null key in map", e); 181 return null; 182 } 183 if (value == null) { 184 LOG.warn("Invalid null value in map", e); 185 return m_internalMap.remove(key); 186 } 187 return null; 188 } 189 190 /** 191 * @see java.util.Map#putAll(java.util.Map) 192 */ 193 public void putAll(Map<? extends K, ? extends V> m) { 194 195 for (Map.Entry<? extends K, ? extends V> entry : m.entrySet()) { 196 put(entry.getKey(), entry.getValue()); 197 } 198 } 199 200 /** 201 * @see java.util.Map#remove(java.lang.Object) 202 */ 203 public V remove(Object key) { 204 205 return m_internalMap.remove(key); 206 } 207 208 /** 209 * @see java.util.Map#size() 210 */ 211 public int size() { 212 213 return m_internalMap.size(); 214 } 215 216 /** 217 * @see java.util.Map#values() 218 */ 219 public Collection<V> values() { 220 221 return m_internalMap.values(); 222 } 223 224}