/** * 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.activemq.filter; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; /** * An implementation class used to implement {@link DestinationMap} * * */ public class DestinationMapNode implements DestinationNode { protected static final String ANY_CHILD = DestinationMap.ANY_CHILD; protected static final String ANY_DESCENDENT = DestinationMap.ANY_DESCENDENT; // we synchronize at the DestinationMap level private DestinationMapNode parent; private List<Object> values = new ArrayList<Object>(); private Map<String, DestinationNode> childNodes = new HashMap<String, DestinationNode>(); private String path = "Root"; // private DestinationMapNode anyChild; private int pathLength; public DestinationMapNode(DestinationMapNode parent) { this.parent = parent; if (parent == null) { pathLength = 0; } else { pathLength = parent.pathLength + 1; } } /** * Returns the child node for the given named path or null if it does not * exist */ public DestinationNode getChild(String path) { return childNodes.get(path); } /** * Returns the child nodes */ public Collection<DestinationNode> getChildren() { return childNodes.values(); } public int getChildCount() { return childNodes.size(); } /** * Returns the child node for the given named path, lazily creating one if * it does not yet exist */ public DestinationMapNode getChildOrCreate(String path) { DestinationMapNode answer = (DestinationMapNode)childNodes.get(path); if (answer == null) { answer = createChildNode(); answer.path = path; childNodes.put(path, answer); } return answer; } /** * Returns a mutable List of the values available at this node in the tree */ @SuppressWarnings({ "rawtypes", "unchecked" }) public List getValues() { return values; } /** * Removes values available at this node in the tree */ @SuppressWarnings({ "rawtypes", "unchecked" }) public List removeValues() { ArrayList v = new ArrayList(values); // parent.getAnyChildNode().getValues().removeAll(v); values.clear(); pruneIfEmpty(); return v; } @SuppressWarnings({ "rawtypes", "unchecked" }) public Set removeDesendentValues() { Set answer = new HashSet(); removeDesendentValues(answer); return answer; } @SuppressWarnings({ "rawtypes", "unchecked" }) protected void removeDesendentValues(Set answer) { ArrayList<DestinationNode> candidates = new ArrayList<>(); for (Map.Entry<String, DestinationNode> child : childNodes.entrySet()) { candidates.add(child.getValue()); } for (DestinationNode node : candidates) { // remove all the values from the child answer.addAll(node.removeValues()); answer.addAll(node.removeDesendentValues()); } } /** * Returns a list of all the values from this node down the tree */ @SuppressWarnings({ "rawtypes", "unchecked" }) public Set getDesendentValues() { Set answer = new HashSet(); appendDescendantValues(answer); return answer; } public void add(String[] paths, int idx, Object value) { if (idx >= paths.length) { values.add(value); } else { getChildOrCreate(paths[idx]).add(paths, idx + 1, value); } } public void set(String[] paths, int idx, Object value) { if (idx >= paths.length) { values.clear(); values.add(value); } else { getChildOrCreate(paths[idx]).set(paths, idx + 1, value); } } public void remove(String[] paths, int idx, Object value) { if (idx >= paths.length) { values.remove(value); pruneIfEmpty(); } else { getChildOrCreate(paths[idx]).remove(paths, ++idx, value); } } public void removeAll(Set<DestinationNode> answer, String[] paths, int startIndex) { DestinationNode node = this; int size = paths.length; for (int i = startIndex; i < size && node != null; i++) { String path = paths[i]; if (path.equals(ANY_DESCENDENT)) { answer.addAll(node.removeDesendentValues()); break; } // TODO is this correct, we are appending wildcard values here??? node.appendMatchingWildcards(answer, paths, i); if (path.equals(ANY_CHILD)) { // node = node.getAnyChildNode(); node = new AnyChildDestinationNode(node); } else { node = node.getChild(path); } } if (node != null) { answer.addAll(node.removeValues()); } } @SuppressWarnings({ "rawtypes", "unchecked" }) public void appendDescendantValues(Set answer) { // add children values, then recursively add their children for(DestinationNode child : childNodes.values()) { answer.addAll(child.getValues()); child.appendDescendantValues(answer); } } /** * Factory method to create a child node */ protected DestinationMapNode createChildNode() { return new DestinationMapNode(this); } /** * Matches any entries in the map containing wildcards */ @SuppressWarnings({ "rawtypes", "unchecked" }) public void appendMatchingWildcards(Set answer, String[] paths, int idx) { if (idx - 1 > pathLength) { return; } DestinationNode wildCardNode = getChild(ANY_CHILD); if (wildCardNode != null) { wildCardNode.appendMatchingValues(answer, paths, idx + 1); } wildCardNode = getChild(ANY_DESCENDENT); if (wildCardNode != null) { // for a wildcard Node match, add all values of the descendant node answer.addAll(wildCardNode.getValues()); // and all descendants for paths like ">.>" answer.addAll(wildCardNode.getDesendentValues()); } } @SuppressWarnings({"rawtypes", "unchecked"}) public void appendMatchingValues(Set answer, String[] paths, int idx) { appendMatchingValues(answer, paths, idx, true); } public void appendMatchingValues(Set<DestinationNode> answer, String[] paths, int startIndex, boolean deep) { DestinationNode node = this; boolean couldMatchAny = true; int size = paths.length; for (int i = startIndex; i < size && node != null; i++) { String path = paths[i]; if (deep && path.equals(ANY_DESCENDENT)) { answer.addAll(node.getDesendentValues()); couldMatchAny = false; break; } node.appendMatchingWildcards(answer, paths, i); if (path.equals(ANY_CHILD)) { node = new AnyChildDestinationNode(node); } else { node = node.getChild(path); } } if (node != null) { answer.addAll(node.getValues()); if (couldMatchAny) { // lets allow FOO.BAR to match the FOO.BAR.> entry in the map DestinationNode child = node.getChild(ANY_DESCENDENT); if (child != null) { answer.addAll(child.getValues()); } } } } public String getPath() { return path; } public boolean isEmpty(){ return childNodes.isEmpty(); } protected void pruneIfEmpty() { if (parent != null && childNodes.isEmpty() && values.isEmpty()) { parent.removeChild(this); } } protected void removeChild(DestinationMapNode node) { childNodes.remove(node.getPath()); pruneIfEmpty(); } }