/*
* 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 org.junit.Assert;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import org.springsource.loaded.ClassRenamer;
import org.springsource.loaded.ReloadableType;
import org.springsource.loaded.TypeDescriptor;
import org.springsource.loaded.TypeRegistry;
/**
* Checking the computation of catchers.
*
* @author Andy Clement
*/
@SuppressWarnings("unused")
public class CatcherTests extends SpringLoadedTests {
/*
* Details on catchers
*
* Four types of method in the super type to think about:
* - private
* - protected
* - default
* - public
*
* And things to keep in mind:
* - private methods are not overridable (invokespecial is used to call them)
* - visibility cannot be reduced, only widened
* - static methods are not overridable
*
* Catching rules:
* - don't need a catcher for a private method, there cannot be code out there that calls it with INVOKEVIRTUAL
* - visibility is preserved except for protected/default, which is widened to public - this enables the executor to call the
* catcher. Doesn't seem to have any side effects (doesn't limit the ability for an overriding method in a further
* subclass to have been declared initially protected).
*/
@Test
public void rewrite() throws Exception {
TypeRegistry typeRegistry = getTypeRegistry("catchers.B");
loadClass("catchers.A");
TypeDescriptor typeDescriptor = typeRegistry.getExtractor().extract(loadBytesForClass("catchers.B"), true);
checkDoesNotContain(typeDescriptor, "privateMethod");
checkDoesContain(typeDescriptor, "0x1 publicMethod");
checkDoesContain(typeDescriptor, "0x1 protectedMethod");
checkDoesContain(typeDescriptor, "0x1 defaultMethod");
ReloadableType rtype = typeRegistry.addType("catchers.B", loadBytesForClass("catchers.B"));
reload(rtype, "2");
}
/**
* Exercising the two codepaths for a catcher. The first 'run' will run the super version. The second 'run' will dispatch to our
* new implementation.
*/
@Test
public void exerciseCatcher() throws Exception {
TypeRegistry registry = getTypeRegistry("catchers..*");
String a = "catchers.A";
String b = "catchers.B";
ReloadableType rtypeA = registry.addType(a, loadBytesForClass(a));
ReloadableType rtypeB = registry.addType(b, loadBytesForClass(b));
Class<?> clazz = loadit("catchers.Runner", ClassRenamer.rename("catchers.Runner", loadBytesForClass("catchers.Runner")));
assertStartsWith("catchers.B@", runUnguarded(clazz, "runToString").returnValue);
Assert.assertEquals(65, runUnguarded(clazz, "runPublicMethod").returnValue);
Assert.assertEquals(23, runUnguarded(clazz, "runProtectedMethod").returnValue);
rtypeB.loadNewVersion("2", retrieveRename(b, b + "2"));
Assert.assertEquals("hey!", runUnguarded(clazz, "runToString").returnValue);
Assert.assertEquals(66, runUnguarded(clazz, "runPublicMethod").returnValue);
Assert.assertEquals(32, runUnguarded(clazz, "runProtectedMethod").returnValue);
// 27-Aug-2010 - typical catcher - TODO should we shorten some type names/method names to reduce class file size?
// METHOD: 0x0001(public) publicMethod()V
// CODE
// GETSTATIC catchers/B.r$typeLorg/springsource/loaded/ReloadableType;
// LDC 0
// INVOKEVIRTUAL org/springsource/loaded/ReloadableType.fetchLatestIfExists(I)Ljava/lang/Object;
// DUP
// IFNULL L0
// CHECKCAST catchers/B__I
// ALOAD 0
// INVOKEINTERFACE catchers/B__I.publicMethod(Lcatchers/B;)V
// RETURN
// L0
// POP
// ALOAD 0
// INVOKESPECIAL catchers/A.publicMethod()V
// RETURN
}
/**
* Now we work with a mixed hierarchy. Type X declares the methods, type Y extends X does not, type Z extends Y does.
*/
@Test
public void exerciseCatcher2() throws Exception {
TypeRegistry registry = getTypeRegistry("catchers..*");
String x = "catchers.X";
String y = "catchers.Y";
String z = "catchers.Z";
ReloadableType rtypeX = registry.addType(x, loadBytesForClass(x));
ReloadableType rtypeY = registry.addType(y, loadBytesForClass(y));
ReloadableType rtypeZ = registry.addType(z, loadBytesForClass(z));
Class<?> clazz = loadRunner("catchers.Runner2");
Assert.assertEquals(1, runUnguarded(clazz, "runPublicX").returnValue);
Assert.assertEquals(1, runUnguarded(clazz, "runPublicY").returnValue); // Y does not override
Assert.assertEquals(3, runUnguarded(clazz, "runPublicZ").returnValue);
Assert.assertEquals('a', runUnguarded(clazz, "runDefaultX").returnValue);
Assert.assertEquals('a', runUnguarded(clazz, "runDefaultY").returnValue); // Y does not override
Assert.assertEquals('c', runUnguarded(clazz, "runDefaultZ").returnValue);
Assert.assertEquals(100L, runUnguarded(clazz, "runProtectedX").returnValue);
Assert.assertEquals(100L, runUnguarded(clazz, "runProtectedY").returnValue); // Y does not override
Assert.assertEquals(300L, runUnguarded(clazz, "runProtectedZ").returnValue);
rtypeY.loadNewVersion("2", retrieveRename(y, y + "2"));
Assert.assertEquals(1, runUnguarded(clazz, "runPublicX").returnValue);
Assert.assertEquals(22, runUnguarded(clazz, "runPublicY").returnValue); // now Y does
Assert.assertEquals(3, runUnguarded(clazz, "runPublicZ").returnValue);
Assert.assertEquals('a', runUnguarded(clazz, "runDefaultX").returnValue);
Assert.assertEquals('B', runUnguarded(clazz, "runDefaultY").returnValue); // now Y does
Assert.assertEquals('c', runUnguarded(clazz, "runDefaultZ").returnValue);
// Runner2.runProtectedX invokes x.callProtectedMethod() which simply returns 'protectedMethod()'
Assert.assertEquals(100L, runUnguarded(clazz, "runProtectedX").returnValue);
Assert.assertEquals(200L, runUnguarded(clazz, "runProtectedY").returnValue); // now Y does
Assert.assertEquals(300L, runUnguarded(clazz, "runProtectedZ").returnValue);
}
// TODO are reloadings happening too frequently now that ctors will force them?
protected Class<?> loadRunner(String name) {
return loadit(name, loadBytesForClass(name));
}
}