in commons-digester3-core/src/main/java/org/apache/commons/digester3/ExtendedBaseRules.java [195:420]
public List<Rule> match( final String namespaceURI, final String pattern, final String name, final Attributes attributes )
{
// calculate the pattern of the parent (if the element has one)
String parentPattern = "";
final int lastIndex = pattern.lastIndexOf( '/' );
boolean hasParent = true;
if ( lastIndex == -1 )
{
// element has no parent
hasParent = false;
}
else
{
// calculate the pattern of the parent
parentPattern = pattern.substring( 0, lastIndex );
}
// we keep the list of universal matches separate
final List<Rule> universalList = new ArrayList<>( counter );
// Universal wildcards ('*') in the middle of the pattern-string
List<Rule> recList = null;
// temporary parentPattern
// we don't want to change anything....
String tempParentPattern = parentPattern;
int parentLastIndex = tempParentPattern.lastIndexOf( '/' );
// look for pattern. Here, we search the whole parent. Not ideal, but does the thing....
while ( parentLastIndex > -1 && recList == null )
{
recList = this.cache.get( tempParentPattern + "/*/" + pattern.substring( lastIndex + 1 ) );
if ( recList != null )
{
// when /*/-pattern-string is found, add method list to universalList.
// Digester will do the rest
universalList.addAll( recList );
}
else
{
// if not, shorten tempParent to move /*/ one position to the left.
// as last part of pattern is always added we make sure pattern is allowed anywhere.
tempParentPattern = parentPattern.substring( 0, parentLastIndex );
}
parentLastIndex = tempParentPattern.lastIndexOf( '/' );
}
// Universal all wildcards ('!*')
// These are always matched so always add them
List<Rule> tempList = this.cache.get( "!*" );
if ( tempList != null )
{
universalList.addAll( tempList );
}
// Universal exact parent match
// need to get this now since only wildcards are considered later
tempList = this.cache.get( "!" + parentPattern + "/?" );
if ( tempList != null )
{
universalList.addAll( tempList );
}
// base behavior means that if we certain matches, we don't continue but we just have
// a single combined loop and so we have to set a variable
boolean ignoreBasicMatches = false;
// see if we have an exact basic pattern match
List<Rule> rulesList = this.cache.get( pattern );
if ( rulesList != null )
{
// we have a match! so ignore all basic matches from now on
ignoreBasicMatches = true;
}
else if ( hasParent ) // see if we have an exact child match
{
// matching children takes preference
rulesList = this.cache.get( parentPattern + "/?" );
if ( rulesList != null )
{
// we have a match! so ignore all basic matches from now on
ignoreBasicMatches = true;
}
else
{
// we don't have a match yet - so try exact ancestor
rulesList = findExactAncesterMatch( pattern );
if ( rulesList != null )
{
// we have a match! so ignore all basic matches from now on
ignoreBasicMatches = true;
}
}
}
// OK - we're ready for the big loop!
// Unlike the basic rules case, we have to go through for all those universal rules in all cases.
// Find the longest key, ie more discriminant
int longKeyLength = 0;
for ( String key : this.cache.keySet() )
{
// find out if it's a universal pattern
// set a flag
final boolean isUniversal = key.startsWith( "!" );
if ( isUniversal )
{
// and find the underlying key
key = key.substring( 1 );
}
// don't need to check exact matches
final boolean wildcardMatchStart = key.startsWith( "*/" );
final boolean wildcardMatchEnd = key.endsWith( "/*" );
if ( wildcardMatchStart || isUniversal && wildcardMatchEnd )
{
boolean parentMatched = false;
boolean basicMatched = false;
boolean ancestorMatched = false;
final boolean parentMatchEnd = key.endsWith( "/?" );
if ( parentMatchEnd )
{
// try for a parent match
parentMatched = parentMatch( key, parentPattern );
}
else if ( wildcardMatchEnd )
{
// check for ancestor match
if ( wildcardMatchStart )
{
final String patternBody = key.substring( 2, key.length() - 2 );
if ( pattern.endsWith( patternBody ) )
{
ancestorMatched = true;
}
else
{
ancestorMatched = pattern.contains( patternBody + "/" );
}
}
else
{
final String bodyPattern = key.substring( 0, key.length() - 2 );
if ( pattern.startsWith( bodyPattern ) )
{
if ( pattern.length() == bodyPattern.length() )
{
// exact match
ancestorMatched = true;
}
else
{
ancestorMatched = pattern.charAt( bodyPattern.length() ) == '/';
}
}
}
}
else
{
// try for a base match
basicMatched = basicMatch( key, pattern );
}
if ( parentMatched || basicMatched || ancestorMatched )
{
if ( isUniversal )
{
// universal rules go straight in (no longest matching rule)
tempList = this.cache.get( "!" + key );
if ( tempList != null )
{
universalList.addAll( tempList );
}
}
else if ( !ignoreBasicMatches )
{
// ensure that all parent matches are SHORTER than rules with same level of matching.
//
// the calculations below don't work for universal matching,
// but we don't care because in that case this if-stmt is not entered.
int keyLength = key.length();
if ( wildcardMatchStart )
{
--keyLength;
}
if ( wildcardMatchEnd || parentMatchEnd )
{
--keyLength;
}
if ( keyLength > longKeyLength )
{
rulesList = this.cache.get( key );
longKeyLength = keyLength;
}
}
}
}
}
// '*' works in practice as a default matching (this is because anything is a deeper match!)
if ( rulesList == null )
{
rulesList = this.cache.get( "*" );
}
// if we've matched a basic pattern, then add to the universal list
if ( rulesList != null )
{
universalList.addAll( rulesList );
}
// don't filter if namespace is null
if ( namespaceURI != null )
{
// remove invalid namespaces
universalList.removeIf( rule -> rule.getNamespaceURI() != null && !rule.getNamespaceURI().equals( namespaceURI ) );
}
// need to make sure that the collection is sorted in the order of addition. We use a custom comparator for this
universalList.sort( Comparator.nullsFirst( Comparator.comparing( order::get ) ) );
return universalList;
}