From 0e12be8b663d58f7a9629ff0fced570e3524bfc7 Mon Sep 17 00:00:00 2001 From: peter Date: Thu, 21 Feb 2013 11:17:53 +0100 Subject: [PATCH 001/354] [Johnny Clasrk] IsNull IsNotNull True False Checks and Assertions (IDEA-35808) --- .../codeInsight/ConditionCheckManager.java | 252 +++++++ .../codeInsight/ConditionChecker.java | 55 ++ .../codeInsight/MethodConditionCheck.java | 396 +++++++++++ .../dataFlow/ConditionCheckDialog.java | 264 ++++++++ .../dataFlow/ControlFlowAnalyzer.java | 77 +++ .../dataFlow/DataFlowInspection.java | 18 + .../dataFlow/MethodCheckerDetailsDialog.java | 621 ++++++++++++++++++ .../dataFlow/fixture/AssertFalse.java | 9 + .../dataFlow/fixture/AssertIsNotNull.java | 10 + .../dataFlow/fixture/AssertIsNull.java | 10 + .../dataFlow/fixture/AssertTrue.java | 9 + .../dataFlow/fixture/IsNotNullCheck.java | 9 + .../dataFlow/fixture/IsNullCheck.java | 9 + .../DataFlowInspectionTest.java | 63 ++ .../src/messages/InspectionsBundle.properties | 16 + resources/src/META-INF/IdeaPlugin.xml | 3 + 16 files changed, 1821 insertions(+) create mode 100644 java/java-impl/src/com/intellij/codeInsight/ConditionCheckManager.java create mode 100644 java/java-impl/src/com/intellij/codeInsight/ConditionChecker.java create mode 100644 java/java-impl/src/com/intellij/codeInsight/MethodConditionCheck.java create mode 100644 java/java-impl/src/com/intellij/codeInspection/dataFlow/ConditionCheckDialog.java create mode 100644 java/java-impl/src/com/intellij/codeInspection/dataFlow/MethodCheckerDetailsDialog.java create mode 100644 java/java-tests/testData/inspection/dataFlow/fixture/AssertFalse.java create mode 100644 java/java-tests/testData/inspection/dataFlow/fixture/AssertIsNotNull.java create mode 100644 java/java-tests/testData/inspection/dataFlow/fixture/AssertIsNull.java create mode 100644 java/java-tests/testData/inspection/dataFlow/fixture/AssertTrue.java create mode 100644 java/java-tests/testData/inspection/dataFlow/fixture/IsNotNullCheck.java create mode 100644 java/java-tests/testData/inspection/dataFlow/fixture/IsNullCheck.java diff --git a/java/java-impl/src/com/intellij/codeInsight/ConditionCheckManager.java b/java/java-impl/src/com/intellij/codeInsight/ConditionCheckManager.java new file mode 100644 index 000000000000..539a3a1b231a --- /dev/null +++ b/java/java-impl/src/com/intellij/codeInsight/ConditionCheckManager.java @@ -0,0 +1,252 @@ +/* + * Copyright 2000-2012 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.codeInsight; + +import com.intellij.openapi.components.*; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.project.Project; +import com.intellij.psi.PsiMethod; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.List; + +/** + * @author Johnny Clark + * Creation Date: 8/3/12 + */ +@State( + name = "IsNullIsNotNullCheckManager", + storages = {@Storage(id = "dir", file = StoragePathMacros.PROJECT_CONFIG_DIR + "/checker.xml", scheme = StorageScheme.DIRECTORY_BASED)} +) +public class ConditionCheckManager implements PersistentStateComponent { + @SuppressWarnings({"FieldCanBeLocal", "UnusedDeclaration"}) private State state; + private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.ConditionCheckManager"); + + private List myIsNullCheckMethods = new ArrayList(); + private List myIsNotNullCheckMethods = new ArrayList(); + + private List myAssertIsNullMethods = new ArrayList(); + private List myAssertIsNotNullMethods = new ArrayList(); + + private List myAssertTrueMethods = new ArrayList(); + private List myAssertFalseMethods = new ArrayList(); + + private static Project myProject; + + public static ConditionCheckManager getInstance(Project project) { + myProject = project; + return ServiceManager.getService(project, ConditionCheckManager.class); + } + + public void setIsNullCheckMethods(List methodConditionChecks) { + myIsNullCheckMethods.clear(); + myIsNullCheckMethods.addAll(methodConditionChecks); + } + + public void setIsNotNullCheckMethods(List methodConditionChecks) { + myIsNotNullCheckMethods.clear(); + myIsNotNullCheckMethods.addAll(methodConditionChecks); + } + + public void setAssertNullMethods(List methodConditionChecks) { + myAssertIsNullMethods.clear(); + myAssertIsNullMethods.addAll(methodConditionChecks); + } + + public void setAssertNotNullMethods(List methodConditionChecks) { + myAssertIsNotNullMethods.clear(); + myAssertIsNotNullMethods.addAll(methodConditionChecks); + } + + public void setAssertTrueMethods(List psiMethodWrappers) { + myAssertTrueMethods.clear(); + myAssertTrueMethods.addAll(psiMethodWrappers); + } + + public void setAssertFalseMethods(List psiMethodWrappers) { + myAssertFalseMethods.clear(); + myAssertFalseMethods.addAll(psiMethodWrappers); + } + + public List getIsNullCheckMethods() { + return myIsNullCheckMethods; + } + + public List getIsNotNullCheckMethods() { + return myIsNotNullCheckMethods; + } + + public List getAssertIsNullMethods() { + return myAssertIsNullMethods; + } + + public List getAssertIsNotNullMethods() { + return myAssertIsNotNullMethods; + } + + public List getAssertFalseMethods() { + return myAssertFalseMethods; + } + + public List getAssertTrueMethods() { + return myAssertTrueMethods; + } + + public static class State { + public List myIsNullCheckMethods = new ArrayList(); + public List myIsNotNullCheckMethods = new ArrayList(); + public List myAssertIsNullMethods = new ArrayList(); + public List myAssertIsNotNullMethods = new ArrayList(); + public List myAssertTrueMethods = new ArrayList(); + public List myAssertFalseMethods = new ArrayList(); + } + + @Override + public State getState() { + State state = new State(); + + loadMethodChecksToState(state.myIsNullCheckMethods, myIsNullCheckMethods); + loadMethodChecksToState(state.myIsNotNullCheckMethods, myIsNotNullCheckMethods); + loadMethodChecksToState(state.myAssertIsNullMethods, myAssertIsNullMethods); + loadMethodChecksToState(state.myAssertIsNotNullMethods, myAssertIsNotNullMethods); + loadMethodChecksToState(state.myAssertTrueMethods, myAssertTrueMethods); + loadMethodChecksToState(state.myAssertFalseMethods, myAssertFalseMethods); + + return state; + } + + private static void loadMethodChecksToState(List listToLoadTo, List listToLoadFrom) { + for (MethodConditionCheck checker : listToLoadFrom) { + listToLoadTo.add(checker.toString()); + } + } + + @Override + public void loadState(State state) { + this.state = state; + loadMethods(myIsNullCheckMethods, state.myIsNullCheckMethods, ConditionChecker.Type.IS_NULL_METHOD); + loadMethods(myIsNotNullCheckMethods, state.myIsNotNullCheckMethods, ConditionChecker.Type.IS_NOT_NULL_METHOD); + loadMethods(myAssertIsNullMethods, state.myAssertIsNullMethods, ConditionChecker.Type.ASSERT_IS_NULL_METHOD); + loadMethods(myAssertIsNotNullMethods, state.myAssertIsNotNullMethods, ConditionChecker.Type.ASSERT_IS_NOT_NULL_METHOD); + loadMethods(myAssertTrueMethods, state.myAssertTrueMethods, ConditionChecker.Type.ASSERT_TRUE_METHOD); + loadMethods(myAssertFalseMethods, state.myAssertFalseMethods, ConditionChecker.Type.ASSERT_FALSE_METHOD); + } + + public void loadMethods(List listToLoadTo, List listToLoadFrom, ConditionChecker.Type type){ + listToLoadTo.clear(); + for (String setting : listToLoadFrom) { + try { + listToLoadTo.add(new MethodConditionCheck.Builder(setting, type, myProject).build()); + } catch (Exception e) { + LOG.error("Problem occurred while attempting to load Condition Check from configuration file. " + e.getMessage()); + } + } + } + + public static boolean isMethod(@NotNull PsiMethod psiMethod, List checkers) { + for (MethodConditionCheck checker : checkers) { + if (checker.matches(psiMethod)) { + return true; + } + } + return false; + } + + public static boolean isCheck(PsiMethod psiMethod) { + ConditionCheckManager manager = getInstance(psiMethod.getProject()); + return isMethod(psiMethod, manager.getIsNullCheckMethods()) || + isMethod(psiMethod, manager.getIsNotNullCheckMethods()) || + isMethod(psiMethod, manager.getAssertIsNullMethods()) || + isMethod(psiMethod, manager.getAssertIsNotNullMethods()) || + isAssertTrueCheckMethod(psiMethod) || + isAssertFalseCheckMethod(psiMethod); + } + + public static boolean isNullCheckMethod(PsiMethod psiMethod) { + return methodMatches(psiMethod, getInstance(psiMethod.getProject()).getIsNullCheckMethods()); + } + + public static boolean isNotNullCheckMethod(PsiMethod psiMethod) { + return methodMatches(psiMethod, getInstance(psiMethod.getProject()).getIsNotNullCheckMethods()); + } + + public static boolean isAssertIsNullCheckMethod(PsiMethod psiMethod) { + return methodMatches(psiMethod, getInstance(psiMethod.getProject()).getAssertIsNullMethods()); + } + + public static boolean isAssertIsNotNullCheckMethod(PsiMethod psiMethod) { + return methodMatches(psiMethod, getInstance(psiMethod.getProject()).getAssertIsNotNullMethods()); + } + + public static boolean isAssertTrueCheckMethod(PsiMethod psiMethod) { + return methodMatches(psiMethod, getInstance(psiMethod.getProject()).getAssertTrueMethods()); + } + + public static boolean isAssertFalseCheckMethod(PsiMethod psiMethod) { + return methodMatches(psiMethod, getInstance(psiMethod.getProject()).getAssertFalseMethods()); + } + + public static boolean isNullCheckMethod(PsiMethod psiMethod, int paramIndex) { + return methodMatches(psiMethod, paramIndex, getInstance(psiMethod.getProject()).getIsNullCheckMethods()); + } + + public static boolean isNotNullCheckMethod(PsiMethod psiMethod, int paramIndex) { + return methodMatches(psiMethod, paramIndex, getInstance(psiMethod.getProject()).getIsNotNullCheckMethods()); + } + + public static boolean isAssertIsNullCheckMethod(PsiMethod psiMethod, int paramIndex) { + return methodMatches(psiMethod, paramIndex, getInstance(psiMethod.getProject()).getAssertIsNullMethods()); + } + + public static boolean isAssertIsNotNullCheckMethod(PsiMethod psiMethod, int paramIndex) { + return methodMatches(psiMethod, paramIndex, getInstance(psiMethod.getProject()).getAssertIsNotNullMethods()); + } + + public static boolean isAssertTrueCheckMethod(PsiMethod psiMethod, int paramIndex) { + return methodMatches(psiMethod, paramIndex, getInstance(psiMethod.getProject()).getAssertTrueMethods()); + } + + public static boolean isAssertFalseCheckMethod(PsiMethod psiMethod, int paramIndex) { + return methodMatches(psiMethod, paramIndex, getInstance(psiMethod.getProject()).getAssertFalseMethods()); + } + + public static boolean methodMatches(PsiMethod psiMethod, List checkers) { + for (MethodConditionCheck checker : checkers) { + if (checker.matches(psiMethod)) + return true; + } + return false; + } + + public static boolean methodMatches(PsiMethod psiMethod, int paramIndex, List checkers) { + for (MethodConditionCheck checker : checkers) { + if (checker.matches(psiMethod, paramIndex)) + return true; + } + return false; + } + + public static boolean isNullCheck(PsiMethod psiMethod) { + ConditionCheckManager manager = getInstance(psiMethod.getProject()); + return isMethod(psiMethod, manager.getIsNullCheckMethods()) || isMethod(psiMethod, manager.getAssertIsNullMethods()); + } + + public static boolean isNotNullCheck(PsiMethod psiMethod) { + ConditionCheckManager manager = getInstance(psiMethod.getProject()); + return isMethod(psiMethod, manager.getIsNotNullCheckMethods()) || isMethod(psiMethod, manager.getAssertIsNotNullMethods()); + } +} diff --git a/java/java-impl/src/com/intellij/codeInsight/ConditionChecker.java b/java/java-impl/src/com/intellij/codeInsight/ConditionChecker.java new file mode 100644 index 000000000000..ffcbb3c57558 --- /dev/null +++ b/java/java-impl/src/com/intellij/codeInsight/ConditionChecker.java @@ -0,0 +1,55 @@ +/* + * Copyright 2000-2012 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.codeInsight; + +import com.intellij.psi.*; + +/** + * Interface for IsNull, IsNotNull Method Checks and Assert True/False/IsNull/IsNotNull Method Checks to be performed by the Constant Condition Inspection. + * These Checkers allow the user to specify that the method in question performs some type of validation on the parameter passed into the method. + * For example, if the method is defined as performing an IsNotNull Check, and variable x is passed into the method, then all code after the method call + * will assume that x is Not Null. + * + * @author Johnny Clark + * Creation Date: 8/14/12 + */ +public interface ConditionChecker { + enum Type { + IS_NULL_METHOD("IsNull Method"), + IS_NOT_NULL_METHOD("IsNotNull Method"), + ASSERT_IS_NULL_METHOD("Assert IsNull Method"), + ASSERT_IS_NOT_NULL_METHOD("Assert IsNotNull Method"), + ASSERT_TRUE_METHOD("Assert True Method"), + ASSERT_FALSE_METHOD("Assert False Method"); + private final String myStringRepresentation; + + Type(String stringRepresentation) { + myStringRepresentation = stringRepresentation; + } + + @Override + public String toString() { + return myStringRepresentation; + } + } + + boolean matches(PsiMethod psiMethod); + boolean matches(PsiMethod psiMethod, int paramIndex); + + boolean overlaps(ConditionChecker checker); + + Type getType(); +} diff --git a/java/java-impl/src/com/intellij/codeInsight/MethodConditionCheck.java b/java/java-impl/src/com/intellij/codeInsight/MethodConditionCheck.java new file mode 100644 index 000000000000..b8f1afbf9442 --- /dev/null +++ b/java/java-impl/src/com/intellij/codeInsight/MethodConditionCheck.java @@ -0,0 +1,396 @@ +/* + * Copyright 2000-2012 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.codeInsight; + +import com.intellij.openapi.project.*; +import com.intellij.psi.*; +import com.intellij.psi.search.*; +import org.jetbrains.annotations.*; + +import java.util.*; + +import static com.intellij.codeInsight.ConditionChecker.Type.*; + +/** + * Used by Constant Condition Inspection to identify methods which perform some type of Validation on the parameters passed into them. + * For example given the following method + *
+ * {@code
+ *  class Foo {
+ *     static boolean validateNotNull(Object o) {
+ *       if (o == null) return false;
+ *       else return true;
+ *     }
+ *   }
+ * }
+ *
+ * The corresponding MethodConditionCheck would be 

+ * myType=Type.IS_NOT_NULL_METHOD + * myPsiClass=Foo + * myPsiMethod=validateNotNull + * myPsiParameter=o + * + * The following block of code would produce a Inspection Warning that o is always true + * + *

+ * {@code
+ *   if (Value.isNotNull(o)) {
+ *     if(o != null) {}
+ *   }
+ * }
+ * 
+ * + * @author Johnny Clark + * Creation Date: 8/14/12 + */ +public class MethodConditionCheck implements ConditionChecker, Comparable { + private final @NotNull Type myType; + private final @NotNull PsiClass myPsiClass; + private final @NotNull PsiMethod myPsiMethod; + private final @NotNull PsiParameter myPsiParameter; + private final String fullName; + private final String shortName; + + public MethodConditionCheck(@NotNull PsiMethod psiMethod, @NotNull PsiParameter psiParameter, @NotNull Type type) { + myPsiMethod = psiMethod; + myPsiParameter = psiParameter; + if (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) + throw new IllegalArgumentException("Type is invalid " + type); + + PsiClass containingClass = psiMethod.getContainingClass(); + if (containingClass == null) + throw new IllegalArgumentException("PsiMethod has null Containing Class"); + + myPsiClass = containingClass; + myType = type; + + validatePsiMethod(); + String className = initClassNameFromPsiMethod(); + String methodName = initMethodNameFromPsiMethod(); + List parameters = initParameterNamesFromPsiMethod(myPsiParameter); + fullName = initFullName(className, methodName, parameters); + shortName = initShortName(methodName, parameters); + } + + @Override + public boolean matches(PsiMethod psiMethod) { + if (myPsiMethod.equals(psiMethod)) { + return true; + } + + // The equals method in PsiMethod compares to see if they are the same object, but sometimes they are not the same object but do represent the same method + if (!myPsiMethod.getName().equals(psiMethod.getName())) return false; + + PsiClass myContainingClass = myPsiMethod.getContainingClass(); + PsiClass containingClass = myPsiMethod.getContainingClass(); + if (myContainingClass == null && containingClass != null) return false; + if (myContainingClass != null && containingClass == null) return false; + if (myContainingClass != null) { // Both must be non-null + String myQualifiedName = myContainingClass.getQualifiedName(); + String qualifiedName = containingClass.getQualifiedName(); + if (myQualifiedName == null && qualifiedName != null) return false; + if (myQualifiedName != null && qualifiedName == null) return false; + if (myQualifiedName != null && !myQualifiedName.equals(qualifiedName)) return false; + } + + PsiParameterList myPsiParameterList = myPsiMethod.getParameterList(); + PsiParameterList psiParameterList = psiMethod.getParameterList(); + if (myPsiParameterList.getParameters().length != psiParameterList.getParameters().length) return false; + for (int i = 0; i < myPsiParameterList.getParameters().length; i++) { + PsiParameter myPsiParameter = myPsiParameterList.getParameters()[i]; + PsiParameter psiParameter = psiParameterList.getParameters()[i]; + if (myPsiParameter == null && psiParameter != null) return false; + if (myPsiParameter != null && psiParameter == null) return false; + if (myPsiParameter != null) { // Both must be non-null + PsiTypeElement myPsiTypeElement = myPsiParameter.getTypeElement(); + PsiTypeElement psiTypeElement = psiParameter.getTypeElement(); + if (myPsiTypeElement == null && psiTypeElement != null) return false; + if (myPsiTypeElement != null && psiTypeElement == null) return false; + if (myPsiTypeElement != null && myPsiTypeElement.getType() == psiTypeElement.getType()) return false; + } + } + + return true; + } + + @Override + public boolean matches(PsiMethod psiMethod, int paramIndex) { + if (matches(psiMethod)) { + PsiParameter[] parameters = myPsiMethod.getParameterList().getParameters(); + if (parameters.length <= paramIndex) + return false; + + PsiParameter parameter = parameters[paramIndex]; + if (parameter.equals(myPsiParameter)) + return true; + else + return false; + } + + return false; + } + + @Override + public boolean overlaps(ConditionChecker checker) { + MethodConditionCheck otherChecker = (MethodConditionCheck) checker; + if (myPsiClass.equals(otherChecker.myPsiClass) && myPsiMethod.equals(otherChecker.myPsiMethod) && myPsiParameter.equals(otherChecker.myPsiParameter)) + return true; + + return false; + } + + @Override + public Type getType() { + return myType; + } + + private void validatePsiMethod() { + PsiElement psiElement = myPsiMethod.getContainingClass(); + if (!(psiElement instanceof PsiClass)) + throw new IllegalArgumentException("PsiMethod " + myPsiMethod + " can not have a null containing class."); + + PsiType returnType = myPsiMethod.getReturnType(); + if (!isAssert()) { + if (returnType == null) + throw new IllegalArgumentException("PsiMethod " + myPsiMethod + " has a null return type PsiType."); + + if (returnType != PsiType.BOOLEAN && !returnType.getCanonicalText().equals(Boolean.class.toString())) { + throw new IllegalArgumentException("PsiMethod " + myPsiMethod + " must have a null return type PsiType of boolean or Boolean."); + } + } + + boolean parameterFound = false; + for (int i = 0; i < myPsiMethod.getParameterList().getParameters().length; i++) { + if (myPsiParameter.equals(myPsiMethod.getParameterList().getParameters()[i])) { + parameterFound = true; + break; + } + } + + if (!parameterFound) { + throw new IllegalArgumentException("PsiMethod " + myPsiMethod + " must have parameter " + getFullyQualifiedName(myPsiParameter)); + } + } + + private boolean isAssert() { + return myType == ASSERT_IS_NULL_METHOD || myType == ASSERT_IS_NOT_NULL_METHOD || myType == ASSERT_TRUE_METHOD || myType == ASSERT_FALSE_METHOD; + } + + private String initClassNameFromPsiMethod() { + PsiElement psiElement = myPsiMethod.getContainingClass(); + PsiClass psiClass = (PsiClass) psiElement; + return psiClass.getQualifiedName(); + } + + private String initMethodNameFromPsiMethod() { + return myPsiMethod.getName(); + } + + private List initParameterNamesFromPsiMethod(PsiParameter selectedParameter) { + List parameters = new ArrayList(); + for (int i = 0; i < myPsiMethod.getParameterList().getParameters().length; i++) { + PsiParameter param = myPsiMethod.getParameterList().getParameters()[i]; + String parameter = getFullyQualifiedName(param); + if (param.equals(selectedParameter)) + parameters.add("*" + parameter + "*"); + else + parameters.add(parameter); + } + return parameters; + } + + public static String getFullyQualifiedName(PsiParameter psiParameter) { + PsiTypeElement typeElement = psiParameter.getTypeElement(); + if (typeElement == null) + throw new RuntimeException("Parameter has null typeElement " + psiParameter.getName()); + + PsiType psiType = typeElement.getType(); + + return psiType.getCanonicalText() + " " + psiParameter.getName(); + } + + private String initFullName(String className, String methodName, List parameters) { + String s = className + "." + methodName + "("; + for (String parameterName : parameters) { + s += parameterName + ", "; + } + s = s.substring(0, s.length() - 2); + s += ")"; + return s; + } + + private String initShortName(String methodName, List parameterNames) { + String shortName = methodName + "("; + for (String parameterName : parameterNames) { + if (parameterNames.lastIndexOf(".") > -1) + shortName += parameterName.substring(parameterName.lastIndexOf(".") + 1) + ", "; + else + shortName += parameterName + ", "; + } + shortName = shortName.substring(0, shortName.lastIndexOf(", ")); + shortName += ")"; + return shortName; + } + + @NotNull + public PsiMethod getPsiMethod() { + return myPsiMethod; + } + + public String getShortName() { + return shortName; + } + + @NotNull + public PsiParameter getPsiParameter() { + return myPsiParameter; + } + + + @Override + public int compareTo(MethodConditionCheck o) { + return fullName.compareToIgnoreCase(fullName); + } + + @Override + public String toString() { + return fullName; + } + + static class Builder { + private final @NotNull String serializedRepresentation; + private final @NotNull Project project; + private final @NotNull Type type; + + + Builder(@NotNull String serializedRepresentation, @NotNull Type type, @NotNull Project project) { + this.serializedRepresentation = serializedRepresentation; + this.project = project; + this.type = type; + } + + private MethodConditionCheck validateFullyQualifiedClassMethodAndParameterNameAndGetPsiMethod(String fullyQualifiedClassMethodAndParameterName) { + String classNameAndMethodName = parseClassNameAndMethodName(fullyQualifiedClassMethodAndParameterName); + + String className = classNameAndMethodName.substring(0, classNameAndMethodName.lastIndexOf(".")); + String methodName = classNameAndMethodName.substring(classNameAndMethodName.lastIndexOf(".") + 1); + + String allParametersSubString = fullyQualifiedClassMethodAndParameterName.substring(fullyQualifiedClassMethodAndParameterName.indexOf("(") + 1, fullyQualifiedClassMethodAndParameterName.lastIndexOf(")")).trim(); + if (allParametersSubString.isEmpty()) { + throw new IllegalArgumentException("Name should contain 1+ parameter (between opening and closing parenthesis). " + fullyQualifiedClassMethodAndParameterName); + } else if (allParametersSubString.contains("*") && allParametersSubString.indexOf("*") == allParametersSubString.lastIndexOf("*")) { + throw new IllegalArgumentException("Selected Parameter should be surrounded by asterisks. " + fullyQualifiedClassMethodAndParameterName); + } + + String parameterClassAndName = allParametersSubString.substring(allParametersSubString.indexOf("*") + 1, allParametersSubString.lastIndexOf("*")).trim(); + + PsiClass psiClass = JavaPsiFacade.getInstance(project).findClass(className, GlobalSearchScope.allScope(project)); + if (psiClass == null) { + throw new IllegalArgumentException("Unable to locate class " + className + " which was parsed from full name " + fullyQualifiedClassMethodAndParameterName); + } + + List psiMethods = findPsiMethodsInPsiClassWithMatchingMethodName(psiClass, methodName); + if (psiMethods.size() == 0) { + throw new IllegalArgumentException("Unable to locate method in class " + className + " named " + methodName + ", which was parsed from full name " + fullyQualifiedClassMethodAndParameterName); + } + + PsiMethod psiMethod = findPsiMethodWithMatchingParameters(psiMethods, allParametersSubString); + if (psiMethod == null) { + throw new IllegalArgumentException("Unable to locate method in class " + className + " named " + methodName + " with a parameter named " + parameterClassAndName + " which was parsed from full name " + fullyQualifiedClassMethodAndParameterName + ". The following methods matched on method name but not parameter name " + psiMethods); + } + + PsiParameter psiParameter = null; + for (int i = 0; i < psiMethod.getParameterList().getParameters().length; i++) { + PsiParameter parameter = psiMethod.getParameterList().getParameters()[i]; + if (parameterClassAndName.equals(getFullyQualifiedName(parameter))) { + psiParameter = parameter; + break; + } + } + + if (psiParameter == null) { + throw new IllegalArgumentException("Unable to locate parameter " + parameterClassAndName + " in class " + className + " named " + methodName + ", which was parsed from full name " + fullyQualifiedClassMethodAndParameterName); + } + + return new MethodConditionCheck(psiMethod, psiParameter, type); + } + + private String parseClassNameAndMethodName(String fullyQualifiedClassMethodAndParameterName) { + if (!fullyQualifiedClassMethodAndParameterName.contains("(")) { + throw new IllegalArgumentException("Name should contain a opening parenthesis. " + fullyQualifiedClassMethodAndParameterName); + } else if (!fullyQualifiedClassMethodAndParameterName.contains(")")) { + throw new IllegalArgumentException("Name should contain a closing parenthesis. " + fullyQualifiedClassMethodAndParameterName); + } else if (fullyQualifiedClassMethodAndParameterName.indexOf("(", fullyQualifiedClassMethodAndParameterName.indexOf("(") + 1) > -1) { + throw new IllegalArgumentException("Name should only contain one opening parenthesis. " + fullyQualifiedClassMethodAndParameterName); + } else if (fullyQualifiedClassMethodAndParameterName.indexOf(")", fullyQualifiedClassMethodAndParameterName.indexOf(")") + 1) > -1) { + throw new IllegalArgumentException("Name should only contain one closing parenthesis. " + fullyQualifiedClassMethodAndParameterName); + } else if (fullyQualifiedClassMethodAndParameterName.indexOf(")") < fullyQualifiedClassMethodAndParameterName.indexOf("(")) { + throw new IllegalArgumentException("Opening parenthesis should precede closing parenthesis. " + fullyQualifiedClassMethodAndParameterName); + } + + String classNameAndMethodName = fullyQualifiedClassMethodAndParameterName.substring(0, fullyQualifiedClassMethodAndParameterName.indexOf("(")); + if (!classNameAndMethodName.contains(".")) { + throw new IllegalArgumentException("Name should contain a dot between the class name and method name (before the opening parenthesis). " + fullyQualifiedClassMethodAndParameterName); + } + return classNameAndMethodName; + } + + private PsiMethod findPsiMethodWithMatchingParameters(List psiMethods, String allParametersSubString) { + String[] parameterClassAndNameArray = allParametersSubString.split(","); + List parameterClassToMatch = new ArrayList(); + for (String parameterClassAndName : parameterClassAndNameArray) { + parameterClassAndName = parameterClassAndName.replace("*", "").trim(); + parameterClassAndName = parameterClassAndName.substring(0, parameterClassAndName.indexOf(" ")).trim(); + parameterClassToMatch.add(parameterClassAndName); + } + + for (PsiMethod method : psiMethods) { + List parameterForCurrentMethod = new ArrayList(); + for (int i = 0; i < method.getParameterList().getParameters().length; i++) { + PsiParameter psiParameter = method.getParameterList().getParameters()[i]; + PsiTypeElement typeElement = psiParameter.getTypeElement(); + if (typeElement == null) + break; + + PsiType psiType = typeElement.getType(); + parameterForCurrentMethod.add(psiType.getCanonicalText().trim()); + } + + if (parameterForCurrentMethod.equals(parameterClassToMatch)) + return method; + } + + return null; + } + + private List findPsiMethodsInPsiClassWithMatchingMethodName(PsiClass psiClass, String methodName) { + List psiMethods = new ArrayList(); + for (int i = 0; i < psiClass.getMethods().length; i++) { + PsiMethod possibleMatchPsiMethod = psiClass.getMethods()[i]; + if (methodName.equals(possibleMatchPsiMethod.getName())) { + psiMethods.add(possibleMatchPsiMethod); + } + } + return psiMethods; + } + + public MethodConditionCheck build() { + return validateFullyQualifiedClassMethodAndParameterNameAndGetPsiMethod(serializedRepresentation); + } + } +} diff --git a/java/java-impl/src/com/intellij/codeInspection/dataFlow/ConditionCheckDialog.java b/java/java-impl/src/com/intellij/codeInspection/dataFlow/ConditionCheckDialog.java new file mode 100644 index 000000000000..d48698d2dfa4 --- /dev/null +++ b/java/java-impl/src/com/intellij/codeInspection/dataFlow/ConditionCheckDialog.java @@ -0,0 +1,264 @@ +/* + * Copyright 2000-2012 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.openapi.project.*; +import com.intellij.openapi.ui.*; +import com.intellij.ui.*; +import com.intellij.ui.components.*; +import org.jetbrains.annotations.*; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.*; +import java.util.*; +import java.util.List; + +/** + * Dialog that appears when user clicks the "Configure IsNull/IsNotNull/True/False Check/Assertion Methods" + * on the Errors dialog for the Constant Conditions Inspection. It is divided into 6 parts + *
    + *
  1. Is Null Check MethodsPanel
  2. + *
  3. Is Not Null Check MethodsPanel
  4. + *
  5. Assert Is Null MethodsPanel
  6. + *
  7. Assert Is Not Null MethodsPanel
  8. + *
  9. Assert True MethodsPanel
  10. + *
  11. Assert False MethodsPanel
  12. + *
+ * + * @author Johnny Clark + * Creation Date: 8/3/12 + */ +public class ConditionCheckDialog extends DialogWrapper { + private final Project myProject; + private final @NotNull Splitter mainSplitter; + private final @NotNull MethodsPanel myIsNullCheckMethodPanel; + private final @NotNull MethodsPanel myIsNotNullCheckMethodPanel; + private final @NotNull MethodsPanel myAssertIsNullMethodPanel; + private final @NotNull MethodsPanel myAssertIsNotNullMethodPanel; + private final @NotNull MethodsPanel myAssertTrueMethodPanel; + private final @NotNull MethodsPanel myAssertFalseMethodPanel; + + public ConditionCheckDialog(Project project, String mainDialogTitle) { + super(project, true); + myProject = project; + + final ConditionCheckManager manager = ConditionCheckManager.getInstance(myProject); + mainSplitter = new Splitter(true, 0.3f); + final Splitter topThirdSplitter = new Splitter(false); + final Splitter bottomTwoThirdsSplitter = new Splitter(true); + final Splitter isNullIsNotNullCheckMethodSplitter = new Splitter(false); + final Splitter assertTrueFalseMethodSplitter = new Splitter(false); + + List isNullCheckMethods = new ArrayList(manager.getIsNullCheckMethods()); + List isNotNullCheckMethods = new ArrayList(manager.getIsNotNullCheckMethods()); + List assertIsNullMethods = new ArrayList(manager.getAssertIsNullMethods()); + List assertIsNotNullMethods = new ArrayList(manager.getAssertIsNotNullMethods()); + List assertTrueMethods = new ArrayList(manager.getAssertTrueMethods()); + List assertFalseMethods = new ArrayList(manager.getAssertFalseMethods()); + + myAssertIsNullMethodPanel = new MethodsPanel(assertIsNullMethods, ConditionChecker.Type.ASSERT_IS_NULL_METHOD, myProject); + myAssertIsNotNullMethodPanel = new MethodsPanel(assertIsNotNullMethods, ConditionChecker.Type.ASSERT_IS_NOT_NULL_METHOD, myProject); + myIsNullCheckMethodPanel = new MethodsPanel(isNullCheckMethods, ConditionChecker.Type.IS_NULL_METHOD, myProject); + myIsNotNullCheckMethodPanel = new MethodsPanel(isNotNullCheckMethods, ConditionChecker.Type.IS_NOT_NULL_METHOD, myProject); + myAssertTrueMethodPanel = new MethodsPanel(assertTrueMethods, ConditionChecker.Type.ASSERT_TRUE_METHOD, myProject); + myAssertFalseMethodPanel = new MethodsPanel(assertFalseMethods, ConditionChecker.Type.ASSERT_FALSE_METHOD, myProject); + + isNullIsNotNullCheckMethodSplitter.setFirstComponent(myIsNullCheckMethodPanel.getComponent()); + isNullIsNotNullCheckMethodSplitter.setSecondComponent(myIsNotNullCheckMethodPanel.getComponent()); + assertTrueFalseMethodSplitter.setFirstComponent(myAssertTrueMethodPanel.getComponent()); + assertTrueFalseMethodSplitter.setSecondComponent(myAssertFalseMethodPanel.getComponent()); + + topThirdSplitter.setFirstComponent(myAssertIsNullMethodPanel.getComponent()); + topThirdSplitter.setSecondComponent(myAssertIsNotNullMethodPanel.getComponent()); + bottomTwoThirdsSplitter.setFirstComponent(isNullIsNotNullCheckMethodSplitter); + bottomTwoThirdsSplitter.setSecondComponent(assertTrueFalseMethodSplitter); + + mainSplitter.setFirstComponent(topThirdSplitter); + mainSplitter.setSecondComponent(bottomTwoThirdsSplitter); + + topThirdSplitter.setPreferredSize(new Dimension(600, 400)); + bottomTwoThirdsSplitter.setPreferredSize(new Dimension(600, 800)); + + myAssertIsNullMethodPanel.setOtherMethodsPanels(myAssertIsNotNullMethodPanel, myIsNullCheckMethodPanel, myIsNotNullCheckMethodPanel, myAssertTrueMethodPanel, myAssertFalseMethodPanel); + myAssertIsNotNullMethodPanel.setOtherMethodsPanels(myAssertIsNullMethodPanel, myIsNullCheckMethodPanel, myIsNotNullCheckMethodPanel, myAssertTrueMethodPanel, myAssertFalseMethodPanel); + myIsNullCheckMethodPanel.setOtherMethodsPanels(myAssertIsNullMethodPanel, myAssertIsNotNullMethodPanel, myIsNotNullCheckMethodPanel, myAssertTrueMethodPanel, myAssertFalseMethodPanel); + myIsNotNullCheckMethodPanel.setOtherMethodsPanels(myAssertIsNullMethodPanel, myAssertIsNotNullMethodPanel, myIsNullCheckMethodPanel, myAssertTrueMethodPanel, myAssertFalseMethodPanel); + myAssertTrueMethodPanel.setOtherMethodsPanels(myAssertIsNullMethodPanel, myAssertIsNotNullMethodPanel, myIsNotNullCheckMethodPanel, myIsNullCheckMethodPanel, myAssertFalseMethodPanel); + myAssertFalseMethodPanel.setOtherMethodsPanels(myAssertIsNullMethodPanel, myAssertIsNotNullMethodPanel, myIsNotNullCheckMethodPanel, myIsNullCheckMethodPanel, myAssertTrueMethodPanel); + + init(); + setTitle(mainDialogTitle); + } + + @Override + protected JComponent createCenterPanel() { + return mainSplitter; + } + + @Override + protected void doOKAction() { + final ConditionCheckManager manager = ConditionCheckManager.getInstance(myProject); + manager.setIsNotNullCheckMethods(myIsNotNullCheckMethodPanel.getMethodConditionChecker()); + manager.setIsNullCheckMethods(myIsNullCheckMethodPanel.getMethodConditionChecker()); + manager.setAssertNotNullMethods(myAssertIsNotNullMethodPanel.getMethodConditionChecker()); + manager.setAssertNullMethods(myAssertIsNullMethodPanel.getMethodConditionChecker()); + manager.setAssertTrueMethods(myAssertTrueMethodPanel.getMethodConditionChecker()); + manager.setAssertFalseMethods(myAssertFalseMethodPanel.getMethodConditionChecker()); + + super.doOKAction(); + } + + /** + * Is Null, Is Not Null, Assert True and Assert False Method Panel at the top of the main Dialog. + */ + class MethodsPanel { + private final @NotNull JBList myList; + private final @NotNull JPanel myPanel; + private final @NotNull Project myProject; + private Set otherPanels; + + public MethodsPanel(final List checkers, final ConditionChecker.Type type, final Project myProject) { + this.myProject = myProject; + myList = new JBList(new CollectionListModel(checkers)); + myPanel = new JPanel(new BorderLayout()); + myPanel.setBorder(IdeBorderFactory.createTitledBorder(initTitle(type), false, new Insets(10, 0, 0, 0))); + myPanel.setPreferredSize(new Dimension(500, 500)); + + myList.setCellRenderer(new ColoredListCellRenderer() { + @Override + protected void customizeCellRenderer(JList list, Object value, int index, boolean selected, boolean hasFocus) { + String s = value.toString(); + if (s.contains("*")) { + int indexOfAsterix1 = s.indexOf("*"); + int indexOfAsterix2 = s.lastIndexOf("*"); + if (indexOfAsterix1 >= 0 && indexOfAsterix1 < s.length() && indexOfAsterix2 >= 0 && indexOfAsterix2 < s.length() && indexOfAsterix1 < indexOfAsterix2) { + append(s.substring(0, indexOfAsterix1), SimpleTextAttributes.REGULAR_ATTRIBUTES); + append(s.substring(indexOfAsterix1 + 1, indexOfAsterix2), SimpleTextAttributes.REGULAR_BOLD_ATTRIBUTES); + append(s.substring(indexOfAsterix2 + 1), SimpleTextAttributes.REGULAR_ATTRIBUTES); + } else { + append(s, SimpleTextAttributes.REGULAR_ATTRIBUTES); + } + } + } + }); + + final ToolbarDecorator toolbarDecorator = ToolbarDecorator.createDecorator(myList).disableUpDownActions() + .setAddAction(new AnActionButtonRunnable() { + @Override + public void run(AnActionButton anActionButton) { + chooseMethod(null, type, myList.getModel().getSize()); + } + }) + .setRemoveAction(new AnActionButtonRunnable() { + @Override + public void run(AnActionButton anActionButton) { + CollectionListModel model = getCollectionListModel(); + if (myList.getSelectedIndex() >= 0 && myList.getSelectedIndex() < model.getSize()) { + model.remove(myList.getSelectedIndex()); + } + } + }); + + myList.addMouseListener( + new MouseAdapter() { + public void mouseClicked(MouseEvent e) { + if (e.getClickCount() == 2) { + int index = myList.locationToIndex(e.getPoint()); + CollectionListModel model = getCollectionListModel(); + if (index >= 0 && model.getSize() > index) { + chooseMethod(model.getElementAt(index), type, index); + } + } + } + } + ); + final JPanel panel = toolbarDecorator.createPanel(); + myPanel.add(panel); + myList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + } + + private String initTitle(@NotNull ConditionChecker.Type type) { + if (type.equals(ConditionChecker.Type.IS_NULL_METHOD)) + return InspectionsBundle.message("configure.checker.option.isNull.method.panel.title"); + else if (type.equals(ConditionChecker.Type.IS_NOT_NULL_METHOD)) + return InspectionsBundle.message("configure.checker.option.isNotNull.method.panel.title"); + else if (type.equals(ConditionChecker.Type.ASSERT_IS_NULL_METHOD)) + return InspectionsBundle.message("configure.checker.option.assert.isNull.method.panel.title"); + else if (type.equals(ConditionChecker.Type.ASSERT_IS_NOT_NULL_METHOD)) + return InspectionsBundle.message("configure.checker.option.assert.isNotNull.method.panel.title"); + else if (type.equals(ConditionChecker.Type.ASSERT_TRUE_METHOD)) + return InspectionsBundle.message("configure.checker.option.assert.true.method.panel.title"); + else if (type.equals(ConditionChecker.Type.ASSERT_FALSE_METHOD)) + return InspectionsBundle.message("configure.checker.option.assert.false.method.panel.title"); + else + throw new IllegalArgumentException("MethodCheckerDetailsDialog does not support type " + type); + } + + private void chooseMethod(@Nullable MethodConditionCheck checker, ConditionChecker.Type type, int index) { + MethodCheckerDetailsDialog pickMethodPanel = new MethodCheckerDetailsDialog(checker, type, myProject, myPanel, getConditionCheckers(), getOtherCheckers()); + pickMethodPanel.show(); + MethodConditionCheck chk = pickMethodPanel.getMethodConditionChecker(); + if (chk != null) { + CollectionListModel model = getCollectionListModel(); + if (model.getSize() <= index) + model.add(chk); + else + model.setElementAt(chk, index); + } + } + + private CollectionListModel getCollectionListModel() { + //noinspection unchecked + return (CollectionListModel) myList.getModel(); + } + + @NotNull + public JPanel getComponent() { + return myPanel; + } + + public List getMethodConditionChecker() { + CollectionListModel model = getCollectionListModel(); + return new ArrayList(model.getItems()); + } + + public Set getConditionCheckers() { + Set set = new HashSet(); + set.addAll(getMethodConditionChecker()); + return set; + } + + public void setOtherMethodsPanels(MethodsPanel p1, MethodsPanel p2, MethodsPanel p3, MethodsPanel p4, MethodsPanel p5) { + otherPanels = new HashSet(); + otherPanels.add(p1); + otherPanels.add(p2); + otherPanels.add(p3); + otherPanels.add(p4); + otherPanels.add(p5); + } + + public Set getOtherCheckers() { + Set otherCheckers = new HashSet(); + for (MethodsPanel otherPanel : otherPanels) { + otherCheckers.addAll(otherPanel.getConditionCheckers()); + } + return otherCheckers; + } + } +} diff --git a/java/java-impl/src/com/intellij/codeInspection/dataFlow/ControlFlowAnalyzer.java b/java/java-impl/src/com/intellij/codeInspection/dataFlow/ControlFlowAnalyzer.java index e3170217680f..2ec949a983bd 100644 --- a/java/java-impl/src/com/intellij/codeInspection/dataFlow/ControlFlowAnalyzer.java +++ b/java/java-impl/src/com/intellij/codeInspection/dataFlow/ControlFlowAnalyzer.java @@ -15,6 +15,7 @@ */ package com.intellij.codeInspection.dataFlow; +import com.intellij.codeInsight.ConditionCheckManager; import com.intellij.codeInsight.ExceptionUtil; import com.intellij.codeInspection.dataFlow.instructions.*; import com.intellij.codeInspection.dataFlow.value.*; @@ -1342,6 +1343,82 @@ class ControlFlowAnalyzer extends JavaElementVisitor { } } + if (ConditionCheckManager.isCheck(resolved)) { + if (ConditionCheckManager.isAssertIsNullCheckMethod(resolved)) { + int paramIndex = 0; + for (PsiExpression param : params) { + param.accept(this); + if (ConditionCheckManager.isAssertIsNullCheckMethod(resolved, paramIndex++)) { + addInstruction(new PushInstruction(myFactory.getConstFactory().getNull(), null)); + addInstruction(new BinopInstruction(JavaTokenType.EQEQ, null, expression.getProject())); + conditionalExit(exitPoint, false); // Exit if Equal NULL is True + } else { + addInstruction(new PopInstruction()); + } + } + return true; + } else if (ConditionCheckManager.isAssertIsNotNullCheckMethod(resolved)) { + int paramIndex = 0; + for (PsiExpression param : params) { + param.accept(this); + if (ConditionCheckManager.isAssertIsNotNullCheckMethod(resolved, paramIndex++)) { + addInstruction(new PushInstruction(myFactory.getConstFactory().getNull(), null)); + addInstruction(new BinopInstruction(JavaTokenType.EQEQ, null, expression.getProject())); + conditionalExit(exitPoint, true); // Exit if NotEqual NULL is True + } else { + addInstruction(new PopInstruction()); + } + } + return true; + } else if (ConditionCheckManager.isNullCheckMethod(resolved)) { + int paramIndex = 0; + for (PsiExpression param : params) { + param.accept(this); + if (ConditionCheckManager.isNullCheckMethod(resolved, paramIndex++)) { + addInstruction(new PushInstruction(myFactory.getConstFactory().getNull(), null)); + addInstruction(new BinopInstruction(JavaTokenType.EQEQ, null, expression.getProject())); + } else { + addInstruction(new PopInstruction()); + } + } + return true; + } else if (ConditionCheckManager.isNotNullCheckMethod(resolved)) { + int paramIndex = 0; + for (PsiExpression param : params) { + param.accept(this); + if (ConditionCheckManager.isNotNullCheckMethod(resolved, paramIndex++)) { + addInstruction(new PushInstruction(myFactory.getConstFactory().getNull(), null)); + addInstruction(new BinopInstruction(JavaTokenType.NE, null, expression.getProject())); + } else { + addInstruction(new PopInstruction()); + } + } + return true; + } else if (ConditionCheckManager.isAssertTrueCheckMethod(resolved)) { + int paramIndex = 0; + for (PsiExpression param : params) { + param.accept(this); + if (ConditionCheckManager.isAssertTrueCheckMethod(resolved, paramIndex++)) { + conditionalExit(exitPoint, false); + } else { + addInstruction(new PopInstruction()); + } + } + return true; + } else if (ConditionCheckManager.isAssertFalseCheckMethod(resolved)) { + int paramIndex = 0; + for (PsiExpression param : params) { + param.accept(this); + if (ConditionCheckManager.isAssertFalseCheckMethod(resolved, paramIndex++)) { + conditionalExit(exitPoint, true); + } else { + addInstruction(new PopInstruction()); + } + } + return true; + } + } + // Idea project only. if (qualifierExpression != null) { if (qualifierExpression.textMatches("LOG")) { diff --git a/java/java-impl/src/com/intellij/codeInspection/dataFlow/DataFlowInspection.java b/java/java-impl/src/com/intellij/codeInspection/dataFlow/DataFlowInspection.java index 56b1d22041f4..2b7bd60bae48 100644 --- a/java/java-impl/src/com/intellij/codeInspection/dataFlow/DataFlowInspection.java +++ b/java/java-impl/src/com/intellij/codeInspection/dataFlow/DataFlowInspection.java @@ -587,6 +587,24 @@ public class DataFlowInspection extends BaseLocalInspectionTool { gc.insets.bottom = 15; add(configureAnnotations, gc); + final JButton configureCheckAnnotations = new JButton(InspectionsBundle.message("configure.checker.option.button")); + configureCheckAnnotations.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + Project project = PlatformDataKeys.PROJECT.getData(DataManager.getInstance().getDataContext(OptionsPanel.this)); + if (project == null) project = ProjectManager.getInstance().getDefaultProject(); + final ConditionCheckDialog dialog = new ConditionCheckDialog(project, + InspectionsBundle.message("configure.checker.option.main.dialog.title") + ); + dialog.show(); + } + }); + gc.gridy++; + gc.fill = GridBagConstraints.NONE; + gc.insets.left = 20; + gc.insets.bottom = 15; + add(configureCheckAnnotations, gc); + gc.fill = GridBagConstraints.HORIZONTAL; gc.weighty = 1; gc.insets.left = 0; diff --git a/java/java-impl/src/com/intellij/codeInspection/dataFlow/MethodCheckerDetailsDialog.java b/java/java-impl/src/com/intellij/codeInspection/dataFlow/MethodCheckerDetailsDialog.java new file mode 100644 index 000000000000..cd261e1c9214 --- /dev/null +++ b/java/java-impl/src/com/intellij/codeInspection/dataFlow/MethodCheckerDetailsDialog.java @@ -0,0 +1,621 @@ +/* + * Copyright 2000-2012 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 myOtherCheckers; + private final @Nullable MethodConditionCheck myPreviouslySelectedChecker; + + /** + * Set by the OK and/or Cancel actions so that the caller can retrieve it via a call to getMethodIsNullIsNotNullChecker + */ + private @Nullable MethodConditionCheck mySelectedChecker; + + MethodCheckerDetailsDialog(@Nullable MethodConditionCheck previouslySelectedChecker, @NotNull ConditionChecker.Type type, @NotNull Project project, @NotNull Component component, @NotNull Set otherCheckersSameType, @NotNull Set otherCheckers) { + super(component, true); + if (!isSupported(type)) + throw new IllegalArgumentException("Type is invalid " + type); + + myProject = project; + myType = type; + myOtherCheckers = new HashSet(otherCheckersSameType); + myOtherCheckers.addAll(otherCheckers); + myPreviouslySelectedChecker = previouslySelectedChecker; + if (myPreviouslySelectedChecker != null) + myOtherCheckers.remove(myPreviouslySelectedChecker); + + PsiClass psiClass = null; + PsiMethod psiMethod = null; + PsiParameter psiParameter = null; + if (previouslySelectedChecker != null) { + psiMethod = previouslySelectedChecker.getPsiMethod(); + psiClass = psiMethod.getContainingClass(); + psiParameter = previouslySelectedChecker.getPsiParameter(); + } + + 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 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 classComponent = new LabeledComponent(); + final LabeledComponent methodComponent = new LabeledComponent(); + final LabeledComponent parameterComponent = new LabeledComponent(); + + 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; + } + + MethodConditionCheck getMethodConditionChecker() { + return mySelectedChecker; + } + + private MethodConditionCheck buildMethodConditionChecker() { + PsiClass psiClass = classField.getPsiClass(); + PsiMethod psiMethod = methodDropDown.getSelectedPsiMethod(); + PsiParameter psiParameter = parameterDropDown.getSelectedPsiParameter(); + if (psiClass != null && psiMethod != null && psiParameter != null) { + return new MethodConditionCheck(psiMethod, psiParameter, myType); + } else { + return null; + } + } + + private boolean overlaps() { + MethodConditionCheck thisChecker = buildMethodConditionChecker(); + for (ConditionChecker overlappingChecker : myOtherCheckers) { + if (thisChecker.overlaps(overlappingChecker)) { + Messages.showMessageDialog(myProject, + InspectionsBundle.message("configure.checker.option.overlap.error.msg") + " " + overlappingChecker.toString(), + InspectionsBundle.message("configure.checker.option.overlap.error.title"), + Messages.getErrorIcon()); + return true; + } + } + return false; + } + + @Override + public void propertyChange(PropertyChangeEvent evt) { +// if (evt.getPropertyName().equals(ClassField.PROPERTY_PSICLASS)) { + 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() { + if (!overlaps()) { + MethodConditionCheck checker = buildMethodConditionChecker(); + if (checker != null) { + if (checker.equals(myPreviouslySelectedChecker)) { + mySelectedChecker = myPreviouslySelectedChecker; + } else { + mySelectedChecker = checker; + } + } + super.doOKAction(); + } + } + + 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 { + private final @NotNull Project myProject; + private @Nullable PsiClass myPsiClass; + public static final String PROPERTY_PSICLASS = "ClassField.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) + setText(myPsiClass.getQualifiedName()); + addActionListener(this); + getChildComponent().addDocumentListener(this); + } + + @Override + public void actionPerformed(ActionEvent e) { + final TreeClassChooser chooser = TreeClassChooserFactory.getInstance(myProject) + .createNoInnerClassesScopeChooser("Choose Class", new EverythingGlobalScope(myProject), new ClassFilter() { + @Override + public boolean isAccepted(PsiClass aClass) { + return !aClass.isAnnotationType(); + } + }, null); + chooser.showDialog(); + PsiClass psiClass = chooser.getSelected(); + if (psiClass != null) + 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, new EverythingGlobalScope(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; + } + } + + private static JavaCodeFragment.VisibilityChecker buildVisibilityChecker() { + return new JavaCodeFragment.VisibilityChecker() { + @Override + public Visibility isDeclarationVisible(PsiElement declaration, PsiElement place) { + return Visibility.VISIBLE; + } + }; + } + } + + /** + * Drop Down for picking Method Name + */ + static class MethodDropDown extends JComboBox implements PropertyChangeListener { + private @Nullable PsiClass myPsiClass; + private final @NotNull ConditionChecker.Type myType; + private final @NotNull SortedComboBoxModel myModel; + + MethodDropDown(@Nullable PsiClass psiClass, @Nullable PsiMethod psiMethod, @NotNull ConditionChecker.Type type, SortedComboBoxModel 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 iterator = myModel.iterator(); iterator.hasNext(); ) { + MethodWrapper methodWrapper = iterator.next(); + if (methodWrapper.getPsiMethod().equals(psiMethod)) { + setSelectedItem(methodWrapper); + } + } + } + } + + private void initValues() { + if (myPsiClass != null) { + myModel.clear(); + myModel.setSelectedItem(null); + PsiMethod[] allMethods = myPsiClass.getAllMethods(); + for (PsiMethod allMethod : allMethods) { + if (qualifies(allMethod)) + myModel.add(new MethodWrapper(allMethod)); + } + } + } + + 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 (int i = 0; i < psiMethod.getParameterList().getParameters().length; i++) { + PsiParameter psiParameter = psiMethod.getParameterList().getParameters()[i]; + 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; + } + + private boolean isMethodFromJavaLangObject(PsiMethod method) { + if (method != null && method.getContainingClass() != null && method.getContainingClass().getName() != null && + (method.getContainingClass().getName().equals("Object") || method.getContainingClass().getName().equals(Object.class.toString()))) { + return true; + } + + return false; + } + + @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; + } + + public static SortedComboBoxModel buildModel() { + return new SortedComboBoxModel(new Comparator() { + @Override + public int compare(MethodWrapper o1, MethodWrapper o2) { + return o1.compareTo(o2); + } + }); + } + + 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 @Nullable PsiMethod myPsiMethod; + private final @NotNull SortedComboBoxModel myModel; + private final @NotNull ConditionChecker.Type myType; + public static final String PROPERTY_PARAMETERDROPDOWN = "ParameterDropDown.myModel"; + + public ParameterDropDown(@Nullable PsiMethod psiMethod, @Nullable PsiParameter psiParameter, @NotNull SortedComboBoxModel 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); + } + } + + List getParameterWrappers() { + List wrappers = new ArrayList(); + 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; + } + + public static SortedComboBoxModel buildModel() { + return new SortedComboBoxModel(new Comparator() { + @Override + public int compare(ParameterWrapper o1, ParameterWrapper o2) { + return o1.compareTo(o2); + } + }); + } + + @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); + } + } + } + + public PsiParameter getSelectedPsiParameter() { + ParameterWrapper parameterWrapper = myModel.getSelectedItem(); + if (parameterWrapper == null) + return null; + + return parameterWrapper.getPsiParameter(); + } + + class ParameterWrapper implements Comparable { + 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 { + private final @NotNull PsiMethod myPsiMethod; + private final @NotNull String myId; + + MethodWrapper(@NotNull PsiMethod psiMethod) { + this.myPsiMethod = psiMethod; + + List parameters = new ArrayList(); + for (int i = 0; i < psiMethod.getParameterList().getParameters().length; i++) { + PsiParameter psiParameter = psiMethod.getParameterList().getParameters()[i]; + parameters.add(getParameterQualifiedName(psiParameter)); + } + + myId = initId(psiMethod.getName(), parameters); + } + + private 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 String initId(String methodName, List 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 int compareTo(MethodWrapper o) { + return myId.compareTo(o.myId); + } + } +} diff --git a/java/java-tests/testData/inspection/dataFlow/fixture/AssertFalse.java b/java/java-tests/testData/inspection/dataFlow/fixture/AssertFalse.java new file mode 100644 index 000000000000..3aa73afcff5b --- /dev/null +++ b/java/java-tests/testData/inspection/dataFlow/fixture/AssertFalse.java @@ -0,0 +1,9 @@ +public class AssertFalse { + void bar() { + final boolean b = call(); + if (Assertions.assertFalse(b)) { + if(b) {} + } + } + boolean call() {return true;} +} diff --git a/java/java-tests/testData/inspection/dataFlow/fixture/AssertIsNotNull.java b/java/java-tests/testData/inspection/dataFlow/fixture/AssertIsNotNull.java new file mode 100644 index 000000000000..5b16eb78ac7b --- /dev/null +++ b/java/java-tests/testData/inspection/dataFlow/fixture/AssertIsNotNull.java @@ -0,0 +1,10 @@ +import java.lang.*; + +public class AssertIsNotNull { + void bar() { + final Object o = call(); + Assertions.assertIsNotNull(o); + if(o == null) {} + } + Object call() {return new Object();} +} diff --git a/java/java-tests/testData/inspection/dataFlow/fixture/AssertIsNull.java b/java/java-tests/testData/inspection/dataFlow/fixture/AssertIsNull.java new file mode 100644 index 000000000000..5915277cc273 --- /dev/null +++ b/java/java-tests/testData/inspection/dataFlow/fixture/AssertIsNull.java @@ -0,0 +1,10 @@ +import java.lang.*; + +public class AssertIsNull { + void bar() { + final Object o = call(); + Assertions.assertIsNull(o); + if(o == null) {} + } + Object call() {return new Object();} +} diff --git a/java/java-tests/testData/inspection/dataFlow/fixture/AssertTrue.java b/java/java-tests/testData/inspection/dataFlow/fixture/AssertTrue.java new file mode 100644 index 000000000000..a71e4382ab44 --- /dev/null +++ b/java/java-tests/testData/inspection/dataFlow/fixture/AssertTrue.java @@ -0,0 +1,9 @@ +public class AssertTrue { + void bar() { + final boolean b = call(); + if (Assertions.assertTrue(b)) { + if(b) {} + } + } + boolean call() {return true;} +} diff --git a/java/java-tests/testData/inspection/dataFlow/fixture/IsNotNullCheck.java b/java/java-tests/testData/inspection/dataFlow/fixture/IsNotNullCheck.java new file mode 100644 index 000000000000..85879eda40a7 --- /dev/null +++ b/java/java-tests/testData/inspection/dataFlow/fixture/IsNotNullCheck.java @@ -0,0 +1,9 @@ +public class IsNotNullCheck { + void bar() { + final Value v = call(); + if (Value.isNotNull(v)) { + if(v == null) {} + } + } + Value call() {return new Value();} +} diff --git a/java/java-tests/testData/inspection/dataFlow/fixture/IsNullCheck.java b/java/java-tests/testData/inspection/dataFlow/fixture/IsNullCheck.java new file mode 100644 index 000000000000..1b2c48593a16 --- /dev/null +++ b/java/java-tests/testData/inspection/dataFlow/fixture/IsNullCheck.java @@ -0,0 +1,9 @@ +public class IsNullCheck { + void bar() { + final Value v = call(); + if (Value.isNull(v)) { + if(v == null) {} + } + } + Value call() {return new Value();} +} diff --git a/java/java-tests/testSrc/com/intellij/codeInspection/DataFlowInspectionTest.java b/java/java-tests/testSrc/com/intellij/codeInspection/DataFlowInspectionTest.java index 56fa8115d95f..fb8f28866961 100644 --- a/java/java-tests/testSrc/com/intellij/codeInspection/DataFlowInspectionTest.java +++ b/java/java-tests/testSrc/com/intellij/codeInspection/DataFlowInspectionTest.java @@ -16,12 +16,19 @@ package com.intellij.codeInspection; import com.intellij.JavaTestUtil; +import com.intellij.codeInsight.ConditionCheckManager; +import com.intellij.codeInsight.ConditionChecker; +import com.intellij.codeInsight.MethodConditionCheck; import com.intellij.codeInsight.NullableNotNullManager; import com.intellij.codeInspection.dataFlow.DataFlowInspection; import com.intellij.openapi.Disposable; import com.intellij.openapi.util.Disposer; +import com.intellij.psi.PsiClass; +import com.intellij.psi.PsiMethod; import com.intellij.testFramework.fixtures.LightCodeInsightFixtureTestCase; +import java.io.IOException; + /** * @author peter */ @@ -158,4 +165,60 @@ public class DataFlowInspectionTest extends LightCodeInsightFixtureTestCase { public void testEqualsHasNoSideEffects() { doTest(); } + public void testIsNullCheck() throws Exception { + ConditionCheckManager.getInstance(myModule.getProject()).getIsNullCheckMethods().add( + buildMethodConditionCheck("Value", "isNull", ConditionChecker.Type.IS_NULL_METHOD, + "public class Value { public static boolean isNull(Value o) {if (o == null) return true; else return false;} }")); + doTest(); + } + + public void testIsNotNullCheck() throws Exception { + ConditionCheckManager.getInstance(myModule.getProject()).getIsNotNullCheckMethods().add( + buildMethodConditionCheck("Value", "isNotNull", ConditionChecker.Type.IS_NOT_NULL_METHOD, + "public class Value { public static boolean isNotNull(Value o) {if (o == null) return false; else return true;} }")); + doTest(); + } + + public void testAssertTrue() throws Exception { + ConditionCheckManager.getInstance(myModule.getProject()).getAssertTrueMethods().add( + buildMethodConditionCheck("Assertions", "assertTrue", ConditionChecker.Type.ASSERT_TRUE_METHOD, + "public class Assertions { public static boolean assertTrue(boolean b) {if(!b) throw new Exception();} }")); + doTest(); + } + + public void testAssertFalse() throws Exception { + ConditionCheckManager.getInstance(myModule.getProject()).getAssertFalseMethods().add( + buildMethodConditionCheck("Assertions", "assertFalse", ConditionChecker.Type.ASSERT_FALSE_METHOD, + "public class Assertions { public static boolean assertFalse(boolean b) {if(b) throw new Exception();} }")); + doTest(); + } + + public void testAssertIsNull() throws Exception { + ConditionCheckManager.getInstance(myModule.getProject()).getAssertIsNullMethods().add( + buildMethodConditionCheck("Assertions", "assertIsNull", ConditionChecker.Type.ASSERT_IS_NULL_METHOD, + "public class Assertions { public static boolean assertIsNull(Object o) {if(o != null) throw new Exception();} }")); + doTest(); + } + + public void testAssertIsNotNull() throws Exception { + ConditionCheckManager.getInstance(myModule.getProject()).getAssertIsNotNullMethods().add( + buildMethodConditionCheck("Assertions", "assertIsNotNull", ConditionChecker.Type.ASSERT_IS_NOT_NULL_METHOD, + "public class Assertions { public static boolean assertIsNotNull(Object o) {if(o == null) throw new Exception();} }")); + doTest(); + } + + private MethodConditionCheck buildMethodConditionCheck(String className, String methodName, ConditionChecker.Type type, String classText) + throws IOException { + myFixture.addClass(classText); + PsiClass psiClass = myFixture.findClass(className); + PsiMethod psiMethod = null; + PsiMethod[] methods = psiClass.getMethods(); + for (PsiMethod tempPsiMethod : methods) { + if (tempPsiMethod.getName().equals(methodName)) { + psiMethod = tempPsiMethod; + break; + } + } + return new MethodConditionCheck(psiMethod, psiMethod.getParameterList().getParameters()[0], type); + } } diff --git a/platform/platform-resources-en/src/messages/InspectionsBundle.properties b/platform/platform-resources-en/src/messages/InspectionsBundle.properties index 084f692e3dc8..663c46229631 100644 --- a/platform/platform-resources-en/src/messages/InspectionsBundle.properties +++ b/platform/platform-resources-en/src/messages/InspectionsBundle.properties @@ -51,6 +51,22 @@ inspection.data.flow.redundant.instanceof.quickfix=Replace with != null inspection.data.flow.simplify.boolean.expression.quickfix=Simplify Boolean Expression inspection.data.flow.simplify.to.assignment.quickfix.name=Simplify to normal assignment configure.annotations.option=Configure annotations +configure.checker.option.button=Configure IsNull/IsNotNull/True/False Check/Assert Methods +configure.checker.option.main.dialog.title=IsNull/IsNotNull Configuration +configure.checker.option.overlap.error.title=Overlapping Check +configure.checker.option.overlap.error.msg=Configuration conflicts with +configure.checker.option.isNull.add.method.checker.dialog.title=Add IsNull Check Method +configure.checker.option.isNotNull.add.method.checker.dialog.title=Add IsNotNull Check Method +configure.checker.option.assert.isNull.add.method.checker.dialog.title=Add Assert IsNull Method +configure.checker.option.assert.isNotNull.add.method.checker.dialog.title=Add Assert IsNotNull Method +configure.checker.option.assert.true.add.method.checker.dialog.title=Add Assert True Method +configure.checker.option.assert.false.add.method.checker.dialog.title=Add Assert False Method +configure.checker.option.assert.isNull.method.panel.title=Assert IsNull Methods +configure.checker.option.assert.isNotNull.method.panel.title=Assert IsNotNull Methods +configure.checker.option.isNull.method.panel.title=IsNull Check Methods +configure.checker.option.isNotNull.method.panel.title=IsNotNull Check Methods +configure.checker.option.assert.true.method.panel.title=Assert True Methods +configure.checker.option.assert.false.method.panel.title=Assert False Methods #messages from dataflow inspection dataflow.message.npe.method.invocation=Method invocation #ref #loc may produce java.lang.NullPointerException diff --git a/resources/src/META-INF/IdeaPlugin.xml b/resources/src/META-INF/IdeaPlugin.xml index a9768a9a1cef..3c635fe04277 100644 --- a/resources/src/META-INF/IdeaPlugin.xml +++ b/resources/src/META-INF/IdeaPlugin.xml @@ -433,6 +433,9 @@ + + From b34abcdbbc48bdfe6d7919a7601078ad6bd9da25 Mon Sep 17 00:00:00 2001 From: peter Date: Thu, 21 Feb 2013 11:21:30 +0100 Subject: [PATCH 002/354] don't hold Project in statics --- .../com/intellij/codeInsight/ConditionCheckManager.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/java/java-impl/src/com/intellij/codeInsight/ConditionCheckManager.java b/java/java-impl/src/com/intellij/codeInsight/ConditionCheckManager.java index 539a3a1b231a..92676fc2ecef 100644 --- a/java/java-impl/src/com/intellij/codeInsight/ConditionCheckManager.java +++ b/java/java-impl/src/com/intellij/codeInsight/ConditionCheckManager.java @@ -45,10 +45,13 @@ public class ConditionCheckManager implements PersistentStateComponent myAssertTrueMethods = new ArrayList(); private List myAssertFalseMethods = new ArrayList(); - private static Project myProject; + private Project myProject; + + public ConditionCheckManager(Project project) { + myProject = project; + } public static ConditionCheckManager getInstance(Project project) { - myProject = project; return ServiceManager.getService(project, ConditionCheckManager.class); } From 7beb260d2331851eb98d1f6e853e6e20c5b8014d Mon Sep 17 00:00:00 2001 From: peter Date: Thu, 21 Feb 2013 11:42:59 +0100 Subject: [PATCH 003/354] @NotNull --- .../src/com/intellij/codeInsight/ConditionChecker.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/java/java-impl/src/com/intellij/codeInsight/ConditionChecker.java b/java/java-impl/src/com/intellij/codeInsight/ConditionChecker.java index ffcbb3c57558..3fb86a0d48b0 100644 --- a/java/java-impl/src/com/intellij/codeInsight/ConditionChecker.java +++ b/java/java-impl/src/com/intellij/codeInsight/ConditionChecker.java @@ -16,6 +16,7 @@ package com.intellij.codeInsight; import com.intellij.psi.*; +import org.jetbrains.annotations.NotNull; /** * Interface for IsNull, IsNotNull Method Checks and Assert True/False/IsNull/IsNotNull Method Checks to be performed by the Constant Condition Inspection. @@ -51,5 +52,6 @@ public interface ConditionChecker { boolean overlaps(ConditionChecker checker); + @NotNull Type getType(); } From 54ff2eb7a2951d36e7a025777e5f41ba11e30ea6 Mon Sep 17 00:00:00 2001 From: peter Date: Thu, 21 Feb 2013 11:43:48 +0100 Subject: [PATCH 004/354] fix some idea warnings --- .../codeInsight/MethodConditionCheck.java | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/java/java-impl/src/com/intellij/codeInsight/MethodConditionCheck.java b/java/java-impl/src/com/intellij/codeInsight/MethodConditionCheck.java index b8f1afbf9442..46c3121d6619 100644 --- a/java/java-impl/src/com/intellij/codeInsight/MethodConditionCheck.java +++ b/java/java-impl/src/com/intellij/codeInsight/MethodConditionCheck.java @@ -154,6 +154,7 @@ public class MethodConditionCheck implements ConditionChecker, Comparable parameters) { + private static String initFullName(String className, String methodName, List parameters) { String s = className + "." + methodName + "("; for (String parameterName : parameters) { s += parameterName + ", "; @@ -234,7 +233,7 @@ public class MethodConditionCheck implements ConditionChecker, Comparable parameterNames) { + private static String initShortName(String methodName, List parameterNames) { String shortName = methodName + "("; for (String parameterName : parameterNames) { if (parameterNames.lastIndexOf(".") > -1) @@ -330,7 +329,7 @@ public class MethodConditionCheck implements ConditionChecker, Comparable psiMethods, String allParametersSubString) { + private static PsiMethod findPsiMethodWithMatchingParameters(List psiMethods, String allParametersSubString) { String[] parameterClassAndNameArray = allParametersSubString.split(","); List parameterClassToMatch = new ArrayList(); for (String parameterClassAndName : parameterClassAndNameArray) { @@ -378,7 +377,7 @@ public class MethodConditionCheck implements ConditionChecker, Comparable findPsiMethodsInPsiClassWithMatchingMethodName(PsiClass psiClass, String methodName) { + private static List findPsiMethodsInPsiClassWithMatchingMethodName(PsiClass psiClass, String methodName) { List psiMethods = new ArrayList(); for (int i = 0; i < psiClass.getMethods().length; i++) { PsiMethod possibleMatchPsiMethod = psiClass.getMethods()[i]; From 29536cfc57062211794dcf75a2ddf8c402b5b4e3 Mon Sep 17 00:00:00 2001 From: Max Medvedev Date: Thu, 21 Feb 2013 17:20:49 +0400 Subject: [PATCH 005/354] IDEA-99659 Groovy: don't complete anonymous class or instance where array is expected --- .../GroovyExpectedTypesProvider.java | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/expectedTypes/GroovyExpectedTypesProvider.java b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/expectedTypes/GroovyExpectedTypesProvider.java index 487703eec40c..6f020e2262c4 100644 --- a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/expectedTypes/GroovyExpectedTypesProvider.java +++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/expectedTypes/GroovyExpectedTypesProvider.java @@ -20,7 +20,6 @@ import com.intellij.openapi.util.Pair; import com.intellij.psi.*; import com.intellij.psi.tree.IElementType; import com.intellij.psi.util.PsiTreeUtil; -import com.intellij.util.ArrayUtilRt; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.plugins.groovy.codeInspection.utils.ControlFlowUtils; @@ -210,10 +209,7 @@ public class GroovyExpectedTypesProvider { final GrArgumentList argumentList = methodCall.getArgumentList(); final GrNamedArgument[] namedArgs = argumentList == null ? GrNamedArgument.EMPTY_ARRAY : argumentList.getNamedArguments(); final GrExpression[] expressionArgs = argumentList == null ? GrExpression.EMPTY_ARRAY : argumentList.getExpressionArguments(); - addConstraintsFromMap(constraints, - GrClosureSignatureUtil.mapArgumentsToParameters(variant, methodCall, true, true, namedArgs, expressionArgs, - closureArgs), - closureIndex == closureArgs.length - 1); + addConstraintsFromMap(constraints, GrClosureSignatureUtil.mapArgumentsToParameters(variant, methodCall, true, true, namedArgs, expressionArgs, closureArgs)); } if (!constraints.isEmpty()) { myResult = constraints.toArray(new TypeConstraint[constraints.size()]); @@ -378,11 +374,10 @@ public class GroovyExpectedTypesProvider { public void visitArgumentList(GrArgumentList list) { List constraints = new ArrayList(); for (GroovyResolveResult variant : ResolveUtil.getCallVariants(list)) { - final GrExpression[] arguments = list.getExpressionArguments(); final Map> map = GrClosureSignatureUtil.mapArgumentsToParameters( variant, list, true, true, list.getNamedArguments(), list.getExpressionArguments(), GrClosableBlock.EMPTY_ARRAY ); - addConstraintsFromMap(constraints, map, ArrayUtilRt.find(arguments, myExpression) == arguments.length - 1); + addConstraintsFromMap(constraints, map); } if (!constraints.isEmpty()) { myResult = constraints.toArray(new TypeConstraint[constraints.size()]); @@ -428,9 +423,7 @@ public class GroovyExpectedTypesProvider { } } - private void addConstraintsFromMap(List constraints, - Map> map, - boolean isLast) { + private void addConstraintsFromMap(List constraints, Map> map) { if (map == null) return; final Pair pair = map.get(myExpression); @@ -441,7 +434,7 @@ public class GroovyExpectedTypesProvider { constraints.add(SubtypeConstraint.create(type)); - if (type instanceof PsiArrayType && isLast) { + if (type instanceof PsiArrayType && pair.first.isVarArgs()) { constraints.add(SubtypeConstraint.create(((PsiArrayType)type).getComponentType())); } } @@ -517,7 +510,7 @@ public class GroovyExpectedTypesProvider { @Override public void visitListOrMap(GrListOrMap listOrMap) { if (listOrMap.isMap()) return; - final TypeConstraint[] constraints = GroovyExpectedTypesProvider.calculateTypeConstraints(listOrMap); + final TypeConstraint[] constraints = calculateTypeConstraints(listOrMap); List result=new ArrayList(constraints.length); for (TypeConstraint constraint : constraints) { if (constraint instanceof SubtypeConstraint) { From 17d102bc9b407dc4c7092c1ae900028752f6d52d Mon Sep 17 00:00:00 2001 From: "Denis.Zhdanov" Date: Thu, 21 Feb 2013 17:48:59 +0400 Subject: [PATCH 006/354] IDEA-95898 Gradle based projects should offer Auto-Import as Maven projects do Iteration 3: there is a component which knows how to resolve project structure changes. Auto-importer now delegates to it --- .../resources/i18n/GradleBundle.properties | 2 +- plugins/gradle/src/META-INF/plugin.xml | 2 + .../gradle/autoimport/GradleAutoImporter.java | 43 ++- .../GradleUserProjectChangesCalculator.java | 9 +- .../gradle/config/GradleConfigNotifier.java | 2 + .../config/GradleConfigNotifierAdapter.java | 4 + .../gradle/config/GradleConfigurable.java | 40 ++- .../plugins/gradle/config/GradleSettings.java | 20 ++ .../gradle/config/GradleToolWindowPanel.java | 3 +- .../manage/GradleDependencyManager.java | 21 ++ .../manage/GradleEntityManageHelper.java | 316 +++++++++++++++++- .../gradle/manage/GradleModuleManager.java | 37 +- .../manage/GradleProjectImportBuilder.java | 1 + .../gradle/manage/GradleProjectManager.java | 55 +++ ...GradleProjectStructureChangesDetector.java | 13 +- .../GradleProjectStructureChangesModel.java | 3 + .../sync/GradleProjectStructureHelper.java | 10 + .../sync/GradleProjectStructureTreeModel.java | 6 +- 18 files changed, 542 insertions(+), 45 deletions(-) create mode 100644 plugins/gradle/src/org/jetbrains/plugins/gradle/manage/GradleProjectManager.java diff --git a/plugins/gradle/resources/i18n/GradleBundle.properties b/plugins/gradle/resources/i18n/GradleBundle.properties index b90dc5b5e3cc..f99b6cd319ad 100644 --- a/plugins/gradle/resources/i18n/GradleBundle.properties +++ b/plugins/gradle/resources/i18n/GradleBundle.properties @@ -11,7 +11,7 @@ gradle.settings.label.select.project=Gradle project: gradle.settings.text.home.path=Gradle home: gradle.settings.text.service.dir.path=Service directory path: gradle.settings.title.service.dir.path=Select gradle service directory to use -gradle.import.title.select.project=Select project to import +gradle.settings.use.auto.import=Use auto-import gradle.import.progress.text=Building Gradle project info gradle.import.label.project.structure=Project structure: gradle.import.label.details=Details: diff --git a/plugins/gradle/src/META-INF/plugin.xml b/plugins/gradle/src/META-INF/plugin.xml index 39464102bab4..a981413c392e 100644 --- a/plugins/gradle/src/META-INF/plugin.xml +++ b/plugins/gradle/src/META-INF/plugin.xml @@ -46,6 +46,7 @@ + @@ -56,6 +57,7 @@ + diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/autoimport/GradleAutoImporter.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/autoimport/GradleAutoImporter.java index 3c61caa5344f..5610330f3948 100644 --- a/plugins/gradle/src/org/jetbrains/plugins/gradle/autoimport/GradleAutoImporter.java +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/autoimport/GradleAutoImporter.java @@ -15,12 +15,18 @@ */ package org.jetbrains.plugins.gradle.autoimport; +import com.intellij.openapi.components.ServiceManager; import com.intellij.openapi.project.Project; import org.jetbrains.annotations.NotNull; +import org.jetbrains.plugins.gradle.config.GradleLocalSettings; +import org.jetbrains.plugins.gradle.config.GradleSettings; import org.jetbrains.plugins.gradle.diff.GradleProjectStructureChange; +import org.jetbrains.plugins.gradle.manage.GradleEntityManageHelper; import org.jetbrains.plugins.gradle.sync.GradleProjectStructureChangesPostProcessor; import java.util.Collection; +import java.util.Set; +import java.util.concurrent.atomic.AtomicBoolean; /** * Automates a task of keeping gradle and ide projects in sync, @@ -29,19 +35,50 @@ import java.util.Collection; * Consider a situation when a user, say, renames a module (gradle sub-project). We would like not only import new module * but remove the old one as well. Unfortunately, we don't have api for distinguishing change like 'gradle sub-project remove' * from 'new module is added to the project by a user'. They both look like 'new ide-local module'. That's why we use the following - * algorithm here: - * // TODO den add doc + * algorithm for every project structure change here: + *
+ * 
    + *
  1. + * Check if there is an {@link GradleUserProjectChange explicitly made project structure change} for the current project + * structure change; + *
  2. + *
  3. + * {@link GradleEntityManageHelper#eliminateChange(Collection, Set, boolean) Resolve the change} if it doesn't occur + * because of user actions (e.g. we don't want to auto-remove a module which is absent at gradle but presents at IDE); + *
  4. + *
+ *
* * @author Denis Zhdanov * @since 2/18/13 7:55 PM */ public class GradleAutoImporter implements GradleProjectStructureChangesPostProcessor { + + @NotNull private final AtomicBoolean myInProgress = new AtomicBoolean(); + + public boolean isInProgress() { + return myInProgress.get(); + } @Override public void processChanges(@NotNull Collection changes, @NotNull Project project, boolean onIdeProjectStructureChange) { - // TODO den implement + if (onIdeProjectStructureChange || !GradleSettings.getInstance(project).isUseAutoImport()) { + return; + } + GradleLocalSettings settings = GradleLocalSettings.getInstance(project); + GradleEntityManageHelper manageHelper = ServiceManager.getService(project, GradleEntityManageHelper.class); + Set nonProcessed; + myInProgress.set(true); + try { + nonProcessed = manageHelper.eliminateChange(changes, settings.getUserProjectChanges(), true); + } + finally { + myInProgress.set(false); + } + changes.clear(); + changes.addAll(nonProcessed); } } diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/autoimport/GradleUserProjectChangesCalculator.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/autoimport/GradleUserProjectChangesCalculator.java index e47866f54240..316d87aa4e0b 100644 --- a/plugins/gradle/src/org/jetbrains/plugins/gradle/autoimport/GradleUserProjectChangesCalculator.java +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/autoimport/GradleUserProjectChangesCalculator.java @@ -89,6 +89,7 @@ public class GradleUserProjectChangesCalculator { public GradleProject updateCurrentProjectState() { GradleProject state = buildCurrentIdeProject(); myLastProjectState = state; + filterOutdatedChanges(); return state; } @@ -101,10 +102,8 @@ public class GradleUserProjectChangesCalculator { public void updateChanges() { GradleProject lastProjectState = myLastProjectState; if (lastProjectState == null) { - lastProjectState = updateCurrentProjectState(); - if (lastProjectState == null) { - return; - } + updateCurrentProjectState(); + return; } GradleProject currentProjectState = buildCurrentIdeProject(); @@ -116,6 +115,8 @@ public class GradleUserProjectChangesCalculator { buildModulePresenceChanges(context); buildDependencyPresenceChanges(context); + filterOutdatedChanges(); + context.currentChanges.addAll(mySettings.getUserProjectChanges()); mySettings.setUserProjectChanges(context.currentChanges); myLastProjectState = currentProjectState; } diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/config/GradleConfigNotifier.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/config/GradleConfigNotifier.java index ccef16d19bfd..ccdb0e6b0f66 100644 --- a/plugins/gradle/src/org/jetbrains/plugins/gradle/config/GradleConfigNotifier.java +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/config/GradleConfigNotifier.java @@ -55,6 +55,8 @@ public interface GradleConfigNotifier { * @see GradleSettings#getServiceDirectoryPath() */ void onServiceDirectoryPathChange(@Nullable String oldPath, @Nullable String newPath); + + void onUseAutoImportChange(boolean oldValue, boolean newValue); /** * Gradle settings changes might affect project structure, e.g. switching from one gradle version to another one or from diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/config/GradleConfigNotifierAdapter.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/config/GradleConfigNotifierAdapter.java index 9d4883f46b4a..4da511c94955 100644 --- a/plugins/gradle/src/org/jetbrains/plugins/gradle/config/GradleConfigNotifierAdapter.java +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/config/GradleConfigNotifierAdapter.java @@ -24,6 +24,10 @@ public abstract class GradleConfigNotifierAdapter implements GradleConfigNotifie public void onServiceDirectoryPathChange(@Nullable String oldPath, @Nullable String newPath) { } + @Override + public void onUseAutoImportChange(boolean oldValue, boolean newValue) { + } + @Override public void onBulkChangeStart() { } diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/config/GradleConfigurable.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/config/GradleConfigurable.java index c708792b397b..eff3351ed109 100644 --- a/plugins/gradle/src/org/jetbrains/plugins/gradle/config/GradleConfigurable.java +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/config/GradleConfigurable.java @@ -26,6 +26,7 @@ import com.intellij.openapi.ui.TextComponentAccessor; import com.intellij.openapi.ui.TextFieldWithBrowseButton; import com.intellij.openapi.util.Comparing; import com.intellij.openapi.util.text.StringUtil; +import com.intellij.ui.components.JBCheckBox; import com.intellij.ui.components.JBLabel; import com.intellij.ui.components.JBRadioButton; import com.intellij.util.Alarm; @@ -64,7 +65,7 @@ import java.util.concurrent.TimeUnit; public class GradleConfigurable implements SearchableConfigurable, Configurable.NoScroll { private enum PathColor { NORMAL, DEDUCED } - + @NonNls public static final String HELP_TOPIC = "reference.settingsdialog.project.gradle"; private static final long BALLOON_DELAY_MILLIS = TimeUnit.SECONDS.toMillis(1); @@ -77,8 +78,8 @@ public class GradleConfigurable implements SearchableConfigurable, Configurable. @NotNull private GradleHomeSettingType myGradleHomeSettingType = GradleHomeSettingType.UNKNOWN; - @NotNull private final JLabel myLinkedProjectLabel = new JBLabel(GradleBundle.message("gradle.settings.label.select.project")); - @NotNull private final JLabel myGradleHomeLabel = new JBLabel(GradleBundle.message("gradle.settings.text.home.path")); + @NotNull private final JLabel myLinkedProjectLabel = new JBLabel(GradleBundle.message("gradle.settings.label.select.project")); + @NotNull private final JLabel myGradleHomeLabel = new JBLabel(GradleBundle.message("gradle.settings.text.home.path")); @NotNull private final JLabel myServiceDirectoryLabel = new JBLabel(GradleBundle.message("gradle.settings.text.service.dir.path")); @NotNull private TextFieldWithBrowseButton myLinkedGradleProjectPathField; @@ -86,6 +87,7 @@ public class GradleConfigurable implements SearchableConfigurable, Configurable. @NotNull private TextFieldWithBrowseButton myServiceDirectoryPathField; @NotNull private JBRadioButton myUseWrapperButton; @NotNull private JBRadioButton myUseLocalDistributionButton; + @NotNull private JBCheckBox myUseAutoImportBox; @Nullable private JComponent myComponent; @@ -156,6 +158,10 @@ public class GradleConfigurable implements SearchableConfigurable, Configurable. return myUseLocalDistributionButton.isSelected(); } + public boolean isUseAutoImport() { + return myUseAutoImportBox.isSelected(); + } + public void setLinkedGradleProjectPath(@NotNull String path) { myLinkedGradleProjectPathField.setText(path); } @@ -175,24 +181,27 @@ public class GradleConfigurable implements SearchableConfigurable, Configurable. initWrapperVsLocalControls(); initGradleHome(testMode); initServiceDirectoryHome(); + myUseAutoImportBox = new JBCheckBox(GradleBundle.message("gradle.settings.use.auto.import")); assert myComponent != null; GridBag pathLabelConstraints = new GridBag().anchor(GridBagConstraints.WEST).weightx(0); - GridBag pathConstraints = new GridBag().weightx(1).coverLine().fillCellHorizontally().anchor(GridBagConstraints.WEST); + GridBag fillLineConstraints = new GridBag().weightx(1).coverLine().fillCellHorizontally().anchor(GridBagConstraints.WEST); myComponent.add(myLinkedProjectLabel, pathLabelConstraints); - myComponent.add(myLinkedGradleProjectPathField, pathConstraints); + myComponent.add(myLinkedGradleProjectPathField, fillLineConstraints); GridBag constraints = new GridBag().coverLine().anchor(GridBagConstraints.WEST); - + // Provide radio buttons if gradle wrapper can be used for particular project. myComponent.add(myUseWrapperButton, constraints); myComponent.add(myUseLocalDistributionButton, constraints); + myComponent.add(myUseAutoImportBox, fillLineConstraints); + myComponent.add(myGradleHomeLabel, pathLabelConstraints); - myComponent.add(myGradleHomePathField, pathConstraints); + myComponent.add(myGradleHomePathField, fillLineConstraints); myComponent.add(myServiceDirectoryLabel, pathLabelConstraints); - myComponent.add(myServiceDirectoryPathField, pathConstraints); + myComponent.add(myServiceDirectoryPathField, fillLineConstraints); myComponent.add(Box.createVerticalGlue(), new GridBag().weightx(1).weighty(1).fillCell().coverLine()); } @@ -404,6 +413,10 @@ public class GradleConfigurable implements SearchableConfigurable, Configurable. { return true; } + + if (myUseAutoImportBox.isSelected() != settings.isUseAutoImport()) { + return true; + } return false; } @@ -428,7 +441,8 @@ public class GradleConfigurable implements SearchableConfigurable, Configurable. myServiceDirectoryPathField.getText()); boolean preferLocalToWrapper = myUseLocalDistributionButton.isSelected(); - myHelper.applySettings(linkedProjectPath, gradleHomePath, preferLocalToWrapper, serviceDirPath, myProject); + boolean useAutoImport = myUseAutoImportBox.isSelected(); + myHelper.applySettings(linkedProjectPath, gradleHomePath, preferLocalToWrapper, useAutoImport, serviceDirPath, myProject); Project defaultProject = myHelper.getDefaultProject(); if (myProject != defaultProject) { @@ -563,6 +577,8 @@ public class GradleConfigurable implements SearchableConfigurable, Configurable. myServiceDirectoryPathField.setText(serviceDirectoryPath); useColorForPath(PathColor.NORMAL, myServiceDirectoryPathField); } + + myUseAutoImportBox.setSelected(settings.isUseAutoImport()); } private static void useColorForPath(@NotNull PathColor color, @NotNull TextFieldWithBrowseButton pathControl) { @@ -674,6 +690,7 @@ public class GradleConfigurable implements SearchableConfigurable, Configurable. void applySettings(@Nullable String linkedProjectPath, @Nullable String gradleHomePath, boolean preferLocalInstallationToWrapper, + boolean useAutoImport, @Nullable String serviceDirectoryPath, @NotNull Project project); @@ -719,10 +736,13 @@ public class GradleConfigurable implements SearchableConfigurable, Configurable. public void applySettings(@Nullable String linkedProjectPath, @Nullable String gradleHomePath, boolean preferLocalInstallationToWrapper, + boolean useAutoImport, @Nullable String serviceDirectoryPath, @NotNull Project project) { - GradleSettings.applySettings(linkedProjectPath, gradleHomePath, preferLocalInstallationToWrapper, serviceDirectoryPath, project); + GradleSettings.applySettings( + linkedProjectPath, gradleHomePath, preferLocalInstallationToWrapper, useAutoImport, serviceDirectoryPath, project + ); } @Override diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/config/GradleSettings.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/config/GradleSettings.java index f1d035208fe7..87757134f1a7 100644 --- a/plugins/gradle/src/org/jetbrains/plugins/gradle/config/GradleSettings.java +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/config/GradleSettings.java @@ -44,6 +44,7 @@ public class GradleSettings implements PersistentStateComponent private String myGradleHome; private String myServiceDirectoryPath; private boolean myPreferLocalInstallationToWrapper; + private boolean myUseAutoImport = true; // Turned on by default. @Override public GradleSettings getState() { @@ -139,9 +140,27 @@ public class GradleSettings implements PersistentStateComponent } } + public boolean isUseAutoImport() { + return myUseAutoImport; + } + + public void setUseAutoImport(boolean useAutoImport) { + myUseAutoImport = useAutoImport; + } + + public static void applyUseAutoImport(boolean useAutoImport, @NotNull Project project) { + final GradleSettings settings = getInstance(project); + final boolean oldValue = settings.isUseAutoImport(); + if (oldValue != useAutoImport) { + settings.setUseAutoImport(useAutoImport); + project.getMessageBus().syncPublisher(GradleConfigNotifier.TOPIC).onUseAutoImportChange(oldValue, useAutoImport); + } + } + public static void applySettings(@Nullable String linkedProjectPath, @Nullable String gradleHomePath, boolean preferLocalInstallationToWrapper, + boolean useAutoImport, @Nullable String serviceDirectoryPath, @NotNull Project project) { @@ -151,6 +170,7 @@ public class GradleSettings implements PersistentStateComponent applyLinkedProjectPath(linkedProjectPath, project); applyGradleHome(gradleHomePath, project); applyPreferLocalInstallationToWrapper(preferLocalInstallationToWrapper, project); + applyUseAutoImport(useAutoImport, project); applyServiceDirectoryPath(serviceDirectoryPath, project); } finally { diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/config/GradleToolWindowPanel.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/config/GradleToolWindowPanel.java index 0cea2ab76522..8adb91f3a252 100644 --- a/plugins/gradle/src/org/jetbrains/plugins/gradle/config/GradleToolWindowPanel.java +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/config/GradleToolWindowPanel.java @@ -95,7 +95,8 @@ public abstract class GradleToolWindowPanel extends SimpleToolWindowPanel { @Override public void onPreferLocalGradleDistributionToWrapperChange(boolean preferLocalToWrapper) { refreshAll(); } @Override public void onGradleHomeChange(@Nullable String oldPath, @Nullable String newPath) { refreshAll(); } @Override public void onServiceDirectoryPathChange(@Nullable String oldPath, @Nullable String newPath) { refreshAll(); } - + @Override public void onUseAutoImportChange(boolean oldValue, boolean newValue) { refreshAll(); } + private void refreshAll() { if (myInBulk) { myRefresh = true; diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/manage/GradleDependencyManager.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/manage/GradleDependencyManager.java index 6bbfb66b7769..22c403502f88 100644 --- a/plugins/gradle/src/org/jetbrains/plugins/gradle/manage/GradleDependencyManager.java +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/manage/GradleDependencyManager.java @@ -2,6 +2,7 @@ package org.jetbrains.plugins.gradle.manage; import com.intellij.openapi.components.ServiceManager; import com.intellij.openapi.module.Module; +import com.intellij.openapi.project.Project; import com.intellij.openapi.roots.*; import com.intellij.openapi.roots.libraries.Library; import com.intellij.openapi.roots.libraries.LibraryTable; @@ -179,4 +180,24 @@ public class GradleDependencyManager { }); } } + + public void setScope(@NotNull final DependencyScope scope, @NotNull final ExportableOrderEntry dependency, boolean synchronous) { + Project project = dependency.getOwnerModule().getProject(); + GradleUtil.executeProjectChangeAction(project, dependency, synchronous, new Runnable() { + @Override + public void run() { + dependency.setScope(scope); + } + }); + } + + public void setExported(final boolean exported, @NotNull final ExportableOrderEntry dependency, boolean synchronous) { + Project project = dependency.getOwnerModule().getProject(); + GradleUtil.executeProjectChangeAction(project, dependency, synchronous, new Runnable() { + @Override + public void run() { + dependency.setExported(exported); + } + }); + } } diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/manage/GradleEntityManageHelper.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/manage/GradleEntityManageHelper.java index 022240590c5f..c9a8838b2a39 100644 --- a/plugins/gradle/src/org/jetbrains/plugins/gradle/manage/GradleEntityManageHelper.java +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/manage/GradleEntityManageHelper.java @@ -23,7 +23,23 @@ import com.intellij.openapi.roots.ModuleOrderEntry; import com.intellij.openapi.roots.libraries.Library; import com.intellij.util.containers.ContainerUtilRt; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.plugins.gradle.autoimport.*; +import org.jetbrains.plugins.gradle.diff.AbstractGradleConflictingPropertyChange; +import org.jetbrains.plugins.gradle.diff.GradleProjectStructureChange; +import org.jetbrains.plugins.gradle.diff.GradleProjectStructureChangeVisitor; +import org.jetbrains.plugins.gradle.diff.contentroot.GradleContentRootPresenceChange; +import org.jetbrains.plugins.gradle.diff.dependency.GradleDependencyExportedChange; +import org.jetbrains.plugins.gradle.diff.dependency.GradleDependencyScopeChange; +import org.jetbrains.plugins.gradle.diff.dependency.GradleLibraryDependencyPresenceChange; +import org.jetbrains.plugins.gradle.diff.dependency.GradleModuleDependencyPresenceChange; +import org.jetbrains.plugins.gradle.diff.library.GradleJarPresenceChange; +import org.jetbrains.plugins.gradle.diff.library.GradleOutdatedLibraryVersionChange; +import org.jetbrains.plugins.gradle.diff.module.GradleModulePresenceChange; +import org.jetbrains.plugins.gradle.diff.project.GradleLanguageLevelChange; +import org.jetbrains.plugins.gradle.diff.project.GradleProjectRenameChange; import org.jetbrains.plugins.gradle.model.gradle.*; +import org.jetbrains.plugins.gradle.model.id.*; import org.jetbrains.plugins.gradle.model.intellij.IdeEntityVisitor; import org.jetbrains.plugins.gradle.model.intellij.ModuleAwareContentRoot; import org.jetbrains.plugins.gradle.sync.GradleProjectStructureHelper; @@ -42,6 +58,7 @@ public class GradleEntityManageHelper { @NotNull private final Project myProject; @NotNull private final GradleProjectStructureHelper myProjectStructureHelper; + @NotNull private final GradleProjectManager myProjectManager; @NotNull private final GradleModuleManager myModuleManager; @NotNull private final GradleLibraryManager myLibraryManager; @NotNull private final GradleJarManager myJarManager; @@ -50,6 +67,7 @@ public class GradleEntityManageHelper { public GradleEntityManageHelper(@NotNull Project project, @NotNull GradleProjectStructureHelper helper, + @NotNull GradleProjectManager projectManager, @NotNull GradleModuleManager moduleManager, @NotNull GradleLibraryManager libraryManager, @NotNull GradleJarManager jarManager, @@ -58,6 +76,7 @@ public class GradleEntityManageHelper { { myProject = project; myProjectStructureHelper = helper; + myProjectManager = projectManager; myModuleManager = moduleManager; myLibraryManager = libraryManager; myJarManager = jarManager; @@ -72,20 +91,36 @@ public class GradleEntityManageHelper { final Set jars = ContainerUtilRt.newHashSet(); final Map> dependencies = ContainerUtilRt.newHashMap(); GradleEntityVisitor visitor = new GradleEntityVisitor() { - @Override public void visit(@NotNull GradleProject project) { } - @Override public void visit(@NotNull GradleModule module) { modules.add(module); } - @Override public void visit(@NotNull GradleLibrary library) { libraries.add(library); } - @Override public void visit(@NotNull GradleJar jar) { jars.add(jar); } - @Override public void visit(@NotNull GradleModuleDependency dependency) { addDependency(dependency); } - @Override public void visit(@NotNull GradleLibraryDependency dependency) { addDependency(dependency); } - @Override public void visit(@NotNull GradleCompositeLibraryDependency dependency) { } - @Override public void visit(@NotNull GradleContentRoot contentRoot) { + @Override + public void visit(@NotNull GradleProject project) { } + + @Override + public void visit(@NotNull GradleModule module) { modules.add(module); } + + @Override + public void visit(@NotNull GradleLibrary library) { libraries.add(library); } + + @Override + public void visit(@NotNull GradleJar jar) { jars.add(jar); } + + @Override + public void visit(@NotNull GradleModuleDependency dependency) { addDependency(dependency); } + + @Override + public void visit(@NotNull GradleLibraryDependency dependency) { addDependency(dependency); } + + @Override + public void visit(@NotNull GradleCompositeLibraryDependency dependency) { } + + @Override + public void visit(@NotNull GradleContentRoot contentRoot) { Collection roots = contentRoots.get(contentRoot.getOwnerModule()); if (roots == null) { contentRoots.put(contentRoot.getOwnerModule(), roots = ContainerUtilRt.newHashSet()); } roots.add(contentRoot); } + private void addDependency(@NotNull GradleDependency dependency) { Collection d = dependencies.get(dependency.getOwnerModule()); if (d == null) { @@ -144,4 +179,269 @@ public class GradleEntityManageHelper { myDependencyManager.removeDependencies(dependencies, synchronous); myModuleManager.removeModules(modules, synchronous); } + + /** + * Tries to eliminate all target changes (namely, all given except those which correspond 'changes to preserve') + * + * @param changesToEliminate changes to eliminate + * @param changesToPreserve changes to preserve + * @param synchronous defines if the processing should be synchronous + * @return non-processed changes + */ + public Set eliminateChange(@NotNull Collection changesToEliminate, + @NotNull final Set changesToPreserve, + boolean synchronous) + { + EliminateChangesContext context = new EliminateChangesContext( + myProjectStructureHelper, changesToPreserve, myProjectManager, myDependencyManager, synchronous + ); + for (GradleProjectStructureChange change : changesToEliminate) { + change.invite(context.visitor); + } + + removeEntities(context.entitiesToRemove, synchronous); + importEntities(context.entitiesToImport, synchronous); + return context.nonProcessedChanges; + } + + private static void processProjectRenameChange(@NotNull GradleProjectRenameChange change, @NotNull EliminateChangesContext context) { + context.projectManager.renameProject(change.getGradleValue(), context.projectStructureHelper.getProject(), context.synchronous); + } + + private static void processLanguageLevelChange(@NotNull GradleLanguageLevelChange change, @NotNull EliminateChangesContext context) { + context.projectManager.setLanguageLevel(change.getGradleValue(), context.projectStructureHelper.getProject(), context.synchronous); + } + + private static void processModulePresenceChange(@NotNull GradleModulePresenceChange change, @NotNull EliminateChangesContext context) { + GradleModuleId id = change.getGradleEntity(); + if (id == null) { + // IDE-local change. + id = change.getIdeEntity(); + assert id != null; + Module module = context.projectStructureHelper.findIdeModule(id.getModuleName()); + if (module != null && !context.changesToPreserve.contains(new GradleAddModuleUserChange(id.getModuleName()))) { + context.entitiesToRemove.add(module); + return; + } + } + else { + GradleModule module = context.projectStructureHelper.findGradleModule(id.getModuleName()); + if (module != null && !context.changesToPreserve.contains(new GradleRemoveModuleUserChange(id.getModuleName()))) { + context.entitiesToImport.add(module); + return; + } + } + context.nonProcessedChanges.add(change); + } + + private static void processContentRootPresenceChange(@NotNull GradleContentRootPresenceChange change, + @NotNull EliminateChangesContext context) + { + GradleContentRootId id = change.getGradleEntity(); + if (id == null) { + // IDE-local change. + id = change.getIdeEntity(); + assert id != null; + ModuleAwareContentRoot root = context.projectStructureHelper.findIdeContentRoot(id); + if (root != null) { + context.entitiesToRemove.add(root); + return; + } + } + else { + GradleContentRoot root = context.projectStructureHelper.findGradleContentRoot(id); + if (root != null) { + context.entitiesToImport.add(root); + return; + } + } + context.nonProcessedChanges.add(change); + } + + private static void processLibraryDependencyPresenceChange(@NotNull GradleLibraryDependencyPresenceChange change, + @NotNull EliminateChangesContext context) + { + GradleLibraryDependencyId id = change.getGradleEntity(); + if (id == null) { + // IDE-local change. + id = change.getIdeEntity(); + assert id != null; + LibraryOrderEntry dependency = context.projectStructureHelper.findIdeLibraryDependency(id); + GradleAddLibraryDependencyUserChange c = new GradleAddLibraryDependencyUserChange(id.getOwnerModuleName(), id.getDependencyName()); + if (dependency != null && !context.changesToPreserve.contains(c)) { + context.entitiesToRemove.add(dependency); + return; + } + } + else { + GradleLibraryDependency dependency = context.projectStructureHelper.findGradleLibraryDependency(id); + GradleRemoveLibraryDependencyUserChange c + = new GradleRemoveLibraryDependencyUserChange(id.getOwnerModuleName(), id.getDependencyName()); + if (dependency != null && !context.changesToPreserve.contains(c)) { + context.entitiesToImport.add(dependency); + return; + } + } + context.nonProcessedChanges.add(change); + } + + private static void processJarPresenceChange(@NotNull GradleJarPresenceChange change, @NotNull EliminateChangesContext context) { + GradleJarId id = change.getGradleEntity(); + if (id == null) { + // IDE-local change. + id = change.getIdeEntity(); + assert id != null; + GradleJar jar = context.projectStructureHelper.findIdeJar(id); + if (jar != null) { + context.entitiesToRemove.add(jar); + return; + } + } + else { + GradleLibrary library = context.projectStructureHelper.findGradleLibrary(id.getLibraryId()); + if (library != null) { + context.entitiesToImport.add(new GradleJar(id.getPath(), id.getLibraryPathType(), null, library)); + return; + } + } + context.nonProcessedChanges.add(change); + } + + private static void processModuleDependencyPresenceChange(@NotNull GradleModuleDependencyPresenceChange change, + @NotNull EliminateChangesContext context) + { + GradleModuleDependencyId id = change.getGradleEntity(); + if (id == null) { + // IDE-local change. + id = change.getIdeEntity(); + assert id != null; + ModuleOrderEntry dependency = context.projectStructureHelper.findIdeModuleDependency(id); + GradleAddModuleDependencyUserChange c = new GradleAddModuleDependencyUserChange(id.getOwnerModuleName(), id.getDependencyName()); + if (dependency != null && !context.changesToPreserve.contains(c)) { + context.entitiesToRemove.add(dependency); + return; + } + } + else { + GradleModuleDependency dependency = context.projectStructureHelper.findGradleModuleDependency(id); + GradleRemoveModuleDependencyUserChange c + = new GradleRemoveModuleDependencyUserChange(id.getOwnerModuleName(), id.getDependencyName()); + if (dependency != null && !context.changesToPreserve.contains(c)) { + context.entitiesToImport.add(dependency); + return; + } + } + context.nonProcessedChanges.add(change); + } + + private static void processDependencyScopeChange(@NotNull GradleDependencyScopeChange change, @NotNull EliminateChangesContext context) { + ExportableOrderEntry dependency = findDependency(change, context); + if (dependency != null) { + context.dependencyManager.setScope(change.getGradleValue(), dependency, context.synchronous); + } + } + + private static void processDependencyExportedStatusChange(@NotNull GradleDependencyExportedChange change, + @NotNull EliminateChangesContext context) + { + ExportableOrderEntry dependency = findDependency(change, context); + if (dependency != null) { + context.dependencyManager.setExported(change.getGradleValue(), dependency, context.synchronous); + } + } + + @Nullable + private static ExportableOrderEntry findDependency(@NotNull AbstractGradleConflictingPropertyChange change, + @NotNull EliminateChangesContext context) + { + GradleEntityId id = change.getEntityId(); + ExportableOrderEntry dependency = null; + if (id instanceof GradleLibraryDependencyId) { + dependency = context.projectStructureHelper.findIdeLibraryDependency((GradleLibraryDependencyId)id); + } + else if (id instanceof GradleModuleDependencyId) { + dependency = context.projectStructureHelper.findIdeModuleDependency((GradleModuleDependencyId)id); + } + else { + context.nonProcessedChanges.add(change); + } + return dependency; + } + + private static class EliminateChangesContext { + @NotNull final Set entitiesToRemove = ContainerUtilRt.newHashSet(); + @NotNull final Set entitiesToImport = ContainerUtilRt.newHashSet(); + @NotNull final Set nonProcessedChanges = ContainerUtilRt.newHashSet(); + @NotNull final Set changesToPreserve = ContainerUtilRt.newHashSet(); + + @NotNull final GradleProjectManager projectManager; + @NotNull final GradleDependencyManager dependencyManager; + final boolean synchronous; + + @NotNull GradleProjectStructureChangeVisitor visitor = new GradleProjectStructureChangeVisitor() { + @Override + public void visit(@NotNull GradleProjectRenameChange change) { + processProjectRenameChange(change, EliminateChangesContext.this); + } + + @Override + public void visit(@NotNull GradleLanguageLevelChange change) { + processLanguageLevelChange(change, EliminateChangesContext.this); + } + + @Override + public void visit(@NotNull GradleModulePresenceChange change) { + processModulePresenceChange(change, EliminateChangesContext.this); + } + + @Override + public void visit(@NotNull GradleContentRootPresenceChange change) { + processContentRootPresenceChange(change, EliminateChangesContext.this); + } + + @Override + public void visit(@NotNull GradleLibraryDependencyPresenceChange change) { + processLibraryDependencyPresenceChange(change, EliminateChangesContext.this); + } + + @Override + public void visit(@NotNull GradleJarPresenceChange change) { + processJarPresenceChange(change, EliminateChangesContext.this); + } + + @Override + public void visit(@NotNull GradleOutdatedLibraryVersionChange change) { + } + + @Override + public void visit(@NotNull GradleModuleDependencyPresenceChange change) { + processModuleDependencyPresenceChange(change, EliminateChangesContext.this); + } + + @Override + public void visit(@NotNull GradleDependencyScopeChange change) { + processDependencyScopeChange(change, EliminateChangesContext.this); + } + + @Override + public void visit(@NotNull GradleDependencyExportedChange change) { + processDependencyExportedStatusChange(change, EliminateChangesContext.this); + } + }; + + @NotNull final GradleProjectStructureHelper projectStructureHelper; + + EliminateChangesContext(@NotNull GradleProjectStructureHelper projectStructureHelper, + @NotNull Set changesToPreserve, + @NotNull GradleProjectManager projectManager, + @NotNull GradleDependencyManager dependencyManager, + boolean synchronous) + { + this.projectStructureHelper = projectStructureHelper; + this.changesToPreserve.addAll(changesToPreserve); + this.projectManager = projectManager; + this.dependencyManager = dependencyManager; + this.synchronous = synchronous; + } + } } diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/manage/GradleModuleManager.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/manage/GradleModuleManager.java index db493961986a..c60b6e2d1e19 100644 --- a/plugins/gradle/src/org/jetbrains/plugins/gradle/manage/GradleModuleManager.java +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/manage/GradleModuleManager.java @@ -7,6 +7,8 @@ import com.intellij.openapi.module.ModuleManager; import com.intellij.openapi.module.StdModuleTypes; import com.intellij.openapi.project.Project; import com.intellij.openapi.roots.*; +import com.intellij.openapi.vfs.LocalFileSystem; +import com.intellij.openapi.vfs.VirtualFile; import com.intellij.util.Alarm; import com.intellij.util.containers.hash.HashMap; import com.intellij.util.ui.UIUtil; @@ -16,6 +18,7 @@ import org.jetbrains.plugins.gradle.util.GradleLog; import org.jetbrains.plugins.gradle.util.GradleUtil; import java.io.File; +import java.io.IOException; import java.util.Collection; import java.util.Map; import java.util.concurrent.TimeUnit; @@ -61,7 +64,7 @@ public class GradleModuleManager { Runnable task = new Runnable() { @Override public void run() { - removeExistingModulesConfigs(modules); + removeExistingModulesConfigs(modules, project); Application application = ApplicationManager.getApplication(); final Map moduleMappings = new HashMap(); application.runWriteAction(new Runnable() { @@ -130,18 +133,28 @@ public class GradleModuleManager { } } - private static void removeExistingModulesConfigs(@NotNull Collection modules) { - for (GradleModule module : modules) { - // Remove existing '*.iml' file if necessary. - final String moduleFilePath = module.getModuleFilePath(); - File file = new File(moduleFilePath); - if (file.isFile()) { - boolean success = file.delete(); - if (!success) { - GradleLog.LOG.warn("Can't remove existing module file at '" + moduleFilePath + "'"); - } - } + private void removeExistingModulesConfigs(@NotNull final Collection modules, @NotNull Project project) { + if (modules.isEmpty()) { + return; } + GradleUtil.executeProjectChangeAction(project, modules, true, new Runnable() { + @Override + public void run() { + LocalFileSystem fileSystem = LocalFileSystem.getInstance(); + for (GradleModule module : modules) { + // Remove existing '*.iml' file if necessary. + VirtualFile file = fileSystem.refreshAndFindFileByPath(module.getModuleFilePath()); + if (file != null) { + try { + file.delete(this); + } + catch (IOException e) { + GradleLog.LOG.warn("Can't remove existing module file at '" + module.getModuleFilePath() + "'"); + } + } + } + } + }); } @SuppressWarnings("MethodMayBeStatic") diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/manage/GradleProjectImportBuilder.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/manage/GradleProjectImportBuilder.java index c2790b9eae6e..947e02c2d1a4 100644 --- a/plugins/gradle/src/org/jetbrains/plugins/gradle/manage/GradleProjectImportBuilder.java +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/manage/GradleProjectImportBuilder.java @@ -141,6 +141,7 @@ public class GradleProjectImportBuilder extends ProjectImportBuilderemptyList(), context.getCurrentChanges()); filterNodes(root); } From 8de5f33a7d7319ae2d475818192bc5d8c64b8d65 Mon Sep 17 00:00:00 2001 From: nik Date: Thu, 21 Feb 2013 17:52:32 +0400 Subject: [PATCH 007/354] cli-parser.jar added to jps distribution --- build/scripts/layouts.gant | 1 + 1 file changed, 1 insertion(+) diff --git a/build/scripts/layouts.gant b/build/scripts/layouts.gant index cdb7688d8cea..cb202f3dc436 100644 --- a/build/scripts/layouts.gant +++ b/build/scripts/layouts.gant @@ -606,6 +606,7 @@ def layout_jps(String home, String target) { include(name: "asm4-all.jar") include(name: "nanoxml-*.jar") include(name: "protobuf-*.jar") + include(name: "cli-parser-*.jar") include(name: "optimizedFileManager.jar") include(name: "log4j.jar") include(name: "jgoodies-forms.jar") From ac91b3616956fa61bba3934b2de5d7b2456a716f Mon Sep 17 00:00:00 2001 From: Alexander Kirillin Date: Thu, 21 Feb 2013 17:59:45 +0400 Subject: [PATCH 008/354] OC-6207 Remove unused receiver and parameter variables in Inline Method --- .../psi/impl/PsiToDocumentSynchronizer.java | 31 ++++++++++++------- 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/platform/lang-impl/src/com/intellij/psi/impl/PsiToDocumentSynchronizer.java b/platform/lang-impl/src/com/intellij/psi/impl/PsiToDocumentSynchronizer.java index 9cf95dcd0367..02b2300f9409 100644 --- a/platform/lang-impl/src/com/intellij/psi/impl/PsiToDocumentSynchronizer.java +++ b/platform/lang-impl/src/com/intellij/psi/impl/PsiToDocumentSynchronizer.java @@ -26,6 +26,7 @@ import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Pair; import com.intellij.openapi.util.text.StringUtil; import com.intellij.psi.*; +import com.intellij.psi.impl.source.tree.ForeignLeafPsiElement; import com.intellij.util.messages.MessageBus; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -105,22 +106,26 @@ public class PsiToDocumentSynchronizer extends PsiTreeChangeAdapter { @Override public void childAdded(@NotNull final PsiTreeChangeEvent event) { - doSync(event, false, new DocSyncAction() { - @Override - public void syncDocument(Document document, PsiTreeChangeEventImpl event) { - insertString(document, event.getOffset(), event.getChild().getText()); - } + if (!(event.getChild() instanceof ForeignLeafPsiElement)) { + doSync(event, false, new DocSyncAction() { + @Override + public void syncDocument(Document document, PsiTreeChangeEventImpl event) { + insertString(document, event.getOffset(), event.getChild().getText()); + } }); + } } @Override public void childRemoved(@NotNull final PsiTreeChangeEvent event) { - doSync(event, false, new DocSyncAction() { - @Override - public void syncDocument(Document document, PsiTreeChangeEventImpl event) { - deleteString(document, event.getOffset(), event.getOffset() + event.getOldLength()); - } - }); + if (!(event.getChild() instanceof ForeignLeafPsiElement)) { + doSync(event, false, new DocSyncAction() { + @Override + public void syncDocument(Document document, PsiTreeChangeEventImpl event) { + deleteString(document, event.getOffset(), event.getOffset() + event.getOldLength()); + } + }); + } } @Override @@ -128,7 +133,9 @@ public class PsiToDocumentSynchronizer extends PsiTreeChangeAdapter { doSync(event, false, new DocSyncAction() { @Override public void syncDocument(Document document, PsiTreeChangeEventImpl event) { - replaceString(document, event.getOffset(), event.getOffset() + event.getOldLength(), event.getNewChild().getText()); + int oldLength = event.getOldChild() instanceof ForeignLeafPsiElement ? 0 : event.getOldLength(); + String newText = event.getNewChild() instanceof ForeignLeafPsiElement ? "" : event.getNewChild().getText(); + replaceString(document, event.getOffset(), event.getOffset() + oldLength, newText); } }); } From f27f0ef1dcab9e47ecb0da39f7bca7a3cc5edf55 Mon Sep 17 00:00:00 2001 From: "Denis.Zhdanov" Date: Thu, 21 Feb 2013 18:09:43 +0400 Subject: [PATCH 009/354] Gradle: show project refresh update messages at the main progress indicator text (make them visible for the indicators located at the status bar) --- plugins/gradle/resources/i18n/GradleBundle.properties | 3 ++- .../org/jetbrains/plugins/gradle/task/AbstractGradleTask.java | 3 ++- .../src/org/jetbrains/plugins/gradle/util/GradleUtil.java | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/plugins/gradle/resources/i18n/GradleBundle.properties b/plugins/gradle/resources/i18n/GradleBundle.properties index f99b6cd319ad..78a97c1100be 100644 --- a/plugins/gradle/resources/i18n/GradleBundle.properties +++ b/plugins/gradle/resources/i18n/GradleBundle.properties @@ -56,7 +56,8 @@ gradle.import.text.error.file.module.compile.output.location=Module compile outp gradle.import.text.error.file.module.test.output.location=Module test output location is undefined gradle.sync.title.tab=project structure changes -gradle.sync.progress.text=Refreshing Gradle project +gradle.sync.progress.initial.text=Refreshing Gradle project +gradle.sync.progress.update.text=Gradle: {0} gradle.sync.change.type.gradle=Gradle local entities gradle.sync.change.type.intellij=IntelliJ IDEA local entities gradle.sync.change.type.conflict=Entities with conflicting setup diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/task/AbstractGradleTask.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/task/AbstractGradleTask.java index 6be6f4448fc2..c2e5af793d9c 100644 --- a/plugins/gradle/src/org/jetbrains/plugins/gradle/task/AbstractGradleTask.java +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/task/AbstractGradleTask.java @@ -12,6 +12,7 @@ import org.jetbrains.plugins.gradle.notification.GradleTaskNotificationListener; import org.jetbrains.plugins.gradle.notification.GradleTaskNotificationListenerAdapter; import org.jetbrains.plugins.gradle.remote.GradleApiFacade; import org.jetbrains.plugins.gradle.remote.GradleApiFacadeManager; +import org.jetbrains.plugins.gradle.util.GradleBundle; import java.util.concurrent.atomic.AtomicReference; @@ -86,7 +87,7 @@ public abstract class AbstractGradleTask implements GradleTask { execute(new GradleTaskNotificationListenerAdapter() { @Override public void onStatusChange(@NotNull GradleTaskNotificationEvent event) { - indicator.setText2(event.getDescription()); + indicator.setText(GradleBundle.message("gradle.sync.progress.update.text", event.getDescription())); } }); } diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/util/GradleUtil.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/util/GradleUtil.java index d7d213910033..a4302558a359 100644 --- a/plugins/gradle/src/org/jetbrains/plugins/gradle/util/GradleUtil.java +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/util/GradleUtil.java @@ -322,7 +322,7 @@ public class GradleUtil { }); } else { - ProgressManager.getInstance().run(new Task.Backgroundable(project, GradleBundle.message("gradle.sync.progress.text")) { + ProgressManager.getInstance().run(new Task.Backgroundable(project, GradleBundle.message("gradle.sync.progress.initial.text")) { @Override public void run(@NotNull ProgressIndicator indicator) { task.execute(indicator); From e869310a923c94838c8c095c11ab4c586e01bb81 Mon Sep 17 00:00:00 2001 From: anna Date: Thu, 21 Feb 2013 13:27:29 +0100 Subject: [PATCH 010/354] ambiguous method call: choose one method if both are from the same hierarchy as the actual problem is there (IDEA-101529) --- .../JavaMethodsConflictResolver.java | 4 +++- .../pck/AmbiguousMethodCall.java | 13 +++++++++++++ .../codeInsight/daemon/AdvHighlightingJdk7Test.java | 4 ++++ 3 files changed, 20 insertions(+), 1 deletion(-) create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/advHighlighting7/ambiguousMethodsFromSameClassAccess/pck/AmbiguousMethodCall.java diff --git a/java/java-psi-impl/src/com/intellij/psi/scope/conflictResolvers/JavaMethodsConflictResolver.java b/java/java-psi-impl/src/com/intellij/psi/scope/conflictResolvers/JavaMethodsConflictResolver.java index 5a67a6a61f20..fdf4f44b08cc 100644 --- a/java/java-psi-impl/src/com/intellij/psi/scope/conflictResolvers/JavaMethodsConflictResolver.java +++ b/java/java-psi-impl/src/com/intellij/psi/scope/conflictResolvers/JavaMethodsConflictResolver.java @@ -550,7 +550,9 @@ public class JavaMethodsConflictResolver implements PsiConflictResolver{ } if (isMoreSpecific == null) { if (!JavaVersionService.getInstance().isAtLeast(myArgumentsList, JavaSdkVersion.JDK_1_7) || - !MethodSignatureUtil.areParametersErasureEqual(method1, method2)) { + !MethodSignatureUtil.areParametersErasureEqual(method1, method2) || + InheritanceUtil.isInheritorOrSelf(class1, class2, true) || + InheritanceUtil.isInheritorOrSelf(class2, class1, true)) { if (typeParameters1.length < typeParameters2.length) return Specifics.FIRST; if (typeParameters1.length > typeParameters2.length) return Specifics.SECOND; } diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/advHighlighting7/ambiguousMethodsFromSameClassAccess/pck/AmbiguousMethodCall.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/advHighlighting7/ambiguousMethodsFromSameClassAccess/pck/AmbiguousMethodCall.java new file mode 100644 index 000000000000..7b468c8e1042 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/advHighlighting7/ambiguousMethodsFromSameClassAccess/pck/AmbiguousMethodCall.java @@ -0,0 +1,13 @@ +package pck; + +class A { + public void bar(I a, Class any) { + System.out.println(a.with(any)); + } + + interface I { + T with(Class aClass); + long with(Class aClass); + } +} + diff --git a/java/java-tests/testSrc/com/intellij/codeInsight/daemon/AdvHighlightingJdk7Test.java b/java/java-tests/testSrc/com/intellij/codeInsight/daemon/AdvHighlightingJdk7Test.java index 92c4d7c351b2..85f4fcaa4697 100644 --- a/java/java-tests/testSrc/com/intellij/codeInsight/daemon/AdvHighlightingJdk7Test.java +++ b/java/java-tests/testSrc/com/intellij/codeInsight/daemon/AdvHighlightingJdk7Test.java @@ -191,4 +191,8 @@ public class AdvHighlightingJdk7Test extends DaemonAnalyzerTestCase { public void testAmbiguousIDEA87672() throws Exception { doTestAmbiguous(); } + + public void testAmbiguousMethodsFromSameClassAccess() throws Exception { + doTestAmbiguous(); + } } From 985efd2362202ee58850fc1ef9e20152cd65e00c Mon Sep 17 00:00:00 2001 From: anna Date: Thu, 21 Feb 2013 15:04:54 +0100 Subject: [PATCH 011/354] more specific method should be chosen before static access is checked (IDEA-101480) --- .../JavaMethodsConflictResolver.java | 20 ++++++++++++------- ...nceMemberNotAccessibleInStaticContext.java | 11 ++++++++++ .../daemon/LightAdvHighlightingJdk7Test.java | 1 + .../psi/resolve/ResolveMethodTest.java | 2 +- 4 files changed, 26 insertions(+), 8 deletions(-) create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/advHighlighting7/InstanceMemberNotAccessibleInStaticContext.java diff --git a/java/java-psi-impl/src/com/intellij/psi/scope/conflictResolvers/JavaMethodsConflictResolver.java b/java/java-psi-impl/src/com/intellij/psi/scope/conflictResolvers/JavaMethodsConflictResolver.java index fdf4f44b08cc..7501526a3d69 100644 --- a/java/java-psi-impl/src/com/intellij/psi/scope/conflictResolvers/JavaMethodsConflictResolver.java +++ b/java/java-psi-impl/src/com/intellij/psi/scope/conflictResolvers/JavaMethodsConflictResolver.java @@ -67,7 +67,7 @@ public class JavaMethodsConflictResolver implements PsiConflictResolver{ checkSameSignatures(conflicts); if (conflicts.size() == 1) return conflicts.get(0); - checkAccessLevels(conflicts); + checkAccessStaticLevels(conflicts, true); if (conflicts.size() == 1) return conflicts.get(0); checkParametersNumber(conflicts, myActualParameterTypes.length, false); @@ -89,6 +89,9 @@ public class JavaMethodsConflictResolver implements PsiConflictResolver{ checkPrimitiveVarargs(conflicts, myActualParameterTypes.length); if (conflicts.size() == 1) return conflicts.get(0); + checkAccessStaticLevels(conflicts, false); + if (conflicts.size() == 1) return conflicts.get(0); + THashSet uniques = new THashSet(conflicts); if (uniques.size() == 1) return uniques.iterator().next(); return null; @@ -158,7 +161,7 @@ public class JavaMethodsConflictResolver implements PsiConflictResolver{ } } - private static void checkAccessLevels(List conflicts) { + private static void checkAccessStaticLevels(List conflicts, boolean checkAccessible) { int conflictsCount = conflicts.size(); int maxCheckLevel = -1; @@ -166,7 +169,7 @@ public class JavaMethodsConflictResolver implements PsiConflictResolver{ int index = 0; for (final CandidateInfo conflict : conflicts) { final MethodCandidateInfo method = (MethodCandidateInfo)conflict; - final int level = getCheckLevel(method); + final int level = checkAccessible ? getCheckAccessLevel(method) : getCheckStaticLevel(method); checkLevels[index++] = level; maxCheckLevel = Math.max(maxCheckLevel, level); } @@ -379,11 +382,14 @@ public class JavaMethodsConflictResolver implements PsiConflictResolver{ return level; } - private static int getCheckLevel(MethodCandidateInfo method){ - boolean visible = method.isAccessible();// && !method.myStaticProblem; + private static int getCheckAccessLevel(MethodCandidateInfo method){ + boolean visible = method.isAccessible(); + return visible ? 1 : 0; + } + + private static int getCheckStaticLevel(MethodCandidateInfo method){ boolean available = method.isStaticsScopeCorrect(); - return (visible ? 1 : 0) << 2 | - (available ? 1 : 0) << 1 | + return (available ? 1 : 0) << 1 | (method.getCurrentFileResolveScope() instanceof PsiImportStaticStatement ? 0 : 1); } diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/advHighlighting7/InstanceMemberNotAccessibleInStaticContext.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/advHighlighting7/InstanceMemberNotAccessibleInStaticContext.java new file mode 100644 index 000000000000..aa827bcea454 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/advHighlighting7/InstanceMemberNotAccessibleInStaticContext.java @@ -0,0 +1,11 @@ +class Foo { + public void foo() {} + + public static void foo(String... s){} +} + +class A { + { + Foo.foo(); + } +} diff --git a/java/java-tests/testSrc/com/intellij/codeInsight/daemon/LightAdvHighlightingJdk7Test.java b/java/java-tests/testSrc/com/intellij/codeInsight/daemon/LightAdvHighlightingJdk7Test.java index 53710582edd5..4b90e72ccf81 100644 --- a/java/java-tests/testSrc/com/intellij/codeInsight/daemon/LightAdvHighlightingJdk7Test.java +++ b/java/java-tests/testSrc/com/intellij/codeInsight/daemon/LightAdvHighlightingJdk7Test.java @@ -166,4 +166,5 @@ public class LightAdvHighlightingJdk7Test extends LightDaemonAnalyzerTestCase { public void testWrongArgsAndUnknownTypeParams() throws Exception { doTest(false, false); } public void testAmbiguousMethodCallIDEA97983() throws Exception { doTest(false, false); } public void testAmbiguousMethodCallIDEA100314() throws Exception { doTest(false, false); } + public void testInstanceMemberNotAccessibleInStaticContext() throws Exception { doTest(false, false); } } diff --git a/java/java-tests/testSrc/com/intellij/psi/resolve/ResolveMethodTest.java b/java/java-tests/testSrc/com/intellij/psi/resolve/ResolveMethodTest.java index 274483b52350..b45903c9f17f 100644 --- a/java/java-tests/testSrc/com/intellij/psi/resolve/ResolveMethodTest.java +++ b/java/java-tests/testSrc/com/intellij/psi/resolve/ResolveMethodTest.java @@ -204,7 +204,7 @@ public class ResolveMethodTest extends ResolveTestCase { PsiElement target = resolve(); assertTrue(target instanceof PsiMethod); PsiMethod method = (PsiMethod) target; - assertEquals(1, method.getParameterList().getParametersCount()); + assertEquals(0, method.getParameterList().getParametersCount()); } public void testClone() throws Exception{ From 6f89c562da03d22933da617365b48630c37dbf5d Mon Sep 17 00:00:00 2001 From: anna Date: Thu, 21 Feb 2013 15:20:35 +0100 Subject: [PATCH 012/354] plugin repository: forbid 2 || threads reading available plugins; disable refresh action when plugins are loading (IDEA-99902) --- .../com/intellij/ide/plugins/PluginManagerMain.java | 10 ++++++++-- .../src/com/intellij/ide/plugins/RepositoryHelper.java | 4 +--- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/platform/platform-impl/src/com/intellij/ide/plugins/PluginManagerMain.java b/platform/platform-impl/src/com/intellij/ide/plugins/PluginManagerMain.java index c5ca4c758e58..05f0fd3921c9 100644 --- a/platform/platform-impl/src/com/intellij/ide/plugins/PluginManagerMain.java +++ b/platform/platform-impl/src/com/intellij/ide/plugins/PluginManagerMain.java @@ -104,6 +104,7 @@ public abstract class PluginManagerMain implements Disposable { protected final MyPluginsFilter myFilter = new MyPluginsFilter(); protected PluginManagerUISettings myUISettings; private boolean myDisposed = false; + private boolean myBusy = false; public PluginManagerMain( PluginManagerUISettings uiSettings) { @@ -249,13 +250,12 @@ public abstract class PluginManagerMain implements Disposable { public void finished() { UIUtil.invokeLaterIfNeeded(new Runnable() { public void run() { + setDownloadStatus(false); if (list != null && errorMessages.isEmpty()) { modifyPluginsList(list); propagateUpdates(list); - setDownloadStatus(false); } else if (!errorMessages.isEmpty()) { - setDownloadStatus(false); if (0 == Messages.showOkCancelDialog( IdeBundle.message("error.list.of.plugins.was.not.loaded", StringUtil.join(errorMessages, ", ")), IdeBundle.message("title.plugins"), @@ -273,6 +273,7 @@ public abstract class PluginManagerMain implements Disposable { protected void setDownloadStatus(boolean status) { pluginTable.setPaintBusy(status); + myBusy = status; } protected void loadAvailablePlugins() { @@ -565,5 +566,10 @@ public abstract class PluginManagerMain implements Disposable { loadAvailablePlugins(); myFilter.setFilter(""); } + + @Override + public void update(AnActionEvent e) { + e.getPresentation().setEnabled(!myBusy); + } } } diff --git a/platform/platform-impl/src/com/intellij/ide/plugins/RepositoryHelper.java b/platform/platform-impl/src/com/intellij/ide/plugins/RepositoryHelper.java index d1ee94fb6c44..fa5ee62d5fb5 100644 --- a/platform/platform-impl/src/com/intellij/ide/plugins/RepositoryHelper.java +++ b/platform/platform-impl/src/com/intellij/ide/plugins/RepositoryHelper.java @@ -38,8 +38,6 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.net.HttpURLConnection; -import java.net.URL; -import java.net.URLConnection; import java.util.ArrayList; import java.util.zip.GZIPInputStream; @@ -127,7 +125,7 @@ public class RepositoryHelper { return temp; } - private static void readPluginsStream(InputStream is, RepositoryContentHandler handler, ProgressIndicator indicator, final String file) + private synchronized static void readPluginsStream(InputStream is, RepositoryContentHandler handler, ProgressIndicator indicator, final String file) throws SAXException, IOException, ParserConfigurationException, ProcessCanceledException { File temp = createLocalPluginsDescriptions(file); SAXParser parser = SAXParserFactory.newInstance().newSAXParser(); From d87c046eadc9c853e4a6e85fdf6872f47a1f5d7e Mon Sep 17 00:00:00 2001 From: "andrey.zaytsev" Date: Wed, 20 Feb 2013 22:21:45 +0400 Subject: [PATCH 013/354] IDEA-101456 Throwable at com.intellij.openapi.application.impl.ApplicationImpl.assertWriteAccessAllowed. also remove temporary breakpoint after hit when debug process stops --- .../debugger/ui/breakpoints/Breakpoint.java | 37 +++++++++++++------ .../xdebugger/impl/XDebugSessionImpl.java | 32 ++++++++++------ 2 files changed, 45 insertions(+), 24 deletions(-) diff --git a/java/debugger/impl/src/com/intellij/debugger/ui/breakpoints/Breakpoint.java b/java/debugger/impl/src/com/intellij/debugger/ui/breakpoints/Breakpoint.java index 58b1917b57a1..3d694e1dd28f 100644 --- a/java/debugger/impl/src/com/intellij/debugger/ui/breakpoints/Breakpoint.java +++ b/java/debugger/impl/src/com/intellij/debugger/ui/breakpoints/Breakpoint.java @@ -249,21 +249,34 @@ public abstract class Breakpoint extends FilteredRequestor implements ClassPrepa } } if (REMOVE_AFTER_HIT) { - debugProcess.addDebugProcessListener(new DebugProcessAdapter() { - @Override - public void resumed(SuspendContext suspendContext) { - DebuggerUIUtil.invokeOnEventDispatch(new Runnable() { - @Override - public void run() { - DebuggerManagerEx.getInstanceEx(myProject).getBreakpointManager().removeBreakpoint(Breakpoint.this); - } - }); - debugProcess.removeDebugProcessListener(this); - } - }); + handleTemporaryBreakpointHit(debugProcess); } } + private void handleTemporaryBreakpointHit(final DebugProcessImpl debugProcess) { + debugProcess.addDebugProcessListener(new DebugProcessAdapter() { + @Override + public void resumed(SuspendContext suspendContext) { + removeBreakpoint(); + } + + @Override + public void processDetached(DebugProcess process, boolean closedByUser) { + removeBreakpoint(); + } + + private void removeBreakpoint() { + DebuggerUIUtil.invokeOnEventDispatch(new Runnable() { + @Override + public void run() { + DebuggerManagerEx.getInstanceEx(myProject).getBreakpointManager().removeBreakpoint(Breakpoint.this); + } + }); + debugProcess.removeDebugProcessListener(this); + } + }); + } + public final void updateUI() { updateUI(EmptyRunnable.getInstance()); } diff --git a/platform/xdebugger-impl/src/com/intellij/xdebugger/impl/XDebugSessionImpl.java b/platform/xdebugger-impl/src/com/intellij/xdebugger/impl/XDebugSessionImpl.java index 41fab55e3864..4df444162c32 100644 --- a/platform/xdebugger-impl/src/com/intellij/xdebugger/impl/XDebugSessionImpl.java +++ b/platform/xdebugger-impl/src/com/intellij/xdebugger/impl/XDebugSessionImpl.java @@ -588,22 +588,30 @@ public class XDebugSessionImpl implements XDebugSession { positionReached(suspendContext); if (breakpoint instanceof XLineBreakpoint && ((XLineBreakpoint)breakpoint).isTemporary()) { - addSessionListener(new XDebugSessionAdapter() { - @Override - public void sessionResumed() { - DebuggerUIUtil.invokeOnEventDispatch(new Runnable() { - @Override - public void run() { - XDebuggerManager.getInstance(myProject).getBreakpointManager().removeBreakpoint(breakpoint); - } - }); - removeSessionListener(this); - } - }); + handleTemporaryBreakpointHit(breakpoint); } return true; } + private void handleTemporaryBreakpointHit(final XBreakpoint breakpoint) { + addSessionListener(new XDebugSessionAdapter() { + private void removeBreakpoint() { + XDebuggerUtil.getInstance().removeBreakpoint(myProject, breakpoint); + removeSessionListener(this); + } + + @Override + public void sessionResumed() { + removeBreakpoint(); + } + + @Override + public void sessionStopped() { + removeBreakpoint(); + } + }); + } + private void processDependencies(final XBreakpoint breakpoint) { XDependentBreakpointManager dependentBreakpointManager = myDebuggerManager.getBreakpointManager().getDependentBreakpointManager(); if (!dependentBreakpointManager.isMasterOrSlave(breakpoint)) return; From c1b2ff060e8d58d6bdd6d7c128e118254843dfbb Mon Sep 17 00:00:00 2001 From: Oleg Sukhodolsky Date: Thu, 21 Feb 2013 19:14:03 +0400 Subject: [PATCH 014/354] RUBY-13089: ruby specific options moved to ruby extansion for Darcula --- colorSchemes/src/colorSchemes/Darcula.xml | 186 ---------------------- 1 file changed, 186 deletions(-) diff --git a/colorSchemes/src/colorSchemes/Darcula.xml b/colorSchemes/src/colorSchemes/Darcula.xml index 8c5f570f8a5c..5546bed865e6 100644 --- a/colorSchemes/src/colorSchemes/Darcula.xml +++ b/colorSchemes/src/colorSchemes/Darcula.xml @@ -1143,192 +1143,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -