Package org.springsource.loaded.test

Source Code of org.springsource.loaded.test.FieldReloadingTests

/*
* 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.test;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;

import java.lang.reflect.InvocationTargetException;

import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.springsource.loaded.ISMgr;
import org.springsource.loaded.MethodInvokerRewriter;
import org.springsource.loaded.ReloadableType;
import org.springsource.loaded.TypeRegistry;
import org.springsource.loaded.test.infra.ClassPrinter;
import org.springsource.loaded.test.infra.Result;
import org.springsource.loaded.test.infra.ResultException;


/**
* Tests that change a type by modifying/adding/removing fields.
*
* @author Andy Clement
* @since 1.0
*/
public class FieldReloadingTests extends SpringLoadedTests {

  @Before
  public void setup() throws Exception {
    super.setup();
  }

  // Field 'i' is added to Add on a reload and interacted with
  @Test
  public void newFieldAdded() throws Exception {
    TypeRegistry r = getTypeRegistry("fields.Add");
    ReloadableType add = loadType(r, "fields.Add");
    Class<?> addClazz = add.getClazz();
    Object addInstance = addClazz.newInstance();
    assertEquals(0, runOnInstance(addClazz, addInstance, "getValue").returnValue);
    //    ClassPrinter.print(add.bytesLoaded, true);

    add.loadNewVersion("2", retrieveRename("fields.Add", "fields.Add002"));
    assertEquals(0, runOnInstance(addClazz, addInstance, "getValue").returnValue);

    runOnInstance(addClazz, addInstance, "setValue", 45);

    assertEquals(45, runOnInstance(addClazz, addInstance, "getValue").returnValue);
    assertEquals(45, add.getField(addInstance, "i", false));
  }
 
  // Variant of the first test but uses a new instance after reloading
  @Test
  public void newFieldAddedInstance() throws Exception {
    TypeRegistry r = getTypeRegistry("fields.Add");
    ReloadableType add = loadType(r, "fields.Add");
    Class<?> addClazz = add.getClazz();
    Object addInstance = addClazz.newInstance();
    assertEquals(0, runOnInstance(addClazz, addInstance, "getValue").returnValue);
    ClassPrinter.print(add.bytesLoaded, true);

    add.loadNewVersion("2", retrieveRename("fields.Add", "fields.Add002"));
    addInstance = addClazz.newInstance();
    assertEquals(0, runOnInstance(addClazz, addInstance, "getValue").returnValue);

    runOnInstance(addClazz, addInstance, "setValue", 45);

    assertEquals(45, runOnInstance(addClazz, addInstance, "getValue").returnValue);
    assertEquals(45, add.getField(addInstance, "i", false));
  }

  /**
   * Do an early set (via FieldReaderWriter) after a reload, so that no prior setup will have been done on the values map for that
   * type. This checks the logic in FieldReaderWriter.setValue()
   */
  @Test
  public void earlySet() throws Exception {
    String s = "fields.S";
    TypeRegistry r = getTypeRegistry(s);
    ReloadableType add = loadType(r, s);
    Class<?> addClazz = add.getClazz();
    Object addInstance = addClazz.newInstance();
    //    assertEquals(0, runOnInstance(addClazz, addInstance, "getValue").returnValue);

    add.loadNewVersion("2", retrieveRename(s, s + "2"));

    add.setField(addInstance, "i", false, 1234);
    assertEquals(1234, runOnInstance(addClazz, addInstance, "getValue").returnValue);

    //    runOnInstance(addClazz, addInstance, "setValue", 45);
    //
    //    assertEquals(45, runOnInstance(addClazz, addInstance, "getValue").returnValue);
    //    assertEquals(45, add.getField(addInstance, "i", false));
  }

  /**
   * Simple test working with double slotters - double and long
   */
  @Test
  public void doubleSlotFields() throws Exception {
    String d = "fields.TwoSlot";
    TypeRegistry r = getTypeRegistry(d);
    ReloadableType add = loadType(r, d);
    Class<?> clazz = add.getClazz();
    Object instance = clazz.newInstance();

    assertEquals(34.5, runOnInstance(clazz, instance, "getDouble").returnValue);
    assertEquals(123L, runOnInstance(clazz, instance, "getLong").returnValue);
    runOnInstance(clazz, instance, "setDouble", 1323d);
    runOnInstance(clazz, instance, "setLong", 23l);

    add.loadNewVersion("2", retrieveRename(d, d + "2"));

    assertEquals(0d, runOnInstance(clazz, instance, "getDouble").returnValue);
    assertEquals(0L, runOnInstance(clazz, instance, "getLong").returnValue);
    runOnInstance(clazz, instance, "setDouble", 1323d);
    runOnInstance(clazz, instance, "setLong", 23l);
    assertEquals(1323d, runOnInstance(clazz, instance, "getDouble").returnValue);
    assertEquals(23L, runOnInstance(clazz, instance, "getLong").returnValue);
  }

  /**
   * In this test there are some fields in a reloadable type shadowing some in a non-reloadable supertype. When the reloadable
   * ones are removed the supertype ones become visible, check they are correctly accessible.
   */
  @Test
  public void setWithFieldsRevealedOnReloadHierarchyNonStatic() throws Exception {
    //    String a = "fields.A"; // not reloadable!
    String b = "fields.B";
    String c = "fields.C";
    TypeRegistry r = getTypeRegistry(b + "," + c);
    ReloadableType btype = loadType(r, b);
    ReloadableType ctype = loadType(r, c);

    Class<?> cclazz = ctype.getClazz();
    Object cinstance = cclazz.newInstance();

    assertEquals(2, runOnInstance(cclazz, cinstance, "getInt").returnValue);
    assertEquals("fromB", runOnInstance(cclazz, cinstance, "getString").returnValue);
    runOnInstance(cclazz, cinstance, "setInt", 22);
    runOnInstance(cclazz, cinstance, "setString", "fromBfromB");
    assertEquals(22, runOnInstance(cclazz, cinstance, "getInt").returnValue);
    assertEquals("fromBfromB", runOnInstance(cclazz, cinstance, "getString").returnValue);
    ctype.setField(cinstance, "i", false, 345);
    assertEquals(345, runOnInstance(cclazz, cinstance, "getInt").returnValue);
    assertEquals(345, ctype.getField(cinstance, "i", false));

    btype.loadNewVersion("2", retrieveRename(b, b + "2"));

    assertEquals(1, runOnInstance(cclazz, cinstance, "getInt").returnValue);
    assertEquals("fromA", runOnInstance(cclazz, cinstance, "getString").returnValue);
    runOnInstance(cclazz, cinstance, "setInt", 22);
    runOnInstance(cclazz, cinstance, "setString", "fromBfromB");
    assertEquals(22, runOnInstance(cclazz, cinstance, "getInt").returnValue);
    assertEquals("fromBfromB", runOnInstance(cclazz, cinstance, "getString").returnValue);
  }

