Java tutorial
/* * 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.codeInspection.dataFlow; import com.intellij.codeInsight.*; import com.intellij.codeInspection.*; import com.intellij.ide.util.*; import com.intellij.openapi.editor.event.*; import com.intellij.openapi.project.*; import com.intellij.openapi.ui.*; import com.intellij.psi.*; import com.intellij.psi.search.*; import com.intellij.ui.*; import org.jetbrains.annotations.*; import javax.swing.*; import java.awt.*; import java.awt.event.*; import java.beans.*; import java.util.*; import java.util.List; import static com.intellij.codeInsight.ConditionChecker.Type.*; /** * Dialog that appears when the user clicks the Add Button or double clicks a row item in a MethodsPanel. The MethodsPanel is accessed from the ConditionCheckDialog */ class MethodCheckerDetailsDialog extends DialogWrapper implements PropertyChangeListener, ItemListener { private final @NotNull ConditionChecker.Type myType; private final @NotNull Project myProject; private final @NotNull ParameterDropDown parameterDropDown; private final @NotNull MethodDropDown methodDropDown; private final @NotNull ClassField classField; private final @NotNull Set<ConditionChecker> myOtherCheckers; private final @Nullable ConditionChecker myPreviouslySelectedChecker; /** * Set by the OK and/or Cancel actions so that the caller can retrieve it via a call to getMethodIsNullIsNotNullChecker */ private @Nullable ConditionChecker mySelectedChecker; MethodCheckerDetailsDialog(@Nullable ConditionChecker previouslySelectedChecker, @NotNull ConditionChecker.Type type, @NotNull Project project, @NotNull Component component, @NotNull Set<ConditionChecker> otherCheckersSameType, @NotNull Set<ConditionChecker> otherCheckers) { super(component, true); if (!isSupported(type)) throw new IllegalArgumentException("Type is invalid " + type); myProject = project; myType = type; myOtherCheckers = new HashSet<ConditionChecker>(otherCheckersSameType); myOtherCheckers.addAll(otherCheckers); myPreviouslySelectedChecker = previouslySelectedChecker; if (myPreviouslySelectedChecker != null) myOtherCheckers.remove(myPreviouslySelectedChecker); PsiClass psiClass = null; PsiMethod psiMethod = null; PsiParameter psiParameter = null; if (previouslySelectedChecker != null) { psiClass = JavaPsiFacade.getInstance(myProject).findClass(previouslySelectedChecker.getClassName(), GlobalSearchScope.allScope(myProject)); if (psiClass != null) { for (PsiMethod method : psiClass.findMethodsByName(previouslySelectedChecker.getMethodName(), true)) { if (previouslySelectedChecker.equals(buildParameterClassListFromPsiMethod(method))) { psiMethod = method; break; } } } if (psiMethod != null) { PsiParameter[] parameters = psiMethod.getParameterList().getParameters(); if (parameters.length - 1 >= previouslySelectedChecker.getCheckedParameterIndex()) { psiParameter = parameters[previouslySelectedChecker.getCheckedParameterIndex()]; } } } if (psiClass == null || psiMethod == null || psiParameter == null) { psiClass = null; psiMethod = null; psiParameter = null; } classField = new ClassField(myProject, psiClass); methodDropDown = new MethodDropDown(psiClass, psiMethod, myType, MethodDropDown.buildModel()); parameterDropDown = new ParameterDropDown(psiMethod, psiParameter, ParameterDropDown.buildModel(), myType); classField.addPropertyChangeListener(methodDropDown); classField.addPropertyChangeListener(parameterDropDown); classField.addPropertyChangeListener(this); methodDropDown.addItemListener(parameterDropDown); methodDropDown.addItemListener(this); parameterDropDown.addItemListener(this); init(); checkOkActionEnable(); setTitle(initTitle(type)); } private static boolean isSupported(ConditionChecker.Type type) { return type == IS_NULL_METHOD || type == IS_NOT_NULL_METHOD || type == ASSERT_IS_NULL_METHOD || type == ASSERT_IS_NOT_NULL_METHOD || type == ASSERT_TRUE_METHOD || type == ASSERT_FALSE_METHOD; } private static List<String> buildParameterClassListFromPsiMethod(PsiMethod psiMethod) { List<String> parameterClasses = new ArrayList<String>(); PsiParameter[] parameters = psiMethod.getParameterList().getParameters(); for (PsiParameter param : parameters) { PsiTypeElement typeElement = param.getTypeElement(); if (typeElement == null) return new ArrayList<String>(); PsiType psiType = typeElement.getType(); parameterClasses.add(psiType.getCanonicalText()); } return parameterClasses; } private static String initTitle(@NotNull ConditionChecker.Type type) { if (type.equals(IS_NULL_METHOD)) { return InspectionsBundle.message("configure.checker.option.isNull.add.method.checker.dialog.title"); } else if (type.equals(IS_NOT_NULL_METHOD)) { return InspectionsBundle.message("configure.checker.option.isNotNull.add.method.checker.dialog.title"); } else if (type.equals(ASSERT_IS_NULL_METHOD)) { return InspectionsBundle .message("configure.checker.option.assert.isNull.add.method.checker.dialog.title"); } else if (type.equals(ASSERT_IS_NOT_NULL_METHOD)) { return InspectionsBundle .message("configure.checker.option.assert.isNotNull.add.method.checker.dialog.title"); } else if (type.equals(ASSERT_TRUE_METHOD)) { return InspectionsBundle .message("configure.checker.option.assert.true.add.method.checker.dialog.title"); } else if (type.equals(ASSERT_FALSE_METHOD)) { return InspectionsBundle .message("configure.checker.option.assert.false.add.method.checker.dialog.title"); } else { throw new IllegalArgumentException("MethodCheckerDetailsDialog does not support type " + type); } } @Override protected JComponent createCenterPanel() { final JPanel panel = new JPanel(); panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS)); final LabeledComponent<ClassField> classComponent = new LabeledComponent<ClassField>(); final LabeledComponent<MethodDropDown> methodComponent = new LabeledComponent<MethodDropDown>(); final LabeledComponent<ParameterDropDown> parameterComponent = new LabeledComponent<ParameterDropDown>(); classComponent.setText("Class"); methodComponent.setText("Method"); parameterComponent.setText("Parameter"); classComponent.setComponent(classField); methodComponent.setComponent(methodDropDown); parameterComponent.setComponent(parameterDropDown); panel.add(classComponent); panel.add(methodComponent); panel.add(parameterComponent); return panel; } @Nullable ConditionChecker getConditionChecker() { return mySelectedChecker; } @Nullable private ConditionChecker buildConditionChecker() { PsiClass psiClass = classField.getPsiClass(); PsiMethod psiMethod = methodDropDown.getSelectedPsiMethod(); PsiParameter psiParameter = parameterDropDown.getSelectedPsiParameter(); if (psiClass != null && psiMethod != null && psiParameter != null) { return new ConditionChecker.FromPsiBuilder(psiMethod, psiParameter, myType).build(); } else { return null; } } private boolean overlaps(ConditionChecker thisChecker) { for (ConditionChecker overlappingChecker : myOtherCheckers) { if (thisChecker.overlaps(overlappingChecker)) { Messages.showMessageDialog(myProject, InspectionsBundle.message("configure.checker.option.overlap.error.msg") + " " + overlappingChecker.getConditionCheckType() + " " + overlappingChecker.toString(), InspectionsBundle.message("configure.checker.option.overlap.error.title"), Messages.getErrorIcon()); return true; } } return false; } @Override public void propertyChange(PropertyChangeEvent evt) { checkOkActionEnable(); } @Override public void itemStateChanged(ItemEvent e) { checkOkActionEnable(); } private void checkOkActionEnable() { if (classField.getPsiClass() == null || methodDropDown.getSelectedPsiMethod() == null || parameterDropDown.getSelectedPsiParameter() == null) { setOKActionEnabled(false); } else { setOKActionEnabled(true); } } @Override protected void doOKAction() { ConditionChecker checker = buildConditionChecker(); if (checker != null && !overlaps(checker)) { if (checker.equals(myPreviouslySelectedChecker)) { mySelectedChecker = myPreviouslySelectedChecker; } else { mySelectedChecker = checker; } super.doOKAction(); } } @Override public boolean isOKActionEnabled() { if (!myOKAction.isEnabled()) return false; PsiClass psiClass = classField.getPsiClass(); PsiMethod psiMethod = methodDropDown.getSelectedPsiMethod(); PsiParameter psiParameter = parameterDropDown.getSelectedPsiParameter(); if (psiClass == null || psiMethod == null || psiParameter == null) { return false; } else { return true; } } /** * Input Text Field for Class Name */ static class ClassField extends EditorTextFieldWithBrowseButton implements ActionListener, DocumentListener { public static final String PROPERTY_PSICLASS = "ClassField.myPsiClass"; private final @NotNull Project myProject; private @Nullable PsiClass myPsiClass; public ClassField(@NotNull Project project, @Nullable PsiClass psiClass) { super(project, true, buildVisibilityChecker()); myProject = project; myPsiClass = psiClass; setPreferredSize(new Dimension(500, (int) getPreferredSize().getHeight())); if (myPsiClass != null) { //noinspection ConstantConditions setText(myPsiClass.getQualifiedName()); } addActionListener(this); getChildComponent().addDocumentListener(this); } private static JavaCodeFragment.VisibilityChecker buildVisibilityChecker() { return new JavaCodeFragment.VisibilityChecker() { @Override public Visibility isDeclarationVisible(PsiElement declaration, PsiElement place) { return Visibility.VISIBLE; } }; } @Override public void actionPerformed(ActionEvent e) { final TreeClassChooser chooser = TreeClassChooserFactory.getInstance(myProject) .createNoInnerClassesScopeChooser("Choose Class", GlobalSearchScope.allScope(myProject), new ClassFilter() { @Override public boolean isAccepted(PsiClass aClass) { return !aClass.isAnnotationType(); } }, null); chooser.showDialog(); PsiClass psiClass = chooser.getSelected(); if (psiClass != null) { //noinspection ConstantConditions setText(chooser.getSelected().getQualifiedName()); } } @Nullable public PsiClass getPsiClass() { return myPsiClass; } @Override public void beforeDocumentChange(DocumentEvent event) { } @Override public void documentChanged(DocumentEvent event) { String className = event.getDocument().getText(); PsiClass psiClass = null; if (className != null) { psiClass = JavaPsiFacade.getInstance(myProject).findClass(className, GlobalSearchScope.allScope(myProject)); } if (psiClass != null && myPsiClass != null) { if (!psiClass.equals(myPsiClass)) { firePropertyChange(PROPERTY_PSICLASS, myPsiClass, psiClass); myPsiClass = psiClass; } } else if (psiClass != null) { firePropertyChange(PROPERTY_PSICLASS, myPsiClass, psiClass); myPsiClass = psiClass; } else if (myPsiClass != null) { firePropertyChange(PROPERTY_PSICLASS, myPsiClass, psiClass); myPsiClass = null; } } } /** * Drop Down for picking Method Name */ static class MethodDropDown extends JComboBox implements PropertyChangeListener { private final @NotNull ConditionChecker.Type myType; private final @NotNull SortedComboBoxModel<MethodWrapper> myModel; private @Nullable PsiClass myPsiClass; MethodDropDown(@Nullable PsiClass psiClass, @Nullable PsiMethod psiMethod, @NotNull ConditionChecker.Type type, @NotNull SortedComboBoxModel<MethodWrapper> model) { super(model); if (!isSupported(type)) throw new IllegalArgumentException("Type is invalid " + type); myPsiClass = psiClass; myType = type; myModel = model; setEnabled(myPsiClass != null); initValues(); if (psiMethod != null) { for (Iterator<MethodWrapper> iterator = myModel.iterator(); iterator.hasNext();) { MethodWrapper methodWrapper = iterator.next(); if (methodWrapper.getPsiMethod().equals(psiMethod)) { setSelectedItem(methodWrapper); } } } } private static boolean isMethodFromJavaLangObject(PsiMethod method) { if (method == null) return false; PsiClass containingClass = method.getContainingClass(); if (containingClass == null) return false; String name = containingClass.getQualifiedName(); if (name == null) return false; if (CommonClassNames.JAVA_LANG_OBJECT.equals(name)) return true; return false; } @NotNull public static SortedComboBoxModel<MethodWrapper> buildModel() { return new SortedComboBoxModel<MethodWrapper>(new Comparator<MethodWrapper>() { @Override public int compare(MethodWrapper o1, MethodWrapper o2) { return o1.compareTo(o2); } }); } private void initValues() { if (myPsiClass != null) { myModel.clear(); myModel.setSelectedItem(null); PsiMethod[] allMethods = myPsiClass.getAllMethods(); for (PsiMethod method : allMethods) { MethodWrapper methodWrapper = new MethodWrapper(method); if (qualifies(method) && !myModel.getItems().contains(methodWrapper)) myModel.add(methodWrapper); } } } public boolean qualifies(PsiMethod psiMethod) { if (isMethodFromJavaLangObject(psiMethod)) { return false; } final PsiParameter[] parameters = psiMethod.getParameterList().getParameters(); if (parameters.length < 1) { return false; } if (myType == IS_NULL_METHOD || myType == IS_NOT_NULL_METHOD) { PsiType returnType = psiMethod.getReturnType(); if (returnType != PsiType.BOOLEAN && (returnType == null || !returnType.getCanonicalText().equals(Boolean.class.toString()))) { return false; } } else if (myType == ASSERT_TRUE_METHOD || myType == ASSERT_FALSE_METHOD) { boolean booleanParamExists = false; for (PsiParameter psiParameter : parameters) { PsiType type = psiParameter.getType(); if (type.equals(PsiType.BOOLEAN) || type.getCanonicalText().equals(Boolean.class.toString())) { booleanParamExists = true; break; } } if (!booleanParamExists) { return false; } } // Else it's ASSERT_IS_NULL_METHOD or ASSERT_IS_NOT_NULL_METHOD. // In that case there is no additional validation return true; } /** * Called when ClassField is set and when user selects entry in the MethodDropDown */ @Override public void propertyChange(PropertyChangeEvent evt) { if (evt.getPropertyName().equals(ClassField.PROPERTY_PSICLASS)) { if (evt.getNewValue() == null) { clear(); } else { setEnabled(true); if (myPsiClass == null || !myPsiClass.equals(evt.getNewValue())) { // ClassChanged so refresh list myPsiClass = (PsiClass) evt.getNewValue(); initValues(); } } } } public void clear() { myModel.clear(); myModel.setSelectedItem(null); setEnabled(false); myPsiClass = null; } @Nullable public PsiMethod getSelectedPsiMethod() { MethodWrapper methodWrapper = myModel.getSelectedItem(); if (methodWrapper == null) return null; return methodWrapper.getPsiMethod(); } } /** * Drop Down for picking Parameter Name */ static class ParameterDropDown extends JComboBox implements PropertyChangeListener, ItemListener { private final @NotNull SortedComboBoxModel<ParameterWrapper> myModel; private final @NotNull ConditionChecker.Type myType; private @Nullable PsiMethod myPsiMethod; public ParameterDropDown(@Nullable PsiMethod psiMethod, @Nullable PsiParameter psiParameter, @NotNull SortedComboBoxModel<ParameterWrapper> model, @NotNull ConditionChecker.Type type) { super(model); if (!isSupported(type)) throw new IllegalArgumentException("Type is invalid " + type); myPsiMethod = psiMethod; myModel = model; myType = type; if (myPsiMethod != null) { setEnabled(true); myModel.addAll(getParameterWrappers()); if (psiParameter != null) { for (Iterator iterator = myModel.iterator(); iterator.hasNext();) { ParameterWrapper wrapper = (ParameterWrapper) iterator.next(); if (wrapper.getPsiParameter().equals(psiParameter)) setSelectedItem(wrapper); } } } else { setEnabled(false); } } public static SortedComboBoxModel<ParameterWrapper> buildModel() { return new SortedComboBoxModel<ParameterWrapper>(new Comparator<ParameterWrapper>() { @Override public int compare(ParameterWrapper o1, ParameterWrapper o2) { return o1.compareTo(o2); } }); } List<ParameterWrapper> getParameterWrappers() { List<ParameterWrapper> wrappers = new ArrayList<ParameterWrapper>(); if (myPsiMethod != null) { PsiParameterList parameterList = myPsiMethod.getParameterList(); for (int i = 0; i < parameterList.getParameters().length; i++) { PsiParameter psiParameter = parameterList.getParameters()[i]; if (myType == ASSERT_TRUE_METHOD || myType == ASSERT_FALSE_METHOD) { PsiType type = psiParameter.getType(); if (type.equals(PsiType.BOOLEAN) || type.getCanonicalText().equals(Boolean.class.toString())) { wrappers.add(new ParameterWrapper(psiParameter, i)); } } else { wrappers.add(new ParameterWrapper(psiParameter, i)); } } } return wrappers; } @Override public void itemStateChanged(ItemEvent e) { if (e.getSource() instanceof MethodDropDown) { // The MethodDropDown has changed. MethodDropDown methodDropDown = (MethodDropDown) e.getSource(); if (methodDropDown.getSelectedPsiMethod() != null) { setEnabled(true); if (myPsiMethod == null || !myPsiMethod.equals(methodDropDown.getSelectedPsiMethod())) { myPsiMethod = methodDropDown.getSelectedPsiMethod(); myModel.clear(); myModel.addAll(getParameterWrappers()); myModel.setSelectedItem(null); } } else { myPsiMethod = null; myModel.clear(); myModel.setSelectedItem(null); setEnabled(false); } } else { throw new RuntimeException( "Unexpected Configuration ParameterDropDown is only expected to receive events from MethodDropDown."); } } @Override public void propertyChange(PropertyChangeEvent evt) { if (evt.getPropertyName().equals(ClassField.PROPERTY_PSICLASS)) { if (evt.getNewValue() == null) { setEnabled(false); } } } @Nullable public PsiParameter getSelectedPsiParameter() { ParameterWrapper parameterWrapper = myModel.getSelectedItem(); if (parameterWrapper == null) return null; return parameterWrapper.getPsiParameter(); } class ParameterWrapper implements Comparable<ParameterWrapper> { private final @NotNull String id; private final @NotNull PsiParameter psiParameter; private final int index; ParameterWrapper(@NotNull PsiParameter psiParameter, int index) { this.psiParameter = psiParameter; this.index = index; String typeName; PsiTypeElement typeElement = psiParameter.getTypeElement(); if (typeElement == null) { typeName = ""; } else { if (typeElement.getType() instanceof PsiPrimitiveType) { typeName = ((PsiPrimitiveType) typeElement.getType()).getBoxedTypeName(); } else { typeName = typeElement.getType().getCanonicalText(); } } id = typeName + " " + psiParameter.getName(); } @Override public int compareTo(ParameterWrapper o) { return index - o.index; } @Override public String toString() { return id; } @NotNull public PsiParameter getPsiParameter() { return psiParameter; } } } static class MethodWrapper implements Comparable<MethodWrapper> { private final @NotNull PsiMethod myPsiMethod; private final @NotNull String myId; MethodWrapper(@NotNull PsiMethod psiMethod) { this.myPsiMethod = psiMethod; List<String> parameterClassNames = new ArrayList<String>(); PsiParameter[] parameters = psiMethod.getParameterList().getParameters(); for (PsiParameter psiParameter : parameters) { parameterClassNames.add(getParameterQualifiedName(psiParameter)); } myId = initId(psiMethod.getName(), parameterClassNames); } private static String getParameterQualifiedName(PsiParameter psiParameter) { PsiTypeElement typeElement = psiParameter.getTypeElement(); if (typeElement == null) { return ""; } if (typeElement.getType() instanceof PsiPrimitiveType) { return ((PsiPrimitiveType) typeElement.getType()).getBoxedTypeName(); } return typeElement.getType().getCanonicalText(); } private static String initId(String methodName, List<String> parameterNames) { String shortName = methodName + "("; for (String parameterName : parameterNames) { if (parameterNames.lastIndexOf(".") > -1) { shortName += parameterName.substring(parameterName.lastIndexOf(".") + 1) + ", "; } else { shortName += parameterName + ", "; } } if (parameterNames.size() > 0) shortName = shortName.substring(0, shortName.lastIndexOf(", ")); shortName += ")"; return shortName; } @NotNull public PsiMethod getPsiMethod() { return myPsiMethod; } @NotNull public String getId() { return myId; } @Override public String toString() { return myId; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; MethodWrapper that = (MethodWrapper) o; if (!myId.equals(that.myId)) return false; return true; } @Override public int hashCode() { return myId.hashCode(); } @Override public int compareTo(MethodWrapper o) { return myId.compareTo(o.myId); } } }