/**
*
* Copyright 2004 Protique Ltd
*
* Licensed 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.activemq.filter;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.activemq.message.ActiveMQDestination;
import EDU.oswego.cs.dl.util.concurrent.ConcurrentHashMap;
/**
* A Map-like data structure allowing values to be indexed by {@link ActiveMQDestination}
* and retrieved by destination - supporting both * and > style of wildcard
* as well as composite destinations.
* <br>
* This class assumes that the index changes rarely but that fast lookup into the index is required.
* So this class maintains a pre-calculated index for destination steps. So looking up the values
* for "TEST.*" or "*.TEST" will be pretty fast.
* <br>
* Looking up of a value could return a single value or a List of matching values if a wildcard or
* composite destination is used.
*
* @version $Revision: 1.1.1.1 $
*/
public class DestinationMap {
private DestinationMapNode rootNode = new DestinationMapNode();
private Map vanillaDestinations = new ConcurrentHashMap();
private boolean containsWildCards = false;
protected static final String ANY_DESCENDENT = DestinationFilter.ANY_DESCENDENT;
protected static final String ANY_CHILD = DestinationFilter.ANY_CHILD;
private Set nullAnswer = new HashSet();
/**
* Looks up the value(s) matching the given Destination key. For simple destinations
* this is typically a List of one single value, for wildcards or composite destinations this will typically be
* a List of matching values.
*
* @param key the destination to lookup
* @return a List of matching values or an empty list if there are no matching values.
*/
public synchronized Set get(ActiveMQDestination key) {
if (key.isComposite()) {
List childDestinations = key.getChildDestinations();
Set answer = new HashSet(childDestinations.size());
for (Iterator iter = childDestinations.iterator(); iter.hasNext();) {
ActiveMQDestination childDestination = (ActiveMQDestination) iter.next();
Object value = get(childDestination);
if (value instanceof Set) {
answer.addAll((Set) value);
}
else if (value != null) {
answer.add(value);
}
return answer;
}
}
return findWildcardMatches(key);
}
/**
* add destination to the map
* @param key
* @param value
*/
public synchronized void put(ActiveMQDestination key, Object value) {
if (key.isComposite()) {
List childDestinations = key.getChildDestinations();
for (Iterator iter = childDestinations.iterator(); iter.hasNext();) {
ActiveMQDestination childDestination = (ActiveMQDestination) iter.next();
put(childDestination, value);
}
return;
}
String[] paths = key.getDestinationPaths();
rootNode.add(paths, 0, value);
if (key.isWildcard()){
containsWildCards = true;
}
addToVanillaDestinations(key, value);
}
/**
* Removes the value from the associated destination
* @param key
* @param value
*/
public synchronized void remove(ActiveMQDestination key, Object value) {
if (key.isComposite()) {
List childDestinations = key.getChildDestinations();
for (Iterator iter = childDestinations.iterator(); iter.hasNext();) {
ActiveMQDestination childDestination = (ActiveMQDestination) iter.next();
remove(childDestination, value);
}
return;
}
String[] paths = key.getDestinationPaths();
rootNode.remove(paths, 0, value);
removeFromVanillaDestinations(key,value);
}
// Implementation methods
//-------------------------------------------------------------------------
protected Set findWildcardMatches(ActiveMQDestination key) {
Set answer = nullAnswer;
if (!containsWildCards && !key.isWildcard()){
answer = getFromVanillaDestinations(key);
}else {
answer = new HashSet();
String[] paths = key.getDestinationPaths();
rootNode.appendMatchingValues(answer, paths, 0);
}
return answer;
}
/**
* remove all destinations associated with a key
* @param key
*/
public void removeAll(ActiveMQDestination key) {
if (key.isComposite()) {
List childDestinations = key.getChildDestinations();
for (Iterator iter = childDestinations.iterator(); iter.hasNext();) {
ActiveMQDestination childDestination = (ActiveMQDestination) iter.next();
removeAll(childDestination);
}
return;
}
String[] paths = key.getDestinationPaths();
rootNode.removeAll(paths, 0);
removeAllFromVanillaDestinations(key);
}
synchronized private void addToVanillaDestinations(ActiveMQDestination key, Object value) {
Set set = new HashSet();
Set oldSet = (Set)vanillaDestinations.get(key);
if (oldSet != null) {
set.addAll(oldSet);
}
set.add(value);
vanillaDestinations.put(key,set);
}
synchronized private void removeFromVanillaDestinations(ActiveMQDestination key,Object value){
Set oldSet = (Set)vanillaDestinations.get(key);
if (oldSet != null) {
Set set = new HashSet(oldSet);
set.remove(value);
if (set.isEmpty()){
vanillaDestinations.remove(key);
}
vanillaDestinations.put(key,set);
}
validateContainsWildCards(key);
}
private void removeAllFromVanillaDestinations(ActiveMQDestination key){
vanillaDestinations.remove(key);
validateContainsWildCards(key);
}
private Set getFromVanillaDestinations(ActiveMQDestination key){
Set answer = (Set)vanillaDestinations.get(key);
return answer != null ? answer : nullAnswer;
}
private void validateContainsWildCards(ActiveMQDestination key){
if (containsWildCards && key.isWildcard()){
containsWildCards = false;
for (Iterator i = vanillaDestinations.keySet().iterator(); i.hasNext();){
ActiveMQDestination dest = (ActiveMQDestination)i.next();
if (dest.isWildcard()){
containsWildCards = true;
break;
}
}
}
}
}