blob: 358f381c9f4ab21a422fa64ce818480a7d23337f [file] [log] [blame]
/*
* Copyright 2000-2011 JetBrains s.r.o.
*
* 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 com.intellij.uiDesigner.core;
import com.intellij.compiler.instrumentation.InstrumentationClassFinder;
import com.intellij.openapi.actionSystem.DataProvider;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.PathManager;
import com.intellij.openapi.application.PluginPathManager;
import com.intellij.openapi.util.SystemInfoRt;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.ui.components.JBTabbedPane;
import com.intellij.uiDesigner.compiler.AsmCodeGenerator;
import com.intellij.uiDesigner.compiler.FormErrorInfo;
import com.intellij.uiDesigner.compiler.NestedFormLoader;
import com.intellij.uiDesigner.compiler.Utils;
import com.intellij.uiDesigner.lw.CompiledClassPropertiesProvider;
import com.intellij.uiDesigner.lw.LwRootContainer;
import com.intellij.util.PathUtil;
import com.intellij.util.ui.UIUtil;
import com.sun.tools.javac.Main;
import gnu.trove.TIntObjectHashMap;
import junit.framework.TestCase;
import org.jetbrains.org.objectweb.asm.ClassWriter;
import javax.swing.*;
import javax.swing.border.EtchedBorder;
import javax.swing.border.TitledBorder;
import java.awt.*;
import java.io.*;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
/**
* @author yole
*/
public class AsmCodeGeneratorTest extends TestCase {
private MyNestedFormLoader myNestedFormLoader;
private MyClassFinder myClassFinder;
@Override
protected void setUp() throws Exception {
super.setUp();
myNestedFormLoader = new MyNestedFormLoader();
final String swingPath = PathUtil.getJarPathForClass(AbstractButton.class);
java.util.List<URL> cp = new ArrayList<URL>();
appendPath(cp, JBTabbedPane.class);
appendPath(cp, TIntObjectHashMap.class);
appendPath(cp, UIUtil.class);
appendPath(cp, SystemInfoRt.class);
appendPath(cp, ApplicationManager.class);
appendPath(cp, PathManager.getResourceRoot(this.getClass(), "/messages/UIBundle.properties"));
appendPath(cp, PathManager.getResourceRoot(this.getClass(), "/RuntimeBundle.properties"));
appendPath(cp, GridLayoutManager.class); // forms_rt
appendPath(cp, DataProvider.class);
myClassFinder = new MyClassFinder(
new URL[] {new File(swingPath).toURI().toURL()},
cp.toArray(new URL[cp.size()])
);
}
private static void appendPath(Collection<URL> container, Class cls) throws MalformedURLException {
final String path = PathUtil.getJarPathForClass(cls);
appendPath(container, path);
}
private static void appendPath(Collection<URL> container, String path) throws MalformedURLException {
container.add(new File(path).toURI().toURL());
}
@Override
protected void tearDown() throws Exception {
myNestedFormLoader = null;
final MyClassFinder classFinder = myClassFinder;
if (classFinder != null) {
classFinder.releaseResources();
myClassFinder = null;
}
super.tearDown();
}
private AsmCodeGenerator initCodeGenerator(final String formFileName, final String className) throws Exception {
final String testDataPath = PluginPathManager.getPluginHomePath("ui-designer") + "/testData/";
return initCodeGenerator(formFileName, className, testDataPath);
}
private AsmCodeGenerator initCodeGenerator(final String formFileName, final String className, final String testDataPath) throws Exception {
String tmpPath = FileUtil.getTempDirectory();
String formPath = testDataPath + formFileName;
String javaPath = testDataPath + className + ".java";
final int rc = Main.compile(new String[]{"-d", tmpPath, javaPath});
assertEquals(0, rc);
final String classPath = tmpPath + "/" + className + ".class";
final File classFile = new File(classPath);
assertTrue(classFile.exists());
final LwRootContainer rootContainer = loadFormData(formPath);
final AsmCodeGenerator codeGenerator = new AsmCodeGenerator(rootContainer, myClassFinder, myNestedFormLoader, false, new ClassWriter(ClassWriter.COMPUTE_FRAMES));
final FileInputStream classStream = new FileInputStream(classFile);
try {
codeGenerator.patchClass(classStream);
}
finally {
classStream.close();
FileUtil.delete(classFile);
final File[] inners = new File(tmpPath).listFiles(new FilenameFilter() {
@Override
public boolean accept(File dir, String name) {
return name.startsWith(className + "$") && name.endsWith(".class");
}
});
if (inners != null) {
for (File file : inners) FileUtil.delete(file);
}
}
return codeGenerator;
}
private LwRootContainer loadFormData(final String formPath) throws Exception {
String formData = FileUtil.loadFile(new File(formPath));
final CompiledClassPropertiesProvider provider = new CompiledClassPropertiesProvider(getClass().getClassLoader());
return Utils.getRootContainer(formData, provider);
}
private Class loadAndPatchClass(final String formFileName, final String className) throws Exception {
final AsmCodeGenerator codeGenerator = initCodeGenerator(formFileName, className);
byte[] patchedData = getVerifiedPatchedData(codeGenerator);
/*
FileOutputStream fos = new FileOutputStream("C:\\yole\\FormPreview27447\\MainPatched.class");
fos.write(patchedData);
fos.close();
*/
myClassFinder.addClassDefinition(className, patchedData);
return myClassFinder.getLoader().loadClass(className);
}
private static byte[] getVerifiedPatchedData(final AsmCodeGenerator codeGenerator) {
byte[] patchedData = codeGenerator.getPatchedData();
FormErrorInfo[] errors = codeGenerator.getErrors();
FormErrorInfo[] warnings = codeGenerator.getWarnings();
if (errors.length == 0 && warnings.length == 0) {
assertNotNull("Class patching failed but no errors or warnings were returned", patchedData);
}
else if (errors.length > 0) {
assertTrue(errors[0].getErrorMessage(), false);
}
else {
assertTrue(warnings[0].getErrorMessage(), false);
}
return patchedData;
}
private JComponent getInstrumentedRootComponent(final String formFileName, final String className) throws Exception {
Class cls = loadAndPatchClass(formFileName, className);
Field rootComponentField = cls.getField("myRootComponent");
rootComponentField.setAccessible(true);
Object instance = cls.newInstance();
return (JComponent)rootComponentField.get(instance);
}
public void testSimple() throws Exception {
JComponent rootComponent = getInstrumentedRootComponent("TestSimple.form", "BindingTest");
assertNotNull(rootComponent);
}
public void testNoSuchField() throws Exception {
AsmCodeGenerator generator = initCodeGenerator("TestNoSuchField.form", "BindingTest");
assertEquals("Cannot bind: field does not exist: BindingTest.myNoSuchField", generator.getErrors() [0].getErrorMessage());
}
public void testStaticField() throws Exception {
AsmCodeGenerator generator = initCodeGenerator("TestStaticField.form", "BindingTest");
assertEquals("Cannot bind: field is static: BindingTest.myStaticField", generator.getErrors() [0].getErrorMessage());
}
public void testFinalField() throws Exception {
AsmCodeGenerator generator = initCodeGenerator("TestFinalField.form", "BindingTest");
assertEquals("Cannot bind: field is final: BindingTest.myFinalField", generator.getErrors() [0].getErrorMessage());
}
public void testPrimitiveField() throws Exception {
AsmCodeGenerator generator = initCodeGenerator("TestPrimitiveField.form", "BindingTest");
assertEquals("Cannot bind: field is of primitive type: BindingTest.myIntField", generator.getErrors() [0].getErrorMessage());
}
public void testIncompatibleTypeField() throws Exception {
AsmCodeGenerator generator = initCodeGenerator("TestIncompatibleTypeField.form", "BindingTest");
assertEquals("Cannot bind: Incompatible types. Cannot assign javax.swing.JPanel to field BindingTest.myStringField", generator.getErrors() [0].getErrorMessage());
}
public void testGridLayout() throws Exception {
JComponent rootComponent = getInstrumentedRootComponent("TestGridConstraints.form", "BindingTest");
final LayoutManager layout = rootComponent.getLayout();
assertTrue(isInstanceOf(layout, GridLayoutManager.class.getName()));
assertEquals(1, invokeMethod(layout, "getRowCount"));
assertEquals(1, invokeMethod(layout, "getColumnCount"));
}
private static boolean isInstanceOf(Object object, final String className) throws ClassNotFoundException {
final Class<?> ethalon = object.getClass().getClassLoader().loadClass(className);
return ethalon.isAssignableFrom(object.getClass());
}
private static Object invokeMethod(Object obj, String methodName) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
return invokeMethod(obj, methodName, new Class[0], new Object[0]);
}
private static Object invokeMethod(Object obj, String methodName, Class[] params, Object[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
final Method method = findMethod(obj.getClass(), methodName, params);
return method.invoke(obj, args);
}
private static Method findMethod(Class<? extends Object> aClass, String methodName, Class[] params) {
try {
final Method method = aClass.getDeclaredMethod(methodName, params);
method.setAccessible(true);
return method;
}
catch (NoSuchMethodException ignored) {
}
final Class<?> parent = aClass.getSuperclass();
if (parent == null) {
return null;
}
return findMethod(parent, methodName, params);
}
public void testCardLayout() throws Exception {
JComponent rootComponent = getInstrumentedRootComponent("TestCardLayout.form", "BindingTest");
assertTrue(rootComponent.getLayout() instanceof CardLayout);
CardLayout cardLayout = (CardLayout) rootComponent.getLayout();
assertEquals(10, cardLayout.getHgap());
assertEquals(20, cardLayout.getVgap());
}
public void testCardLayoutShow() throws Exception {
JComponent rootComponent = getInstrumentedRootComponent("TestCardLayoutShow.form", "BindingTest");
assertTrue(rootComponent.getLayout() instanceof CardLayout);
assertEquals(rootComponent.getComponentCount(), 2);
assertFalse(rootComponent.getComponent(0).isVisible());
assertTrue(rootComponent.getComponent(1).isVisible());
}
public void testGridConstraints() throws Exception {
JComponent rootComponent = getInstrumentedRootComponent("TestGridConstraints.form", "BindingTest");
assertEquals(1, rootComponent.getComponentCount());
final LayoutManager layout = rootComponent.getLayout();
assertTrue(isInstanceOf(layout, GridLayoutManager.class.getName()));
final Object constraints = invokeMethod(layout, "getConstraints", new Class[] {int.class}, new Object[] {0});
assertTrue(isInstanceOf(constraints, GridConstraints.class.getName()));
assertEquals(1, invokeMethod(constraints, "getColSpan"));
assertEquals(1, invokeMethod(constraints, "getRowSpan"));
}
public void testIntProperty() throws Exception {
JComponent rootComponent = getInstrumentedRootComponent("TestIntProperty.form", "BindingTest");
assertEquals(1, rootComponent.getComponentCount());
JTextField textField = (JTextField) rootComponent.getComponent(0);
assertEquals(37, textField.getColumns());
assertEquals(false, textField.isEnabled());
}
public void testDoubleProperty() throws Exception {
JSplitPane splitPane = (JSplitPane)getInstrumentedRootComponent("TestDoubleProperty.form", "BindingTest");
assertEquals(0.1f, splitPane.getResizeWeight(), 0.001f);
}
public void testStringProperty() throws Exception {
JComponent rootComponent = getInstrumentedRootComponent("TestGridConstraints.form", "BindingTest");
JButton btn = (JButton) rootComponent.getComponent(0);
assertEquals("MyTestButton", btn.getText());
}
public void testSplitPane() throws Exception {
JSplitPane splitPane = (JSplitPane)getInstrumentedRootComponent("TestSplitPane.form", "BindingTest");
assertTrue(splitPane.getLeftComponent() instanceof JLabel);
assertTrue(splitPane.getRightComponent() instanceof JCheckBox);
}
public void testTabbedPane() throws Exception {
JTabbedPane tabbedPane = (JTabbedPane) getInstrumentedRootComponent("TestTabbedPane.form", "BindingTest");
assertEquals(2, tabbedPane.getTabCount());
assertEquals("First", tabbedPane.getTitleAt(0));
assertEquals("Test Value", tabbedPane.getTitleAt(1));
assertTrue(tabbedPane.getComponentAt(0) instanceof JLabel);
assertTrue(tabbedPane.getComponentAt(1) instanceof JButton);
}
public void testScrollPane() throws Exception {
JScrollPane scrollPane = (JScrollPane)getInstrumentedRootComponent("TestScrollPane.form", "BindingTest");
assertTrue(scrollPane.getViewport().getView() instanceof JList);
}
public void testBorder() throws Exception {
JPanel panel = (JPanel) getInstrumentedRootComponent("TestBorder.form", "BindingTest");
assertTrue(panel.getBorder() instanceof TitledBorder);
TitledBorder border = (TitledBorder) panel.getBorder();
assertEquals("BorderTitle", border.getTitle());
assertTrue(border.getBorder().toString(), border.getBorder() instanceof EtchedBorder);
}
public void testMnemonic() throws Exception {
JPanel panel = (JPanel) getInstrumentedRootComponent("TestMnemonics.form", "BindingTest");
JLabel label = (JLabel) panel.getComponent(0);
assertEquals("Mnemonic", label.getText());
assertEquals('M', label.getDisplayedMnemonic());
assertEquals(3, label.getDisplayedMnemonicIndex());
}
public void testMnemonicFromProperty() throws Exception {
JPanel panel = (JPanel) getInstrumentedRootComponent("TestMnemonicsProperty.form", "BindingTest");
JLabel label = (JLabel) panel.getComponent(0);
assertEquals("Mnemonic", label.getText());
assertEquals('M', label.getDisplayedMnemonic());
assertEquals(3, label.getDisplayedMnemonicIndex());
}
public void testGridBagLayout() throws Exception {
JPanel panel = (JPanel) getInstrumentedRootComponent("TestGridBag.form", "BindingTest");
assertTrue(panel.getLayout() instanceof GridBagLayout);
GridBagLayout gridBag = (GridBagLayout) panel.getLayout();
JButton btn = (JButton) panel.getComponent(0);
GridBagConstraints gbc = gridBag.getConstraints(btn);
assertNotNull(gbc);
assertEquals(2, gbc.gridheight);
assertEquals(2, gbc.gridwidth);
assertEquals(1.0, gbc.weightx, 0.01);
assertEquals(new Insets(1, 2, 3, 4), gbc.insets);
assertEquals(GridBagConstraints.HORIZONTAL, gbc.fill);
assertEquals(GridBagConstraints.NORTHWEST, gbc.anchor);
}
public void testGridBagSpacer() throws Exception {
JPanel panel = (JPanel) getInstrumentedRootComponent("TestGridBagSpacer.form", "BindingTest");
assertTrue(panel.getLayout() instanceof GridBagLayout);
assertTrue(panel.getComponent(0) instanceof JLabel);
assertTrue(panel.getComponent(1) instanceof JPanel);
GridBagLayout gridBag = (GridBagLayout) panel.getLayout();
GridBagConstraints gbc = gridBag.getConstraints(panel.getComponent(0));
assertEquals(0.0, gbc.weightx, 0.01);
assertEquals(0.0, gbc.weighty, 0.01);
gbc = gridBag.getConstraints(panel.getComponent(1));
assertEquals(0.0, gbc.weightx, 0.01);
assertEquals(1.0, gbc.weighty, 0.01);
}
public void testLabelFor() throws Exception {
JPanel panel = (JPanel) getInstrumentedRootComponent("TestLabelFor.form", "BindingTest");
JTextField textField = (JTextField) panel.getComponent(0);
JLabel label = (JLabel) panel.getComponent(1);
assertEquals(textField, label.getLabelFor());
}
public void testFieldReference() throws Exception {
Class cls = loadAndPatchClass("TestFieldReference.form", "FieldReferenceTest");
JPanel instance = (JPanel) cls.newInstance();
assertEquals(1, instance.getComponentCount());
}
public void testChainedConstructor() throws Exception {
Class cls = loadAndPatchClass("TestChainedConstructor.form", "ChainedConstructorTest");
Field scrollPaneField = cls.getField("myScrollPane");
Object instance = cls.newInstance();
JScrollPane scrollPane = (JScrollPane) scrollPaneField.get(instance);
assertNotNull(scrollPane.getViewport().getView());
}
public void testConditionalMethodCall() throws Exception {
JPanel panel = (JPanel) getInstrumentedRootComponent("TestConditionalMethodCall.form", "ConditionalMethodCallTest");
assertNotNull(panel);
}
public void testMethodCallInSuper() throws Exception {
// todo[yole] make this test work in headless
if (!GraphicsEnvironment.isHeadless()) {
Class cls = loadAndPatchClass("TestMethodCallInSuper.form", "MethodCallInSuperTest");
JDialog instance = (JDialog) cls.newInstance();
assertEquals(1, instance.getContentPane().getComponentCount());
}
}
public void testClientProp() throws Exception { // IDEA-46372
JComponent rootComponent = getInstrumentedRootComponent("TestClientProp.form", "BindingTest");
assertEquals(1, rootComponent.getComponentCount());
JTable table = (JTable) rootComponent.getComponent(0);
assertSame(Boolean.TRUE, table.getClientProperty("terminateEditOnFocusLost"));
}
public void testIdeadev14081() throws Exception {
// NOTE: That doesn't really reproduce the bug as it's dependent on a particular instrumentation sequence used during form preview
// (the nested form is instrumented with a new AsmCodeGenerator instance directly in the middle of instrumentation of the current form)
final String testDataPath = PluginPathManager.getPluginHomePath("ui-designer") + File.separatorChar + "testData" + File.separatorChar +
File.separatorChar + "formEmbedding" + File.separatorChar + "Ideadev14081" + File.separatorChar;
AsmCodeGenerator embeddedClassGenerator = initCodeGenerator("Embedded.form", "Embedded", testDataPath);
byte[] embeddedPatchedData = getVerifiedPatchedData(embeddedClassGenerator);
myClassFinder.addClassDefinition("Embedded", embeddedPatchedData);
myNestedFormLoader.registerNestedForm("Embedded.form", testDataPath + "Embedded.form");
AsmCodeGenerator mainClassGenerator = initCodeGenerator("Main.form", "Main", testDataPath);
byte[] mainPatchedData = getVerifiedPatchedData(mainClassGenerator);
/*
FileOutputStream fos = new FileOutputStream("C:\\yole\\FormPreview27447\\MainPatched.class");
fos.write(mainPatchedData);
fos.close();
*/
myClassFinder.addClassDefinition("Main", mainPatchedData);
final Class mainClass = myClassFinder.getLoader().loadClass("Main");
Object instance = mainClass.newInstance();
assert instance != null : mainClass;
}
private static class MyClassFinder extends InstrumentationClassFinder {
private static final String TEST_PROPERTY_CONTENT = "test=Test Value\nmnemonic=Mne&monic";
private final byte[] myTestProperties = Charset.defaultCharset().encode(TEST_PROPERTY_CONTENT).array();
private final Map<String, byte[]> myClassData = new HashMap<String, byte[]>();
private MyClassFinder(URL[] platformUrls, URL[] classpathUrls) {
super(platformUrls, classpathUrls);
}
public void addClassDefinition(String name, byte[] bytes) {
myClassData.put(name.replace('.', '/'), bytes);
}
protected InputStream lookupClassBeforeClasspath(String internalClassName) {
final byte[] bytes = myClassData.get(internalClassName);
if (bytes != null) {
return new ByteArrayInputStream(bytes);
}
return null;
}
public InputStream getResourceAsStream(String name) throws IOException {
if (name.equals("TestProperties.properties")) {
return new ByteArrayInputStream(myTestProperties, 0, TEST_PROPERTY_CONTENT.length());
}
return super.getResourceAsStream(name);
}
}
private class MyNestedFormLoader implements NestedFormLoader {
private final Map<String, String> myFormMap = new HashMap<String, String>();
public void registerNestedForm(String formName, String fileName) {
myFormMap.put(formName, fileName);
}
@Override
public LwRootContainer loadForm(String formFileName) throws Exception {
final String fileName = myFormMap.get(formFileName);
if (fileName != null) {
return loadFormData(fileName);
}
throw new UnsupportedOperationException("No nested form found for name " + formFileName);
}
@Override
public String getClassToBindName(LwRootContainer container) {
return container.getClassToBind();
}
}
}