blob: 5a53be2e75e6219c99cd3c673da834b872763aee [file] [log] [blame]
/**********************************************************************
* This file is part of "Object Teams Dynamic Runtime Environment"
*
* Copyright 2009, 2015 Oliver Frank 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
*
* Please visit http://www.eclipse.org/objectteams for updates and contact.
*
* Contributors:
* Oliver Frank - Initial API and implementation
* Stephan Herrmann - Initial API and implementation
**********************************************************************/
package org.eclipse.objectteams.otredyn.runtime;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.objectteams.ITeam;
import org.objectteams.ITeamManager;
import org.objectteams.Team;
/**
* This class administrates the the active teams and their
* callin ids for all joinpoints.
* Teams have to register/unregister themselves at this class,
* while their activation/deactivation.
* The generated client code can call methods of this class to
* get the active teams and callin ids.
* @author Oliver Frank
*
*/
public class TeamManager implements ITeamManager {
// void handleTeamStateChange(ITeam t, TeamStateChange stateChange) ;
private static List<List<ITeam>> _teams = new ArrayList<List<ITeam>>();
private static List<List<Integer>> _callinIds = new ArrayList<List<Integer>>();
private static Map<String, Integer> joinpointMap = new HashMap<String, Integer>();
// key: Team class, value: list of global memberIds, indexed by local accessId, id of null means "not mapped in this team (try super)"
private static Map<Class<?>, List<Integer>> accessIdMap = new HashMap<Class<?>, List<Integer>>();
private static int currentJoinpointId = 0;
// map all original joinpoints to their inherited versions in subclasses
private static Map<Integer,List<Integer>> joinpointToSubJoinpoints = new HashMap<Integer, List<Integer>>();
private static IClassRepository classRepository;
// synchronization: fields _teams and _callinIds are protected using the TeamManager class object as the monitor
public static void setup(IClassRepository repo) {
classRepository = repo;
Team.registerTeamManager(new TeamManager()); // install callback
}
/**
* Returns all active teams and corresponding callin IDs for joinpoint.
* This method is intended to be called by generated client code.
* @param joinpointId
* @return a two-element array or null if there are no active teams.
* If non-null the first array element is an array (ITeam[]) of all active teams for the joinpoint,
* and the second array element is an array (int[]) of corresponding callind IDs.
* Both arrays have the same length and elements with equal index correspond between both sub-arrays.
*/
public synchronized static Object[] getTeamsAndCallinIds(int joinpointId) {
List<ITeam> teams = _teams.get(joinpointId);
int size = teams.size();
if (size == 0)
return null;
List<Integer> callinIds = _callinIds.get(joinpointId);
ITeam[] active = new ITeam[size];
int[] ids = new int[size];
int count = 0;
Thread th = Thread.currentThread();
for (int i = 0; i < active.length; i++) {
ITeam t = teams.get(i);
if (t.isActive(th)) {
active[count] = t;
ids[count++] = callinIds.get(i);
}
}
if (count == 0)
return null;
if (count != size) {
System.arraycopy(active, 0, active = new ITeam[count], 0, count);
System.arraycopy(ids, 0, ids = new int[count], 0, count);
}
return new Object[] { active, ids };
}
/**
* Returns the member id for a given team and a access id
* used in this team.
* This method is intended to be called by
* generated client code.
* @param accessId
* @param Team
* @return
*/
public static int getMemberId(int accessId, Class<? extends ITeam> teamClass) {
List<Integer> teamMap = accessIdMap.get(teamClass);
Integer id = -1;
if (teamMap == null || accessId >= teamMap.size() || (id = teamMap.get(accessId)) == null) {
Class<?> superClass = teamClass.getSuperclass();
if (ITeam.class.isAssignableFrom(superClass)) {
@SuppressWarnings("unchecked") Class<? extends ITeam> superTeam = (Class<? extends ITeam>) superClass;
return getMemberId(accessId, superTeam);
}
if (id == null)
return -1;
}
return id;
}
/**
* Returns and possibly creates a globally unique id for a
* joinpoint identifier. Additionally it prepares the
* structures needed to store the teams and callin ids for a joinpoint.
* @param joinpointIdentifier
* @return a joinpoint id
*/
public synchronized static int getJoinpointId(String joinpointIdentifier) {
Integer joinpointId = getExistingJoinpointId(joinpointIdentifier);
if (joinpointId == null) {
joinpointMap.put(joinpointIdentifier, currentJoinpointId);
List<ITeam> teams = new ArrayList<ITeam>();
_teams.add(teams);
List<Integer> callinIds = new ArrayList<Integer>();
_callinIds.add(callinIds);
return currentJoinpointId++;
}
return joinpointId;
}
/**
* Returns an existing globally unique joinpoint id
* for a joinpoint identifier
* @param joinpointIdentifier
* @return the joinpoint id or null if a joinpoint id
* doesn't exist for this joinpoint identifier
*/
private static Integer getExistingJoinpointId(String joinpointIdentifier) {
return joinpointMap.get(joinpointIdentifier);
}
/**
* Handles registration and unregistration of teams.
* Stores the team and the ids dependend on the joinpoints and
* tell the {@link AbstractBoundClass} representing the base classes
* of the team, that there is new Binding
* @param t
* @param stateChange
*/
public void handleTeamStateChange(ITeam t, ITeamManager.TeamStateChange stateChange) {
IClassIdentifierProvider provider = ClassIdentifierProviderFactory.getClassIdentifierProvider();
Class<? extends ITeam> teamClass = t.getClass();
String teamId = provider.getClassIdentifier(teamClass);
IBoundTeam teem = classRepository.getTeam(teamClass.getName(), teamId, teamClass.getClassLoader());
for (IBinding binding : teem.getBindings()) {
// OTDRE cannot add methods into a sub base, hence we have to use the declaring base class for static methods:
// (see https://bugs.eclipse.org/435136#c1)
String boundClassName = ((binding.getBaseFlags() & IBinding.STATIC_BASE) != 0) ?
binding.getDeclaringBaseClassName() : binding.getBoundClass();
String boundClassIdentifier = provider.getBoundClassIdentifier(teamClass, boundClassName);
// FIXME(SH): the following may need adaptation for OT/Equinox or other multi-classloader settings:
IBoundClass boundClass = classRepository.getBoundClass(boundClassName.replace('/', '.'), boundClassIdentifier, teamClass.getClassLoader());
switch (binding.getType()) {
case CALLIN_BINDING:
handleBindingForBase(t, stateChange, binding, boundClass, provider);
break;
default:
// no further action for *ACCESS bindings
}
}
}
private void handleBindingForBase(ITeam t, ITeamManager.TeamStateChange stateChange, IBinding binding, IBoundClass boundClass, IClassIdentifierProvider provider) {
IMethod method = boundClass.getMethod(binding.getMemberName(), binding.getMemberSignature(), binding.getBaseFlags(), binding.isHandleCovariantReturn());
int joinpointId = getJoinpointId(boundClass
.getMethodIdentifier(method));
synchronized (method) {
changeTeamsForJoinpoint(t, binding.getPerTeamId(), joinpointId, stateChange);
List<Integer> subJoinpoints = joinpointToSubJoinpoints.get(joinpointId);
if (subJoinpoints != null)
for (Integer subJoinpoint : subJoinpoints)
changeTeamsForJoinpoint(t, binding.getPerTeamId(), subJoinpoint, stateChange);
}
boundClass.handleAddingOfBinding(binding); // TODO: do we want/need to group all bindings into one action?
for (IBoundClass tsubBase : boundClass.getTSubsOfThis(classRepository, provider)) {
handleBindingForBase(t, stateChange, binding, tsubBase, provider);
}
}
/**
* When a team is about to be activated propagate its bindings to all base classes,
* but don't yet register any join points, we aren't activating yet.
*/
public static void prepareTeamActivation(Class<? extends ITeam> teamClass) {
String teamName = teamClass.getName();
ClassLoader teamClassLoader = teamClass.getClassLoader();
IClassIdentifierProvider provider = ClassIdentifierProviderFactory.getClassIdentifierProvider();
String teamId = provider.getClassIdentifier(teamClass);
IBoundTeam teem = classRepository.getTeam(teamName, teamId, teamClassLoader);
for (IBinding binding : teem.getBindings()) {
String boundClassName = binding.getBoundClass();
String boundClassIdentifier = provider.getBoundClassIdentifier(teamClass, boundClassName);
// FIXME(SH): the following may need adaptation for OT/Equinox or other multi-classloader settings:
IBoundClass boundClass = classRepository.getBoundClass(boundClassName.replace('/', '.'), boundClassIdentifier, teamClass.getClassLoader());
switch (binding.getType()) {
case CALLIN_BINDING:
prepareBindingForBase(binding, boundClass, provider);
break;
default: // no further action for *ACCESS bindings
}
}
}
private static void prepareBindingForBase(IBinding binding, IBoundClass boundClass, IClassIdentifierProvider idProvider) {
boundClass.handleAddingOfBinding(binding);
for (IBoundClass tsubBase : boundClass.getTSubsOfThis(classRepository, idProvider))
prepareBindingForBase(binding, tsubBase, idProvider);
}
public static void handleTeamLoaded(Class<? extends ITeam> teamClass) {
if (teamClass != null)
handleDecapsulation(teamClass);
performPendingTask();
}
private static void handleDecapsulation(Class<? extends ITeam> teamClass) {
IClassIdentifierProvider provider = ClassIdentifierProviderFactory.getClassIdentifierProvider();
String teamId = provider.getClassIdentifier(teamClass);
IBoundTeam teem = classRepository.getTeam(teamClass.getName(), teamId, teamClass.getClassLoader());
for (IBinding binding : teem.getBindings()) {
String boundClassName = binding.getBoundClass();
String boundClassIdentifier = provider.getBoundClassIdentifier(teamClass, boundClassName.replace('.', '/'));
// FIXME(SH): the following may need adaptation for OT/Equinox or other multi-classloader settings:
IBoundClass boundClass = classRepository.getBoundClass(boundClassName.replace('/', '.'), boundClassIdentifier, teamClass.getClassLoader());
switch (binding.getType()) {
case FIELD_ACCESS: // fallthrough
case METHOD_ACCESS:
IMember member = null;
if (binding.getType() == IBinding.BindingType.FIELD_ACCESS) {
member = boundClass.getField(binding.getMemberName(),
binding.getMemberSignature());
} else {
member = boundClass.getMethod(binding.getMemberName(), binding.getMemberSignature(), 0/*flags*/, false/*covariantReturn*/);
}
int memberId = member.getGlobalId(boundClass);
synchronized (member) {
addAccessIds(teamClass, teem, binding.getPerTeamId(), memberId);
}
//$FALL-THROUGH$
case ROLE_BASE_BINDING:
boundClass.handleAddingOfBinding(binding);
break;
default: // no action for CALLIN_BINDING here
}
}
}
/**
* Stores the access ids of a team and the corresponding member ids,
* where accessId is local to the team and the member id is globally unique.
* @param teamClass
* @param teem
* @param accessId
* @param memberId
* @param stateChange
*/
private static void addAccessIds(Class<? extends ITeam> teamClass, IBoundTeam teem, int accessId, int memberId) {
List<Integer> accessIds = accessIdMap.get(teamClass);
if (accessIds == null) {
int highestAccessId = teem.getHighestAccessId() + 1;
accessIds = new ArrayList<Integer>(highestAccessId);
for (int i = 0; i <= highestAccessId; i++) {
accessIds.add(null);
}
accessIdMap.put(teamClass, accessIds);
}
accessIds.set(accessId, memberId);
}
/**
* Stores or removes the team
* @param t
* @param callinId
* @param joinpointId
* @param stateChange
*/
private synchronized static void changeTeamsForJoinpoint(ITeam t, int callinId, int joinpointId, TeamManager.TeamStateChange stateChange) {
switch (stateChange) {
case REGISTER:
List<ITeam> teams = _teams.get(joinpointId);
teams.add(0, t);
List<Integer> callinIds = _callinIds.get(joinpointId);
callinIds.add(0, callinId);
break;
case UNREGISTER:
teams = _teams.get(joinpointId);
callinIds = _callinIds.get(joinpointId);
int index = teams.indexOf(t);
while (index > -1) {
teams.remove(index);
callinIds.remove(index);
index = teams.indexOf(t);
}
break;
default: throw new RuntimeException("Unknown team state change: " + stateChange.name());
}
}
/**
* Merge the teams and callin ids of two joinpoints into the second.
* This is used so that activating teams for the second joinpoint (subclass)
* will include the effect of activation of the former (superclass).
* @param superClass
* @param subClass
* @param superMethod
* @param subMethod
*/
public static void mergeJoinpoints(IBoundClass superClass,
IBoundClass subClass, final IMethod superMethod, IMethod subMethod, final boolean handleCovariantReturn) {
if (subClass.isAnonymous()) {
// destClass is a placeholder, schedule real merging for when a real subclass gets wired:
subClass.addWiringTask(new ISubclassWiringTask() {
public void wire(IBoundClass superClass, IBoundClass subClass) {
IMethod subMethod = subClass.getMethod(superMethod.getName(), superMethod.getSignature(),
0/*flags*/, handleCovariantReturn);
mergeJoinpoints(superClass, subClass, superMethod, subMethod, handleCovariantReturn);
}
});
return;
}
synchronized (subClass) {
Integer superJoinpointId = getJoinpointId(superClass.getMethodIdentifier(superMethod));
if (superJoinpointId != null) {
int subJoinpointId = getJoinpointId(subClass.getMethodIdentifier(subMethod));
List<Integer> subJoinpoints = joinpointToSubJoinpoints.get(superJoinpointId);
if (subJoinpoints == null) {
subJoinpoints = new ArrayList<Integer>();
joinpointToSubJoinpoints.put(superJoinpointId, subJoinpoints);
}
// already processed?
if (!subJoinpoints.contains(subJoinpointId)) {
subJoinpoints.add(subJoinpointId);
applyJoinpointMerge(superJoinpointId, subJoinpointId);
}
}
}
}
private synchronized static void applyJoinpointMerge(Integer srcJoinpointId, int destJoinpointId) {
List<ITeam> teams = _teams.get(destJoinpointId);
List<Integer> callinIds = _callinIds.get(destJoinpointId);
List<ITeam> srcTeams = _teams.get(srcJoinpointId);
List<Integer> srcCallins = _callinIds.get(srcJoinpointId);
for (int s=0; s<srcTeams.size(); s++) {
int d = 0; // FIXME(SH): find insertion index based on activation priority!!
teams.add(d, srcTeams.get(s));
callinIds.add(0, srcCallins.get(s));
}
// transitively pass the new information down the tree of subJoinpoints:
List<Integer> destDests = joinpointToSubJoinpoints.get(destJoinpointId);
if (destDests != null && !destDests.isEmpty())
for (Integer destDest : destDests)
applyJoinpointMerge(destJoinpointId, destDest);
}
/**
* A "mailbox" for pending tasks (per thread).
* The weaver stores redefinition tasks here that failed because the class
* was in a state between load-time transformation and definition.
*/
public static ThreadLocal<Runnable> pendingTasks = new ThreadLocal<Runnable>();
/**
* Perform any tasks that are pending for this thread: redefinitions of a class
* that was in a state between load-time transformation and definition.
*/
public static void performPendingTask() {
Runnable task = pendingTasks.get();
if (task != null) {
// System.out.println("pending "+task);
pendingTasks.set(null);
try {
task.run();
} catch (Throwable t) {
System.err.println(t+" because "+t.getCause());
pendingTasks.set(task); // no success try later ??
}
}
}
}