  /**
   * In this test there are some fields in a reloadable type shadowing some in a non-reloadable supertype. When the reloadable
   * ones are removed the supertype ones become visible, check they are correctly accessible.
   */
  @Test
  public void setWithFieldsRevealedOnReloadHierarchyStatic() throws Exception {
    //    String a = "fields.A"; // not reloadable!
    String b = "fields.Ba";
    String c = "fields.Ca";
    TypeRegistry r = getTypeRegistry(b + "," + c);
    ReloadableType btype = loadType(r, b);
    ReloadableType ctype = loadType(r, c);

    Class<?> cclazz = ctype.getClazz();
    Object cinstance = cclazz.newInstance();

    assertEquals(2, runOnInstance(cclazz, cinstance, "getInt").returnValue);
    assertEquals("fromB", runOnInstance(cclazz, cinstance, "getString").returnValue);
    runOnInstance(cclazz, cinstance, "setInt", 22);
    runOnInstance(cclazz, cinstance, "setString", "fromBfromB");
    assertEquals(22, runOnInstance(cclazz, cinstance, "getInt").returnValue);
    assertEquals("fromBfromB", runOnInstance(cclazz, cinstance, "getString").returnValue);
    ctype.setField(null, "i", true, 11111);
    assertEquals(11111, ctype.getField(null, "i", true));
    assertEquals(11111, runOnInstance(cclazz, cinstance, "getInt").returnValue);

    btype.loadNewVersion("2", retrieveRename(b, b + "2"));

    assertEquals(1, runOnInstance(cclazz, cinstance, "getInt").returnValue);
    assertEquals("fromA", runOnInstance(cclazz, cinstance, "getString").returnValue);
    runOnInstance(cclazz, cinstance, "setInt", 22);
    runOnInstance(cclazz, cinstance, "setString", "fromBfromB");
    assertEquals(22, runOnInstance(cclazz, cinstance, "getInt").returnValue);
    assertEquals("fromBfromB", runOnInstance(cclazz, cinstance, "getString").returnValue);
    ctype.setField(null, "i", true, 12221);
    assertEquals(12221, ctype.getField(null, "i", true));
    assertEquals(12221, runOnInstance(cclazz, cinstance, "getInt").returnValue);
  }

  // All the other primitive field types
  @Test
  public void newFieldAdded2() throws Exception {
    TypeRegistry r = getTypeRegistry("fields.AddB");
    ReloadableType add = loadType(r, "fields.AddB");
    Class<?> addClazz = add.getClazz();
    Object addInstance = addClazz.newInstance();
    assertEquals((short) 0, runOnInstance(addClazz, addInstance, "getShort").returnValue);
    assertEquals(0L, runOnInstance(addClazz, addInstance, "getLong").returnValue);
    assertEquals(false, runOnInstance(addClazz, addInstance, "getBoolean").returnValue);
    assertEquals('a', runOnInstance(addClazz, addInstance, "getChar").returnValue);
    assertEquals(0d, runOnInstance(addClazz, addInstance, "getDouble").returnValue);
    assertEquals(0f, runOnInstance(addClazz, addInstance, "getFloat").returnValue);

    add.loadNewVersion("2", retrieveRename("fields.AddB", "fields.AddB002"));

    assertEquals((short) 0, runOnInstance(addClazz, addInstance, "getShort").returnValue);
    assertEquals(0L, runOnInstance(addClazz, addInstance, "getLong").returnValue);
    assertEquals(false, runOnInstance(addClazz, addInstance, "getBoolean").returnValue);
    assertEquals((char) 0, runOnInstance(addClazz, addInstance, "getChar").returnValue);
    assertEquals(0d, runOnInstance(addClazz, addInstance, "getDouble").returnValue);
    assertEquals(0f, runOnInstance(addClazz, addInstance, "getFloat").returnValue);

    runOnInstance(addClazz, addInstance, "setShort", (short) 451);
    runOnInstance(addClazz, addInstance, "setLong", 328L);
    runOnInstance(addClazz, addInstance, "setBoolean", true);
    runOnInstance(addClazz, addInstance, "setChar", 'z');
    runOnInstance(addClazz, addInstance, "setDouble", 2222d);
    runOnInstance(addClazz, addInstance, "setFloat", 12f);

    assertEquals((short) 451, runOnInstance(addClazz, addInstance, "getShort").returnValue);
    assertEquals(328L, runOnInstance(addClazz, addInstance, "getLong").returnValue);
    assertEquals(true, runOnInstance(addClazz, addInstance, "getBoolean").returnValue);
    assertEquals('z', runOnInstance(addClazz, addInstance, "getChar").returnValue);
    assertEquals(2222d, runOnInstance(addClazz, addInstance, "getDouble").returnValue);
    assertEquals(12f, runOnInstance(addClazz, addInstance, "getFloat").returnValue);
  }

  //  public void fieldRemoved() throws Exception {
  //    TypeRegistry r = getTypeRegistry("fields.Removed");
  //    ReloadableType add = loadType(r, "fields.Removed");
  //    Class<?> addClazz = add.getClazz();
  //    Object addInstance = addClazz.newInstance();
  //    assertEquals(0, runOnInstance(addClazz, addInstance, "getValue").returnValue);
  //    runOnInstance(addClazz, addInstance, "setValue", 45);
  //    assertEquals(45, runOnInstance(addClazz, addInstance, "getValue").returnValue);
  //
  //    add.loadNewVersion("2", retrieveRename("fields.Removed", "fields.Removed002"));
  //
  //    assertEquals(0, runOnInstance(addClazz, addInstance, "getValue").returnValue);
  //
  //    runOnInstance(addClazz, addInstance, "setValue", 45);
  //
  //    assertEquals(45, runOnInstance(addClazz, addInstance, "getValue").returnValue);
  //  }

  //  @Test
  //  public void fieldOverloading() throws Exception {
  //    TypeRegistry r = getTypeRegistry("fields..*");
  //
  //    ReloadableType one = loadType(r, "fields.One");
  //    ReloadableType two = loadType(r, "fields.Two");
  //
  //    Class<?> oneClazz = one.getClazz();
  //    Object oneInstance = oneClazz.newInstance();
  //    Class<?> twoClazz = two.getClazz();
  //    Object twoInstance = twoClazz.newInstance();
  //
  //    // Field 'a' is only defined in One and 'inherited' by Two
  //    assertEquals("a from One", runOnInstance(oneClazz, oneInstance, "getOneA").returnValue);
  //    assertEquals("a from One", runOnInstance(twoClazz, twoInstance, "getTwoA").returnValue);
  //
  //    runOnInstance(oneClazz, oneInstance, "setOneA", "abcde");
  //    assertEquals("abcde", runOnInstance(oneClazz, oneInstance, "getOneA").returnValue);
  //
  //    runOnInstance(twoClazz, twoInstance, "setOneA", "abcde");
  //    assertEquals("abcde", runOnInstance(twoClazz, twoInstance, "getTwoA").returnValue);
  //
  //    // Field 'b' is defined in One and Two
  //    assertEquals("b from One", runOnInstance(oneClazz, oneInstance, "getOneB").returnValue);
  //    assertEquals("b from Two", runOnInstance(twoClazz, twoInstance, "getTwoB").returnValue);
  //
  //    // Field 'c' is private in One and public in Two
  //    assertEquals("c from One", runOnInstance(oneClazz, oneInstance, "getOneC").returnValue);
  //    assertEquals("c from Two", runOnInstance(twoClazz, twoInstance, "getTwoC").returnValue);
  //
  //    // Now... set the private field 'c' in One then try to access the field c in both One and Two
  //    // Should be different if the FieldAccessor is preserving things correctly
  //    runOnInstance(twoClazz, twoInstance, "setOneC", "abcde");
  //    assertEquals("abcde", runOnInstance(twoClazz, twoInstance, "getOneC").returnValue);
  //    assertEquals("c from Two", runOnInstance(twoClazz, twoInstance, "getTwoC").returnValue);
  //  }
  //
  //  @Test
  //  public void invokevirtual() throws Exception {
  //    TypeRegistry typeRegistry = getTypeRegistry("virtual.CalleeOne");
  //
  //    // The first target does not define toString()
  //    ReloadableType target = typeRegistry.addType("virtual.CalleeOne", loadBytesForClass("virtual.CalleeOne"));
  //
  //    Class<?> callerClazz = loadit("virtual.CallerOne", loadBytesForClass("virtual.CallerOne"));
  //
  //    // Run the initial version which does not define toString()
  //    Result result = runUnguarded(callerClazz, "run");
  //    // something like virtual.CalleeOne@4cee32
  //    assertTrue(((String) result.returnValue).startsWith("virtual.CalleeOne@"));
  //
  //    // Load a version that does define toString()
  //    target.loadNewVersion("002", retrieveRename("virtual.CalleeOne", "virtual.CalleeOne002"));
  //    result = runUnguarded(callerClazz, "run");
  //    assertEquals("abcd", result.returnValue);
  //
  //    // Load a version that does not define toString()
  //    target.loadNewVersion("003", retrieveRename("virtual.CalleeOne", "virtual.CalleeOne003"));
  //    result = runUnguarded(callerClazz, "run");
  //    // something like virtual.CalleeOne@4cee32
  //    assertTrue(((String) result.returnValue).startsWith("virtual.CalleeOne@"));
  //  }

  // TODO [perf][crit] reduce boxing by having variants of set/get for all primitives and calling them
  // instead of the generic Object one checking fields ok after rewriting
  @Test
  public void intArrayFieldAccessing() throws Exception {
    TypeRegistry typeRegistry = TypeRegistry.getTypeRegistryFor(binLoader);
    // Configure it directly such that data.Pear is considered reloadable
    configureForTesting(typeRegistry, "data.Pear");
    //    ReloadableType pear =
    typeRegistry.addType("data.Pear", loadBytesForClass("data.Pear"));

    byte[] callerbytes = loadBytesForClass("data.Banana");

    byte[] rewrittenBytes = MethodInvokerRewriter.rewrite(typeRegistry, callerbytes);
    Class<?> callerClazz = loadit("data.Banana", rewrittenBytes);
    Object o = callerClazz.newInstance();

    Result result = runOnInstance(callerClazz, o, "getIntArrayField");
    System.out.println(result);
    Assert.assertTrue(result.returnValue instanceof int[]);
    Assert.assertEquals(3, ((int[]) result.returnValue)[1]);

    result = runOnInstance(callerClazz, o, "setIntArrayField", new Object[] { new int[] { 44, 55, 66 } });

    result = runOnInstance(callerClazz, o, "getIntArrayField");
    Assert.assertTrue(result.returnValue instanceof int[]);
    Assert.assertEquals(55, ((int[]) result.returnValue)[1]);
  }

