001 package org.apache.commons.digester3;
002
003 /*
004 * Licensed to the Apache Software Foundation (ASF) under one
005 * or more contributor license agreements. See the NOTICE file
006 * distributed with this work for additional information
007 * regarding copyright ownership. The ASF licenses this file
008 * to you under the Apache License, Version 2.0 (the
009 * "License"); you may not use this file except in compliance
010 * with the License. You may obtain a copy of the License at
011 *
012 * http://www.apache.org/licenses/LICENSE-2.0
013 *
014 * Unless required by applicable law or agreed to in writing,
015 * software distributed under the License is distributed on an
016 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017 * KIND, either express or implied. See the License for the
018 * specific language governing permissions and limitations
019 * under the License.
020 */
021
022 import java.util.ArrayList;
023 import java.util.HashMap;
024 import java.util.List;
025
026 import org.xml.sax.Attributes;
027
028 /**
029 * <p>
030 * Default implementation of the <code>Rules</code> interface that supports the standard rule matching behavior. This
031 * class can also be used as a base class for specialized <code>Rules</code> implementations.
032 * </p>
033 * <p>
034 * The matching policies implemented by this class support two different types of pattern matching rules:
035 * </p>
036 * <ul>
037 * <li><em>Exact Match</em> - A pattern "a/b/c" exactly matches a <code><c></code> element, nested inside a
038 * <code><b></code> element, which is nested inside an <code><a></code> element.</li>
039 * <li><em>Tail Match</em> - A pattern "*/a/b" matches a <code><b></code> element, nested inside an
040 * <code><a></code> element, no matter how deeply the pair is nested.</li>
041 * </ul>
042 * <p>
043 * Note that wildcard patterns are ignored if an explicit match can be found (and when multiple wildcard patterns match,
044 * only the longest, ie most explicit, pattern is considered a match).
045 * </p>
046 * <p>
047 * See the package documentation for package org.apache.commons.digester3 for more information.
048 * </p>
049 */
050
051 public class RulesBase
052 extends AbstractRulesImpl
053 {
054
055 // ----------------------------------------------------- Instance Variables
056
057 /**
058 * The set of registered Rule instances, keyed by the matching pattern. Each value is a List containing the Rules
059 * for that pattern, in the order that they were orginally registered.
060 */
061 protected HashMap<String, List<Rule>> cache = new HashMap<String, List<Rule>>();
062
063 /**
064 * The set of registered Rule instances, in the order that they were originally registered.
065 */
066 protected ArrayList<Rule> rules = new ArrayList<Rule>();
067
068 // ------------------------------------------------------------- Properties
069
070 /**
071 * {@inheritDoc}
072 */
073 @Override
074 public void setDigester( Digester digester )
075 {
076 super.setDigester( digester );
077 for ( Rule rule : rules )
078 {
079 rule.setDigester( digester );
080 }
081 }
082
083 // --------------------------------------------------------- Public Methods
084
085 /**
086 * {@inheritDoc}
087 */
088 @Override
089 protected void registerRule( String pattern, Rule rule )
090 {
091 // to help users who accidently add '/' to the end of their patterns
092 int patternLength = pattern.length();
093 if ( patternLength > 1 && pattern.endsWith( "/" ) )
094 {
095 pattern = pattern.substring( 0, patternLength - 1 );
096 }
097
098 List<Rule> list = cache.get( pattern );
099 if ( list == null )
100 {
101 list = new ArrayList<Rule>();
102 cache.put( pattern, list );
103 }
104 list.add( rule );
105 rules.add( rule );
106 }
107
108 /**
109 * {@inheritDoc}
110 */
111 public void clear()
112 {
113 cache.clear();
114 rules.clear();
115 }
116
117 /**
118 * {@inheritDoc}
119 */
120 public List<Rule> match( String namespaceURI, String pattern, String name, Attributes attributes )
121 {
122 // List rulesList = (List) this.cache.get(pattern);
123 List<Rule> rulesList = lookup( namespaceURI, pattern );
124 if ( ( rulesList == null ) || ( rulesList.size() < 1 ) )
125 {
126 // Find the longest key, ie more discriminant
127 String longKey = "";
128 for ( String key : cache.keySet() )
129 {
130 if ( key.startsWith( "*/" )
131 && ( pattern.equals( key.substring( 2 ) ) || pattern.endsWith( key.substring( 1 ) )
132 && key.length() > longKey.length() ) )
133 {
134 // rulesList = (List) this.cache.get(key);
135 rulesList = lookup( namespaceURI, key );
136 longKey = key;
137 }
138 }
139 }
140 if ( rulesList == null )
141 {
142 rulesList = new ArrayList<Rule>();
143 }
144 return ( rulesList );
145 }
146
147 /**
148 * {@inheritDoc}
149 */
150 public List<Rule> rules()
151 {
152 return ( this.rules );
153 }
154
155 // ------------------------------------------------------ Protected Methods
156
157 /**
158 * Return a List of Rule instances for the specified pattern that also match the specified namespace URI (if any).
159 * If there are no such rules, return <code>null</code>.
160 *
161 * @param namespaceURI Namespace URI to match, or <code>null</code> to select matching rules regardless of namespace
162 * URI
163 * @param pattern Pattern to be matched
164 * @return a List of Rule instances for the specified pattern that also match the specified namespace URI (if any)
165 */
166 protected List<Rule> lookup( String namespaceURI, String pattern )
167 {
168 // Optimize when no namespace URI is specified
169 List<Rule> list = this.cache.get( pattern );
170 if ( list == null )
171 {
172 return ( null );
173 }
174 if ( ( namespaceURI == null ) || ( namespaceURI.length() == 0 ) )
175 {
176 return ( list );
177 }
178
179 // Select only Rules that match on the specified namespace URI
180 ArrayList<Rule> results = new ArrayList<Rule>();
181 for ( Rule item : list )
182 {
183 if ( ( namespaceURI.equals( item.getNamespaceURI() ) ) || ( item.getNamespaceURI() == null ) )
184 {
185 results.add( item );
186 }
187 }
188 return ( results );
189 }
190
191 }