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 }