  @Test
  public void staticIntArrayFieldAccessing() throws Exception {
    TypeRegistry typeRegistry = TypeRegistry.getTypeRegistryFor(binLoader);
    // Configure it directly such that data.Pear is considered reloadable
    configureForTesting(typeRegistry, "data.Pear");
    typeRegistry.addType("data.Pear", loadBytesForClass("data.Pear"));

    byte[] callerbytes = loadBytesForClass("data.Banana");

    byte[] rewrittenBytes = MethodInvokerRewriter.rewrite(typeRegistry, callerbytes);
    Class<?> callerClazz = loadit("data.Banana", rewrittenBytes);
    Object o = callerClazz.newInstance();

    Result result = runOnInstance(callerClazz, o, "getStaticIntArrayField");
    Assert.assertTrue(result.returnValue instanceof int[]);
    Assert.assertEquals(33, ((int[]) result.returnValue)[1]);

    result = runOnInstance(callerClazz, o, "setStaticIntArrayField", new Object[] { new int[] { 44, 55, 66 } });

    result = runOnInstance(callerClazz, o, "getStaticIntArrayField");
    Assert.assertTrue(result.returnValue instanceof int[]);
    Assert.assertEquals(55, ((int[]) result.returnValue)[1]);
  }

  // The problem with default visibility fields is that the executors cannot see them.  So we create accessors for
  // these fields in the target types.  If two types in a hierarchy declare the same field, then these accessor methods will be in an
  // override relationship.  The overriding accessors will be subject to virtual dispatch - but the runtime type should be
  // correct so that the right one is called.
  @Test
  public void defaultFields() throws Exception {
    TypeRegistry tr = getTypeRegistry("accessors..*");

    ReloadableType top = tr.addType("accessors.DefaultFields", loadBytesForClass("accessors.DefaultFields"));
    ReloadableType bottom = tr.addType("accessors.DefaultFieldsSub", loadBytesForClass("accessors.DefaultFieldsSub"));

    ClassPrinter.print(top.bytesLoaded);
    Object topInstance = top.getClazz().newInstance();
    Result result = runOnInstance(top.getClazz(), topInstance, "a");
    assertEquals(1, result.returnValue);

    Object botInstance = bottom.getClazz().newInstance();
    result = runOnInstance(bottom.getClazz(), botInstance, "a");
    assertEquals(1, result.returnValue);

    result = runOnInstance(bottom.getClazz(), botInstance, "b");
    assertEquals(2, result.returnValue);
  }

  // Tests allowing for inherited fields from a non-reloadable type.  Reload a new version of the class
  // that shadows the method in the non-reloadable type.
  @Test
  public void inheritedNonReloadable1() throws Exception {
    String top = "fields.ReloadableTop";
    TypeRegistry tr = getTypeRegistry(top);
    ReloadableType rTop = tr.addType(top, loadBytesForClass(top));

    Class<?> cTop = rTop.getClazz();
    Object iTop = cTop.newInstance();

    // The field 'I' is in code that is not reloadable.

    // First lets use the accessors that are also not in reloadable code
    assertEquals(5, runOnInstance(cTop, iTop, "getI_NotReloadable").returnValue);
    runOnInstance(cTop, iTop, "setI_ReloadableTop", 4);
    assertEquals(4, runOnInstance(cTop, iTop, "getI_NotReloadable").returnValue);

    // Now use accessors that are in reloadable code
    assertEquals(4, runOnInstance(cTop, iTop, "getI_ReloadableTop").returnValue);
    runOnInstance(cTop, iTop, "setI_ReloadableTop", 3);
    assertEquals(3, runOnInstance(cTop, iTop, "getI_ReloadableTop").returnValue);

    // Now reload the ReloadableTop class and introduce that field i which will shadow the one in the supertype
    rTop.loadNewVersion("2", retrieveRename(top, top + "002"));
    assertEquals(0, runOnInstance(cTop, iTop, "getI_ReloadableTop").returnValue);
    runOnInstance(cTop, iTop, "setI_ReloadableTop", 44);
    assertEquals(44, runOnInstance(cTop, iTop, "getI_ReloadableTop").returnValue);

    // Now access the older version of the field still there on the non-reloadable type
    assertEquals(3, runOnInstance(cTop, iTop, "getI_NotReloadable").returnValue);
  }

  // Now a pair of types with one field initially.  New field added - original should continue to be accessed
  // through the GETFIELD, the new one via the map
  @Test
  public void inheritedNonReloadable2() throws Exception {
    String top = "fields.X";
    TypeRegistry tr = getTypeRegistry(top);
    ReloadableType rTop = tr.addType(top, loadBytesForClass(top));

    Class<?> cTop = rTop.getClazz();
    Object iTop = cTop.newInstance();

    assertEquals(5, runOnInstance(cTop, iTop, "getI").returnValue);
    runOnInstance(cTop, iTop, "setI", 4);
    assertEquals(4, runOnInstance(cTop, iTop, "getI").returnValue);

    //    // Now reload the ReloadableTop class and introduce that field i which will shadow the one in the supertype
    rTop.loadNewVersion("2", retrieveRename(top, top + "002"));

    assertEquals(0, runOnInstance(cTop, iTop, "getJ").returnValue);
    runOnInstance(cTop, iTop, "setJ", 44);
    assertEquals(44, runOnInstance(cTop, iTop, "getJ").returnValue);

    assertEquals(4, runOnInstance(cTop, iTop, "getI").returnValue);

    ISMgr fa = getFieldAccessor(iTop);
    assertContains("j=44", fa.toString());
    assertDoesNotContain("i=", fa.toString());
  }

  @Test
  public void changingFieldFromNonstaticToStatic() throws Exception {
    String y = "fields.Y";
    TypeRegistry tr = getTypeRegistry(y);
    ReloadableType rtype = tr.addType(y, loadBytesForClass(y));

    Class<?> clazz = rtype.getClazz();
    Object instance = clazz.newInstance();
    Object instance2 = clazz.newInstance();

    assertEquals(5, runOnInstance(clazz, instance, "getJ").returnValue);
    runOnInstance(clazz, instance, "setJ", 4);
    assertEquals(4, runOnInstance(clazz, instance, "getJ").returnValue);

    rtype.loadNewVersion("2", retrieveRename(y, y + "002"));

    runOnInstance(clazz, instance, "setJ", 4);
    assertEquals(4, runOnInstance(clazz, instance, "getJ").returnValue);

    String staticFieldMapData = getStaticFieldsMap(clazz);
    assertContains("j=4", staticFieldMapData);

    runOnInstance(clazz, instance2, "setJ", 404);
    assertEquals(404, runOnInstance(clazz, instance, "getJ").returnValue);
    assertEquals(404, runOnInstance(clazz, instance2, "getJ").returnValue);
  }

