467 lines
19 KiB
Java
467 lines
19 KiB
Java
/*
|
|
* Copyright 2010-2012 VMware and contributors
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
package org.springsource.loaded;
|
|
|
|
import java.lang.reflect.Field;
|
|
import java.lang.reflect.Modifier;
|
|
import java.util.HashMap;
|
|
import java.util.Map;
|
|
import java.util.logging.Level;
|
|
import java.util.logging.Logger;
|
|
|
|
/**
|
|
* Able to read or write a particular field in a type. Knows nothing about the instance upon which the read/write may be getting
|
|
* done.
|
|
*
|
|
* @author Andy Clement
|
|
* @since 0.5.0
|
|
*/
|
|
public class FieldReaderWriter {
|
|
|
|
private static Logger log = Logger.getLogger(FieldReaderWriter.class.getName());
|
|
|
|
/**
|
|
* The type descriptor for the type that defines the field we want to access
|
|
*/
|
|
protected TypeDescriptor typeDescriptor;
|
|
|
|
protected FieldMember theField;
|
|
|
|
public FieldReaderWriter(FieldMember theField, TypeDescriptor typeDescriptor) {
|
|
this.theField = theField;
|
|
this.typeDescriptor = typeDescriptor;
|
|
assert theField.typename == typeDescriptor.typename;
|
|
}
|
|
|
|
protected FieldReaderWriter() {
|
|
}
|
|
|
|
/**
|
|
* Set the value of an instance field on the specified instance to the specified value. If a state manager is passed in things
|
|
* can be done in a more optimal way, otherwise the state manager has to be discovered from the instance.
|
|
*
|
|
* @param instance the object instance upon which to set the field
|
|
* @param newValue the new value for that field
|
|
* @param stateManager the optional state manager for this instance, which will be looked up (expensive) if not passed in
|
|
* @throws IllegalAccessException if the field value cannot be set
|
|
*/
|
|
public void setValue(Object instance, Object newValue, ISMgr stateManager) throws IllegalAccessException {
|
|
if (typeDescriptor.isReloadable()) {
|
|
if (stateManager == null) {
|
|
// Look it up using reflection
|
|
stateManager = findInstanceStateManager(instance);
|
|
}
|
|
String declaringTypeName = typeDescriptor.getName();
|
|
Map<String, Object> typeLevelValues = stateManager.getMap().get(declaringTypeName);
|
|
if (typeLevelValues == null) {
|
|
// first time we've accessed this type for an instance field
|
|
typeLevelValues = new HashMap<String, Object>();
|
|
stateManager.getMap().put(declaringTypeName, typeLevelValues);
|
|
}
|
|
typeLevelValues.put(theField.getName(), newValue);
|
|
} else { // the type is not reloadable, must use reflection to access the value
|
|
// TODO generate get/set in the topmost reloader for these kinds of field and use them?
|
|
if (typeDescriptor.isInterface()) {
|
|
// field resolution has left us with an interface field, those can't be set like this
|
|
throw new IncompatibleClassChangeError("Expected non-static field " + instance.getClass().getName() + "."
|
|
+ theField.getName());
|
|
} else {
|
|
findAndSetFieldValueInHierarchy(instance, newValue);
|
|
}
|
|
}
|
|
}
|
|
|
|
public void setStaticFieldValue(Class<?> clazz, Object newValue, SSMgr stateManager) throws IllegalAccessException {
|
|
if (clazz == null) {
|
|
throw new IllegalStateException();
|
|
}
|
|
// First decision - is the field part of a reloadable type or not? The typeDescriptor here is the actual owner
|
|
// of the field, at this point we *know* this class has this field.
|
|
if (typeDescriptor.isReloadable()) {
|
|
if (stateManager == null) {
|
|
// need to go and find it, there *will* be one but it will be slow to retrieve (reflection)
|
|
stateManager = findStaticStateManager(clazz);
|
|
}
|
|
String declaringTypeName = typeDescriptor.getName();
|
|
Map<String, Object> typeLevelValues = stateManager.getMap().get(declaringTypeName);
|
|
if (typeLevelValues == null) {
|
|
typeLevelValues = new HashMap<String, Object>();
|
|
stateManager.getMap().put(declaringTypeName, typeLevelValues);
|
|
}
|
|
typeLevelValues.put(theField.getName(), newValue);
|
|
} else { // the type is not reloadable, must use reflection to access the value
|
|
try {
|
|
Field f = locateFieldByReflection(clazz, typeDescriptor.getDottedName(), typeDescriptor.isInterface(),
|
|
theField.getName());
|
|
f.setAccessible(true);
|
|
f.set(null, newValue);
|
|
// cant cache result - we dont control the sets so won't know it is happening anyway
|
|
} catch (Exception e) {
|
|
throw new IllegalStateException("Unexpectedly unable to reflectively set the field " + theField.getName()
|
|
+ " on the type " + clazz.getName());
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Return the value of the field for which is reader-writer exists. To improve performance a fieldAccessor can be supplied but
|
|
* if it is missing the code will go and discover it.
|
|
*
|
|
* @param instance the instance for which the field should be fetched
|
|
* @param stateManager an optional state manager containing the map of values (will be discovered if not supplied)
|
|
* @return the value of the field
|
|
* @throws IllegalAccessException if there is a problem accessing the field value
|
|
*/
|
|
public Object getValue(Object instance, ISMgr stateManager) throws IllegalAccessException, IllegalArgumentException {
|
|
Object result = null;
|
|
String fieldname = theField.getName();
|
|
if (typeDescriptor.isReloadable()) {
|
|
if (stateManager == null) {
|
|
// find it using reflection
|
|
stateManager = findInstanceStateManager(instance);
|
|
}
|
|
String declaringTypeName = typeDescriptor.getName();
|
|
Map<String, Object> typeLevelValues = stateManager.getMap().get(declaringTypeName);
|
|
boolean knownField = false;
|
|
if (typeLevelValues != null) {
|
|
knownField = typeLevelValues.containsKey(fieldname);
|
|
}
|
|
if (knownField) {
|
|
result = typeLevelValues.get(fieldname);
|
|
}
|
|
|
|
// If a field has been deleted it may 'reveal' a field in a supertype. The revealed field may be in a type
|
|
// not yet dealt with. In this case typeLevelValues may be null (type not seen before) or the typelevelValues
|
|
// may not have heard of our field name. In these cases we need to go and find the field and 'relocate' it
|
|
// into our map, where it will be processed from now on.
|
|
if (typeLevelValues == null || !knownField) {
|
|
|
|
FieldMember fieldOnOriginalType = typeDescriptor.getReloadableType().getTypeRegistry()
|
|
.getReloadableType(declaringTypeName).getTypeDescriptor().getField(fieldname);
|
|
|
|
if (fieldOnOriginalType != null) {
|
|
// Copy the field into the map - that is where it will live from now on
|
|
ReloadableType rt = typeDescriptor.getReloadableType();
|
|
try {
|
|
Field f = rt.getClazz().getDeclaredField(fieldname);
|
|
f.setAccessible(true);
|
|
result = f.get(instance);
|
|
if (typeLevelValues == null) {
|
|
typeLevelValues = new HashMap<String, Object>();
|
|
stateManager.getMap().put(declaringTypeName, typeLevelValues);
|
|
}
|
|
typeLevelValues.put(fieldname, result);
|
|
} catch (Exception e) {
|
|
throw new IllegalStateException("Unexpectedly unable to access field " + fieldname + " on class "
|
|
+ rt.getClazz(), e);
|
|
}
|
|
} else {
|
|
// The field was not on the original type. As not seen before, can default it
|
|
result = Utils.toResultCheckIfNull(null, theField.getDescriptor());
|
|
if (typeLevelValues == null) {
|
|
typeLevelValues = new HashMap<String, Object>();
|
|
stateManager.getMap().put(declaringTypeName, typeLevelValues);
|
|
}
|
|
typeLevelValues.put(fieldname, result);
|
|
return result;
|
|
}
|
|
}
|
|
if (result != null) {
|
|
result = Utils.checkCompatibility(typeDescriptor.getTypeRegistry(), result, theField.getDescriptor());
|
|
if (result == null) {
|
|
// Was not compatible, forget it
|
|
typeLevelValues.remove(fieldname);
|
|
}
|
|
}
|
|
result = Utils.toResultCheckIfNull(result, theField.getDescriptor());
|
|
} else {
|
|
// the type is not reloadable, must use reflection to access the value.
|
|
// TODO measure how often we hit the reflection path, should never happen unless reflection is already on the frame
|
|
|
|
if (typeDescriptor.isInterface()) { // cant be an instance field if it is found to be on an interface
|
|
throw new IncompatibleClassChangeError("Expected non-static field " + instance.getClass().getName() + "."
|
|
+ fieldname);
|
|
} else {
|
|
result = findAndGetFieldValueInHierarchy(instance);
|
|
}
|
|
}
|
|
|
|
if (GlobalConfiguration.isRuntimeLogging && log.isLoggable(Level.FINER)) {
|
|
log.finer("<getValue() value of " + theField + " is " + result);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
public Object getStaticFieldValue(Class<?> clazz, SSMgr stateManager) throws IllegalAccessException, IllegalArgumentException {
|
|
Object result = null;
|
|
if (clazz == null) {
|
|
throw new IllegalStateException();
|
|
}
|
|
// First decision - is the field part of a reloadable type or not? The typeDescriptor here is the actual owner
|
|
// of the field, at this point we *know* this class has this field.
|
|
if (typeDescriptor.isReloadable()) {
|
|
if (stateManager == null) {
|
|
// need to go and find it, there *will* be one but it will be slow to retrieve (reflection)
|
|
stateManager = findStaticStateManager(clazz);
|
|
if (stateManager == null) {
|
|
return Utils.toResultCheckIfNull(null, theField.descriptor);
|
|
}
|
|
}
|
|
String declaringTypeName = typeDescriptor.getName();
|
|
Map<String, Object> typeLevelValues = stateManager.getMap().get(declaringTypeName);
|
|
String fieldname = theField.getName();
|
|
boolean knownField = false;
|
|
if (typeLevelValues != null) {
|
|
knownField = typeLevelValues.containsKey(fieldname);
|
|
}
|
|
if (knownField) {
|
|
result = typeLevelValues.get(fieldname);
|
|
}
|
|
// If a field has been deleted it may 'reveal' a field in a supertype. The revealed field may be in a type
|
|
// not yet dealt with. In this case typeLevelValues may be null (type not seen before) or the typelevelValues
|
|
// may not have heard of our field name. In these cases we need to go and find the field and 'relocate' it
|
|
// into our map, where it will be processed from now on.
|
|
|
|
// These revealed fields are not necessarily in the original form of the type so cannot always be accessed via reflection
|
|
if (typeLevelValues == null || !knownField) {
|
|
|
|
FieldMember fieldOnOriginalType = typeDescriptor.getReloadableType().getTypeRegistry()
|
|
.getReloadableType(declaringTypeName).getTypeDescriptor().getField(fieldname);
|
|
|
|
if (fieldOnOriginalType != null) { // && fieldOnOriginalType.isStatic()
|
|
// can use reflection
|
|
ReloadableType rt = typeDescriptor.getReloadableType();
|
|
try {
|
|
Field f = rt.getClazz().getDeclaredField(theField.getName());
|
|
if (!Modifier.isStatic(f.getModifiers())) {
|
|
// need to default it anyway, cant see that original value
|
|
// TODO this is a dup of the code below, refactor
|
|
result = Utils.toResultCheckIfNull(null, theField.getDescriptor());
|
|
if (typeLevelValues == null) {
|
|
typeLevelValues = new HashMap<String, Object>();
|
|
stateManager.getMap().put(declaringTypeName, typeLevelValues);
|
|
}
|
|
typeLevelValues.put(fieldname, result);
|
|
return result;
|
|
}
|
|
f.setAccessible(true);
|
|
// TODO can fail on this next line if the field we've found is non-static
|
|
result = f.get(null);
|
|
if (typeLevelValues == null) {
|
|
typeLevelValues = new HashMap<String, Object>();
|
|
stateManager.getMap().put(declaringTypeName, typeLevelValues);
|
|
}
|
|
typeLevelValues.put(theField.getName(), result);
|
|
} catch (Exception e) {
|
|
throw new IllegalStateException("Unexpectedly unable to read field " + theField.getName() + " on type "
|
|
+ rt.getClazz(), e);
|
|
}
|
|
} else {
|
|
// The field was not on the original type. As not seen before, can default it
|
|
result = Utils.toResultCheckIfNull(null, theField.getDescriptor());
|
|
if (typeLevelValues == null) {
|
|
typeLevelValues = new HashMap<String, Object>();
|
|
stateManager.getMap().put(declaringTypeName, typeLevelValues);
|
|
}
|
|
typeLevelValues.put(fieldname, result);
|
|
return result;
|
|
}
|
|
}
|
|
// A problem that can occur is if a fields type is changed on a reload. If the field
|
|
// was previously written to we can then retrieve it and attempt to pass it back to the caller
|
|
// and they'll get something unexpected.
|
|
if (result != null) {
|
|
result = Utils.checkCompatibility(typeDescriptor.getTypeRegistry(), result, theField.getDescriptor());
|
|
if (result == null) {
|
|
typeLevelValues.remove(theField.getName());
|
|
}
|
|
}
|
|
result = Utils.toResultCheckIfNull(result, theField.getDescriptor());
|
|
} else { // the type is not reloadable, must use reflection to access the value
|
|
// TODO measure how often this code gets hit - ensure it does not in the common (non reflective) case
|
|
try {
|
|
Field f = locateFieldByReflection(clazz, typeDescriptor.getDottedName(), typeDescriptor.isInterface(),
|
|
theField.getName());
|
|
f.setAccessible(true);
|
|
result = f.get(null);
|
|
// cant cache result - we dont control the sets so won't know it is happening anyway
|
|
} catch (Exception e) {
|
|
throw new IllegalStateException("Unexpectedly unable to set static field " + theField.getName() + " on type "
|
|
+ typeDescriptor.getDottedName());
|
|
}
|
|
|
|
}
|
|
|
|
if (GlobalConfiguration.isRuntimeLogging && log.isLoggable(Level.FINER)) {
|
|
log.finer("<getValue() value of " + theField + " is " + result);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Discover the named field in the hierarchy using the standard rules of resolution.
|
|
*
|
|
* @param clazz the class upon which to start looking
|
|
* @param typeWanted where the field is!
|
|
* @param name the name of the field to find
|
|
* @return the jlrField object representing that field, or null if not found
|
|
*/
|
|
private Field locateFieldByReflection(Class<?> clazz, String typeWanted, boolean isInterface, String name) {
|
|
if (clazz.getName().equals(typeWanted)) {
|
|
Field[] fs = clazz.getDeclaredFields();
|
|
if (fs != null) {
|
|
for (Field f : fs) {
|
|
if (f.getName().equals(name)) {
|
|
return f;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// Check interfaces
|
|
if (!isInterface) { // not worth looking!
|
|
Class<?>[] interfaces = clazz.getInterfaces();
|
|
if (interfaces != null) {
|
|
for (Class<?> intface : interfaces) {
|
|
Field f = locateFieldByReflection(intface, typeWanted, isInterface, name);
|
|
if (f != null) {
|
|
return f;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// Check superclass
|
|
Class<?> superclass = clazz.getSuperclass();
|
|
if (superclass == null) {
|
|
return null;
|
|
} else {
|
|
return locateFieldByReflection(superclass, typeWanted, isInterface, name);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Discover the instance state manager for the specific object instance. Will fail by exception rather than returning null.
|
|
*
|
|
* @param instance the object instance on which to look
|
|
* @return the discovered state manager
|
|
*/
|
|
private ISMgr findInstanceStateManager(Object instance) {
|
|
Class<?> clazz = typeDescriptor.getReloadableType().getClazz();
|
|
try {
|
|
Field fieldAccessorField = clazz.getField(Constants.fInstanceFieldsName);
|
|
if (fieldAccessorField == null) {
|
|
throw new IllegalStateException("Cant find field accessor for type " + clazz.getName());
|
|
}
|
|
ISMgr stateManager = (ISMgr) fieldAccessorField.get(instance);
|
|
if (stateManager == null) {
|
|
// Looks to not have been initialized yet, this can happen if a non standard ctor was used.
|
|
// We could push this step into the generated ctors...
|
|
ISMgr instanceStateManager = new ISMgr(instance, typeDescriptor.getReloadableType());
|
|
fieldAccessorField.set(instance,instanceStateManager);
|
|
stateManager = (ISMgr) fieldAccessorField.get(instance);
|
|
// For some reason it didn't stick!
|
|
if (stateManager == null) {
|
|
throw new IllegalStateException("The class '" + clazz.getName()
|
|
+ "' has a null instance state manager object, instance is " + instance);
|
|
}
|
|
}
|
|
return stateManager;
|
|
} catch (Exception e) {
|
|
throw new IllegalStateException("Unexpectedly unable to find instance state manager on class " + clazz.getName(), e);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Discover the static state manager on the specified class and return it. Will fail by exception rather than returning null.
|
|
*
|
|
* @param clazz the class on which to look
|
|
* @return the discovered state manager
|
|
*/
|
|
private SSMgr findStaticStateManager(Class<?> clazz) {
|
|
try {
|
|
Field stateManagerField = clazz.getField(Constants.fStaticFieldsName);
|
|
if (stateManagerField == null) {
|
|
throw new IllegalStateException("Cant find field accessor for type " + typeDescriptor.getReloadableType().getName());
|
|
}
|
|
SSMgr stateManager = (SSMgr) stateManagerField.get(null);
|
|
// Field should always have been initialized - it is done at the start of the top most reloadable type <clinit>
|
|
if (stateManager == null) {
|
|
throw new IllegalStateException("Instance of this class has no state manager: " + clazz.getName());
|
|
}
|
|
return stateManager;
|
|
} catch (Exception e) {
|
|
throw new IllegalStateException("Unexpectedly unable to find static state manager on class " + clazz.getName(), e);
|
|
}
|
|
}
|
|
|
|
public boolean isStatic() {
|
|
return theField.isStatic();
|
|
}
|
|
|
|
/**
|
|
* Walk up the instance hierarchy looking for the field, and when it is found access it and return the result. Will exit via
|
|
* exception if it cannot find the field or something goes wrong when accessing it.
|
|
*
|
|
* @param instance the object instance upon which the field is being accessed
|
|
* @return the value of the field
|
|
*/
|
|
private Object findAndGetFieldValueInHierarchy(Object instance) {
|
|
Class<?> clazz = instance.getClass();
|
|
String fieldname = theField.getName();
|
|
String searchName = typeDescriptor.getName().replace('/', '.');
|
|
while (clazz != null && !clazz.getName().equals(searchName)) {
|
|
clazz = clazz.getSuperclass();
|
|
}
|
|
if (clazz == null) {
|
|
throw new IllegalStateException("Failed to find " + searchName + " in hierarchy of " + instance.getClass());
|
|
}
|
|
try {
|
|
Field f = clazz.getDeclaredField(fieldname);
|
|
f.setAccessible(true);
|
|
return f.get(instance);
|
|
} catch (Exception e) {
|
|
throw new IllegalStateException("Unexpectedly could not access field named " + fieldname + " on class "
|
|
+ clazz.getName());
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Walk up the instance hierarchy looking for the field, and when it is found set it. Will exit via exception if it cannot find
|
|
* the field or something goes wrong when accessing it.
|
|
*
|
|
* @param instance the object instance upon which the field is being set
|
|
* @param newValue the new value for the field
|
|
*/
|
|
private void findAndSetFieldValueInHierarchy(Object instance, Object newValue) {
|
|
Class<?> clazz = instance.getClass();
|
|
String fieldname = theField.getName();
|
|
String searchName = typeDescriptor.getName().replace('/', '.');
|
|
while (clazz != null && !clazz.getName().equals(searchName)) {
|
|
clazz = clazz.getSuperclass();
|
|
}
|
|
if (clazz == null) {
|
|
throw new IllegalStateException("Failed to find " + searchName + " in hierarchy of " + instance.getClass());
|
|
}
|
|
try {
|
|
Field f = clazz.getDeclaredField(fieldname);
|
|
f.setAccessible(true);
|
|
f.set(instance, newValue);
|
|
} catch (Exception e) {
|
|
throw new IllegalStateException("Unexpectedly could not access field named " + fieldname + " on class "
|
|
+ clazz.getName());
|
|
}
|
|
|
|
}
|
|
} |