/*******************************************************************************
* Copyright (c) 1998, 2015 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.mappingsmodel.meta;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.lang.reflect.Field;
import java.util.AbstractSet;
import java.util.Iterator;
import org.eclipse.persistence.tools.workbench.test.mappingsmodel.MappingsModelTestTools;
import org.eclipse.persistence.tools.workbench.test.mappingsmodel.spi.meta.ClasspathTestTool;
import org.eclipse.persistence.tools.workbench.test.utility.JavaTools;
import org.eclipse.persistence.tools.workbench.test.utility.TestTools;
import junit.framework.Test;
import junit.framework.TestCase;
import junit.framework.TestSuite;
import org.eclipse.persistence.tools.workbench.mappingsmodel.meta.MWClass;
import org.eclipse.persistence.tools.workbench.mappingsmodel.meta.MWClassAttribute;
import org.eclipse.persistence.tools.workbench.mappingsmodel.meta.MWClassRepository;
import org.eclipse.persistence.tools.workbench.mappingsmodel.meta.MWMethod;
import org.eclipse.persistence.tools.workbench.mappingsmodel.meta.MWModifier;
import org.eclipse.persistence.tools.workbench.mappingsmodel.project.relational.MWRelationalProject;
import org.eclipse.persistence.tools.workbench.mappingsmodel.spi.meta.ExternalClassDescription;
import org.eclipse.persistence.tools.workbench.mappingsmodel.spi.meta.ExternalClassNotFoundException;
import org.eclipse.persistence.tools.workbench.utility.ClassTools;
import org.eclipse.persistence.tools.workbench.utility.Classpath;
import org.eclipse.persistence.tools.workbench.utility.CollectionTools;
import org.eclipse.persistence.tools.workbench.utility.events.CollectionChangeEvent;
import org.eclipse.persistence.tools.workbench.utility.events.CollectionChangeListener;
import org.eclipse.persistence.tools.workbench.utility.io.FileTools;
import org.eclipse.persistence.tools.workbench.utility.io.IndentingPrintWriter;
import org.eclipse.persistence.tools.workbench.utility.iterators.TransformationIterator;
import org.eclipse.persistence.tools.workbench.utility.node.Node;
public class MWClassRepositoryTests extends TestCase {
private MWRelationalProject project;
private MWClassRepository repository;
String eventType;
public static Test suite() {
return new TestSuite(MWClassRepositoryTests.class);
}
public MWClassRepositoryTests(String name) {
super(name);
}
protected void setUp() throws Exception {
super.setUp();
this.project = this.buildProject();
this.repository = this.project.getRepository();
}
private MWRelationalProject buildProject() {
return new MWRelationalProject(this.getClass().getName(), MappingsModelTestTools.buildSPIManager(), null);
}
protected void tearDown() throws Exception {
TestTools.clear(this);
super.tearDown();
}
public void testUserTypes() {
this.repository.addCollectionChangeListener(MWClassRepository.USER_TYPES_COLLECTION, this.buildListener());
this.eventType = null;
MWClass objectType = this.project.typeNamed(Object.class.getName());
MWClass abstractSetType = this.project.typeNamed(AbstractSet.class.getName());
assertEquals(AbstractSet.class.getName(), abstractSetType.getName());
assertNull(this.eventType);
this.eventType = null;
MWClass fooType = this.project.typeNamed("com.bar.Foo");
assertEquals("com.bar.Foo", fooType.getName());
assertNull(this.eventType);
fooType.setSuperclass(abstractSetType);
assertEquals("add", this.eventType);
this.eventType = null;
fooType.setSuperclass(objectType);
assertEquals("remove", this.eventType);
}
public void testModifierChange() {
this.repository.addCollectionChangeListener(MWClassRepository.USER_TYPES_COLLECTION, this.buildListener());
this.eventType = null;
MWClass abstractSetType = this.project.typeNamed(AbstractSet.class.getName());
assertEquals(AbstractSet.class.getName(), abstractSetType.getName());
assertNull(this.eventType);
this.eventType = null;
MWClass fooType = this.project.typeNamed("com.bar.Foo");
assertEquals("com.bar.Foo", fooType.getName());
assertNull(this.eventType);
fooType.getModifier().setAccessLevel(MWModifier.PROTECTED);
assertEquals("add", this.eventType);
this.eventType = null;
fooType.getModifier().setAccessLevel(MWModifier.PUBLIC);
assertEquals("remove", this.eventType);
}
private CollectionChangeListener buildListener() {
return new CollectionChangeListener() {
public void itemsAdded(CollectionChangeEvent e) {
MWClassRepositoryTests.this.eventType = "add";
}
public void itemsRemoved(CollectionChangeEvent e) {
MWClassRepositoryTests.this.eventType = "remove";
}
public void collectionChanged(CollectionChangeEvent e) {
throw new RuntimeException("unexpected event");
}
};
}
public void testClassLoader() throws Exception {
File tempDir = this.buildTempDir();
this.repository.addClasspathEntry(tempDir.getAbsolutePath());
MWClass testType = this.fullyPopulatedTypeNamed("foo.bar.TestClass");
assertEquals(2, testType.attributesSize());
assertEquals(3, testType.methodsSize());
MWClass intType = this.repository.typeFor(int.class);
MWClassAttribute attribute = testType.attributeNamed("attribute2");
assertEquals(intType, attribute.getType());
assertEquals(1, attribute.getDimensionality());
assertNull(testType.attributeNamed("attribute3"));
MWMethod method = testType.methodWithSignature("method2(int)");
assertEquals(intType, method.getReturnType());
assertEquals(intType, method.getMethodParameter().getType());
assertNull(testType.methodWithSignature("method3()"));
this.buildClassFile(tempDir, true);
this.repository.refreshExternalClassDescriptions();
testType.refresh();
assertNotNull(testType.attributeNamed("attribute3"));
assertNotNull(testType.methodWithSignature("method3()"));
this.deleteDir(tempDir);
}
private File buildTempDir() throws Exception {
File tempDir = FileTools.emptyTemporaryDirectory(ClassTools.shortClassNameForObject(this) + "." + this.getName());
File fooDir = new File(tempDir, "foo");
fooDir.mkdir();
File barDir = new File(fooDir, "bar");
barDir.mkdir();
this.buildClassFile(tempDir, false);
return tempDir;
}
private void buildClassFile(File tempDir, boolean printExtraStuff) throws Exception {
File sourceFile = new File(new File(new File(tempDir, "foo"), "bar"), "TestClass.java");
IndentingPrintWriter pw = new IndentingPrintWriter(new FileWriter(sourceFile));
this.printSourceOn(pw, printExtraStuff);
pw.close();
this.compile(sourceFile);
}
private void printSourceOn(IndentingPrintWriter pw, boolean printExtraStuff) {
pw.println("package foo.bar;");
pw.println("public class TestClass {");
pw.indent();
pw.println("private java.lang.String attribute1;");
pw.println("private int[] attribute2;");
if (printExtraStuff) {
pw.println("private java.lang.Object attribute3;");
}
pw.println("public TestClass() {super();}");
pw.println("public void method1() {}");
pw.println("public int method2(int returnValue) {return returnValue;}");
if (printExtraStuff) {
pw.println("public void method3() {}");
}
pw.undent();
pw.println("}");
}
private void compile(File sourceFile) throws IOException, InterruptedException {
JavaTools.compile(sourceFile);
}
private void deleteDir(File dir) {
FileTools.deleteDirectory(dir);
}
public void testCoreClassNames() throws Exception {
assertTrue(MWClassRepository.coreClassNamesContains(java.lang.Object.class.getName()));
assertTrue(MWClassRepository.coreClassNamesContains(java.util.List.class.getName()));
assertTrue(MWClassRepository.coreClassNamesContains(org.eclipse.persistence.indirection.ValueHolderInterface.class.getName()));
assertTrue(MWClassRepository.coreClassNamesContains(org.eclipse.persistence.sessions.Session.class.getName()));
}
public void testPartiallyLoadedClassNames() throws Exception {
this.verifyPartiallyPopulatedCoreType(java.lang.Object.class);
this.verifyPartiallyPopulatedCoreType(java.lang.String.class);
this.verifyPartiallyPopulatedCoreType(java.util.List.class);
assertTrue(this.project.typeFor(java.util.List.class).interfacesSize() != 0);
this.verifyPartiallyPopulatedCoreType(org.eclipse.persistence.indirection.ValueHolderInterface.class);
this.verifyPartiallyPopulatedCoreType(org.eclipse.persistence.indirection.ValueHolder.class);
assertTrue(this.project.typeFor(org.eclipse.persistence.indirection.ValueHolder.class).interfacesSize() != 0);
this.verifyPartiallyPopulatedCoreType(org.eclipse.persistence.descriptors.ClassDescriptor.class);
assertTrue(this.project.typeFor(org.eclipse.persistence.descriptors.ClassDescriptor.class).interfacesSize() == 2);
assertTrue(this.project.typeFor(this.getClass()).isStub());
}
private void verifyPartiallyPopulatedCoreType(Class javaClass) {
MWClass type = this.project.typeFor(javaClass);
assertFalse(type.isStub());
assertTrue(this.typeIsPartiallyPopulatedCoreType(type));
type.attributesSize(); // trigger complete population
assertFalse(this.typeIsPartiallyPopulatedCoreType(type));
}
private boolean typeIsPartiallyPopulatedCoreType(MWClass type) {
return ((Boolean) ClassTools.getFieldValue(type, "partiallyPopulatedCoreType")).booleanValue();
}
public void testChangeClasspath() throws Exception {
ClasspathTestTool tool = new ClasspathTestTool(ClassTools.shortClassNameForObject(this) + "." + this.getName());
tool.setUp();
String testClassName = ClasspathTestTool.TEST_CLASS_NAME;
String versionMethodName1 = ClasspathTestTool.VERSION_MEMBER_PREFIX + 1;
String versionMethodSignature1 = versionMethodName1 + "()";
String versionMethodName2 = ClasspathTestTool.VERSION_MEMBER_PREFIX + 2;
String versionMethodSignature2 = versionMethodName2 + "()";
int index1 = this.repository.classpathEntriesSize();
this.repository.addClasspathEntry(tool.subdir1.getAbsolutePath());
// make sure the .class file was found...
this.verifyClassNamed(testClassName);
// ...and it is the right one
MWClass testType = this.fullyPopulatedTypeNamed(testClassName);
assertNotNull("method is missing: " + versionMethodSignature1,
testType.methodWithSignature(versionMethodSignature1));
// now, change the path to point to subdir2
this.repository.removeClasspathEntry(index1);
this.repository.addClasspathEntry(tool.subdir2.getAbsolutePath());
testType.refresh();
assertTrue("method should be gone: " + versionMethodSignature1,
testType.methodWithSignature(versionMethodSignature1) == null);
assertTrue("method is missing: " + versionMethodSignature2,
testType.methodWithSignature(versionMethodSignature2) != null);
tool.tearDown();
}
public void testRefreshExternalClassesDirectory() throws Exception {
ClasspathTestTool tool = new ClasspathTestTool(ClassTools.shortClassNameForObject(this) + "." + this.getName());
tool.setUp();
String testClassName = ClasspathTestTool.TEST_CLASS_NAME;
File classFile = new File(tool.subdir1, Classpath.convertToClassFileName(testClassName));
this.repository.addClasspathEntry(tool.subdir1.getAbsolutePath());
this.verifyRefreshExternalClassDescriptions(testClassName, classFile);
tool.tearDown();
}
public void testRefreshExternalClassesJar() throws Exception {
ClasspathTestTool tool = new ClasspathTestTool(ClassTools.shortClassNameForObject(this) + "." + this.getName());
tool.setUp();
this.testRefreshExternalClassesArchive(tool.jarFile1, ClasspathTestTool.TEST_CLASS_NAME);
tool.tearDown();
}
public void testRefreshExternalClassesZip() throws Exception {
ClasspathTestTool tool = new ClasspathTestTool(ClassTools.shortClassNameForObject(this) + "." + this.getName());
tool.setUp();
this.testRefreshExternalClassesArchive(tool.zipFile1, ClasspathTestTool.TEST_CLASS_NAME);
tool.tearDown();
}
private void testRefreshExternalClassesArchive(File archiveFile, String testClassName) {
this.repository.addClasspathEntry(archiveFile.getAbsolutePath());
this.verifyRefreshExternalClassDescriptions(testClassName, archiveFile);
}
private void verifyRefreshExternalClassDescriptions(String testClassName, File file) {
this.verifyClassNamed(testClassName);
File xFile = new File(file.getAbsolutePath() + ".x");
file.renameTo(xFile);
this.repository.refreshExternalClassDescriptions();
this.verifyMissingClassNamed(testClassName);
xFile.renameTo(file);
this.repository.refreshExternalClassDescriptions();
this.verifyClassNamed(testClassName);
file.delete();
this.repository.refreshExternalClassDescriptions();
this.verifyMissingClassNamed(testClassName);
}
public void testRelativePath() throws Exception {
ClasspathTestTool tool = new ClasspathTestTool(ClassTools.shortClassNameForObject(this) + "." + this.getName());
tool.setUp();
this.project.setSaveDirectory(tool.workingDirectory);
this.repository.addClasspathEntry(tool.jarFile1.getAbsolutePath().substring(tool.workingDirectory.getAbsolutePath().length() + 1));
this.verifyClassNamed(ClasspathTestTool.TEST_CLASS_NAME);
tool.tearDown();
}
// this test is not so easy to do now, so we commented it out...
// the compiler used to generate static fields, e.g.
// static Class class$0;
// to hold the class used by code generated for
// the "com.foo.Bar.class" syntax, i.e.
// (class$0 != null) ? class$0 : class$0 = Class.forName("com.foo.Bar")
// this is compiler-dependent, but seems pretty consistent across compilers...
// public void testIsSynthetic() throws Exception {
// this.project.getRepository().addClasspathEntry(Classpath.locationFor(this.getClass()));
// Field[] fields = this.getClass().getDeclaredFields();
// MWClass type = this.fullyPopulatedTypeFor(this.getClass());
// assertTrue("compiler generated fields not found in: " + this.getClass().getName(), fields.length > type.attributesSize());
// }
//
public void testCheckTypeNameNull() {
boolean exCaught = false;
try {
MWClass type = this.repository.typeNamed(null);
fail("invalid type: " + type);
} catch (NullPointerException ex) {
exCaught = true;
}
assertTrue(exCaught);
}
public void testCheckTypeNameEmptyName() {
boolean exCaught = false;
try {
MWClass type = this.repository.typeNamed("");
fail("invalid type: " + type);
} catch (IllegalArgumentException ex) {
exCaught = true;
}
assertTrue(exCaught);
}
public void testCheckTypeNameArray() {
boolean exCaught = false;
try {
MWClass type = this.repository.typeNamed(new Object[0].getClass().getName());
fail("invalid type: " + type);
} catch (IllegalArgumentException ex) {
exCaught = true;
}
assertTrue(exCaught);
}
public void testCheckTypeNameCoreTypeCollision() {
boolean exCaught = false;
try {
MWClass type = this.repository.typeNamed("java.lang.OBJECT");
fail("invalid type: " + type);
} catch (IllegalArgumentException ex) {
exCaught = true;
}
assertTrue(exCaught);
}
public void testCheckTypeNameUserTypeCollision() {
MWClass fooType = this.repository.typeNamed("xxx.Foo");
fooType.addAttribute("attribute1");
boolean exCaught = false;
try {
MWClass type = this.repository.typeNamed("XXX.Foo");
fail("invalid type: " + type);
} catch (IllegalArgumentException ex) {
exCaught = true;
}
assertTrue(exCaught);
}
public void testTypeNamedIgnoreCaseCollision() {
MWClass fooType = this.repository.typeNamed("xxx.Foo");
fooType.addAttribute("attribute1");
MWClass barType = this.repository.typeNamed("xxx.Bar");
barType.setSuperclass(fooType); // add a reference to Foo
String newTypeName = "XXX.Foo";
MWClass collision = this.repository.typeNamedIgnoreCase(newTypeName);
assertSame(fooType, collision);
boolean exCaught = false;
try {
MWClass type = this.repository.typeNamed(newTypeName);
fail("invalid type: " + type);
} catch (IllegalArgumentException ex) {
exCaught = true;
}
assertTrue(exCaught);
}
public void testTypeNamedIgnoreCaseGarbage() {
MWClass fooType = this.repository.typeNamed("xxx.Foo");
fooType.addAttribute("attribute1");
String newTypeName = "XXX.Foo";
MWClass collision = this.repository.typeNamedIgnoreCase(newTypeName);
// since there are no references to Foo, it should be "garbage collected"
assertNull(collision);
MWClass type = this.repository.typeNamed(newTypeName);
assertNotNull(type);
}
public void testTypeRenamedNull() {
MWClass type = this.repository.typeNamed("xxx.Foo");
boolean exCaught = false;
try {
type.setName(null);
fail("invalid type: " + type);
} catch (NullPointerException ex) {
exCaught = true;
}
assertTrue(exCaught);
}
public void testTypeRenamedEmptyName() {
MWClass type = this.repository.typeNamed("xxx.Foo");
boolean exCaught = false;
try {
type.setName("");
fail("invalid type: " + type);
} catch (IllegalArgumentException ex) {
exCaught = true;
}
assertTrue(exCaught);
}
public void testTypeRenamedArray() {
MWClass type = this.repository.typeNamed("xxx.Foo");
boolean exCaught = false;
try {
type.setName(new Object[0].getClass().getName());
fail("invalid type: " + type);
} catch (IllegalArgumentException ex) {
exCaught = true;
}
assertTrue(exCaught);
}
public void testTypeRenamedCoreTypeCollision() {
MWClass type = this.repository.typeNamed("xxx.Foo");
boolean exCaught = false;
try {
type.setName("java.lang.OBJECT");
fail("invalid type: " + type);
} catch (IllegalArgumentException ex) {
exCaught = true;
}
assertTrue(exCaught);
}
public void testTypeRenamedCoreType() {
MWClass type = this.repository.typeNamed("xxx.Foo");
boolean exCaught = false;
try {
type.setName(Object.class.getName());
fail("invalid type: " + type);
} catch (IllegalArgumentException ex) {
exCaught = true;
}
assertTrue(exCaught);
}
public void testTypeRenamedUserTypeCollision() {
MWClass fooType = this.repository.typeNamed("xxx.Foo");
fooType.addAttribute("attribute1");
MWClass barType = this.repository.typeNamed("xxx.Bar");
barType.setSuperclass(fooType);
MWClass bazType = this.repository.typeNamed("xxx.Baz");
boolean exCaught = false;
try {
bazType.setName(fooType.getName());
fail("invalid type: " + bazType);
} catch (IllegalArgumentException ex) {
exCaught = true;
}
assertTrue(exCaught);
}
public void testTypeRenamedIgnoreCaseCollision() {
MWClass fooType = this.repository.typeNamed("xxx.Foo");
fooType.addAttribute("attribute1");
MWClass barType = this.repository.typeNamed("xxx.Bar");
barType.setSuperclass(fooType);
MWClass bazType = this.repository.typeNamed("xxx.Baz");
boolean exCaught = false;
try {
bazType.setName("xxx.FOO");
fail("invalid type: " + bazType);
} catch (IllegalArgumentException ex) {
exCaught = true;
}
assertTrue(exCaught);
}
public void testTypeRenamedIgnoreCaseGarbage() {
MWClass fooType = this.repository.typeNamed("xxx.Foo");
fooType.addAttribute("attribute1");
MWClass bazType = this.repository.typeNamed("xxx.Baz");
String newTypeName = "XXX.Foo";
MWClass collision = this.repository.typeNamedIgnoreCase(newTypeName);
// since there are no references to Foo, it should be "garbage collected"
assertNull(collision);
bazType.setName(newTypeName);
assertEquals(newTypeName, bazType.getName());
}
public void testTypeReferences() {
MWClass fooType = this.repository.typeNamed("xxx.Foo");
fooType.addAttribute("attribute1");
MWClass barType = this.repository.typeNamed("xxx.Bar");
barType.setSuperclass(fooType);
assertTrue(this.projectContainsAnyReferencesTo(fooType));
assertFalse(this.projectContainsAnyReferencesTo(barType));
}
private boolean projectContainsAnyReferencesTo(Node node) {
for (Iterator stream = this.project.branchReferences(); stream.hasNext(); ) {
Node.Reference ref = (Node.Reference) stream.next();
if (ref.getTarget() == node) {
return true;
}
}
return false;
}
private MWClass fullyPopulatedTypeFor(Class javaClass) throws ExternalClassNotFoundException {
return this.fullyPopulatedTypeNamed(javaClass.getName());
}
// private void verifyClass(Class javaClass) {
// this.verifyClassNamed(javaClass.getName());
// }
//
private void verifyMissingClassNamed(String className) {
assertTrue("external class should be gone: " + className,
! CollectionTools.contains(this.classNames(), className));
}
private void verifyClassNamed(String className) {
assertTrue("missing external class: " + className,
CollectionTools.contains(this.classNames(), className));
}
private Iterator classNames(Iterator externalClassDescriptions) {
return new TransformationIterator(externalClassDescriptions) {
protected Object transform(Object next) {
return ((ExternalClassDescription) next).getName();
}
};
}
private Iterator classNames() {
return this.classNames(this.repository.externalClassDescriptions());
}
private MWClass fullyPopulatedTypeNamed(String className) throws ExternalClassNotFoundException {
MWClass result = this.project.typeNamed(className);
result.refresh();
return result;
}
// private void dump(Iterator stream) {
// while (stream.hasNext()) {
// System.out.println(stream.next().toString());
// }
// }
//
}