  /**
   * An instance field is accessed from a subtype, then the supertype is reloaded where the field has been made static. Should be
   * an error when the subtype attempts to access it again.
   */
  @Test
  public void changingFieldFromNonstaticToStaticWithSubtypes() throws Exception {
    String y = "fields.Ya";
    String z = "fields.Za";
    TypeRegistry tr = getTypeRegistry(y + "," + z);
    ReloadableType rtype = tr.addType(y, loadBytesForClass(y));
    ReloadableType ztype = tr.addType(z, loadBytesForClass(z));

    Class<?> clazz = ztype.getClazz();
    Object instance = clazz.newInstance();

    assertEquals(5, runOnInstance(clazz, instance, "getJ").returnValue);
    runOnInstance(clazz, instance, "setJ", 4);
    assertEquals(4, runOnInstance(clazz, instance, "getJ").returnValue);

    rtype.loadNewVersion("2", retrieveRename(y, y + "002"));

    try {
      runOnInstance(clazz, instance, "setJ", 4);
      fail("should not have worked, field has changed from non-static to static");
    } catch (ResultException re) {
      assertTrue(re.getCause() instanceof InvocationTargetException);
      assertTrue(re.getCause().getCause() instanceof IncompatibleClassChangeError);
      assertEquals("Expected non-static field fields.Za.j", re.getCause().getCause().getMessage());
    }

    // Now should be an IncompatibleClassChangeError
    try {
      runOnInstance(clazz, instance, "getJ");
      fail("should not have worked, field has changed from non-static to static");
    } catch (ResultException re) {
      assertTrue(re.getCause() instanceof InvocationTargetException);
      assertTrue(re.getCause().getCause() instanceof IncompatibleClassChangeError);
      assertEquals("Expected non-static field fields.Za.j", re.getCause().getCause().getMessage());
    }

  }

  /**
   * A static field is accessed from a subtype, then the supertype is reloaded where the field has been made non-static. Should be
   * an error when the subtype attempts to access it again.
   */
  @Test
  public void changingFieldFromStaticToNonstaticWithSubtypes() throws Exception {
    String y = "fields.Yb";
    String z = "fields.Zb";
    TypeRegistry tr = getTypeRegistry(y + "," + z);
    ReloadableType rtype = tr.addType(y, loadBytesForClass(y));
    ReloadableType ztype = tr.addType(z, loadBytesForClass(z));

    Class<?> clazz = ztype.getClazz();
    Object instance = clazz.newInstance();

    assertEquals(5, runOnInstance(clazz, instance, "getJ").returnValue);
    runOnInstance(clazz, instance, "setJ", 4);
    assertEquals(4, runOnInstance(clazz, instance, "getJ").returnValue);

    rtype.loadNewVersion("2", retrieveRename(y, y + "002"));

    // Now should be an IncompatibleClassChangeError
    try {
      runOnInstance(clazz, instance, "setJ", 4);
      fail("Should not have worked, field has changed from static to non-static");
    } catch (ResultException re) {
      assertTrue(re.getCause() instanceof InvocationTargetException);
      assertTrue(re.getCause().getCause() instanceof IncompatibleClassChangeError);
      // When compiled with AspectJ vs Eclipse JDT the GETSTATIC actually varies.
      // With AspectJ it is: PUTSTATIC fields/Yb.j : I
      // With JDT (4.3) it is: PUTSTATIC fields/Zb.j : I
      // hence the error is different
      assertEquals("Expected static field fields.Zb.j", re.getCause().getCause().getMessage());
    }

    // Now should be an IncompatibleClassChangeError
    try {
      runOnInstance(clazz, instance, "getJ");
      fail("Should not have worked, field has changed from static to non-static");
    } catch (ResultException re) {
      assertTrue(re.getCause() instanceof InvocationTargetException);
      assertTrue(re.getCause().getCause() instanceof IncompatibleClassChangeError);
      // When compiled with AspectJ vs Eclipse JDT the GETSTATIC actually varies.
      // With AspectJ it is: GETSTATIC fields/Yb.j : I
      // With JDT (4.3) it is: GETSTATIC fields/Zb.j : I
      // hence the error is different
      assertEquals("Expected static field fields.Zb.j", re.getCause().getCause().getMessage());
    }
  }

  /**
   * Load a hierarchy of types. There is a field 'i' in both P and Q. R returns 'i'. Initially it returns Q.i but once Q has been
   * reloaded it should be returning P.i
   */
  @Test
  public void removingFieldsShadowingSuperFields() throws Exception {
    String p = "fields.P";
    String q = "fields.Q";
    String r = "fields.R";
    TypeRegistry tr = getTypeRegistry(p + "," + q + "," + r);
    //    ReloadableType ptype =
    tr.addType(p, loadBytesForClass(p));
    ReloadableType qtype = tr.addType(q, loadBytesForClass(q));
    ReloadableType rtype = tr.addType(r, loadBytesForClass(r));

    Class<?> rClazz = rtype.getClazz();
    Object rInstance = rClazz.newInstance();

    assertEquals(2, runOnInstance(rClazz, rInstance, "getI").returnValue);

    qtype.loadNewVersion("2", retrieveRename(q, q + "2"));

    assertEquals(1, runOnInstance(rClazz, rInstance, "getI").returnValue);
  }

