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