/**
* Copyright (c) 2010 Darmstadt University of Technology.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Marcel Bruch - initial API and implementation.
*/
package org.eclipse.recommenders.utils.names;
import static org.eclipse.recommenders.utils.Checks.*;
import static org.eclipse.recommenders.utils.Throws.throwIllegalArgumentException;
import static org.eclipse.recommenders.utils.names.VmTypeName.*;
import java.util.ArrayList;
import java.util.Map;
import org.apache.commons.lang3.StringUtils;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.MapMaker;
public class VmMethodName implements IMethodName {
private static final long serialVersionUID = 688964238062226061L;
private static Map<String /* name */, VmMethodName> index = new MapMaker().weakValues().makeMap();
public static synchronized VmMethodName get(final String vmFullQualifiedTypeName, final String vmMethodSignature) {
return get(vmFullQualifiedTypeName + "." + vmMethodSignature);
}
/**
* Creates a new {@link VmMethodName} from the given method argument but replaces the declaring type by the given
* new base type.
* <p>
* Example: vmMethodName = "Ljava/lang/String.wait()V", vmBaseTypeName = "Ljava/lang/Object" --> res =
* "Ljava/lang/Object.wait()".
*
* @param vmBaseTypeName
* @param vmMethodName
* @return
*/
public static VmMethodName rebase(final ITypeName vmBaseTypeName, final IMethodName vmMethodName) {
ensureIsInstanceOf(vmBaseTypeName, VmTypeName.class);
ensureIsInstanceOf(vmMethodName, VmMethodName.class);
return get(vmBaseTypeName.getIdentifier(), vmMethodName.getSignature());
}
public static synchronized VmMethodName get(final String vmFullQualifiedMethodName) {
VmMethodName res = index.get(vmFullQualifiedMethodName);
if (res == null) {
if (vmFullQualifiedMethodName.startsWith("< ")) {
throwIllegalArgumentException("invalid input: " + vmFullQualifiedMethodName);
}
res = new VmMethodName(vmFullQualifiedMethodName);
index.put(vmFullQualifiedMethodName, res);
}
return res;
}
public static final IMethodName NULL = VmMethodName.get("L_null.null()V");
// public static String removeGenerics(final String typeName) {
// return StringUtils.substringBefore(typeName, "<");
// }
private String identifier;
/**
* @see #get(String)
*/
@VisibleForTesting
protected VmMethodName(final String vmFullQualifiedMethodName) {
identifier = vmFullQualifiedMethodName;
// // perform syntax check by creating every possible element from this
// string. If no exception is thrown everything should be ok...
getDeclaringType();
getParameterTypes();
getReturnType();
}
@Override
public ITypeName getDeclaringType() {
final int bracket = identifier.lastIndexOf('(');
final int methodSeperator = identifier.lastIndexOf('.', bracket);
return VmTypeName.get(identifier.substring(0, methodSeperator));
}
@Override
public String getDescriptor() {
final int bracket = identifier.lastIndexOf('(');
return identifier.substring(bracket);
}
@Override
public String getIdentifier() {
return identifier;
}
@Override
public String getName() {
final int methodSeperator = identifier.lastIndexOf('.');
final int argumentsSeperator = identifier.lastIndexOf('(');
return identifier.substring(methodSeperator + 1, argumentsSeperator);
}
@Override
public ITypeName[] getParameterTypes() {
final ArrayList<VmTypeName> argTypes = new ArrayList<VmTypeName>();
final int openingBracket = identifier.lastIndexOf('(');
final char[] desc = identifier.substring(openingBracket + 1).toCharArray();
int off = 0;
while (true) {
if (desc[off] == ')') {
break;
}
switch (desc[off]) {
case 'V':
argTypes.add(VOID);
break;
case 'Z':
argTypes.add(BOOLEAN);
break;
case 'C':
argTypes.add(CHAR);
break;
case 'B':
argTypes.add(BYTE);
break;
case 'S':
argTypes.add(SHORT);
break;
case 'I':
argTypes.add(INT);
break;
case 'F':
argTypes.add(FLOAT);
break;
case 'J':
argTypes.add(LONG);
break;
case 'D':
argTypes.add(DOUBLE);
break;
case 'L': {
final int start = off;
do {
off++;
// TODO Marcel: Generics 'handling' is a bit strange... need
// to fix that here when fully supporting generics later on.
if (desc[off] == '<') {
off++;
int numberOfOpenGenerics = 1;
while (numberOfOpenGenerics != 0) {
switch (desc[off]) {
case '>':
numberOfOpenGenerics -= 1;
break;
case '<':
numberOfOpenGenerics += 1;
break;
}
off++;
}
}
} while (desc[off] != ';');
// off points to the ';' now
final String argumentTypeName = new String(desc, start, off - start);
argTypes.add(VmTypeName.get(argumentTypeName));
break;
}
case '[': {
final int start = off;
off++;
while (desc[off] == '[') {
// do we have an array? -> increase offset if we have
// multidimensional arrays:
// jump over all array counters
off++;
}
// now, off is guaranteed to point to the first letter of the
// type: either 'L' or a primitive letter.
// if we have an object type:
if (desc[off] == 'L') {
off++;
while (desc[off] != ';') {
// go forward until the next semicolon
off++;
}
// off points directly on the ';' Thus
final String typeName = new String(desc, start, off - start);
argTypes.add(VmTypeName.get(typeName));
} else {
// if it is not a declared type, off points directly on the
// primitive letter
final String typeName = new String(desc, start, off + 1 - start);
argTypes.add(VmTypeName.get(typeName));
}
break;
}
}
off++;
}
return argTypes.toArray(new VmTypeName[argTypes.size()]);
}
@Override
public ITypeName getReturnType() {
String returnType = StringUtils.substringAfterLast(identifier, ")");
// strip off throws type from method return
returnType = StringUtils.substringBefore(returnType, "|");
if (!returnType.endsWith(";")) {
// be sure that if it does not end with a ';' is MUST be a primitive
// or an array of primitives:
final ITypeName res = VmTypeName.get(returnType);
ensureIsTrue(res.isPrimitiveType() || res.isArrayType() && res.getArrayBaseType().isPrimitiveType());
return res;
} else {
returnType = StringUtils.substring(returnType, 0, -1);
return VmTypeName.get(returnType);
}
}
@Override
public String getSignature() {
final int methodSeparator = identifier.lastIndexOf('.');
return identifier.substring(methodSeparator + 1);
}
@Override
public boolean isInit() {
final String name = getName();
return "<init>".equals(name) || "<subtype-init>".equals(name);
}
@Override
public boolean isStaticInit() {
return "<clinit>".equals(getName());
}
@Override
public boolean isSynthetic() {
return getName().contains("$");
}
@Override
public boolean similar(final IMethodName other) {
String s1 = other.getSignature();
return s1.equals(getSignature());
}
@Override
public String toString() {
return getIdentifier();
}
@Override
public boolean isVoid() {
return getReturnType().isVoid();
}
@Override
public int compareTo(final IMethodName o) {
return getIdentifier().compareTo(o.getIdentifier());
}
@Override
public boolean hasParameters() {
return getParameterTypes().length > 0;
}
}