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 076 for (Map.Entry<K, V> entry : otherMap.entrySet()) { 077 put(entry.getKey(), entry.getValue()); 078 } 079 } 080 081 /** 082 * 083 * @see java.util.Map#clear() 084 */ 085 public void clear() { 086 087 m_internalMap.clear(); 088 } 089 090 /** 091 * @see java.util.Map#containsKey(java.lang.Object) 092 */ 093 public boolean containsKey(Object key) { 094 095 return m_internalMap.containsKey(key); 096 } 097 098 /** 099 * @see java.util.Map#containsValue(java.lang.Object) 100 */ 101 public boolean containsValue(Object value) { 102 103 return m_internalMap.containsValue(value); 104 } 105 106 /** 107 * @see java.util.Map#entrySet() 108 */ 109 public Set<Map.Entry<K, V>> entrySet() { 110 111 return m_internalMap.entrySet(); 112 } 113 114 /** 115 * @see java.util.Map#equals(java.lang.Object) 116 */ 117 @Override 118 public boolean equals(Object o) { 119 120 return m_internalMap.equals(o); 121 } 122 123 /** 124 * @see java.util.Map#get(java.lang.Object) 125 */ 126 public V get(Object key) { 127 128 return m_internalMap.get(key); 129 } 130 131 /** 132 * @see java.util.Map#hashCode() 133 */ 134 @Override 135 public int hashCode() { 136 137 return m_internalMap.hashCode(); 138 } 139 140 /** 141 * @see java.util.Map#isEmpty() 142 */ 143 public boolean isEmpty() { 144 145 return m_internalMap.isEmpty(); 146 } 147 148 /** 149 * @see java.util.Map#keySet() 150 */ 151 public Set<K> keySet() { 152 153 return m_internalMap.keySet(); 154 } 155 156 /** 157 * Sets the given map value for the given key, unless either of them is null.<p> 158 * 159 * If the value is null, 160 * 161 * @param key the key 162 * @param value the value 163 * 164 * @return the old value 165 * 166 * @see java.util.Map#put(java.lang.Object, java.lang.Object) 167 */ 168 public V put(K key, V value) { 169 170 if ((key != null) && (value != null)) { 171 return m_internalMap.put(key, value); 172 } 173 Exception e = new Exception(); 174 try { 175 // we want to print a stack trace when null is used as a key/value 176 throw e; 177 } catch (Exception e2) { 178 e = e2; 179 } 180 if (key == null) { 181 LOG.warn("Invalid null key in map", e); 182 return null; 183 } 184 if (value == null) { 185 LOG.warn("Invalid null value in map", e); 186 return m_internalMap.remove(key); 187 } 188 return null; 189 } 190 191 /** 192 * @see java.util.Map#putAll(java.util.Map) 193 */ 194 public void putAll(Map<? extends K, ? extends V> m) { 195 196 for (Map.Entry<? extends K, ? extends V> entry : m.entrySet()) { 197 put(entry.getKey(), entry.getValue()); 198 } 199 } 200 201 /** 202 * @see java.util.Map#remove(java.lang.Object) 203 */ 204 public V remove(Object key) { 205 206 return m_internalMap.remove(key); 207 } 208 209 /** 210 * @see java.util.Map#size() 211 */ 212 public int size() { 213 214 return m_internalMap.size(); 215 } 216 217 /** 218 * @see java.lang.Object#toString() 219 */ 220 @Override 221 public String toString() { 222 223 return "" + m_internalMap; 224 } 225 226 /** 227 * @see java.util.Map#values() 228 */ 229 public Collection<V> values() { 230 231 return m_internalMap.values(); 232 } 233 234}