1 package org.apache.commons.digester3;
2
3 /*
4 * Licensed to the Apache Software Foundation (ASF) under one
5 * or more contributor license agreements. See the NOTICE file
6 * distributed with this work for additional information
7 * regarding copyright ownership. The ASF licenses this file
8 * to you under the Apache License, Version 2.0 (the
9 * "License"); you may not use this file except in compliance
10 * with the License. You may obtain a copy of the License at
11 *
12 * http://www.apache.org/licenses/LICENSE-2.0
13 *
14 * Unless required by applicable law or agreed to in writing,
15 * software distributed under the License is distributed on an
16 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17 * KIND, either express or implied. See the License for the
18 * specific language governing permissions and limitations
19 * under the License.
20 */
21
22 import java.util.ArrayList;
23 import java.util.HashMap;
24 import java.util.LinkedList;
25 import java.util.List;
26
27 import org.xml.sax.Attributes;
28
29 /**
30 * <p>
31 * Default implementation of the <code>Rules</code> interface that supports the standard rule matching behavior. This
32 * class can also be used as a base class for specialized <code>Rules</code> implementations.
33 * </p>
34 * <p>
35 * The matching policies implemented by this class support two different types of pattern matching rules:
36 * </p>
37 * <ul>
38 * <li><em>Exact Match</em> - A pattern "a/b/c" exactly matches a <code><c></code> element, nested inside a
39 * <code><b></code> element, which is nested inside an <code><a></code> element.</li>
40 * <li><em>Tail Match</em> - A pattern "*/a/b" matches a <code><b></code> element, nested inside an
41 * <code><a></code> element, no matter how deeply the pair is nested.</li>
42 * </ul>
43 * <p>
44 * Note that wildcard patterns are ignored if an explicit match can be found (and when multiple wildcard patterns match,
45 * only the longest, ie most explicit, pattern is considered a match).
46 * </p>
47 * <p>
48 * See the package documentation for package org.apache.commons.digester3 for more information.
49 * </p>
50 */
51
52 public class RulesBase
53 extends AbstractRulesImpl
54 {
55
56 // ----------------------------------------------------- Instance Variables
57
58 /**
59 * The set of registered Rule instances, keyed by the matching pattern. Each value is a List containing the Rules
60 * for that pattern, in the order that they were orginally registered.
61 */
62 protected HashMap<String, List<Rule>> cache = new HashMap<String, List<Rule>>();
63
64 /**
65 * The subset of registered Rule instances with wildcard pattern.
66 */
67 protected List<String> wildcardCache = new LinkedList<String>();
68
69 /**
70 * The set of registered Rule instances, in the order that they were originally registered.
71 */
72 protected ArrayList<Rule> rules = new ArrayList<Rule>();
73
74 // ------------------------------------------------------------- Properties
75
76 /**
77 * {@inheritDoc}
78 */
79 @Override
80 public void setDigester( Digester digester )
81 {
82 super.setDigester( digester );
83 for ( Rule rule : rules )
84 {
85 rule.setDigester( digester );
86 }
87 }
88
89 // --------------------------------------------------------- Public Methods
90
91 /**
92 * {@inheritDoc}
93 */
94 @Override
95 protected void registerRule( String pattern, Rule rule )
96 {
97 // to help users who accidently add '/' to the end of their patterns
98 int patternLength = pattern.length();
99 if ( patternLength > 1 && pattern.endsWith( "/" ) )
100 {
101 pattern = pattern.substring( 0, patternLength - 1 );
102 }
103
104 List<Rule> list = cache.get( pattern );
105 if ( list == null )
106 {
107 list = new ArrayList<Rule>();
108 if ( pattern.startsWith( "*/" ) )
109 {
110 wildcardCache.add( pattern.substring( 1 ) );
111 }
112 cache.put( pattern, list );
113 }
114 list.add( rule );
115 rules.add( rule );
116 }
117
118 /**
119 * {@inheritDoc}
120 */
121 public void clear()
122 {
123 wildcardCache.clear();
124 cache.clear();
125 rules.clear();
126 }
127
128 /**
129 * {@inheritDoc}
130 */
131 public List<Rule> match( String namespaceURI, String pattern, String name, Attributes attributes )
132 {
133 // List rulesList = (List) this.cache.get(pattern);
134 List<Rule> rulesList = lookup( namespaceURI, pattern );
135 if ( ( rulesList == null ) || ( rulesList.size() < 1 ) )
136 {
137 // Find the longest key, ie more discriminant
138 String longKey = "";
139 for ( String key : wildcardCache )
140 {
141 if ( ( pattern.equals( key.substring( 1 ) ) || pattern.endsWith( key ) )
142 && key.length() > longKey.length() )
143 {
144 longKey = key;
145 }
146 }
147 if ( longKey.length() > 0 )
148 {
149 rulesList = lookup( namespaceURI, "*" + longKey );
150 }
151 }
152 if ( rulesList == null )
153 {
154 rulesList = new ArrayList<Rule>();
155 }
156 return ( rulesList );
157 }
158
159 /**
160 * {@inheritDoc}
161 */
162 public List<Rule> rules()
163 {
164 return ( this.rules );
165 }
166
167 // ------------------------------------------------------ Protected Methods
168
169 /**
170 * Return a List of Rule instances for the specified pattern that also match the specified namespace URI (if any).
171 * If there are no such rules, return <code>null</code>.
172 *
173 * @param namespaceURI Namespace URI to match, or <code>null</code> to select matching rules regardless of namespace
174 * URI
175 * @param pattern Pattern to be matched
176 * @return a List of Rule instances for the specified pattern that also match the specified namespace URI (if any)
177 */
178 protected List<Rule> lookup( String namespaceURI, String pattern )
179 {
180 // Optimize when no namespace URI is specified
181 List<Rule> list = this.cache.get( pattern );
182 if ( list == null )
183 {
184 return ( null );
185 }
186 if ( ( namespaceURI == null ) || ( namespaceURI.length() == 0 ) )
187 {
188 return ( list );
189 }
190
191 // Select only Rules that match on the specified namespace URI
192 ArrayList<Rule> results = new ArrayList<Rule>();
193 for ( Rule item : list )
194 {
195 if ( ( namespaceURI.equals( item.getNamespaceURI() ) ) || ( item.getNamespaceURI() == null ) )
196 {
197 results.add( item );
198 }
199 }
200 return ( results );
201 }
202
203 }