//
// This file is part of the prose package.
//
// The contents of this file are subject to the Mozilla Public License
// Version 1.1 (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.mozilla.org/MPL/
//
// Software distributed under the License is distributed on an "AS IS" basis,
// WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
// for the specific language governing rights and limitations under the
// License.
//
// The Original Code is prose.
//
// The Initial Developers of the Original Code are Andrei Popovici and
// Angela Nicoara. Portions created by Andrei Popovici and Angela Nicoara
// are Copyright (C) 2002 Andrei Popovici and Angela Nicoara.
// All Rights Reserved.
//
// Contributor(s):
// $Id: JoinPointManager.java,v 1.6 2008/11/18 11:37:06 anicoara Exp $
// =====================================================================
//
package ch.ethz.prose.engine;
// used packages
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Field;
import java.lang.reflect.Constructor;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Vector;
import ch.ethz.jvmai.ExceptionJoinPoint;
import ch.ethz.jvmai.ExceptionCatchJoinPoint;
import ch.ethz.jvmai.FieldAccessJoinPoint;
import ch.ethz.jvmai.FieldModificationJoinPoint;
import ch.ethz.jvmai.JVMAspectInterface;
import ch.ethz.jvmai.JoinPoint;
import ch.ethz.jvmai.JoinPointHook;
import ch.ethz.jvmai.MethodEntryJoinPoint;
import ch.ethz.jvmai.MethodExitJoinPoint;
import ch.ethz.jvmai.MethodRedefineJoinPoint;
import ch.ethz.jvmai.ConstructorJoinPoint;
import ch.ethz.prose.Insertable;
import ch.ethz.prose.ProsePermission;
import ch.ethz.prose.crosscut.Crosscut;
import ch.ethz.inf.util.Logger;
import ch.ethz.jvmai.SunBCELRepositoryInterface;
/**
* Class JoinPointManagerImpl defines implements two of the basic methods
* <code>JoinPointManager</code>. Subclasses of <code>AbstractJoinPointManager</code>
* should define only the link between registering event requests and the proper notification.
* They should use the <code>notifyListeners</code> method in order to notify the listeners
* of an event that this event occured, and invoke <code>handleExceptions</code> to handle
* notification exceptions. By providing an implementation of <code>handleExceptions</code>
* with a different <code>handleExceptions</code> method, subclasses may change the policy
* for exception handling.
* <p>
* All implementations of <code>AbstractJoinPointManager</code> should also define the
* abstract methods <code>suspendListenerNotification</code> and <code>resumeListenerNotification</code>
*
* @version $Revision: 1.6 $
* @author Andrei Popovici
* @author Angela Nicoara
*/
public class JoinPointManager extends JoinPointHook implements JoinPointQuery {
private static ProsePermission permission = new ProsePermission("registerListener");
// mapping JoinPointRequest -> List(JoinPointListener)
protected final Map req2listener;
// mapping JoinPointListener -> List(JoinPointRequest)
protected final Map listener2req;
// List(ClassLoadListeners)
protected final Set classLoadListeners;
// indicates if this JoinPointManager is connected to the Hook or if it is the test JPM
protected boolean isConnected;
// indicates if the table listener2req is maintained for Reverse Mapping.
protected boolean enableRevMap;
protected final HashSet enabledJoinPoints = new HashSet();
private JVMAspectInterface aspectInterface = null;
private Vector loadedClasses;
private ClassLoadConsumer classLoaderNotifier;
/**
* Create an <code>JoinPointManagerImpl</code> with no registered listeners
* or events.
*/
public JoinPointManager(boolean isConnected, JVMAspectInterface ai, boolean enableRevMap) {
req2listener = new HashMap();
listener2req = new HashMap();
classLoadListeners = new HashSet();
this.isConnected = isConnected;
aspectInterface = ai;
this.enableRevMap = enableRevMap;
loadedClasses = new Vector();
if (isConnected)
connectToJVMAI();
}
public static class ClassLoadConsumer extends Thread {
public boolean isRunning = true;
JoinPointManager jpm;
public ClassLoadConsumer(Vector classVector, JoinPointManager j) {
jpm=j;
}
{
Class c = NoClassDefFoundError.class;
}
public void run() {
while (isRunning) {
if (!jpm.loadedClasses.isEmpty()) {
Class c=(Class)jpm.loadedClasses.get(0);
try { c.getDeclaredMethods(); }
catch (NoClassDefFoundError e) {
c= null;
}
synchronized(jpm.loadedClasses) {
if (!jpm.loadedClasses.isEmpty())
jpm.loadedClasses.remove(0);
}
if (c!=null)jpm.notifyClassLoadListeners(c);
}
else {
synchronized(jpm.loadedClasses) {
try {jpm.loadedClasses.wait();} catch (java.lang.InterruptedException e){};
}
}
}
}
}
public void connectToJVMAI() {
if (aspectInterface == null)
throw new RuntimeException("Cannot have a NULL aspect interface");
loadedClasses = new Vector();
classLoaderNotifier = new ClassLoadConsumer(loadedClasses,this);
classLoaderNotifier.start();
aspectInterface.setJoinPointHook(this);
}
public void disconnectFromJVMAI() {
if (aspectInterface == null)
return;
if (isConnected) {
aspectInterface.setJoinPointHook(null);
classLoaderNotifier.isRunning=false;
synchronized(loadedClasses) {
loadedClasses.notifyAll();
}
}
// this should be part of a teardown procedure
listener2req.clear();
req2listener.clear();
}
public JVMAspectInterface getAspectInterface() {
return aspectInterface;
}
public boolean isReverseMappingEnabled() {
return enableRevMap;
}
public void onFieldAccess(FieldAccessJoinPoint joinPoint) {
List caughtExceptions = null;
ListenerList listeners = (ListenerList)joinPoint.getAopTag();
for (int i = 0; i < listeners.nrOfListeners; i++) {
try {
listeners.listenerArray[i].joinPointReached(joinPoint);
}
catch (Exception e) {
if (caughtExceptions == null)
caughtExceptions = new Vector();
caughtExceptions.add(handleRuntimeException(e));
}
}
handleExceptions(caughtExceptions,joinPoint);
}
public void onFieldModification(FieldModificationJoinPoint joinPoint) {
List caughtExceptions = null;
ListenerList listeners = (ListenerList)joinPoint.getAopTag();
for (int i = 0; i < listeners.nrOfListeners; i++) {
try {
listeners.listenerArray[i].joinPointReached(joinPoint);
}
catch (Exception e) {
if (caughtExceptions == null)
caughtExceptions = new Vector();
caughtExceptions.add(handleRuntimeException(e));
}
}
handleExceptions(caughtExceptions,joinPoint);
}
public void onMethodEntry(MethodEntryJoinPoint joinPoint) {
List caughtExceptions = null;
ListenerList listeners = (ListenerList)joinPoint.getAopTag();
for (int i = 0; i < listeners.nrOfListeners; i++) {
try {
listeners.listenerArray[i].joinPointReached(joinPoint);
}
catch (Exception e) {
if (caughtExceptions == null)
caughtExceptions = new Vector();
caughtExceptions.add(handleRuntimeException(e));
}
}
handleExceptions(caughtExceptions,joinPoint);
}
public void onMethodExit(MethodExitJoinPoint joinPoint) {
List caughtExceptions = null;
ListenerList listeners = (ListenerList)joinPoint.getAopTag();
for (int i = 0; i < listeners.nrOfListeners; i++) {
try {
listeners.listenerArray[i].joinPointReached(joinPoint);
}
catch (Exception e) {
if (caughtExceptions == null)
caughtExceptions = new Vector();
caughtExceptions.add(handleRuntimeException(e));
}
}
handleExceptions(caughtExceptions,joinPoint);
}
/**
* Hook method to be called whenever a registered constructor is invoked.
*/
public void onConstructor(ConstructorJoinPoint joinPoint) {
List caughtExceptions = null;
ListenerList listeners = (ListenerList)joinPoint.getAopTag();
for (int i = 0; i < listeners.nrOfListeners; i++) {
try {
listeners.listenerArray[i].joinPointReached(joinPoint);
}
catch (Exception e) {
if (caughtExceptions == null)
caughtExceptions = new Vector();
caughtExceptions.add(handleRuntimeException(e));
}
}
handleExceptions(caughtExceptions,joinPoint);
}
/**
* Hook method to be called whenever an Exception event is reported from
* the jvmai-interface.
* For every listener registered on an exception throw event it invoces the
* corresponding <code>joinPointReached</code>-method.
*/
public void onExceptionThrow(ExceptionJoinPoint joinPoint) {
//System.out.println("JoinPointManager -> onExceptionThrow");
List caughtExceptions = null;
ListenerList listeners = (ListenerList)joinPoint.getAopTag();
//System.out.println("JoinPointManager onExceptionThrow -> listeners.nrOfListeners = " + listeners.nrOfListeners);
//System.out.println("JoinPointManager onExceptionThrow -> listeners.toString = " + listeners.toString());
for (int i = 0; i < listeners.nrOfListeners; i++) {
try {
listeners.listenerArray[i].joinPointReached(joinPoint);
}
catch (Exception e) {
if (caughtExceptions == null)
caughtExceptions = new Vector();
caughtExceptions.add(handleRuntimeException(e));
}
}
handleExceptions(caughtExceptions,joinPoint);
}
/**
* Hook method to be called whenever an Exception Catch event is reported from
* the jvmai-interface.
* For every listener registered on an exception catch event it invoces the
* corresponding <code>joinPointReached</code>-method.
*/
public void onExceptionCatch(ExceptionCatchJoinPoint joinPoint) {
//System.out.println("JoinPointManager -> onExceptionCatch");
List caughtExceptions = null;
ListenerList listeners = (ListenerList)joinPoint.getAopTag();
//System.out.println("JoinPointManager onExceptionCatch -> listeners.nrOfListeners = " + listeners.nrOfListeners);
//System.out.println("JoinPointManager onExceptionCatch -> listeners.toString = " + listeners.toString());
for (int i = 0; i < listeners.nrOfListeners; i++) {
try {
listeners.listenerArray[i].joinPointReached(joinPoint);
}
catch (Exception e) {
if (caughtExceptions == null)
caughtExceptions = new Vector();
caughtExceptions.add(handleRuntimeException(e));
}
}
handleExceptions(caughtExceptions,joinPoint);
}
public void onClassLoad(Class cls) {
// Supress notification for classes which are already loaded.
// Bugfix for multiple remote insertion of the same aspect.
//
// Only required if aspects are inserted remotely
// TODO: disable this if property 'prose.port' is not set.
try {
// look if cls is already in BCEL repository
if(aspectInterface instanceof SunBCELRepositoryInterface &&
((SunBCELRepositoryInterface) aspectInterface).isClassLoaded(cls.getName())) {
return;
}
} catch(Exception e) { System.err.println("###" + e); e.printStackTrace(); }
// BIG FIXME: this is because of an error with versions prior to
// 1.4!. In addition, at this time (for whatever reason)
// I cannot check whether cls is assignable from ..
synchronized(loadedClasses) {
loadedClasses.add(cls);
loadedClasses.notifyAll();
}
}
/** Register the listener <code>lsnr</code> for events
* requested by <code>jpr</code>. If the request <code>jpr</code>
* is the first of its kind, the join corresponding join point
* will be enabled. More precise, if no other request with an equal
* signature has been previously registered, the join point
* will be enabled.
*
* This method checks for the permission ProsePermission "registerListener" to be present.
*/
public void registerListener(JoinPointListener list, JoinPointRequest req) {
// security check
//AccessController.checkPermission(permission); // FIXME
//System.out.println("JoinPointManager - JoinPointListener(list) = " + list);
//System.out.println("JoinPointManager - JoinPointRequest(req) = " + req);
ListenerList listeners = null;
synchronized(req2listener) {
listeners = (ListenerList)req2listener.get(req);
if (listeners == null) {
listeners = new ListenerList();
req2listener.put(req,listeners);
}
if (isConnected)
req.enableJoinPoint(listeners);
}
// note the trick: add adds the element at the end of the list.
// therefore, during notification, it will be notified last.
// therefore, the registration-notification ordering constraint
// holds..yeyeyeye
listeners.add(list);
if (enableRevMap) {
Set joinpoints = null;
synchronized(listener2req) {
joinpoints = (Set)listener2req.get(list);
if (joinpoints == null) {
joinpoints = new HashSet();
listener2req.put(list, joinpoints);
}
}
joinpoints.add(req);
}
}
/** Deregister listener <code>lsnr</code> from this JoinPointManager.
* As a result of this action, the listener <code>lsnr</code> will
* cease being notified of any kind of events. Upon this action,
* any 'lonely event' -- one for whom no listener exists will be
* disabled. More precisely, if <code>lsnr</code> was the last listener
* listening on events with the signature <em>S</em>,
* <em>S.disableJoinPoint</em> will be performed.
*/
public void unregisterListener(JoinPointListener listener) {
//iterate through our vectors...
try {
if (listener instanceof Insertable)
((Insertable)listener).withdrawalAction(true);
}
catch (Throwable e) {
// this must be ignored. A malicious crosscut may
// want to remain forever inserted. In fact,
// the unregistration should be robust.
e.printStackTrace();
}
Collection toRemoveKey = new Vector();
synchronized(req2listener) {
Iterator i = req2listener.keySet().iterator();
while(i.hasNext()) {
JoinPointRequest crtRequest = (JoinPointRequest)i.next();
ListenerList crtListenerList = (ListenerList)req2listener.get(crtRequest);
crtListenerList.remove(listener);
if (crtListenerList.contains(listener)) {
throw new Error("Vector.removeAll works improperly; please reimplement");
}
if (crtListenerList.isEmpty()) {
if (isConnected)
crtRequest.disableJoinPoint();
toRemoveKey.add(crtRequest);
}
}
Iterator oldKeys = toRemoveKey.iterator();
while(oldKeys.hasNext()) {
Object key = oldKeys.next();
req2listener.remove(key);
}
}
if (enableRevMap)
listener2req.remove(listener);
try {
if (listener instanceof Insertable)
((Insertable)listener).withdrawalAction(true);
}
catch (Throwable e) {
// this must be ignored. A malicious crosscut may
// want to remain forever inserted. In fact,
// the unregistration should be robust.
e.printStackTrace();
}
}
/**
* Return the state of the join-point manager. The registered listeners and
* the associated requests will be returned.
*/
public String toString() {
String isConnectedString = "(isNotConnected)";
if (isConnected) isConnectedString = "(isConnected)";
StringBuffer result = new StringBuffer();
result.append(" Join Point Manager (" + System.identityHashCode(this) + "State " + isConnectedString + ": \n");
result.append(req2listener.toString());
return result.toString();
}
/**
* This method should be called if a notification fails. If the notification
* fails with a Runtime exception, the default implementation of this
* method throws the corresponding run-time exception. Otherwise it
* returns the exception that caused the failure.
*
* @param e the exception to be handled
* @return a non-runtime exception thrown by the advice
*/
protected Exception handleRuntimeException(Exception e) {
// the obvious case
if (e!= null && e instanceof RuntimeException) {
throw (RuntimeException) e;
}
// invoked via reflection
if (e instanceof InvocationTargetException) {
InvocationTargetException invokedViaReflection = (InvocationTargetException)e;
if (invokedViaReflection.getTargetException() != null &&
invokedViaReflection.getTargetException() instanceof RuntimeException)
throw (RuntimeException)(invokedViaReflection.getTargetException());
else {
Exception realException = (Exception)invokedViaReflection.getTargetException();
if (realException != null)
return realException;
}
}
return e;
}
/**
* This method should be called if the notification fails for some of the
* listeners. Subclasses are encouraged to override this method in order to
* implement their own exception handling policy, e.g., removing the <em>bad</em>
* listeners from this <code>JoinPointManager</code>.
*
* @param exceptionList a list of <code>Exception</code> objects, thrown during the notification
* of listeners of <code>crtEvent</code>.
* @param crtEvent a <code>JoinPointEvent</code> whose listeners were not properly
* notified.
*/
protected void handleExceptions(List exceptionList,JoinPoint crtEvent) {
if (exceptionList != null) {
Iterator it = exceptionList.iterator();
while (it.hasNext()) {
Logger.error("Notification failed for " + crtEvent, (Exception) it.next());
}
}
}
/**
* Notify all registered class load listeners that the class
* <code>newClass</code> has just been loaded and prepared.
* This method should be invoked by implementations of this abstract
* immediately after a class of intereest (containing potential join-points)
* is loaded and prepared.
*
* @param newClass the class that is new to the system
*/
protected void notifyClassLoadListeners(Class newClass) {
synchronized(classLoadListeners) {
Iterator i=classLoadListeners.iterator();
while (i.hasNext()) {
ClassLoadListener crtListener = (ClassLoadListener)i.next();
crtListener.classLoaded(newClass);
}
}
}
/**
* Register a class load listener event. All class load listeners
* will be notified every time a class of interest (containing potential join-points)
* has been loaded.
*
* @param updateGuy the listener of class load events
*/
public void registerListener(ClassLoadListener updateGuy) {
classLoadListeners.add(updateGuy);
}
/**
* Unregister a class load listener.
*
* @param updateGuy the listener to be removed from the listeners list.
*/
public void unregisterListener(ClassLoadListener updateGuy) {
classLoadListeners.remove(updateGuy);
}
/**
* Suspend notification of all listeners interested in join-points
* that occur in the specified thread. This method is idempotent.
* Successive calls to this method with the same argument have
* the same effect as calling it only once.
*/
public void suspendListenerNotification(Thread t) {
// if (isConnected && aspectInterface != null)
aspectInterface.suspendNotification(t);
}
/**
* Resume the notification of listeners interested in join-points
* that occur in the specified thread.
*/
public void resumeListenerNotification(Thread t) {
// if (isConnected && aspectInterface != null)
aspectInterface.resumeNotification(t);
}
/**
* Returns all currently loaded classes in the virtual machine.
*/
public List getLoadedClasses() {
List result = aspectInterface.getLoadedClasses();
return result;
}
public Set allJoinpoints() {
return req2listener.keySet();
}
public Set getCrosscuts(JoinPointRequest jpr) {
Set result = new HashSet();
if (jpr == null) return result;
ListenerList listeners = (ListenerList)req2listener.get(jpr);
if (listeners == null)
return result;
else
for (int i=0; i < listeners.nrOfListeners; i++)
result.add(listeners.listenerArray[i]);
return result;
}
public JoinPointRequest createJoinPointRequest(String kind,Object o) {
if (MethodEntryJoinPoint.KIND.equals(kind))
return new MethodEntryRequest((Method)o, this);
if (MethodExitJoinPoint.KIND.equals(kind))
return new MethodExitRequest((Method)o,this);
if (MethodRedefineJoinPoint.KIND.equals(kind))
return new MethodRedefineRequest((Method) o, this);
if (FieldAccessJoinPoint.KIND.equals(kind))
return new FieldAccessRequest((Field)o, this);
if (FieldModificationJoinPoint.KIND.equals(kind))
return new FieldModificationRequest((Field)o, this);
if (ExceptionJoinPoint.KIND.equals(kind))
return new ExceptionThrowRequest((Class)o,this);
if (ExceptionCatchJoinPoint.KIND.equals(kind))
return new ExceptionCatchRequest((Class)o,this);
if (ConstructorJoinPoint.KIND.equals(kind))
return new ConstructorRequest((Constructor)o, this);
throw new RuntimeException("unknown kind");
}
public Set getJoinpoints(Crosscut cc) {
if (cc == null) return (Set)null;
if (enableRevMap) {
Set result = (Set)listener2req.get(cc);
if (result == null)
return new HashSet();
else
return result;
}
else {
return new HashSet(cc.createRequest());
}
}
}