blob: 4496609352238082a6bf30e84c82746df4adbc43 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.apache.felix.ipojo.manipulation;
import junit.framework.Assert;
import org.apache.felix.ipojo.InstanceManager;
import org.apache.felix.ipojo.Pojo;
import org.apache.felix.ipojo.metadata.Element;
import org.junit.Ignore;
import org.junit.Test;
import org.mockito.Mockito;
import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.concurrent.Callable;
import static junit.framework.Assert.assertEquals;
import static org.fest.assertions.Assertions.assertThat;
/**
* Checks the inner class manipulation
*/
public class InnerClassAdapterTest {
public static String baseClassDirectory = "target/test-classes/";
public static ManipulatedClassLoader manipulate(String className, Manipulator manipulator) throws IOException {
final File mainClassFile = new File(baseClassDirectory + className.replace(".", "/") + ".class");
byte[] bytecode = ManipulatorTest.getBytesFromFile(
mainClassFile);
// Preparation.
try {
manipulator.prepare(bytecode);
} catch (IOException e) {
Assert.fail("Cannot read " + className);
}
// Inner class preparation
for (String inner : manipulator.getInnerClasses()) {
// Get the bytecode and start manipulation
String resourcePath = inner + ".class";
byte[] innerClassBytecode;
try {
innerClassBytecode = ManipulatorTest.getBytesFromFile(new File(baseClassDirectory + resourcePath));
manipulator.prepareInnerClass(inner, innerClassBytecode);
} catch (IOException e) {
Assert.fail("Cannot find or analyze inner class '" + resourcePath + "'");
}
}
// Now manipulate the classes.
byte[] out = new byte[0];
try {
out = manipulator.manipulate(bytecode);
} catch (IOException e) {
Assert.fail("Cannot manipulate the class " + className + " : " + e.getMessage());
}
ManipulatedClassLoader classloader = new ManipulatedClassLoader(className, out);
// Visit inner classes
for (String inner : manipulator.getInnerClasses()) {
// Get the bytecode and start manipulation
String resourcePath = inner + ".class";
byte[] innerClassBytecode;
try {
innerClassBytecode = ManipulatorTest.getBytesFromFile(new File(baseClassDirectory + resourcePath));
byte[] manipulated = manipulator.manipulateInnerClass(inner, innerClassBytecode);
classloader.addInnerClass(inner.replace("/", "."), manipulated);
} catch (IOException e) {
Assert.fail("Cannot find inner class '" + resourcePath + "'");
}
}
// Lookup for all the other inner classes (not manipulated)
File[] files = mainClassFile.getParentFile().listFiles(new FilenameFilter() {
public boolean accept(File dir, String name) {
return name.startsWith(mainClassFile.getName().substring(0, mainClassFile.getName().length() -
".class".length()) + "$");
}
});
for (File f : files) {
String name = className + f.getName().substring(f.getName().indexOf("$"));
name = name.substring(0, name.length() - ".class".length());
byte[] innerClassBytecode = ManipulatorTest.getBytesFromFile(f);
classloader.addInnerClassIfNotAlreadyDefined(name, innerClassBytecode);
}
return classloader;
}
public static ManipulatedClassLoader manipulate(String className, Manipulator manipulator,
ManipulatedClassLoader initial) throws IOException {
byte[] bytecode = initial.get(className);
final File mainClassFile = new File(baseClassDirectory + className.replace(".", "/") + ".class");
String mainClass = className;
// Preparation.
try {
manipulator.prepare(bytecode);
} catch (IOException e) {
Assert.fail("Cannot read " + className);
}
// Inner class preparation
for (String inner : manipulator.getInnerClasses()) {
// Get the bytecode and start manipulation
String resourcePath = inner + ".class";
byte[] innerClassBytecode;
try {
innerClassBytecode = initial.get(inner.replace("/", "."));
manipulator.prepareInnerClass(inner, innerClassBytecode);
} catch (IOException e) {
Assert.fail("Cannot find or analyze inner class '" + resourcePath + "'");
}
}
// Now manipulate the classes.
byte[] out = new byte[0];
try {
out = manipulator.manipulate(bytecode);
} catch (IOException e) {
Assert.fail("Cannot manipulate the class " + className + " : " + e.getMessage());
}
ManipulatedClassLoader classloader = new ManipulatedClassLoader(className, out);
// Visit inner classes
for (String inner : manipulator.getInnerClasses()) {
// Get the bytecode and start manipulation
String resourcePath = inner + ".class";
byte[] innerClassBytecode;
try {
innerClassBytecode = initial.get(inner.replace("/", "."));
byte[] manipulated = manipulator.manipulateInnerClass(inner, innerClassBytecode);
classloader.addInnerClass(inner.replace("/", "."), manipulated);
} catch (IOException e) {
Assert.fail("Cannot find inner class '" + resourcePath + "'");
}
}
// Lookup for all the other inner classes (not manipulated)
File[] files = mainClassFile.getParentFile().listFiles(new FilenameFilter() {
public boolean accept(File dir, String name) {
return name.startsWith(mainClassFile.getName().substring(0, mainClassFile.getName().length() -
".class".length()) + "$");
}
});
for (File f : files) {
String name = className + f.getName().substring(f.getName().indexOf("$"));
name = name.substring(0, name.length() - ".class".length());
byte[] innerClassBytecode = ManipulatorTest.getBytesFromFile(f);
classloader.addInnerClassIfNotAlreadyDefined(name, innerClassBytecode);
}
return classloader;
}
private static Element getInnerClassMetadataByName(Element[] inners, String name) {
for (Element element : inners) {
if (name.equals(element.getAttribute("name"))) {
return element;
}
}
return null;
}
private static Element getMethodByName(Element[] methods, String name) {
for (Element element : methods) {
if (name.equals(element.getAttribute("name"))) {
return element;
}
}
return null;
}
@Test
public void testManipulatingTheInner() throws Exception {
Manipulator manipulator = new Manipulator(this.getClass().getClassLoader());
String className = "test.PojoWithInner";
byte[] origin = ManipulatorTest.getBytesFromFile(new File(baseClassDirectory + className.replace(".",
"/") + ".class"));
ManipulatedClassLoader classloader = manipulate(className, manipulator);
Class cl = classloader.findClass(className);
Assert.assertNotNull(cl);
Assert.assertNotNull(manipulator.getManipulationMetadata());
Assert.assertFalse(manipulator.getInnerClasses().isEmpty());
System.out.println(manipulator.getManipulationMetadata());
// The manipulation add stuff to the class.
Assert.assertTrue(classloader.get(className).length > origin.length);
boolean found = false;
Constructor cst = null;
Constructor[] csts = cl.getDeclaredConstructors();
for (Constructor cst2 : csts) {
System.out.println(Arrays.asList(cst2.getParameterTypes()));
if (cst2.getParameterTypes().length == 1 &&
cst2.getParameterTypes()[0].equals(InstanceManager.class)) {
found = true;
cst = cst2;
}
}
Assert.assertTrue(found);
// We still have the empty constructor
found = false;
csts = cl.getDeclaredConstructors();
for (Constructor cst1 : csts) {
System.out.println(Arrays.asList(cst1.getParameterTypes()));
if (cst1.getParameterTypes().length == 0) {
found = true;
}
}
Assert.assertTrue(found);
// Check the POJO interface
Assert.assertTrue(Arrays.asList(cl.getInterfaces()).contains(Pojo.class));
InstanceManager im = Mockito.mock(InstanceManager.class);
cst.setAccessible(true);
Object pojo = cst.newInstance(new Object[]{im});
Assert.assertNotNull(pojo);
Assert.assertTrue(pojo instanceof Pojo);
Method method = cl.getMethod("doSomething", new Class[0]);
Assert.assertTrue(((Boolean) method.invoke(pojo, new Object[0])).booleanValue());
}
@Test
public void testInnerClasses() throws IOException, ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
Manipulator manipulator = new Manipulator(this.getClass().getClassLoader());
String className = "test.inner.ComponentWithInnerClasses";
ManipulatedClassLoader classloader = manipulate(className, manipulator);
Class clazz = classloader.findClass(className);
Assert.assertNotNull(clazz);
Assert.assertNotNull(manipulator.getManipulationMetadata());
Assert.assertFalse(manipulator.getInnerClasses().isEmpty());
// We should have found only 2 inner classes.
assertThat(manipulator.getInnerClasses().size()).isEqualTo(3);
// Check that all inner classes are manipulated.
InstanceManager im = Mockito.mock(InstanceManager.class);
Constructor constructor = clazz.getDeclaredConstructor(InstanceManager.class);
constructor.setAccessible(true);
Object pojo = constructor.newInstance(im);
Assert.assertNotNull(pojo);
Assert.assertTrue(pojo instanceof Pojo);
Method method = clazz.getMethod("doSomething", new Class[0]);
String result = (String) method.invoke(pojo);
assertEquals(result, "foofoofoofoo");
}
@Test
public void testDoubleManipulation() throws IOException, ClassNotFoundException, NoSuchMethodException,
IllegalAccessException, InvocationTargetException, InstantiationException {
Manipulator manipulator = new Manipulator(this.getClass().getClassLoader());
String className = "test.inner.ComponentWithInnerClasses";
ManipulatedClassLoader classloader = manipulate(className, manipulator);
manipulator = new Manipulator(this.getClass().getClassLoader());
classloader = manipulate(className, manipulator, classloader);
Class clazz = classloader.findClass(className);
Assert.assertNotNull(clazz);
Assert.assertNotNull(manipulator.getManipulationMetadata());
Assert.assertFalse(manipulator.getInnerClasses().isEmpty());
// We should have found only 2 inner classes.
assertThat(manipulator.getInnerClasses().size()).isEqualTo(3);
// Check that all inner classes are manipulated.
InstanceManager im = Mockito.mock(InstanceManager.class);
Constructor constructor = clazz.getDeclaredConstructor(InstanceManager.class);
constructor.setAccessible(true);
Object pojo = constructor.newInstance(im);
Assert.assertNotNull(pojo);
Assert.assertTrue(pojo instanceof Pojo);
Method method = clazz.getMethod("doSomething", new Class[0]);
String result = (String) method.invoke(pojo);
assertEquals(result, "foofoofoofoo");
}
@Test
public void testThatManipulationMetadataContainsTheInnerClasses() throws IOException, ClassNotFoundException,
NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
Manipulator manipulator = new Manipulator(this.getClass().getClassLoader());
String className = "test.inner.ComponentWithInnerClasses";
manipulate(className, manipulator);
assertThat(manipulator.getInnerClasses().size()).isEqualTo(3);
Element manipulation = manipulator.getManipulationMetadata();
System.out.println(manipulation);
Element[] inners = manipulation.getElements("inner");
assertThat(inners.length).isEqualTo(3);
Element inner = getInnerClassMetadataByName(inners, "MyInnerWithANativeMethod");
assertThat(inner).isNotNull();
assertThat(getMethodByName(inner.getElements("method"), "foo")).isNotNull();
inner = getInnerClassMetadataByName(inners, "MyInnerClass");
assertThat(inner).isNotNull();
assertThat(getMethodByName(inner.getElements("method"), "foo")).isNotNull();
inner = getInnerClassMetadataByName(inners, "1");
assertThat(inner).isNotNull();
assertThat(getMethodByName(inner.getElements("method"), "compute")).isNotNull();
}
@Test
public void testThatTheClassContainsTheFlagsForTheInnerMethods() throws IOException, ClassNotFoundException,
NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException {
Manipulator manipulator = new Manipulator(this.getClass().getClassLoader());
String className = "test.inner.ComponentWithInnerClasses";
ManipulatedClassLoader classLoader = manipulate(className, manipulator);
Class clazz = classLoader.findClass(className);
String flag = "__M" + "MyInnerWithANativeMethod" + "___" + "foo";
assertThat(clazz.getDeclaredField(flag)).isNotNull();
flag = "__M" + "MyInnerClass" + "___" + "foo";
assertThat(clazz.getDeclaredField(flag)).isNotNull();
flag = "__M" + "1" + "___" + "compute" + "$java_lang_String";
assertThat(clazz.getDeclaredField(flag)).isNotNull();
}
@Test
public void testThatStaticInnerClassesAreNotManipulated() throws Exception {
Manipulator manipulator = new Manipulator(this.getClass().getClassLoader());
String className = "test.inner.ComponentWithInnerClasses";
ManipulatedClassLoader classLoader = manipulate(className, manipulator);
Class clazz = classLoader.findClass(className);
Class inner = findInnerClass(clazz.getClasses(), "MyStaticInnerClass");
assertThat(inner).isNotNull();
Method bar = inner.getMethod("bar");
Object o = inner.newInstance();
bar.setAccessible(true);
assertThat(bar).isNotNull();
assertThat((String) bar.invoke(o)).isEqualTo("bar");
}
@Test
public void testThatAnonymousClassDeclaredInStaticFieldsAreNotManipulated() throws Exception {
Manipulator manipulator = new Manipulator(this.getClass().getClassLoader());
String className = "test.inner.ComponentWithInnerClasses";
ManipulatedClassLoader classLoader = manipulate(className, manipulator);
Class clazz = classLoader.findClass(className);
Method method = clazz.getMethod("call");
assertThat(method).isNotNull();
assertThat(method.invoke(null)).isEqualTo(1);
}
private Class findInnerClass(Class[] classes, String name) {
for (Class clazz : classes) {
if (clazz.getName().contains(name)) {
return clazz;
}
}
return null;
}
}