blob: 21e72aa420784e496848ab9eb11cc8150e58061a [file] [log] [blame]
/*
* Copyright 2000-2013 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.make;
import com.intellij.lang.java.JavaParserDefinition;
import com.intellij.lexer.Lexer;
import com.intellij.openapi.application.ApplicationNamesInfo;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.fileEditor.FileDocumentManager;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.module.ModuleUtil;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Ref;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.patterns.ElementPattern;
import com.intellij.patterns.PlatformPatterns;
import com.intellij.patterns.PsiJavaPatterns;
import com.intellij.pom.java.LanguageLevel;
import com.intellij.psi.*;
import com.intellij.psi.codeStyle.CodeStyleManager;
import com.intellij.psi.codeStyle.JavaCodeStyleManager;
import com.intellij.psi.impl.source.tree.JavaDocElementType;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.tree.IElementType;
import com.intellij.psi.util.InheritanceUtil;
import com.intellij.uiDesigner.*;
import com.intellij.uiDesigner.compiler.*;
import com.intellij.uiDesigner.core.SupportCode;
import com.intellij.uiDesigner.lw.*;
import com.intellij.uiDesigner.shared.BorderType;
import com.intellij.util.IncorrectOperationException;
import gnu.trove.TIntObjectHashMap;
import gnu.trove.TObjectIntHashMap;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.awt.*;
import java.util.*;
public final class FormSourceCodeGenerator {
private static final Logger LOG = Logger.getInstance("com.intellij.uiDesigner.make.FormSourceCodeGenerator");
@NonNls private StringBuffer myBuffer;
private Stack<Boolean> myIsFirstParameterStack;
private final Project myProject;
private final ArrayList<FormErrorInfo> myErrors;
private boolean myNeedLoadLabelText;
private boolean myNeedLoadButtonText;
private static final Map<Class, LayoutSourceGenerator> ourComponentLayoutCodeGenerators = new HashMap<Class, LayoutSourceGenerator>();
private static final Map<String, LayoutSourceGenerator> ourContainerLayoutCodeGenerators = new HashMap<String, LayoutSourceGenerator>();
@NonNls private static final TIntObjectHashMap<String> ourFontStyleMap = new TIntObjectHashMap<String>();
@NonNls private static final TIntObjectHashMap<String> ourTitleJustificationMap = new TIntObjectHashMap<String>();
@NonNls private static final TIntObjectHashMap<String> ourTitlePositionMap = new TIntObjectHashMap<String>();
private static final ElementPattern ourSuperCallPattern = PsiJavaPatterns.psiExpressionStatement().withFirstChild(PlatformPatterns.psiElement(PsiMethodCallExpression.class).withFirstChild(
PlatformPatterns.psiElement().withText(PsiKeyword.SUPER)));
static {
ourComponentLayoutCodeGenerators.put(LwSplitPane.class, new SplitPaneLayoutSourceGenerator());
ourComponentLayoutCodeGenerators.put(LwTabbedPane.class, new TabbedPaneLayoutSourceGenerator());
ourComponentLayoutCodeGenerators.put(LwScrollPane.class, new ScrollPaneLayoutSourceGenerator());
ourComponentLayoutCodeGenerators.put(LwToolBar.class, new ToolBarLayoutSourceGenerator());
ourFontStyleMap.put(Font.PLAIN, "java.awt.Font.PLAIN");
ourFontStyleMap.put(Font.BOLD, "java.awt.Font.BOLD");
ourFontStyleMap.put(Font.ITALIC, "java.awt.Font.ITALIC");
ourFontStyleMap.put(Font.BOLD | Font.ITALIC, "java.awt.Font.BOLD | java.awt.Font.ITALIC");
ourTitlePositionMap.put(0, "javax.swing.border.TitledBorder.DEFAULT_POSITION");
ourTitlePositionMap.put(1, "javax.swing.border.TitledBorder.ABOVE_TOP");
ourTitlePositionMap.put(2, "javax.swing.border.TitledBorder.TOP");
ourTitlePositionMap.put(3, "javax.swing.border.TitledBorder.BELOW_TOP");
ourTitlePositionMap.put(4, "javax.swing.border.TitledBorder.ABOVE_BOTTOM");
ourTitlePositionMap.put(5, "javax.swing.border.TitledBorder.BOTTOM");
ourTitlePositionMap.put(6, "javax.swing.border.TitledBorder.BELOW_BOTTOM");
ourTitleJustificationMap.put(0, "javax.swing.border.TitledBorder.DEFAULT_JUSTIFICATION");
ourTitleJustificationMap.put(1, "javax.swing.border.TitledBorder.LEFT");
ourTitleJustificationMap.put(2, "javax.swing.border.TitledBorder.CENTER");
ourTitleJustificationMap.put(3, "javax.swing.border.TitledBorder.RIGHT");
ourTitleJustificationMap.put(4, "javax.swing.border.TitledBorder.LEADING");
ourTitleJustificationMap.put(5, "javax.swing.border.TitledBorder.TRAILING");
}
public FormSourceCodeGenerator(@NotNull final Project project){
myProject = project;
myErrors = new ArrayList<FormErrorInfo>();
}
public void generate(final VirtualFile formFile) {
myNeedLoadLabelText = false;
myNeedLoadButtonText = false;
final Module module = ModuleUtil.findModuleForFile(formFile, myProject);
if (module == null) {
return;
}
// ensure that new instances of generators are used for every run
ourContainerLayoutCodeGenerators.clear();
ourContainerLayoutCodeGenerators.put(UIFormXmlConstants.LAYOUT_INTELLIJ, new GridLayoutSourceGenerator());
ourContainerLayoutCodeGenerators.put(UIFormXmlConstants.LAYOUT_GRIDBAG, new GridBagLayoutSourceGenerator());
ourContainerLayoutCodeGenerators.put(UIFormXmlConstants.LAYOUT_BORDER, new BorderLayoutSourceGenerator());
ourContainerLayoutCodeGenerators.put(UIFormXmlConstants.LAYOUT_FLOW, new FlowLayoutSourceGenerator());
ourContainerLayoutCodeGenerators.put(UIFormXmlConstants.LAYOUT_CARD, new CardLayoutSourceGenerator());
ourContainerLayoutCodeGenerators.put(UIFormXmlConstants.LAYOUT_FORM, new FormLayoutSourceGenerator());
myErrors.clear();
final PsiPropertiesProvider propertiesProvider = new PsiPropertiesProvider(module);
final Document doc = FileDocumentManager.getInstance().getDocument(formFile);
final LwRootContainer rootContainer;
try {
rootContainer = Utils.getRootContainer(doc.getText(), propertiesProvider);
}
catch (AlienFormFileException ignored) {
// ignoring this file
return;
}
catch (Exception e) {
myErrors.add(new FormErrorInfo(null, UIDesignerBundle.message("error.cannot.process.form.file", e)));
return;
}
if (rootContainer.getClassToBind() == null) {
// form skipped - no class to bind
return;
}
ErrorAnalyzer.analyzeErrors(module, formFile, null, rootContainer, null);
FormEditingUtil.iterate(
rootContainer,
new FormEditingUtil.ComponentVisitor<LwComponent>() {
public boolean visit(final LwComponent iComponent) {
final ErrorInfo errorInfo = ErrorAnalyzer.getErrorForComponent(iComponent);
if (errorInfo != null) {
String message;
if (iComponent.getBinding() != null) {
message = UIDesignerBundle.message("error.for.component", iComponent.getBinding(), errorInfo.myDescription);
}
else {
message = errorInfo.myDescription;
}
myErrors.add(new FormErrorInfo(iComponent.getId(), message));
}
return true;
}
}
);
if (myErrors.size() != 0) {
return;
}
try {
_generate(rootContainer, module);
}
catch (ClassToBindNotFoundException e) {
// ignore
}
catch (CodeGenerationException e) {
myErrors.add(new FormErrorInfo(e.getComponentId(), e.getMessage()));
}
catch (IncorrectOperationException e) {
myErrors.add(new FormErrorInfo(null, e.getMessage()));
}
}
public ArrayList<FormErrorInfo> getErrors() {
return myErrors;
}
private void _generate(final LwRootContainer rootContainer, final Module module) throws CodeGenerationException, IncorrectOperationException{
myBuffer = new StringBuffer();
myIsFirstParameterStack = new Stack<Boolean>();
final HashMap<LwComponent,String> component2variable = new HashMap<LwComponent,String>();
final TObjectIntHashMap<String> class2variableIndex = new TObjectIntHashMap<String>();
final HashMap<String,LwComponent> id2component = new HashMap<String, LwComponent>();
if (rootContainer.getComponentCount() != 1) {
throw new CodeGenerationException(null, UIDesignerBundle.message("error.one.toplevel.component.required"));
}
final LwComponent topComponent = (LwComponent)rootContainer.getComponent(0);
String id = Utils.findNotEmptyPanelWithXYLayout(topComponent);
if (id != null) {
throw new CodeGenerationException(id, UIDesignerBundle.message("error.nonempty.xy.panels.found"));
}
final PsiClass classToBind = FormEditingUtil.findClassToBind(module, rootContainer.getClassToBind());
if (classToBind == null) {
throw new ClassToBindNotFoundException(UIDesignerBundle.message("error.class.to.bind.not.found", rootContainer.getClassToBind()));
}
final boolean haveCustomCreateComponents = Utils.getCustomCreateComponentCount(rootContainer) > 0;
if (haveCustomCreateComponents) {
if (FormEditingUtil.findCreateComponentsMethod(classToBind) == null) {
throw new CodeGenerationException(null, UIDesignerBundle.message("error.no.custom.create.method"));
}
myBuffer.append(AsmCodeGenerator.CREATE_COMPONENTS_METHOD_NAME).append("();");
}
generateSetupCodeForComponent(topComponent,
component2variable,
class2variableIndex,
id2component, module, classToBind);
generateComponentReferenceProperties(topComponent, component2variable, class2variableIndex, id2component, classToBind);
generateButtonGroups(rootContainer, component2variable, class2variableIndex, id2component, classToBind);
final String methodText = myBuffer.toString();
final PsiManager psiManager = PsiManager.getInstance(module.getProject());
final PsiElementFactory elementFactory = JavaPsiFacade.getInstance(psiManager.getProject()).getElementFactory();
PsiClass newClass = (PsiClass) classToBind.copy();
cleanup(newClass);
// [anton] the comments are written according to the SCR 26896
final PsiClass fakeClass = elementFactory.createClassFromText(
"{\n" +
"// GUI initializer generated by " + ApplicationNamesInfo.getInstance().getFullProductName() + " GUI Designer\n" +
"// >>> IMPORTANT!! <<<\n" +
"// DO NOT EDIT OR ADD ANY CODE HERE!\n" +
"" + AsmCodeGenerator.SETUP_METHOD_NAME + "();\n" +
"}\n" +
"\n" +
"/** Method generated by " + ApplicationNamesInfo.getInstance().getFullProductName() + " GUI Designer\n" +
" * >>> IMPORTANT!! <<<\n" +
" * DO NOT edit this method OR call it in your code!\n" +
" * @noinspection ALL\n" +
" */\n" +
"private void " + AsmCodeGenerator.SETUP_METHOD_NAME + "()\n" +
"{\n" +
methodText +
"}\n",
null
);
final CodeStyleManager formatter = CodeStyleManager.getInstance(module.getProject());
final JavaCodeStyleManager styler = JavaCodeStyleManager.getInstance(module.getProject());
PsiMethod method = (PsiMethod) newClass.add(fakeClass.getMethods()[0]);
// don't generate initializer block if $$$setupUI$$$() is called explicitly from one of the constructors
boolean needInitializer = true;
boolean needSetupUI = false;
for(PsiMethod constructor: newClass.getConstructors()) {
if (containsMethodIdentifier(constructor, method)) {
needInitializer = false;
}
else if (haveCustomCreateComponents && hasCustomComponentAffectingReferences(constructor, newClass, rootContainer, null)) {
needInitializer = false;
needSetupUI = true;
}
}
if (needSetupUI) {
for(PsiMethod constructor: newClass.getConstructors()) {
addSetupUICall(constructor, rootContainer, method);
}
}
if (needInitializer) {
newClass.addBefore(fakeClass.getInitializers()[0], method);
}
@NonNls final String grcMethodText = "/** @noinspection ALL */ public javax.swing.JComponent " +
AsmCodeGenerator.GET_ROOT_COMPONENT_METHOD_NAME +
"() { return " + topComponent.getBinding() + "; }";
generateMethodIfRequired(newClass, method, AsmCodeGenerator.GET_ROOT_COMPONENT_METHOD_NAME, grcMethodText, topComponent.getBinding() != null);
final String loadButtonTextMethodText = getLoadMethodText(AsmCodeGenerator.LOAD_BUTTON_TEXT_METHOD, AbstractButton.class, module);
generateMethodIfRequired(newClass, method, AsmCodeGenerator.LOAD_BUTTON_TEXT_METHOD, loadButtonTextMethodText, myNeedLoadButtonText);
final String loadLabelTextMethodText = getLoadMethodText(AsmCodeGenerator.LOAD_LABEL_TEXT_METHOD, JLabel.class, module);
generateMethodIfRequired(newClass, method, AsmCodeGenerator.LOAD_LABEL_TEXT_METHOD, loadLabelTextMethodText, myNeedLoadLabelText);
newClass = (PsiClass) styler.shortenClassReferences(newClass);
newClass = (PsiClass) formatter.reformat(newClass);
if (!lexemsEqual(classToBind, newClass)) {
classToBind.replace(newClass);
}
}
private static void addSetupUICall(final PsiMethod constructor, final LwRootContainer rootContainer, final PsiMethod setupUIMethod) {
final PsiCodeBlock psiCodeBlock = constructor.getBody();
if (psiCodeBlock == null) {
return;
}
final PsiClass classToBind = constructor.getContainingClass();
final PsiStatement[] statements = psiCodeBlock.getStatements();
PsiElement anchor = psiCodeBlock.getRBrace();
Ref<Boolean> callsThisConstructor = new Ref<Boolean>(Boolean.FALSE);
for(PsiStatement statement: statements) {
if (containsMethodIdentifier(statement, setupUIMethod)) {
return;
}
if (!ourSuperCallPattern.accepts(statement) &&
hasCustomComponentAffectingReferences(statement, classToBind, rootContainer, callsThisConstructor)) {
anchor = statement;
break;
}
}
if (!callsThisConstructor.get().booleanValue()) {
final PsiElementFactory factory = JavaPsiFacade.getInstance(constructor.getProject()).getElementFactory();
try {
PsiStatement setupUIStatement = factory.createStatementFromText(AsmCodeGenerator.SETUP_METHOD_NAME + "();", constructor);
psiCodeBlock.addBefore(setupUIStatement, anchor);
}
catch (IncorrectOperationException e) {
LOG.error(e);
}
}
}
private static boolean hasCustomComponentAffectingReferences(final PsiElement element,
final PsiClass classToBind,
final LwRootContainer rootContainer,
@Nullable final Ref<Boolean> callsThisConstructor) {
final Ref<Boolean> result = new Ref<Boolean>(Boolean.FALSE);
element.accept(new JavaRecursiveElementWalkingVisitor() {
@Override public void visitReferenceExpression(final PsiReferenceExpression expression) {
super.visitReferenceElement(expression);
final PsiElement psiElement = expression.resolve();
if (psiElement == null) {
return;
}
if (psiElement instanceof PsiField) {
PsiField field = (PsiField) psiElement;
if (field.getContainingClass().equals(classToBind)) {
if (Utils.isBoundField(rootContainer, field.getName())) {
result.set(Boolean.TRUE);
}
}
}
else if (psiElement instanceof PsiMethod) {
PsiMethod method = (PsiMethod) psiElement;
if (method.isConstructor()) {
if (method.getContainingClass() == classToBind) {
if (callsThisConstructor != null) {
callsThisConstructor.set(Boolean.TRUE);
}
}
else if (method.getContainingClass() != classToBind.getSuperClass()) {
result.set(Boolean.TRUE);
}
}
else {
result.set(Boolean.TRUE);
}
}
}
});
return result.get().booleanValue();
}
private static boolean lexemsEqual(final PsiClass classToBind, final PsiClass newClass) {
Lexer oldTextLexer = JavaParserDefinition.createLexer(LanguageLevel.HIGHEST);
Lexer newTextLexer = JavaParserDefinition.createLexer(LanguageLevel.HIGHEST);
String oldBuffer = classToBind.getText();
String newBuffer = newClass.getText();
oldTextLexer.start(oldBuffer);
newTextLexer.start(newBuffer);
while(true) {
IElementType oldLexem = oldTextLexer.getTokenType();
IElementType newLexem = newTextLexer.getTokenType();
if (oldLexem == null || newLexem == null) {
// must terminate at the same time
return oldLexem == null && newLexem == null;
}
if (oldLexem != newLexem) {
return false;
}
if (oldLexem != TokenType.WHITE_SPACE && oldLexem != JavaDocElementType.DOC_COMMENT) {
int oldStart = oldTextLexer.getTokenStart();
int newStart = newTextLexer.getTokenStart();
int oldLength = oldTextLexer.getTokenEnd() - oldTextLexer.getTokenStart();
int newLength = newTextLexer.getTokenEnd() - newTextLexer.getTokenStart();
if (oldLength != newLength) {
return false;
}
for(int i=0; i<oldLength; i++) {
if (oldBuffer.charAt(oldStart+i) != newBuffer.charAt(newStart+i)) {
return false;
}
}
}
oldTextLexer.advance();
newTextLexer.advance();
}
}
@NonNls
private String getLoadMethodText(final String methodName, final Class componentClass, Module module) {
final boolean needIndex = haveSetDisplayedMnemonic(componentClass, module);
return
"/** @noinspection ALL */ " +
"private void " + methodName + "(" + componentClass.getName() + " component, java.lang.String text) {" +
" StringBuffer result = new StringBuffer(); " +
" boolean haveMnemonic = false; " +
" char mnemonic = '\\0';" +
(needIndex ? "int mnemonicIndex = -1;" : "") +
" for(int i=0; i<text.length(); i++) {" +
" if (text.charAt(i) == '&') {" +
" i++;" +
" if (i == text.length()) break;" +
" if (!haveMnemonic && text.charAt(i) != '&') {" +
" haveMnemonic = true;" +
" mnemonic = text.charAt(i);" +
(needIndex ? "mnemonicIndex = result.length();" : "") +
" }" +
" }" +
" result.append(text.charAt(i));" +
" }" +
" component.setText(result.toString()); " +
" if (haveMnemonic) {" +
(componentClass.equals(AbstractButton.class)
? " component.setMnemonic(mnemonic);"
: " component.setDisplayedMnemonic(mnemonic);") +
(needIndex ? "component.setDisplayedMnemonicIndex(mnemonicIndex);" : "") +
"} }";
}
private void generateMethodIfRequired(PsiClass aClass, PsiMethod anchor, final String methodName, String methodText, boolean condition) throws IncorrectOperationException {
PsiElementFactory elementFactory = JavaPsiFacade.getInstance(myProject).getElementFactory();
PsiMethod newMethod = null;
PsiMethod[] oldMethods = aClass.findMethodsByName(methodName, false);
if (!condition) {
for(PsiMethod oldMethod: oldMethods) {
oldMethod.delete();
}
}
else {
newMethod = elementFactory.createMethodFromText(methodText, aClass);
if (oldMethods.length > 0) {
newMethod = (PsiMethod) oldMethods [0].replace(newMethod);
}
else {
newMethod = (PsiMethod) aClass.addAfter(newMethod, anchor);
}
}
}
public static void cleanup(final PsiClass aClass) throws IncorrectOperationException{
final PsiMethod[] methods = aClass.findMethodsByName(AsmCodeGenerator.SETUP_METHOD_NAME, false);
for (final PsiMethod method: methods) {
final PsiClassInitializer[] initializers = aClass.getInitializers();
for (final PsiClassInitializer initializer : initializers) {
if (containsMethodIdentifier(initializer, method)) {
initializer.delete();
}
}
method.delete();
}
deleteMethods(aClass, AsmCodeGenerator.GET_ROOT_COMPONENT_METHOD_NAME);
deleteMethods(aClass, AsmCodeGenerator.LOAD_BUTTON_TEXT_METHOD);
deleteMethods(aClass, AsmCodeGenerator.LOAD_LABEL_TEXT_METHOD);
}
private static void deleteMethods(final PsiClass aClass, final String methodName) throws IncorrectOperationException {
final PsiMethod[] grcMethods = aClass.findMethodsByName(methodName, false);
for(final PsiMethod grcMethod: grcMethods) {
grcMethod.delete();
}
}
private static boolean containsMethodIdentifier(final PsiElement element, final PsiMethod setupMethod) {
if (element instanceof PsiMethodCallExpression) {
final PsiMethod psiMethod = ((PsiMethodCallExpression)element).resolveMethod();
if (setupMethod.equals(psiMethod)){
return true;
}
}
final PsiElement[] children = element.getChildren();
for (int i = children.length - 1; i >= 0; i--) {
if (containsMethodIdentifier(children[i], setupMethod)) {
return true;
}
}
return false;
}
private void generateSetupCodeForComponent(final LwComponent component,
final HashMap<LwComponent, String> component2TempVariable,
final TObjectIntHashMap<String> class2variableIndex,
final HashMap<String, LwComponent> id2component,
final Module module,
final PsiClass aClass) throws CodeGenerationException{
id2component.put(component.getId(), component);
GlobalSearchScope globalSearchScope = module.getModuleWithDependenciesAndLibrariesScope(false);
final LwContainer parent = component.getParent();
final String variable = getVariable(component, component2TempVariable, class2variableIndex, aClass);
final String componentClass = component instanceof LwNestedForm
? getNestedFormClass(module, (LwNestedForm) component)
: getComponentLayoutGenerator(component.getParent()).mapComponentClass(component.getComponentClassName());
if (component.isCustomCreate() && component.getBinding() == null) {
throw new CodeGenerationException(component.getId(), UIDesignerBundle.message("error.custom.create.no.binding"));
}
if (!component.isCustomCreate()) {
final String binding = component.getBinding();
if (binding != null) {
myBuffer.append(binding);
}
else {
myBuffer.append("final ");
myBuffer.append(componentClass);
myBuffer.append(" ");
myBuffer.append(variable);
}
myBuffer.append('=');
startConstructor(componentClass);
endConstructor(); // will finish the line
}
if (component instanceof LwContainer) {
final LwContainer container = (LwContainer)component;
if (!container.isCustomCreate() || container.getComponentCount() > 0) {
getComponentLayoutGenerator(container).generateContainerLayout(container, this, variable);
}
}
// introspected properties
final LwIntrospectedProperty[] introspectedProperties = component.getAssignedIntrospectedProperties();
// see SCR #35990
Arrays.sort(introspectedProperties, new Comparator<LwIntrospectedProperty>() {
public int compare(LwIntrospectedProperty p1, LwIntrospectedProperty p2) {
return p1.getName().compareTo(p2.getName());
}
});
for (final LwIntrospectedProperty property : introspectedProperties) {
if (property instanceof LwIntroComponentProperty) {
// component properties are processed in second pass
continue;
}
Object value = component.getPropertyValue(property);
//noinspection HardCodedStringLiteral
final boolean isTextWithMnemonicProperty =
"text".equals(property.getName()) &&
(isAssignableFrom(AbstractButton.class.getName(), componentClass, globalSearchScope) ||
isAssignableFrom(JLabel.class.getName(), componentClass, globalSearchScope));
// handle resource bundles
if (property instanceof LwRbIntroStringProperty) {
final StringDescriptor descriptor = (StringDescriptor)value;
if (descriptor.getValue() == null) {
if (isTextWithMnemonicProperty) {
if (isAssignableFrom(AbstractButton.class.getName(), componentClass, globalSearchScope)) {
myNeedLoadButtonText = true;
startMethodCall("this", AsmCodeGenerator.LOAD_BUTTON_TEXT_METHOD);
pushVar(variable);
push(descriptor);
endMethod();
}
else {
myNeedLoadLabelText = true;
startMethodCall("this", AsmCodeGenerator.LOAD_LABEL_TEXT_METHOD);
pushVar(variable);
push(descriptor);
endMethod();
}
}
else {
startMethodCall(variable, property.getWriteMethodName());
push(descriptor);
endMethod();
}
continue;
}
else {
value = descriptor.getValue();
}
}
else if (property instanceof LwIntroListModelProperty) {
generateListModelProperty(property, class2variableIndex, aClass, value, variable);
continue;
}
SupportCode.TextWithMnemonic textWithMnemonic = null;
if (isTextWithMnemonicProperty) {
textWithMnemonic = SupportCode.parseText((String)value);
value = textWithMnemonic.myText;
}
final String propertyClass = property.getPropertyClassName();
if (propertyClass.equals(Color.class.getName())) {
ColorDescriptor descriptor = (ColorDescriptor) value;
if (!descriptor.isColorSet()) continue;
}
startMethodCall(variable, property.getWriteMethodName());
if (propertyClass.equals(Dimension.class.getName())) {
newDimension((Dimension)value);
}
else if (propertyClass.equals(Integer.class.getName())) {
push(((Integer)value).intValue());
}
else if (propertyClass.equals(Double.class.getName())) {
push(((Double)value).doubleValue());
}
else if (propertyClass.equals(Float.class.getName())) {
push(((Float)value).floatValue());
}
else if (propertyClass.equals(Long.class.getName())) {
push(((Long) value).longValue());
}
else if (propertyClass.equals(Short.class.getName())) {
push(((Short) value).shortValue());
}
else if (propertyClass.equals(Byte.class.getName())) {
push(((Byte) value).byteValue());
}
else if (propertyClass.equals(Character.class.getName())) {
push(((Character) value).charValue());
}
else if (propertyClass.equals(Boolean.class.getName())) {
push(((Boolean)value).booleanValue());
}
else if (propertyClass.equals(Rectangle.class.getName())) {
newRectangle((Rectangle)value);
}
else if (propertyClass.equals(Insets.class.getName())) {
newInsets((Insets)value);
}
else if (propertyClass.equals(String.class.getName())) {
push((String)value);
}
else if (propertyClass.equals(Color.class.getName())) {
pushColor((ColorDescriptor) value);
}
else if (propertyClass.equals(Font.class.getName())) {
pushFont(variable, (FontDescriptor) value, property.getReadMethodName());
}
else if (propertyClass.equals(Icon.class.getName())) {
pushIcon((IconDescriptor) value);
}
else if (property instanceof LwIntroEnumProperty) {
pushVar(propertyClass.replace('$', '.') + "." + value.toString());
}
else {
throw new RuntimeException("unexpected property class: " + propertyClass);
}
endMethod();
// special handling of mnemonics
if (!isTextWithMnemonicProperty) {
continue;
}
if (textWithMnemonic.myMnemonicIndex == -1) {
continue;
}
if (isAssignableFrom(AbstractButton.class.getName(), componentClass, globalSearchScope)) {
generateSetMnemonic(variable, textWithMnemonic, module, "setMnemonic", AbstractButton.class);
}
else if (isAssignableFrom(JLabel.class.getName(), componentClass, globalSearchScope)) {
generateSetMnemonic(variable, textWithMnemonic, module, "setDisplayedMnemonic", JLabel.class);
}
}
generateClientProperties(component, variable);
// add component to parent
if (!(component.getParent() instanceof LwRootContainer)) {
final String parentVariable = getVariable(parent, component2TempVariable, class2variableIndex, aClass);
String componentVar = variable;
if (component instanceof LwNestedForm) {
componentVar = variable + "." + AsmCodeGenerator.GET_ROOT_COMPONENT_METHOD_NAME + "()";
}
getComponentLayoutGenerator(component.getParent()).generateComponentLayout(component, this, componentVar, parentVariable);
}
if (component instanceof LwContainer) {
final LwContainer container = (LwContainer)component;
generateBorder(container, variable);
for (int i = 0; i < container.getComponentCount(); i++) {
generateSetupCodeForComponent((LwComponent)container.getComponent(i), component2TempVariable, class2variableIndex, id2component,
module, aClass);
}
}
}
private void generateSetMnemonic(final String variable, final SupportCode.TextWithMnemonic textWithMnemonic, final Module module,
@NonNls final String setMethodName, final Class controlClass) {
startMethodCall(variable, setMethodName);
pushVar("'" + textWithMnemonic.getMnemonicChar() + "'");
endMethod();
if (haveSetDisplayedMnemonic(controlClass, module)) {
// generated code needs to be compatible with jdk 1.3
startMethodCall(variable, "setDisplayedMnemonicIndex");
push(textWithMnemonic.myMnemonicIndex);
endMethod();
}
}
private boolean haveSetDisplayedMnemonic(final Class controlClass, final Module module) {
PsiClass aClass = JavaPsiFacade.getInstance(myProject).findClass(controlClass.getName(), module.getModuleWithLibrariesScope());
return aClass != null && aClass.findMethodsByName("setDisplayedMnemonicIndex", true).length > 0;
}
private void generateListModelProperty(final LwIntrospectedProperty property, final TObjectIntHashMap<String> class2variableIndex,
final PsiClass aClass, final Object value, final String variable) {
String valueClassName;
if (property.getPropertyClassName().equals(ComboBoxModel.class.getName())) {
valueClassName = DefaultComboBoxModel.class.getName();
}
else {
valueClassName = DefaultListModel.class.getName();
}
String modelVarName = generateUniqueVariableName(valueClassName, class2variableIndex, aClass);
myBuffer.append("final ");
myBuffer.append(valueClassName);
myBuffer.append(" ");
myBuffer.append(modelVarName);
myBuffer.append("= new ").append(valueClassName).append("();");
String[] items = (String[]) value;
for(String item: items) {
startMethodCall(modelVarName, "addElement");
push(item);
endMethod();
}
startMethodCall(variable, property.getWriteMethodName());
pushVar(modelVarName);
endMethod();
}
private void generateBorder(final LwContainer container, final String variable) {
final BorderType borderType = container.getBorderType();
final StringDescriptor borderTitle = container.getBorderTitle();
final Insets borderSize = container.getBorderSize();
final String borderFactoryMethodName = borderType.getBorderFactoryMethodName();
final boolean borderNone = borderType.equals(BorderType.NONE);
if (!borderNone || borderTitle != null) {
startMethodCall(variable, "setBorder");
startStaticMethodCall(BorderFactory.class, "createTitledBorder");
if (!borderNone) {
startStaticMethodCall(BorderFactory.class, borderFactoryMethodName);
if (borderType.equals(BorderType.LINE)) {
if (container.getBorderColor() == null) {
pushVar("java.awt.Color.black");
}
else {
pushColor(container.getBorderColor());
}
}
else if (borderType.equals(BorderType.EMPTY) && borderSize != null) {
push(borderSize.top);
push(borderSize.left);
push(borderSize.bottom);
push(borderSize.right);
}
endMethod();
}
else if (isCustomBorder(container)) {
push((String) null);
}
push(borderTitle);
if (isCustomBorder(container)) {
push(container.getBorderTitleJustification(), ourTitleJustificationMap);
push(container.getBorderTitlePosition(), ourTitlePositionMap);
if (container.getBorderTitleFont() != null || container.getBorderTitleColor() != null) {
if (container.getBorderTitleFont() == null) {
push((String) null);
}
else {
pushFont(variable, container.getBorderTitleFont(), "getFont");
}
if (container.getBorderTitleColor() != null) {
pushColor(container.getBorderTitleColor());
}
}
}
endMethod(); // createTitledBorder
endMethod(); // setBorder
}
}
private static boolean isCustomBorder(final LwContainer container) {
return container.getBorderTitleJustification() != 0 || container.getBorderTitlePosition() != 0 ||
container.getBorderTitleColor() != null || container.getBorderTitleFont() != null;
}
private void generateClientProperties(final LwComponent component, final String variable) throws CodeGenerationException {
HashMap props = component.getDelegeeClientProperties();
for (final Object o : props.entrySet()) {
Map.Entry e = (Map.Entry)o;
startMethodCall(variable, "putClientProperty");
push((String) e.getKey());
Object value = e.getValue();
if (value instanceof StringDescriptor) {
push(((StringDescriptor) value).getValue());
}
else if (value instanceof Boolean) {
if (((Boolean) value).booleanValue()) {
pushVar("Boolean.TRUE");
}
else {
pushVar("Boolean.FALSE");
}
}
else {
startConstructor(value.getClass().getName());
if (value instanceof Integer) {
push(((Integer) value).intValue());
}
else if (value instanceof Double) {
push(((Double) value).doubleValue());
}
else {
throw new CodeGenerationException(component.getId(), "Unknown client property value type");
}
endConstructor();
}
endMethod();
}
}
private static String getNestedFormClass(Module module, final LwNestedForm nestedForm) throws CodeGenerationException {
final LwRootContainer container;
try {
container = new PsiNestedFormLoader(module).loadForm(nestedForm.getFormFileName());
return container.getClassToBind();
}
catch (Exception e) {
throw new CodeGenerationException(nestedForm.getId(), e.getMessage());
}
}
private void generateComponentReferenceProperties(final LwComponent component,
final HashMap<LwComponent, String> component2variable,
final TObjectIntHashMap<String> class2variableIndex,
final HashMap<String, LwComponent> id2component,
final PsiClass aClass) {
String variable = getVariable(component, component2variable, class2variableIndex, aClass);
final LwIntrospectedProperty[] introspectedProperties = component.getAssignedIntrospectedProperties();
for (final LwIntrospectedProperty property : introspectedProperties) {
if (property instanceof LwIntroComponentProperty) {
String componentId = (String) component.getPropertyValue(property);
if (componentId != null && componentId.length() > 0) {
LwComponent target = id2component.get(componentId);
if (target != null) {
String targetVariable = getVariable(target, component2variable, class2variableIndex, aClass);
startMethodCall(variable, property.getWriteMethodName());
pushVar(targetVariable);
endMethod();
}
}
}
}
if (component instanceof LwContainer) {
final LwContainer container = (LwContainer)component;
for (int i = 0; i < container.getComponentCount(); i++) {
generateComponentReferenceProperties((LwComponent)container.getComponent(i), component2variable, class2variableIndex, id2component,
aClass);
}
}
}
private void generateButtonGroups(final LwRootContainer rootContainer,
final HashMap<LwComponent, String> component2variable,
final TObjectIntHashMap<String> class2variableIndex,
final HashMap<String, LwComponent> id2component,
final PsiClass aClass) {
IButtonGroup[] groups = rootContainer.getButtonGroups();
boolean haveGroupDeclaration = false;
for(IButtonGroup group: groups) {
boolean haveGroupConstructor = false;
String[] ids = group.getComponentIds();
for(String id: ids) {
LwComponent target = id2component.get(id);
if (target != null) {
if (!haveGroupConstructor) {
if (group.isBound()) {
append(group.getName());
}
else {
if (!haveGroupDeclaration) {
append("javax.swing.ButtonGroup buttonGroup;");
haveGroupDeclaration = true;
}
append("buttonGroup");
}
append("= new javax.swing.ButtonGroup();");
haveGroupConstructor = true;
}
String targetVariable = getVariable(target, component2variable, class2variableIndex, aClass);
startMethodCall(group.isBound() ? group.getName() : "buttonGroup", "add");
pushVar(targetVariable);
endMethod();
}
}
}
}
private static LayoutSourceGenerator getComponentLayoutGenerator(final LwContainer container) {
LayoutSourceGenerator generator = ourComponentLayoutCodeGenerators.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) {
generator = ourContainerLayoutCodeGenerators.get(layoutManager);
if (generator != null) {
return generator;
}
}
parent = parent.getParent();
}
return GridLayoutSourceGenerator.INSTANCE;
}
void push(final StringDescriptor descriptor) {
if (descriptor == null) {
push((String)null);
}
else if (descriptor.getValue() != null) {
push(descriptor.getValue());
}
else {
startMethodCall("java.util.ResourceBundle.getBundle(\"" + descriptor.getBundleName() + "\")", "getString");
push(descriptor.getKey());
endMethod();
}
}
private void pushColor(final ColorDescriptor descriptor) {
if (descriptor.getColor() != null) {
startConstructor(Color.class.getName());
push(descriptor.getColor().getRGB());
endConstructor();
}
else if (descriptor.getSwingColor() != null) {
startStaticMethodCall(UIManager.class, "getColor");
push(descriptor.getSwingColor());
endMethod();
}
else if (descriptor.getSystemColor() != null) {
pushVar("java.awt.SystemColor." + descriptor.getSystemColor());
}
else if (descriptor.getAWTColor() != null) {
pushVar("java.awt.Color." + descriptor.getAWTColor());
}
else {
throw new IllegalStateException("Unknown color type");
}
}
private void pushFont(final String variable, final FontDescriptor fontDescriptor, @NonNls final String getterName) {
if (fontDescriptor.getSwingFont() != null) {
startStaticMethodCall(UIManager.class, "getFont");
push(fontDescriptor.getSwingFont());
endMethod();
}
else {
startConstructor(Font.class.getName());
if (fontDescriptor.getFontName() != null) {
push(fontDescriptor.getFontName());
}
else {
pushVar(variable + "." + getterName + "().getName()");
}
if (fontDescriptor.getFontStyle() >= 0) {
push(fontDescriptor.getFontStyle(), ourFontStyleMap);
}
else {
pushVar(variable + "." + getterName + "().getStyle()");
}
if (fontDescriptor.getFontSize() >= 0) {
push(fontDescriptor.getFontSize());
}
else {
pushVar(variable + "." + getterName + "().getSize()");
}
endMethod();
}
}
public void pushIcon(final IconDescriptor iconDescriptor) {
startConstructor(ImageIcon.class.getName());
startMethodCall("getClass()", "getResource");
push("/" + iconDescriptor.getIconPath());
endMethod();
endMethod();
}
private boolean isAssignableFrom(final String className, final String fromName, final GlobalSearchScope scope) {
final PsiClass aClass = JavaPsiFacade.getInstance(myProject).findClass(className, scope);
final PsiClass fromClass = JavaPsiFacade.getInstance(myProject).findClass(fromName, scope);
if (aClass == null || fromClass == null) {
return false;
}
return InheritanceUtil.isInheritorOrSelf(fromClass, aClass, true);
}
/**
* @return variable idx
*/
private static String getVariable(final LwComponent component,
final HashMap<LwComponent, String> component2variable,
final TObjectIntHashMap<String> class2variableIndex,
final PsiClass aClass) {
if (component2variable.containsKey(component)) {
return component2variable.get(component);
}
if (component.getBinding() != null) {
return component.getBinding();
}
@NonNls final String className = component instanceof LwNestedForm ? "nestedForm" : component.getComponentClassName();
String result = generateUniqueVariableName(className, class2variableIndex, aClass);
component2variable.put(component, result);
return result;
}
private static String generateUniqueVariableName(@NonNls final String className, final TObjectIntHashMap<String> class2variableIndex,
final PsiClass aClass) {
final String shortName;
if (className.startsWith("javax.swing.J")) {
shortName = className.substring("javax.swing.J".length());
}
else {
final int idx = className.lastIndexOf('.');
if (idx != -1) {
shortName = className.substring(idx + 1);
}
else {
shortName = className;
}
}
if (!class2variableIndex.containsKey(className)) class2variableIndex.put(className, 0);
String result;
do {
class2variableIndex.increment(className);
int newIndex = class2variableIndex.get(className);
result = Character.toLowerCase(shortName.charAt(0)) + shortName.substring(1) + newIndex;
} while(aClass.findFieldByName(result, true) != null);
return result;
}
void newDimensionOrNull(final Dimension dimension) {
if (dimension.width == -1 && dimension.height == -1) {
checkParameter();
myBuffer.append("null");
}
else {
newDimension(dimension);
}
}
void newDimension(final Dimension dimension) {
startConstructor(Dimension.class.getName());
push(dimension.width);
push(dimension.height);
endConstructor();
}
void newInsets(final Insets insets){
startConstructor(Insets.class.getName());
push(insets.top);
push(insets.left);
push(insets.bottom);
push(insets.right);
endConstructor();
}
private void newRectangle(final Rectangle rectangle) {
startConstructor(Rectangle.class.getName());
push(rectangle.x);
push(rectangle.y);
push(rectangle.width);
push(rectangle.height);
endConstructor();
}
void startMethodCall(@NonNls final String variable, @NonNls final String methodName) {
checkParameter();
append(variable);
myBuffer.append('.');
myBuffer.append(methodName);
myBuffer.append('(');
myIsFirstParameterStack.push(Boolean.TRUE);
}
private void startStaticMethodCall(final Class aClass, @NonNls final String methodName) {
checkParameter();
myBuffer.append(aClass.getName());
myBuffer.append('.');
myBuffer.append(methodName);
myBuffer.append('(');
myIsFirstParameterStack.push(Boolean.TRUE);
}
void endMethod() {
myBuffer.append(')');
myIsFirstParameterStack.pop();
if (myIsFirstParameterStack.empty()) {
myBuffer.append(";\n");
}
}
void startConstructor(final String className) {
checkParameter();
myBuffer.append("new ");
myBuffer.append(className);
myBuffer.append('(');
myIsFirstParameterStack.push(Boolean.TRUE);
}
void endConstructor() {
endMethod();
}
void push(final byte value) {
checkParameter();
append(value);
}
void append(byte value) {
myBuffer.append("(byte) ");
myBuffer.append(value);
}
void push(final short value) {
checkParameter();
append(value);
}
void append(short value) {
myBuffer.append("(short) ");
myBuffer.append(value);
}
void push(final int value) {
checkParameter();
append(value);
}
void append(final int value) {
myBuffer.append(value);
}
void push(final int value, final TIntObjectHashMap map){
final String stringRepresentation = (String)map.get(value);
if (stringRepresentation != null) {
checkParameter();
myBuffer.append(stringRepresentation);
}
else {
push(value);
}
}
private void push(final double value) {
checkParameter();
append(value);
}
public void append(final double value) {
myBuffer.append(value);
}
private void push(final float value) {
checkParameter();
append(value);
}
public void append(final float value) {
myBuffer.append(value).append("f");
}
private void push(final long value) {
checkParameter();
append(value);
}
public void append(final long value) {
myBuffer.append(value).append("L");
}
private void push(final char value) {
checkParameter();
append(value);
}
public void append(final char value) {
myBuffer.append("'").append(value).append("'");
}
void push(final boolean value) {
checkParameter();
myBuffer.append(value);
}
void push(final String value) {
checkParameter();
if (value == null) {
myBuffer.append("null");
}
else {
myBuffer.append('"');
myBuffer.append(StringUtil.escapeStringCharacters(value));
myBuffer.append('"');
}
}
void pushVar(@NonNls final String variable) {
checkParameter();
append(variable);
}
void append(@NonNls final String text) {
myBuffer.append(text);
}
void checkParameter() {
if (!myIsFirstParameterStack.empty()) {
final Boolean b = myIsFirstParameterStack.pop();
if (b.equals(Boolean.FALSE)) {
myBuffer.append(',');
}
myIsFirstParameterStack.push(Boolean.FALSE);
}
}
}