001/**
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.activemq.filter;
018
019import java.util.ArrayList;
020import java.util.Collection;
021import java.util.HashMap;
022import java.util.HashSet;
023import java.util.List;
024import java.util.Map;
025import java.util.Set;
026
027/**
028 * An implementation class used to implement {@link DestinationMap}
029 *
030 *
031 */
032public class DestinationMapNode implements DestinationNode {
033    protected static final String ANY_CHILD = DestinationMap.ANY_CHILD;
034    protected static final String ANY_DESCENDENT = DestinationMap.ANY_DESCENDENT;
035
036    // we synchronize at the DestinationMap level
037    private DestinationMapNode parent;
038    private List<Object> values = new ArrayList<Object>();
039    private Map<String, DestinationNode> childNodes = new HashMap<String, DestinationNode>();
040    private String path = "Root";
041    // private DestinationMapNode anyChild;
042    private int pathLength;
043
044    public DestinationMapNode(DestinationMapNode parent) {
045        this.parent = parent;
046        if (parent == null) {
047            pathLength = 0;
048        } else {
049            pathLength = parent.pathLength + 1;
050        }
051    }
052
053    /**
054     * Returns the child node for the given named path or null if it does not
055     * exist
056     */
057    public DestinationNode getChild(String path) {
058        return childNodes.get(path);
059    }
060
061    /**
062     * Returns the child nodes
063     */
064    public Collection<DestinationNode> getChildren() {
065        return childNodes.values();
066    }
067
068    public int getChildCount() {
069        return childNodes.size();
070    }
071
072    /**
073     * Returns the child node for the given named path, lazily creating one if
074     * it does not yet exist
075     */
076    public DestinationMapNode getChildOrCreate(String path) {
077        DestinationMapNode answer = (DestinationMapNode)childNodes.get(path);
078        if (answer == null) {
079            answer = createChildNode();
080            answer.path = path;
081            childNodes.put(path, answer);
082        }
083        return answer;
084    }
085
086    /**
087     * Returns a mutable List of the values available at this node in the tree
088     */
089    @SuppressWarnings({ "rawtypes", "unchecked" })
090    public List getValues() {
091        return values;
092    }
093
094    /**
095     * Removes values available at this node in the tree
096     */
097    @SuppressWarnings({ "rawtypes", "unchecked" })
098    public List removeValues() {
099        ArrayList v = new ArrayList(values);
100        // parent.getAnyChildNode().getValues().removeAll(v);
101        values.clear();
102        pruneIfEmpty();
103        return v;
104    }
105
106    @SuppressWarnings({ "rawtypes", "unchecked" })
107    public Set removeDesendentValues() {
108        Set answer = new HashSet();
109        removeDesendentValues(answer);
110        return answer;
111    }
112
113    @SuppressWarnings({ "rawtypes", "unchecked" })
114    protected void removeDesendentValues(Set answer) {
115        for (Map.Entry<String, DestinationNode> child : childNodes.entrySet()) {
116            // remove all the values from the child
117            answer.addAll(child.getValue().removeValues());
118            answer.addAll(child.getValue().removeDesendentValues());
119        }
120    }
121
122    /**
123     * Returns a list of all the values from this node down the tree
124     */
125    @SuppressWarnings({ "rawtypes", "unchecked" })
126    public Set getDesendentValues() {
127        Set answer = new HashSet();
128        appendDescendantValues(answer);
129        return answer;
130    }
131
132    public void add(String[] paths, int idx, Object value) {
133        if (idx >= paths.length) {
134            values.add(value);
135        } else {
136            getChildOrCreate(paths[idx]).add(paths, idx + 1, value);
137        }
138    }
139
140    public void set(String[] paths, int idx, Object value) {
141        if (idx >= paths.length) {
142            values.clear();
143            values.add(value);
144        } else {
145            getChildOrCreate(paths[idx]).set(paths, idx + 1, value);
146        }
147    }
148
149    public void remove(String[] paths, int idx, Object value) {
150        if (idx >= paths.length) {
151            values.remove(value);
152            pruneIfEmpty();
153        } else {
154            getChildOrCreate(paths[idx]).remove(paths, ++idx, value);
155        }
156    }
157
158    public void removeAll(Set<DestinationNode> answer, String[] paths, int startIndex) {
159        DestinationNode node = this;
160        int size = paths.length;
161        for (int i = startIndex; i < size && node != null; i++) {
162
163            String path = paths[i];
164            if (path.equals(ANY_DESCENDENT)) {
165                answer.addAll(node.removeDesendentValues());
166                break;
167            }
168
169            // TODO is this correct, we are appending wildcard values here???
170            node.appendMatchingWildcards(answer, paths, i);
171            if (path.equals(ANY_CHILD)) {
172                // node = node.getAnyChildNode();
173                node = new AnyChildDestinationNode(node);
174            } else {
175                node = node.getChild(path);
176            }
177        }
178
179        if (node != null) {
180            answer.addAll(node.removeValues());
181        }
182
183    }
184
185    @SuppressWarnings({ "rawtypes", "unchecked" })
186    public void appendDescendantValues(Set answer) {
187        // add children values, then recursively add their children
188        for(DestinationNode child : childNodes.values()) {
189            answer.addAll(child.getValues());
190            child.appendDescendantValues(answer);
191        }
192    }
193
194    /**
195     * Factory method to create a child node
196     */
197    protected DestinationMapNode createChildNode() {
198        return new DestinationMapNode(this);
199    }
200
201    /**
202     * Matches any entries in the map containing wildcards
203     */
204    @SuppressWarnings({ "rawtypes", "unchecked" })
205    public void appendMatchingWildcards(Set answer, String[] paths, int idx) {
206        if (idx - 1 > pathLength) {
207            return;
208        }
209        DestinationNode wildCardNode = getChild(ANY_CHILD);
210        if (wildCardNode != null) {
211            wildCardNode.appendMatchingValues(answer, paths, idx + 1);
212        }
213        wildCardNode = getChild(ANY_DESCENDENT);
214        if (wildCardNode != null) {
215            // for a wildcard Node match, add all values of the descendant node
216            answer.addAll(wildCardNode.getValues());
217            // and all descendants for paths like ">.>"
218            answer.addAll(wildCardNode.getDesendentValues());
219        }
220    }
221
222    public void appendMatchingValues(Set<DestinationNode> answer, String[] paths, int startIndex) {
223        DestinationNode node = this;
224        boolean couldMatchAny = true;
225        int size = paths.length;
226        for (int i = startIndex; i < size && node != null; i++) {
227            String path = paths[i];
228            if (path.equals(ANY_DESCENDENT)) {
229                answer.addAll(node.getDesendentValues());
230                couldMatchAny = false;
231                break;
232            }
233
234            node.appendMatchingWildcards(answer, paths, i);
235
236            if (path.equals(ANY_CHILD)) {
237                node = new AnyChildDestinationNode(node);
238            } else {
239                node = node.getChild(path);
240            }
241        }
242        if (node != null) {
243            answer.addAll(node.getValues());
244            if (couldMatchAny) {
245                // lets allow FOO.BAR to match the FOO.BAR.> entry in the map
246                DestinationNode child = node.getChild(ANY_DESCENDENT);
247                if (child != null) {
248                    answer.addAll(child.getValues());
249                }
250            }
251        }
252    }
253
254    public String getPath() {
255        return path;
256    }
257
258    public boolean isEmpty(){
259        return childNodes.isEmpty();
260    }
261
262    protected void pruneIfEmpty() {
263        if (parent != null && childNodes.isEmpty() && values.isEmpty()) {
264            parent.removeChild(this);
265        }
266    }
267
268    protected void removeChild(DestinationMapNode node) {
269        childNodes.remove(node.getPath());
270        pruneIfEmpty();
271    }
272}