/*
* Copyright 2004-2010 Brian S O'Neill
*
* Licensed 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.cojen.classfile;
import java.io.Serializable;
import java.io.Externalizable;
import java.io.ObjectOutput;
import java.io.ObjectInput;
import java.io.IOException;
import java.io.ObjectStreamException;
import java.lang.reflect.Method;
import java.util.List;
import java.util.ArrayList;
import org.cojen.util.WeakCanonicalSet;
/**
* This class is used to build method descriptor strings as
* defined in <i>The Java Virtual Machine Specification</i>, section 4.3.3.
* MethodDesc instances are canonicalized and therefore "==" comparable.
*
* @author Brian S O'Neill
*/
public class MethodDesc extends Descriptor implements Serializable {
private static final TypeDesc[] EMPTY_PARAMS = new TypeDesc[0];
// MethodDesc and TypeDesc can share the same instance cache.
private final static WeakCanonicalSet<Descriptor> cInstances = TypeDesc.cInstances;
static MethodDesc intern(MethodDesc desc) {
return cInstances.put(desc);
}
/**
* Acquire a MethodDesc from a set of arguments.
* @param ret return type of method; null implies void
* @param params parameters to method; null implies none
*/
public static MethodDesc forArguments(TypeDesc ret, TypeDesc[] params) {
if (ret == null) {
ret = TypeDesc.VOID;
}
if (params == null || params.length == 0) {
params = EMPTY_PARAMS;
}
return intern(new MethodDesc(ret, params));
}
/**
* Acquire a MethodDesc from a type descriptor. This syntax is described in
* section 4.3.3, Method Descriptors.
*/
public static MethodDesc forDescriptor(String desc)
throws IllegalArgumentException
{
try {
int cursor = 0;
char c;
if ((c = desc.charAt(cursor++)) != '(') {
throw invalidDescriptor(desc);
}
StringBuffer buf = new StringBuffer();
List<TypeDesc> list = new ArrayList<TypeDesc>();
while ((c = desc.charAt(cursor++)) != ')') {
switch (c) {
case 'V':
case 'I':
case 'C':
case 'Z':
case 'D':
case 'F':
case 'J':
case 'B':
case 'S':
buf.append(c);
break;
case '[':
buf.append(c);
continue;
case 'L':
while (true) {
buf.append(c);
if (c == ';') {
break;
}
c = desc.charAt(cursor++);
}
break;
default:
throw invalidDescriptor(desc);
}
list.add(TypeDesc.forDescriptor(buf.toString()));
buf.setLength(0);
}
TypeDesc ret = TypeDesc.forDescriptor(desc.substring(cursor));
TypeDesc[] tds = list.toArray(new TypeDesc[list.size()]);
return intern(new MethodDesc(desc, ret, tds));
} catch (NullPointerException e) {
throw invalidDescriptor(desc);
} catch (IndexOutOfBoundsException e) {
throw invalidDescriptor(desc);
}
}
public static MethodDesc forMethod(Method method) {
Class[] paramClasses = method.getParameterTypes();
TypeDesc[] paramTypes;
if (paramClasses == null || paramClasses.length == 0) {
paramTypes = EMPTY_PARAMS;
} else {
paramTypes = new TypeDesc[paramClasses.length];
for (int i=paramClasses.length; --i>=0; ) {
paramTypes[i] = TypeDesc.forClass(paramClasses[i]);
}
}
return forArguments(TypeDesc.forClass(method.getReturnType()), paramTypes);
}
private static IllegalArgumentException invalidDescriptor(String desc) {
return new IllegalArgumentException("Invalid descriptor: " + desc);
}
private transient final String mDescriptor;
private transient final TypeDesc mRetType;
private transient final TypeDesc[] mParams;
private MethodDesc(TypeDesc ret, TypeDesc[] params) {
mDescriptor = generateDescriptor(ret, params);
mRetType = ret;
mParams = params;
}
private MethodDesc(String desc, TypeDesc ret, TypeDesc[] params) {
mDescriptor = desc;
mRetType = ret;
mParams = params;
}
/**
* Returns a method descriptor string, excluding generics.
*/
public String getDescriptor() {
return mDescriptor;
}
/**
* Returns a method descriptor string, including any generics.
*/
//public abstract String getGenericDescriptor();
/**
* Returns the described return type, which is TypeDesc.VOID if void.
*/
public TypeDesc getReturnType() {
return mRetType;
}
public int getParameterCount() {
return mParams.length;
}
public TypeDesc[] getParameterTypes() {
TypeDesc[] params = mParams;
return (params != EMPTY_PARAMS) ? (TypeDesc[])params.clone() : params;
}
/**
* Returns this in Java method signature syntax.
*
* @param name method name
*/
public String toMethodSignature(String name) {
return toMethodSignature(name, false);
}
/**
* Returns this in Java method signature syntax.
*
* @param name method name
* @param varargs request that the last argument, if it is an array, to
* be formatted in varargs syntax.
*/
public String toMethodSignature(String name, boolean varargs) {
StringBuffer buf = new StringBuffer();
buf.append(mRetType.getFullName());
buf.append(' ');
buf.append(name);
buf.append('(');
TypeDesc[] params = mParams;
for (int i=0; i<params.length; i++) {
if (i > 0) {
buf.append(", ");
}
TypeDesc param = params[i];
if (varargs && param.isArray() && i == (params.length - 1)) {
buf.append(param.getComponentType().getFullName());
buf.append("...");
} else {
buf.append(param.getFullName());
}
}
return buf.append(')').toString();
}
public String toString() {
// TODO: Return generic descriptor
return mDescriptor;
}
public int hashCode() {
return mDescriptor.hashCode();
}
public boolean equals(Object other) {
if (this == other) {
return true;
}
if (other instanceof MethodDesc) {
return ((MethodDesc)other).mDescriptor.equals(mDescriptor);
}
return false;
}
Object writeReplace() throws ObjectStreamException {
return new External(mDescriptor);
}
private static String generateDescriptor(TypeDesc ret, TypeDesc[] params) {
int length = ret.getDescriptor().length() + 2;
int paramsLength = params.length;
for (int i=paramsLength; --i >=0; ) {
length += params[i].getDescriptor().length();
}
char[] buf = new char[length];
buf[0] = '(';
int index = 1;
String paramDesc;
for (int i=0; i<paramsLength; i++) {
paramDesc = params[i].getDescriptor();
int paramDescLength = paramDesc.length();
paramDesc.getChars(0, paramDescLength, buf, index);
index += paramDescLength;
}
buf[index++] = ')';
paramDesc = ret.getDescriptor();
paramDesc.getChars(0, paramDesc.length(), buf, index);
return new String(buf);
}
private static class External implements Externalizable {
private String mDescriptor;
public External() {
}
public External(String desc) {
mDescriptor = desc;
}
public void writeExternal(ObjectOutput out) throws IOException {
out.writeUTF(mDescriptor);
}
public void readExternal(ObjectInput in) throws IOException {
mDescriptor = in.readUTF();
}
public Object readResolve() throws ObjectStreamException {
return forDescriptor(mDescriptor);
}
}
}