//
// 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 Developer of the Original Code is Angela Nicoara. Portions
// created by Angela Nicoara are Copyright (C) 2004 Angela Nicoara.
// All Rights Reserved.
//
// Contributor(s):
// $Id: HotSwapFieldWeaver.java,v 1.2 2008/11/18 11:09:31 anicoara Exp $
// =====================================================================
//
package ch.ethz.inf.iks.jvmai.jvmdi;
import java.lang.reflect.Field;
//import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Member;
import java.util.*;
import ch.ethz.jvmai.MethodWeaver;
/**
* Remembers the fields where a callback should be inserted. For each method
* that references the specified field, a callback is woven using the
* MethodWeaver class.
*
* @author Angela Nicoara
* @author Johann Gyger
* @version $Revision: 1.2 $
*/
public class HotSwapFieldWeaver {
// Codes used for {@link #status}
protected final static int FW_UNCHANGED = 0x000;
protected final static int FW_ACCESS_ENABLED = 0x001;
protected final static int FW_MODIFICATION_ENABLED = 0x002;
protected final static int FW_MODIFIED = 0x004;
protected final static int FW_WOVEN = 0x008;
protected final static int FW_REGISTERED = 0x010;
/**
* Holds informations about knonw field accesses and modifications
* and gathers this informations.
*/
private static HotSwapClassRegister classRegister = HotSwapClassRegister.getInstance();
/**
* Collection of all field weavers. For each field there exists exactly one
* field weaver in the system.
*/
protected static Map weavers = new HashMap();
/**
* Maps the string identifier for each field weaver to the weaver.
*/
protected static Map weaverNames = new HashMap();
/**
* Maps unique field ids to the field objects.
*/
protected static Field fieldMap[] = new Field[1024];
/**
* Manages unique method ids.
*/
protected static UniqueIdArrayManager idMgr = new UniqueIdArrayManager();
/**
* Get a unique field weaver for `target'.
*
* @param target field that will be woven
*/
public static synchronized HotSwapFieldWeaver getWeaver(Field target) {
if (target == null)
throw new NullPointerException("Parameter `target' must not be null.");
HotSwapFieldWeaver result;
synchronized(weavers) {
result = (HotSwapFieldWeaver) weavers.get(target);
if (result == null) {
result = new HotSwapFieldWeaver(target);
weavers.put(target, result);
weaverNames.put(result.key, result);
}
}
return result;
};
/**
* Re-weave all modified f and activate them in the VM.
*/
public static void commit() {
synchronized(weavers) {
// The Collection is copied to an array to allow removal of entries from the collection
// while weaving.
HotSwapFieldWeaver[] fweavers = new HotSwapFieldWeaver[weavers.size()];
weavers.values().toArray( fweavers );
for( int i = 0; i < fweavers.length; i++ ) {
HotSwapFieldWeaver fw = fweavers[i];
if( (FW_MODIFIED & fw.status) > 0 )
try{ fw.weave(); }
catch( ClassNotFoundException e)
{ System.err.println( "can not load class file for " + fw.target.getDeclaringClass().getName() ); }
// TODO: remove old (inactive) weavers
}
}
}
/**
* Reset all field weavers.
*/
public static void resetAll() {
synchronized(weavers) {
weavers = new HashMap();
weaverNames = new HashMap();
fieldMap = new Field[1024];
idMgr.reset();
}
}
/**
* Returns an identifier for <CODE>method</CODE> and
* stores the association, so that {@link #idToMethod(int) idToMethod()}
* may be used to get the <CODE>method</CODE> using the id.
*
* Note: only the id is unique, if <CODE>createNewMethodId(Method)
* </CODE> is called more than one time with the same argument,
* each call will create a new id and a new map entry.
*
* @param field that will be associated with a new identifier.
* @return new identifier for <CODE>method</CODE>.
*/
static int createNewFieldId( Field field ) {
if( null == field )
throw new NullPointerException();
int result;
synchronized( fieldMap ) {
result = idMgr.newId();
// check if map must be expanded
int mapLength = fieldMap.length;
if( mapLength <= result ) {
Field newMap[] = new Field[ 2 * mapLength ];
System.arraycopy( fieldMap, 0, newMap, 0, mapLength );
fieldMap = newMap;
}
// add method to the map
fieldMap[result] = field;
}
return result;
}
/**
* Returns the Method object associated to <CODE>methodId</CODE>
* or <CODE>null</CODE>, if <CODE>methodID</CODE> is not a valid
* id.
* The ids must be created with {@link #createNewFieldId createNewFieldId()}.
*
* @param fieldId id for the Method that will be returned.
* @return Method associated to <CODE>methodID<CODE> or <CODE>null</CODE>.
* @throws ArrayOutOfBoundException
*/
public static Field idToField( int fieldId ) {
return fieldMap[fieldId];
}
/**
* Field bound to this weaver.
*/
protected Field target;
/**
* Unique numerical identifier for {#link: #target target}, created with
* {@link HotSwapFieldWeaver#createNewFieldId createNewFieldId()}.
*/
protected int targetId;
/**
* Holds the state of the weaver.
*/
protected int status = FW_UNCHANGED;
/**
* Unique identifiere string for the target of this weaver. The format is
* <CODE>ClassName#FieldName#JNISignature</CODE>.
*/
protected String key;
/**
* Create a new field weaver.
*
* @param target target field for which callbacks will be woven
*/
protected HotSwapFieldWeaver(Field target) {
this.target = target;
targetId = createNewFieldId( target );
key = target.getDeclaringClass().getName() + '#' + target.getName() + '#' + JNIUtil.jniSignature( target.getType() );
}
/**
* Enable field access join point.
*
* @param flag enable/disable
*/
public void setFieldAccessEnabled(boolean flag) {
//System.out.println("HotSwapFieldWeaver.setFieldAccessEnabled(" + String.valueOf(flag) + ")");
if( flag != (0 < (FW_ACCESS_ENABLED & status)) ) {
status = FW_MODIFIED | (flag ? status | FW_ACCESS_ENABLED : status & ~FW_ACCESS_ENABLED);
}
}
/**
* Enable field modification join point.
*
* @param flag enable/disable
*/
public void setFieldModificationEnabled(boolean flag) {
//System.out.println("HotSwapFieldWeaver.setFieldModificationEnabled(" + String.valueOf(flag) + ")");
if( flag != (0 < (FW_MODIFICATION_ENABLED & status)) ) {
status = FW_MODIFIED | (flag ? status | FW_MODIFICATION_ENABLED : status & ~FW_MODIFICATION_ENABLED);
}
}
/**
* Returns the target of this FieldWeaver.
*
* @return Field
*/
public Field getTarget() {
return target;
}
/**
* Returns the unique id of the target of this field.
* This is used as parameter for the advice callback
* function.
*
* @return int target's id
*/
public int getTargetId() {
return targetId;
}
public String debugString() {
StringBuffer sb = new StringBuffer();
sb.append("FieldWeaver for: ");
sb.append(target);
sb.append("\n\tStatus: ");
sb.append(status);
sb.append("\n\tString id: ");
sb.append(key);
sb.append("\n\tint id: ");
sb.append(targetId);
return sb.toString();
}
/**
* Returns a unique String identifier for the target field.
* The format is <CODE>ClassName#FieldName#JNISignature</CODE>.
*
* @return String identifier for the target field.
*/
public String getKey() {
return key;
}
/**
* Weave advice that are associated with the field in this weaver. The
* weaving is actually done by the MethodWeaver. This method only informs the
* MethodWeaver instances that are involved.
*
* @throws java.lang.ClassNotFoundException can not load the class defining
* the field (this will only be thrown for private fields).
*/
protected void weave() throws ClassNotFoundException{
//System.out.println("HotSwapFieldWeaver.weave() for " + target.getName());
// first the already known accessors and/or modifiers will be woven,
// afterwards the loaded classes will be scanned.
//
// Make shure that all methods of the classes, which have references to 'target'
// are scanned for accesses to it.
List targetClasses = (List) HotSwapClassRegister.knownFieldReferences.get( key );
classRegister.scanClasses( targetClasses );
// 1. Set the method weavers for all accessors
// 1.a Get the registered entries
List accessors = (List) HotSwapClassRegister.knownFieldAccesses.get( key );
if( null != accessors ) {
Iterator iter = accessors.iterator();
while( iter.hasNext() ) {
String str = (String) iter.next();
// 1.b Convert each entry in a reflected method object
Member member = HotSwapAspectInterfaceImpl.getMethodFromString( str );
// 1.c Get the method weaver for the method
MethodWeaver mw = HotSwapClassWeaver.getWeaver( member );
// 1.d Register (or unregister) 'target'
if( (FW_ACCESS_ENABLED & status) > 0 )
mw.addFieldAccessor( this );
else
mw.removeFieldAccessor( this );
//System.out.println("FieldWeaver field access changed: " + mw.getTarget().getName() );
}
}
// 2. Set the method weavers for all modifiers
// 2.a Get the registered entries
List modifiers = (List) HotSwapClassRegister.knownFieldModifiers.get( key );
if( null != modifiers ) {
Iterator iter = modifiers.iterator();
while( iter.hasNext() ) {
String str = (String) iter.next();
// 2.b Convert each entry in a reflected method object
Member member = HotSwapAspectInterfaceImpl.getMethodFromString( str );
// 2.c Get the method weaver for the method
MethodWeaver mw = HotSwapClassWeaver.getWeaver( member );
// 2.d Register (or unregister) 'target'
if( (FW_MODIFICATION_ENABLED & status) > 0 )
mw.addFieldModifier( this );
else
mw.removeFieldModifier( this );
//System.out.println("FieldWeaver field modification changed: " + mw.getTarget().getName() );
}
}
if( ((FW_ACCESS_ENABLED | FW_MODIFICATION_ENABLED) & status) == 0 ) {
// 3. Remove the weaver, if there is no enabled watch,
// since only modified weavers get woven, this means
// that the watch was disabled.
remove();
return;
}
// 4. Scan all loaded classes to which 'target' is visible.
scanPotentialAccesses();
// 5. Change the state of this weaver.
status = (status & ~FW_MODIFIED) | FW_WOVEN;
}
/**
* Scanns all classes to which {@link #target target} is visible. The results
* will be addes to {@link HotSwapClassRegister.knownFieldAccesses
* HotSwapClassRegister.knownFieldAccesses} and {@link
* HotSwapClassRegister.knownFieldModifications
* HotSwapClassRegister.knownFieldModifications}. Classes that
* will be loaded later, get scanned at loading.
*
* @throws ClassNotFoundException can not load the class that defines
* the field (this will only be thrown if the field is private).
*/
private void scanPotentialAccesses() throws ClassNotFoundException {
// prevent repeated registration for the same weaver.
if( (FW_REGISTERED & status) > 0 )
return;
status |= FW_REGISTERED;
// determine which classes should get scanned for this weaver.
Class declaringClass = target.getDeclaringClass();
switch( target.getModifiers() & (Modifier.PRIVATE | Modifier.PROTECTED | Modifier.PUBLIC) ) {
case Modifier.PRIVATE:
// scan the class declaring the field.
classRegister.registrationRequest( declaringClass );
// scan inner classes
Class[] innerClasses = declaringClass.getDeclaredClasses();
for( int i = 0; i < innerClasses.length; i++ ) {
classRegister.registrationRequest( innerClasses[i]);
}
break;
case Modifier.PROTECTED:
// scan all classes belonging to the same package
classRegister.registrationRequest( declaringClass.getPackage() );
// scan all subclasses
classRegister.registrationRequestSubClasses( declaringClass );
break;
case Modifier.PUBLIC:
// check class modifier (if the class is not public, it's only
// accessible for classes from the same package).
if( Modifier.isPublic( declaringClass.getModifiers() ) ) {
classRegister.registrationRequestAll();
break;
}
// if the class is not public, handle the field like
// a field without modifiers.
default:
// scan all classes in the same package
classRegister.registrationRequest( declaringClass.getPackage() );
}
}
/**
* Removes this field weaver.
* Removes also all registrations in HotSwapClassRegister, which was done for it
* and the fieldMap entry for it's target.
*/
private void remove() {
// 1. remove the weaver from the field weaver map.
weavers.remove( target );
weaverNames.remove( target.getDeclaringClass().getName() + '#' + target.getName() + '#' + JNIUtil.jniSignature( target.getType() ) );
// 1a. remove the field map entry
idMgr.releaseId( targetId );
fieldMap[targetId] = null;
// 2. remove registrations, done for this weaver.
if( (FW_REGISTERED & status) > 0 )
// If this field join point was never woven, it was also never registered.
return;
Class declaringClass = target.getDeclaringClass();
switch( target.getModifiers() & (Modifier.PRIVATE | Modifier.PROTECTED | Modifier.PUBLIC ) ) {
case Modifier.PRIVATE:
classRegister.removeRequest( declaringClass );
Class[] innerClasses = declaringClass.getDeclaredClasses();
for( int i = 0; i < innerClasses.length; i++ ) {
classRegister.removeRequest( innerClasses[i]);
}
break;
case Modifier.PROTECTED:
// scan all classes belonging to the same package
classRegister.removeRequest( declaringClass.getPackage() );
// scan all subclasses
classRegister.removeRequestSubClass( declaringClass );
break;
case Modifier.PUBLIC:
// check class modifier (if the class is not public, it's only
// accessible for classes from the same package).
if( Modifier.isPublic( declaringClass.getModifiers() ) ) {
classRegister.removeRequestAll();
break;
}
// if the class is not public, handle the field like
// a field without modifiers.
default:
// scan all classes in the same package
classRegister.removeRequest( declaringClass.getPackage() );
}
status &= ~FW_REGISTERED;
}
}