  /**
   * Similar to previous but now fields.Pa is not reloadable.
   */
  @Test
  public void removingFieldsShadowingSuperFields2() throws Exception {
    //    String p = "fields.Pa";
    String q = "fields.Qa";
    String r = "fields.Ra";
    TypeRegistry tr = getTypeRegistry(q + "," + r);
    //    ReloadableType ptype = tr.addType(p, loadBytesForClass(p));
    ReloadableType qtype = tr.addType(q, loadBytesForClass(q));
    ReloadableType rtype = tr.addType(r, loadBytesForClass(r));

    Class<?> rClazz = rtype.getClazz();
    Object rInstance = rClazz.newInstance();

    assertEquals(2, runOnInstance(rClazz, rInstance, "getI").returnValue);

    qtype.loadNewVersion("2", retrieveRename(q, q + "2"));

    assertEquals(1, runOnInstance(rClazz, rInstance, "getI").returnValue);
  }

  /**
   * Here the field in Qc is shadowing (same name) a field in the interface. Once the new version of Qc is loaded that doesn't
   * define the field, this exposes the field in the interface but it is static and as we are running a GETFIELD operation in Rc,
   * it is illegal (IncompatibleClassChangeError)
   */
  @Test
  public void removingFieldsShadowingInterfaceFields1() throws Exception {
    //    String i = "fields.Ic";
    String q = "fields.Qc";
    String r = "fields.Rc";
    TypeRegistry tr = getTypeRegistry(q + "," + r);
    ReloadableType qtype = tr.addType(q, loadBytesForClass(q));
    ReloadableType rtype = tr.addType(r, loadBytesForClass(r));

    Class<?> rClazz = rtype.getClazz();
    Object rInstance = rClazz.newInstance();

    assertEquals(2, runOnInstance(rClazz, rInstance, "getNumber").returnValue);

    qtype.loadNewVersion("2", retrieveRename(q, q + "2"));

    try {
      runOnInstance(rClazz, rInstance, "getNumber");
      fail();
    } catch (ResultException re) {
      Throwable e = (re).getCause();
      assertTrue(e.getCause() instanceof IncompatibleClassChangeError);
      assertEquals("Expected non-static field fields.Rc.number", e.getCause().getMessage());
    }
  }

  /**
   * Both 'normal' reloadable field access and reflective field access will use the same set methods on ReloadableType. In the
   * reflection case the right FieldAccessor must be discovered, in the normal case it is passed in. This test checks that using
   * either route behaves - exercising the method directly and not through reflection.
   */
  @Test
  public void accessingFieldsThroughReloadableType() throws Exception {
    String p = "fields.P";
    String q = "fields.Q";
    String r = "fields.R";
    TypeRegistry tr = getTypeRegistry(p + "," + q + "," + r);
    ReloadableType ptype = tr.addType(p, loadBytesForClass(p));
    ReloadableType qtype = tr.addType(q, loadBytesForClass(q));
    ReloadableType rtype = tr.addType(r, loadBytesForClass(r));

    Class<?> rClazz = rtype.getClazz();
    Object rInstance = rClazz.newInstance();

    // Before reloading, check both routes get to the same field:
    assertEquals(2, runOnInstance(rClazz, rInstance, "getI").returnValue);
    assertEquals(2, rtype.getField(rInstance, "i", false));

    // Now mess with it via one route and check result via the other:
    rtype.setField(rInstance, "i", false, 44);
    assertEquals(44, runOnInstance(rClazz, rInstance, "getI").returnValue);

    qtype.loadNewVersion("2", retrieveRename(q, q + "2"));

    // After reloading, check both routes get to the same field:
    assertEquals(1, runOnInstance(rClazz, rInstance, "getI").returnValue);
    assertEquals(1, ptype.getField(rInstance, "i", false));

    // Now mess with it via one route and check result via the other:
    rtype.setField(rInstance, "i", false, 357);
    assertEquals(357, runOnInstance(rClazz, rInstance, "getI").returnValue);
  }

  /**
   * Fields are changed from int>String and String>int. When this happens we should remove the int value we have and treat the
   * field as unset.
   */
  @Test
  public void changingFieldType() throws Exception {
    String p = "fields.T";
    TypeRegistry tr = getTypeRegistry(p);
    ReloadableType type = tr.addType(p, loadBytesForClass(p));
    Class<?> rClazz = type.getClazz();
    Object rInstance = rClazz.newInstance();

    // Before reloading, check both routes get to the same field
    assertEquals(345, runOnInstance(rClazz, rInstance, "getI").returnValue);
    assertEquals(345, type.getField(rInstance, "i", false));

    assertEquals("stringValue", runOnInstance(rClazz, rInstance, "getJ").returnValue);
    assertEquals("stringValue", type.getField(rInstance, "j", false));

    type.loadNewVersion("2", retrieveRename(p, p + "2"));

    // On the reload the field changed type, so we now check it was reset to default value
    assertEquals(null, runOnInstance(rClazz, rInstance, "getI").returnValue);
    assertEquals(null, type.getField(rInstance, "i", false));

    assertEquals(0, runOnInstance(rClazz, rInstance, "getJ").returnValue);
    assertEquals(0, type.getField(rInstance, "j", false));
  }

  /**
   * Two types in a hierarchy, the field is initially accessed in the supertype but an interface that the subtype implements then
   * introduces that field on a reload. Although it is introduced in the interface, the bytecode reference to the field is
   * directly to that in the supertype - so the interface one will not be found (the subtype would be pointing at the interface
   * one if B was recompiled with this setup).
   */
  @Test
  public void introducingStaticFieldInInterface() throws Exception {
    String a = "sfields.A";
    String b = "sfields.B";
    String i = "sfields.I";
    TypeRegistry tr = getTypeRegistry(a + "," + b + "," + i);
    ReloadableType itype = tr.addType(i, loadBytesForClass(i));
    ReloadableType atype = tr.addType(a, loadBytesForClass(a));
    ReloadableType btype = tr.addType(b, loadBytesForClass(b));

    Class<?> bClazz = btype.getClazz();
    Object bInstance = bClazz.newInstance();

    // No changes, should find i in the supertype A
    assertEquals(45, runOnInstance(bClazz, bInstance, "getI").returnValue);
    assertEquals(45, atype.getField(bInstance, "i", true));

    // Reload i which now has a definition of the field
    itype.loadNewVersion("2", retrieveRename(i, i + "2"));

    // Introduced in the interface but the instruction was GETSTATIC A.i so we won't find it on the interface
    // Even if we did find it on the interface the value would be zero because the staticinitializer for I2
    // has not run
    assertEquals(45, runOnInstance(bClazz, bInstance, "getI").returnValue);
    assertEquals(45, atype.getField(bInstance, "i", true));
  }

