blob: 46efd1944d93d822a734f0bd64fc5326b22244fe [file] [log] [blame]
/*
* Copyright 2001-2014 the original author or authors.
*
* 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 org.jetbrains.generate.tostring.inspection;
import com.intellij.codeInsight.TestFrameworks;
import com.intellij.codeInspection.ProblemHighlightType;
import com.intellij.codeInspection.ProblemsHolder;
import com.intellij.codeInspection.ui.RegExFormatter;
import com.intellij.codeInspection.ui.RegExInputVerifier;
import com.intellij.openapi.ui.Messages;
import com.intellij.psi.*;
import com.intellij.psi.util.InheritanceUtil;
import com.intellij.ui.DocumentAdapter;
import com.intellij.util.ui.CheckBox;
import com.intellij.util.ui.UIUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.generate.tostring.GenerateToStringContext;
import org.jetbrains.generate.tostring.GenerateToStringUtils;
import org.jetbrains.generate.tostring.util.StringUtil;
import javax.swing.*;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.text.Document;
import java.awt.*;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
/**
* Inspection to check if the current class overrides the toString() method.
* <p/>
* This inspection will use filter information from the GenerateToString plugin settings to exclude certain fields (eg. constants etc.).
* Warns if the class has fields to be dumped and does not have a toString method.
*/
public class ClassHasNoToStringMethodInspection extends AbstractToStringInspection {
/** User options for classes to exclude. Must be a regexp pattern */
public String excludeClassNames = ""; // must be public for JDOMSerialization
private Pattern excludeClassNamesPattern;
/** User options for excluded exception classes */
public boolean excludeException = true; // must be public for JDOMSerialization
/** User options for excluded deprecated classes */
public boolean excludeDeprecated = true; // must be public for JDOMSerialization
/** User options for excluded enum classes */
public boolean excludeEnum = false; // must be public for JDOMSerialization
/** User options for excluded abstract classes */
public boolean excludeAbstract = false; // must be public for JDOMSerialization
public boolean excludeTestCode = false;
public boolean excludeInnerClasses = false;
public ClassHasNoToStringMethodInspection() {
try {
excludeClassNamesPattern = Pattern.compile(excludeClassNames);
} catch (PatternSyntaxException e) {
}
}
@Override
@NotNull
public String getDisplayName() {
return "Class does not override 'toString()' method";
}
@Override
@NotNull
public String getShortName() {
return "ClassHasNoToStringMethod";
}
@NotNull
@Override
public PsiElementVisitor buildVisitor(@NotNull final ProblemsHolder holder, boolean isOnTheFly) {
return new JavaElementVisitor() {
@Override
public void visitClass(PsiClass clazz) {
if (log.isDebugEnabled()) log.debug("checkClass: clazz=" + clazz);
// must be a class
final PsiIdentifier nameIdentifier = clazz.getNameIdentifier();
if (nameIdentifier == null || clazz.getName() == null) {
return;
}
if (excludeException && InheritanceUtil.isInheritor(clazz, CommonClassNames.JAVA_LANG_THROWABLE)) {
return;
}
if (excludeDeprecated && clazz.isDeprecated()) {
return;
}
if (excludeEnum && clazz.isEnum()) {
return;
}
if (excludeAbstract && clazz.hasModifierProperty(PsiModifier.ABSTRACT)) {
return;
}
if (excludeTestCode && TestFrameworks.getInstance().isTestClass(clazz)) {
return;
}
if (excludeInnerClasses && clazz.getContainingClass() != null) {
return;
}
// if it is an excluded class - then skip
if (excludeClassNamesPattern != null) {
try {
String name = clazz.getName();
if (name != null && excludeClassNamesPattern.matcher(name).matches()) {
return;
}
} catch (PatternSyntaxException ignore) {}
}
// must have fields
PsiField[] fields = clazz.getFields();
if (fields.length == 0) {
return;
}
// get list of fields and getter methods supposed to be dumped in the toString method
fields = GenerateToStringUtils.filterAvailableFields(clazz, GenerateToStringContext.getConfig().getFilterPattern());
PsiMethod[] methods = null;
if (GenerateToStringContext.getConfig().isEnableMethods()) {
// okay 'getters in code generation' is enabled so check
methods = GenerateToStringUtils.filterAvailableMethods(clazz, GenerateToStringContext.getConfig().getFilterPattern());
}
// there should be any fields
if (Math.max(fields.length, methods == null ? 0 : methods.length) == 0) {
return;
}
// okay some fields/getter methods are supposed to dumped, does a toString method exist
final PsiMethod[] toStringMethods = clazz.findMethodsByName("toString", false);
for (PsiMethod method : toStringMethods) {
final PsiParameterList parameterList = method.getParameterList();
if (parameterList.getParametersCount() == 0) {
// toString() method found
return;
}
}
final PsiMethod[] superMethods = clazz.findMethodsByName("toString", true);
for (PsiMethod method : superMethods) {
final PsiParameterList parameterList = method.getParameterList();
if (parameterList.getParametersCount() != 0) {
continue;
}
if (method.hasModifierProperty(PsiModifier.FINAL)) {
// final toString() in super class found
return;
}
}
holder.registerProblem(nameIdentifier, "Class '" + clazz.getName() + "' does not override 'toString()' method",
ProblemHighlightType.GENERIC_ERROR_OR_WARNING, GenerateToStringQuickFix.getInstance());
}
};
}
/**
* Creates the options panel in the settings for user changeable options.
*
* @return the options panel
*/
@Override
public JComponent createOptionsPanel() {
JPanel panel = new JPanel(new GridBagLayout());
GridBagConstraints constraints = new GridBagConstraints();
constraints.gridx = 0;
constraints.gridy = 0;
constraints.weightx = 0.0;
constraints.anchor = GridBagConstraints.WEST;
constraints.fill = GridBagConstraints.NONE;
panel.add(new JLabel("Exclude classes (reg exp):"), constraints);
final JFormattedTextField excludeClassNamesField = new JFormattedTextField(new RegExFormatter());
excludeClassNamesField.setValue(excludeClassNamesPattern);
excludeClassNamesField.setColumns(25);
excludeClassNamesField.setInputVerifier(new RegExInputVerifier());
excludeClassNamesField.setFocusLostBehavior(JFormattedTextField.COMMIT);
excludeClassNamesField.setMinimumSize(excludeClassNamesField.getPreferredSize());
UIUtil.fixFormattedField(excludeClassNamesField);
Document document = excludeClassNamesField.getDocument();
document.addDocumentListener(new DocumentAdapter() {
@Override
protected void textChanged(DocumentEvent e) {
try {
excludeClassNamesField.commitEdit();
excludeClassNamesPattern = (Pattern)excludeClassNamesField.getValue();
excludeClassNames = excludeClassNamesPattern.pattern();
} catch (final Exception ignore) {}
}
});
constraints.gridx = 1;
constraints.gridy = 0;
constraints.weightx = 1.0;
constraints.anchor = GridBagConstraints.NORTHWEST;
constraints.fill = GridBagConstraints.NONE;
panel.add(excludeClassNamesField, constraints);
final CheckBox excludeExceptionCheckBox = new CheckBox("Ignore exception classes", this, "excludeException");
constraints.gridx = 0;
constraints.gridy = 1;
constraints.gridwidth = 2;
constraints.fill = GridBagConstraints.HORIZONTAL;
panel.add(excludeExceptionCheckBox, constraints);
final CheckBox excludeDeprecatedCheckBox = new CheckBox("Ignore deprecated classes", this, "excludeDeprecated");
constraints.gridy = 2;
panel.add(excludeDeprecatedCheckBox, constraints);
final CheckBox excludeEnumCheckBox = new CheckBox("Ignore enum classes", this, "excludeEnum");
constraints.gridy = 3;
panel.add(excludeEnumCheckBox, constraints);
final CheckBox excludeAbstractCheckBox = new CheckBox("Ignore abstract classes", this, "excludeAbstract");
constraints.gridy = 4;
panel.add(excludeAbstractCheckBox, constraints);
final CheckBox excludeInTestCodeCheckBox = new CheckBox("Ignore test classes", this, "excludeTestCode");
constraints.gridy = 5;
panel.add(excludeInTestCodeCheckBox, constraints);
final CheckBox excludeInnerClasses = new CheckBox("Ignore inner classes", this, "excludeInnerClasses");
constraints.gridy = 6;
constraints.weighty = 1.0;
panel.add(excludeInnerClasses, constraints);
return panel;
}
}