/** * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.hadoop.gateway.util.urltemplate; import java.util.Collection; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.Map; import java.util.regex.Pattern; abstract class Segment { static final String ANONYMOUS_PARAM = ""; static final String DEFAULT_PATTERN = ""; static final String STAR_PATTERN = "*"; static final String GLOB_PATTERN = "**"; static final char DOT = '.'; static final char ESC = '\\'; // Note: The order of these is important. The numbers must be from most specific to least. public static final int STATIC = 1; public static final int REGEX = 2; public static final int STAR = 3; public static final int DEFAULT = 4; public static final int GLOB = 5; public static final int UNKNOWN = 6; // private String paramName; // ?queryName={paramName=value} private Token token; private Map<String,Value> values; // protected Segment( String paramName, String valuePattern ) { // this.paramName = paramName; // this.values = new LinkedHashMap<String,Value>(); // this.values.put( valuePattern, new Value( valuePattern ) ); // } protected Segment( Token token ) { this.token = token; this.values = new LinkedHashMap<String,Value>(); this.values.put( token.effectivePattern, new Value( token ) ); } // protected Segment( Segment that ) { // this.paramName = that.paramName; // this.values = new LinkedHashMap<String,Value>(); // for( Value thatValue : that.getValues() ) { // Value thisValue = new Value( thatValue ); // this.values.put( thisValue.getPattern(), thisValue ); // } // } protected Token getToken() { return token; } @Override public int hashCode() { return token.parameterName.hashCode(); } @Override @SuppressWarnings( "unchecked" ) public boolean equals( Object obj ) { boolean equal = false; if( obj instanceof Segment ) { Segment that = (Segment)obj; equal = ( this.token.parameterName.equals( that.token.parameterName ) && this.values.size() == that.values.size() ); if( equal ) { for( String pattern: this.values.keySet() ) { equal = that.values.containsKey( pattern ); if( !equal ) { break; } } } } return equal; } public String getParamName() { return token.parameterName; } public Collection<Value> getValues() { return values.values(); } public Value getFirstValue() { Value first = null; if( !values.isEmpty() ) { first = values.values().iterator().next(); } return first; } public boolean matches( Segment that ) { if( getClass().isInstance( that ) ) { for( Value thisValue: this.values.values() ) { for( Value thatValue: that.values.values() ) { if( thisValue.matches( thatValue ) ) { return true; } } } } return false; } void addValue( Token token ) { Value value = new Value( token ); values.put( token.effectivePattern, value ); } // void addValue( String valuePattern ) { // Value value = new Value( valuePattern ); // values.put( valuePattern, value ); // } public String toString() { StringBuilder s = new StringBuilder(); s.append( getParamName() ); Collection<Value> values = getValues(); if( values == null ) { s.append( "null" ); } else if( values.isEmpty() ) { s.append( "empty" ); } else { s.append( "[" ); Iterator i = values.iterator(); while( i.hasNext() ) { s.append( i.next() ); if( i.hasNext() ) { s.append( "," ); } } s.append( "]" ); } return s.toString(); } public class Value { private int type; private Token token; private Pattern regex; private Value( Token token ) { this.token = token; this.regex = null; String effectivePattern = token.effectivePattern; if( token.isLiteral() ) { this.type = STATIC; } else if( DEFAULT_PATTERN.equals( effectivePattern ) ) { this.type = DEFAULT; } else if( STAR_PATTERN.equals( effectivePattern ) ) { this.type = STAR; } else if( GLOB_PATTERN.equals( effectivePattern ) ) { type = GLOB; } else if ( effectivePattern != null && effectivePattern.contains( STAR_PATTERN ) ) { this.type = REGEX; this.regex = compileRegex( effectivePattern ); } else { this.type = STATIC; } } // private Value( String pattern ) { // this.pattern = pattern; // this.regex = null; // if( DEFAULT_PATTERN.equals( pattern ) ) { // this.type = DEFAULT; // } else if( STAR_PATTERN.equals( pattern ) ) { // this.type = STAR; // } else if( GLOB_PATTERN.equals( pattern ) ) { // type = GLOB; // } else if ( pattern != null && pattern.contains( STAR_PATTERN ) ) { // this.type = REGEX; // this.regex = compileRegex( pattern ); // } else { // this.type = STATIC; // } // } private Value( Value that ) { this.type = that.type; this.token = that.token; this.regex = that.regex; } Token getToken() { return token; } public int getType() { return type; } public String getPattern() { return token.originalPattern; } String getOriginalPattern() { return token.originalPattern; } String getEffectivePattern() { return token.effectivePattern; } public Pattern getRegex() { return regex; } public String toString() { return token.effectivePattern; } public boolean matches( Value that ) { boolean matches = getClass().isInstance( that ); if( matches ) { switch( this.getType() ) { case( STATIC ): matches = this.token.originalPattern.equals( that.token.originalPattern ); //matches = matchThisStatic( that ); // See: MatcherTest.testWildcardCharacterInInputTemplate break; case( DEFAULT ): case( STAR ): case( GLOB ): matches = true; //matches = matchThisWildcard( that ); // See: MatcherTest.testWildcardCharacterInInputTemplate break; case( REGEX ): matches = this.regex.matcher( that.token.effectivePattern ).matches(); //matches = matchThisRegex( that ); // See: MatcherTest.testWildcardCharacterInInputTemplate break; default: matches = false; } } return matches; } // See: MatcherTest.testWildcardCharacterInInputTemplate // private boolean matchThisStatic( Value that ) { // boolean matches = false; // switch( that.getType() ) { // case( STATIC ): // matches = this.pattern.equals( that.pattern ); // break; // case( DEFAULT ): // case( STAR ): // case( GLOB ): // matches = true; // break; // case( REGEX ): // matches = that.regex.matcher( this.pattern ).matches(); // break; // } // return matches; // } // See: MatcherTest.testWildcardCharacterInInputTemplate // private boolean matchThisWildcard( Value that ) { // boolean matches = false; // switch( that.getType() ) { // case( STATIC ): // matches = true; // break; // case( DEFAULT ): // case( STAR ): // case( GLOB ): // matches = true; // break; // case( REGEX ): // matches = true; // break; // } // return matches; // } // See: MatcherTest.testWildcardCharacterInInputTemplate // private boolean matchThisRegex( Value that ) { // boolean matches = false; // switch( that.getType() ) { // case( STATIC ): // matches = this.regex.matcher( that.pattern ).matches(); // break; // case( DEFAULT ): // case( STAR ): // case( GLOB ): // matches = true; // break; // case( REGEX ): // matches = this.pattern.equals( that.pattern ); // break; // } // return matches; // } } // Escape .\${ and turn * into .* static final String createRegex( final String segment ) { StringBuilder regex = new StringBuilder( segment ); int len = regex.length(); for( int i=len-1; i>=0; i-- ) { char c = regex.charAt( i ); switch( c ) { case '*': regex.insert( i, DOT ); break; case '\\': case '.': case '{': case '}': case '$': regex.insert( i, ESC ); break; default: // noop } } return regex.toString(); } // Creates a pattern for a simplified filesystem style wildcard '*' syntax. static final Pattern compileRegex( String segment ) { // // Turn '*' into '/' to keep it safe. // // Chose '/' because that can't exist in a segment. // segment = segment.replaceAll( "\\*", "/" ); // // Turn '.' into '\.'. // segment = segment.replaceAll( "\\.", "\\\\." ); // // Turn '$' into '\$'. // segment = escapeSpecialRegExChar( segment, '$' ); // segment = escapeSpecialRegExChar( segment, '{' ); // // Turn '/' back into '.*'. // segment = segment.replaceAll( "/", "\\.\\*" ); segment = createRegex( segment ); return Pattern.compile( segment ); } // private static String escapeSpecialRegExChar( String input, char c ) { // int i = input.indexOf( c ); // if( i >= 0 ) { // int inputLength = input.length(); // StringBuilder output = new StringBuilder( inputLength + 1 ); // output.append( input, 0, i ); // output.append( '\\' ); // if( i < inputLength ) { // output.append( input, i, inputLength ); // } // input = output.toString(); // } // return input; // } // Escape }