/* * 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.util.HashMap; import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; /** * Static State Manager. The top most class in every hierarchy of reloadable types gets a static state manager instance. * The static state manager is used to find the value of a field for a particular object instance. The FieldAccessor is * added to the top most type in a reloadable hierarchy and is accessible to all the subtypes. It maintains a map from * type names to fields (name/value pairs). * * @author Andy Clement * @since 0.5.0 */ public class SSMgr { private static Logger log = Logger.getLogger(SSMgr.class.getName()); Map<String, Map<String, Object>> values = new HashMap<String, Map<String, Object>>(); public Object getValue(ReloadableType rtype, String name) throws IllegalAccessException { // System.out.println("SSMgr.getValue(rtype=" + rtype + ",name=" + name + ")"); Object result = null; // quick look to see if it is nearby (searches up supertype hierarchy, but only up to the topmost reloadabletype) FieldMember fieldmember = rtype.findStaticField(name);//InstanceField(name); // Why can fieldmember be null? // 1. Field really does not exist - shouldn't really be possible if the code is 'valid' // 2. Field is inherited from a supertype (usually because a reload has occurred) if (fieldmember == null) { FieldReaderWriter flr = rtype.locateField(name); if (flr == null) { log.info("Unexpectedly unable to locate static field " + name + " starting from type " + rtype.dottedtypename + ": clinit running late?"); return null; } result = flr.getStaticFieldValue(rtype.getClazz(), this); } else { if (!fieldmember.isStatic()) { throw new IncompatibleClassChangeError("Expected static field " + rtype.dottedtypename + "." + fieldmember.getName()); } String declaringTypeName = fieldmember.getDeclaringTypeName(); Map<String, Object> typeLevelValues = values.get(declaringTypeName); boolean knownField = false; if (typeLevelValues != null) { knownField = typeLevelValues.containsKey(name); } if (knownField) { result = typeLevelValues.get(name); } // 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) { // TODO lookup performance? FieldMember fieldOnOriginalType = rtype.getTypeRegistry().getReloadableType(declaringTypeName).getTypeDescriptor() .getField(name); if (fieldOnOriginalType != null) { // Copy that field into the map... where it is going to live from now on ReloadableType rt = rtype.getTypeRegistry().getReloadableType(fieldmember.getDeclaringTypeName()); try { Field f = rt.getClazz().getDeclaredField(name); f.setAccessible(true); result = f.get(null); if (typeLevelValues == null) { typeLevelValues = new HashMap<String, Object>(); values.put(declaringTypeName, typeLevelValues); } typeLevelValues.put(name, result); } catch (Exception e) { throw new IllegalStateException("Unexpectedly unable to access field " + name + " on type " + rt.getClazz().getName(), e); } } else { // The field was not on the original type. As not seen before, can default it result = Utils.toResultCheckIfNull(null, fieldmember.getDescriptor()); if (typeLevelValues == null) { typeLevelValues = new HashMap<String, Object>(); values.put(declaringTypeName, typeLevelValues); } typeLevelValues.put(name, result); return result; } } if (result != null) { result = Utils.checkCompatibility(rtype.getTypeRegistry(), result, fieldmember.getDescriptor()); if (result == null) { typeLevelValues.remove(fieldmember.getName()); } } result = Utils.toResultCheckIfNull(result, fieldmember.getDescriptor()); } // if (GlobalConfiguration.isRuntimeLogging && log.isLoggable(Level.FINER)) { // log.finer("<getValue() value of " + name + " is " + result); // } return result; } // TODO ensure can't set field values on interfaces (constants)? (guess we should never encounter the code that tries it) public void setValue(ReloadableType rtype, Object newValue, String name) throws IllegalAccessException { if (GlobalConfiguration.isRuntimeLogging && log.isLoggable(Level.FINEST)) { log.finest("Static field set: " + rtype.getName() + "." + name + " " + newValue); } // TODO can setValue for static fields ignore interfaces? since we know all those fields will be final - what about the clinit calls to setup the fields though? FieldMember fieldmember = rtype.findStaticField(name);//InstanceField(name); if (fieldmember == null) { // If the field is null, there are two possible reasons: // 1. The field does not exist in the hierarchy at all // 2. The field is on a type just above our topmost reloadable type FieldReaderWriter frw = rtype.locateField(name); if (frw == null) { // bad code redeployed? log.info("Unexpectedly unable to locate static field " + name + " starting from type " + rtype.dottedtypename + ": clinit running late?"); return; } frw.setStaticFieldValue(rtype.getClazz(), newValue, this); } else { if (!fieldmember.isStatic()) { throw new IncompatibleClassChangeError("Expected static field " + rtype.dottedtypename + "." + fieldmember.getName()); } Map<String, Object> typeValues = values.get(fieldmember.getDeclaringTypeName());//rtype.getName()); if (typeValues == null) { typeValues = new HashMap<String, Object>(); values.put(fieldmember.getDeclaringTypeName(), typeValues); } typeValues.put(name, newValue); } } private String valuesToString() { StringBuilder s = new StringBuilder(); s.append("FieldAccessor:" + System.identityHashCode(this)).append("\n"); for (Map.Entry<String, Map<String, Object>> entry : values.entrySet()) { s.append("Type " + entry.getKey()).append("\n"); for (Map.Entry<String, Object> entry2 : entry.getValue().entrySet()) { s.append(" " + entry2.getKey() + "=" + entry2.getValue()).append("\n"); } } return s.toString(); } public String toString() { return valuesToString(); } Map<String, Map<String, Object>> getMap() { return values; } }