/*******************************************************************************
* Copyright (c) 1998, 2016 Oracle and/or its affiliates. All rights reserved.
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v1.0 and Eclipse Distribution License v. 1.0
* which accompanies this distribution.
* The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html
* and the Eclipse Distribution License is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* Contributors:
* Oracle - initial API and implementation from Oracle TopLink
******************************************************************************/
package org.eclipse.persistence.tools.workbench.test.utility.classfile;
import java.lang.reflect.Constructor;
import java.lang.reflect.Modifier;
import org.eclipse.persistence.tools.workbench.test.utility.ClassToolsTests;
import org.eclipse.persistence.tools.workbench.utility.Bag;
import org.eclipse.persistence.tools.workbench.utility.ClassTools;
import org.eclipse.persistence.tools.workbench.utility.CollectionTools;
import org.eclipse.persistence.tools.workbench.utility.classfile.ClassDeclaration;
import org.eclipse.persistence.tools.workbench.utility.classfile.ClassFile;
import org.eclipse.persistence.tools.workbench.utility.classfile.Field;
import org.eclipse.persistence.tools.workbench.utility.classfile.FieldPool;
import org.eclipse.persistence.tools.workbench.utility.classfile.InnerClass;
import org.eclipse.persistence.tools.workbench.utility.classfile.Method;
import org.eclipse.persistence.tools.workbench.utility.classfile.MethodPool;
import junit.framework.Test;
import junit.framework.TestCase;
import junit.framework.TestSuite;
public class ClassFileTests extends TestCase {
private static final String[] NESTED_CLASS_NAMES =
new String[] {
"org.eclipse.persistence.tools.workbench.test.utility.classfile.ClassFileTestClass$1$LocalClass1",
"org.eclipse.persistence.tools.workbench.test.utility.classfile.ClassFileTestClass$1$LocalClass2",
"org.eclipse.persistence.tools.workbench.test.utility.classfile.ClassFileTestClass$1",
"org.eclipse.persistence.tools.workbench.test.utility.classfile.ClassFileTestClass$2",
"org.eclipse.persistence.tools.workbench.test.utility.classfile.ClassFileTestClass$AnotherClass$DoubleNestedClass",
"org.eclipse.persistence.tools.workbench.test.utility.classfile.ClassFileTestClass$AnotherClass",
"org.eclipse.persistence.tools.workbench.test.utility.classfile.ClassFileTestClass$DeprecatedStaticInnerInterface",
"org.eclipse.persistence.tools.workbench.test.utility.classfile.ClassFileTestClass$InnerClass1$NestedInnerClass",
"org.eclipse.persistence.tools.workbench.test.utility.classfile.ClassFileTestClass$InnerClass1",
"org.eclipse.persistence.tools.workbench.test.utility.classfile.ClassFileTestClass$InnerInterface1",
"org.eclipse.persistence.tools.workbench.test.utility.classfile.ClassFileTestClass$StaticInnerClass",
"org.eclipse.persistence.tools.workbench.test.utility.classfile.ClassFileTestClass$StaticInnerInterface"
};
/**
* WARNING: hack-o-rama - we munge the class names above, depending
* on the compiler-generated class name; see the comment in ClassToolsTests
*/
static {
for (int i = 0; i < NESTED_CLASS_NAMES.length; i++) {
NESTED_CLASS_NAMES[i] = munge(NESTED_CLASS_NAMES[i]);
}
}
private static String munge(String className) {
return ClassToolsTests.compilerDependentClassNameFor(className);
}
public static Test suite() {
return new TestSuite(ClassFileTests.class);
}
public ClassFileTests(String name) {
super(name);
}
public void testClassFileHeader() throws Exception {
this.verifyClassFileHeader(ClassFileTestClass.class);
for (int i = 0; i < NESTED_CLASS_NAMES.length; i++) {
this.verifyClassFileHeader(Class.forName(NESTED_CLASS_NAMES[i]));
}
}
private void verifyClassFileHeader(Class javaClass) throws Exception {
ClassFile classFile = ClassFile.forClass(javaClass);
assertEquals(0xCAFEBABE, classFile.getHeader().getMagic());
}
public void testNestedClasses() throws Exception {
ClassFile classFile;
classFile = ClassFile.forClass(ClassFileTestClass.class);
assertTrue(classFile.isTopLevelClass());
assertFalse(classFile.isNestedClass());
assertFalse(classFile.isMemberClass());
assertFalse(classFile.isLocalClass());
assertFalse(classFile.isAnonymousClass());
classFile = ClassFile.forClass(Class.forName(munge("org.eclipse.persistence.tools.workbench.test.utility.classfile.ClassFileTestClass$1$LocalClass1")));
assertFalse(classFile.isTopLevelClass());
assertTrue(classFile.isNestedClass());
assertFalse(classFile.isMemberClass());
assertTrue(classFile.isLocalClass());
assertFalse(classFile.isAnonymousClass());
classFile = ClassFile.forClass(Class.forName(munge("org.eclipse.persistence.tools.workbench.test.utility.classfile.ClassFileTestClass$1$LocalClass2")));
assertFalse(classFile.isTopLevelClass());
assertTrue(classFile.isNestedClass());
assertFalse(classFile.isMemberClass());
assertTrue(classFile.isLocalClass());
assertFalse(classFile.isAnonymousClass());
classFile = ClassFile.forClass(Class.forName("org.eclipse.persistence.tools.workbench.test.utility.classfile.ClassFileTestClass$1"));
assertFalse(classFile.isTopLevelClass());
assertTrue(classFile.isNestedClass());
assertFalse(classFile.isMemberClass());
assertFalse(classFile.isLocalClass());
assertTrue(classFile.isAnonymousClass());
classFile = ClassFile.forClass(Class.forName("org.eclipse.persistence.tools.workbench.test.utility.classfile.ClassFileTestClass$2"));
assertFalse(classFile.isTopLevelClass());
assertTrue(classFile.isNestedClass());
assertFalse(classFile.isMemberClass());
assertFalse(classFile.isLocalClass());
assertTrue(classFile.isAnonymousClass());
classFile = ClassFile.forClass(Class.forName("org.eclipse.persistence.tools.workbench.test.utility.classfile.ClassFileTestClass$AnotherClass$DoubleNestedClass"));
assertFalse(classFile.isTopLevelClass());
assertTrue(classFile.isNestedClass());
assertTrue(classFile.isMemberClass());
assertFalse(classFile.isLocalClass());
assertFalse(classFile.isAnonymousClass());
classFile = ClassFile.forClass(Class.forName("org.eclipse.persistence.tools.workbench.test.utility.classfile.ClassFileTestClass$AnotherClass"));
assertFalse(classFile.isTopLevelClass());
assertTrue(classFile.isNestedClass());
assertTrue(classFile.isMemberClass());
assertFalse(classFile.isLocalClass());
assertFalse(classFile.isAnonymousClass());
classFile = ClassFile.forClass(Class.forName("org.eclipse.persistence.tools.workbench.test.utility.classfile.ClassFileTestClass$DeprecatedStaticInnerInterface"));
assertFalse(classFile.isTopLevelClass());
assertTrue(classFile.isNestedClass());
assertTrue(classFile.isMemberClass());
assertFalse(classFile.isLocalClass());
assertFalse(classFile.isAnonymousClass());
classFile = ClassFile.forClass(Class.forName("org.eclipse.persistence.tools.workbench.test.utility.classfile.ClassFileTestClass$InnerClass1$NestedInnerClass"));
assertFalse(classFile.isTopLevelClass());
assertTrue(classFile.isNestedClass());
assertTrue(classFile.isMemberClass());
assertFalse(classFile.isLocalClass());
assertFalse(classFile.isAnonymousClass());
classFile = ClassFile.forClass(Class.forName("org.eclipse.persistence.tools.workbench.test.utility.classfile.ClassFileTestClass$InnerClass1"));
assertFalse(classFile.isTopLevelClass());
assertTrue(classFile.isNestedClass());
assertTrue(classFile.isMemberClass());
assertFalse(classFile.isLocalClass());
assertFalse(classFile.isAnonymousClass());
classFile = ClassFile.forClass(Class.forName("org.eclipse.persistence.tools.workbench.test.utility.classfile.ClassFileTestClass$InnerInterface1"));
assertFalse(classFile.isTopLevelClass());
assertTrue(classFile.isNestedClass());
assertTrue(classFile.isMemberClass());
assertFalse(classFile.isLocalClass());
assertFalse(classFile.isAnonymousClass());
classFile = ClassFile.forClass(Class.forName("org.eclipse.persistence.tools.workbench.test.utility.classfile.ClassFileTestClass$StaticInnerClass"));
assertFalse(classFile.isTopLevelClass());
assertTrue(classFile.isNestedClass());
assertTrue(classFile.isMemberClass());
assertFalse(classFile.isLocalClass());
assertFalse(classFile.isAnonymousClass());
classFile = ClassFile.forClass(Class.forName("org.eclipse.persistence.tools.workbench.test.utility.classfile.ClassFileTestClass$StaticInnerInterface"));
assertFalse(classFile.isTopLevelClass());
assertTrue(classFile.isNestedClass());
assertTrue(classFile.isMemberClass());
assertFalse(classFile.isLocalClass());
assertFalse(classFile.isAnonymousClass());
}
public void testClassDeclaration() throws Exception {
this.verifyClassDeclaration(ClassFileTestClass.class);
for (int i = 0; i < NESTED_CLASS_NAMES.length; i++) {
this.verifyClassDeclaration(Class.forName(NESTED_CLASS_NAMES[i]));
}
}
private void verifyClassDeclaration(Class javaClass) throws Exception {
ClassFile classFile = ClassFile.forClass(javaClass);
ClassDeclaration declaration = classFile.getDeclaration();
assertEquals(Modifier.toString(javaClass.getModifiers()), Modifier.toString(declaration.standardAccessFlags()));
assertTrue(declaration.isInterface() || declaration.isSuper()); // all new (non-interface) classes should have this bit set
assertEquals(javaClass.getName(), declaration.thisClassName());
if ( ! javaClass.isInterface()) {
assertEquals(javaClass.getSuperclass().getName(), declaration.superClassName());
}
this.verifyClasses(javaClass.getInterfaces(), declaration.interfaceNames());
}
private void verifyClasses(Class[] expectedClasses, String[] actualClassNames) {
String[] expectedClassNames = new String[expectedClasses.length];
for (int i = expectedClasses.length; i-- > 0; ) {
expectedClassNames[i] = expectedClasses[i].getName();
}
Bag expected = CollectionTools.bag(expectedClassNames);
Bag actual = CollectionTools.bag(actualClassNames);
assertEquals(expected, actual);
}
public void testFields() throws Exception {
this.verifyFields(ClassFileTestClass.class);
for (int i = 0; i < NESTED_CLASS_NAMES.length; i++) {
this.verifyFields(Class.forName(NESTED_CLASS_NAMES[i]));
}
}
private void verifyFields(Class javaClass) throws Exception {
ClassFile classFile = ClassFile.forClass(javaClass);
FieldPool fieldPool = classFile.getFieldPool();
for (short i = fieldPool.getCount(); i-- > 0; ) {
this.verifyField(fieldPool.get(i), javaClass);
}
}
private void verifyField(Field actualField, Class javaClass) throws Exception {
java.lang.reflect.Field expectedField = javaClass.getDeclaredField(actualField.name());
assertNotNull(expectedField);
assertEquals(Modifier.toString(expectedField.getModifiers()), Modifier.toString(actualField.standardAccessFlags()));
assertEquals(expectedField.getType(), actualField.getFieldDescriptor().javaClass());
assertEquals(expectedField.getType().getName(), actualField.javaTypeName());
if (actualField.name().toLowerCase().indexOf("deprecated") == -1) {
assertFalse(actualField.isDeprecated());
} else {
assertTrue(actualField.isDeprecated());
}
if (actualField.name().toLowerCase().indexOf('$') == -1) {
assertFalse(actualField.isSynthetic());
} else {
assertTrue(actualField.isSynthetic());
}
}
public void testFieldConstantValue() throws Exception {
this.verifyFieldConstantValue(ClassFileTestClass.class);
}
private void verifyFieldConstantValue(Class javaClass) throws Exception {
ClassFile classFile = ClassFile.forClass(javaClass);
FieldPool fieldPool = classFile.getFieldPool();
Field field;
field = fieldPool.fieldNamed("byteStatic_55");
assertEquals(new Integer(55), field.constantValue());
field = fieldPool.fieldNamed("shortStatic_55");
assertEquals(new Integer(55), field.constantValue());
field = fieldPool.fieldNamed("intStatic_55");
assertEquals(new Integer(55), field.constantValue());
field = fieldPool.fieldNamed("longStatic_55L");
assertEquals(new Long(55L), field.constantValue());
field = fieldPool.fieldNamed("floatStatic_5_55F");
assertEquals(new Float(5.55F), field.constantValue());
field = fieldPool.fieldNamed("floatStatic_5_0e7F");
assertEquals(new Float(5e7F), field.constantValue());
field = fieldPool.fieldNamed("doubleStatic_5_55D");
assertEquals(new Double(5.55D), field.constantValue());
field = fieldPool.fieldNamed("doubleStatic_5_0e55D");
assertEquals(new Double(5e55D), field.constantValue());
field = fieldPool.fieldNamed("booleanStatic_true");
assertEquals(new Integer(1), field.constantValue());
field = fieldPool.fieldNamed("booleanStatic_false");
assertEquals(new Integer(0), field.constantValue());
field = fieldPool.fieldNamed("charStatic_A");
assertEquals(new Integer('A'), field.constantValue());
field = fieldPool.fieldNamed("stringStatic_A_String");
assertEquals("A String", field.constantValue());
field = fieldPool.fieldNamed("privateString");
boolean exCaught = false;
Object constantValue = null;
try {
constantValue = field.constantValue();
} catch (IllegalStateException ex) {
exCaught = true;
}
assertTrue("bogus constant value: " + constantValue, exCaught);
}
public void testFieldArrayDepth() throws Exception {
this.verifyFieldArrayDepth(ClassFileTestClass.class);
}
private void verifyFieldArrayDepth(Class javaClass) throws Exception {
ClassFile classFile = ClassFile.forClass(javaClass);
FieldPool fieldPool = classFile.getFieldPool();
Field field = fieldPool.fieldNamed("byteStatic_55");
assertEquals(0, field.getFieldDescriptor().arrayDepth());
field = fieldPool.fieldNamed("privateString");
assertEquals(0, field.getFieldDescriptor().arrayDepth());
field = fieldPool.fieldNamed("packageStringArray1D");
assertEquals(1, field.getFieldDescriptor().arrayDepth());
field = fieldPool.fieldNamed("packageStringArray2D");
assertEquals(2, field.getFieldDescriptor().arrayDepth());
field = fieldPool.fieldNamed("packageIntArray2D");
assertEquals(2, field.getFieldDescriptor().arrayDepth());
}
public void testFieldElementType() throws Exception {
this.verifyFieldElementType(ClassFileTestClass.class);
}
private void verifyFieldElementType(Class javaClass) throws Exception {
ClassFile classFile = ClassFile.forClass(javaClass);
FieldPool fieldPool = classFile.getFieldPool();
Field field = fieldPool.fieldNamed("byteStatic_55");
assertEquals("byte", field.getFieldDescriptor().elementTypeName());
field = fieldPool.fieldNamed("privateString");
assertEquals("java.lang.String", field.getFieldDescriptor().elementTypeName());
field = fieldPool.fieldNamed("packageStringArray1D");
assertEquals("java.lang.String", field.getFieldDescriptor().elementTypeName());
field = fieldPool.fieldNamed("packageStringArray2D");
assertEquals("java.lang.String", field.getFieldDescriptor().elementTypeName());
field = fieldPool.fieldNamed("packageIntArray2D");
assertEquals("int", field.getFieldDescriptor().elementTypeName());
}
public void testMethods() throws Exception {
this.verifyMethods(ClassFileTestClass.class);
for (int i = 0; i < NESTED_CLASS_NAMES.length; i++) {
this.verifyMethods(Class.forName(NESTED_CLASS_NAMES[i]));
}
}
private void verifyMethods(Class javaClass) throws Exception {
ClassFile classFile = ClassFile.forClass(javaClass);
MethodPool methodPool = classFile.getMethodPool();
int staticInitializationMethodCount = 0;
for (short i = methodPool.getCount(); i-- > 0; ) {
Method actualMethod = methodPool.get(i);
this.verifyMethod(actualMethod, javaClass);
if (actualMethod.isStaticInitializationMethod()) {
staticInitializationMethodCount++;
}
}
assertTrue(staticInitializationMethodCount <= 1);
}
private void verifyMethod(Method actualMethod, Class javaClass) throws Exception {
if (actualMethod.isStaticInitializationMethod()) {
this.verifyStaticInitializationMethod(actualMethod, javaClass);
} else if (actualMethod.isConstructor()) {
this.verifyConstructor(actualMethod, javaClass);
} else {
this.verifyNormalMethod(actualMethod, javaClass);
}
}
private void verifyStaticInitializationMethod(Method actualMethod, Class javaClass) throws Exception {
assertEquals(0, actualMethod.getParameterDescriptors().length);
assertEquals(Modifier.STATIC, actualMethod.standardAccessFlags());
assertEquals(void.class, actualMethod.getReturnDescriptor().javaClass());
assertEquals("void", actualMethod.javaReturnTypeName());
}
private void verifyConstructor(Method actualMethod, Class javaClass) throws Exception {
Class[] actualParmTypes = new Class[actualMethod.getParameterDescriptors().length];
for (int i = 0; i < actualMethod.getParameterDescriptors().length; i++) {
actualParmTypes[i] = actualMethod.getParameterDescriptor(i).javaClass();
}
Constructor expectedCtor = javaClass.getDeclaredConstructor(actualParmTypes);
assertNotNull(expectedCtor);
assertEquals(Modifier.toString(expectedCtor.getModifiers()), Modifier.toString(actualMethod.standardAccessFlags()));
assertEquals(void.class, actualMethod.getReturnDescriptor().javaClass());
assertEquals("<init>", actualMethod.name());
assertEquals(expectedCtor.getName(), actualMethod.constructorName());
this.verifyClasses(expectedCtor.getExceptionTypes(), actualMethod.exceptionClassNames());
}
private void verifyNormalMethod(Method actualMethod, Class javaClass) throws Exception {
Class[] actualParmTypes = new Class[actualMethod.getParameterDescriptors().length];
for (int i = 0; i < actualMethod.getParameterDescriptors().length; i++) {
actualParmTypes[i] = actualMethod.getParameterDescriptor(i).javaClass();
}
java.lang.reflect.Method expectedMethod = javaClass.getDeclaredMethod(actualMethod.name(), actualParmTypes);
assertNotNull(expectedMethod);
assertEquals(Modifier.toString(expectedMethod.getModifiers()), Modifier.toString(actualMethod.standardAccessFlags()));
assertEquals(expectedMethod.isDefault(), actualMethod.isDefault());
assertEquals(expectedMethod.getReturnType(), actualMethod.getReturnDescriptor().javaClass());
assertEquals(expectedMethod.getReturnType().getName(), actualMethod.javaReturnTypeName());
this.verifyClasses(expectedMethod.getExceptionTypes(), actualMethod.exceptionClassNames());
if (actualMethod.name().toLowerCase().indexOf("deprecated") == -1) {
assertFalse(actualMethod.isDeprecated());
} else {
assertTrue(actualMethod.isDeprecated());
}
}
public void testDeclaringClass() throws Exception {
this.verifyDeclaringClass(ClassFileTestClass.class);
for (int i = 0; i < NESTED_CLASS_NAMES.length; i++) {
this.verifyDeclaringClass(Class.forName(NESTED_CLASS_NAMES[i]));
}
}
private void verifyDeclaringClass(Class javaClass) throws Exception {
ClassFile classFile = ClassFile.forClass(javaClass);
String actualDeclaringClassName = classFile.declaringClassName();
Class expectedDeclaringClass = javaClass.getDeclaringClass();
assertEquals((expectedDeclaringClass == null) ? null : expectedDeclaringClass.getName(), actualDeclaringClassName);
}
public void testMemberClasses() throws Exception {
this.verifyMemberClasses(ClassFileTestClass.class);
for (int i = 0; i < NESTED_CLASS_NAMES.length; i++) {
this.verifyMemberClasses(Class.forName(NESTED_CLASS_NAMES[i]));
}
}
private void verifyMemberClasses(Class javaClass) throws Exception {
ClassFile classFile = ClassFile.forClass(javaClass);
String[] memberClassNames = classFile.declaredMemberClassNames();
// "member" classes should match up with the "declared" classes
this.verifyClasses(javaClass.getDeclaredClasses(), memberClassNames);
for (short i = 0; i < memberClassNames.length; i++) {
InnerClass memberClass = classFile.getAttributePool().innerClassNamed(memberClassNames[i]);
assertEquals(javaClass.getName(), memberClass.outerClassInfoName());
}
}
public void testNonMemberClasses() throws Exception {
this.verifyNonMemberClasses(ClassFileTestClass.class);
}
private void verifyNonMemberClasses(Class javaClass) throws Exception {
ClassFile classFile = ClassFile.forClass(javaClass);
String[] innerClassNames = classFile.nestedClassNames();
// these can change over time and are compiler-dependent, so the tests
// may fail in the future - use ClassFileDumper to find the expected values
assertTrue(CollectionTools.contains(innerClassNames, javaClass.getName() + "$2"));
// Eclipse generates ...$3 while the jdk generates ...$1
assertTrue(CollectionTools.contains(innerClassNames, javaClass.getName() + "$3") ||
CollectionTools.contains(innerClassNames, javaClass.getName() + "$1"));
assertTrue(CollectionTools.contains(innerClassNames, munge(javaClass.getName() + "$1$LocalClass1")));
assertTrue(CollectionTools.contains(innerClassNames, munge(javaClass.getName() + "$1$LocalClass2")));
}
public void testInterfaceDeclaration() throws Exception {
this.verifyClassDeclaration(ClassFileTestInterface.class);
}
public void testInterfaceFields() throws Exception {
this.verifyFields(ClassFileTestInterface.class);
}
public void testInterfaceMethods() throws Exception {
this.verifyMethods(ClassFileTestInterface.class);
}
public void testInterfaceMemberClasses() throws Exception {
this.verifyMemberClasses(ClassFileTestInterface.class);
}
public void testSyntheticClass() throws Exception {
ClassFile classFile = ClassFile.forClass(ClassFileTestClass.class);
assertFalse(classFile.isSynthetic());
// this class was not marked synthetic until jdk 1.4.2 and it doesn't seem to be marked synthetic in 1.5
String javaVersion = System.getProperty("java.version");
if (javaVersion.compareTo("1.4.2") >= 0 && javaVersion.compareTo("1.5.0") < 0) {
Class syntheticClass = null;
try {
syntheticClass = Class.forName(String.class.getName() + "$1");
} catch (ClassNotFoundException ex) {
// the eclipse compiler does not generate this class;
// the jdk compiler generates it, but it does nothing (according to JAD)
return;
}
classFile = ClassFile.forClass(syntheticClass);
assertTrue(classFile.isSynthetic());
}
}
public void testDeprecatedClass() throws Exception {
ClassFile classFile = ClassFile.forClass(ClassFileTestClass.class);
assertFalse(classFile.isDeprecated());
classFile = ClassFile.forClass(Class.forName(ClassFileTestClass.class.getName() + "$DeprecatedStaticInnerInterface"));
assertTrue(classFile.isDeprecated());
}
public void testSourceFile() throws Exception {
this.verifySourceFile(ClassFileTestClass.class);
}
private void verifySourceFile(Class javaClass) throws Exception {
String expectedSourceFileName = ClassTools.shortNameFor(javaClass) + ".java";
ClassFile classFile = ClassFile.forClass(javaClass);
assertEquals(expectedSourceFileName, classFile.sourceFileName());
// the source file for the inner class is the outer class's source file
classFile = ClassFile.forClass(Class.forName(javaClass.getName() + "$1"));
assertEquals(expectedSourceFileName, classFile.sourceFileName());
}
}