  /**
   * In this test several fields are setup and queried, then on reload their type is changed - ensure the results are as expected.
   */
  @Test
  public void testingCompatibilityOnFieldTypeChanges() throws Exception {
    String a = "fields.TypeChanging";
    String b = "fields.Middle";
    TypeRegistry tr = getTypeRegistry(a + "," + b);
    ReloadableType type = tr.addType(a, loadBytesForClass(a));
    tr.addType(b, loadBytesForClass(b));

    Class<?> clazz = type.getClazz();
    Object instance = clazz.newInstance();
    // Should be fine
    assertEquals("SubInstance", toString(runOnInstance(clazz, instance, "getSuper").returnValue));
    assertEquals(1, runOnInstance(clazz, instance, "getI").returnValue);
    assertEquals(34L, runOnInstance(clazz, instance, "getLong").returnValue);
    assertEquals(true, runOnInstance(clazz, instance, "getBoolean").returnValue);
    assertEquals((short) 32, runOnInstance(clazz, instance, "getShort").returnValue);
    assertEquals('a', runOnInstance(clazz, instance, "getChar").returnValue);
    assertEquals(2.0f, runOnInstance(clazz, instance, "getFloat").returnValue);
    assertEquals(3.141d, runOnInstance(clazz, instance, "getDouble").returnValue);
    assertEquals((byte) 0xff, runOnInstance(clazz, instance, "getByte").returnValue);
    assertEquals("{1,2,3}", toString(runOnInstance(clazz, instance, "getWasArray").returnValue));
    assertEquals("abc", toString(runOnInstance(clazz, instance, "getWasNotArray").returnValue));

    // This changes all the field types
    type.loadNewVersion("2", retrieveRename(a, a + "2"));

    assertEquals("SubInstance", toString(runOnInstance(clazz, instance, "getSuper").returnValue));
    assertEquals(1, runOnInstance(clazz, instance, "getI").returnValue);
    assertEquals(34L, runOnInstance(clazz, instance, "getLong").returnValue);
    assertEquals(true, runOnInstance(clazz, instance, "getBoolean").returnValue);
    assertEquals((short) 32, runOnInstance(clazz, instance, "getShort").returnValue);
    assertEquals('a', runOnInstance(clazz, instance, "getChar").returnValue);
    assertEquals(2.0f, runOnInstance(clazz, instance, "getFloat").returnValue);
    assertEquals(3.141d, runOnInstance(clazz, instance, "getDouble").returnValue);
    assertEquals((byte) 0xff, runOnInstance(clazz, instance, "getByte").returnValue);
    assertEquals("0", toString(runOnInstance(clazz, instance, "getWasArray").returnValue));
    assertEquals(null, toString(runOnInstance(clazz, instance, "getWasNotArray").returnValue));
  }

  public String toString(Object o) {
    if (o == null) {
      return null;
    }
    if (o instanceof int[]) {
      int[] is = (int[]) o;
      StringBuilder s = new StringBuilder();
      s.append("{");
      for (int i = 0; i < is.length; i++) {
        if (i > 0) {
          s.append(',');
        }
        s.append(is[i]);
      }
      s.append('}');
      return s.toString();
    } else {
      return o.toString();
    }
  }

  // bogus test - the inlining of constants means that second version of the class already returns the value...
  //  @Test
  //  public void newFieldOnInterface() throws Exception {
  //    String intface = "sfields.X";
  //    String impl = "sfields.Y";
  //    TypeRegistry tr = getTypeRegistry(intface + "," + impl);
  //    ReloadableType intType = tr.addType(intface, loadBytesForClass(intface));
  //    ReloadableType implType = tr.addType(impl, loadBytesForClass(impl));
  //
  //    Class<?> implClazz = implType.getClazz();
  //    Object implInstance = implClazz.newInstance();
  //
  //    assertEquals(34, runOnInstance(implClazz, implInstance, "getnum").returnValue);
  //
  //    intType.loadNewVersion("2", retrieveRename(intface, intface + "2"));
  //    implType.loadNewVersion("2", retrieveRename(impl, impl + "2", "sfields.X2:sfields.X"));
  //
  //    assertEquals(34, runOnInstance(implClazz, implInstance, "getnum").returnValue);
  //  }

  @Test
  public void modifyingStaticFieldsInHierarchy() throws Exception {
    String c = "sfields.C";
    String d = "sfields.D";
    String e = "sfields.E";
    TypeRegistry tr = getTypeRegistry(c + "," + d + "," + e);
    //    ReloadableType ctype =
    tr.addType(c, loadBytesForClass(c));
    ReloadableType dtype = tr.addType(d, loadBytesForClass(d));
    ReloadableType etype = tr.addType(e, loadBytesForClass(e));

    Class<?> eClazz = etype.getClazz();
    Object eInstance = eClazz.newInstance();

    // No changes, the field in D should be used
    assertEquals(4, runOnInstance(eClazz, eInstance, "getI").returnValue);
    assertEquals(4, etype.getField(null, "i", true));

    etype.setField(null, "i", true, 123);
    assertEquals(123, runOnInstance(eClazz, eInstance, "getI").returnValue);

    // Reload d which removes the field and makes the variant in C visible
    dtype.loadNewVersion("2", retrieveRename(d, d + "2"));

    assertEquals(3, runOnInstance(eClazz, eInstance, "getI").returnValue);
    assertEquals(3, etype.getField(null, "i", true));

    // Now interact with that field in C that has become visible
    etype.setField(null, "i", true, 12345);

    assertEquals(12345, runOnInstance(eClazz, eInstance, "getI").returnValue);
    assertEquals(12345, etype.getField(null, "i", true));

    // Use the setter
    runOnInstance(eClazz, eInstance, "setI", 6789);

    assertEquals(6789, runOnInstance(eClazz, eInstance, "getI").returnValue);
    assertEquals(6789, etype.getField(null, "i", true));
  }

