| /* |
| * 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; |
| } |
| |
| } |