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 }