blob: 8d38863b939f2111c597b814d735464f7552a397 [file] [log] [blame]
/*
* Copyright 2000-2009 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;
import com.intellij.codeHighlighting.HighlightDisplayLevel;
import com.intellij.codeInsight.daemon.impl.SeverityRegistrar;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.extensions.Extensions;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.*;
import com.intellij.uiDesigner.designSurface.GuiEditor;
import com.intellij.uiDesigner.inspections.FormInspectionTool;
import com.intellij.uiDesigner.lw.IButtonGroup;
import com.intellij.uiDesigner.lw.IComponent;
import com.intellij.uiDesigner.lw.IContainer;
import com.intellij.uiDesigner.lw.IRootContainer;
import com.intellij.uiDesigner.quickFixes.*;
import com.intellij.uiDesigner.radComponents.RadComponent;
import com.intellij.uiDesigner.radComponents.RadRootContainer;
import com.intellij.util.IncorrectOperationException;
import com.intellij.util.containers.HashSet;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
/**
* @author Anton Katilin
* @author Vladimir Kondratyev
*/
public final class ErrorAnalyzer {
private static final Logger LOG = Logger.getInstance("#com.intellij.uiDesigner.ErrorAnalyzer");
/**
* Value {@link ErrorInfo}
*/
@NonNls
public static final String CLIENT_PROP_CLASS_TO_BIND_ERROR = "classToBindError";
/**
* Value {@link ErrorInfo}
*/
@NonNls
public static final String CLIENT_PROP_BINDING_ERROR = "bindingError";
@NonNls public static final String CLIENT_PROP_ERROR_ARRAY = "errorArray";
private ErrorAnalyzer() {
}
public static void analyzeErrors(final GuiEditor editor, final IRootContainer rootContainer, @Nullable final ProgressIndicator progress) {
analyzeErrors(editor.getModule(), editor.getFile(), editor, rootContainer, progress);
}
/**
* @param editor if null, no quick fixes are created. This is used in form to source compiler.
*/
public static void analyzeErrors(@NotNull final Module module,
@NotNull final VirtualFile formFile,
@Nullable final GuiEditor editor,
@NotNull final IRootContainer rootContainer,
@Nullable final ProgressIndicator progress) {
if (module.isDisposed()) {
return;
}
// 1. Validate class to bind
final String classToBind = rootContainer.getClassToBind();
final PsiClass psiClass;
if (classToBind != null) {
psiClass = FormEditingUtil.findClassToBind(module, classToBind);
if (psiClass == null) {
final QuickFix[] fixes = editor != null ? new QuickFix[]{new CreateClassToBindFix(editor, classToBind)} : QuickFix.EMPTY_ARRAY;
final ErrorInfo errorInfo = new ErrorInfo(null, null, UIDesignerBundle.message("error.class.does.not.exist", classToBind),
HighlightDisplayLevel.ERROR, fixes);
rootContainer.putClientProperty(CLIENT_PROP_CLASS_TO_BIND_ERROR, errorInfo);
}
else {
rootContainer.putClientProperty(CLIENT_PROP_CLASS_TO_BIND_ERROR, null);
}
}
else {
rootContainer.putClientProperty(CLIENT_PROP_CLASS_TO_BIND_ERROR, null);
psiClass = null;
}
// 2. Validate bindings to fields
// field name -> error message
final ArrayList<String> usedBindings = new ArrayList<String>(); // for performance reasons
final Set<IButtonGroup> processedGroups = new HashSet<IButtonGroup>();
FormEditingUtil.iterate(
rootContainer,
new FormEditingUtil.ComponentVisitor<IComponent>() {
public boolean visit(final IComponent component) {
if (progress != null && progress.isCanceled()) return false;
// Reset previous error (if any)
component.putClientProperty(CLIENT_PROP_BINDING_ERROR, null);
final String binding = component.getBinding();
// a. Check that field exists and field is not static
if (psiClass != null && binding != null) {
if (validateFieldInClass(component, binding, component.getComponentClassName(), psiClass, editor, module)) return true;
}
// b. Check that binding is unique
if (binding != null) {
if (usedBindings.contains(binding)) {
// TODO[vova] implement
component.putClientProperty(
CLIENT_PROP_BINDING_ERROR,
new ErrorInfo(
component, null, UIDesignerBundle.message("error.binding.already.exists", binding),
HighlightDisplayLevel.ERROR,
QuickFix.EMPTY_ARRAY
)
);
return true;
}
usedBindings.add(binding);
}
IButtonGroup group = FormEditingUtil.findGroupForComponent(rootContainer, component);
if (group != null && !processedGroups.contains(group)) {
processedGroups.add(group);
if (group.isBound()) {
validateFieldInClass(component, group.getName(), ButtonGroup.class.getName(), psiClass, editor, module);
}
}
return true;
}
}
);
if (progress != null) progress.checkCanceled();
// Check that there are no panels in XY with children
FormEditingUtil.iterate(
rootContainer,
new FormEditingUtil.ComponentVisitor<IComponent>() {
public boolean visit(final IComponent component) {
if (progress != null && progress.isCanceled()) return false;
// Clear previous error (if any)
component.putClientProperty(CLIENT_PROP_ERROR_ARRAY, null);
if (!(component instanceof IContainer)) {
return true;
}
final IContainer container = (IContainer)component;
if (container instanceof IRootContainer) {
final IRootContainer rootContainer = (IRootContainer)container;
if (rootContainer.getComponentCount() > 1) {
// TODO[vova] implement
putError(component, new ErrorInfo(
component, null, UIDesignerBundle.message("error.multiple.toplevel.components"),
HighlightDisplayLevel.ERROR,
QuickFix.EMPTY_ARRAY
));
}
}
else if (container.isXY() && container.getComponentCount() > 0) {
// TODO[vova] implement
putError(component, new ErrorInfo(
component, null, UIDesignerBundle.message("error.panel.not.laid.out"),
HighlightDisplayLevel.ERROR,
QuickFix.EMPTY_ARRAY
)
);
}
return true;
}
}
);
if (progress != null) progress.checkCanceled();
try {
// Run inspections for form elements
final PsiFile formPsiFile = PsiManager.getInstance(module.getProject()).findFile(formFile);
if (formPsiFile != null && rootContainer instanceof RadRootContainer) {
final List<FormInspectionTool> formInspectionTools = new ArrayList<FormInspectionTool>();
final FormInspectionTool[] registeredFormInspections = Extensions.getExtensions(FormInspectionTool.EP_NAME);
for (FormInspectionTool formInspectionTool : registeredFormInspections) {
if (formInspectionTool.isActive(formPsiFile) && !rootContainer.isInspectionSuppressed(formInspectionTool.getShortName(), null)) {
formInspectionTools.add(formInspectionTool);
}
}
if (formInspectionTools.size() > 0 && editor != null) {
for (FormInspectionTool tool : formInspectionTools) {
tool.startCheckForm(rootContainer);
}
FormEditingUtil.iterate(
rootContainer,
new FormEditingUtil.ComponentVisitor<RadComponent>() {
public boolean visit(final RadComponent component) {
if (progress != null && progress.isCanceled()) return false;
for (FormInspectionTool tool : formInspectionTools) {
if (rootContainer.isInspectionSuppressed(tool.getShortName(), component.getId())) continue;
ErrorInfo[] errorInfos = tool.checkComponent(editor, component);
if (errorInfos != null) {
ArrayList<ErrorInfo> errorList = getErrorInfos(component);
if (errorList == null) {
errorList = new ArrayList<ErrorInfo>();
component.putClientProperty(CLIENT_PROP_ERROR_ARRAY, errorList);
}
Collections.addAll(errorList, errorInfos);
}
}
return true;
}
}
);
for (FormInspectionTool tool : formInspectionTools) {
tool.doneCheckForm(rootContainer);
}
}
}
}
catch (Exception e) {
LOG.error(e);
}
}
private static boolean validateFieldInClass(final IComponent component, final String fieldName, final String fieldClassName,
final PsiClass psiClass, final GuiEditor editor, final Module module) {
final PsiField[] fields = psiClass.getFields();
PsiField field = null;
for(int i = fields.length - 1; i >=0 ; i--){
if(fieldName.equals(fields[i].getName())){
field = fields[i];
break;
}
}
if(field == null){
final QuickFix[] fixes = editor != null
? new QuickFix[]{ new CreateFieldFix(editor, psiClass, fieldClassName, fieldName) }
: QuickFix.EMPTY_ARRAY;
component.putClientProperty(
CLIENT_PROP_BINDING_ERROR,
new ErrorInfo(
component, null, UIDesignerBundle.message("error.no.field.in.class", fieldName, psiClass.getQualifiedName()),
HighlightDisplayLevel.ERROR,
fixes
)
);
return true;
}
else if(field.hasModifierProperty(PsiModifier.STATIC)){
component.putClientProperty(
CLIENT_PROP_BINDING_ERROR,
new ErrorInfo(
component, null, UIDesignerBundle.message("error.cant.bind.to.static", fieldName),
HighlightDisplayLevel.ERROR,
QuickFix.EMPTY_ARRAY
)
);
return true;
}
// Check that field has correct fieldType
try {
final String className = fieldClassName.replace('$', '.'); // workaround for PSI
final PsiType componentType = JavaPsiFacade.getInstance(module.getProject()).getElementFactory().createTypeFromText(
className,
null
);
final PsiType fieldType = field.getType();
if(!fieldType.isAssignableFrom(componentType)){
final QuickFix[] fixes = editor != null ? new QuickFix[]{
new ChangeFieldTypeFix(editor, field, componentType)
} : QuickFix.EMPTY_ARRAY;
component.putClientProperty(
CLIENT_PROP_BINDING_ERROR,
new ErrorInfo(
component, null, UIDesignerBundle.message("error.bind.incompatible.types", fieldType.getPresentableText(), className),
HighlightDisplayLevel.ERROR,
fixes
)
);
return true;
}
}
catch (IncorrectOperationException e) {
}
if (component.isCustomCreate() && FormEditingUtil.findCreateComponentsMethod(psiClass) == null) {
final QuickFix[] fixes = editor != null ? new QuickFix[]{
new GenerateCreateComponentsFix(editor, psiClass)
} : QuickFix.EMPTY_ARRAY;
component.putClientProperty(
CLIENT_PROP_BINDING_ERROR,
new ErrorInfo(
component, "Custom Create",
UIDesignerBundle.message("error.no.custom.create.method"), HighlightDisplayLevel.ERROR,
fixes));
return true;
}
return false;
}
private static void putError(final IComponent component, final ErrorInfo errorInfo) {
ArrayList<ErrorInfo> errorList = getErrorInfos(component);
if (errorList == null) {
errorList = new ArrayList<ErrorInfo>();
component.putClientProperty(CLIENT_PROP_ERROR_ARRAY, errorList);
}
errorList.add(errorInfo);
}
/**
* @return first ErrorInfo for the specified component. If component doesn't contain
* any error then the method returns <code>null</code>.
*/
@Nullable
public static ErrorInfo getErrorForComponent(@NotNull final IComponent component){
// Check bind to class errors
{
final ErrorInfo errorInfo = (ErrorInfo)component.getClientProperty(CLIENT_PROP_CLASS_TO_BIND_ERROR);
if(errorInfo != null){
return errorInfo;
}
}
// Check binding errors
{
final ErrorInfo error = (ErrorInfo)component.getClientProperty(CLIENT_PROP_BINDING_ERROR);
if(error != null){
return error;
}
}
// General error
{
final ArrayList<ErrorInfo> errorInfo = getErrorInfos(component);
if(errorInfo != null && errorInfo.size() > 0){
return errorInfo.get(0);
}
}
return null;
}
@NotNull public static ErrorInfo[] getAllErrorsForComponent(@NotNull IComponent component) {
List<ErrorInfo> result = new ArrayList<ErrorInfo>();
ErrorInfo errorInfo = (ErrorInfo)component.getClientProperty(CLIENT_PROP_CLASS_TO_BIND_ERROR);
if (errorInfo != null) {
result.add(errorInfo);
}
errorInfo = (ErrorInfo)component.getClientProperty(CLIENT_PROP_BINDING_ERROR);
if (errorInfo != null) {
result.add(errorInfo);
}
final ArrayList<ErrorInfo> errorInfos = getErrorInfos(component);
if (errorInfos != null) {
result.addAll(errorInfos);
}
return result.toArray(new ErrorInfo[result.size()]);
}
private static ArrayList<ErrorInfo> getErrorInfos(final IComponent component) {
//noinspection unchecked
return (ArrayList<ErrorInfo>)component.getClientProperty(CLIENT_PROP_ERROR_ARRAY);
}
@Nullable
public static HighlightDisplayLevel getHighlightDisplayLevel(final Project project, @NotNull final RadComponent component) {
HighlightDisplayLevel displayLevel = null;
for(ErrorInfo errInfo: getAllErrorsForComponent(component)) {
if (displayLevel == null || SeverityRegistrar.getSeverityRegistrar(project).compare(errInfo.getHighlightDisplayLevel().getSeverity(), displayLevel.getSeverity()) > 0) {
displayLevel = errInfo.getHighlightDisplayLevel();
}
}
return displayLevel;
}
}