blob: b33256d242d8afdb387ad5fe8a2c569eeb71d859 [file] [log] [blame]
/*
* Copyright 2000-2014 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.compiler;
import com.intellij.compiler.instrumentation.InstrumentationClassFinder;
import com.intellij.uiDesigner.UIFormXmlConstants;
import com.intellij.uiDesigner.lw.*;
import com.intellij.uiDesigner.shared.BorderType;
import org.jetbrains.asm4.*;
import org.jetbrains.asm4.Label;
import org.jetbrains.asm4.commons.GeneratorAdapter;
import org.jetbrains.asm4.commons.Method;
import javax.swing.*;
import javax.swing.border.Border;
import java.awt.*;
import java.io.*;
import java.lang.reflect.Modifier;
import java.util.*;
/**
* @author yole
*/
public class AsmCodeGenerator {
private final LwRootContainer myRootContainer;
private final InstrumentationClassFinder myFinder;
private final ArrayList myErrors;
private final ArrayList myWarnings;
private final Map myIdToLocalMap = new HashMap();
private static final String CONSTRUCTOR_NAME = "<init>";
private String myClassToBind;
private byte[] myPatchedData;
private static final Map myContainerLayoutCodeGenerators = new HashMap();
private static final Map myComponentLayoutCodeGenerators = new HashMap();
private static final Map myPropertyCodeGenerators = new LinkedHashMap(); // need LinkedHashMap for deterministic iteration
public static final String SETUP_METHOD_NAME = "$$$setupUI$$$";
public static final String GET_ROOT_COMPONENT_METHOD_NAME = "$$$getRootComponent$$$";
public static final String CREATE_COMPONENTS_METHOD_NAME = "createUIComponents";
public static final String LOAD_LABEL_TEXT_METHOD = "$$$loadLabelText$$$";
public static final String LOAD_BUTTON_TEXT_METHOD = "$$$loadButtonText$$$";
private static final Type ourButtonGroupType = Type.getType(ButtonGroup.class);
private static final Type ourBorderFactoryType = Type.getType(BorderFactory.class);
private static final Type ourBorderType = Type.getType(Border.class);
private static final Method ourCreateTitledBorderMethod = Method.getMethod(
"javax.swing.border.TitledBorder createTitledBorder(javax.swing.border.Border,java.lang.String,int,int,java.awt.Font,java.awt.Color)");
private static final String ourBorderFactoryClientProperty = "BorderFactoryClass";
private final NestedFormLoader myFormLoader;
private final boolean myIgnoreCustomCreation;
private final ClassWriter myClassWriter;
static {
myContainerLayoutCodeGenerators.put(UIFormXmlConstants.LAYOUT_INTELLIJ, new GridLayoutCodeGenerator());
myContainerLayoutCodeGenerators.put(UIFormXmlConstants.LAYOUT_GRIDBAG, new GridBagLayoutCodeGenerator());
myContainerLayoutCodeGenerators.put(UIFormXmlConstants.LAYOUT_BORDER, new SimpleLayoutCodeGenerator(Type.getType(BorderLayout.class)));
myContainerLayoutCodeGenerators.put(UIFormXmlConstants.LAYOUT_CARD, new CardLayoutCodeGenerator());
myContainerLayoutCodeGenerators.put(UIFormXmlConstants.LAYOUT_FLOW, new FlowLayoutCodeGenerator());
myComponentLayoutCodeGenerators.put(LwSplitPane.class, new SplitPaneLayoutCodeGenerator());
myComponentLayoutCodeGenerators.put(LwTabbedPane.class, new TabbedPaneLayoutCodeGenerator());
myComponentLayoutCodeGenerators.put(LwScrollPane.class, new ScrollPaneLayoutCodeGenerator());
myComponentLayoutCodeGenerators.put(LwToolBar.class, new ToolBarLayoutCodeGenerator());
myPropertyCodeGenerators.put(String.class.getName(), new StringPropertyCodeGenerator());
myPropertyCodeGenerators.put(Dimension.class.getName(), new DimensionPropertyCodeGenerator());
myPropertyCodeGenerators.put(Insets.class.getName(), new InsetsPropertyCodeGenerator());
myPropertyCodeGenerators.put(Rectangle.class.getName(), new RectanglePropertyCodeGenerator());
myPropertyCodeGenerators.put(Color.class.getName(), new ColorPropertyCodeGenerator());
myPropertyCodeGenerators.put(Font.class.getName(), new FontPropertyCodeGenerator());
myPropertyCodeGenerators.put(Icon.class.getName(), new IconPropertyCodeGenerator());
myPropertyCodeGenerators.put(ListModel.class.getName(), new ListModelPropertyCodeGenerator(DefaultListModel.class));
myPropertyCodeGenerators.put(ComboBoxModel.class.getName(), new ListModelPropertyCodeGenerator(DefaultComboBoxModel.class));
myPropertyCodeGenerators.put("java.lang.Enum", new EnumPropertyCodeGenerator());
}
public AsmCodeGenerator(LwRootContainer rootContainer,
InstrumentationClassFinder finder,
NestedFormLoader formLoader,
final boolean ignoreCustomCreation,
final ClassWriter classWriter) {
myFormLoader = formLoader;
myIgnoreCustomCreation = ignoreCustomCreation;
if (finder == null){
throw new IllegalArgumentException("loader cannot be null");
}
if (rootContainer == null){
throw new IllegalArgumentException("rootContainer cannot be null");
}
myRootContainer = rootContainer;
myFinder = finder;
myErrors = new ArrayList();
myWarnings = new ArrayList();
myClassWriter = classWriter;
}
public void patchFile(final File classFile) {
if (!classFile.exists()) {
myErrors.add(new FormErrorInfo(null, "Class to bind does not exist: " + myRootContainer.getClassToBind()));
return;
}
FileInputStream fis;
try {
byte[] patchedData;
fis = new FileInputStream(classFile);
try {
patchedData = patchClass(fis);
if (patchedData == null) {
return;
}
}
finally {
fis.close();
}
FileOutputStream fos = new FileOutputStream(classFile);
try {
fos.write(patchedData);
}
finally {
fos.close();
}
}
catch (IOException e) {
myErrors.add(new FormErrorInfo(null, "Cannot read or write class file " + classFile.getPath() + ": " + e.toString()));
}
catch(IllegalStateException e) {
myErrors.add(new FormErrorInfo(null, "Unexpected data in form file when patching class " + classFile.getPath() + ": " + e.toString()));
}
}
public byte[] patchClass(InputStream classStream) {
try {
final ClassReader reader = new ClassReader(classStream);
return patchClass(reader);
}
catch (IOException e) {
myErrors.add(new FormErrorInfo(null, "Error reading class data stream"));
return null;
}
}
public byte[] patchClass(ClassReader reader) {
myClassToBind = myRootContainer.getClassToBind();
if (myClassToBind == null){
myWarnings.add(new FormErrorInfo(null, "No class to bind specified"));
return null;
}
if (myRootContainer.getComponentCount() != 1) {
myErrors.add(new FormErrorInfo(null, "There should be only one component at the top level"));
return null;
}
String nonEmptyPanel = Utils.findNotEmptyPanelWithXYLayout(myRootContainer.getComponent(0));
if (nonEmptyPanel != null) {
myErrors.add(new FormErrorInfo(nonEmptyPanel,
"There are non empty panels with XY layout. Please lay them out in a grid."));
return null;
}
FirstPassClassVisitor visitor = new FirstPassClassVisitor();
reader.accept(visitor, 0);
reader.accept(new FormClassVisitor(myClassWriter, visitor.isExplicitSetupCall()), 0);
myPatchedData = myClassWriter.toByteArray();
return myPatchedData;
}
public FormErrorInfo[] getErrors() {
return (FormErrorInfo[])myErrors.toArray(new FormErrorInfo[myErrors.size()]);
}
public FormErrorInfo[] getWarnings() {
return (FormErrorInfo[])myWarnings.toArray(new FormErrorInfo[myWarnings.size()]);
}
public byte[] getPatchedData() {
return myPatchedData;
}
static void pushPropValue(GeneratorAdapter generator, String propertyClass, Object value) {
PropertyCodeGenerator codeGen = (PropertyCodeGenerator)myPropertyCodeGenerators.get(propertyClass);
if (codeGen == null) {
throw new RuntimeException("Unknown property class " + propertyClass);
}
codeGen.generatePushValue(generator, value);
}
static InstrumentationClassFinder.PseudoClass getComponentClass(String className, final InstrumentationClassFinder finder) throws CodeGenerationException {
try {
return finder.loadClass(className);
}
catch (ClassNotFoundException e) {
throw new CodeGenerationException(null, "Class not found: " + className);
}
catch(UnsupportedClassVersionError e) {
throw new CodeGenerationException(null, "Unsupported class version error: " + className);
}
catch (IOException e) {
throw new CodeGenerationException(null, e.getMessage(), e);
}
}
public static Type typeFromClassName(final String className) {
return Type.getType("L" + className.replace('.', '/') + ";");
}
class FormClassVisitor extends ClassVisitor {
private String myClassName;
private String mySuperName;
private final Map myFieldDescMap = new HashMap();
private final Map myFieldAccessMap = new HashMap();
private boolean myHaveCreateComponentsMethod = false;
private int myCreateComponentsAccess;
private final boolean myExplicitSetupCall;
public FormClassVisitor(final ClassVisitor cv, final boolean explicitSetupCall) {
super(Opcodes.ASM4, cv);
myExplicitSetupCall = explicitSetupCall;
}
public void visit(final int version,
final int access,
final String name,
final String signature,
final String superName,
final String[] interfaces) {
super.visit(version, access, name, signature, superName, interfaces);
myClassName = name;
mySuperName = superName;
for (Iterator iterator = myPropertyCodeGenerators.values().iterator(); iterator.hasNext();) {
PropertyCodeGenerator propertyCodeGenerator = (PropertyCodeGenerator)iterator.next();
propertyCodeGenerator.generateClassStart(this, name, myFinder);
}
}
public String getClassName() {
return myClassName;
}
public MethodVisitor visitMethod(final int access,
final String name,
final String desc,
final String signature,
final String[] exceptions) {
if (name.equals(SETUP_METHOD_NAME) || name.equals(GET_ROOT_COMPONENT_METHOD_NAME) ||
name.equals(LOAD_BUTTON_TEXT_METHOD) || name.equals(LOAD_LABEL_TEXT_METHOD)) {
return null;
}
if (name.equals(CREATE_COMPONENTS_METHOD_NAME) && desc.equals("()V")) {
myHaveCreateComponentsMethod = true;
myCreateComponentsAccess = access;
}
final MethodVisitor methodVisitor = super.visitMethod(access, name, desc, signature, exceptions);
if (name.equals(CONSTRUCTOR_NAME) && !myExplicitSetupCall) {
return new FormConstructorVisitor(methodVisitor, myClassName, mySuperName);
}
return methodVisitor;
}
MethodVisitor visitNewMethod(final int access,
final String name,
final String desc,
final String signature,
final String[] exceptions) {
return super.visitMethod(access, name, desc, signature, exceptions);
}
public FieldVisitor visitField(final int access, final String name, final String desc, final String signature, final Object value) {
myFieldDescMap.put(name, desc);
myFieldAccessMap.put(name, new Integer(access));
return super.visitField(access, name, desc, signature, value);
}
public void visitEnd() {
final boolean haveCustomCreateComponents = Utils.getCustomCreateComponentCount(myRootContainer) > 0 &&
!myIgnoreCustomCreation;
if (haveCustomCreateComponents && !myHaveCreateComponentsMethod) {
myErrors.add(new FormErrorInfo(null, "Form contains components with Custom Create option but no createUIComponents() method"));
}
Method method = Method.getMethod("void " + SETUP_METHOD_NAME + " ()");
GeneratorAdapter generator = new GeneratorAdapter(Opcodes.ACC_PRIVATE | Opcodes.ACC_SYNTHETIC, method, null, null, cv);
if (haveCustomCreateComponents && myHaveCreateComponentsMethod) {
generator.visitVarInsn(Opcodes.ALOAD, 0);
int opcode = myCreateComponentsAccess == Opcodes.ACC_PRIVATE ? Opcodes.INVOKESPECIAL : Opcodes.INVOKEVIRTUAL;
generator.visitMethodInsn(opcode, myClassName, CREATE_COMPONENTS_METHOD_NAME, "()V");
}
buildSetupMethod(generator);
final String rootBinding = myRootContainer.getComponent(0).getBinding();
if (rootBinding != null && myFieldDescMap.containsKey(rootBinding)) {
buildGetRootComponenMethod();
}
for (Iterator iterator = myPropertyCodeGenerators.values().iterator(); iterator.hasNext();) {
PropertyCodeGenerator propertyCodeGenerator = (PropertyCodeGenerator)iterator.next();
propertyCodeGenerator.generateClassEnd(this);
}
super.visitEnd();
}
private void buildGetRootComponenMethod() {
final Type componentType = Type.getType(JComponent.class);
final Method method = new Method(GET_ROOT_COMPONENT_METHOD_NAME, componentType, new Type[0]);
GeneratorAdapter generator = new GeneratorAdapter(Opcodes.ACC_PUBLIC | Opcodes.ACC_SYNTHETIC, method, null, null, cv);
final LwComponent topComponent = (LwComponent)myRootContainer.getComponent(0);
final String binding = topComponent.getBinding();
generator.loadThis();
generator.getField(typeFromClassName(myClassName), binding,
Type.getType((String) myFieldDescMap.get(binding)));
generator.returnValue();
generator.endMethod();
}
private void buildSetupMethod(final GeneratorAdapter generator) {
try {
final LwComponent topComponent = (LwComponent)myRootContainer.getComponent(0);
generateSetupCodeForComponent(topComponent, generator, -1);
generateComponentReferenceProperties(topComponent, generator);
generateButtonGroups(myRootContainer, generator);
}
catch (CodeGenerationException e) {
myErrors.add(new FormErrorInfo(e.getComponentId(), e.getMessage()));
}
generator.returnValue();
generator.endMethod();
}
private void generateSetupCodeForComponent(final LwComponent lwComponent,
final GeneratorAdapter generator,
final int parentLocal) throws CodeGenerationException {
String className;
if (lwComponent instanceof LwNestedForm) {
LwRootContainer nestedFormContainer;
LwNestedForm nestedForm = (LwNestedForm) lwComponent;
if (myFormLoader == null) {
throw new CodeGenerationException(null, "Attempt to compile nested form with no nested form loader specified");
}
try {
nestedFormContainer = myFormLoader.loadForm(nestedForm.getFormFileName());
}
catch (Exception e) {
throw new CodeGenerationException(lwComponent.getId(), e.getMessage());
}
// if nested form is empty, ignore
if (nestedFormContainer.getComponentCount() == 0) {
return;
}
if (nestedFormContainer.getComponent(0).getBinding() == null) {
throw new CodeGenerationException(lwComponent.getId(), "No binding on root component of nested form " + nestedForm.getFormFileName());
}
try {
Utils.validateNestedFormLoop(nestedForm.getFormFileName(), myFormLoader);
}
catch (RecursiveFormNestingException e) {
throw new CodeGenerationException(lwComponent.getId(), "Recursive form nesting is not allowed");
}
className = myFormLoader.getClassToBindName(nestedFormContainer);
}
else {
className = getComponentCodeGenerator(lwComponent.getParent()).mapComponentClass(lwComponent.getComponentClassName());
}
Type componentType = typeFromClassName(className);
int componentLocal = generator.newLocal(componentType);
myIdToLocalMap.put(lwComponent.getId(), new Integer(componentLocal));
InstrumentationClassFinder.PseudoClass componentClass = getComponentClass(className, myFinder);
validateFieldBinding(lwComponent, componentClass);
if (myIgnoreCustomCreation) {
try {
boolean creatable = true;
if ((componentClass.getModifiers() & (Modifier.PRIVATE | Modifier.ABSTRACT)) != 0) {
creatable = false;
}
else {
if (!componentClass.hasDefaultPublicConstructor()) {
creatable = false;
}
}
if (!creatable) {
componentClass = Utils.suggestReplacementClass(componentClass);
componentType = Type.getType(componentClass.getDescriptor());
}
}
catch (ClassNotFoundException e) {
throw new CodeGenerationException(lwComponent.getId(), e.getMessage(), e);
}
catch (IOException e) {
throw new CodeGenerationException(lwComponent.getId(), e.getMessage(), e);
}
}
if (!lwComponent.isCustomCreate() || myIgnoreCustomCreation) {
generator.newInstance(componentType);
generator.dup();
generator.invokeConstructor(componentType, Method.getMethod("void <init>()"));
generator.storeLocal(componentLocal);
generateFieldBinding(lwComponent, generator, componentLocal);
}
else {
final String binding = lwComponent.getBinding();
if (binding == null) {
throw new CodeGenerationException(lwComponent.getId(),
"Only components bound to fields can have custom creation code");
}
generator.loadThis();
generator.getField(getMainClassType(), binding,
Type.getType((String)myFieldDescMap.get(binding)));
generator.storeLocal(componentLocal);
}
if (lwComponent instanceof LwContainer) {
LwContainer lwContainer = (LwContainer) lwComponent;
if (!lwContainer.isCustomCreate() || lwContainer.getComponentCount() > 0) {
getComponentCodeGenerator(lwContainer).generateContainerLayout(lwContainer, generator, componentLocal);
}
}
generateComponentProperties(lwComponent, componentClass, generator, componentLocal);
// add component to parent
if (!(lwComponent.getParent() instanceof LwRootContainer)) {
final LayoutCodeGenerator parentCodeGenerator = getComponentCodeGenerator(lwComponent.getParent());
if (lwComponent instanceof LwNestedForm) {
componentLocal = getNestedFormComponent(generator, componentClass, componentLocal);
}
parentCodeGenerator.generateComponentLayout(lwComponent, generator, componentLocal, parentLocal);
}
if (lwComponent instanceof LwContainer) {
LwContainer container = (LwContainer) lwComponent;
generateBorder(container, generator, componentLocal);
for(int i=0; i<container.getComponentCount(); i++) {
generateSetupCodeForComponent((LwComponent) container.getComponent(i), generator, componentLocal);
}
}
}
private int getNestedFormComponent(GeneratorAdapter generator, InstrumentationClassFinder.PseudoClass componentClass, int formLocal) throws CodeGenerationException {
final Type componentType = Type.getType(JComponent.class);
int componentLocal = generator.newLocal(componentType);
generator.loadLocal(formLocal);
generator.invokeVirtual(Type.getType(componentClass.getDescriptor()), new Method(GET_ROOT_COMPONENT_METHOD_NAME, componentType, new Type[0]));
generator.storeLocal(componentLocal);
return componentLocal;
}
private LayoutCodeGenerator getComponentCodeGenerator(final LwContainer container) {
LayoutCodeGenerator generator = (LayoutCodeGenerator) myComponentLayoutCodeGenerators.get(container.getClass());
if (generator != null) {
return generator;
}
LwContainer parent = container;
while(parent != null) {
final String layoutManager = parent.getLayoutManager();
if (layoutManager != null && layoutManager.length() > 0) {
if (layoutManager.equals(UIFormXmlConstants.LAYOUT_FORM) &&
!myContainerLayoutCodeGenerators.containsKey(UIFormXmlConstants.LAYOUT_FORM)) {
myContainerLayoutCodeGenerators.put(UIFormXmlConstants.LAYOUT_FORM, new FormLayoutCodeGenerator());
}
generator = (LayoutCodeGenerator) myContainerLayoutCodeGenerators.get(layoutManager);
if (generator != null) {
return generator;
}
}
parent = parent.getParent();
}
return GridLayoutCodeGenerator.INSTANCE;
}
private void generateComponentProperties(final LwComponent lwComponent,
final InstrumentationClassFinder.PseudoClass componentClass,
final GeneratorAdapter generator,
final int componentLocal) throws CodeGenerationException {
// introspected properties
final LwIntrospectedProperty[] introspectedProperties = lwComponent.getAssignedIntrospectedProperties();
for (int i = 0; i < introspectedProperties.length; i++) {
final LwIntrospectedProperty property = introspectedProperties[i];
if (property instanceof LwIntroComponentProperty) {
continue;
}
final String propertyClass = property.getCodeGenPropertyClassName();
if (myIgnoreCustomCreation) {
try {
final String descriptor;
// convert wrapper classes to primitive
if (propertyClass.equals(Integer.class.getName())) {
descriptor = "(I)V";
}
else if (propertyClass.equals(Boolean.class.getName())) {
descriptor = "(Z)V";
}
else if (propertyClass.equals(Double.class.getName())) {
descriptor = "(D)V";
}
else if (propertyClass.equals(Float.class.getName())) {
descriptor = "(F)V";
}
else if (propertyClass.equals(Long.class.getName())) {
descriptor = "(L)V";
}
else if (propertyClass.equals(Byte.class.getName())) {
descriptor = "(B)V";
}
else if (propertyClass.equals(Short.class.getName())) {
descriptor = "(S)V";
}
else if (propertyClass.equals(Character.class.getName())) {
descriptor = "(C)V";
}
else {
descriptor = "(L"+Class.forName(propertyClass).getName().replace('.', '/') + ";)V";
}
final InstrumentationClassFinder.PseudoMethod setter = componentClass.findMethodInHierarchy(property.getWriteMethodName(),
descriptor);
if (setter == null) {
continue;
}
}
catch (Exception e) {
continue;
}
}
final PropertyCodeGenerator propGen = (PropertyCodeGenerator) myPropertyCodeGenerators.get(propertyClass);
try {
if (propGen != null && propGen.generateCustomSetValue(lwComponent, componentClass, property, generator, componentLocal, myClassName)) {
continue;
}
}
catch (IOException e) {
throw new CodeGenerationException(lwComponent.getId(), e.getMessage(), e);
}
catch (ClassNotFoundException e) {
throw new CodeGenerationException(lwComponent.getId(), e.getMessage(), e);
}
generator.loadLocal(componentLocal);
Object value = lwComponent.getPropertyValue(property);
Type setterArgType;
if (propertyClass.equals(Integer.class.getName())) {
generator.push(((Integer) value).intValue());
setterArgType = Type.INT_TYPE;
}
else if (propertyClass.equals(Boolean.class.getName())) {
generator.push(((Boolean) value).booleanValue());
setterArgType = Type.BOOLEAN_TYPE;
}
else if (propertyClass.equals(Double.class.getName())) {
generator.push(((Double) value).doubleValue());
setterArgType = Type.DOUBLE_TYPE;
}
else if (propertyClass.equals(Float.class.getName())) {
generator.push(((Float) value).floatValue());
setterArgType = Type.FLOAT_TYPE;
}
else if (propertyClass.equals(Long.class.getName())) {
generator.push(((Long) value).longValue());
setterArgType = Type.LONG_TYPE;
}
else if (propertyClass.equals(Short.class.getName())) {
generator.push(((Short) value).intValue());
setterArgType = Type.SHORT_TYPE;
}
else if (propertyClass.equals(Byte.class.getName())) {
generator.push(((Byte) value).intValue());
setterArgType = Type.BYTE_TYPE;
}
else if (propertyClass.equals(Character.class.getName())) {
generator.push(((Character) value).charValue());
setterArgType = Type.CHAR_TYPE;
}
else {
if (propGen == null) {
continue;
}
propGen.generatePushValue(generator, value);
setterArgType = typeFromClassName(property.getPropertyClassName());
}
Type declaringType = (property.getDeclaringClassName() != null)
? typeFromClassName(property.getDeclaringClassName())
: Type.getType(componentClass.getDescriptor());
generator.invokeVirtual(declaringType, new Method(property.getWriteMethodName(),
Type.VOID_TYPE, new Type[] { setterArgType } ));
}
generateClientProperties(lwComponent, componentClass, generator, componentLocal);
}
private void generateClientProperties(final LwComponent lwComponent,
final InstrumentationClassFinder.PseudoClass componentClass,
final GeneratorAdapter generator,
final int componentLocal) throws CodeGenerationException {
HashMap props = lwComponent.getDelegeeClientProperties();
for (Iterator iterator = props.entrySet().iterator(); iterator.hasNext();) {
Map.Entry e = (Map.Entry) iterator.next();
generator.loadLocal(componentLocal);
generator.push((String) e.getKey());
Object value = e.getValue();
if (value instanceof StringDescriptor) {
generator.push(((StringDescriptor) value).getValue());
}
else if (value instanceof Boolean) {
boolean boolValue = ((Boolean) value).booleanValue();
Type booleanType = Type.getType(Boolean.class);
if (boolValue) {
generator.getStatic(booleanType, "TRUE", booleanType);
}
else {
generator.getStatic(booleanType, "FALSE", booleanType);
}
}
else {
Type valueType = Type.getType(value.getClass());
generator.newInstance(valueType);
generator.dup();
if (value instanceof Integer) {
generator.push(((Integer)value).intValue());
generator.invokeConstructor(valueType, Method.getMethod("void <init>(int)"));
}
else if (value instanceof Double) {
generator.push(((Double)value).doubleValue());
generator.invokeConstructor(valueType, Method.getMethod("void <init>(double)"));
}
else {
throw new CodeGenerationException(lwComponent.getId(), "Unknown client property value type");
}
}
Type componentType = Type.getType(componentClass.getDescriptor());
Type objectType = Type.getType(Object.class);
generator.invokeVirtual(componentType, new Method("putClientProperty",
Type.VOID_TYPE, new Type[] { objectType, objectType } ));
}
}
private void generateComponentReferenceProperties(final LwComponent component,
final GeneratorAdapter generator) throws CodeGenerationException {
if (component instanceof LwNestedForm) return;
int componentLocal = ((Integer) myIdToLocalMap.get(component.getId())).intValue();
final LayoutCodeGenerator layoutCodeGenerator = getComponentCodeGenerator(component.getParent());
InstrumentationClassFinder.PseudoClass componentClass = getComponentClass(layoutCodeGenerator.mapComponentClass(component.getComponentClassName()), myFinder);
final LwIntrospectedProperty[] introspectedProperties = component.getAssignedIntrospectedProperties();
for (int i = 0; i < introspectedProperties.length; i++) {
final LwIntrospectedProperty property = introspectedProperties[i];
if (property instanceof LwIntroComponentProperty) {
String targetId = (String) component.getPropertyValue(property);
if (targetId != null && targetId.length() > 0) {
// we may have a reference property pointing to a component which is
// no longer valid
final Integer targetLocalInt = (Integer)myIdToLocalMap.get(targetId);
if (targetLocalInt != null) {
int targetLocal = targetLocalInt.intValue();
generator.loadLocal(componentLocal);
generator.loadLocal(targetLocal);
Type declaringType = (property.getDeclaringClassName() != null)
? typeFromClassName(property.getDeclaringClassName())
: Type.getType(componentClass.getDescriptor());
generator.invokeVirtual(declaringType,
new Method(property.getWriteMethodName(),
Type.VOID_TYPE, new Type[] { typeFromClassName(property.getPropertyClassName()) } ));
}
}
}
}
if (component instanceof LwContainer) {
LwContainer container = (LwContainer) component;
for(int i=0; i<container.getComponentCount(); i++) {
generateComponentReferenceProperties((LwComponent) container.getComponent(i), generator);
}
}
}
private void generateButtonGroups(final LwRootContainer rootContainer, final GeneratorAdapter generator) throws CodeGenerationException {
IButtonGroup[] groups = rootContainer.getButtonGroups();
if (groups.length > 0) {
try {
InstrumentationClassFinder.PseudoClass buttonGroupClass = null; // cached
int groupLocal = generator.newLocal(ourButtonGroupType);
for(int groupIndex=0; groupIndex<groups.length; groupIndex++) {
String[] ids = groups [groupIndex].getComponentIds();
if (ids.length > 0) {
generator.newInstance(ourButtonGroupType);
generator.dup();
generator.invokeConstructor(ourButtonGroupType, Method.getMethod("void <init>()"));
generator.storeLocal(groupLocal);
if (groups [groupIndex].isBound() && !myIgnoreCustomCreation) {
if (buttonGroupClass == null) {
buttonGroupClass = myFinder.loadClass(ButtonGroup.class.getName());
}
validateFieldClass(groups [groupIndex].getName(), buttonGroupClass, null);
generator.loadThis();
generator.loadLocal(groupLocal);
generator.putField(getMainClassType(), groups [groupIndex].getName(), ourButtonGroupType);
}
for(int i = 0; i<ids.length; i++) {
Integer localInt = (Integer) myIdToLocalMap.get(ids [i]);
if (localInt != null) {
generator.loadLocal(groupLocal);
generator.loadLocal(localInt.intValue());
generator.invokeVirtual(ourButtonGroupType, Method.getMethod("void add(javax.swing.AbstractButton)"));
}
}
}
}
}
catch (IOException e) {
throw new CodeGenerationException(rootContainer.getId(), e.getMessage(), e);
}
catch (ClassNotFoundException e) {
throw new CodeGenerationException(rootContainer.getId(), e.getMessage(), e);
}
}
}
private void generateFieldBinding(final LwComponent lwComponent,
final GeneratorAdapter generator,
final int componentLocal) throws CodeGenerationException {
final String binding = lwComponent.getBinding();
if (binding != null) {
Integer access = (Integer) myFieldAccessMap.get(binding);
if ((access.intValue() & Opcodes.ACC_STATIC) != 0) {
throw new CodeGenerationException(lwComponent.getId(), "Cannot bind: field is static: " + myClassToBind + "." + binding);
}
if ((access.intValue() & Opcodes.ACC_FINAL) != 0) {
throw new CodeGenerationException(lwComponent.getId(), "Cannot bind: field is final: " + myClassToBind + "." + binding);
}
generator.loadThis();
generator.loadLocal(componentLocal);
generator.putField(getMainClassType(), binding,
Type.getType((String)myFieldDescMap.get(binding)));
}
}
private Type getMainClassType() {
return Type.getType("L" + myClassName + ";");
}
private void validateFieldBinding(LwComponent component, final InstrumentationClassFinder.PseudoClass componentClass) throws CodeGenerationException {
String binding = component.getBinding();
if (binding == null) return;
validateFieldClass(binding, componentClass, component.getId());
}
private void validateFieldClass(String binding, InstrumentationClassFinder.PseudoClass componentClass, String componentId) throws CodeGenerationException {
if (!myFieldDescMap.containsKey(binding)) {
throw new CodeGenerationException(componentId, "Cannot bind: field does not exist: " + myClassToBind + "." + binding);
}
final Type fieldType = Type.getType((String)myFieldDescMap.get(binding));
if (fieldType.getSort() != Type.OBJECT) {
throw new CodeGenerationException(componentId, "Cannot bind: field is of primitive type: " + myClassToBind + "." + binding);
}
try {
final InstrumentationClassFinder.PseudoClass fieldClass = myFinder.loadClass(fieldType.getClassName());
if (!fieldClass.isAssignableFrom(componentClass)) {
throw new CodeGenerationException(componentId, "Cannot bind: Incompatible types. Cannot assign " + componentClass.getName().replace('/', '.') + " to field " + myClassToBind + "." + binding);
}
}
catch (ClassNotFoundException e) {
throw new CodeGenerationException(componentId, "Class not found: " + fieldType.getClassName());
}
catch (IOException e) {
throw new CodeGenerationException(componentId, e.getMessage(), e);
}
}
private void generateBorder(final LwContainer container, final GeneratorAdapter generator, final int componentLocal) {
final BorderType borderType = container.getBorderType();
final StringDescriptor borderTitle = container.getBorderTitle();
final String borderFactoryMethodName = borderType.getBorderFactoryMethodName();
final boolean borderNone = borderType.equals(BorderType.NONE);
if (!borderNone || borderTitle != null) {
// object to invoke setBorder
generator.loadLocal(componentLocal);
if (!borderNone) {
if (borderType.equals(BorderType.LINE)) {
if (container.getBorderColor() == null) {
Type colorType = Type.getType(Color.class);
generator.getStatic(colorType, "black", colorType);
}
else {
pushPropValue(generator, Color.class.getName(), container.getBorderColor());
}
generator.invokeStatic(ourBorderFactoryType,
new Method(borderFactoryMethodName, ourBorderType,
new Type[] { Type.getType(Color.class) } ));
}
else if (borderType.equals(BorderType.EMPTY) && container.getBorderSize() != null) {
Insets size = container.getBorderSize();
generator.push(size.top);
generator.push(size.left);
generator.push(size.bottom);
generator.push(size.right);
generator.invokeStatic(ourBorderFactoryType,
new Method(borderFactoryMethodName, ourBorderType,
new Type[] { Type.INT_TYPE, Type.INT_TYPE, Type.INT_TYPE, Type.INT_TYPE }));
}
else {
generator.invokeStatic(ourBorderFactoryType,
new Method(borderFactoryMethodName, ourBorderType, new Type[0]));
}
}
else {
generator.push((String) null);
}
if (borderTitle != null) {
pushBorderProperties(container, generator, borderTitle, componentLocal);
Type borderFactoryType = ourBorderFactoryType;
StringDescriptor borderFactoryValue = (StringDescriptor)container.getDelegeeClientProperties().get(ourBorderFactoryClientProperty);
if (borderFactoryValue == null && Boolean.valueOf(System.getProperty("idea.is.internal")).booleanValue()) {
borderFactoryValue = StringDescriptor.create("com.intellij.ui.IdeBorderFactory$PlainSmallWithIndent");
container.getDelegeeClientProperties().put(ourBorderFactoryClientProperty, borderFactoryValue);
}
if (borderFactoryValue != null && borderFactoryValue.getValue().length() != 0) {
borderFactoryType = typeFromClassName(borderFactoryValue.getValue());
}
generator.invokeStatic(borderFactoryType, ourCreateTitledBorderMethod);
}
// set border
generator.invokeVirtual(Type.getType(JComponent.class),
Method.getMethod("void setBorder(javax.swing.border.Border)"));
}
}
private void pushBorderProperties(final LwContainer container, final GeneratorAdapter generator, final StringDescriptor borderTitle,
final int componentLocal) {
pushPropValue(generator, "java.lang.String", borderTitle);
generator.push(container.getBorderTitleJustification());
generator.push(container.getBorderTitlePosition());
final FontDescriptor font = container.getBorderTitleFont();
if (font == null) {
generator.push((String) null);
}
else {
FontPropertyCodeGenerator.generatePushFont(generator, componentLocal, container, font, "getFont");
}
if (container.getBorderTitleColor() == null) {
generator.push((String) null);
}
else {
pushPropValue(generator, Color.class.getName(), container.getBorderTitleColor());
}
}
}
private class FormConstructorVisitor extends MethodVisitor {
private final String myClassName;
private final String mySuperName;
private boolean callsSelfConstructor = false;
private boolean mySetupCalled = false;
private boolean mySuperCalled = false;
public FormConstructorVisitor(final MethodVisitor mv, final String className, final String superName) {
super(Opcodes.ASM4, mv);
myClassName = className;
mySuperName = superName;
}
public void visitFieldInsn(final int opcode, final String owner, final String name, final String desc) {
if (opcode == Opcodes.GETFIELD && !mySetupCalled && !callsSelfConstructor && Utils.isBoundField(myRootContainer, name)) {
callSetupUI();
}
super.visitFieldInsn(opcode, owner, name, desc);
}
public void visitMethodInsn(final int opcode, final String owner, final String name, final String desc) {
if (opcode == Opcodes.INVOKESPECIAL && name.equals(CONSTRUCTOR_NAME)) {
if (owner.equals(myClassName)) {
callsSelfConstructor = true;
}
else if (owner.equals(mySuperName)) {
mySuperCalled = true;
}
else if (mySuperCalled) {
callSetupUI();
}
}
else if (mySuperCalled) {
callSetupUI();
}
super.visitMethodInsn(opcode, owner, name, desc);
}
public void visitJumpInsn(final int opcode, final Label label) {
if (mySuperCalled) {
callSetupUI();
}
super.visitJumpInsn(opcode, label);
}
private void callSetupUI() {
if (!mySetupCalled) {
mv.visitVarInsn(Opcodes.ALOAD, 0);
mv.visitMethodInsn(Opcodes.INVOKESPECIAL, myClassName, SETUP_METHOD_NAME, "()V");
mySetupCalled = true;
}
}
public void visitInsn(final int opcode) {
if (opcode == Opcodes.RETURN && !mySetupCalled && !callsSelfConstructor) {
callSetupUI();
}
super.visitInsn(opcode);
}
}
private static class FirstPassClassVisitor extends ClassVisitor {
private boolean myExplicitSetupCall = false;
public FirstPassClassVisitor() {
super(Opcodes.ASM4, new ClassVisitor(Opcodes.ASM4){});
}
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
if (name.equals(CONSTRUCTOR_NAME)) {
return new FirstPassConstructorVisitor();
}
return null;
}
public boolean isExplicitSetupCall() {
return myExplicitSetupCall;
}
private class FirstPassConstructorVisitor extends MethodVisitor {
public FirstPassConstructorVisitor() {
super(Opcodes.ASM4, new MethodVisitor(Opcodes.ASM4){});
}
public void visitMethodInsn(final int opcode, final String owner, final String name, final String desc) {
if (name.equals(SETUP_METHOD_NAME)) {
myExplicitSetupCall = true;
}
}
}
}
}