diff --git a/java/java-analysis-impl/src/com/intellij/codeInsight/InferredAnnotationsManagerImpl.java b/java/java-analysis-impl/src/com/intellij/codeInsight/InferredAnnotationsManagerImpl.java index 353ec8701f6e..77a227d49847 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInsight/InferredAnnotationsManagerImpl.java +++ b/java/java-analysis-impl/src/com/intellij/codeInsight/InferredAnnotationsManagerImpl.java @@ -17,12 +17,12 @@ package com.intellij.codeInsight; import com.intellij.codeInspection.bytecodeAnalysis.ProjectBytecodeAnalysis; import com.intellij.codeInspection.dataFlow.ContractInference; +import com.intellij.codeInspection.dataFlow.HardcodedContracts; import com.intellij.codeInspection.dataFlow.MethodContract; import com.intellij.codeInspection.dataFlow.PurityInference; +import com.intellij.openapi.project.Project; import com.intellij.openapi.util.text.StringUtil; -import com.intellij.psi.PsiAnnotation; -import com.intellij.psi.PsiMethod; -import com.intellij.psi.PsiModifierListOwner; +import com.intellij.psi.*; import com.intellij.psi.util.PsiUtil; import com.intellij.util.containers.ContainerUtil; import org.jetbrains.annotations.NotNull; @@ -33,14 +33,29 @@ import java.util.List; import static com.intellij.codeInspection.dataFlow.ControlFlowAnalyzer.ORG_JETBRAINS_ANNOTATIONS_CONTRACT; public class InferredAnnotationsManagerImpl extends InferredAnnotationsManager { + private final Project myProject; + + public InferredAnnotationsManagerImpl(Project project) { + myProject = project; + } @Nullable @Override public PsiAnnotation findInferredAnnotation(@NotNull PsiModifierListOwner listOwner, @NotNull String annotationFQN) { listOwner = BaseExternalAnnotationsManager.preferCompiledElement(listOwner); - PsiAnnotation fromBytecode = ProjectBytecodeAnalysis.getInstance(listOwner.getProject()).findInferredAnnotation(listOwner, annotationFQN); - if (fromBytecode != null) { - return fromBytecode; + + if (ORG_JETBRAINS_ANNOTATIONS_CONTRACT.equals(annotationFQN) && listOwner instanceof PsiMethod) { + PsiAnnotation anno = getHardcodedContractAnnotation((PsiMethod)listOwner); + if (anno != null) { + return anno; + } + } + + if (!ignoreBytecodeInference(listOwner, annotationFQN)) { + PsiAnnotation fromBytecode = ProjectBytecodeAnalysis.getInstance(myProject).findInferredAnnotation(listOwner, annotationFQN); + if (fromBytecode != null) { + return fromBytecode; + } } if (ORG_JETBRAINS_ANNOTATIONS_CONTRACT.equals(annotationFQN) && canHaveContract(listOwner)) { @@ -50,10 +65,34 @@ public class InferredAnnotationsManagerImpl extends InferredAnnotationsManager { return null; } + private PsiAnnotation getHardcodedContractAnnotation(PsiMethod listOwner) { + List contracts = HardcodedContracts.getHardcodedContracts(listOwner, null); + return contracts.isEmpty() ? null : createContractAnnotation(contracts, true); + } + + private static boolean ignoreBytecodeInference(PsiModifierListOwner owner, String annotationFQN) { + if (ORG_JETBRAINS_ANNOTATIONS_CONTRACT.equals(annotationFQN) && hasHardcodedContracts(owner)) { + return true; + } + if (AnnotationUtil.NOT_NULL.equals(annotationFQN) && + owner instanceof PsiParameter && owner.getParent() != null && + hasHardcodedContracts(owner.getParent().getParent())) { + return true; + } + return false; + } + + private static boolean hasHardcodedContracts(PsiElement owner) { + return owner instanceof PsiMethod && !HardcodedContracts.getHardcodedContracts((PsiMethod)owner, null).isEmpty(); + } + @Nullable - private static PsiAnnotation getInferredContractAnnotation(PsiMethod method) { - List contracts = ContractInference.inferContracts((PsiMethod)method); - boolean pure = PurityInference.inferPurity(method); + private PsiAnnotation getInferredContractAnnotation(PsiMethod method) { + return createContractAnnotation(ContractInference.inferContracts(method), PurityInference.inferPurity(method)); + } + + @Nullable + private PsiAnnotation createContractAnnotation(List contracts, boolean pure) { final String attrs; if (!contracts.isEmpty() && pure) { attrs = "value = " + "\"" + StringUtil.join(contracts, "; ") + "\"; pure = true"; @@ -64,7 +103,7 @@ public class InferredAnnotationsManagerImpl extends InferredAnnotationsManager { } else { return null; } - return ProjectBytecodeAnalysis.getInstance(method.getProject()).createContractAnnotation(attrs); + return ProjectBytecodeAnalysis.getInstance(myProject).createContractAnnotation(attrs); } private static boolean canHaveContract(PsiModifierListOwner listOwner) { @@ -76,15 +115,18 @@ public class InferredAnnotationsManagerImpl extends InferredAnnotationsManager { public PsiAnnotation[] findInferredAnnotations(@NotNull PsiModifierListOwner listOwner) { listOwner = BaseExternalAnnotationsManager.preferCompiledElement(listOwner); List result = ContainerUtil.newArrayList(); - PsiAnnotation[] fromBytecode = ProjectBytecodeAnalysis.getInstance(listOwner.getProject()).findInferredAnnotations(listOwner); + PsiAnnotation[] fromBytecode = ProjectBytecodeAnalysis.getInstance(myProject).findInferredAnnotations(listOwner); for (PsiAnnotation annotation : fromBytecode) { - if (!ORG_JETBRAINS_ANNOTATIONS_CONTRACT.equals(annotation.getQualifiedName()) || canHaveContract(listOwner)) { - result.add(annotation); + if (!ignoreBytecodeInference(listOwner, annotation.getQualifiedName())) { + if (!ORG_JETBRAINS_ANNOTATIONS_CONTRACT.equals(annotation.getQualifiedName()) || canHaveContract(listOwner)) { + result.add(annotation); + } } } if (canHaveContract(listOwner)) { - ContainerUtil.addIfNotNull(result, getInferredContractAnnotation((PsiMethod)listOwner)); + PsiAnnotation hardcoded = getHardcodedContractAnnotation((PsiMethod)listOwner); + ContainerUtil.addIfNotNull(result, hardcoded != null ? hardcoded : getInferredContractAnnotation((PsiMethod)listOwner)); } return result.isEmpty() ? PsiAnnotation.EMPTY_ARRAY : result.toArray(new PsiAnnotation[result.size()]); diff --git a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/PostHighlightingPass.java b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/PostHighlightingPass.java index 06b11a3d364d..23fe242e812c 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/PostHighlightingPass.java +++ b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/PostHighlightingPass.java @@ -149,7 +149,8 @@ public class PostHighlightingPass extends ProgressableTextEditorHighlightingPass myInLibrary = fileIndex.isInLibraryClasses(virtualFile) || fileIndex.isInLibrarySource(virtualFile); myRefCountHolder = RefCountHolder.endUsing(myFile, progress); - if (myRefCountHolder == null || !myRefCountHolder.retrieveUnusedReferencesInfo(progress, new Runnable() { + boolean r = true; + if (myRefCountHolder == null || !(r=myRefCountHolder.retrieveUnusedReferencesInfo(progress, new Runnable() { @Override public void run() { boolean errorFound = collectHighlights(elementSet, highlights, progress); @@ -158,8 +159,9 @@ public class PostHighlightingPass extends ProgressableTextEditorHighlightingPass fileStatusMap.setErrorFoundFlag(myDocument, true); } } - })) { + }))) { // we must be sure GHP will restart + FileStatusMap.log("myRefCountHolder: ", myRefCountHolder, "; retrieved: ", r); fileStatusMap.markFileScopeDirty(getDocument(), Pass.UPDATE_ALL); GeneralHighlightingPass.cancelAndRestartDaemonLater(progress, myProject, this); } diff --git a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/RefCountHolder.java b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/RefCountHolder.java index 7bb318192ba9..0b0511478ed9 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/RefCountHolder.java +++ b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/RefCountHolder.java @@ -35,7 +35,10 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.lang.ref.SoftReference; -import java.util.*; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.Map; import java.util.concurrent.atomic.AtomicReference; public class RefCountHolder { @@ -328,10 +331,11 @@ public class RefCountHolder { return true; } - private static void log(@NonNls Object... s) { - //System.err.println("RFC: "+ Arrays.asList(s)); + private static void log(@NonNls @NotNull Object... info) { + FileStatusMap.log(info); } + public boolean retrieveUnusedReferencesInfo(@NotNull ProgressIndicator indicator, @NotNull Runnable analyze) { ProgressIndicator old = myState.get(); if (!myState.compareAndSet(READY, indicator)) { @@ -340,7 +344,9 @@ public class RefCountHolder { } log("r: changed ", old, "->", indicator); try { - if (analyzedUnder != indicator) { + ProgressIndicator under = analyzedUnder; + if (under != indicator) { + log("r: analyzed under ", under, "->", indicator); return false; } analyze.run(); @@ -350,6 +356,6 @@ public class RefCountHolder { assert set : myState.get(); log("r: changed back ", indicator, "->", READY); } - return true; + return true; } } diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DataFlowInspectionBase.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DataFlowInspectionBase.java index d66d63bf85c3..6572fd309b78 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DataFlowInspectionBase.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DataFlowInspectionBase.java @@ -64,6 +64,7 @@ public class DataFlowInspectionBase extends BaseJavaBatchLocalInspectionTool { @NonNls private static final String SHORT_NAME = "ConstantConditions"; public boolean SUGGEST_NULLABLE_ANNOTATIONS = false; public boolean DONT_REPORT_TRUE_ASSERT_STATEMENTS = false; + public boolean TREAT_UNKNOWN_MEMBERS_AS_NULLABLE = false; public boolean IGNORE_ASSERT_STATEMENTS = false; public boolean REPORT_CONSTANT_REFERENCE_VALUES = true; @@ -82,6 +83,9 @@ public class DataFlowInspectionBase extends BaseJavaBatchLocalInspectionTool { if (!REPORT_CONSTANT_REFERENCE_VALUES) { node.addContent(new Element("option").setAttribute("name", "REPORT_CONSTANT_REFERENCE_VALUES").setAttribute("value", "false")); } + if (TREAT_UNKNOWN_MEMBERS_AS_NULLABLE) { + node.addContent(new Element("option").setAttribute("name", "TREAT_UNKNOWN_MEMBERS_AS_NULLABLE").setAttribute("value", "true")); + } } @Override @@ -121,7 +125,7 @@ public class DataFlowInspectionBase extends BaseJavaBatchLocalInspectionTool { PsiClass containingClass = PsiTreeUtil.getParentOfType(scope, PsiClass.class); if (containingClass != null && PsiUtil.isLocalOrAnonymousClass(containingClass)) return; - final StandardDataFlowRunner dfaRunner = new StandardDataFlowRunner(scope) { + final StandardDataFlowRunner dfaRunner = new StandardDataFlowRunner(scope, TREAT_UNKNOWN_MEMBERS_AS_NULLABLE) { @Override protected boolean shouldCheckTimeLimit() { if (!onTheFly) return false; diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DataFlowRunner.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DataFlowRunner.java index 07e5fcc5480d..10e0a0f4acd3 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DataFlowRunner.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DataFlowRunner.java @@ -57,13 +57,17 @@ public class DataFlowRunner { public static final int MAX_STATES_PER_BRANCH = 300; protected DataFlowRunner(PsiElement block) { + this(block, false); + } + + protected DataFlowRunner(PsiElement block, boolean unknownMembersAreNullable) { PsiElement parentConstructor = PsiTreeUtil.findFirstParent(block, new Condition() { @Override public boolean value(PsiElement psiElement) { return psiElement instanceof PsiMethod && ((PsiMethod)psiElement).isConstructor(); } }); - myValueFactory = new DfaValueFactory(parentConstructor == null); + myValueFactory = new DfaValueFactory(parentConstructor == null, unknownMembersAreNullable); } public DfaValueFactory getFactory() { diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/HardcodedContracts.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/HardcodedContracts.java index 7e77dc281c1d..97342a027799 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/HardcodedContracts.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/HardcodedContracts.java @@ -15,9 +15,13 @@ */ package com.intellij.codeInspection.dataFlow; -import com.intellij.psi.*; +import com.intellij.psi.PsiClass; +import com.intellij.psi.PsiExpression; +import com.intellij.psi.PsiMethod; +import com.intellij.psi.PsiMethodCallExpression; import com.siyeh.ig.psiutils.ExpressionUtils; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import java.util.Collections; import java.util.List; @@ -28,8 +32,8 @@ import static com.intellij.codeInspection.dataFlow.MethodContract.createConstrai /** * @author peter */ -class HardcodedContracts { - static List getHardcodedContracts(@NotNull PsiMethod method, @NotNull PsiMethodCallExpression call) { +public class HardcodedContracts { + public static List getHardcodedContracts(@NotNull PsiMethod method, @Nullable PsiMethodCallExpression call) { PsiClass owner = method.getContainingClass(); if (owner == null) return Collections.emptyList(); @@ -49,6 +53,13 @@ class HardcodedContracts { return Collections.singletonList(new MethodContract(constraints, THROW_EXCEPTION)); } } + else if ("java.util.Objects".equals(className)) { + if ("requireNonNull".equals(methodName) && paramCount > 0) { + MethodContract.ValueConstraint[] constraints = createConstraintArray(paramCount); + constraints[0] = NULL_VALUE; + return Collections.singletonList(new MethodContract(constraints, THROW_EXCEPTION)); + } + } else if ("junit.framework.Assert".equals(className) || "org.junit.Assert".equals(className) || "junit.framework.TestCase".equals(className) || @@ -82,8 +93,8 @@ class HardcodedContracts { } private static List handleTestFrameworks(int paramCount, String className, String methodName, - @NotNull PsiMethodCallExpression call) { - if ("assertThat".equals(methodName)) { + @Nullable PsiMethodCallExpression call) { + if ("assertThat".equals(methodName) && call != null) { PsiExpression[] args = call.getArgumentList().getExpressions(); if (args.length == paramCount) { for (int i = 1; i < args.length; i++) { diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/PurityInference.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/PurityInference.java index 29f530aba0f6..7686cf21855c 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/PurityInference.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/PurityInference.java @@ -34,7 +34,7 @@ import java.util.List; public class PurityInference { public static boolean inferPurity(@NotNull final PsiMethod method) { - if (method instanceof PsiCompiledElement || method.getReturnType() == PsiType.VOID || method.getBody() == null) { + if (method instanceof PsiCompiledElement || method.getReturnType() == PsiType.VOID || method.getBody() == null || method.isConstructor()) { return false; } diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/StandardDataFlowRunner.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/StandardDataFlowRunner.java index 2652c6ff19f6..68e881c97f14 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/StandardDataFlowRunner.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/StandardDataFlowRunner.java @@ -41,7 +41,10 @@ public class StandardDataFlowRunner extends DataFlowRunner { private boolean myIsInMethod = false; public StandardDataFlowRunner(PsiElement block) { - super(block); + this (block, false); + } + public StandardDataFlowRunner(PsiElement block, boolean unknownMembersAreNullable) { + super(block, unknownMembersAreNullable); } @Override diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/StandardInstructionVisitor.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/StandardInstructionVisitor.java index 96102b53f5d4..57ace52e9b10 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/StandardInstructionVisitor.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/StandardInstructionVisitor.java @@ -343,7 +343,11 @@ public class StandardInstructionVisitor extends InstructionVisitor { } if (type != null && (type instanceof PsiClassType || type.getArrayDimensions() > 0)) { - return factory.createTypeValue(type, myReturnTypeNullability.get(instruction)); + Nullness nullability = myReturnTypeNullability.get(instruction); + if (nullability == Nullness.UNKNOWN && factory.isUnknownMembersAreNullable()) { + nullability = Nullness.NULLABLE; + } + return factory.createTypeValue(type, nullability); } return DfaUnknownValue.getInstance(); } diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/value/DfaValueFactory.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/value/DfaValueFactory.java index da1d2d952634..4a260d7c827b 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/value/DfaValueFactory.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/value/DfaValueFactory.java @@ -42,9 +42,11 @@ public class DfaValueFactory { private final Map, Boolean> myConvertibleCache = ContainerUtil.newHashMap(); private final Map myDfaTypes = ContainerUtil.newHashMap(); private final boolean myHonorFieldInitializers; + private final boolean myUnknownMembersAreNullable; - public DfaValueFactory(boolean honorFieldInitializers) { + public DfaValueFactory(boolean honorFieldInitializers, boolean unknownMembersAreNullable) { myHonorFieldInitializers = honorFieldInitializers; + myUnknownMembersAreNullable = unknownMembersAreNullable; myValues.add(null); myVarFactory = new DfaVariableValue.Factory(this); myConstFactory = new DfaConstValue.Factory(this); @@ -58,6 +60,10 @@ public class DfaValueFactory { return myHonorFieldInitializers; } + public boolean isUnknownMembersAreNullable() { + return myUnknownMembersAreNullable; + } + public DfaValue createTypeValue(@Nullable PsiType type, Nullness nullability) { type = TypeConversionUtil.erasure(type); if (type == null) return DfaUnknownValue.getInstance(); diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/value/DfaVariableValue.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/value/DfaVariableValue.java index 840103f5c993..76d0be269059 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/value/DfaVariableValue.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/value/DfaVariableValue.java @@ -165,6 +165,8 @@ public class DfaVariableValue extends DfaValue { return nullability; } + Nullness defaultNullability = var instanceof PsiMember && myFactory.isUnknownMembersAreNullable() ? Nullness.NULLABLE : Nullness.UNKNOWN; + if (var instanceof PsiParameter && var.getParent() instanceof PsiForeachStatement) { PsiExpression iteratedValue = ((PsiForeachStatement)var.getParent()).getIteratedValue(); if (iteratedValue != null) { @@ -178,7 +180,7 @@ public class DfaVariableValue extends DfaValue { if (var instanceof PsiField && DfaPsiUtil.isFinalField((PsiVariable)var) && myFactory.isHonorFieldInitializers()) { List initializers = DfaPsiUtil.findAllConstructorInitializers((PsiField)var); if (initializers.isEmpty()) { - return Nullness.UNKNOWN; + return defaultNullability; } boolean hasUnknowns = false; @@ -204,13 +206,13 @@ public class DfaVariableValue extends DfaValue { if (DfaPsiUtil.isInitializedNotNull((PsiField)var)) { return Nullness.NOT_NULL; } - return Nullness.UNKNOWN; + return defaultNullability; } return Nullness.NOT_NULL; } - return Nullness.UNKNOWN; + return defaultNullability; } public boolean isFlushableByCalls() { 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 73d8848d4d17..e383b6a4e89a 100644 --- a/java/java-impl/src/com/intellij/codeInspection/dataFlow/DataFlowInspection.java +++ b/java/java-impl/src/com/intellij/codeInspection/dataFlow/DataFlowInspection.java @@ -68,6 +68,7 @@ public class DataFlowInspection extends DataFlowInspectionBase { private final JCheckBox myReportConstantReferences; private final JCheckBox mySuggestNullables; private final JCheckBox myDontReportTrueAsserts; + private final JCheckBox myTreatUnknownMembersAsNullable; private OptionsPanel() { super(new GridBagLayout()); @@ -116,6 +117,15 @@ public class DataFlowInspection extends DataFlowInspectionBase { } }); + myTreatUnknownMembersAsNullable = new JCheckBox("Treat non-annotated methods and fields as @Nullable"); + myTreatUnknownMembersAsNullable.setSelected(TREAT_UNKNOWN_MEMBERS_AS_NULLABLE); + myTreatUnknownMembersAsNullable.getModel().addChangeListener(new ChangeListener() { + @Override + public void stateChanged(ChangeEvent e) { + TREAT_UNKNOWN_MEMBERS_AS_NULLABLE = myTreatUnknownMembersAsNullable.isSelected(); + } + }); + gc.insets = new Insets(0, 0, 0, 0); gc.gridy = 0; add(mySuggestNullables, gc); @@ -167,6 +177,9 @@ public class DataFlowInspection extends DataFlowInspectionBase { gc.gridy++; add(myReportConstantReferences, gc); + + gc.gridy++; + add(myTreatUnknownMembersAsNullable, gc); } } diff --git a/java/java-tests/testData/codeInspection/bytecodeAnalysis/annotations/java/lang/annotations.xml b/java/java-tests/testData/codeInspection/bytecodeAnalysis/annotations/java/lang/annotations.xml index 00ee3b5c3075..e8f10ef4dace 100644 --- a/java/java-tests/testData/codeInspection/bytecodeAnalysis/annotations/java/lang/annotations.xml +++ b/java/java-tests/testData/codeInspection/bytecodeAnalysis/annotations/java/lang/annotations.xml @@ -1557,6 +1557,11 @@ + + + + + diff --git a/java/java-tests/testData/inspection/dataFlow/fixture/ParanoidMode.java b/java/java-tests/testData/inspection/dataFlow/fixture/ParanoidMode.java new file mode 100644 index 000000000000..96448a4d0d95 --- /dev/null +++ b/java/java-tests/testData/inspection/dataFlow/fixture/ParanoidMode.java @@ -0,0 +1,22 @@ +import org.jetbrains.annotations.NotNull; + +class Test { + Object o; + + void field() { + o.hashCode(); + } + + void callUnknownMethod() { + unknownObject().hashCode(); + } + + void callNotNullMethod() { + knownObject().hashCode(); + } + + native Object unknownObject(); + + @NotNull + native Object knownObject(); +} \ No newline at end of file diff --git a/java/java-tests/testSrc/com/intellij/codeInsight/daemon/LossyEncodingTest.java b/java/java-tests/testSrc/com/intellij/codeInsight/daemon/LossyEncodingTest.java index 903905eb5c3a..6cf226623f92 100644 --- a/java/java-tests/testSrc/com/intellij/codeInsight/daemon/LossyEncodingTest.java +++ b/java/java-tests/testSrc/com/intellij/codeInsight/daemon/LossyEncodingTest.java @@ -54,6 +54,12 @@ public class LossyEncodingTest extends DaemonAnalyzerTestCase { return new LocalInspectionTool[]{new LossyEncodingInspection()}; } + @Override + protected void tearDown() throws Exception { + UIUtil.dispatchAllInvocationEvents(); // invokeLater() in EncodingProjectManagerImpl.reloadAllFilesUnder() + super.tearDown(); + } + public void testText() throws Exception { doTest("Text.txt"); Charset ascii = CharsetToolkit.forName("US-ASCII"); @@ -115,6 +121,7 @@ public class LossyEncodingTest extends DaemonAnalyzerTestCase { private void doTest(@NonNls String filePath) throws Exception { doTest(BASE_PATH + "/" + filePath, true, false); + UIUtil.dispatchAllInvocationEvents(); } public void testNativeEncoding() throws Exception { diff --git a/java/java-tests/testSrc/com/intellij/codeInspection/DataFlowInspectionTest.java b/java/java-tests/testSrc/com/intellij/codeInspection/DataFlowInspectionTest.java index 490ded30353c..92465cf858bc 100644 --- a/java/java-tests/testSrc/com/intellij/codeInspection/DataFlowInspectionTest.java +++ b/java/java-tests/testSrc/com/intellij/codeInspection/DataFlowInspectionTest.java @@ -137,6 +137,13 @@ public class DataFlowInspectionTest extends LightCodeInsightFixtureTestCase { myFixture.testHighlighting(true, false, true, getTestName(false) + ".java"); } + public void testParanoidMode() { + final DataFlowInspection inspection = new DataFlowInspection(); + inspection.TREAT_UNKNOWN_MEMBERS_AS_NULLABLE = true; + myFixture.enableInspections(inspection); + myFixture.testHighlighting(true, false, true, getTestName(false) + ".java"); + } + public void testReportConstantReferences() { doTestReportConstantReferences(); myFixture.launchAction(myFixture.findSingleIntention("Replace with 'null'")); diff --git a/java/java-tests/testSrc/com/intellij/codeInspection/PurityInferenceFromSourceTest.groovy b/java/java-tests/testSrc/com/intellij/codeInspection/PurityInferenceFromSourceTest.groovy index c21e09c00a11..a77607314101 100644 --- a/java/java-tests/testSrc/com/intellij/codeInspection/PurityInferenceFromSourceTest.groovy +++ b/java/java-tests/testSrc/com/intellij/codeInspection/PurityInferenceFromSourceTest.groovy @@ -129,6 +129,13 @@ int smthPure() { return 3; } """ } + public void "test don't analyze constructors"() { + assertPure false, """ +public Foo() { +} +""" + } + private void assertPure(boolean expected, String classBody) { def clazz = myFixture.addClass("final class Foo { $classBody }") assert expected == PurityInference.inferPurity(clazz.methods[0]) diff --git a/platform/analysis-impl/src/com/intellij/codeInsight/daemon/impl/DaemonProgressIndicator.java b/platform/analysis-impl/src/com/intellij/codeInsight/daemon/impl/DaemonProgressIndicator.java index 9a3e52cc750b..5c23a2dd7247 100644 --- a/platform/analysis-impl/src/com/intellij/codeInsight/daemon/impl/DaemonProgressIndicator.java +++ b/platform/analysis-impl/src/com/intellij/codeInsight/daemon/impl/DaemonProgressIndicator.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2013 JetBrains s.r.o. + * Copyright 2000-2014 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. @@ -75,4 +75,9 @@ public class DaemonProgressIndicator extends AbstractProgressIndicatorBase { public final int hashCode() { return super.hashCode(); } + + @Override + public String toString() { + return super.toString() + (debug ? "; "+myTraceableDisposable.getStackTrace()+"\n;" : ""); + } } diff --git a/platform/analysis-impl/src/com/intellij/codeInsight/daemon/impl/FileStatusMap.java b/platform/analysis-impl/src/com/intellij/codeInsight/daemon/impl/FileStatusMap.java index 0bb406f23545..0e74a56657d6 100644 --- a/platform/analysis-impl/src/com/intellij/codeInsight/daemon/impl/FileStatusMap.java +++ b/platform/analysis-impl/src/com/intellij/codeInsight/daemon/impl/FileStatusMap.java @@ -21,6 +21,7 @@ import com.intellij.codeHighlighting.Pass; import com.intellij.codeHighlighting.TextEditorHighlightingPassRegistrar; import com.intellij.codeInsight.daemon.ProblemHighlightFilter; import com.intellij.openapi.Disposable; +import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.editor.Document; import com.intellij.openapi.editor.Editor; @@ -28,8 +29,10 @@ import com.intellij.openapi.editor.RangeMarker; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Key; import com.intellij.openapi.util.TextRange; +import com.intellij.openapi.util.text.StringUtil; import com.intellij.psi.PsiDocumentManager; import com.intellij.psi.PsiFile; +import com.intellij.util.ConcurrencyUtil; import com.intellij.util.containers.WeakHashMap; import gnu.trove.TIntObjectHashMap; import gnu.trove.TIntObjectProcedure; @@ -39,7 +42,10 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.TestOnly; +import java.util.Arrays; import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; public class FileStatusMap implements Disposable { private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.daemon.impl.FileStatusMap"); @@ -367,4 +373,27 @@ public class FileStatusMap implements Disposable { } }; + // logging + private static final ConcurrentMap threads = new ConcurrentHashMap(); + private static int getThreadNum() { + return ConcurrencyUtil.cacheOrGet(threads, Thread.currentThread(), threads.size()); + } + private static final StringBuffer log = new StringBuffer(); + private static final boolean IN_TESTS = ApplicationManager.getApplication().isUnitTestMode(); + static void log(@NonNls Object... info) { + if (IN_TESTS) { + if (log.length() > 10000) { + log.replace(0, log.length()-5000, ""); + } + String s = StringUtil.repeatSymbol(' ', getThreadNum() * 4) + Arrays.asList(info) + "\n"; + log.append(s); + } + } + @NotNull + public static String getAndClearLog() { + String l = log.toString(); + log.setLength(0); + return l; + } + } diff --git a/platform/analysis-impl/src/com/intellij/codeInsight/daemon/impl/GeneralHighlightingPass.java b/platform/analysis-impl/src/com/intellij/codeInsight/daemon/impl/GeneralHighlightingPass.java index eb082a163875..9b17c0adbe9e 100644 --- a/platform/analysis-impl/src/com/intellij/codeInsight/daemon/impl/GeneralHighlightingPass.java +++ b/platform/analysis-impl/src/com/intellij/codeInsight/daemon/impl/GeneralHighlightingPass.java @@ -417,7 +417,7 @@ public class GeneralHighlightingPass extends ProgressableTextEditorHighlightingP @Override public void run() { Application application = ApplicationManager.getApplication(); - if (!project.isDisposed() && !application.isDisposed()) { + if (!project.isDisposed() && !application.isDisposed() && !application.isUnitTestMode()) { ApplicationManager.getApplication().invokeLater(new Runnable() { @Override public void run() { diff --git a/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/DaemonCodeAnalyzerImpl.java b/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/DaemonCodeAnalyzerImpl.java index 0d02b32baed4..f9c025bb964a 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/DaemonCodeAnalyzerImpl.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/DaemonCodeAnalyzerImpl.java @@ -46,6 +46,7 @@ import com.intellij.openapi.extensions.Extensions; import com.intellij.openapi.fileEditor.FileEditor; import com.intellij.openapi.fileEditor.FileEditorManager; import com.intellij.openapi.fileEditor.TextEditor; +import com.intellij.openapi.fileEditor.ex.FileEditorManagerEx; import com.intellij.openapi.fileEditor.impl.text.TextEditorProvider; import com.intellij.openapi.fileTypes.FileType; import com.intellij.openapi.fileTypes.FileTypeManager; @@ -283,7 +284,7 @@ public class DaemonCodeAnalyzerImpl extends DaemonCodeAnalyzerEx implements JDOM if (application.isWriteAccessAllowed()) { throw new AssertionError("Must not start highlighting from within write action, or deadlock is imminent"); } - + DaemonProgressIndicator.setDebug(true); ((FileTypeManagerImpl)FileTypeManager.getInstance()).drainReDetectQueue(); // pump first so that queued event do not interfere UIUtil.dispatchAllInvocationEvents(); @@ -297,6 +298,7 @@ public class DaemonCodeAnalyzerImpl extends DaemonCodeAnalyzerEx implements JDOM Project project = file.getProject(); setUpdateByTimerEnabled(false); + FileStatusMap.getAndClearLog(); FileStatusMap fileStatusMap = getFileStatusMap(); fileStatusMap.allowDirt(canChangeDocument); @@ -342,8 +344,15 @@ public class DaemonCodeAnalyzerImpl extends DaemonCodeAnalyzerEx implements JDOM return getHighlights(document, null, project); } finally { + DaemonProgressIndicator.setDebug(false); + String log = FileStatusMap.getAndClearLog(); fileStatusMap.allowDirt(true); - waitForTermination(); + try { + waitForTermination(); + } + catch (Throwable e) { + LOG.error(log, e); + } } } @@ -741,7 +750,7 @@ public class DaemonCodeAnalyzerImpl extends DaemonCodeAnalyzerEx implements JDOM if (ApplicationManager.getApplication().isWriteAccessAllowed()) { // makes no sense to start from within write action, will cancel anyway - // we'll restart when write action finish + // we'll restart when the write action finish return; } if (documentManager.hasUncommitedDocuments()) { @@ -811,28 +820,30 @@ public class DaemonCodeAnalyzerImpl extends DaemonCodeAnalyzerEx implements JDOM // Editors in modal context List editors = getActiveEditors(); - Collection activeFileEditors = new THashSet(editors.size()); + Collection activeTextEditors = new THashSet(editors.size()); for (Editor editor : editors) { TextEditor textEditor = TextEditorProvider.getInstance().getTextEditor(editor); - activeFileEditors.add(textEditor); + activeTextEditors.add(textEditor); } if (ApplicationManager.getApplication().getCurrentModalityState() != ModalityState.NON_MODAL) { - return activeFileEditors; + return activeTextEditors; } // Editors in tabs. Collection result = new THashSet(); - Collection documents = new THashSet(activeFileEditors.size()); + Collection files = new THashSet(activeTextEditors.size()); final FileEditor[] tabEditors = FileEditorManager.getInstance(myProject).getSelectedEditors(); for (FileEditor tabEditor : tabEditors) { - if (tabEditor instanceof TextEditor) { - documents.add(((TextEditor)tabEditor).getEditor().getDocument()); + VirtualFile file = ((FileEditorManagerEx)FileEditorManager.getInstance(myProject)).getFile(tabEditor); + if (file != null) { + files.add(file); } result.add(tabEditor); } // do not duplicate documents - for (FileEditor fileEditor : activeFileEditors) { - if (fileEditor instanceof TextEditor && documents.contains(((TextEditor)fileEditor).getEditor().getDocument())) continue; + for (FileEditor fileEditor : activeTextEditors) { + VirtualFile file = ((FileEditorManagerEx)FileEditorManager.getInstance(myProject)).getFile(fileEditor); + if (file != null && files.contains(file)) continue; result.add(fileEditor); } return result; diff --git a/platform/lang-impl/src/com/intellij/codeInsight/template/impl/LiveTemplateLookupElement.java b/platform/lang-impl/src/com/intellij/codeInsight/template/impl/LiveTemplateLookupElement.java index d01de9eed5b2..a1624e923273 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/template/impl/LiveTemplateLookupElement.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/template/impl/LiveTemplateLookupElement.java @@ -62,7 +62,9 @@ abstract public class LiveTemplateLookupElement extends LookupElement { if (shortcut == TemplateSettings.DEFAULT_CHAR) { shortcut = TemplateSettings.getInstance().getDefaultShortcutChar(); } - presentation.setTypeText(" [" + KeyEvent.getKeyText(shortcut) + "] "); + if (shortcut != TemplateSettings.CUSTOM_CHAR) { + presentation.setTypeText(" [" + KeyEvent.getKeyText(shortcut) + "] "); + } } if (StringUtil.isNotEmpty(myDescription)) { presentation.setTailText(" (" + myDescription + ")", true); diff --git a/platform/lang-impl/src/com/intellij/codeInsight/template/impl/TemplateListPanel.java b/platform/lang-impl/src/com/intellij/codeInsight/template/impl/TemplateListPanel.java index 1c0b72a5a454..a1a60ad9d39e 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/template/impl/TemplateListPanel.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/template/impl/TemplateListPanel.java @@ -27,8 +27,11 @@ import com.intellij.openapi.Disposable; import com.intellij.openapi.actionSystem.*; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.keymap.KeymapUtil; +import com.intellij.openapi.keymap.impl.ui.KeymapPanel; import com.intellij.openapi.options.ConfigurationException; import com.intellij.openapi.options.SchemesManager; +import com.intellij.openapi.options.ex.Settings; import com.intellij.openapi.project.DumbAwareAction; import com.intellij.openapi.ui.*; import com.intellij.openapi.ui.popup.JBPopupFactory; @@ -50,13 +53,14 @@ import org.jetbrains.annotations.Nullable; import javax.swing.*; import javax.swing.border.EmptyBorder; +import javax.swing.event.HyperlinkEvent; import javax.swing.event.TreeSelectionEvent; import javax.swing.event.TreeSelectionListener; import javax.swing.tree.*; import java.awt.*; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -import java.awt.event.KeyEvent; +import java.awt.event.*; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; import java.util.*; import java.util.List; @@ -80,9 +84,11 @@ public class TemplateListPanel extends JPanel implements Disposable { private CheckboxTree myTree; private final List myTemplateGroups = new ArrayList(); private JComboBox myExpandByCombo; + private HyperlinkLabel myOpenKeymapLabel; private static final String SPACE = CodeInsightBundle.message("template.shortcut.space"); private static final String TAB = CodeInsightBundle.message("template.shortcut.tab"); private static final String ENTER = CodeInsightBundle.message("template.shortcut.enter"); + private static final String CUSTOM = "Custom"; private CheckedTreeNode myTreeRoot = new CheckedTreeNode(null); @@ -136,16 +142,11 @@ public class TemplateListPanel extends JPanel implements Disposable { initTemplates(groups, templateSettings.getLastSelectedTemplateGroup(), templateSettings.getLastSelectedTemplateKey()); - - if (templateSettings.getDefaultShortcutChar() == TemplateSettings.TAB_CHAR) { - myExpandByCombo.setSelectedItem(TAB); - } - else if (templateSettings.getDefaultShortcutChar() == TemplateSettings.ENTER_CHAR) { - myExpandByCombo.setSelectedItem(ENTER); - } - else { - myExpandByCombo.setSelectedItem(SPACE); - } + char shortcutChar = templateSettings.getDefaultShortcutChar(); + myExpandByCombo.setSelectedItem(shortcutChar == TemplateSettings.CUSTOM_CHAR ? CUSTOM : + shortcutChar == TemplateSettings.TAB_CHAR ? TAB : + shortcutChar == TemplateSettings.ENTER_CHAR ? ENTER : + SPACE); UiNotifyConnector.doWhenFirstShown(this, new Runnable() { @Override @@ -279,15 +280,10 @@ public class TemplateListPanel extends JPanel implements Disposable { private char getDefaultShortcutChar() { Object selectedItem = myExpandByCombo.getSelectedItem(); - if (TAB.equals(selectedItem)) { - return TemplateSettings.TAB_CHAR; - } - else if (ENTER.equals(selectedItem)) { - return TemplateSettings.ENTER_CHAR; - } - else { - return TemplateSettings.SPACE_CHAR; - } + if (TAB.equals(selectedItem)) return TemplateSettings.TAB_CHAR; + if (ENTER.equals(selectedItem)) return TemplateSettings.ENTER_CHAR; + if (SPACE.equals(selectedItem)) return TemplateSettings.SPACE_CHAR; + else return TemplateSettings.CUSTOM_CHAR; } private List getTemplateGroups() { @@ -347,16 +343,82 @@ public class TemplateListPanel extends JPanel implements Disposable { gbConstraints.gridx = 1; gbConstraints.insets = new Insets(0, 4, 0, 0); - myExpandByCombo = new JComboBox(); - myExpandByCombo.addItem(SPACE); - myExpandByCombo.addItem(TAB); - myExpandByCombo.addItem(ENTER); + myExpandByCombo = new ComboBox(); panel.add(myExpandByCombo, gbConstraints); + myOpenKeymapLabel = new HyperlinkLabel("Change"); gbConstraints.gridx = 2; + panel.add(myOpenKeymapLabel, gbConstraints); + + gbConstraints.gridx = 3; gbConstraints.weightx = 1; panel.add(new JPanel(), gbConstraints); panel.setBorder(new EmptyBorder(0, 0, 10, 0)); + + myExpandByCombo.addItemListener(new ItemListener() { + @Override + public void itemStateChanged(ItemEvent e) { + myOpenKeymapLabel.setVisible(myExpandByCombo.getSelectedItem() == CUSTOM); + } + }); + for (String s : ContainerUtil.ar(SPACE, TAB, ENTER, CUSTOM)) { + //noinspection unchecked + myExpandByCombo.addItem(s); + } + //noinspection unchecked + myExpandByCombo.setRenderer(new ListCellRendererWrapper() { + @Override + public void customize(JList list, Object value, int index, boolean selected, boolean hasFocus) { + if (value == CUSTOM) { + Shortcut[] shortcuts = getCurrentCustomShortcuts(); + String shortcutText = shortcuts.length == 0 ? "" : KeymapUtil.getShortcutsText(shortcuts); + setText(StringUtil.isEmpty(shortcutText) ? "Custom..." : "Custom (" + shortcutText + ")"); + } + } + + private Shortcut[] getCurrentCustomShortcuts() { + Settings allSettings = Settings.KEY.getData(DataManager.getInstance().getDataContext(myOpenKeymapLabel)); + KeymapPanel keymapPanel = allSettings == null ? null : allSettings.find(KeymapPanel.class); + Shortcut[] shortcuts = keymapPanel == null ? null : keymapPanel.getCurrentShortcuts(IdeActions.ACTION_EXPAND_LIVE_TEMPLATE_CUSTOM); + if (shortcuts == null) { + Shortcut shortcut = ActionManager.getInstance().getKeyboardShortcut(IdeActions.ACTION_EXPAND_LIVE_TEMPLATE_CUSTOM); + shortcuts = shortcut == null ? Shortcut.EMPTY_ARRAY : new Shortcut[]{shortcut}; + } + return shortcuts; + } + }); + addPropertyChangeListener(new PropertyChangeListener() { + public void propertyChange(final PropertyChangeEvent evt) { + if (isConfigurableOpenEvent(evt)) { + resizeComboToFitCustomShortcut(); + } + } + + private boolean isConfigurableOpenEvent(PropertyChangeEvent evt) { + return evt.getPropertyName().equals("ancestor") && evt.getNewValue() != null && evt.getOldValue() == null; + } + + private void resizeComboToFitCustomShortcut() { + myExpandByCombo.setPrototypeDisplayValue(null); + myExpandByCombo.setPrototypeDisplayValue(CUSTOM); + } + }); + + myOpenKeymapLabel.addHyperlinkListener(new HyperlinkAdapter() { + @Override + protected void hyperlinkActivated(HyperlinkEvent e) { + Settings allSettings = Settings.KEY.getData(DataManager.getInstance().getDataContext(myOpenKeymapLabel)); + final KeymapPanel keymapPanel = allSettings == null ? null : allSettings.find(KeymapPanel.class); + if (keymapPanel != null) { + allSettings.select(keymapPanel).doWhenDone(new Runnable() { + public void run() { + keymapPanel.selectAction(IdeActions.ACTION_EXPAND_LIVE_TEMPLATE_CUSTOM); + } + }); + } + } + }); + return panel; } diff --git a/platform/lang-impl/src/com/intellij/codeInsight/template/impl/TemplateSettings.java b/platform/lang-impl/src/com/intellij/codeInsight/template/impl/TemplateSettings.java index cdefbe83a252..060521016991 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/template/impl/TemplateSettings.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/template/impl/TemplateSettings.java @@ -68,10 +68,12 @@ public class TemplateSettings implements PersistentStateComponent, Expo public static final char TAB_CHAR = '\t'; public static final char ENTER_CHAR = '\n'; public static final char DEFAULT_CHAR = 'D'; + public static final char CUSTOM_CHAR = 'C'; @NonNls private static final String SPACE = "SPACE"; @NonNls private static final String TAB = "TAB"; @NonNls private static final String ENTER = "ENTER"; + @NonNls private static final String CUSTOM = "CUSTOM"; @NonNls private static final String NAME = "name"; @NonNls private static final String VALUE = "value"; @@ -265,13 +267,10 @@ public class TemplateSettings implements PersistentStateComponent, Expo Element element = parentNode.getChild(DEFAULT_SHORTCUT); if (element != null) { String shortcut = element.getAttributeValue(SHORTCUT); - if (TAB.equals(shortcut)) { - myDefaultShortcutChar = TAB_CHAR; - } else if (ENTER.equals(shortcut)) { - myDefaultShortcutChar = ENTER_CHAR; - } else { - myDefaultShortcutChar = SPACE_CHAR; - } + myDefaultShortcutChar = TAB.equals(shortcut) ? TAB_CHAR : + ENTER.equals(shortcut) ? ENTER_CHAR : + CUSTOM.equals(shortcut) ? CUSTOM_CHAR : + SPACE_CHAR; } ExportableTemplateSettings exportableSettings = ServiceManager.getService(ExportableTemplateSettings.class); @@ -313,13 +312,10 @@ public class TemplateSettings implements PersistentStateComponent, Expo public Element getState() { Element parentNode = new Element("TemplateSettings"); Element element = new Element(DEFAULT_SHORTCUT); - if (myDefaultShortcutChar == TAB_CHAR) { - element.setAttribute(SHORTCUT, TAB); - } else if (myDefaultShortcutChar == ENTER_CHAR) { - element.setAttribute(SHORTCUT, ENTER); - } else { - element.setAttribute(SHORTCUT, SPACE); - } + element.setAttribute(SHORTCUT, myDefaultShortcutChar == TAB_CHAR ? TAB : + myDefaultShortcutChar == ENTER_CHAR ? ENTER : + myDefaultShortcutChar == CUSTOM_CHAR ? CUSTOM : + SPACE); parentNode.addContent(element); return parentNode; diff --git a/platform/lang-impl/src/com/intellij/codeInsight/template/impl/editorActions/ExpandLiveTemplateByTabAction.java b/platform/lang-impl/src/com/intellij/codeInsight/template/impl/editorActions/ExpandLiveTemplateByTabAction.java index 326daba46e1e..4cfbf4c453fa 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/template/impl/editorActions/ExpandLiveTemplateByTabAction.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/template/impl/editorActions/ExpandLiveTemplateByTabAction.java @@ -15,39 +15,15 @@ */ package com.intellij.codeInsight.template.impl.editorActions; -import com.intellij.codeInsight.template.TemplateManager; -import com.intellij.codeInsight.template.impl.TemplateManagerImpl; import com.intellij.codeInsight.template.impl.TemplateSettings; -import com.intellij.openapi.actionSystem.DataContext; -import com.intellij.openapi.editor.Caret; -import com.intellij.openapi.editor.Editor; import com.intellij.openapi.editor.actionSystem.EditorAction; -import com.intellij.openapi.editor.actionSystem.EditorWriteActionHandler; -import com.intellij.openapi.project.Project; -import com.intellij.psi.PsiDocumentManager; -import org.jetbrains.annotations.Nullable; /** * @author peter */ public class ExpandLiveTemplateByTabAction extends EditorAction { public ExpandLiveTemplateByTabAction() { - super(new EditorWriteActionHandler(true) { - @Override - public void executeWriteAction(Editor editor, @Nullable Caret caret, DataContext dataContext) { - Project project = editor.getProject(); - assert project != null; - PsiDocumentManager.getInstance(project).commitDocument(editor.getDocument()); - TemplateManager.getInstance(project).startTemplate(editor, TemplateSettings.TAB_CHAR); - } - - @Override - public boolean isEnabled(Editor editor, DataContext dataContext) { - Project project = editor.getProject(); - return project != null && - ((TemplateManagerImpl)TemplateManager.getInstance(project)).prepareTemplate(editor, TemplateSettings.TAB_CHAR, null) != null; - } - }); + super(ExpandLiveTemplateCustomAction.createExpandTemplateHandler(TemplateSettings.TAB_CHAR)); setInjectedContext(true); } } diff --git a/platform/lang-impl/src/com/intellij/codeInsight/template/impl/editorActions/ExpandLiveTemplateCustomAction.java b/platform/lang-impl/src/com/intellij/codeInsight/template/impl/editorActions/ExpandLiveTemplateCustomAction.java new file mode 100644 index 000000000000..745d267225b5 --- /dev/null +++ b/platform/lang-impl/src/com/intellij/codeInsight/template/impl/editorActions/ExpandLiveTemplateCustomAction.java @@ -0,0 +1,58 @@ +/* + * Copyright 2000-2014 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.template.impl.editorActions; + +import com.intellij.codeInsight.template.TemplateManager; +import com.intellij.codeInsight.template.impl.TemplateManagerImpl; +import com.intellij.codeInsight.template.impl.TemplateSettings; +import com.intellij.openapi.actionSystem.DataContext; +import com.intellij.openapi.editor.Caret; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.editor.actionSystem.EditorAction; +import com.intellij.openapi.editor.actionSystem.EditorWriteActionHandler; +import com.intellij.openapi.project.Project; +import com.intellij.psi.PsiDocumentManager; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * @author peter + */ +public class ExpandLiveTemplateCustomAction extends EditorAction { + public ExpandLiveTemplateCustomAction() { + super(createExpandTemplateHandler(TemplateSettings.CUSTOM_CHAR)); + setInjectedContext(true); + } + + static EditorWriteActionHandler createExpandTemplateHandler(final char shortcutChar) { + return new EditorWriteActionHandler(true) { + @Override + public void executeWriteAction(Editor editor, @Nullable Caret caret, DataContext dataContext) { + Project project = editor.getProject(); + assert project != null; + PsiDocumentManager.getInstance(project).commitDocument(editor.getDocument()); + TemplateManager.getInstance(project).startTemplate(editor, shortcutChar); + } + + @Override + protected boolean isEnabledForCaret(@NotNull Editor editor, @NotNull Caret caret, DataContext dataContext) { + Project project = editor.getProject(); + return project != null && + ((TemplateManagerImpl)TemplateManager.getInstance(project)).prepareTemplate(editor, shortcutChar, null) != null; + } + }; + } +} diff --git a/platform/lang-impl/src/com/intellij/psi/impl/DocumentCommitThread.java b/platform/lang-impl/src/com/intellij/psi/impl/DocumentCommitThread.java index 76377ef6b98e..fd9c69710189 100644 --- a/platform/lang-impl/src/com/intellij/psi/impl/DocumentCommitThread.java +++ b/platform/lang-impl/src/com/intellij/psi/impl/DocumentCommitThread.java @@ -16,6 +16,7 @@ package com.intellij.psi.impl; import com.intellij.codeInsight.daemon.impl.DaemonProgressIndicator; +import com.intellij.diagnostic.ThreadDumper; import com.intellij.ide.startup.impl.StartupManagerImpl; import com.intellij.openapi.Disposable; import com.intellij.openapi.application.ApplicationAdapter; @@ -51,6 +52,7 @@ import java.util.List; public class DocumentCommitThread extends DocumentCommitProcessor implements Runnable, Disposable { private static final Logger LOG = Logger.getInstance("#com.intellij.psi.impl.DocumentCommitThread"); + private static final String NAME = "Document commit thread"; private final Queue documentsToCommit = new Queue(10); private final List documentsToApplyInEDT = new ArrayList(10); // guarded by documentsToCommit @@ -59,32 +61,49 @@ public class DocumentCommitThread extends DocumentCommitProcessor implements Run private CommitTask currentTask; // guarded by documentsToCommit private volatile boolean threadFinished; private volatile boolean myEnabled; // true if we can do commits. set to false temporarily during the write action. + private int runningWriteActions; // accessed in EDT only public static DocumentCommitThread getInstance() { return ServiceManager.getService(DocumentCommitThread.class); } - public DocumentCommitThread(final ApplicationEx application) { myApplication = application; // install listener in EDT to avoid missing events in case we are inside write action right now application.invokeLater(new Runnable() { @Override public void run() { + assert runningWriteActions == 0; + if (application.isDisposed()) return; + assert !application.isWriteAccessAllowed(); application.addApplicationListener(new ApplicationAdapter() { - private int runningWriteActions; - @Override public void beforeWriteActionStart(Object action) { - if (runningWriteActions++ == 0) { + int writeActionsBefore = runningWriteActions++; + if (writeActionsBefore == 0) { disable("Write action started: " + action); } + else { + log("before write action: " + action + "; " + writeActionsBefore + " write actions already running", null, false); + } } @Override public void writeActionFinished(Object action) { - if (--runningWriteActions == 0) { + // crazy things happen when running tests, like starting write action in one thread but firing its end in the other + int writeActionsAfter = runningWriteActions = Math.max(0,runningWriteActions-1); + if (writeActionsAfter == 0) { enable("Write action finished: " + action); } + else { + log("after write action: " + action + "; " + writeActionsAfter + " write actions still running", null, false); + if (writeActionsAfter < 0) { + System.err.println("mismatched listeners: " + writeActionsAfter + ";\n==== log==="+log+"\n====end log==="+ + ";\n=======threaddump====\n" + + ThreadDumper.dumpThreadsToString()+"\n=====END threaddump======="); + clearLog(); + assert false; + } + } } }, DocumentCommitThread.this); @@ -92,7 +111,8 @@ public class DocumentCommitThread extends DocumentCommitProcessor implements Run } }); log("Starting thread", null, false); - new Thread(this, "Document commit thread").start(); + Thread thread = new Thread(this, NAME); + thread.start(); } @Override @@ -169,18 +189,20 @@ public class DocumentCommitThread extends DocumentCommitProcessor implements Run } } - private final StringBuilder log = new StringBuilder(); + final StringBuilder log = new StringBuilder(); @Override public void log(@NonNls String msg, @Nullable CommitTask task, boolean synchronously, @NonNls Object... args) { if (true) return; - String indent = new SimpleDateFormat("mm:ss:SSSS").format(new Date()) + - (SwingUtilities.isEventDispatchThread() ? "- " : Thread.currentThread().getName().equals("Document commit thread") ? "- >" : "-"); + String indent = new SimpleDateFormat("hh:mm:ss:SSSS").format(new Date()) + + (SwingUtilities.isEventDispatchThread() ? "-(EDT) " : + Thread.currentThread().getName().equals(NAME) ? "-(DCT) " : + "- "); @NonNls String s = indent + msg + (synchronously ? " (sync)" : "") + - (task == null ? "" : "; task: " + task+" ("+System.identityHashCode(task)+")"); + (task == null ? " - " : "; task: " + task+" ("+System.identityHashCode(task)+")"); for (Object arg : args) { if (!StringUtil.isEmpty(String.valueOf(arg))) { @@ -195,11 +217,13 @@ public class DocumentCommitThread extends DocumentCommitProcessor implements Run } } - System.err.println(s); +// System.err.println(s); - log.append(s).append("\n"); - if (log.length() > 1000000) { - log.delete(0, 1000000); + synchronized (log) { + log.append(s).append("\n"); + if (log.length() > 100000) { + log.delete(0, log.length()-50000); + } } } @@ -219,10 +243,16 @@ public class DocumentCommitThread extends DocumentCommitProcessor implements Run @TestOnly public void clearQueue() { cancelAll(); - log.setLength(0); + clearLog(); wakeUpQueue(); } + private void clearLog() { + synchronized (log) { + log.setLength(0); + } + } + private void markRemovedCurrentTask(@Nullable CommitTask newTask) { CommitTask task = currentTask; if (task != null && (newTask == null || task.equals(newTask))) { @@ -285,7 +315,7 @@ public class DocumentCommitThread extends DocumentCommitProcessor implements Run ProgressIndicator indicator; synchronized (documentsToCommit) { if (!myEnabled || documentsToCommit.isEmpty()) { - documentsToCommit.wait(); + documentsToCommit.wait(1000); return; } task = documentsToCommit.pullFirst(); @@ -554,4 +584,9 @@ public class DocumentCommitThread extends DocumentCommitProcessor implements Run boolean isEnabled() { return myEnabled; } + + @Override + public String toString() { + return "Document commit thread; application: "+myApplication+"; isDisposed: "+isDisposed+"; threadFinished: "+threadFinished+"; myEnabled: "+myEnabled+"; runningWriteActions: "+runningWriteActions; + } } diff --git a/platform/platform-api/src/com/intellij/openapi/actionSystem/IdeActions.java b/platform/platform-api/src/com/intellij/openapi/actionSystem/IdeActions.java index 049bde52f582..9c335aca6fea 100644 --- a/platform/platform-api/src/com/intellij/openapi/actionSystem/IdeActions.java +++ b/platform/platform-api/src/com/intellij/openapi/actionSystem/IdeActions.java @@ -82,6 +82,7 @@ public interface IdeActions { @NonNls String ACTION_CHOOSE_LOOKUP_ITEM_COMPLETE_STATEMENT = "EditorChooseLookupItemCompleteStatement"; @NonNls String ACTION_CHOOSE_LOOKUP_ITEM_DOT = "EditorChooseLookupItemDot"; @NonNls String ACTION_EXPAND_LIVE_TEMPLATE_BY_TAB = "ExpandLiveTemplateByTab"; + @NonNls String ACTION_EXPAND_LIVE_TEMPLATE_CUSTOM = "ExpandLiveTemplateCustom"; @NonNls String ACTION_LOOKUP_UP = "EditorLookupUp"; @NonNls String ACTION_LOOKUP_DOWN = "EditorLookupDown"; diff --git a/platform/platform-api/src/com/intellij/openapi/progress/AbstractTaskQueue.java b/platform/platform-api/src/com/intellij/openapi/progress/AbstractTaskQueue.java index 052cad6a403f..4c0585fc2edb 100644 --- a/platform/platform-api/src/com/intellij/openapi/progress/AbstractTaskQueue.java +++ b/platform/platform-api/src/com/intellij/openapi/progress/AbstractTaskQueue.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2009 JetBrains s.r.o. + * Copyright 2000-2014 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. @@ -24,7 +24,7 @@ import java.util.Queue; @SomeQueue public abstract class AbstractTaskQueue { - private final static Logger LOG = Logger.getInstance("#com.intellij.openapi.progress.AbstractTaskQueue"); + private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.progress.AbstractTaskQueue"); private final Object myLock; private final Queue myQueue; @@ -66,6 +66,7 @@ public abstract class AbstractTaskQueue { } private class MyWorker implements Runnable { + @Override public void run() { while (true) { try { @@ -77,17 +78,24 @@ public abstract class AbstractTaskQueue { // each task is executed only once, once it has been taken from the queue.. runStuff(stuff); } - } catch (Throwable t) { + } + catch (Throwable t) { LOG.info(t); - } finally { - synchronized (myLock) { - if (myQueue.isEmpty()) { - myActive = false; - return; - } - } + } + finally { + if (isEmpty()) return; } } } } + + public boolean isEmpty() { + synchronized (myLock) { + if (myQueue.isEmpty()) { + myActive = false; + return true; + } + } + return false; + } } diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/impl/EditorImpl.java b/platform/platform-impl/src/com/intellij/openapi/editor/impl/EditorImpl.java index c5171ae7c68c..41a62d914462 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/impl/EditorImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/impl/EditorImpl.java @@ -2768,7 +2768,7 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi effectType, fontType, currentColor, logicalPosition); final VirtualFile file = getVirtualFile(); if (myProject != null && file != null && !isOneLineMode()) { - int offset = lIterator.getStart(); + int offset = position.x; String additionalText = ""; for (EditorLinePainter painter : EditorLinePainter.EP_NAME.getExtensions()) { Collection extensions = painter.getLineExtensions(myProject, file, lIterator.getLineNumber()); @@ -2785,7 +2785,10 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi } } } - myLinePaintersWidth = Math.max(myLinePaintersWidth, position.x); + for (char ch : additionalText.toCharArray()) { + offset += EditorUtil.charWidth(ch, Font.ITALIC, this); + } + myLinePaintersWidth = Math.max(myLinePaintersWidth, offset); } position.x = 0; diff --git a/platform/platform-impl/src/com/intellij/openapi/fileEditor/impl/FileEditorManagerImpl.java b/platform/platform-impl/src/com/intellij/openapi/fileEditor/impl/FileEditorManagerImpl.java index abf16d54c43d..6faadcaf2fb6 100644 --- a/platform/platform-impl/src/com/intellij/openapi/fileEditor/impl/FileEditorManagerImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/fileEditor/impl/FileEditorManagerImpl.java @@ -232,7 +232,7 @@ public class FileEditorManagerImpl extends FileEditorManagerEx implements Projec fm.doWhenFocusSettlesDown(run); } else { - UIUtil.invokeAndWaitIfNeeded(run); + UIUtil.invokeLaterIfNeeded(run); } return result; diff --git a/platform/platform-impl/src/com/intellij/openapi/keymap/impl/ui/KeymapPanel.java b/platform/platform-impl/src/com/intellij/openapi/keymap/impl/ui/KeymapPanel.java index ebb3686969f0..f562e7d540bc 100644 --- a/platform/platform-impl/src/com/intellij/openapi/keymap/impl/ui/KeymapPanel.java +++ b/platform/platform-impl/src/com/intellij/openapi/keymap/impl/ui/KeymapPanel.java @@ -66,6 +66,7 @@ import com.intellij.util.ui.UIUtil; import com.intellij.util.ui.tree.TreeUtil; import org.jetbrains.annotations.Nls; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import javax.swing.*; import javax.swing.border.EmptyBorder; @@ -955,13 +956,18 @@ public class KeymapPanel extends JPanel implements SearchableConfigurable, Confi public void dispose() { } + @Nullable + public Shortcut[] getCurrentShortcuts(String actionId) { + return mySelectedKeymap == null ? null : mySelectedKeymap.getShortcuts(actionId); + } + private void editSelection(InputEvent e) { final String actionId = myActionsTree.getSelectedActionId(); if (actionId == null) return; DefaultActionGroup group = new DefaultActionGroup(); - final Shortcut[] shortcuts = mySelectedKeymap.getShortcuts(actionId); + final Shortcut[] shortcuts = getCurrentShortcuts(actionId); final Set abbreviations = AbbreviationManager.getInstance().getAbbreviations(actionId); final ShortcutRestrictions restrictions = ActionShortcutRestrictions.getForActionId(actionId); diff --git a/platform/platform-resources-en/src/messages/ActionsBundle.properties b/platform/platform-resources-en/src/messages/ActionsBundle.properties index 0174ab7fe9e6..3da9a7382d01 100644 --- a/platform/platform-resources-en/src/messages/ActionsBundle.properties +++ b/platform/platform-resources-en/src/messages/ActionsBundle.properties @@ -57,6 +57,7 @@ group.EditorActions.text=Editor Actions action.EditorChooseLookupItem.text=Choose Lookup Item action.EditorChooseLookupItemReplace.text=Choose Lookup Item Replace action.EditorChooseLookupItemCompleteStatement.text=Choose Lookup Item and Invoke Complete Statement +action.EditorChooseLookupItemDot.text=Choose Lookup Item and Insert Dot action.NextTemplateVariable.text=Next Template Variable or Finish In-Place Refactoring action.PreviousTemplateVariable.text=Previous Template Variable action.NextParameter.text=Next Parameter @@ -497,6 +498,8 @@ action.ClassNameCompletion.text=Class _Name Completion action.ClassNameCompletion.description=Complete class name and add import for it action.InsertLiveTemplate.text=Insert Live _Template... action.InsertLiveTemplate.description=Show popup list of live templates starting with the specified prefix +action.ExpandLiveTemplateCustom.text=Expand Live Template +action.ExpandLiveTemplateCustom.description=Invoke the live template with the prefix typed in the editor action.SurroundWithLiveTemplate.text=Surround with Live Tem_plate... action.SurroundWithLiveTemplate.description=Surrounds the selection with one of the template action.CommentByLineComment.text=Comment with _Line Comment diff --git a/platform/platform-resources/src/idea/PlatformLangActionManager.xml b/platform/platform-resources/src/idea/PlatformLangActionManager.xml index a5431feb2538..e715373903c8 100644 --- a/platform/platform-resources/src/idea/PlatformLangActionManager.xml +++ b/platform/platform-resources/src/idea/PlatformLangActionManager.xml @@ -8,6 +8,7 @@ + diff --git a/platform/platform-tests/testSrc/com/intellij/openapi/progress/util/ProgressIndicatorTest.java b/platform/platform-tests/testSrc/com/intellij/openapi/progress/util/ProgressIndicatorTest.java index a93b3eef8376..b2381dbe0fb6 100644 --- a/platform/platform-tests/testSrc/com/intellij/openapi/progress/util/ProgressIndicatorTest.java +++ b/platform/platform-tests/testSrc/com/intellij/openapi/progress/util/ProgressIndicatorTest.java @@ -25,6 +25,7 @@ import com.intellij.testFramework.PlatformTestUtil; import com.intellij.util.Alarm; import com.intellij.util.Function; import com.intellij.util.Processor; +import com.intellij.util.SystemProperties; import com.intellij.util.containers.ContainerUtil; import com.intellij.util.containers.DoubleArrayList; import com.intellij.util.containers.Stack; @@ -34,6 +35,7 @@ import org.jetbrains.annotations.NotNull; import java.util.Collections; import java.util.List; +import java.util.Random; import java.util.concurrent.Future; import java.util.concurrent.atomic.AtomicBoolean; @@ -49,7 +51,8 @@ public class ProgressIndicatorTest extends LightPlatformTestCase { fail("Please restore ProgressIndicatorBase.checkCanceled() check!"); } catch(ProcessCanceledException ex) { - assertTrue("Should have no stackframe", ex.getStackTrace().length == 0); + boolean isInternal = SystemProperties.getBooleanProperty("idea.is.internal", false); + assertTrue("Should have no stackframe", isInternal ? ex.getStackTrace().length != 0 : ex.getStackTrace().length == 0); } } @@ -138,8 +141,8 @@ public class ProgressIndicatorTest extends LightPlatformTestCase { assertTrue(indicator.isCanceled()); } - public void testThereIsNoDelayBetweenIndicatorCancelAndProgressManagerCheckCanceled() { - for (int i=0; i<1000;i++) { + public void testThereIsNoDelayBetweenIndicatorCancelAndProgressManagerCheckCanceled() throws Throwable { + for (int i=0; i<100;i++) { final ProgressIndicatorBase indicator = new ProgressIndicatorBase(); List threads = ContainerUtil.map(Collections.nCopies(10, ""), new Function() { @Override @@ -151,13 +154,16 @@ public class ProgressIndicatorTest extends LightPlatformTestCase { @Override public void run() { try { - boolean canceled = indicator.isCanceled(); + Thread.sleep(new Random().nextInt(100)); indicator.cancel(); ProgressManager.checkCanceled(); fail("checkCanceled() must know about canceled indicator even from different thread"); } catch (ProcessCanceledException ignored) { } + catch (Throwable e) { + exception = e; + } } }, indicator); } @@ -177,7 +183,7 @@ public class ProgressIndicatorTest extends LightPlatformTestCase { } }); } - + if (exception != null) throw exception; } private volatile boolean checkCanceledCalled; @@ -288,7 +294,6 @@ public class ProgressIndicatorTest extends LightPlatformTestCase { @Override public void initStateFrom(@NotNull ProgressIndicator indicator) { - throw new RuntimeException(); } @NotNull diff --git a/platform/platform-tests/testSrc/com/intellij/psi/impl/PsiDocumentManagerImplTest.java b/platform/platform-tests/testSrc/com/intellij/psi/impl/PsiDocumentManagerImplTest.java index 693c23766d62..717d64c2517c 100644 --- a/platform/platform-tests/testSrc/com/intellij/psi/impl/PsiDocumentManagerImplTest.java +++ b/platform/platform-tests/testSrc/com/intellij/psi/impl/PsiDocumentManagerImplTest.java @@ -15,6 +15,7 @@ */ package com.intellij.psi.impl; +import com.intellij.diagnostic.ThreadDumper; import com.intellij.ide.impl.ProjectUtil; import com.intellij.mock.MockDocument; import com.intellij.mock.MockPsiFile; @@ -26,7 +27,7 @@ import com.intellij.openapi.fileEditor.FileDocumentManager; import com.intellij.openapi.project.Project; import com.intellij.openapi.project.ex.ProjectManagerEx; import com.intellij.openapi.util.ThrowableComputable; -import com.intellij.openapi.util.io.FileUtil; +import com.intellij.openapi.util.io.FileUtilRt; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vfs.LocalFileSystem; import com.intellij.openapi.vfs.VfsUtil; @@ -385,6 +386,9 @@ public class PsiDocumentManagerImplTest extends PlatformLangTestCase { WriteCommandAction.runWriteCommandAction(null, new Runnable() { @Override public void run() { + if (commitThread.isEnabled()) { + System.err.println("commitThread: "+commitThread + ";\n"+commitThread.log+";\n"+ThreadDumper.dumpThreadsToString()); + } assertFalse(commitThread.isEnabled()); WriteCommandAction.runWriteCommandAction(null, new Runnable() { @Override @@ -451,7 +455,7 @@ public class PsiDocumentManagerImplTest extends PlatformLangTestCase { WriteCommandAction.runWriteCommandAction(myProject, new ThrowableComputable() { @Override public Object compute() throws Exception { - VfsUtil.saveText(vFile, StringUtil.repeat("a", FileUtil.LARGE_FOR_CONTENT_LOADING + 1)); + VfsUtil.saveText(vFile, StringUtil.repeat("a", FileUtilRt.LARGE_FOR_CONTENT_LOADING + 1)); return null; } }); diff --git a/platform/testFramework/src/com/intellij/testFramework/PlatformTestCase.java b/platform/testFramework/src/com/intellij/testFramework/PlatformTestCase.java index ea7a7c0deed1..41b41fb5f62e 100644 --- a/platform/testFramework/src/com/intellij/testFramework/PlatformTestCase.java +++ b/platform/testFramework/src/com/intellij/testFramework/PlatformTestCase.java @@ -482,7 +482,10 @@ public abstract class PlatformTestCase extends UsefulTestCase implements DataPro private void disposeProject(@NotNull CompositeException result) /* throws nothing */ { try { DocumentCommitThread.getInstance().clearQueue(); - UIUtil.dispatchAllInvocationEvents(); + // sometimes SwingUtilities maybe confused about EDT at this point + if (SwingUtilities.isEventDispatchThread()) { + UIUtil.dispatchAllInvocationEvents(); + } } catch (Exception e) { result.add(e); diff --git a/platform/util/src/com/intellij/openapi/util/TraceableDisposable.java b/platform/util/src/com/intellij/openapi/util/TraceableDisposable.java index 038f2eec78c9..d4c5bd615b93 100644 --- a/platform/util/src/com/intellij/openapi/util/TraceableDisposable.java +++ b/platform/util/src/com/intellij/openapi/util/TraceableDisposable.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2012 JetBrains s.r.o. + * Copyright 2000-2014 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. @@ -16,10 +16,12 @@ package com.intellij.openapi.util; import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.io.PrintStream; import java.io.PrintWriter; +import java.io.StringWriter; /** * Traces creation and disposal by storing corresponding stacktraces. @@ -72,4 +74,11 @@ public class TraceableDisposable { super.printStackTrace(s); } } + + @NotNull + public String getStackTrace() { + StringWriter out = new StringWriter(); + new DisposalException("").printStackTrace(new PrintWriter(out)); + return out.toString(); + } } diff --git a/platform/util/testSrc/com/intellij/util/xmlb/XmlSerializerTest.java b/platform/util/testSrc/com/intellij/util/xmlb/XmlSerializerTest.java index 8d0576c12405..71e785195eb8 100644 --- a/platform/util/testSrc/com/intellij/util/xmlb/XmlSerializerTest.java +++ b/platform/util/testSrc/com/intellij/util/xmlb/XmlSerializerTest.java @@ -19,7 +19,6 @@ package com.intellij.util.xmlb; import com.intellij.openapi.util.JDOMUtil; import com.intellij.openapi.util.Ref; import com.intellij.openapi.util.text.StringUtil; -import com.intellij.util.containers.ContainerUtil; import com.intellij.util.xmlb.annotations.*; import junit.framework.AssertionFailedError; import junit.framework.TestCase; @@ -254,7 +253,7 @@ public class XmlSerializerTest extends TestCase { } public static class BeanWithMap { - public Map VALUES = new HashMap(); + public Map VALUES = new LinkedHashMap(); { VALUES.put("a", "1"); @@ -302,7 +301,7 @@ public class XmlSerializerTest extends TestCase { keyAttributeName = "name", valueAttributeName = "value" ) - public Map VALUES = new HashMap(); + public Map VALUES = new LinkedHashMap(); { VALUES.put("a", "1"); @@ -336,7 +335,7 @@ public class XmlSerializerTest extends TestCase { public static class BeanWithMapWithBeanValue { - public Map VALUES = new HashMap(); + public Map VALUES = new LinkedHashMap(); } public void testMapWithBeanValue() { @@ -519,7 +518,7 @@ public class XmlSerializerTest extends TestCase { Element element = serialize(bean, null); - Element node = (Element)element.getChildren().get(0); + Element node = element.getChildren().get(0); element.removeContent(node); element.addContent(node); @@ -1007,15 +1006,15 @@ public class XmlSerializerTest extends TestCase { } public static class BeanWithSetKeysInMap { - public Map, String> myMap = new HashMap, String>(); + public Map, String> myMap = new LinkedHashMap, String>(); } public void testSetKeysInMap() { final BeanWithSetKeysInMap bean = new BeanWithSetKeysInMap(); - bean.myMap.put(new HashSet(Arrays.asList("1", "2", "3")), "numbers"); - bean.myMap.put(new HashSet(Arrays.asList("a", "b", "c")), "letters"); + bean.myMap.put(new LinkedHashSet(Arrays.asList("a", "b", "c")), "letters"); + bean.myMap.put(new LinkedHashSet(Arrays.asList("1", "2", "3")), "numbers"); - BeanWithSetKeysInMap bb = (BeanWithSetKeysInMap)doSerializerTest( + BeanWithSetKeysInMap bb = doSerializerTest( "\n" + "