  /**
   * Switch some fields from their primitive forms to their boxed forms, check data is preserved, then switch them back and check
   * data is preserved.
   */
  @Test
  public void switchToFromBoxing() throws Exception {
    String e = "fields.E";

    TypeRegistry tr = getTypeRegistry(e);
    ReloadableType type = tr.addType(e, loadBytesForClass(e));
    Class<?> clazz = type.getClazz();
    Object rInstance = clazz.newInstance();

    // Before reloading, check both routes get to the same field
    assertEquals(100, runOnInstance(clazz, rInstance, "getInt").returnValue);
    assertEquals(100, type.getField(rInstance, "i", false));
    assertEquals((short) 200, runOnInstance(clazz, rInstance, "getShort").returnValue);
    assertEquals((short) 200, type.getField(rInstance, "s", false));
    assertEquals(324L, runOnInstance(clazz, rInstance, "getLong").returnValue);
    assertEquals(324L, type.getField(rInstance, "j", false));
    assertEquals(2.5d, runOnInstance(clazz, rInstance, "getDouble").returnValue);
    assertEquals(2.5d, type.getField(rInstance, "d", false));
    assertEquals(true, runOnInstance(clazz, rInstance, "getBoolean").returnValue);
    assertEquals(true, type.getField(rInstance, "z", false));
    assertEquals(32f, runOnInstance(clazz, rInstance, "getFloat").returnValue);
    assertEquals(32f, type.getField(rInstance, "f", false));
    assertEquals('a', runOnInstance(clazz, rInstance, "getChar").returnValue);
    assertEquals('a', type.getField(rInstance, "c", false));
    assertEquals((byte) 255, runOnInstance(clazz, rInstance, "getByte").returnValue);
    assertEquals((byte) 255, type.getField(rInstance, "b", false));

    type.loadNewVersion("2", retrieveRename(e, e + "2"));

    // Now all boxed versions
    assertEquals(100, runOnInstance(clazz, rInstance, "getInt").returnValue);
    assertEquals(100, type.getField(rInstance, "i", false));
    assertEquals((short) 200, runOnInstance(clazz, rInstance, "getShort").returnValue);
    assertEquals((short) 200, type.getField(rInstance, "s", false));
    assertEquals(324L, runOnInstance(clazz, rInstance, "getLong").returnValue);
    assertEquals(324L, type.getField(rInstance, "j", false));
    assertEquals(2.5d, runOnInstance(clazz, rInstance, "getDouble").returnValue);
    assertEquals(2.5d, type.getField(rInstance, "d", false));
    assertEquals(true, runOnInstance(clazz, rInstance, "getBoolean").returnValue);
    assertEquals(true, type.getField(rInstance, "z", false));
    assertEquals(32f, runOnInstance(clazz, rInstance, "getFloat").returnValue);
    assertEquals(32f, type.getField(rInstance, "f", false));
    assertEquals('a', runOnInstance(clazz, rInstance, "getChar").returnValue);
    assertEquals('a', type.getField(rInstance, "c", false));
    assertEquals((byte) 255, runOnInstance(clazz, rInstance, "getByte").returnValue);
    assertEquals((byte) 255, type.getField(rInstance, "b", false));

    // revert to unboxed
    type.loadNewVersion("3", loadBytesForClass(e));

    assertEquals(100, runOnInstance(clazz, rInstance, "getInt").returnValue);
    assertEquals(100, type.getField(rInstance, "i", false));
    assertEquals((short) 200, runOnInstance(clazz, rInstance, "getShort").returnValue);
    assertEquals((short) 200, type.getField(rInstance, "s", false));
    assertEquals(324L, runOnInstance(clazz, rInstance, "getLong").returnValue);
    assertEquals(324L, type.getField(rInstance, "j", false));
    assertEquals(2.5d, runOnInstance(clazz, rInstance, "getDouble").returnValue);
    assertEquals(2.5d, type.getField(rInstance, "d", false));
    assertEquals(true, runOnInstance(clazz, rInstance, "getBoolean").returnValue);
    assertEquals(true, type.getField(rInstance, "z", false));
    assertEquals(32f, runOnInstance(clazz, rInstance, "getFloat").returnValue);
    assertEquals(32f, type.getField(rInstance, "f", false));
    assertEquals('a', runOnInstance(clazz, rInstance, "getChar").returnValue);
    assertEquals('a', type.getField(rInstance, "c", false));
    assertEquals((byte) 255, runOnInstance(clazz, rInstance, "getByte").returnValue);
    assertEquals((byte) 255, type.getField(rInstance, "b", false));
  }

  @Test
  public void ctorReloadWithNewField() throws Exception {
    String y = "ctors.JR";
    TypeRegistry tr = getTypeRegistry(y);
    ReloadableType rtype = tr.addType(y, loadBytesForClass(y));

    Class<?> clazz = rtype.getClazz();
    Object instance = runStaticUnguarded(clazz, "getInstance").returnValue;

    assertEquals("hello", runOnInstance(clazz, instance, "printMessage").returnValue);

    rtype.loadNewVersion("2", retrieveRename(y, y + "2"));

    assertEquals("goodbye", runOnInstance(clazz, instance, "printMessage").returnValue);
   
    Object instance2 = runStaticUnguarded(clazz,"getInstance").returnValue;
   
    Object ret = runOnInstance(clazz,instance2,"getFieldReflectively").returnValue;
    assertEquals(34,ret);

    ret = runOnInstance(clazz,instance2,"setFieldReflectively",99).returnValue;
    
    ret = runOnInstance(clazz,instance2,"getFieldReflectively").returnValue;
    assertEquals(99,ret);
  }
}
TOP

Related Classes of org.springsource.loaded.test.FieldReloadingTests

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.