blob: 9f4cb10a222ce79f0e76d7730b5c502b9319d420 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2017 IBM Corporation and others.
* 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:
* IBM Corporation - initial API and implementation
* Holger Schill - Bug 455199 - [debug] Debugging doesn't work properly when inner classes are used
*******************************************************************************/
package org.eclipse.jdt.internal.debug.eval.ast.engine;
import java.util.Iterator;
import java.util.List;
import org.eclipse.jdt.core.Signature;
import org.eclipse.jdt.debug.core.IJavaReferenceType;
import org.eclipse.jdt.internal.debug.core.model.JDIReferenceType;
import com.sun.jdi.ClassNotLoadedException;
import com.sun.jdi.ClassNotPreparedException;
import com.sun.jdi.ClassType;
import com.sun.jdi.Field;
import com.sun.jdi.InterfaceType;
import com.sun.jdi.Method;
import com.sun.jdi.ReferenceType;
import com.sun.jdi.Type;
public class BinaryBasedSourceGenerator {
private static final String RUN_METHOD_NAME = "___run"; //$NON-NLS-1$
private static final String EVAL_METHOD_NAME = "___eval"; //$NON-NLS-1$
private static final String ANONYMOUS_CLASS_NAME = "___EvalClass"; //$NON-NLS-1$
private String[] fLocalVariableTypeNames;
private String[] fLocalVariableNames;
private boolean fIsInStaticMethod;
private StringBuffer fSource;
private int fRunMethodStartOffset;
private int fRunMethodLength;
private int fCodeSnippetPosition;
private String fCompilationUnitName;
/**
* Level of source code to generate (major, minor). For example 1 and 4
* indicates 1.4.
*/
private int fSourceMajorLevel;
private int fSourceMinorLevel;
public BinaryBasedSourceGenerator(String[] localTypesNames,
String[] localVariables, boolean isInStaticMethod,
String sourceLevel) {
fLocalVariableTypeNames = localTypesNames;
fLocalVariableNames = localVariables;
fIsInStaticMethod = isInStaticMethod;
int index = sourceLevel.indexOf('.');
String num;
if (index != -1) {
num = sourceLevel.substring(0, index);
} else {
num = sourceLevel;
}
fSourceMajorLevel = Integer.valueOf(num).intValue();
if (index != -1) {
num = sourceLevel.substring(index + 1);
fSourceMinorLevel = Integer.valueOf(num).intValue();
} else {
fSourceMinorLevel = 0;
}
}
/**
* Build source for an object value (instance context)
*/
public void buildSource(JDIReferenceType referenceType) {
ReferenceType reference = (ReferenceType) referenceType
.getUnderlyingType();
fSource = buildTypeDeclaration(reference, buildRunMethod(reference),
null);
}
/**
* Build source for a class type (static context)
*/
public void buildSourceStatic(IJavaReferenceType type) {
Type underlyingType = ((JDIReferenceType) type).getUnderlyingType();
if (!(underlyingType instanceof ReferenceType)) {
return;
}
ReferenceType refType = (ReferenceType) underlyingType;
fSource = buildTypeDeclaration(refType, buildRunMethod(refType), null,
false);
String packageName = getPackageName(refType.name());
if (packageName != null) {
fSource.insert(0, "package " + packageName + ";\n"); //$NON-NLS-1$ //$NON-NLS-2$
fCodeSnippetPosition += 10 + packageName.length();
}
fCompilationUnitName = getSimpleName(refType.name());
}
protected String getUniqueMethodName(String methodName, ReferenceType type) {
List<Method> methods = type.methodsByName(methodName);
while (!methods.isEmpty()) {
methodName += '_';
methods = type.methodsByName(methodName);
}
return methodName;
}
private StringBuffer buildRunMethod(ReferenceType type) {
StringBuffer source = new StringBuffer();
if (isInStaticMethod()) {
source.append("static "); //$NON-NLS-1$
}
source.append("void "); //$NON-NLS-1$
source.append(getUniqueMethodName(RUN_METHOD_NAME, type));
source.append('(');
for (int i = 0, length = fLocalVariableNames.length; i < length; i++) {
source.append(getDotName(fLocalVariableTypeNames[i]));
source.append(' ');
source.append(fLocalVariableNames[i]);
if (i + 1 < length)
{
source.append(", "); //$NON-NLS-1$
}
}
source.append(") throws Throwable {"); //$NON-NLS-1$
source.append('\n');
fCodeSnippetPosition = source.length();
fRunMethodStartOffset = fCodeSnippetPosition;
source.append('\n');
source.append('}').append('\n');
fRunMethodLength = source.length();
return source;
}
private StringBuffer buildTypeDeclaration(ReferenceType referenceType,
StringBuffer buffer, String nestedTypeName) {
Field thisField = null;
List<Field> fields = referenceType.visibleFields();
for (Iterator<Field> iterator = fields.iterator(); iterator.hasNext();) {
Field field = iterator.next();
if (field.name().startsWith("this$")) { //$NON-NLS-1$
thisField = field;
break;
}
}
StringBuffer source = buildTypeDeclaration(referenceType, buffer,
nestedTypeName, thisField != null);
if (thisField == null) {
String packageName = getPackageName(referenceType.name());
if (packageName != null) {
source.insert(0, "package " + packageName + ";\n"); //$NON-NLS-1$ //$NON-NLS-2$
fCodeSnippetPosition += 10 + packageName.length();
}
if (isAnonymousTypeName(referenceType.name())) {
fCompilationUnitName = ANONYMOUS_CLASS_NAME;
} else {
fCompilationUnitName = getSimpleName(referenceType.name());
}
} else {
try {
return buildTypeDeclaration((ReferenceType) thisField.type(),
source, referenceType.name());
} catch (ClassNotLoadedException e) {
}
}
return source;
}
private StringBuffer buildTypeDeclaration(ReferenceType referenceType,
StringBuffer buffer, String nestedTypeName,
boolean hasEnclosingInstance) {
StringBuffer source = new StringBuffer();
String typeName = referenceType.name();
boolean isAnonymousType = isAnonymousTypeName(typeName);
if (isAnonymousType) {
ClassType classType = (ClassType) referenceType;
List<InterfaceType> interfaceList = classType.interfaces();
String superClassName = classType.superclass().name();
if (hasEnclosingInstance) {
source.append("void "); //$NON-NLS-1$
source.append(getUniqueMethodName(EVAL_METHOD_NAME,
referenceType));
source.append("() {\nnew "); //$NON-NLS-1$
if (interfaceList.size() != 0) {
source.append(getDotName(interfaceList
.get(0).name()));
} else {
source.append(getDotName(superClassName));
}
source.append("()"); //$NON-NLS-1$
} else {
source.append("public class ").append(ANONYMOUS_CLASS_NAME).append(" "); //$NON-NLS-1$ //$NON-NLS-2$
if (interfaceList.size() != 0) {
source.append(" implements ").append(getDotName(interfaceList.get(0).name())); //$NON-NLS-1$
} else {
source.append(" extends ").append(getDotName(superClassName)); //$NON-NLS-1$
}
}
} else {
if (referenceType.isFinal()) {
source.append("final "); //$NON-NLS-1$
}
if (referenceType.isStatic()) {
source.append("static "); //$NON-NLS-1$
}
if (referenceType instanceof ClassType) {
ClassType classType = (ClassType) referenceType;
if (classType.isAbstract()) {
source.append("abstract "); //$NON-NLS-1$
}
source.append("class "); //$NON-NLS-1$
source.append(getSimpleName(typeName)).append(' ');
String genericSignature = referenceType.genericSignature();
if (genericSignature != null
&& isSourceLevelGreaterOrEqual(1, 5)) {
String[] typeParameters = Signature
.getTypeParameters(genericSignature);
if (typeParameters.length > 0) {
source.append('<');
source.append(Signature
.getTypeVariable(typeParameters[0]));
String[] typeParameterBounds = Signature
.getTypeParameterBounds(typeParameters[0]);
source.append(" extends ").append(Signature.toString(typeParameterBounds[0]).replace('/', '.')); //$NON-NLS-1$
for (int i = 1; i < typeParameterBounds.length; i++) {
source.append(" & ").append(Signature.toString(typeParameterBounds[i]).replace('/', '.')); //$NON-NLS-1$
}
for (int j = 1; j < typeParameters.length; j++) {
source.append(',')
.append(Signature
.getTypeVariable(typeParameters[j]));
typeParameterBounds = Signature
.getTypeParameterBounds(typeParameters[j]);
source.append(" extends ").append(Signature.toString(typeParameterBounds[0]).replace('/', '.')); //$NON-NLS-1$
for (int i = 1; i < typeParameterBounds.length; i++) {
source.append(" & ").append(Signature.toString(typeParameterBounds[i]).replace('/', '.')); //$NON-NLS-1$
}
}
source.append("> "); //$NON-NLS-1$
}
String[] superClassInterfaces = SignatureExt
.getTypeSuperClassInterfaces(genericSignature);
int length = superClassInterfaces.length;
if (length > 0) {
source.append("extends ").append(Signature.toString(superClassInterfaces[0]).replace('/', '.')); //$NON-NLS-1$
if (length > 1) {
source.append(" implements ").append(Signature.toString(superClassInterfaces[1]).replace('/', '.')); //$NON-NLS-1$
for (int i = 2; i < length; i++) {
source.append(',')
.append(Signature
.toString(superClassInterfaces[1]));
}
}
}
} else {
ClassType superClass = classType.superclass();
if (superClass != null) {
source.append("extends ").append(getDotName(superClass.name())).append(' '); //$NON-NLS-1$
}
List<InterfaceType> interfaces;
try {
interfaces = classType.interfaces();
} catch (ClassNotPreparedException e) {
return new StringBuffer();
}
if (interfaces.size() != 0) {
source.append("implements "); //$NON-NLS-1$
Iterator<InterfaceType> iterator = interfaces.iterator();
InterfaceType interface_ = iterator
.next();
source.append(getDotName(interface_.name()));
while (iterator.hasNext()) {
source.append(',')
.append(getDotName(iterator
.next().name()));
}
}
}
} else if (referenceType instanceof InterfaceType) {
if (buffer != null) {
source.append("abstract class "); //$NON-NLS-1$
source.append(getSimpleName(typeName)).append("___ implements "); //$NON-NLS-1$
source.append(typeName.replace('$', '.')).append(" {\n"); //$NON-NLS-1$
fCodeSnippetPosition += source.length();
source.append(buffer).append("}\n"); //$NON-NLS-1$
return source;
}
source.append("interface "); //$NON-NLS-1$
source.append(getSimpleName(typeName));
}
}
source.append(" {\n"); //$NON-NLS-1$
if (buffer != null && !(referenceType instanceof InterfaceType)) {
fCodeSnippetPosition += source.length();
source.append(buffer);
}
List<Field> fields = referenceType.fields();
for (Iterator<Field> iterator = fields.iterator(); iterator.hasNext();) {
Field field = iterator.next();
if (!field.name().startsWith("this$")) { //$NON-NLS-1$
source.append(buildFieldDeclaration(field));
}
}
List<Method> methods = referenceType.methods();
for (Iterator<Method> iterator = methods.iterator(); iterator.hasNext();) {
Method method = iterator.next();
if (!method.isConstructor() && !method.isStaticInitializer()
&& !method.isBridge()) {
source.append(buildMethodDeclaration(method));
}
}
List<ReferenceType> nestedTypes = referenceType.nestedTypes();
if (nestedTypeName == null) {
for (Iterator<ReferenceType> iterator = nestedTypes.iterator(); iterator.hasNext();) {
ReferenceType nestedType = iterator.next();
if (isADirectInnerType(typeName, nestedType.name())) {
source.append(buildTypeDeclaration(nestedType, null, null,
true));
}
}
} else {
for (Iterator<ReferenceType> iterator = nestedTypes.iterator(); iterator.hasNext();) {
ReferenceType nestedType = iterator.next();
if (!nestedTypeName.equals(nestedType.name())
&& isADirectInnerType(typeName, nestedType.name())) {
source.append(buildTypeDeclaration(nestedType, null, null,
true));
}
}
}
if (isAnonymousType & hasEnclosingInstance) {
source.append("};\n"); //$NON-NLS-1$
}
source.append("}\n"); //$NON-NLS-1$
return source;
}
private StringBuffer buildFieldDeclaration(Field field) {
StringBuffer source = new StringBuffer();
if (field.isFinal()) {
source.append("final "); //$NON-NLS-1$
}
if (field.isStatic()) {
source.append("static "); //$NON-NLS-1$
}
if (field.isPublic()) {
source.append("public "); //$NON-NLS-1$
} else if (field.isPrivate()) {
source.append("private "); //$NON-NLS-1$
} else if (field.isProtected()) {
source.append("protected "); //$NON-NLS-1$
}
source.append(getDotName(field.typeName())).append(' ')
.append(field.name()).append(';').append('\n');
return source;
}
private StringBuffer buildMethodDeclaration(Method method) {
StringBuffer source = new StringBuffer();
if (method.isFinal()) {
source.append("final "); //$NON-NLS-1$
}
if (method.isStatic()) {
source.append("static "); //$NON-NLS-1$
}
if (method.isNative()) {
source.append("native "); //$NON-NLS-1$
} else if (method.isAbstract()) {
source.append("abstract "); //$NON-NLS-1$
}
if (method.isPublic()) {
source.append("public "); //$NON-NLS-1$
} else if (method.isPrivate()) {
source.append("private "); //$NON-NLS-1$
} else if (method.isProtected()) {
source.append("protected "); //$NON-NLS-1$
}
String genericSignature = method.genericSignature();
if (genericSignature != null && isSourceLevelGreaterOrEqual(1, 5)) {
String[] typeParameters = Signature
.getTypeParameters(genericSignature);
if (typeParameters.length > 0) {
source.append('<');
source.append(Signature.getTypeVariable(typeParameters[0]));
String[] typeParameterBounds = Signature
.getTypeParameterBounds(typeParameters[0]);
source.append(" extends ").append(Signature.toString(typeParameterBounds[0]).replace('/', '.')); //$NON-NLS-1$
for (int i = 1; i < typeParameterBounds.length; i++) {
source.append(" & ").append(Signature.toString(typeParameterBounds[i]).replace('/', '.')); //$NON-NLS-1$
}
for (int j = 1; j < typeParameters.length; j++) {
source.append(',').append(
Signature.getTypeVariable(typeParameters[j]));
typeParameterBounds = Signature
.getTypeParameterBounds(typeParameters[j]);
source.append(" extends ").append(Signature.toString(typeParameterBounds[0]).replace('/', '.')); //$NON-NLS-1$
for (int i = 1; i < typeParameterBounds.length; i++) {
source.append(" & ").append(Signature.toString(typeParameterBounds[i]).replace('/', '.')); //$NON-NLS-1$
}
}
source.append("> "); //$NON-NLS-1$
}
source.append(
Signature.toString(
Signature.getReturnType(genericSignature)).replace(
'/', '.')).append(' ').append(method.name())
.append('(');
String[] parameterTypes = Signature
.getParameterTypes(genericSignature);
int i = 0;
if (parameterTypes.length != 0) {
source.append(
Signature.toString(parameterTypes[0]).replace('/', '.'))
.append(" arg").append(i++); //$NON-NLS-1$
if (method.isVarArgs()) {
for (int j = 1; j < parameterTypes.length - 1; j++) {
source.append(',')
.append(Signature.toString(parameterTypes[j])
.replace('/', '.'))
.append(" arg").append(i++); //$NON-NLS-1$
}
String typeName = Signature.toString(
parameterTypes[parameterTypes.length - 1]).replace(
'/', '.');
source.append(',')
.append(typeName.substring(0, typeName.length() - 2))
.append("...").append(" arg").append(i++); //$NON-NLS-1$ //$NON-NLS-2$
} else {
for (int j = 1; j < parameterTypes.length; j++) {
source.append(',')
.append(Signature.toString(parameterTypes[j])
.replace('/', '.'))
.append(" arg").append(i++); //$NON-NLS-1$
}
}
}
source.append(')');
} else {
source.append(getDotName(method.returnTypeName())).append(' ')
.append(method.name()).append('(');
List<String> arguments = method.argumentTypeNames();
int i = 0;
if (arguments.size() != 0) {
Iterator<String> iterator = arguments.iterator();
source.append(getDotName(iterator.next()))
.append(" arg").append(i++); //$NON-NLS-1$
if (method.isVarArgs()) {
while (iterator.hasNext()) {
source.append(',');
String argName = getDotName(iterator.next());
if (!iterator.hasNext()) {
source.append(
argName.substring(0, argName.length() - 2))
.append("..."); //$NON-NLS-1$
} else {
source.append(argName);
}
source.append(" arg").append(i++); //$NON-NLS-1$
}
} else {
while (iterator.hasNext()) {
source.append(',')
.append(getDotName(iterator.next()))
.append(" arg").append(i++); //$NON-NLS-1$
}
}
}
source.append(')');
}
if (method.isAbstract() || method.isNative()) {
// No body for abstract and native methods
source.append(";\n"); //$NON-NLS-1$
} else {
source.append('{').append('\n');
source.append(getReturnStatement(method.returnTypeName()));
source.append('}').append('\n');
}
return source;
}
private String getReturnStatement(String returnTypeName) {
String typeName = getSimpleName(returnTypeName);
if (typeName.charAt(typeName.length() - 1) == ']') {
return "return null;\n"; //$NON-NLS-1$
}
switch (typeName.charAt(0)) {
case 'v':
return ""; //$NON-NLS-1$
case 'b':
if (typeName.length() >= 1 && typeName.charAt(1) == 'o') {
return "return false;\n"; //$NON-NLS-1$
}
case 's':
case 'c':
case 'i':
case 'l':
case 'd':
case 'f':
return "return 0;\n"; //$NON-NLS-1$
default:
return "return null;\n"; //$NON-NLS-1$
}
}
private String getDotName(String typeName) {
return typeName.replace('$', '.');
}
private boolean isAnonymousTypeName(String typeName) {
char char0 = getSimpleName(typeName).charAt(0);
return '0' <= char0 && char0 <= '9';
}
private String getSimpleName(String qualifiedName) {
int pos = qualifiedName.lastIndexOf('$');
if (pos == -1) {
pos = qualifiedName.lastIndexOf('.');
}
return ((pos == -1) ? qualifiedName : qualifiedName.substring(pos + 1));
}
private String getPackageName(String qualifiedName) {
int pos = qualifiedName.lastIndexOf('.');
return ((pos == -1) ? null : qualifiedName.substring(0, pos));
}
private boolean isADirectInnerType(String typeName, String nestedTypeName) {
String end = nestedTypeName.substring(typeName.length() + 1);
return end.indexOf('$') == -1;
}
private boolean isInStaticMethod() {
return fIsInStaticMethod;
}
public StringBuffer getSource() {
return fSource;
}
public int getCodeSnippetPosition() {
return fCodeSnippetPosition;
}
public String getCompilationUnitName() {
return fCompilationUnitName;
}
public int getSnippetStart() {
return fCodeSnippetPosition - 2;
}
public int getRunMethodStart() {
return fCodeSnippetPosition - fRunMethodStartOffset;
}
public int getRunMethodLength() {
return fRunMethodLength;
}
/**
* Returns whether the source to be generated is greater than or equal to
* the given source level.
*
* @param major
* major level - e.g. 1 from 1.4
* @param minor
* minor level - e.g. 4 from 1.4
* @return
*/
public boolean isSourceLevelGreaterOrEqual(int major, int minor) {
return (fSourceMajorLevel > major)
|| (fSourceMajorLevel == major && fSourceMinorLevel >= minor);
}
}