/*
* $Id: ArraysUtil.java 1011 2008-06-16 17:57:36Z amandel $
*
* Copyright 2006, The jCoderZ.org Project. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following
* disclaimer in the documentation and/or other materials
* provided with the distribution.
* * Neither the name of the jCoderZ.org Project nor the names of
* its contributors may be used to endorse or promote products
* derived from this software without specific prior written
* permission.
*
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
* BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package org.jcoderz.commons.tracing;
import java.util.Arrays;
import java.util.Iterator;
import java.util.regex.Pattern;
import org.jcoderz.commons.ArgumentMalformedException;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
/**
* This class allows to match aspectJ like patterns with the java
* internal class representation.
* <p>
* The following limitations / specialties should be considered:
* <ul>
* <li>the '+' is not supported so you can not check for classes
* implementing a certain interface.</li>
* <li><code>throws</code> is not supported as a part of the matching
* signature.</li>
* <li>The pattern is based on the information you can find at <a
* href="http://aspectwerkz.codehaus.org/definition_issues.html">
* aspectwerkz</a></li>
* <li>The method name <code>new</code> can be used to refer to the
* constructor (<code><init></code>) and to the class (
* <code><clinit></code>) initialization.</li>
* </ul>
* TODO: Javadoc
* TODO: Support '+' for inheritance
*
* @author Andreas Mandel
*/
public class AspectPattern
{
private final String mPattern;
private final String mClassRegex;
private final String mMethodRegex;
private String mReturnPattern;
/** The tracer class that should be called if the pattern matches. */
private String mTracerClass;
private int mModifiers;
private Pattern mClassMatcher;
private Pattern mMethodMatcher;
/**
* Creates new aspect pattern matcher. The input is translated into
* regular expressions which are compiled and stored in this
* AspectPattern.
*
* @param in the aspectJ like pattern.
*/
public AspectPattern (String in)
{
mPattern = in;
int pos = 0;
pos = getTracerClass(0);
pos = getModifiers(pos);
pos = getReturnType(pos);
final int paraPos = mPattern.indexOf('(', pos);
final int methodPos = mPattern.substring(pos, paraPos).lastIndexOf('.')
+ pos;
// TODO Support +!
mClassRegex = convertToPattern(mPattern.substring(pos, methodPos));
final String methodName = convertToMethod(mPattern.substring(
methodPos + 1, paraPos));
final String arguments = convertToArgumets(mPattern.substring(
paraPos + 1, mPattern.length() - 1));
mMethodRegex = methodName + "\\(" + arguments + "\\)" + mReturnPattern;
}
/**
* Parse the optional name of the tracer class to be used.
* As a side effect {@link #mTracerClass} is set.
* @param i the pos where to start parsing
* @return the pos after the tracer class name.
*/
private int getTracerClass (int i)
{
int pos = i;
int end = i;
while (pos < mPattern.length()
&& Character.isWhitespace(mPattern.charAt(pos)))
{
pos++;
}
if (pos < mPattern.length()
&& mPattern.charAt(pos) == '(')
{
end = mPattern.indexOf(')', pos);
if (end == -1)
{
throw new ArgumentMalformedException(
"pattern", mPattern,
"Opening '(' for tracing class name is not closed.");
}
mTracerClass = mPattern.substring(pos + 1, end);
end += 1;
while (end < mPattern.length()
&& Character.isWhitespace(mPattern.charAt(end)))
{
end++;
}
}
return end;
}
/**
* Tests if the given class with the given access flags matches this
* pattern.
*
* @param acc the access flags.
* @param className name of the class in internal representation.
* @param arguments argument string in internal representation.
* @return true if the given parameters match this matcher.
*/
public boolean matches (int acc, String className, String arguments)
{
return ((acc & mModifiers) == mModifiers
&& className.matches(mClassRegex)
&& arguments.matches(mMethodRegex));
}
/**
* @param substring
* @return
*/
private String convertToArgumets (String substring)
{
final String[] args = substring.split(",");
final StringBuffer pattern = new StringBuffer();
final Iterator i = Arrays.asList(args).iterator();
while (i.hasNext())
{
final String arg = (String) i.next();
if (!org.jcoderz.commons.util.StringUtil.isEmptyOrNull(arg))
{
final Type argType = tryBasicType(arg);
if (argType == null)
{
if ("*".equals(arg))
{
pattern.append("\\[*([VZCBSIFJD]|L[^;]+;)");
}
else if ("..".equals(arg))
{
pattern.append(".*");
}
else
{
pattern.append(convertToArgumentPattern(arg));
}
}
else
{
pattern.append(argType.getDescriptor());
}
}
}
return pattern.toString();
}
/**
* @param method
* @return
*/
private String convertToMethod (String method)
{
String result;
// NEW!!!
if ("new".equals(method))
{
if ((mModifiers & Opcodes.ACC_STATIC) != 0)
{
result = "<init>";
}
else
{
result = "<clinit>";
}
}
else
{
result = method.replaceAll("\\*", ".*");
}
return result;
}
/**
* @param pos
* @return
*/
private int getReturnType (int pos)
{
final int result = (mPattern + " ").indexOf(' ', pos) + 1;
final String typeString = mPattern.substring(pos, result - 1);
final Type returnType = tryBasicType(typeString);
if (returnType == null)
{
mReturnPattern = convertToArgumentPattern(typeString);
}
else
{
mReturnPattern = returnType.getDescriptor();
}
return result;
}
/**
* @param typeString
* @return
*/
private String convertToPattern (String typeString)
{
String resultPattern = typeString;
if (org.jcoderz.commons.util.StringUtil.isNullOrEmpty(typeString))
{
resultPattern = "";
}
else if ("*".equals(typeString))
{
resultPattern = "[^/]*"; // any
}
else
{
resultPattern = resultPattern.replaceAll("\\.", "/");
resultPattern = resultPattern.replaceAll("\\*", "[^\\/]*");
resultPattern = resultPattern.replaceAll("//", ".*");
// NO 'java.lang / java.util Magic if package is given or
// wildcards are used.
if (resultPattern.indexOf('/') < 0
&& resultPattern.indexOf('*') < 0)
{
final StringBuffer pattern = new StringBuffer("(java/util/");
pattern.append(resultPattern);
pattern.append('|');
pattern.append("java/lang/");
pattern.append(resultPattern);
pattern.append('|');
pattern.append(resultPattern);
pattern.append(')');
resultPattern = pattern.toString();
}
}
return resultPattern;
}
/**
* @param typeString
* @return
*/
private String convertToArgumentPattern (String typeString)
{
String resultPattern = typeString;
if (org.jcoderz.commons.util.StringUtil.isNullOrEmpty(typeString))
{
resultPattern = "";
}
else if ("*".equals(typeString))
{
resultPattern = "\\[*([VZCBSIFJD]|L[^;]+;)";
}
else
{
String arrayPrefix = "";
// take care for arrays!!!
while (resultPattern.endsWith("[]"))
{
arrayPrefix += "\\[";
resultPattern
= resultPattern.substring(0, resultPattern.length()
- "[]".length());
}
resultPattern = resultPattern.replaceAll("\\.", "/");
resultPattern = resultPattern.replaceAll("\\*", "[^\\/]*");
resultPattern = resultPattern.replaceAll("//", ".*");
// NO 'java.lang / java.util Magic if package is given or
// wildcards are used.
if (resultPattern.indexOf('/') >= 0
|| resultPattern.indexOf('*') >= 0)
{
final StringBuffer pattern = new StringBuffer(arrayPrefix);
pattern.append('L');
pattern.append(resultPattern);
pattern.append(';');
resultPattern = pattern.toString();
}
else
{
final StringBuffer pattern = new StringBuffer("(");
pattern.append(arrayPrefix);
pattern.append("Ljava/util/");
pattern.append(resultPattern);
pattern.append(';');
pattern.append('|');
pattern.append(arrayPrefix);
pattern.append("Ljava/lang/");
pattern.append(resultPattern);
pattern.append(';');
pattern.append('|');
pattern.append(arrayPrefix);
pattern.append('L');
pattern.append(resultPattern);
pattern.append(";)");
resultPattern = pattern.toString();
}
}
return resultPattern;
}
/**
* @param typeString
* @return
*/
private Type tryBasicType (String typeString)
{
final StringBuffer type = new StringBuffer();
String in = typeString;
while (in.endsWith("[]"))
{
type.append('[');
in = in.substring(0, in.length() - "[]".length());
}
if ("int".equals(in))
{
type.append('I');
}
else if ("void".equals(in))
{
type.append('V');
}
else if ("boolean".equals(in))
{
type.append('Z');
}
else if ("char".equals(in))
{
type.append('C');
}
else if ("short".equals(in))
{
type.append('S');
}
else if ("float".equals(in))
{
type.append('F');
}
else if ("long".equals(in))
{
type.append('J');
}
else if ("double".equals(in))
{
type.append('D');
}
else
{
type.setLength(0);
}
Type resultType = null;
if (type.length() != 0)
{
resultType = Type.getType(type.toString());
if (resultType.getSort() == Type.OBJECT)
{
resultType = null;
}
}
return resultType;
}
/**
* @param pos
* @return
*/
private int getModifiers (int pos)
{
int result = pos;
int oldResult;
do
{
oldResult = result;
result = checkModifier("public ", Opcodes.ACC_PUBLIC, result);
result = checkModifier("private ", Opcodes.ACC_PRIVATE, result);
result = checkModifier("protected ", Opcodes.ACC_PROTECTED, result);
result = checkModifier("static ", Opcodes.ACC_STATIC, result);
result = checkModifier("final ", Opcodes.ACC_FINAL, result);
result
= checkModifier(
"synchronized ", Opcodes.ACC_SYNCHRONIZED, result);
result
= checkModifier(
"deprecated ", Opcodes.ACC_DEPRECATED, result);
}
while (result != oldResult);
return result;
}
/**
* @param modifier
* @param acc
* @param pos
* @return
*/
private int checkModifier (String modifier, int acc, int pos)
{
int result = pos;
if (mPattern.startsWith(modifier, pos))
{
mModifiers |= acc;
result += modifier.length();
}
return result;
}
public int getModifiers ()
{
return mModifiers;
}
public String getMethodPattern ()
{
return mMethodRegex;
}
public String getClassRegex ()
{
return mClassRegex;
}
// HOWTO Handle inheritance?
/**
* @param internalClassname
* @return
*/
public boolean matchClass (String internalClassname)
{
if (mClassMatcher == null)
{
mClassMatcher = Pattern.compile(mClassRegex);
}
return mClassMatcher.matcher(internalClassname).matches();
}
/**
* @param methodDesc
* @return
*/
public boolean matchMethod (String methodDesc)
{
if (mMethodMatcher == null)
{
mMethodMatcher = Pattern.compile(mMethodRegex);
}
return mMethodMatcher.matcher(methodDesc).matches();
}
/** {@inheritDoc} */
public String toString ()
{
return mPattern + " = '" + AsmUtil.toString(mModifiers) + " "
+ mClassRegex + "#" + mMethodRegex + "'";
}
/**
* @param access
* @return
*/
public boolean matchAccess (int access)
{
return (access & mModifiers) == mModifiers;
}
String getMethodRegex ()
{
return mMethodRegex;
}
public String getTracerClass ()
{
return mTracerClass;
}
}