blob: b5afab16b1412935d83498abc5478217772c7a57 [file] [log] [blame]
/**********************************************************************
* This file is part of "Object Teams Development Tooling"-Software
*
* Copyright 2010 Stephan Herrmann
*
* 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
* $Id: TeamMethodGenerator.java 23417 2010-02-03 20:13:55Z stephan $
*
* Please visit http://www.eclipse.org/objectteams for updates and contact.
*
* Contributors:
* Stephan Herrmann - Initial API and implementation
**********************************************************************/
package org.eclipse.objectteams.otdt.internal.core.compiler.statemachine.transformer;
import static org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants.AccFinal;
import static org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants.AccPrivate;
import static org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants.AccProtected;
import static org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants.AccPublic;
import static org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants.AccSynchronized;
import static org.eclipse.jdt.internal.compiler.lookup.ExtraCompilerModifiers.AccVisibilityMASK;
import org.eclipse.jdt.core.compiler.CharOperation;
import org.eclipse.jdt.internal.compiler.ClassFile;
import org.eclipse.jdt.internal.compiler.CompilationResult;
import org.eclipse.jdt.internal.compiler.ast.Argument;
import org.eclipse.jdt.internal.compiler.ast.Expression;
import org.eclipse.jdt.internal.compiler.ast.FieldDeclaration;
import org.eclipse.jdt.internal.compiler.ast.MethodDeclaration;
import org.eclipse.jdt.internal.compiler.ast.QualifiedTypeReference;
import org.eclipse.jdt.internal.compiler.ast.Statement;
import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration;
import org.eclipse.jdt.internal.compiler.ast.TypeReference;
import org.eclipse.jdt.internal.compiler.classfmt.ClassFileReader;
import org.eclipse.jdt.internal.compiler.classfmt.MethodInfo;
import org.eclipse.jdt.internal.compiler.env.IBinaryMethod;
import org.eclipse.jdt.internal.compiler.flow.FlowInfo;
import org.eclipse.jdt.internal.compiler.flow.InitializationFlowContext;
import org.eclipse.jdt.internal.compiler.lookup.Binding;
import org.eclipse.jdt.internal.compiler.lookup.ClassScope;
import org.eclipse.jdt.internal.compiler.lookup.ExtraCompilerModifiers;
import org.eclipse.jdt.internal.compiler.lookup.FieldBinding;
import org.eclipse.jdt.internal.compiler.lookup.MethodBinding;
import org.eclipse.jdt.internal.compiler.lookup.ReferenceBinding;
import org.eclipse.jdt.internal.compiler.lookup.SourceTypeBinding;
import org.eclipse.jdt.internal.compiler.lookup.TypeBinding;
import org.eclipse.jdt.internal.compiler.lookup.TypeConstants;
import org.eclipse.objectteams.otdt.internal.core.compiler.bytecode.BytecodeTransformer;
import org.eclipse.objectteams.otdt.internal.core.compiler.bytecode.ConstantPoolObject;
import org.eclipse.objectteams.otdt.internal.core.compiler.bytecode.ConstantPoolObjectMapper;
import org.eclipse.objectteams.otdt.internal.core.compiler.bytecode.ConstantPoolObjectReader;
import org.eclipse.objectteams.otdt.internal.core.compiler.control.Dependencies;
import org.eclipse.objectteams.otdt.internal.core.compiler.control.ITranslationStates;
import org.eclipse.objectteams.otdt.internal.core.compiler.model.MethodModel;
import org.eclipse.objectteams.otdt.internal.core.compiler.model.RoleModel;
import org.eclipse.objectteams.otdt.internal.core.compiler.util.AstEdit;
import org.eclipse.objectteams.otdt.internal.core.compiler.util.AstGenerator;
/**
* This class adds methods and fields from org.objectteams.Team to those teams that
* extend a non-team class and thus cannot inherit these necessary members.
* Methods are copied via byte-code copy, fields are generated at AST-level (incl. initialization).
*
* Technical note: the way this generator is constructed it assumes that org.objectteams.Team
* exists in binary form, i.e., a team leveraging this generator cannot be compiled together
* with org.objectteams.Team.
*
* @since 1.4.0
*/
public class TeamMethodGenerator {
static final char[][] JAVA_LANG_WEAKHASHMAP = new char[][] {"java".toCharArray(), "util".toCharArray(), "WeakHashMap".toCharArray()}; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
static final char[][] JAVA_LANG_THREAD = new char[][] {"java".toCharArray(), "lang".toCharArray(), "Thread".toCharArray()}; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
static final char[][] JAVA_LANG_THREADLOCAL = new char[][] {"java".toCharArray(), "lang".toCharArray(), "ThreadLocal".toCharArray()}; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
/** Simple structure to represent one method of o.o.Team. */
static class MethodDescriptor {
String selector;
String signature;
Args args;
boolean hasBooleanReturn; // if false void return is added
int modifiers;
// store from binary method of o.o.Team:
int methodCodeOffset;
ReferenceBinding declaringClass;
MethodBinding binding;
MethodDescriptor(String selector, String signature, Args args, boolean hasBooleanReturn, int modifiers) {
super();
this.selector = selector;
this.signature = signature;
this.args = args;
this.hasBooleanReturn = hasBooleanReturn;
this.modifiers = modifiers;
}
Argument[] makeArgs(AstGenerator gen) {
switch (this.args) {
case THREAD:
return new Argument[] {
gen.argument("thread".toCharArray(), //$NON-NLS-1$
gen.qualifiedTypeReference(JAVA_LANG_THREAD))
};
case BOOLEAN:
return new Argument[] {
gen.argument("flag".toCharArray(), //$NON-NLS-1$
gen.baseTypeReference(TypeConstants.BOOLEAN))
};
default:
return null;
}
}
TypeReference makeReturnRef(AstGenerator gen) {
if (this.hasBooleanReturn)
return gen.baseTypeReference(TypeConstants.BOOLEAN);
return gen.baseTypeReference(TypeConstants.VOID);
}
}
enum Args { NONE, THREAD, BOOLEAN }
/** Public methods to copy from o.o.Team: */
@SuppressWarnings("nls")
final MethodDescriptor[] methodDescriptors = new MethodDescriptor[] {
new MethodDescriptor("activate", "()V", Args.NONE, false, AccPublic),
new MethodDescriptor("activate", "(Ljava/lang/Thread;)V",Args.THREAD, false, AccPublic),
new MethodDescriptor("deactivate", "()V", Args.NONE, false, AccPublic),
new MethodDescriptor("deactivate", "(Ljava/lang/Thread;)V",Args.THREAD, false, AccPublic),
new MethodDescriptor("isActive", "()Z", Args.NONE, true, AccPublic|AccFinal),
new MethodDescriptor("isActive", "(Ljava/lang/Thread;)Z",Args.THREAD, true, AccPublic|AccFinal),
new MethodDescriptor("isExecutingCallin", "()Z", Args.NONE, true, AccPublic),
new MethodDescriptor("deactivateForEndedThread", "(Ljava/lang/Thread;)V",Args.THREAD, false, AccPublic),
new MethodDescriptor("internalIsActiveSpecificallyFor", "(Ljava/lang/Thread;)Z",Args.THREAD, true, AccPublic),
new MethodDescriptor("_OT$setExecutingCallin", "(Z)Z", Args.BOOLEAN, true, AccPublic),
new MethodDescriptor("_OT$activateForAllThreads", "()V", Args.NONE, false, AccPrivate),
new MethodDescriptor("doRegistration", "()V", Args.NONE, false, AccPrivate),
new MethodDescriptor("doUnregistration", "()V", Args.NONE, false, AccPrivate),
};
// ==== Currently, this is where we globally store the byte code of org.objectteams.Team: ====
public byte[] classBytes;
public int[] constantPoolOffsets;
// --- variant if o.o.Team is a SourceTypeBinding:
public SourceTypeBinding ooTeamBinding;
// ==== ====
/** When binary methods for o.o.Team are created record the relevant method bindings. */
public void registerTeamMethod(IBinaryMethod method, MethodBinding methodBinding) {
String selector = String.valueOf(method.getSelector());
String descriptor = String.valueOf(method.getMethodDescriptor());
registerTeamMethod(methodBinding.declaringClass, methodBinding, selector, descriptor, -1/*structOffset not yet known*/);
}
/** When o.o.Team is read from .class file, record the byte code here. */
public synchronized void maybeRegisterTeamClassBytes(ClassFileReader teamClass, ReferenceBinding teamClassBinding) {
if (this.classBytes != null)
return;
this.classBytes = teamClass.getBytes();
this.constantPoolOffsets = teamClass.getConstantPoolOffsets();
for (IBinaryMethod method : teamClass.getMethods()) {
if (this.classBytes == null && method instanceof MethodInfo) {
// repair if class already nulled its byte reference:
this.classBytes = ((MethodInfo)method).reference;
this.constantPoolOffsets = ((MethodInfo)method).constantPoolOffsets;
}
String selector = String.valueOf(method.getSelector());
String descriptor = String.valueOf(method.getMethodDescriptor());
int structOffset = ((MethodInfo)method).getStructOffset();
// relevant new info is structOffset, everything has already been registered from registerTeamMethod(IBinaryMethod,MethodBinding)
registerTeamMethod(teamClassBinding, null, selector, descriptor, structOffset);
}
}
private boolean registerTeamMethod(ReferenceBinding declaringClass, MethodBinding methodBinding, String selector, String descriptor, int structOffset) {
for (int s = 0; s < this.methodDescriptors.length; s++) {
if ( selector.equals(this.methodDescriptors[s].selector)
&& descriptor.equals(this.methodDescriptors[s].signature))
{
if (methodBinding != null)
this.methodDescriptors[s].binding = methodBinding;
this.methodDescriptors[s].declaringClass = declaringClass;
this.methodDescriptors[s].methodCodeOffset = structOffset;
return true;
}
}
return false;
}
// --- alternative initialization when compiling o.o.Team from source: ---
public synchronized void registerOOTeamClass(SourceTypeBinding ooTeamBinding) {
if (this.classBytes != null)
return;
this.ooTeamBinding = ooTeamBinding;
}
public boolean requestBytes() {
if (this.classBytes != null)
return true;
if (this.ooTeamBinding != null) {
if (isOOTConverted())
return false;
boolean result = Dependencies.ensureBindingState(this.ooTeamBinding, ITranslationStates.STATE_BYTE_CODE_GENERATED);
// the above causes callbacks to registerSourceMethodBytes(), info will be stored in corresponding MethodModels
// finish collecting info:
MethodModel model = this.methodDescriptors[0].binding.model;
this.classBytes = model.getBytes();
this.constantPoolOffsets = model.getConstantPoolOffsets();
return result;
}
return false; // shouldn't
}
boolean isOOTConverted() {
if (this.ooTeamBinding == null)
return false;
ClassScope scope = this.ooTeamBinding.scope;
if (scope == null)
return false;
TypeDeclaration typeDecl = scope.referenceContext;
return typeDecl.isConverted;
}
public boolean registerSourceMethodBytes(MethodBinding method) {
String selector = String.valueOf(method.selector);
String signature = String.valueOf(method.signature());
return registerTeamMethod(method.declaringClass, method, selector, signature, -1/*no structOffset before class is complete*/);
}
// ==============
/**
* Add the AST representing all relevant methods and fields from o.o.Team,
* and prepare methods for byte-code copy.
*/
public void addMethodsAndFields(TypeDeclaration teamDecl) {
// FIXME(SH): test subteams of teams already treated by this generator!
AstGenerator gen = new AstGenerator(teamDecl);
boolean hasBoundRole = false;
for (RoleModel role : teamDecl.getTeamModel().getRoles(false)) {
if (role.isBound()) {
hasBoundRole = true;
break;
}
}
// methods:
for (MethodDescriptor methodDescriptor : this.methodDescriptors) {
MethodDeclaration newMethod = null;
if ((methodDescriptor.modifiers & AccVisibilityMASK) == AccPublic) {
// public methods are always copied
newMethod = new CopiedTeamMethod(teamDecl.compilationResult, methodDescriptor, gen);
} else {
// privates are only copied if bound roles exist, else created as empty
if (hasBoundRole) {
newMethod = new CopiedTeamMethod(teamDecl.compilationResult, methodDescriptor, gen);
} else {
newMethod = gen.method(teamDecl.compilationResult,
methodDescriptor.modifiers,
methodDescriptor.hasBooleanReturn
? gen.baseTypeReference(TypeConstants.BOOLEAN)
: gen.baseTypeReference(TypeConstants.VOID),
methodDescriptor.selector.toCharArray(),
null,
new Statement[0]); // regular empty method.
}
}
AstEdit.addGeneratedMethod(teamDecl, newMethod);
}
// fields:
addFields(teamDecl, gen);
}
/** create field ASTs. */
@SuppressWarnings("nls")
void addFields(TypeDeclaration teamDecl, AstGenerator gen) {
// private WeakHashMap<Thread, Boolean> _OT$activatedThreads = new WeakHashMap<Thread, Boolean>();
addPrivateField(teamDecl, gen,
weakHashMapTypeReference(gen),
"_OT$activatedThreads".toCharArray(),
gen.allocation(weakHashMapTypeReference(gen), null));
// private Object _OT$registrationLock= new Object();
addPrivateField(teamDecl, gen,
gen.qualifiedTypeReference(TypeConstants.JAVA_LANG_OBJECT),
"_OT$registrationLock".toCharArray(),
gen.allocation(gen.qualifiedTypeReference(TypeConstants.JAVA_LANG_OBJECT), null));
// private boolean _OT$lazyGlobalActiveFlag = false;
addPrivateField(teamDecl, gen,
gen.baseTypeReference(TypeConstants.BOOLEAN),
"_OT$lazyGlobalActiveFlag".toCharArray(),
gen.booleanLiteral(false));
// private boolean _OT$isExecutingCallin = false;
addPrivateField(teamDecl, gen,
gen.baseTypeReference(TypeConstants.BOOLEAN),
"_OT$isExecutingCallin".toCharArray(),
gen.booleanLiteral(false));
// private int _OT$registrationState = _OT$UNREGISTERED;
addPrivateField(teamDecl, gen,
gen.baseTypeReference(TypeConstants.INT),
"_OT$registrationState".toCharArray(),
gen.intLiteral(0));
// private boolean _OT$globalActive = false;
addPrivateField(teamDecl, gen,
gen.baseTypeReference(TypeConstants.BOOLEAN),
"_OT$globalActive".toCharArray(),
gen.booleanLiteral(false));
// private ThreadLocal<Integer> _OT$implicitActivationsPerThread = new ThreadLocal<Integer>() {
// protected synchronized Integer initialValue() {
// return Integer.valueOf(0);
// }
// };
TypeDeclaration anonThreadLocal = gen.anonymousType(teamDecl.compilationResult);
anonThreadLocal.methods = new MethodDeclaration[] {
gen.method( teamDecl.compilationResult,
AccProtected|AccSynchronized,
gen.qualifiedTypeReference(TypeConstants.JAVA_LANG_INTEGER),
"initialValue".toCharArray(),
null/*arguments*/,
new Statement[] {
gen.returnStatement(gen.intLiteral(0)) // rely on autoboxing
})
};
addPrivateField(teamDecl, gen,
threadLocalReference(gen),
"_OT$implicitActivationsPerThread".toCharArray(),
gen.anonymousAllocation(threadLocalReference(gen), null/*arguments*/, anonThreadLocal));
}
void addPrivateField(TypeDeclaration teamDecl, AstGenerator gen, TypeReference type, char[] name, Expression init) {
FieldDeclaration field = gen.field(AccPrivate, type, name, init);
AstEdit.addField(teamDecl, field, true, false);
field.binding.modifiers |= ExtraCompilerModifiers.AccLocallyUsed;
}
QualifiedTypeReference weakHashMapTypeReference(AstGenerator gen) {
return gen.parameterizedQualifiedTypeReference(
JAVA_LANG_WEAKHASHMAP,
new TypeReference[]{
gen.qualifiedTypeReference(JAVA_LANG_THREAD),
gen.qualifiedTypeReference(TypeConstants.JAVA_LANG_BOOLEAN)
});
}
QualifiedTypeReference threadLocalReference(AstGenerator gen) {
return gen.parameterizedQualifiedTypeReference(
JAVA_LANG_THREADLOCAL,
new TypeReference[] {
gen.qualifiedTypeReference(TypeConstants.JAVA_LANG_INTEGER)
});
}
/**
* Add fake method bindings for methods that will be generated by the OTRE.
* These bindings are needed by the TeamConstantPoolMapper.
*/
public static void addFakedTeamRegistrationMethods(ReferenceBinding teamBinding) {
MethodBinding registrationMethod;
registrationMethod = new MethodBinding(AccPublic,
"_OT$registerAtBases".toCharArray(), //$NON-NLS-1$
TypeBinding.VOID, // return type
Binding.NO_PARAMETERS,
Binding.NO_EXCEPTIONS, // exceptions
teamBinding);
teamBinding.addMethod(registrationMethod);
MethodModel.getModel(registrationMethod)._fakeKind = MethodModel.FakeKind.TEAM_REGISTRATION_METHOD;
registrationMethod = new MethodBinding(AccPublic,
"_OT$unregisterFromBases".toCharArray(), //$NON-NLS-1$
TypeBinding.VOID, // return type
Binding.NO_PARAMETERS,
Binding.NO_EXCEPTIONS, // exceptions
teamBinding);
teamBinding.addMethod(registrationMethod);
MethodModel.getModel(registrationMethod)._fakeKind = MethodModel.FakeKind.TEAM_REGISTRATION_METHOD;
}
/** Special crippled method declarations that only serve as place-holder for copy-inheritance. */
class CopiedTeamMethod extends MethodDeclaration {
MethodDescriptor descriptor;
public CopiedTeamMethod(CompilationResult compilationResult, MethodDescriptor descriptor, AstGenerator gen) {
super(compilationResult);
this.selector = descriptor.selector.toCharArray();
this.descriptor = descriptor;
this.isCopied = true;
this.modifiers = descriptor.modifiers;
this.arguments = descriptor.makeArgs(gen);
this.returnType = descriptor.makeReturnRef(gen);
}
@Override
public void resolve(ClassScope upperScope) {
// nop
}
@Override
public void analyseCode(ClassScope classScope, InitializationFlowContext initializationContext, FlowInfo flowInfo) {
// nop
}
@Override
public void generateCode(ClassScope classScope, ClassFile classFile) {
if (isOOTConverted())
return; // don't actually try to generate, o.o.Team has no byte codes in this scenario
this.binding.copyInheritanceSrc = this.descriptor.binding;
ConstantPoolObjectMapper mapper = new TeamConstantPoolMapper(this.descriptor.binding, this.binding);
byte[] bytes;
int[] offsets;
int structOffset;
if (this.descriptor.methodCodeOffset == -1) {
MethodModel srcModel = this.descriptor.binding.model;
bytes = srcModel.getBytes();
offsets = srcModel.getConstantPoolOffsets();
structOffset = srcModel.getStructOffset();
} else {
bytes = TeamMethodGenerator.this.classBytes;
offsets = TeamMethodGenerator.this.constantPoolOffsets;
structOffset = this.descriptor.methodCodeOffset;
}
ConstantPoolObjectReader reader = new ConstantPoolObjectReader( bytes,
offsets,
this.descriptor.declaringClass.getTeamModel(),
this.scope.environment());
new BytecodeTransformer().doCopyMethodCode( null /*srcRoleModel*/, this.binding, // source
(SourceTypeBinding)this.binding.declaringClass, this, // destination
bytes, // source bytes
offsets,
structOffset,
reader, mapper, // mapping strategies
classFile); // final destination
}
}
/** Simple constant mapper: only translate from o.o.Team to current team type. */
static class TeamConstantPoolMapper extends ConstantPoolObjectMapper {
ReferenceBinding srcType, dstType;
public TeamConstantPoolMapper(MethodBinding srcMethodBinding, MethodBinding dstMethodBinding) {
super(srcMethodBinding, dstMethodBinding);
this.srcType = srcMethodBinding.declaringClass;
this.dstType = dstMethodBinding.declaringClass;
}
@Override
public ConstantPoolObject mapConstantPoolObject(ConstantPoolObject src_cpo, boolean addMarkerArgAllowed) {
return mapConstantPoolObject(src_cpo);
}
@Override
public ConstantPoolObject mapConstantPoolObject(ConstantPoolObject src_cpo) {
int type=src_cpo.getType();
TypeBinding clazz = null;
switch (type) {
case MethodRefTag:
clazz = src_cpo.getMethodRef().declaringClass;
break;
case FieldRefTag:
clazz = src_cpo.getFieldRef().declaringClass;
break;
case ClassTag:
clazz = src_cpo.getClassObject();
}
if (clazz != this.srcType)
return src_cpo; // only map references to o.o.Team
// perform the mapping:
switch(type){
case FieldRefTag:
return new ConstantPoolObject(
FieldRefTag,
mapField(src_cpo.getFieldRef()));
case MethodRefTag:
return new ConstantPoolObject(
MethodRefTag,
mapMethod(src_cpo.getMethodRef()));
case ClassTag:
return new ConstantPoolObject(
ClassTag,
this.dstType); // don't search further, we already have it.
}
//if no mapping is needed, return original ConstantPoolObject
return src_cpo;
}
private MethodBinding mapMethod(MethodBinding methodRef) {
for (MethodBinding dstBinding : this.dstType.methods())
if ( CharOperation.equals(dstBinding.selector, methodRef.selector)
&& CharOperation.equals(dstBinding.signature(), methodRef.signature()))
return dstBinding;
return methodRef;
}
private FieldBinding mapField(FieldBinding fieldRef) {
for (FieldBinding dstBinding : this.dstType.fields())
if ( CharOperation.equals(dstBinding.name, fieldRef.name)
&& dstBinding.type == fieldRef.type)
return dstBinding;
return fieldRef;
}
}
}