Java tutorial
/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ package org.apache.openjpa.lib.util.concurrent; import java.util.concurrent.ConcurrentHashMap; import java.util.Enumeration; import java.util.Set; import java.util.Collection; import java.util.AbstractSet; import java.util.Iterator; import java.util.AbstractCollection; import java.util.Random; import java.util.HashSet; import java.util.TreeSet; import org.apache.commons.collections.set.MapBackedSet; /** * A subclass of {@link ConcurrentHashMap} that allows null keys and values. * In exchange, it weakens the contract of {@link #putIfAbsent} and the other * concurrent methods added in {@link #ConcurrentHashMap}. * * @since 1.1.0 */ public class NullSafeConcurrentHashMap extends ConcurrentHashMap { private enum Markers { NULL, MAP_BACKED_SET_DUMMY_VAL } // The second argument is used within MapBackedSet as the value for // all the key-val pairs that are put into the underlying Map. This // is required for our usage since ConcurrentHashMap does not allow // null values. private Set randomKeys = MapBackedSet.decorate(new ConcurrentHashMap(), Markers.MAP_BACKED_SET_DUMMY_VAL); private Random random = new Random(); public NullSafeConcurrentHashMap(int size, float load, int concurrencyLevel) { super(size, load, concurrencyLevel); } public NullSafeConcurrentHashMap() { } /** * Returns internal representation for object. */ private static Object maskNull(Object o) { return (o == null ? Markers.NULL : o); } /** * Returns object represented by specified internal representation. */ private static Object unmaskNull(Object o) { return (o == Markers.NULL ? null : o); } public Entry removeRandom() { // this doesn't just use randomEntryIterator() because that iterator // has weaker concurrency guarantees than this method. In particular, // this method will continue to attempt to remove random entries even // as other threads remove the same entries, whereas the random // iterator may return values that have been removed. for (Iterator iter = randomKeys.iterator(); iter.hasNext();) { // randomKeys contains null-masked data Object key = iter.next(); if (key != null && randomKeys.remove(key)) { Object val = super.remove(key); if (val != null) return new EntryImpl(unmaskNull(key), unmaskNull(val)); } } // if randomKeys is empty, fall back to non-random behavior. for (Iterator iter = super.keySet().iterator(); iter.hasNext();) { Object key = iter.next(); if (key == null) continue; Object val = super.remove(key); if (val != null) return new EntryImpl(unmaskNull(key), unmaskNull(val)); } return null; } /** * The returned data structure should not be shared among multiple * threads. */ public Iterator<Entry> randomEntryIterator() { return new Iterator<Entry>() { Iterator randomIter = randomKeys.iterator(); Iterator nonRandomIter = NullSafeConcurrentHashMap.super.keySet().iterator(); Set returned = new HashSet(); Entry next; boolean nextSet = false; public boolean hasNext() { // we've set the next value and we haven't returned it yet if (nextSet) return true; // compute the next value. If the computation returns null, // return false. Else, store the next value and return true. Object nextKey; Object nextValue; if (randomIter.hasNext()) { nextKey = randomIter.next(); nextValue = NullSafeConcurrentHashMap.super.get(nextKey); if (nextValue != null) { returned.add(nextKey); next = new EntryImpl(unmaskNull(nextKey), unmaskNull(nextValue)); nextSet = true; return true; } } while (nonRandomIter.hasNext()) { nextKey = nonRandomIter.next(); if (returned.contains(nextKey)) continue; nextValue = NullSafeConcurrentHashMap.super.get(nextKey); if (nextValue != null) { returned.add(nextKey); next = new EntryImpl(unmaskNull(nextKey), unmaskNull(nextValue)); nextSet = true; return true; } } return false; } public Entry next() { // hasNext() will initialize this.next if (!nextSet && !hasNext()) return null; // if we get here, then we're about to return a next value nextSet = false; if (containsKey(next.getKey())) return next; // something has changed since the last iteration (presumably // due to multi-threaded access to the underlying data // structure); recurse return next(); } public void remove() { throw new UnsupportedOperationException(); } }; } @Override public Object remove(Object key) { Object maskedKey = maskNull(key); Object val = unmaskNull(super.remove(maskedKey)); randomKeys.remove(maskedKey); return val; } @Override public boolean remove(Object key, Object value) { Object maskedKey = maskNull(key); boolean val = super.remove(maskedKey, maskNull(value)); randomKeys.remove(maskedKey); return val; } @Override public boolean replace(Object key, Object oldValue, Object newValue) { return super.replace(maskNull(key), maskNull(oldValue), maskNull(newValue)); } @Override public Object replace(Object key, Object value) { return unmaskNull(super.replace(maskNull(key), maskNull(value))); } @Override public Object putIfAbsent(Object key, Object value) { Object maskedKey = maskNull(key); Object superVal = super.putIfAbsent(maskedKey, maskNull(value)); addRandomKey(maskedKey); return unmaskNull(superVal); } @Override public Object put(Object key, Object value) { Object maskedKey = maskNull(key); Object superVal = super.put(maskedKey, maskNull(value)); addRandomKey(maskedKey); return unmaskNull(superVal); } /** * Potentially adds <code>maskedKey</ccode> to the set of random keys * to be removed by {@link #removeRandom()}. * * @since 1.1.0 */ private void addRandomKey(Object maskedKey) { // Add one in every three keys to the set. Only do this when // there are less than 16 elements in the random key set; this // means that the algorithm will be pseudo-random for up to // 16 removes (either via removeRandom() or normal remove() // calls) that have no intervening put() calls. if (random != null && randomKeys.size() < 16 && random.nextInt(10) < 3) randomKeys.add(maskedKey); } @Override public Object get(Object key) { return unmaskNull(super.get(maskNull(key))); } @Override public boolean containsKey(Object key) { return super.containsKey(maskNull(key)); } @Override public boolean containsValue(Object value) { return super.containsValue(maskNull(value)); } @Override public boolean contains(Object value) { throw new UnsupportedOperationException(); } @Override public Enumeration elements() { throw new UnsupportedOperationException(); } @Override public Set entrySet() { return new TranslatingSet(super.entrySet()) { protected Object unmask(Object internal) { final Entry e = (Entry) internal; return new Entry() { public Object getKey() { return unmaskNull(e.getKey()); } public Object getValue() { return unmaskNull(e.getValue()); } public Object setValue(Object value) { return unmaskNull(e.setValue(maskNull(value))); } @Override public int hashCode() { return e.hashCode(); } }; } }; } @Override public Enumeration keys() { throw new UnsupportedOperationException(); } @Override public Set keySet() { return new TranslatingSet(super.keySet()) { protected Object unmask(Object internal) { return unmaskNull(internal); } }; } @Override public Collection values() { return new TranslatingCollection(super.values()) { protected Object unmask(Object internal) { return unmaskNull(internal); } }; } private abstract class TranslatingSet extends AbstractSet { private Set backingSet; private TranslatingSet(Set backing) { this.backingSet = backing; } protected abstract Object unmask(Object internal); public Iterator iterator() { final Iterator iterator = backingSet.iterator(); return new Iterator() { public boolean hasNext() { return iterator.hasNext(); } public Object next() { return unmask(iterator.next()); } public void remove() { iterator.remove(); } }; } public int size() { return backingSet.size(); } } private abstract class TranslatingCollection extends AbstractCollection { private Collection backingCollection; private TranslatingCollection(Collection backing) { this.backingCollection = backing; } protected abstract Object unmask(Object internal); public Iterator iterator() { final Iterator iterator = backingCollection.iterator(); return new Iterator() { public boolean hasNext() { return iterator.hasNext(); } public Object next() { return unmask(iterator.next()); } public void remove() { iterator.remove(); } }; } public int size() { return backingCollection.size(); } } private class EntryImpl implements Entry { final Object key; final Object val; private EntryImpl(Object key, Object val) { this.key = key; this.val = val; } public Object getKey() { return key; } public Object getValue() { return val; } public Object setValue(Object value) { throw new UnsupportedOperationException(); } } public interface KeyFilter { /** * @param key may be null * @return whether or not <code>key</code> shuold be excluded */ public boolean exclude(Object key); } }