001package 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 022import java.util.ArrayList; 023import java.util.HashMap; 024import java.util.LinkedList; 025import java.util.List; 026 027import org.xml.sax.Attributes; 028 029/** 030 * <p> 031 * Default implementation of the <code>Rules</code> interface that supports the standard rule matching behavior. This 032 * class can also be used as a base class for specialized <code>Rules</code> implementations. 033 * </p> 034 * <p> 035 * The matching policies implemented by this class support two different types of pattern matching rules: 036 * </p> 037 * <ul> 038 * <li><em>Exact Match</em> - A pattern "a/b/c" exactly matches a <code><c></code> element, nested inside a 039 * <code><b></code> element, which is nested inside an <code><a></code> element.</li> 040 * <li><em>Tail Match</em> - A pattern "*/a/b" matches a <code><b></code> element, nested inside an 041 * <code><a></code> element, no matter how deeply the pair is nested.</li> 042 * </ul> 043 * <p> 044 * Note that wildcard patterns are ignored if an explicit match can be found (and when multiple wildcard patterns match, 045 * only the longest, ie most explicit, pattern is considered a match). 046 * </p> 047 * <p> 048 * See the package documentation for package org.apache.commons.digester3 for more information. 049 * </p> 050 */ 051 052public class RulesBase 053 extends AbstractRulesImpl 054{ 055 056 // ----------------------------------------------------- Instance Variables 057 058 /** 059 * The set of registered Rule instances, keyed by the matching pattern. Each value is a List containing the Rules 060 * for that pattern, in the order that they were orginally registered. 061 */ 062 protected HashMap<String, List<Rule>> cache = new HashMap<String, List<Rule>>(); 063 064 /** 065 * The subset of registered Rule instances with wildcard pattern. 066 */ 067 protected List<String> wildcardCache = new LinkedList<String>(); 068 069 /** 070 * The set of registered Rule instances, in the order that they were originally registered. 071 */ 072 protected ArrayList<Rule> rules = new ArrayList<Rule>(); 073 074 // ------------------------------------------------------------- Properties 075 076 /** 077 * {@inheritDoc} 078 */ 079 @Override 080 public void setDigester( Digester digester ) 081 { 082 super.setDigester( digester ); 083 for ( Rule rule : rules ) 084 { 085 rule.setDigester( digester ); 086 } 087 } 088 089 // --------------------------------------------------------- Public Methods 090 091 /** 092 * {@inheritDoc} 093 */ 094 @Override 095 protected void registerRule( String pattern, Rule rule ) 096 { 097 // to help users who accidently add '/' to the end of their patterns 098 int patternLength = pattern.length(); 099 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}