diff --git a/java/compiler/impl/src/com/intellij/compiler/impl/CompileDriver.java b/java/compiler/impl/src/com/intellij/compiler/impl/CompileDriver.java index 00b601bf69bf..aef725908db7 100644 --- a/java/compiler/impl/src/com/intellij/compiler/impl/CompileDriver.java +++ b/java/compiler/impl/src/com/intellij/compiler/impl/CompileDriver.java @@ -1703,6 +1703,9 @@ public class CompileDriver { if (context.getMessageCount(CompilerMessageCategory.ERROR) > 0) { return false; } + if (LOG.isDebugEnabled() && items.length > 0) { + LOG.debug("Start processing files by " + adapter.getCompiler().getDescription()); + } final CompileScope scope = context.getCompileScope(); final List toProcess = new ArrayList(); final Set allUrls = new HashSet(); @@ -1806,7 +1809,6 @@ public class CompileDriver { final VirtualFile file = aProcessed.getFile(); vFiles.add(file); if (LOG.isDebugEnabled()) { - LOG.debug("File processed by " + adapter.getCompiler().getDescription()); LOG.debug("\tFile processed " + file.getPresentableUrl() + "; ts=" + file.getTimeStamp()); } diff --git a/java/compiler/impl/src/com/intellij/compiler/impl/rmiCompiler/RmicCompiler.java b/java/compiler/impl/src/com/intellij/compiler/impl/rmiCompiler/RmicCompiler.java index 75aef5a38dbf..04972a1e68f3 100644 --- a/java/compiler/impl/src/com/intellij/compiler/impl/rmiCompiler/RmicCompiler.java +++ b/java/compiler/impl/src/com/intellij/compiler/impl/rmiCompiler/RmicCompiler.java @@ -110,7 +110,7 @@ public class RmicCompiler implements ClassPostProcessingCompiler{ } if (!VfsUtil.isAncestor(outputDir, outputClassFile, true)) { - LOG.assertTrue(false, outputClassFile.getPath() + " should be located under the output root " + outputDir.getPath()); + LOG.error(outputClassFile.getPath() + " should be located under the output root " + outputDir.getPath()); } final ProcessingItem item = createProcessingItem(module, outputClassFile, outputDir, diff --git a/java/compiler/impl/src/com/intellij/packaging/impl/compiler/ArtifactCompileScope.java b/java/compiler/impl/src/com/intellij/packaging/impl/compiler/ArtifactCompileScope.java index 4702d30f3f31..38805aa9083c 100644 --- a/java/compiler/impl/src/com/intellij/packaging/impl/compiler/ArtifactCompileScope.java +++ b/java/compiler/impl/src/com/intellij/packaging/impl/compiler/ArtifactCompileScope.java @@ -31,10 +31,7 @@ import com.intellij.util.Processor; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.util.Arrays; -import java.util.Collection; -import java.util.HashSet; -import java.util.Set; +import java.util.*; /** * @author nik @@ -87,9 +84,10 @@ public class ArtifactCompileScope { Set artifacts = new HashSet(); final ArtifactManager artifactManager = ArtifactManager.getInstance(project); final Set modules = new HashSet(Arrays.asList(compileScope.getAffectedModules())); + final List allModules = Arrays.asList(ModuleManager.getInstance(project).getModules()); for (Artifact artifact : artifactManager.getArtifacts()) { if (artifact.isBuildOnMake()) { - if (modules.containsAll(Arrays.asList(ModuleManager.getInstance(project).getModules())) + if (modules.containsAll(allModules) || containsModuleOutput(artifact, modules, artifactManager)) { artifacts.add(artifact); } diff --git a/java/compiler/impl/src/com/intellij/packaging/impl/compiler/IncrementalArtifactsCompiler.java b/java/compiler/impl/src/com/intellij/packaging/impl/compiler/IncrementalArtifactsCompiler.java index 38d04612f2e8..770fc0989716 100644 --- a/java/compiler/impl/src/com/intellij/packaging/impl/compiler/IncrementalArtifactsCompiler.java +++ b/java/compiler/impl/src/com/intellij/packaging/impl/compiler/IncrementalArtifactsCompiler.java @@ -84,6 +84,9 @@ public class IncrementalArtifactsCompiler implements PackagingCompiler { final CompileContext context = builderContext.getCompileContext(); final Set artifactsToBuild = ArtifactCompileScope.getArtifactsToBuild(project, context.getCompileScope()); + if (LOG.isDebugEnabled()) { + LOG.debug("artifacts to build: " + artifactsToBuild); + } List additionalArtifacts = new ArrayList(); for (BuildParticipantProvider provider : BuildParticipantProvider.EXTENSION_POINT_NAME.getExtensions()) { for (Module module : ModuleManager.getInstance(project).getModules()) { @@ -93,6 +96,9 @@ public class IncrementalArtifactsCompiler implements PackagingCompiler { } } } + if (LOG.isDebugEnabled() && !additionalArtifacts.isEmpty()) { + LOG.debug("additional artifacts to build: " + additionalArtifacts); + } artifactsToBuild.addAll(additionalArtifacts); final List allArtifacts = new ArrayList(Arrays.asList(ArtifactManager.getInstance(project).getArtifacts())); @@ -120,7 +126,7 @@ public class IncrementalArtifactsCompiler implements PackagingCompiler { ArtifactPackagingProcessingItem[] allProcessingItems = collectItems(builderContext, context.getProject()); if (LOG.isDebugEnabled()) { - int num = Math.min(100, allProcessingItems.length); + int num = Math.min(5000, allProcessingItems.length); LOG.debug("All files (" + num + " of " + allProcessingItems.length + "):"); for (int i = 0; i < num; i++) { LOG.debug(allProcessingItems[i].getFile().getPath()); @@ -203,7 +209,7 @@ public class IncrementalArtifactsCompiler implements PackagingCompiler { final boolean testMode = ApplicationManager.getApplication().isUnitTestMode(); if (LOG.isDebugEnabled()) { - int num = Math.min(100, items.length); + int num = Math.min(200, items.length); LOG.debug("Files to process (" + num + " of " + items.length + "):"); for (int i = 0; i < num; i++) { LOG.debug(items[i].getFile().getPath()); diff --git a/java/compiler/impl/src/com/intellij/packaging/impl/elements/ModuleOutputPackagingElement.java b/java/compiler/impl/src/com/intellij/packaging/impl/elements/ModuleOutputPackagingElement.java index 3f2083f9e12d..29d5c9d709b9 100644 --- a/java/compiler/impl/src/com/intellij/packaging/impl/elements/ModuleOutputPackagingElement.java +++ b/java/compiler/impl/src/com/intellij/packaging/impl/elements/ModuleOutputPackagingElement.java @@ -80,9 +80,12 @@ public class ModuleOutputPackagingElement extends PackagingElement 0) { final File copy = new File(new File(myPathTf.getText()), file.getName()); - if (copy.mkdirs()) { - try { - final File fromFile = VfsUtil.virtualToIoFile(file); - if (fromFile.isFile()) { - FileUtil.copy(fromFile, copy); - } else { - FileUtil.copyDir(fromFile, copy); - } - model.addRoot(LocalFileSystem.getInstance().refreshAndFindFileByIoFile(copy), type); + if (!copy.getParentFile().exists() && !copy.getParentFile().mkdirs()) continue; + try { + final File fromFile = VfsUtil.virtualToIoFile(file); + if (fromFile.isFile()) { + FileUtil.copy(fromFile, copy); + } else { + FileUtil.copyDir(fromFile, copy); } - catch (IOException e1) { - //skip - } - continue; + model.addRoot(LocalFileSystem.getInstance().refreshAndFindFileByIoFile(copy), type); } + catch (IOException e1) { + LOG.error(e1); + } + + continue; } model.addRoot(file, type); diff --git a/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/projectRoot/daemon/ProjectStructureProblemsHolderImpl.java b/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/projectRoot/daemon/ProjectStructureProblemsHolderImpl.java index 60cc936ced23..82cb561b8fd8 100644 --- a/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/projectRoot/daemon/ProjectStructureProblemsHolderImpl.java +++ b/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/projectRoot/daemon/ProjectStructureProblemsHolderImpl.java @@ -46,8 +46,14 @@ public class ProjectStructureProblemsHolderImpl implements ProjectStructureProbl try { buf.append(""); if (myProblemDescriptions != null) { + int problems = 0; for (ProjectStructureProblemDescription problemDescription : myProblemDescriptions) { buf.append(problemDescription.getMessage()).append("
"); + problems++; + if (problems >= 10 && myProblemDescriptions.size() > 12) { + buf.append(myProblemDescriptions.size() - problems).append(" more problems...
"); + break; + } } } buf.append(""); diff --git a/java/java-impl/src/com/intellij/codeInsight/daemon/impl/PostHighlightingPass.java b/java/java-impl/src/com/intellij/codeInsight/daemon/impl/PostHighlightingPass.java index 7c60ae78f1fb..b4e59b8238af 100644 --- a/java/java-impl/src/com/intellij/codeInsight/daemon/impl/PostHighlightingPass.java +++ b/java/java-impl/src/com/intellij/codeInsight/daemon/impl/PostHighlightingPass.java @@ -55,11 +55,11 @@ import com.intellij.profile.codeInspection.InspectionProjectProfileManager; import com.intellij.psi.*; import com.intellij.psi.codeStyle.JavaCodeStyleManager; import com.intellij.psi.impl.PsiClassImplUtil; -import com.intellij.psi.impl.search.PsiSearchHelperImpl; import com.intellij.psi.impl.source.jsp.jspJava.JspxImportStatement; import com.intellij.psi.jsp.JspFile; import com.intellij.psi.jsp.JspSpiUtil; import com.intellij.psi.search.GlobalSearchScope; +import com.intellij.psi.search.PsiSearchHelper; import com.intellij.psi.search.SearchScope; import com.intellij.psi.search.searches.MethodReferencesSearch; import com.intellij.psi.search.searches.OverridingMethodsSearch; @@ -525,12 +525,12 @@ public class PostHighlightingPass extends TextEditorHighlightingPass { // some classes may have references from within XML outside dependent modules, e.g. our actions if (member instanceof PsiClass) scope = scope.uniteWith(GlobalSearchScope.projectScope(myProject)); - PsiSearchHelperImpl.Result cheapEnough = ((PsiSearchHelperImpl)myFile.getManager().getSearchHelper()).isItCheapEnoughToSearch(myFile, name, scope); - if (cheapEnough == PsiSearchHelperImpl.Result.TOO_MANY_OCCURRENCES) return false; + PsiSearchHelper.SearchCostResult cheapEnough = myFile.getManager().getSearchHelper().isCheapEnoughToSearch(name, scope, myFile); + if (cheapEnough == PsiSearchHelper.SearchCostResult.TOO_MANY_OCCURRENCES) return false; //search usages if it cheap //if count is 0 there is no usages since we've called myRefCountHolder.isReferenced() before - if (cheapEnough == PsiSearchHelperImpl.Result.ZERO_OCCURRENCES && !canbeReferencedViaWeirdNames(member)) return true; + if (cheapEnough == PsiSearchHelper.SearchCostResult.ZERO_OCCURRENCES && !canbeReferencedViaWeirdNames(member)) return true; Query query = member instanceof PsiMethod ? MethodReferencesSearch.search((PsiMethod)member, scope, true) diff --git a/java/java-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightMethodUtil.java b/java/java-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightMethodUtil.java index c34393b8415a..7087c9eb2c17 100644 --- a/java/java-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightMethodUtil.java +++ b/java/java-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightMethodUtil.java @@ -32,11 +32,13 @@ import com.intellij.codeInsight.intention.QuickFixFactory; import com.intellij.openapi.project.IndexNotReadyException; import com.intellij.openapi.util.Comparing; import com.intellij.openapi.util.TextRange; +import com.intellij.openapi.util.text.StringUtil; import com.intellij.psi.*; import com.intellij.psi.infos.CandidateInfo; import com.intellij.psi.infos.MethodCandidateInfo; import com.intellij.psi.util.*; import com.intellij.xml.util.XmlStringUtil; +import org.intellij.lang.annotations.Language; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -558,7 +560,7 @@ public class HighlightMethodUtil { return createMismatchedArgumentsHtmlTooltip(list, parameters, methodName, substitutor, aClass); } - private static String createMismatchedArgumentsHtmlTooltip(PsiExpressionList list, + private static String createShortMismatchedArgumentsHtmlTooltip(PsiExpressionList list, PsiParameter[] parameters, String methodName, PsiSubstitutor substitutor, @@ -572,11 +574,86 @@ public class HighlightMethodUtil { Integer.valueOf(cols - parameters.length + 1), parensizedName, HighlightUtil.formatClass(aClass, false), createMismatchedArgsHtmlTooltipParamsRow(parameters, substitutor, expressions), - createMismatchedArgsHtmlTooltipExpressionsRow(expressions, parameters, substitutor, cols) + createMismatchedArgsHtmlTooltipArgumentsRow(expressions, parameters, substitutor, cols) ); } - private static String createMismatchedArgsHtmlTooltipExpressionsRow(final PsiExpression[] expressions, final PsiParameter[] parameters, + private static String esctrim(@NotNull String s) { + return StringUtil.first(XmlStringUtil.escapeString(s), 40, true); + } + + private static String createMismatchedArgumentsHtmlTooltip(PsiExpressionList list, + PsiParameter[] parameters, + String methodName, + PsiSubstitutor substitutor, + PsiClass aClass) { + if (Math.max(parameters.length, list.getExpressions().length) <= 2) { + return createShortMismatchedArgumentsHtmlTooltip(list, parameters, methodName, substitutor, aClass); + } + else { + return createLongMismatchedArgumentsHtmlTooltip(list, parameters, methodName, substitutor, aClass); + } + } + + private static String createLongMismatchedArgumentsHtmlTooltip(PsiExpressionList list, + PsiParameter[] parameters, + String methodName, + PsiSubstitutor substitutor, + PsiClass aClass) { + PsiExpression[] expressions = list.getExpressions(); + + @NonNls @Language("HTML") + String s = "" + + " "+ + " " + +" " + ; + + + for (int i = 0; i < Math.max(parameters.length,expressions.length); i++) { + PsiParameter parameter = i < parameters.length ? parameters[i] : null; + PsiExpression expression = i < expressions.length ? expressions[i] : null; + @NonNls String mismatchColor = showShortType(i, parameters, expressions, substitutor) ? null : "red"; + + s += ""; + s += ""; + + s += ""; + + s += ""; + + s += ""; + } + + s+= "
" + methodName + "() in " + HighlightUtil.formatClass(aClass, false) +" cannot be applied to:
Expected
Parameters:
Actual
Arguments:

"; + if (parameter != null) { + s += esctrim(parameter.getName()) +":"; + } + s += ""; + if (parameter != null) { + PsiType type = substitutor.substitute(parameter.getType()); + s += "" + + esctrim(showShortType(i, parameters, expressions, substitutor) + ? type.getPresentableText() + : HighlightUtil.formatType(type)) + + "" + ; + } + s += ""; + if (expression != null) { + PsiType type = expression.getType(); + s += "" + + esctrim(expression.getText()) + "  "+ + (mismatchColor == null || type == null || type == PsiType.NULL ? "" : "("+esctrim(HighlightUtil.formatType(type))+")") + + "" + ; + + } + s += "
"; + return s; + } + + private static String createMismatchedArgsHtmlTooltipArgumentsRow(final PsiExpression[] expressions, final PsiParameter[] parameters, final PsiSubstitutor substitutor, final int cols) { @NonNls String ms = ""; for (int i = 0; i < expressions.length; i++) { diff --git a/java/java-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightVisitorImpl.java b/java/java-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightVisitorImpl.java index 2f50f7adb3b5..6a021df38048 100644 --- a/java/java-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightVisitorImpl.java +++ b/java/java-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightVisitorImpl.java @@ -80,7 +80,7 @@ public class HighlightVisitorImpl extends JavaElementVisitor implements Highligh } } }; - + @SuppressWarnings({"UnusedDeclaration"}) //in plugin.xml public HighlightVisitorImpl(Project project) { this(JavaPsiFacade.getInstance(project).getResolveHelper()); @@ -111,10 +111,18 @@ public class HighlightVisitorImpl extends JavaElementVisitor implements Highligh element.accept(this); if (myRefCountHolder != null) { - registerReferencesFromInjectedFragments(element); + instantiateInjections(element); } } + private void instantiateInjections(final PsiElement element) { + InjectedLanguageUtil.enumerate(element, myFile, new PsiLanguageInjectionHost.InjectedPsiVisitor() { + public void visit(@NotNull final PsiFile injectedPsi, @NotNull final List places) { + // no op + } + }, false); + } + private void registerReferencesFromInjectedFragments(final PsiElement element) { InjectedLanguageUtil.enumerate(element, myFile, new PsiLanguageInjectionHost.InjectedPsiVisitor() { public void visit(@NotNull final PsiFile injectedPsi, @NotNull final List places) { @@ -280,6 +288,7 @@ public class HighlightVisitorImpl extends JavaElementVisitor implements Highligh @Override public void visitComment(PsiComment comment) { super.visitComment(comment); if (!myHolder.hasErrorResults()) myHolder.add(HighlightUtil.checkUnclosedComment(comment)); + if (!myHolder.hasErrorResults()) registerReferencesFromInjectedFragments(comment); } @Override public void visitContinueStatement(PsiContinueStatement statement) { @@ -510,6 +519,7 @@ public class HighlightVisitorImpl extends JavaElementVisitor implements Highligh super.visitLiteralExpression(expression); if (myHolder.hasErrorResults()) return; myHolder.add(HighlightUtil.checkLiteralExpressionParsingError(expression)); + if (!myHolder.hasErrorResults()) registerReferencesFromInjectedFragments(expression); } @Override public void visitMethod(PsiMethod method) { diff --git a/java/java-impl/src/com/intellij/codeInspection/varScopeCanBeNarrowed/FieldCanBeLocalInspection.java b/java/java-impl/src/com/intellij/codeInspection/varScopeCanBeNarrowed/FieldCanBeLocalInspection.java index d6c709e8fa87..c78e0a180756 100644 --- a/java/java-impl/src/com/intellij/codeInspection/varScopeCanBeNarrowed/FieldCanBeLocalInspection.java +++ b/java/java-impl/src/com/intellij/codeInspection/varScopeCanBeNarrowed/FieldCanBeLocalInspection.java @@ -15,9 +15,9 @@ */ package com.intellij.codeInspection.varScopeCanBeNarrowed; +import com.intellij.codeInsight.CodeInsightUtil; import com.intellij.codeInsight.daemon.GroupNames; import com.intellij.codeInsight.daemon.ImplicitUsageProvider; -import com.intellij.codeInsight.CodeInsightUtil; import com.intellij.codeInspection.InspectionsBundle; import com.intellij.codeInspection.LocalQuickFix; import com.intellij.codeInspection.ProblemDescriptor; @@ -29,6 +29,7 @@ import com.intellij.openapi.editor.ScrollType; import com.intellij.openapi.extensions.Extensions; import com.intellij.openapi.fileEditor.FileEditorManager; import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Ref; import com.intellij.psi.*; import com.intellij.psi.codeStyle.JavaCodeStyleManager; import com.intellij.psi.codeStyle.VariableKind; @@ -153,13 +154,13 @@ public class FieldCanBeLocalInspection extends BaseLocalInspectionTool { } } } - final Collection writtenVariables = ControlFlowUtil.getWrittenVariables(controlFlow, 0, controlFlow.getSize(), false); + final Ref> writtenVariables = new Ref>(); final List readBeforeWrites = ControlFlowUtil.getReadBeforeWrite(controlFlow); for (final PsiReferenceExpression readBeforeWrite : readBeforeWrites) { final PsiElement resolved = readBeforeWrite.resolve(); if (resolved instanceof PsiField) { final PsiField field = (PsiField)resolved; - if (writtenVariables.contains(field)){ + if (!(field.getType() instanceof PsiPrimitiveType) || getWrittenVariables(controlFlow, writtenVariables).contains(field)){ PsiElement parent = body.getParent(); if (!(parent instanceof PsiMethod) || !((PsiMethod)parent).isConstructor() || @@ -177,6 +178,13 @@ public class FieldCanBeLocalInspection extends BaseLocalInspectionTool { } } + private static Collection getWrittenVariables(ControlFlow controlFlow, Ref> writtenVariables) { + if (writtenVariables.get() == null) { + writtenVariables.set(ControlFlowUtil.getWrittenVariables(controlFlow, 0, controlFlow.getSize(), false)); + } + return writtenVariables.get(); + } + private static void removeFieldsReferencedFromInitializers(final PsiClass aClass, final Set candidates) { aClass.accept(new JavaRecursiveElementWalkingVisitor() { @Override public void visitMethod(PsiMethod method) { diff --git a/java/java-impl/src/com/intellij/lang/java/JavaDocumentationProvider.java b/java/java-impl/src/com/intellij/lang/java/JavaDocumentationProvider.java index df905629b703..4cb54acf4a6b 100644 --- a/java/java-impl/src/com/intellij/lang/java/JavaDocumentationProvider.java +++ b/java/java-impl/src/com/intellij/lang/java/JavaDocumentationProvider.java @@ -549,18 +549,16 @@ public class JavaDocumentationProvider implements CodeDocumentationProvider, Ext @Nullable - private static String generateExternalJavadoc(final PsiElement element, String fromUrl, boolean checkCompiled, JavaDocExternalFilter filter) { - if (!checkCompiled || element instanceof PsiCompiledElement) { - try { - String externalDoc = filter.getExternalDocInfoForElement(fromUrl, element); - if (externalDoc != null && externalDoc.length() > 0) { - return externalDoc; - } - } - catch (Exception e) { - //try to generate some javadoc + private static String fetchExternalJavadoc(final PsiElement element, String fromUrl, JavaDocExternalFilter filter) { + try { + String externalDoc = filter.getExternalDocInfoForElement(fromUrl, element); + if (externalDoc != null && externalDoc.length() > 0) { + return externalDoc; } } + catch (Exception e) { + //try to generate some javadoc + } return null; } @@ -762,7 +760,7 @@ public class JavaDocumentationProvider implements CodeDocumentationProvider, Ext if (docURLs != null) { for (String docURL : docURLs) { try { - final String javadoc = generateExternalJavadoc(element, docURL, true, docFilter); + final String javadoc = fetchExternalJavadoc(element, docURL, docFilter); if (javadoc != null) return javadoc; } catch (IndexNotReadyException e) { diff --git a/java/java-impl/src/com/intellij/psi/impl/PsiClassImplUtil.java b/java/java-impl/src/com/intellij/psi/impl/PsiClassImplUtil.java index c9115d619abe..900283e9661f 100644 --- a/java/java-impl/src/com/intellij/psi/impl/PsiClassImplUtil.java +++ b/java/java-impl/src/com/intellij/psi/impl/PsiClassImplUtil.java @@ -21,6 +21,7 @@ import com.intellij.openapi.roots.ProjectRootManager; import com.intellij.openapi.util.*; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.*; +import com.intellij.psi.augment.PsiAugmentProvider; import com.intellij.psi.filters.OrFilter; import com.intellij.psi.impl.compiled.ClsElementImpl; import com.intellij.psi.impl.source.PsiImmediateClassType; @@ -554,6 +555,10 @@ public class PsiClassImplUtil { if (!processor.execute(field, state)) return false; } } + + for (PsiField field : PsiAugmentProvider.collectAugments(aClass, PsiField.class)) { + if (!processor.execute(field, state)) return false; + } } if (classHint == null || classHint.shouldProcess(ElementClassHint.DeclaractionKind.METHOD)) { @@ -566,6 +571,10 @@ public class PsiClassImplUtil { } if (!processor.execute(method, state)) return false; } + + for (PsiMethod method : PsiAugmentProvider.collectAugments(aClass, PsiMethod.class)) { + if (!processor.execute(method, state)) return false; + } } if (classHint == null || classHint.shouldProcess(ElementClassHint.DeclaractionKind.CLASS)) { diff --git a/java/java-impl/src/com/intellij/psi/impl/source/PsiClassImpl.java b/java/java-impl/src/com/intellij/psi/impl/source/PsiClassImpl.java index dfc56ac874bf..b3b167fd4a64 100644 --- a/java/java-impl/src/com/intellij/psi/impl/source/PsiClassImpl.java +++ b/java/java-impl/src/com/intellij/psi/impl/source/PsiClassImpl.java @@ -27,7 +27,6 @@ import com.intellij.openapi.ui.Queryable; import com.intellij.openapi.util.Pair; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.*; -import com.intellij.psi.augment.PsiAugmentProvider; import com.intellij.psi.impl.InheritanceImplUtil; import com.intellij.psi.impl.PsiClassImplUtil; import com.intellij.psi.impl.PsiImplUtil; @@ -52,7 +51,6 @@ import com.intellij.psi.stubs.IStubElementType; import com.intellij.psi.stubs.PsiFileStub; import com.intellij.psi.stubs.StubElement; import com.intellij.psi.util.PsiTreeUtil; -import com.intellij.util.ArrayUtil; import com.intellij.util.IncorrectOperationException; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; @@ -304,16 +302,12 @@ public class PsiClassImpl extends JavaStubPsiElement> implements @NotNull public PsiField[] getFields() { - final PsiField[] owns = getStubOrPsiChildren(Constants.FIELD_BIT_SET, PsiField.ARRAY_FACTORY); - final List augments = PsiAugmentProvider.collectAugments(this, PsiField.class); - return ArrayUtil.mergeArrayAndCollection(owns, augments, PsiField.ARRAY_FACTORY); + return getStubOrPsiChildren(Constants.FIELD_BIT_SET, PsiField.ARRAY_FACTORY); } @NotNull public PsiMethod[] getMethods() { - final PsiMethod[] owns = getStubOrPsiChildren(Constants.METHOD_BIT_SET, PsiMethod.ARRAY_FACTORY); - final List augments = PsiAugmentProvider.collectAugments(this, PsiMethod.class); - return ArrayUtil.mergeArrayAndCollection(owns, augments, PsiMethod.ARRAY_FACTORY); + return getStubOrPsiChildren(Constants.METHOD_BIT_SET, PsiMethod.ARRAY_FACTORY); } @NotNull diff --git a/java/java-impl/src/com/intellij/psi/impl/source/codeStyle/ImportHelper.java b/java/java-impl/src/com/intellij/psi/impl/source/codeStyle/ImportHelper.java index 7168c8a5213b..aeca7ad64065 100644 --- a/java/java-impl/src/com/intellij/psi/impl/source/codeStyle/ImportHelper.java +++ b/java/java-impl/src/com/intellij/psi/impl/source/codeStyle/ImportHelper.java @@ -671,7 +671,10 @@ public class ImportHelper{ PsiElement currentFileResolveScope = resolveResult.getCurrentFileResolveScope(); if (!(currentFileResolveScope instanceof PsiImportStatementBase)) continue; - if (context != null && currentFileResolveScope instanceof JspxImportStatement && context != ((JspxImportStatement)currentFileResolveScope).getDeclarationFile()) { + if (context != null && + (!currentFileResolveScope.isValid() || + currentFileResolveScope instanceof JspxImportStatement && + context != ((JspxImportStatement)currentFileResolveScope).getDeclarationFile())) { continue; } diff --git a/java/java-impl/src/com/intellij/psi/scope/MethodProcessorSetupFailedException.java b/java/java-impl/src/com/intellij/psi/scope/MethodProcessorSetupFailedException.java index 77c7cce96c62..d15e175d384f 100644 --- a/java/java-impl/src/com/intellij/psi/scope/MethodProcessorSetupFailedException.java +++ b/java/java-impl/src/com/intellij/psi/scope/MethodProcessorSetupFailedException.java @@ -28,4 +28,10 @@ public class MethodProcessorSetupFailedException extends Exception{ public MethodProcessorSetupFailedException(@NonNls String message){ super(message); } + + @SuppressWarnings({"NonSynchronizedMethodOverridesSynchronizedMethod"}) + @Override + public Throwable fillInStackTrace() { + return this; + } } diff --git a/java/java-impl/src/com/intellij/psi/scope/conflictResolvers/JavaMethodsConflictResolver.java b/java/java-impl/src/com/intellij/psi/scope/conflictResolvers/JavaMethodsConflictResolver.java index 1ca3b4b5de97..24d624ff2920 100644 --- a/java/java-impl/src/com/intellij/psi/scope/conflictResolvers/JavaMethodsConflictResolver.java +++ b/java/java-impl/src/com/intellij/psi/scope/conflictResolvers/JavaMethodsConflictResolver.java @@ -56,20 +56,23 @@ public class JavaMethodsConflictResolver implements PsiConflictResolver{ public CandidateInfo resolveConflict(List conflicts){ if (conflicts.isEmpty()) return null; if (conflicts.size() == 1) return conflicts.get(0); + + checkParametersNumber(conflicts, myActualParameterTypes.length, true); + if (conflicts.size() == 1) return conflicts.get(0); + checkSameSignatures(conflicts); - if (conflicts.size() == 1) return conflicts.get(0); + checkAccessLevels(conflicts); - if (conflicts.size() == 1) return conflicts.get(0); - checkParametersNumber(conflicts, myActualParameterTypes.length); + checkParametersNumber(conflicts, myActualParameterTypes.length, false); if (conflicts.size() == 1) return conflicts.get(0); final int applicabilityLevel = checkApplicability(conflicts); if (conflicts.size() == 1) return conflicts.get(0); - checkSpecifics(conflicts, applicabilityLevel); + checkSpecifics(conflicts, applicabilityLevel); if (conflicts.size() == 1) return conflicts.get(0); THashSet uniques = new THashSet(conflicts); @@ -196,9 +199,12 @@ public class JavaMethodsConflictResolver implements PsiConflictResolver{ return ((MethodCandidateInfo)info).isApplicable(); } - private static void checkParametersNumber(final List conflicts, final int argumentsCount) { + private static void checkParametersNumber(final List conflicts, + final int argumentsCount, + boolean checkForStaticAccessProblem) { boolean parametersNumberMatch = false; for (CandidateInfo info : conflicts) { + if (checkForStaticAccessProblem && !info.isStaticsScopeCorrect()) return; if (info instanceof MethodCandidateInfo) { final PsiMethod method = ((MethodCandidateInfo)info).getElement(); if (method.isVarArgs()) return; diff --git a/java/java-tests/testData/inspection/fieldCanBeLocal/localStateVar2InnerClass/expected.xml b/java/java-tests/testData/inspection/fieldCanBeLocal/localStateVar2InnerClass/expected.xml new file mode 100644 index 000000000000..4704d91e891d --- /dev/null +++ b/java/java-tests/testData/inspection/fieldCanBeLocal/localStateVar2InnerClass/expected.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/java/java-tests/testData/inspection/fieldCanBeLocal/localStateVar2InnerClass/src/Test.java b/java/java-tests/testData/inspection/fieldCanBeLocal/localStateVar2InnerClass/src/Test.java new file mode 100644 index 000000000000..155a4d1dfd6f --- /dev/null +++ b/java/java-tests/testData/inspection/fieldCanBeLocal/localStateVar2InnerClass/src/Test.java @@ -0,0 +1,11 @@ +import java.util.*; +public class Outer { + private Map value = new HashMap(); + + public class Inner { + + public Inner(String s) { + if (!value.containsKey(s)) value.put(s, ""); + } + } +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/fieldCanBeLocal/stateField/expected.xml b/java/java-tests/testData/inspection/fieldCanBeLocal/stateField/expected.xml new file mode 100644 index 000000000000..580a889dbfd6 --- /dev/null +++ b/java/java-tests/testData/inspection/fieldCanBeLocal/stateField/expected.xml @@ -0,0 +1,3 @@ + + + diff --git a/java/java-tests/testData/inspection/fieldCanBeLocal/stateField/src/Test.java b/java/java-tests/testData/inspection/fieldCanBeLocal/stateField/src/Test.java new file mode 100644 index 000000000000..79774c0c5ee9 --- /dev/null +++ b/java/java-tests/testData/inspection/fieldCanBeLocal/stateField/src/Test.java @@ -0,0 +1,8 @@ +import java.util.*; +public class Test { + private final Map myModuleToOutput = new HashMap(); + + void foo() { + if (myModuleToOutput.containsKey(null)) myModuleToOutput.put("", ""); + } +} \ No newline at end of file diff --git a/java/java-tests/testSrc/com/intellij/codeInspection/FieldCanBeLocalTest.java b/java/java-tests/testSrc/com/intellij/codeInspection/FieldCanBeLocalTest.java index 10630e3c49c4..24a8d482ba1b 100644 --- a/java/java-tests/testSrc/com/intellij/codeInspection/FieldCanBeLocalTest.java +++ b/java/java-tests/testSrc/com/intellij/codeInspection/FieldCanBeLocalTest.java @@ -26,4 +26,6 @@ public class FieldCanBeLocalTest extends InspectionTestCase { public void testStaticAccess() throws Exception { doTest(); } public void testInnerClassConstructor() throws Exception { doTest(); } public void testLocalVar2InnerClass() throws Exception { doTest(); } + public void testStateField() throws Exception { doTest(); } + public void testLocalStateVar2InnerClass() throws Exception { doTest(); } } diff --git a/platform/lang-api/src/com/intellij/codeInsight/completion/DummyIdentifierPatcher.java b/platform/lang-api/src/com/intellij/codeInsight/completion/DummyIdentifierPatcher.java index b2d38b8f14b6..5f058270a5fb 100644 --- a/platform/lang-api/src/com/intellij/codeInsight/completion/DummyIdentifierPatcher.java +++ b/platform/lang-api/src/com/intellij/codeInsight/completion/DummyIdentifierPatcher.java @@ -32,8 +32,9 @@ public class DummyIdentifierPatcher extends FileCopyPatcher { public void patchFileCopy(@NotNull final PsiFile fileCopy, @NotNull final Document document, @NotNull final OffsetMap map) { if (StringUtil.isEmpty(myDummyIdentifier)) return; - document.replaceString(map.getOffset(CompletionInitializationContext.START_OFFSET), map.getOffset(CompletionInitializationContext.SELECTION_END_OFFSET), - myDummyIdentifier); + int startOffset = map.getOffset(CompletionInitializationContext.START_OFFSET); + int endOffset = map.getOffset(CompletionInitializationContext.SELECTION_END_OFFSET); + document.replaceString(startOffset, endOffset, myDummyIdentifier); } @Override diff --git a/platform/lang-api/src/com/intellij/openapi/projectRoots/JdkUtil.java b/platform/lang-api/src/com/intellij/openapi/projectRoots/JdkUtil.java index c13746244d7e..da262d36699d 100644 --- a/platform/lang-api/src/com/intellij/openapi/projectRoots/JdkUtil.java +++ b/platform/lang-api/src/com/intellij/openapi/projectRoots/JdkUtil.java @@ -162,9 +162,14 @@ public class JdkUtil { writer.close(); } + String classpath = PathUtil.getJarPathForClass(commandLineWrapper); + final Class ourUrlClassLoader = UrlClassLoader.class; + if (ourUrlClassLoader.getName().equals(parametersList.getPropertyValue("java.system.class.loader"))) { + classpath += File.pathSeparator + PathUtil.getJarPathForClass(ourUrlClassLoader); + } + commandLine.addParameter("-classpath"); - commandLine.addParameter(PathUtil.getJarPathForClass(commandLineWrapper) + File.pathSeparator + - PathUtil.getJarPathForClass(UrlClassLoader.class)); + commandLine.addParameter(classpath); } catch (IOException e) { LOG.error(e); diff --git a/platform/lang-api/src/com/intellij/psi/search/PsiSearchHelper.java b/platform/lang-api/src/com/intellij/psi/search/PsiSearchHelper.java index a3d1183f9f90..341b8206908f 100644 --- a/platform/lang-api/src/com/intellij/psi/search/PsiSearchHelper.java +++ b/platform/lang-api/src/com/intellij/psi/search/PsiSearchHelper.java @@ -179,4 +179,10 @@ public interface PsiSearchHelper { @NotNull String text, short searchContext, boolean caseSensitive); + + SearchCostResult isCheapEnoughToSearch(@NotNull String name, @NotNull GlobalSearchScope scope, @Nullable PsiFile fileToIgnoreOccurencesIn); + + enum SearchCostResult { + ZERO_OCCURRENCES, FEW_OCCURRENCES, TOO_MANY_OCCURRENCES + } } diff --git a/platform/lang-api/src/com/intellij/psi/stubs/StubIndex.java b/platform/lang-api/src/com/intellij/psi/stubs/StubIndex.java index 4b4034f373d6..c331e6e9441d 100644 --- a/platform/lang-api/src/com/intellij/psi/stubs/StubIndex.java +++ b/platform/lang-api/src/com/intellij/psi/stubs/StubIndex.java @@ -29,13 +29,13 @@ import org.jetbrains.annotations.NotNull; import java.util.Collection; public abstract class StubIndex { - private static StubIndex ourInstance = CachedSingletonsRegistry.markCachedField(StubIndex.class); + + private static class StubIndexHolder { + private static final StubIndex ourInstance = ApplicationManager.getApplication().getComponent(StubIndex.class); + } public static StubIndex getInstance() { - if (ourInstance == null) { - ourInstance = ApplicationManager.getApplication().getComponent(StubIndex.class); - } - return ourInstance; + return StubIndexHolder.ourInstance; } public abstract Collection get( diff --git a/platform/lang-api/src/com/intellij/psi/util/PsiTreeUtil.java b/platform/lang-api/src/com/intellij/psi/util/PsiTreeUtil.java index bbbea29c8583..b47dc9f8958f 100644 --- a/platform/lang-api/src/com/intellij/psi/util/PsiTreeUtil.java +++ b/platform/lang-api/src/com/intellij/psi/util/PsiTreeUtil.java @@ -28,6 +28,7 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; public class PsiTreeUtil { @@ -49,8 +50,8 @@ public class PsiTreeUtil { public static boolean isAncestor(@Nullable PsiElement ancestor, @NotNull PsiElement element, boolean strict) { if (ancestor == null) return false; // fast path to avoid loading tree - if ((ancestor instanceof StubBasedPsiElement && (((StubBasedPsiElement)ancestor).getStub() != null)) || - (element instanceof StubBasedPsiElement && ((StubBasedPsiElement)element).getStub() != null)) { + if (ancestor instanceof StubBasedPsiElement && ((StubBasedPsiElement)ancestor).getStub() != null || + element instanceof StubBasedPsiElement && ((StubBasedPsiElement)element).getStub() != null) { if (ancestor.getContainingFile() != element.getContainingFile()) return false; } @@ -345,7 +346,7 @@ public class PsiTreeUtil { @NotNull public static PsiElement[] collectElements(@Nullable PsiElement element, @NotNull PsiElementFilter filter) { - PsiElementProcessor.CollectFilteredElements processor = new PsiElementProcessor.CollectFilteredElements(filter); + PsiElementProcessor.CollectFilteredElements processor = new PsiElementProcessor.CollectFilteredElements(filter); processElements(element, processor); return processor.toArray(); } @@ -551,7 +552,8 @@ public class PsiTreeUtil { return false; } - public static PsiElement[] filterAncestors(PsiElement[] elements) { + @NotNull + public static PsiElement[] filterAncestors(@NotNull PsiElement[] elements) { if (LOG.isDebugEnabled()) { for (PsiElement element : elements) { LOG.debug("element = " + element); @@ -559,9 +561,7 @@ public class PsiTreeUtil { } ArrayList filteredElements = new ArrayList(); - for (PsiElement element : elements) { - filteredElements.add(element); - } + filteredElements.addAll(Arrays.asList(elements)); int previousSize; do { diff --git a/platform/lang-impl/src/com/intellij/codeInsight/controlflow/ControlFlowUtil.java b/platform/lang-impl/src/com/intellij/codeInsight/controlflow/ControlFlowUtil.java index 0d690cb36dc7..5860786b79e9 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/controlflow/ControlFlowUtil.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/controlflow/ControlFlowUtil.java @@ -56,6 +56,11 @@ public class ControlFlowUtil { public void clear() { myIndex = -1; } + + @Override + public String toString() { + return "Stack(" + (myIndex + 1) + ") elements"; + } } public static int[] postOrder(Instruction[] flow) { @@ -74,7 +79,7 @@ public class ControlFlowUtil { while (!stack.isEmpty()) { final int num = stack.pop(); - result[num] = N++; + result[N++] = num; for (Instruction succ : flow[num].allSucc()) { final int succNum = succ.num(); if (!visited[succNum]) { diff --git a/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/IndentsPass.java b/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/IndentsPass.java index 221f2dd4e26b..872bebc23d45 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/IndentsPass.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/IndentsPass.java @@ -21,10 +21,12 @@ package com.intellij.codeInsight.daemon.impl; import com.intellij.codeHighlighting.TextEditorHighlightingPass; import com.intellij.codeInsight.highlighting.BraceMatchingUtil; +import com.intellij.lang.LanguageParserDefinitions; import com.intellij.openapi.editor.*; import com.intellij.openapi.editor.colors.EditorColors; import com.intellij.openapi.editor.colors.EditorColorsScheme; import com.intellij.openapi.editor.ex.EditorEx; +import com.intellij.openapi.editor.highlighter.EditorHighlighter; import com.intellij.openapi.editor.highlighter.HighlighterIterator; import com.intellij.openapi.editor.markup.CustomHighlighterRenderer; import com.intellij.openapi.editor.markup.HighlighterTargetArea; @@ -36,6 +38,7 @@ import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Key; import com.intellij.openapi.util.TextRange; import com.intellij.psi.PsiFile; +import com.intellij.psi.tree.TokenSet; import com.intellij.util.containers.IntStack; import com.intellij.util.text.CharArrayUtil; import org.jetbrains.annotations.NotNull; @@ -182,7 +185,7 @@ public class IndentsPass extends TextEditorHighlightingPass implements DumbAware private List buildDescriptors() { if (!myEditor.getSettings().isIndentGuidesShown()) return Collections.emptyList(); - int[] lineIndents = calcIndents(myDocument); + int[] lineIndents = calcIndents(); List descriptors = new ArrayList(); @@ -191,13 +194,14 @@ public class IndentsPass extends TextEditorHighlightingPass implements DumbAware lines.push(0); indents.push(0); + final CharSequence chars = myDocument.getCharsSequence(); for (int line = 1; line < lineIndents.length; line++) { int curIndent = lineIndents[line]; while (!indents.empty() && curIndent <= indents.peek()) { final int level = indents.pop(); int startLine = lines.pop(); - descriptors.add(new IndentGuideDescriptor(level, startLine, line)); + descriptors.add(createDescriptor(level, startLine, line, chars)); } int prevLine = line - 1; @@ -213,23 +217,47 @@ public class IndentsPass extends TextEditorHighlightingPass implements DumbAware final int level = indents.pop(); if (level > 0) { int startLine = lines.pop(); - descriptors.add(new IndentGuideDescriptor(level, startLine, myDocument.getLineCount())); + descriptors.add(createDescriptor(level, startLine, myDocument.getLineCount(), chars)); } } return descriptors; } - private int[] calcIndents(Document doc) { + private IndentGuideDescriptor createDescriptor(int level, int startLine, int endLine, CharSequence chars) { + while (startLine > 0 && isBlankLine(startLine, chars)) startLine--; + return new IndentGuideDescriptor(level, startLine, endLine); + } + + private boolean isBlankLine(int line, CharSequence chars) { + int startOffset = myDocument.getLineStartOffset(line); + return CharArrayUtil.shiftForward(chars, startOffset, " \t") >= myDocument.getLineEndOffset(line); + } + + private int[] calcIndents() { + final Document doc = myDocument; CharSequence chars = doc.getCharsSequence(); int[] lineIndents = new int[doc.getLineCount()]; + TokenSet comments = LanguageParserDefinitions.INSTANCE.forLanguage(myFile.getLanguage()).getCommentTokens(); + int prevColumn = -1; + final EditorHighlighter highlighter = myEditor.getHighlighter(); for (int line = 0; line < lineIndents.length; line++) { - int lineStart = myDocument.getLineStartOffset(line); - int lineEnd = myDocument.getLineEndOffset(line); + int lineStart = doc.getLineStartOffset(line); + int lineEnd = doc.getLineEndOffset(line); int nonWhitespaceOffset = CharArrayUtil.shiftForward(chars, lineStart, " \t"); if (nonWhitespaceOffset < lineEnd) { - lineIndents[line] = myEditor.calcColumnNumber(nonWhitespaceOffset, line); + final int column = myEditor.calcColumnNumber(nonWhitespaceOffset, line); + if (column < prevColumn) { + final HighlighterIterator it = highlighter.createIterator(nonWhitespaceOffset); + if (comments.contains(it.getTokenType())) { + lineIndents[line] = -1; + continue; + } + } + + lineIndents[line] = column; + prevColumn = column; } else { lineIndents[line] = -1; @@ -243,13 +271,17 @@ public class IndentsPass extends TextEditorHighlightingPass implements DumbAware } else { int startLine = line; - for (; line < lineIndents.length && lineIndents[line] == -1; line++); + while (line < lineIndents.length && lineIndents[line] == -1) { + //noinspection AssignmentToForLoopParameter + line++; + } + int bottomIndent = line < lineIndents.length ? lineIndents[line] : topIndent; int indent = Math.min(topIndent, bottomIndent); if (bottomIndent < topIndent) { - int nonWhitespaceOffset = CharArrayUtil.shiftForward(chars, myDocument.getLineStartOffset(line), " \t"); - HighlighterIterator iterator = myEditor.getHighlighter().createIterator(nonWhitespaceOffset); + int nonWhitespaceOffset = CharArrayUtil.shiftForward(chars, doc.getLineStartOffset(line), " \t"); + HighlighterIterator iterator = highlighter.createIterator(nonWhitespaceOffset); if (BraceMatchingUtil.isRBraceToken(iterator, chars, myFile.getFileType())) { indent = topIndent; } @@ -260,6 +292,7 @@ public class IndentsPass extends TextEditorHighlightingPass implements DumbAware lineIndents[blankLine] = Math.min(topIndent, indent); } + //noinspection AssignmentToForLoopParameter line--; // will be incremented back at the end of the loop; } } diff --git a/platform/lang-impl/src/com/intellij/codeInsight/dataflow/DFAEngine.java b/platform/lang-impl/src/com/intellij/codeInsight/dataflow/DFAEngine.java index 94457d42c0ac..65908b8d7466 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/dataflow/DFAEngine.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/dataflow/DFAEngine.java @@ -23,7 +23,7 @@ import java.util.*; public class DFAEngine { private static final Logger LOG = Logger.getInstance(DFAEngine.class.getName()); - private static final double TIME_LIMIT = 5*10e9; + private static final double TIME_LIMIT = 3*10e9; private final Instruction[] myFlow; @@ -86,6 +86,9 @@ public class DFAEngine { worklist.add(instruction); visited[number] = true; + // It is essential to apply this check!!! + // This gives us more chances that resulting info will be closer to expected result + // Also it is used as indicator that "equals" method is implemented correctly in E while (true) { count++; if (count > limit){ @@ -105,6 +108,9 @@ public class DFAEngine { final DFAMap joinedE = join(currentInstruction, info); final DFAMap newE = myDfa.fun(joinedE, currentInstruction); if (!mySemilattice.eq(newE, oldE)) { + if (LOG.isDebugEnabled()){ + LOG.debug("Number: " + currentNumber + " old: " + oldE.keySet() + " new: " + newE.keySet()); + } info.set(currentNumber, newE); for (Instruction next : getNext(currentInstruction)) { worklist.add(next); diff --git a/platform/lang-impl/src/com/intellij/codeInsight/dataflow/DFAMap.java b/platform/lang-impl/src/com/intellij/codeInsight/dataflow/DFAMap.java index bb57567e16e6..148a5942c3ec 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/dataflow/DFAMap.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/dataflow/DFAMap.java @@ -37,7 +37,7 @@ public class DFAMap { public DFAMap() { } - public DFAMap(DFAMap initialMap) { + private DFAMap(DFAMap initialMap) { myK = initialMap.myK; myV = initialMap.myV; myAll = initialMap.myAll == null ? null : new HashMap(initialMap.myAll); @@ -71,6 +71,7 @@ public class DFAMap { } } + @Nullable public V get(String key) { if (myAll != null) { return myAll.get(key); @@ -166,6 +167,27 @@ public class DFAMap { } public DFAMap asWritable() { - return this; + return new DFAMap(this); + } + + @Override + public String toString() { + if (this == ourEmptyMap){ + return "Empty Map"; + } + if (myAll != null){ + return myAll.toString(); + } + if (myK != null){ + return "{" + myK + "=" + myV + "}"; + } + return "Empty"; + } + + public Set keySet() { + if (myAll != null){ + return myAll.keySet(); + } + return myK != null ? Collections.singleton(myK) : Collections.emptySet(); } } diff --git a/platform/lang-impl/src/com/intellij/codeInsight/dataflow/DfaInstance.java b/platform/lang-impl/src/com/intellij/codeInsight/dataflow/DfaInstance.java index 716ec6989374..ce618834fe4b 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/dataflow/DfaInstance.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/dataflow/DfaInstance.java @@ -18,6 +18,8 @@ import com.intellij.codeInsight.controlflow.Instruction; import org.jetbrains.annotations.NotNull; public interface DfaInstance { + // Please ensure that E has correctly implemented equals method + // Invariant: fun must create new instance of DFAMap if modifies it DFAMap fun(DFAMap e, Instruction instruction); diff --git a/platform/lang-impl/src/com/intellij/codeInsight/navigation/actions/GotoDeclarationAction.java b/platform/lang-impl/src/com/intellij/codeInsight/navigation/actions/GotoDeclarationAction.java index 4978f3180d57..9d62e35ad33b 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/navigation/actions/GotoDeclarationAction.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/navigation/actions/GotoDeclarationAction.java @@ -20,6 +20,7 @@ import com.intellij.codeInsight.CodeInsightActionHandler; import com.intellij.codeInsight.CodeInsightBundle; import com.intellij.codeInsight.TargetElementUtilBase; import com.intellij.codeInsight.actions.BaseCodeInsightAction; +import com.intellij.codeInsight.hint.HintManager; import com.intellij.codeInsight.navigation.NavigationUtil; import com.intellij.featureStatistics.FeatureUsageTracker; import com.intellij.ide.util.DefaultPsiElementCellRenderer; @@ -65,10 +66,9 @@ public class GotoDeclarationAction extends BaseCodeInsightAction implements Code public void invoke(@NotNull final Project project, @NotNull Editor editor, @NotNull PsiFile file) { PsiDocumentManager.getInstance(project).commitAllDocuments(); - PsiElement element; try { int offset = editor.getCaretModel().getOffset(); - element = findTargetElement(project, editor, offset); + PsiElement element = findTargetElement(project, editor, offset); if (element == null) { FeatureUsageTracker.getInstance().triggerFeatureUsed("navigation.goto.declaration"); chooseAmbiguousTarget(editor, offset); @@ -109,7 +109,10 @@ public class GotoDeclarationAction extends BaseCodeInsightAction implements Code return true; } }; - chooseAmbiguousTarget(editor, offset,navigateProcessor, CodeInsightBundle.message("declaration.navigation.title")); + boolean found = chooseAmbiguousTarget(editor, offset, navigateProcessor, CodeInsightBundle.message("declaration.navigation.title")); + if (!found) { + HintManager.getInstance().showErrorHint(editor, "Cannot find declaration to go to"); + } } // returns true if processor is run or is going to be run after showing popup @@ -127,7 +130,7 @@ public class GotoDeclarationAction extends BaseCodeInsightAction implements Code processor.execute(element); return true; } - else if (candidates.size() > 1) { + if (candidates.size() > 1) { PsiElement[] elements = candidates.toArray(new PsiElement[candidates.size()]); final TextRange range = reference.getRangeInElement(); final String refText = range.substring(reference.getElement().getText()); diff --git a/platform/lang-impl/src/com/intellij/codeInsight/template/CustomTemplateCallback.java b/platform/lang-impl/src/com/intellij/codeInsight/template/CustomTemplateCallback.java index 854c6d05290d..a62ab1038153 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/template/CustomTemplateCallback.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/template/CustomTemplateCallback.java @@ -43,23 +43,13 @@ public class CustomTemplateCallback { private Project myProject; private RangeMarker myGlobalMarker; private RangeMarker myEndOffsetMarker; - - //private final Map myCheckpoints = new HashMap(); private final Map myCheckpoints = new HashMap(); - /*private static class MyCheckpoint { - int myFixedLength = null; - RangeMarker myFixedOffset; - }*/ - public CustomTemplateCallback(Editor editor, PsiFile file) { myEditor = editor; myFile = file; myProject = file.getProject(); myTemplateManager = TemplateManagerImpl.getInstance(myProject); - } - - public void fixInitialEditorState() { myStartOffset = myEditor.getCaretModel().getOffset(); myGlobalMarker = myEditor.getDocument().createRangeMarker(myStartOffset, myStartOffset); myGlobalMarker.setGreedyToLeft(true); diff --git a/platform/lang-impl/src/com/intellij/codeInsight/template/impl/TemplateImpl.java b/platform/lang-impl/src/com/intellij/codeInsight/template/impl/TemplateImpl.java index dac35a95ccd9..dfb1b3c4ed07 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/template/impl/TemplateImpl.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/template/impl/TemplateImpl.java @@ -305,6 +305,10 @@ public class TemplateImpl extends Template implements SchemeElement { return variable; } + public void removeVariable(int i) { + myVariables.remove(i); + } + public int getVariableCount() { return myVariables.size(); } diff --git a/platform/lang-impl/src/com/intellij/codeInsight/template/impl/TemplateManagerImpl.java b/platform/lang-impl/src/com/intellij/codeInsight/template/impl/TemplateManagerImpl.java index d8baf07f3855..0a331403e0df 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/template/impl/TemplateManagerImpl.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/template/impl/TemplateManagerImpl.java @@ -246,7 +246,6 @@ public class TemplateManagerImpl extends TemplateManager implements ProjectCompo int caretOffset = editor.getCaretModel().getOffset(); if (customLiveTemplate.isApplicable(file, caretOffset, false)) { final CustomTemplateCallback callback = new CustomTemplateCallback(editor, file); - callback.fixInitialEditorState(); String key = customLiveTemplate.computeTemplateKey(callback); if (key != null) { int offsetBeforeKey = caretOffset - key.length(); diff --git a/platform/lang-impl/src/com/intellij/codeInspection/ex/DescriptorProviderInspection.java b/platform/lang-impl/src/com/intellij/codeInspection/ex/DescriptorProviderInspection.java index e894b14d3d2b..b56965123654 100644 --- a/platform/lang-impl/src/com/intellij/codeInspection/ex/DescriptorProviderInspection.java +++ b/platform/lang-impl/src/com/intellij/codeInspection/ex/DescriptorProviderInspection.java @@ -26,6 +26,7 @@ import com.intellij.openapi.components.PathMacroManager; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.util.Comparing; import com.intellij.openapi.util.JDOMUtil; +import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vcs.FileStatus; import com.intellij.psi.PsiElement; import com.intellij.util.ArrayUtil; @@ -39,7 +40,6 @@ import org.jetbrains.annotations.Nullable; import java.io.*; import java.util.*; -import java.util.regex.Matcher; /** * @author max @@ -263,7 +263,7 @@ public abstract class DescriptorProviderInspection extends InspectionTool implem @NonNls final String template = description.getDescriptionTemplate(); int line = description instanceof ProblemDescriptor ? ((ProblemDescriptor)description).getLineNumber() : -1; final String text = description instanceof ProblemDescriptor ? ((ProblemDescriptor)description).getPsiElement().getText() : ""; - @NonNls String problemText = template.replaceAll("#ref", Matcher.quoteReplacement(text)).replaceAll(" #loc ", " "); + @NonNls String problemText = StringUtil.replace(StringUtil.replace(template, "#ref", StringUtil.quoteReplacement(text)), " #loc ", " "); Element element = refEntity.getRefManager().export(refEntity, parentNode, line); @NonNls Element problemClassElement = new Element(InspectionsBundle.message("inspection.export.results.problem.element.tag")); diff --git a/platform/lang-impl/src/com/intellij/execution/console/LanguageConsoleImpl.java b/platform/lang-impl/src/com/intellij/execution/console/LanguageConsoleImpl.java index c299ce3522a3..deff67214367 100644 --- a/platform/lang-impl/src/com/intellij/execution/console/LanguageConsoleImpl.java +++ b/platform/lang-impl/src/com/intellij/execution/console/LanguageConsoleImpl.java @@ -214,6 +214,10 @@ public class LanguageConsoleImpl implements Disposable, TypeSafeDataProvider { myUiUpdateRunnable = uiUpdateRunnable; } + public void flushAllUiUpdates() { + myUpdateQueue.flush(); + } + public LightVirtualFile getHistoryFile() { return myHistoryFile; } diff --git a/platform/lang-impl/src/com/intellij/execution/impl/SingleConfigurationConfigurable.form b/platform/lang-impl/src/com/intellij/execution/impl/SingleConfigurationConfigurable.form index 3be743de1a37..12ac18a596a4 100644 --- a/platform/lang-impl/src/com/intellij/execution/impl/SingleConfigurationConfigurable.form +++ b/platform/lang-impl/src/com/intellij/execution/impl/SingleConfigurationConfigurable.form @@ -3,7 +3,7 @@ - + @@ -25,15 +25,6 @@ - - - - - - - - - @@ -79,6 +70,22 @@ + + + + + + + + + + + + + + + + diff --git a/platform/lang-impl/src/com/intellij/execution/ui/RunContentManagerImpl.java b/platform/lang-impl/src/com/intellij/execution/ui/RunContentManagerImpl.java index 232c64880345..90b65bfd1e26 100644 --- a/platform/lang-impl/src/com/intellij/execution/ui/RunContentManagerImpl.java +++ b/platform/lang-impl/src/com/intellij/execution/ui/RunContentManagerImpl.java @@ -578,50 +578,51 @@ public class RunContentManagerImpl implements RunContentManager, Disposable { } private void waitForProcess(final RunContentDescriptor descriptor) { - String progressTitle = ExecutionBundle.message("terminating.process.progress.title", descriptor.getDisplayName()); - ProgressManager.getInstance().runProcessWithProgressSynchronously(new Runnable() { - private ProgressIndicator myProgressIndicator; - private final Semaphore mySemaphore = new Semaphore(); + public void run() { + final Semaphore semaphore = new Semaphore(); + semaphore.down(); - private final Runnable myWaitThread = new Runnable() { - public void run() { - descriptor.getProcessHandler().waitFor(); - mySemaphore.up(); - } - }; - - private final Runnable myCancelListener = new Runnable() { - public void run() { - while(true) { - if(myProgressIndicator != null && (myProgressIndicator.isCanceled() || !myProgressIndicator.isRunning())) { - mySemaphore.up(); - break; - } + ApplicationManager.getApplication().executeOnPooledThread(new Runnable() { + public void run() { + final ProcessHandler processHandler = descriptor.getProcessHandler(); try { - synchronized (this) { - wait(2000); + if (processHandler != null) { + processHandler.waitFor(); } } - catch (InterruptedException ignore) { + finally { + semaphore.up(); } } - } - }; + }); + + final ProgressIndicator progressIndicator = ProgressManager.getInstance().getProgressIndicator(); - public void run() { - myProgressIndicator = ProgressManager.getInstance().getProgressIndicator(); - if (myProgressIndicator != null) { - myProgressIndicator.setText(ExecutionBundle.message("waiting.for.vm.detach.progress.text")); + if (progressIndicator != null) { + progressIndicator.setText(ExecutionBundle.message("waiting.for.vm.detach.progress.text")); + ApplicationManager.getApplication().executeOnPooledThread(new Runnable() { + public void run() { + while(true) { + if(progressIndicator.isCanceled() || !progressIndicator.isRunning()) { + semaphore.up(); + break; + } + try { + synchronized (this) { + wait(2000L); + } + } + catch (InterruptedException ignore) { + } + } + } + }); } - ApplicationManager.getApplication().executeOnPooledThread(myWaitThread); - ApplicationManager.getApplication().executeOnPooledThread(myCancelListener); - - mySemaphore.down(); - mySemaphore.waitFor(); + semaphore.waitFor(); } - }, progressTitle, true, myProject); + }, ExecutionBundle.message("terminating.process.progress.title", descriptor.getDisplayName()), true, myProject); } public interface MyRunContentListener extends EventListener { diff --git a/platform/lang-impl/src/com/intellij/find/actions/ShowUsagesAction.java b/platform/lang-impl/src/com/intellij/find/actions/ShowUsagesAction.java index 14be608e3edb..64110e91224d 100644 --- a/platform/lang-impl/src/com/intellij/find/actions/ShowUsagesAction.java +++ b/platform/lang-impl/src/com/intellij/find/actions/ShowUsagesAction.java @@ -184,6 +184,7 @@ public class ShowUsagesAction extends AnAction { usageViewSettings.GROUP_BY_MODULE = false; usageViewSettings.GROUP_BY_PACKAGE = false; usageViewSettings.GROUP_BY_USAGE_TYPE = false; + usageViewSettings.GROUP_BY_SCOPE = false; UsageViewManager manager = UsageViewManager.getInstance(handler.getProject()); final UsageViewImpl usageView = (UsageViewImpl)manager.createUsageView(UsageTarget.EMPTY_ARRAY, Usage.EMPTY_ARRAY, presentation, null); @@ -194,6 +195,7 @@ public class ShowUsagesAction extends AnAction { usageViewSettings.GROUP_BY_MODULE = save.GROUP_BY_MODULE; usageViewSettings.GROUP_BY_PACKAGE = save.GROUP_BY_PACKAGE; usageViewSettings.GROUP_BY_USAGE_TYPE = save.GROUP_BY_USAGE_TYPE; + usageViewSettings.GROUP_BY_SCOPE = save.GROUP_BY_SCOPE; } }); @@ -694,4 +696,4 @@ public class ShowUsagesAction extends AnAction { return null; } } -} \ No newline at end of file +} diff --git a/platform/lang-impl/src/com/intellij/ide/actions/GotoActionBase.java b/platform/lang-impl/src/com/intellij/ide/actions/GotoActionBase.java index 646cd246e267..0fda5957f06e 100644 --- a/platform/lang-impl/src/com/intellij/ide/actions/GotoActionBase.java +++ b/platform/lang-impl/src/com/intellij/ide/actions/GotoActionBase.java @@ -48,7 +48,7 @@ public abstract class GotoActionBase extends AnAction { protected abstract void gotoActionPerformed(AnActionEvent e); - public final void update(final AnActionEvent event) { + public void update(final AnActionEvent event) { final Presentation presentation = event.getPresentation(); final DataContext dataContext = event.getDataContext(); final Project project = PlatformDataKeys.PROJECT.getData(dataContext); diff --git a/platform/lang-impl/src/com/intellij/ide/favoritesTreeView/FavoritesTreeViewPanel.java b/platform/lang-impl/src/com/intellij/ide/favoritesTreeView/FavoritesTreeViewPanel.java index a0575affbbff..526f7921f678 100644 --- a/platform/lang-impl/src/com/intellij/ide/favoritesTreeView/FavoritesTreeViewPanel.java +++ b/platform/lang-impl/src/com/intellij/ide/favoritesTreeView/FavoritesTreeViewPanel.java @@ -59,6 +59,7 @@ import com.intellij.util.ui.UIUtil; import com.intellij.util.ui.tree.TreeUtil; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import javax.swing.*; import javax.swing.tree.DefaultMutableTreeNode; @@ -457,14 +458,15 @@ public class FavoritesTreeViewPanel extends JPanel implements DataProvider { } } - private PsiDirectory getDirectory() { + @Nullable + private PsiDirectory[] getSelectedDirectories() { if (myBuilder == null) return null; final Object[] selectedNodeElements = getSelectedNodeElements(); if (selectedNodeElements.length != 1) return null; for (FavoriteNodeProvider nodeProvider : Extensions.getExtensions(FavoriteNodeProvider.EP_NAME, myProject)) { final PsiElement psiElement = nodeProvider.getPsiElement(selectedNodeElements[0]); if (psiElement instanceof PsiDirectory) { - return (PsiDirectory)psiElement; + return new PsiDirectory[]{(PsiDirectory)psiElement}; } else if (psiElement instanceof PsiDirectoryContainer) { final String moduleName = nodeProvider.getElementModuleName(selectedNodeElements[0]); GlobalSearchScope searchScope = GlobalSearchScope.projectScope(myProject); @@ -474,16 +476,15 @@ public class FavoritesTreeViewPanel extends JPanel implements DataProvider { searchScope = GlobalSearchScope.moduleScope(module); } } - final PsiDirectory[] directories = ((PsiDirectoryContainer)psiElement).getDirectories(searchScope); - if (directories.length == 1) return directories[0]; + return ((PsiDirectoryContainer)psiElement).getDirectories(searchScope); } } return null; } public PsiDirectory[] getDirectories() { - PsiDirectory directory = getDirectory(); - return directory == null ? PsiDirectory.EMPTY_ARRAY : new PsiDirectory[]{directory}; + final PsiDirectory[] directories = getSelectedDirectories(); + return directories == null ? PsiDirectory.EMPTY_ARRAY : directories; } public PsiDirectory getOrChooseDirectory() { diff --git a/platform/lang-impl/src/com/intellij/ide/util/DefaultPsiElementCellRenderer.java b/platform/lang-impl/src/com/intellij/ide/util/DefaultPsiElementCellRenderer.java index 4581b24eb125..ea90cb413355 100644 --- a/platform/lang-impl/src/com/intellij/ide/util/DefaultPsiElementCellRenderer.java +++ b/platform/lang-impl/src/com/intellij/ide/util/DefaultPsiElementCellRenderer.java @@ -20,7 +20,7 @@ import com.intellij.openapi.util.Iconable; import com.intellij.psi.PsiElement; import com.intellij.psi.presentation.java.SymbolPresentationUtil; -public class DefaultPsiElementCellRenderer extends PsiElementListCellRenderer { +public class DefaultPsiElementCellRenderer extends PsiElementListCellRenderer { protected int getIconFlags() { return Iconable.ICON_FLAG_VISIBILITY; } @@ -33,4 +33,4 @@ public class DefaultPsiElementCellRenderer extends PsiElementListCellRenderer { return SymbolPresentationUtil.getSymbolContainerText(element); } -} \ No newline at end of file +} diff --git a/platform/lang-impl/src/com/intellij/ide/util/PsiElementListCellRenderer.java b/platform/lang-impl/src/com/intellij/ide/util/PsiElementListCellRenderer.java index 278cd1f1e4e2..893b28f93dac 100644 --- a/platform/lang-impl/src/com/intellij/ide/util/PsiElementListCellRenderer.java +++ b/platform/lang-impl/src/com/intellij/ide/util/PsiElementListCellRenderer.java @@ -99,12 +99,11 @@ public abstract class PsiElementListCellRenderer extends J } } - SimpleTextAttributes nameAttributes; if (isProblemFile) { attributes = TextAttributes.merge(new TextAttributes(color, null, Color.red, EffectType.WAVE_UNDERSCORE, Font.PLAIN),attributes); } - nameAttributes = attributes != null ? SimpleTextAttributes.fromTextAttributes(attributes):null; + SimpleTextAttributes nameAttributes = attributes != null ? SimpleTextAttributes.fromTextAttributes(attributes) : null; if (nameAttributes == null) nameAttributes = new SimpleTextAttributes(Font.PLAIN, color); diff --git a/platform/lang-impl/src/com/intellij/injected/editor/DocumentWindowImpl.java b/platform/lang-impl/src/com/intellij/injected/editor/DocumentWindowImpl.java index df80a4c384e5..7ea0829403ab 100644 --- a/platform/lang-impl/src/com/intellij/injected/editor/DocumentWindowImpl.java +++ b/platform/lang-impl/src/com/intellij/injected/editor/DocumentWindowImpl.java @@ -27,6 +27,7 @@ import com.intellij.openapi.editor.ex.*; import com.intellij.openapi.editor.impl.DocumentImpl; import com.intellij.openapi.editor.markup.MarkupModel; import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Pair; import com.intellij.openapi.util.ProperTextRange; import com.intellij.openapi.util.TextRange; import com.intellij.openapi.util.UserDataHolderBase; @@ -40,8 +41,8 @@ import org.jetbrains.annotations.Nullable; import java.beans.PropertyChangeListener; import java.util.ArrayList; -import java.util.List; import java.util.Collections; +import java.util.List; /** * @author Alexey @@ -254,9 +255,47 @@ public class DocumentWindowImpl extends UserDataHolderBase implements Disposable startOffset += perfixLength; endOffset -= suffixLength; s = s.subSequence(perfixLength, s.length() - suffixLength); - - deleteString(startOffset, endOffset); - insertString(startOffset, s); + + doReplaceString(startOffset, endOffset, s); + } + + private void doReplaceString(int startOffset, int endOffset, CharSequence s) { + assert intersectWithEditable(new TextRange(startOffset, startOffset)) != null; + assert intersectWithEditable(new TextRange(endOffset, endOffset)) != null; + + List> hostRangesToModify = new ArrayList>(myShreds.size()); + + int offset = startOffset; + int curRangeStart = 0; + for (int i = 0; i < myShreds.size(); i++) { + PsiLanguageInjectionHost.Shred shred = myShreds.get(i); + curRangeStart += shred.prefix.length(); + if (offset < curRangeStart) offset = curRangeStart; + RangeMarker hostRange = shred.getHostRangeMarker(); + if (!hostRange.isValid()) continue; + int hostRangeLength = hostRange.getEndOffset() - hostRange.getStartOffset(); + TextRange range = TextRange.from(curRangeStart, hostRangeLength); + if (range.contains(offset) || range.getEndOffset() == offset/* in case of inserting at the end*/) { + TextRange rangeToModify = new TextRange(offset, Math.min(range.getEndOffset(), endOffset)); + TextRange hostRangeToModify = rangeToModify.shiftRight(hostRange.getStartOffset() - curRangeStart); + CharSequence toReplace = i == myShreds.size() - 1 ? s : s.subSequence(0, Math.min(hostRangeToModify.getLength(), s.length())); + s = s.subSequence(toReplace.length(), s.length()); + hostRangesToModify.add(Pair.create(hostRangeToModify, toReplace)); + offset = rangeToModify.getEndOffset(); + } + curRangeStart += hostRangeLength; + curRangeStart += shred.suffix.length(); + if (curRangeStart >= endOffset) break; + } + + int delta = 0; + for (Pair pair : hostRangesToModify) { + TextRange hostRange = pair.getFirst(); + CharSequence replace = pair.getSecond(); + + myDelegate.replaceString(hostRange.getStartOffset() + delta, hostRange.getEndOffset() + delta, replace); + delta -= hostRange.getLength() - replace.length(); + } } public boolean isWritable() { diff --git a/platform/lang-impl/src/com/intellij/openapi/editor/actions/SelectWordAtCaretAction.java b/platform/lang-impl/src/com/intellij/openapi/editor/actions/SelectWordAtCaretAction.java index c7117d616122..1b1cf675cf1d 100644 --- a/platform/lang-impl/src/com/intellij/openapi/editor/actions/SelectWordAtCaretAction.java +++ b/platform/lang-impl/src/com/intellij/openapi/editor/actions/SelectWordAtCaretAction.java @@ -25,11 +25,16 @@ package com.intellij.openapi.editor.actions; import com.intellij.codeInsight.editorActions.SelectWordUtil; +import com.intellij.codeInsight.highlighting.BraceMatchingUtil; import com.intellij.openapi.actionSystem.DataContext; import com.intellij.openapi.editor.*; import com.intellij.openapi.editor.actionSystem.EditorActionHandler; -import com.intellij.openapi.util.TextRange; +import com.intellij.openapi.editor.ex.EditorEx; +import com.intellij.openapi.editor.highlighter.HighlighterIterator; import com.intellij.openapi.project.DumbAware; +import com.intellij.openapi.util.TextRange; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.util.text.CharArrayUtil; import java.util.ArrayList; import java.util.List; @@ -111,7 +116,23 @@ public class SelectWordAtCaretAction extends TextComponentEditorAction implement private static void selectWithGuide(Editor editor, IndentGuideDescriptor guide) { final Document doc = editor.getDocument(); int startOffset = editor.logicalPositionToOffset(new LogicalPosition(guide.startLine, 0)); - int endOffset = Math.min(editor.logicalPositionToOffset(new LogicalPosition(guide.endLine + 1, 0)), doc.getTextLength()); + int endOffset = Math.min(doc.getLineStartOffset(guide.endLine), doc.getTextLength()); + + final VirtualFile file = ((EditorEx)editor).getVirtualFile(); + if (file != null) { + // Make sure selection contains closing matching brace. + + final CharSequence chars = doc.getCharsSequence(); + int nonWhitespaceOffset = CharArrayUtil.shiftForward(chars, endOffset, " \t\n"); + HighlighterIterator iterator = ((EditorEx)editor).getHighlighter().createIterator(nonWhitespaceOffset); + if (BraceMatchingUtil.isRBraceToken(iterator, chars, file.getFileType())) { + if (((EditorEx)editor).calcColumnNumber(iterator.getStart(), doc.getLineNumber(iterator.getStart())) == guide.indentLevel) { + endOffset = iterator.getEnd(); + endOffset = CharArrayUtil.shiftForward(chars, endOffset, " \t"); + if (endOffset < chars.length() && chars.charAt(endOffset) == '\n') endOffset++; + } + } + } editor.getSelectionModel().setSelection(startOffset, endOffset); } diff --git a/platform/lang-impl/src/com/intellij/packageDependencies/DefaultScopesProvider.java b/platform/lang-impl/src/com/intellij/packageDependencies/DefaultScopesProvider.java index 1e1f0d67afb5..6a080f33fed4 100644 --- a/platform/lang-impl/src/com/intellij/packageDependencies/DefaultScopesProvider.java +++ b/platform/lang-impl/src/com/intellij/packageDependencies/DefaultScopesProvider.java @@ -32,7 +32,6 @@ import java.util.ArrayList; import java.util.List; public class DefaultScopesProvider implements CustomScopesProvider { - private static NamedScope myAllScope; private NamedScope myProblemsScope; private final Project myProject; @@ -55,27 +54,28 @@ public class DefaultScopesProvider implements CustomScopesProvider { return list; } + private static class NamedScopeHolder { + private static final NamedScope myAllScope = new NamedScope("All", new PackageSet() { + public boolean contains(final PsiFile file, final NamedScopesHolder holder) { + return true; + } + + public PackageSet createCopy() { + return this; + } + + public String getText() { + return FilePatternPackageSet.SCOPE_FILE + ":*//*"; + } + + public int getNodePriority() { + return 0; + } + }); + } + public static NamedScope getAllScope() { - if (myAllScope == null) { - myAllScope = new NamedScope("All", new PackageSet() { - public boolean contains(final PsiFile file, final NamedScopesHolder holder) { - return true; - } - - public PackageSet createCopy() { - return this; - } - - public String getText() { - return FilePatternPackageSet.SCOPE_FILE + ":*//*"; - } - - public int getNodePriority() { - return 0; - } - }); - } - return myAllScope; + return NamedScopeHolder.myAllScope; } public NamedScope getProblemsScope() { @@ -100,4 +100,4 @@ public class DefaultScopesProvider implements CustomScopesProvider { } return myProblemsScope; } -} \ No newline at end of file +} diff --git a/platform/lang-impl/src/com/intellij/psi/formatter/FormattingDocumentModelImpl.java b/platform/lang-impl/src/com/intellij/psi/formatter/FormattingDocumentModelImpl.java index 84d0cf06351e..2afe6c2464ee 100644 --- a/platform/lang-impl/src/com/intellij/psi/formatter/FormattingDocumentModelImpl.java +++ b/platform/lang-impl/src/com/intellij/psi/formatter/FormattingDocumentModelImpl.java @@ -26,6 +26,7 @@ import com.intellij.psi.PsiDocumentManager; import com.intellij.psi.PsiFile; import com.intellij.psi.impl.PsiDocumentManagerImpl; import com.intellij.psi.impl.PsiToDocumentSynchronizer; +import org.jetbrains.annotations.NotNull; public class FormattingDocumentModelImpl implements FormattingDocumentModel{ @@ -56,7 +57,7 @@ public class FormattingDocumentModelImpl implements FormattingDocumentModel{ } - private static Document getDocumentToBeUsedFor(final PsiFile file) { + public static Document getDocumentToBeUsedFor(final PsiFile file) { final Project project = file.getProject(); final Document document = PsiDocumentManager.getInstance(project).getDocument(file); if (document == null) return null; @@ -91,4 +92,11 @@ public class FormattingDocumentModelImpl implements FormattingDocumentModel{ public PsiFile getFile() { return myFile; } + + public static boolean canUseDocumentModel(@NotNull Document document,@NotNull PsiFile file) { + PsiDocumentManager psiDocumentManager = PsiDocumentManager.getInstance(file.getProject()); + return !psiDocumentManager.isUncommited(document) && + !psiDocumentManager.isDocumentBlockedByPsi(document) && + file.getText().equals(document.getText()); + } } diff --git a/platform/lang-impl/src/com/intellij/psi/impl/search/PsiSearchHelperImpl.java b/platform/lang-impl/src/com/intellij/psi/impl/search/PsiSearchHelperImpl.java index e24fdb3b6ad7..e5747b88b7e1 100644 --- a/platform/lang-impl/src/com/intellij/psi/impl/search/PsiSearchHelperImpl.java +++ b/platform/lang-impl/src/com/intellij/psi/impl/search/PsiSearchHelperImpl.java @@ -158,11 +158,11 @@ public class PsiSearchHelperImpl implements PsiSearchHelper { return processElementsWithWord(occurenceProcessor, searchScope, identifier, UsageSearchContext.IN_COMMENTS, true); } - public boolean processElementsWithWord(@NotNull TextOccurenceProcessor processor, + public boolean processElementsWithWord(@NotNull final TextOccurenceProcessor processor, @NotNull SearchScope searchScope, - @NotNull String text, + @NotNull final String text, short searchContext, - boolean caseSensitively) { + final boolean caseSensitively) { if (text.length() == 0) { throw new IllegalArgumentException("Cannot search for elements with empty text"); } @@ -179,10 +179,11 @@ public class PsiSearchHelperImpl implements PsiSearchHelper { PsiElement[] scopeElements = scope.getScope(); final boolean ignoreInjectedPsi = scope.isIgnoreInjectedPsi(); - for (final PsiElement scopeElement : scopeElements) { - if (!processElementsWithWordInScopeElement(scopeElement, processor, text, caseSensitively, ignoreInjectedPsi)) return false; - } - return true; + return JobUtil.invokeConcurrentlyUnderMyProgress(Arrays.asList(scopeElements), new Processor() { + public boolean process(PsiElement scopeElement) { + return processElementsWithWordInScopeElement(scopeElement, processor, text, caseSensitively, ignoreInjectedPsi); + } + }, "textsearch"); } } @@ -200,7 +201,7 @@ public class PsiSearchHelperImpl implements PsiSearchHelper { }).booleanValue(); } - private boolean processElementsWithTextInGlobalScope(final TextOccurenceProcessor processor, + private boolean processElementsWithTextInGlobalScope(@NotNull final TextOccurenceProcessor processor, final GlobalSearchScope scope, final StringSearcher searcher, final short searchContext, @@ -424,10 +425,7 @@ public class PsiSearchHelperImpl implements PsiSearchHelper { myManager.getCacheManager().processFilesWithWord(processor, word, UsageSearchContext.IN_STRINGS, scope, true); } - public static enum Result { - ZERO_OCCURRENCES, FEW_OCCURRENCES, TOO_MANY_OCCURRENCES - } - public Result isItCheapEnoughToSearch(@Nullable final PsiFile fileToIgnoreOccurencesIn, @NotNull String name, @NotNull GlobalSearchScope scope) { + public SearchCostResult isCheapEnoughToSearch(@NotNull String name, @NotNull GlobalSearchScope scope, @Nullable final PsiFile fileToIgnoreOccurencesIn) { final int[] count = {0}; if (!processFilesWithText(scope, UsageSearchContext.ANY, true, name, new Processor() { public boolean process(PsiFile file) { @@ -438,11 +436,11 @@ public class PsiSearchHelperImpl implements PsiSearchHelper { } } })) { - return Result.TOO_MANY_OCCURRENCES; + return SearchCostResult.TOO_MANY_OCCURRENCES; } synchronized (count) { - return count[0] == 0 ? Result.ZERO_OCCURRENCES : Result.FEW_OCCURRENCES; + return count[0] == 0 ? SearchCostResult.ZERO_OCCURRENCES : SearchCostResult.FEW_OCCURRENCES; } } } diff --git a/platform/lang-impl/src/com/intellij/util/indexing/FileBasedIndex.java b/platform/lang-impl/src/com/intellij/util/indexing/FileBasedIndex.java index 61dbf0e63879..4de538b56949 100644 --- a/platform/lang-impl/src/com/intellij/util/indexing/FileBasedIndex.java +++ b/platform/lang-impl/src/com/intellij/util/indexing/FileBasedIndex.java @@ -287,13 +287,12 @@ public class FileBasedIndex implements ApplicationComponent { } } - private static FileBasedIndex ourInstance = CachedSingletonsRegistry.markCachedField(FileBasedIndex.class); - public static FileBasedIndex getInstance() { - if (ourInstance == null) { - ourInstance = ApplicationManager.getApplication().getComponent(FileBasedIndex.class); - } + private static class FileBasedIndexHolder { + private static final FileBasedIndex ourInstance = ApplicationManager.getApplication().getComponent(FileBasedIndex.class); + } - return ourInstance; + public static FileBasedIndex getInstance() { + return FileBasedIndexHolder.ourInstance; } /** diff --git a/platform/lvcs-impl/src/com/intellij/history/integration/patches/PatchCreator.java b/platform/lvcs-impl/src/com/intellij/history/integration/patches/PatchCreator.java index 7994f5961fa1..2f4dc002f43f 100644 --- a/platform/lvcs-impl/src/com/intellij/history/integration/patches/PatchCreator.java +++ b/platform/lvcs-impl/src/com/intellij/history/integration/patches/PatchCreator.java @@ -18,7 +18,7 @@ package com.intellij.history.integration.patches; import com.intellij.history.integration.IdeaGateway; import com.intellij.openapi.diff.impl.patch.FilePatch; -import com.intellij.openapi.diff.impl.patch.PatchBuilder; +import com.intellij.openapi.diff.impl.patch.TextPatchBuilder; import com.intellij.openapi.diff.impl.patch.UnifiedDiffWriter; import com.intellij.openapi.vcs.VcsException; import com.intellij.openapi.vcs.changes.Change; @@ -35,7 +35,7 @@ public class PatchCreator { throws IOException, VcsException { Writer writer = new OutputStreamWriter(new FileOutputStream(filePath)); try { - List patches = PatchBuilder.buildPatch(changes, gw.getBaseDir(), false, isReverse); + List patches = TextPatchBuilder.buildPatch(changes, gw.getBaseDir(), isReverse); String lineSeparator = CodeStyleSettingsManager.getInstance(gw.getProject()).getCurrentSettings().getLineSeparator(); UnifiedDiffWriter.write(patches, writer, lineSeparator); } diff --git a/platform/lvcs-impl/testSrc/com/intellij/historyIntegrTests/PatchingTestCase.java b/platform/lvcs-impl/testSrc/com/intellij/historyIntegrTests/PatchingTestCase.java index 6d89eb3df885..a36384218eb1 100644 --- a/platform/lvcs-impl/testSrc/com/intellij/historyIntegrTests/PatchingTestCase.java +++ b/platform/lvcs-impl/testSrc/com/intellij/historyIntegrTests/PatchingTestCase.java @@ -16,8 +16,10 @@ package com.intellij.historyIntegrTests; +import com.intellij.openapi.diff.impl.patch.BinaryFilePatch; import com.intellij.openapi.diff.impl.patch.FilePatch; import com.intellij.openapi.diff.impl.patch.PatchReader; +import com.intellij.openapi.diff.impl.patch.PatchVirtualFileReader; import com.intellij.openapi.diff.impl.patch.formove.PatchApplier; import com.intellij.openapi.vfs.VirtualFile; @@ -44,7 +46,7 @@ public abstract class PatchingTestCase extends IntegrationTestCase { protected void applyPatch() throws Exception { List patches = new ArrayList(); - PatchReader reader = new PatchReader(getFS().refreshAndFindFileByPath(patchFilePath)); + PatchReader reader = PatchVirtualFileReader.create(getFS().refreshAndFindFileByPath(patchFilePath)); while (true) { FilePatch p = reader.readNextPatch(); @@ -52,6 +54,6 @@ public abstract class PatchingTestCase extends IntegrationTestCase { patches.add(p); } - new PatchApplier(myProject, root, patches, null, null).execute(); + new PatchApplier(myProject, root, patches, null, null).execute(); } } diff --git a/platform/platform-api/src/com/intellij/execution/process/OSProcessHandler.java b/platform/platform-api/src/com/intellij/execution/process/OSProcessHandler.java index bdc7a7dfd412..8668e4d292a7 100644 --- a/platform/platform-api/src/com/intellij/execution/process/OSProcessHandler.java +++ b/platform/platform-api/src/com/intellij/execution/process/OSProcessHandler.java @@ -91,18 +91,19 @@ public class OSProcessHandler extends ProcessHandler { public ProcessWaitFor(final Process process) { myWaitSemaphore.down(); - final Runnable action = new Runnable() { + + myWaitForThreadFuture = executeOnPooledThread(new Runnable() { public void run() { try { myExitCode = process.waitFor(); } catch (InterruptedException ignored) { } - myWaitSemaphore.up(); + finally { + myWaitSemaphore.up(); + } } - }; - - myWaitForThreadFuture = executeOnPooledThread(action); + }); } public int waitFor() { diff --git a/platform/platform-api/src/com/intellij/openapi/fileTypes/FileTypeManager.java b/platform/platform-api/src/com/intellij/openapi/fileTypes/FileTypeManager.java index 9b8400aa8a10..873acb6bbcc4 100644 --- a/platform/platform-api/src/com/intellij/openapi/fileTypes/FileTypeManager.java +++ b/platform/platform-api/src/com/intellij/openapi/fileTypes/FileTypeManager.java @@ -17,7 +17,6 @@ package com.intellij.openapi.fileTypes; import com.intellij.openapi.application.Application; import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.application.CachedSingletonsRegistry; import com.intellij.openapi.vfs.VirtualFile; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; @@ -31,18 +30,22 @@ import java.util.List; */ public abstract class FileTypeManager{ - private static FileTypeManager ourInstance = CachedSingletonsRegistry.markCachedField(FileTypeManager.class); + private static class FileTypeManagerHolder { + private static final FileTypeManager ourInstance = createFileTypeManager(); + } + /** * Returns the singleton instance of the FileTypeManager component. * * @return the instace of FileTypeManager */ public static FileTypeManager getInstance() { - if (ourInstance == null) { - Application app = ApplicationManager.getApplication(); - ourInstance = app != null ? app.getComponent(FileTypeManager.class) : new MockFileTypeManager(); - } - return ourInstance; + return FileTypeManagerHolder.ourInstance; + } + + private static FileTypeManager createFileTypeManager() { + Application app = ApplicationManager.getApplication(); + return app == null ? new MockFileTypeManager() : app.getComponent(FileTypeManager.class); } public abstract void registerFileType(@NotNull FileType type, @NotNull List defaultAssociations); diff --git a/platform/platform-api/src/com/intellij/openapi/ui/MasterDetailsComponent.java b/platform/platform-api/src/com/intellij/openapi/ui/MasterDetailsComponent.java index a1f8687aefda..7707e58c53d4 100644 --- a/platform/platform-api/src/com/intellij/openapi/ui/MasterDetailsComponent.java +++ b/platform/platform-api/src/com/intellij/openapi/ui/MasterDetailsComponent.java @@ -777,6 +777,10 @@ public abstract class MasterDetailsComponent implements Configurable, Persistent public boolean isDisplayInBold() { return myDisplayInBold; } + + public void setDisplayInBold(boolean displayInBold) { + myDisplayInBold = displayInBold; + } } @SuppressWarnings({"ConstantConditions"}) diff --git a/platform/platform-api/src/com/intellij/openapi/util/process/ProcessCloseUtil.java b/platform/platform-api/src/com/intellij/openapi/util/process/ProcessCloseUtil.java index 7081c5fa1beb..6f6db5543ffd 100644 --- a/platform/platform-api/src/com/intellij/openapi/util/process/ProcessCloseUtil.java +++ b/platform/platform-api/src/com/intellij/openapi/util/process/ProcessCloseUtil.java @@ -19,8 +19,8 @@ import com.intellij.openapi.application.Application; import com.intellij.openapi.application.ApplicationManager; import com.intellij.util.concurrency.Semaphore; -import java.util.concurrent.Future; import java.io.IOException; +import java.util.concurrent.Future; public class ProcessCloseUtil { private static final long ourSynchronousWaitTimeout = 1000; @@ -34,25 +34,32 @@ public class ProcessCloseUtil { outerSemaphore.down(); final Application application = ApplicationManager.getApplication(); - final Future future = application.executeOnPooledThread(new Runnable() { + application.executeOnPooledThread(new Runnable() { public void run() { - final Semaphore semaphore = new Semaphore(); - semaphore.down(); + try { + final Semaphore semaphore = new Semaphore(); + semaphore.down(); - final Runnable closeRunnable = new Runnable() { - public void run() { - closeProcessImpl(process); - semaphore.up(); + final Runnable closeRunnable = new Runnable() { + public void run() { + try { + closeProcessImpl(process); + } + finally { + semaphore.up(); + } + } + }; + + final Future innerFuture = application.executeOnPooledThread(closeRunnable); + semaphore.waitFor(ourAsynchronousWaitTimeout); + if ( ! (innerFuture.isDone() || innerFuture.isCancelled())) { + innerFuture.cancel(true); // will call interrupt() } - }; - - final Future innerFuture = application.executeOnPooledThread(closeRunnable); - semaphore.waitFor(ourAsynchronousWaitTimeout); - if ( ! (innerFuture.isDone() || innerFuture.isCancelled())) { - innerFuture.cancel(true); // will call interrupt() } - - outerSemaphore.up(); + finally { + outerSemaphore.up(); + } } }); diff --git a/platform/platform-api/src/com/intellij/openapi/vfs/newvfs/ManagingFS.java b/platform/platform-api/src/com/intellij/openapi/vfs/newvfs/ManagingFS.java index bc3122889a53..9793edf8447d 100644 --- a/platform/platform-api/src/com/intellij/openapi/vfs/newvfs/ManagingFS.java +++ b/platform/platform-api/src/com/intellij/openapi/vfs/newvfs/ManagingFS.java @@ -30,13 +30,13 @@ import java.io.DataOutputStream; import java.util.List; public abstract class ManagingFS implements FileSystemInterface { - private static ManagingFS ourInstance = CachedSingletonsRegistry.markCachedField(ManagingFS.class); + + private static class ManagingFSHolder { + private static final ManagingFS ourInstance = ApplicationManager.getApplication().getComponent(ManagingFS.class); + } public static ManagingFS getInstance() { - if (ourInstance == null) { - ourInstance = ApplicationManager.getApplication().getComponent(ManagingFS.class); - } - return ourInstance; + return ManagingFSHolder.ourInstance; } @Nullable diff --git a/platform/platform-api/src/com/intellij/util/ui/DialogUtil.java b/platform/platform-api/src/com/intellij/util/ui/DialogUtil.java index a40134fee4f7..a09c07867f41 100644 --- a/platform/platform-api/src/com/intellij/util/ui/DialogUtil.java +++ b/platform/platform-api/src/com/intellij/util/ui/DialogUtil.java @@ -25,7 +25,12 @@ import javax.swing.*; * @since 5.1 */ public class DialogUtil { + public static void registerMnemonic(AbstractButton button) { + registerMnemonic(button, UIUtil.MNEMONIC); + } + + public static void registerMnemonic(AbstractButton button, char mn) { String text = button.getText(); if (text != null) { @@ -34,7 +39,7 @@ public class DialogUtil { int index = -1; for (int i = 0; i < text.length(); i++) { char ch = text.charAt(i); - if (ch != UIUtil.MNEMONIC) { + if (ch != mn) { realText.append(ch); } else if (i + 1 < text.length()) { diff --git a/platform/platform-api/src/com/intellij/util/ui/OptionsDialog.java b/platform/platform-api/src/com/intellij/util/ui/OptionsDialog.java index f1b84baca9c1..a3fd2f94eb6a 100644 --- a/platform/platform-api/src/com/intellij/util/ui/OptionsDialog.java +++ b/platform/platform-api/src/com/intellij/util/ui/OptionsDialog.java @@ -28,7 +28,7 @@ import java.awt.*; public abstract class OptionsDialog extends DialogWrapper { - private JCheckBox myCheckBoxDoNotShowDialog; + protected JCheckBox myCheckBoxDoNotShowDialog; protected String getDoNotShowMessage() { return CommonBundle.message("dialog.options.do.not.show"); @@ -68,6 +68,7 @@ public abstract class OptionsDialog extends DialogWrapper { final JPanel panel = addDoNotShowCheckBox(southPanel, myCheckBoxDoNotShowDialog); myCheckBoxDoNotShowDialog.setSelected(!isToBeShown()); + DialogUtil.registerMnemonic(myCheckBoxDoNotShowDialog, '&'); return panel; } diff --git a/platform/platform-impl/src/com/intellij/ide/actions/OpenProjectFileChooserDescriptor.java b/platform/platform-impl/src/com/intellij/ide/actions/OpenProjectFileChooserDescriptor.java index e527436d6d7a..8248da96c861 100644 --- a/platform/platform-impl/src/com/intellij/ide/actions/OpenProjectFileChooserDescriptor.java +++ b/platform/platform-impl/src/com/intellij/ide/actions/OpenProjectFileChooserDescriptor.java @@ -19,6 +19,7 @@ import com.intellij.ide.highlighter.ProjectFileType; import com.intellij.ide.impl.ProjectUtil; import com.intellij.openapi.application.impl.ApplicationInfoImpl; import com.intellij.openapi.fileChooser.FileChooserDescriptor; +import com.intellij.openapi.fileChooser.FileElement; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.IconLoader; import com.intellij.openapi.vfs.VirtualFile; @@ -66,6 +67,7 @@ public class OpenProjectFileChooserDescriptor extends FileChooserDescriptor { } public boolean isFileVisible(final VirtualFile file, final boolean showHiddenFiles) { + if (!showHiddenFiles && FileElement.isFileHidden(file)) return false; return isProjectFile(file) || super.isFileVisible(file, showHiddenFiles) && file.isDirectory(); } diff --git a/platform/platform-impl/src/com/intellij/ide/ui/search/SearchUtil.java b/platform/platform-impl/src/com/intellij/ide/ui/search/SearchUtil.java index cb28a0720c59..f2642492b77c 100644 --- a/platform/platform-impl/src/com/intellij/ide/ui/search/SearchUtil.java +++ b/platform/platform-impl/src/com/intellij/ide/ui/search/SearchUtil.java @@ -16,13 +16,13 @@ package com.intellij.ide.ui.search; -import com.intellij.openapi.application.Application; -import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.options.Configurable; +import com.intellij.openapi.options.ConfigurableGroup; import com.intellij.openapi.options.MasterDetails; import com.intellij.openapi.options.SearchableConfigurable; -import com.intellij.openapi.options.ConfigurableGroup; import com.intellij.openapi.options.ex.GlassPanel; +import com.intellij.openapi.options.ex.IdeConfigurablesGroup; +import com.intellij.openapi.options.ex.ProjectConfigurablesGroup; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.popup.JBPopup; import com.intellij.openapi.ui.popup.JBPopupFactory; @@ -62,11 +62,8 @@ public class SearchUtil { } public static void processProjectConfigurables(Project project, HashMap> options) { - processConfigurables(project.getExtensions(Configurable.PROJECT_CONFIGURABLES), options); - processConfigurables(project.getComponents(Configurable.class), options); - final Application app = ApplicationManager.getApplication(); - processConfigurables(app.getExtensions(Configurable.APPLICATION_CONFIGURABLES), options); - processConfigurables(app.getComponents(Configurable.class), options); + processConfigurables(new ProjectConfigurablesGroup(project, false).getConfigurables(), options); + processConfigurables(new IdeConfigurablesGroup().getConfigurables(), options); } private static void processConfigurables(final Configurable[] configurables, diff --git a/platform/platform-impl/src/com/intellij/ide/ui/search/SearchableOptionsRegistrarImpl.java b/platform/platform-impl/src/com/intellij/ide/ui/search/SearchableOptionsRegistrarImpl.java index 6c29be7e3760..6f18ae4accee 100644 --- a/platform/platform-impl/src/com/intellij/ide/ui/search/SearchableOptionsRegistrarImpl.java +++ b/platform/platform-impl/src/com/intellij/ide/ui/search/SearchableOptionsRegistrarImpl.java @@ -157,7 +157,7 @@ public class SearchableOptionsRegistrarImpl extends SearchableOptionsRegistrar { Set configs = myStorage.get(option); if (configs == null) { configs = new THashSet(3, 0.9f); - myStorage.put(option, configs); + myStorage.put(new String(option), configs); } configs.add(new OptionDescription(null, myIdentifierTable.intern(id), hit != null ? myIdentifierTable.intern(hit) : null, diff --git a/platform/platform-impl/src/com/intellij/openapi/command/impl/DocumentReferenceByVirtualFile.java b/platform/platform-impl/src/com/intellij/openapi/command/impl/DocumentReferenceByVirtualFile.java index 25c33c45aa49..dc2c2643658f 100644 --- a/platform/platform-impl/src/com/intellij/openapi/command/impl/DocumentReferenceByVirtualFile.java +++ b/platform/platform-impl/src/com/intellij/openapi/command/impl/DocumentReferenceByVirtualFile.java @@ -31,7 +31,7 @@ public class DocumentReferenceByVirtualFile implements DocumentReference { @Nullable public Document getDocument() { - assert myFile.isValid() : "should not be called on references to deleted files"; + assert myFile.isValid() : "should not be called on references to deleted file: " + myFile; return FileDocumentManager.getInstance().getDocument(myFile); } diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/impl/IndentsModelImpl.java b/platform/platform-impl/src/com/intellij/openapi/editor/impl/IndentsModelImpl.java index ddfa1218e4c3..59527d4d3734 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/impl/IndentsModelImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/impl/IndentsModelImpl.java @@ -19,8 +19,9 @@ */ package com.intellij.openapi.editor.impl; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.editor.*; +import com.intellij.openapi.editor.IndentGuideDescriptor; +import com.intellij.openapi.editor.IndentsModel; +import com.intellij.openapi.editor.LogicalPosition; import java.util.ArrayList; import java.util.List; @@ -37,9 +38,12 @@ public class IndentsModelImpl implements IndentsModel { final LogicalPosition pos = myEditor.getCaretModel().getLogicalPosition(); final int column = pos.column; final int line = pos.line; - for (IndentGuideDescriptor indent : myIndents) { - if (column == indent.indentLevel && line >= indent.startLine && line < indent.endLine) { - return indent; + + if (column > 0) { + for (IndentGuideDescriptor indent : myIndents) { + if (column == indent.indentLevel && line >= indent.startLine && line < indent.endLine) { + return indent; + } } } return null; diff --git a/platform/platform-impl/src/com/intellij/openapi/fileChooser/ex/FileSaverDialogImpl.java b/platform/platform-impl/src/com/intellij/openapi/fileChooser/ex/FileSaverDialogImpl.java index 7bd17500ab7e..c6a3ba190648 100644 --- a/platform/platform-impl/src/com/intellij/openapi/fileChooser/ex/FileSaverDialogImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/fileChooser/ex/FileSaverDialogImpl.java @@ -15,10 +15,11 @@ */ package com.intellij.openapi.fileChooser.ex; -import com.intellij.openapi.fileChooser.FileSystemTree; import com.intellij.openapi.fileChooser.FileSaverDescriptor; import com.intellij.openapi.fileChooser.FileSaverDialog; +import com.intellij.openapi.fileChooser.FileSystemTree; import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.Messages; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.vfs.VirtualFileWrapper; import com.intellij.ui.DocumentAdapter; @@ -160,4 +161,18 @@ public class FileSaverDialogImpl extends FileChooserDialogImpl implements FileSa //double check. FileChooserFactoryImpl sets enable ok button super.setOKActionEnabled(isFileNameExist()); } + + @Override + protected void doOKAction() { + final File file = getFile(); + if (file != null && file.exists()) { + if (OK_EXIT_CODE != Messages.showYesNoDialog(this.getRootPane(), + UIBundle.message("file.chooser.save.dialog.confirmation", file.getName()), + UIBundle.message("file.chooser.save.dialog.confirmation.title"), + Messages.getWarningIcon())) { + return; + } + } + super.doOKAction(); + } } diff --git a/platform/platform-impl/src/com/intellij/openapi/progress/impl/ProgressManagerImpl.java b/platform/platform-impl/src/com/intellij/openapi/progress/impl/ProgressManagerImpl.java index 54fd5ed11639..d5e66819e3b0 100644 --- a/platform/platform-impl/src/com/intellij/openapi/progress/impl/ProgressManagerImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/progress/impl/ProgressManagerImpl.java @@ -66,7 +66,7 @@ public class ProgressManagerImpl extends ProgressManager { } } }; - thread.setPriority(Thread.MIN_PRIORITY + 1); + thread.setPriority(Thread.MAX_PRIORITY - 1); thread.start(); } } diff --git a/platform/platform-impl/src/com/intellij/openapi/progress/util/SmoothProgressAdapter.java b/platform/platform-impl/src/com/intellij/openapi/progress/util/SmoothProgressAdapter.java index 7b22b3b1e736..950d6ea57978 100644 --- a/platform/platform-impl/src/com/intellij/openapi/progress/util/SmoothProgressAdapter.java +++ b/platform/platform-impl/src/com/intellij/openapi/progress/util/SmoothProgressAdapter.java @@ -142,8 +142,12 @@ public class SmoothProgressAdapter extends BlockingProgressIndicator { } ); - super.stop(); // should be last to not leaveModal before closing the dialog - semaphore.up(); + try { + super.stop(); // should be last to not leaveModal before closing the dialog + } + finally { + semaphore.up(); + } } public synchronized void setText(String text) { diff --git a/platform/platform-impl/src/com/intellij/openapi/project/DumbServiceImpl.java b/platform/platform-impl/src/com/intellij/openapi/project/DumbServiceImpl.java index 1d6fb06e5439..24005c0169ec 100644 --- a/platform/platform-impl/src/com/intellij/openapi/project/DumbServiceImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/project/DumbServiceImpl.java @@ -111,7 +111,7 @@ public class DumbServiceImpl extends DumbService { } try { final int size = runner.queryNeededFiles(indicator); - if ((size + runner.getNumberOfPendingUpdateJobs(indicator)) < 50) { + if (application.isHeadlessEnvironment() || (size + runner.getNumberOfPendingUpdateJobs(indicator)) < 50) { // If not that many files found, process them on the spot, avoiding entering dumb mode // Consider number of pending tasks as well, becase they may take noticeable time to process even if the number of files is small if (size > 0) { @@ -227,10 +227,10 @@ public class DumbServiceImpl extends DumbService { final Semaphore semaphore = new Semaphore(); semaphore.down(); runWhenSmart(new Runnable() { - public void run() { - semaphore.up(); - } - }); + public void run() { + semaphore.up(); + } + }); semaphore.waitFor(); } diff --git a/platform/platform-impl/src/com/intellij/openapi/project/FileContentQueue.java b/platform/platform-impl/src/com/intellij/openapi/project/FileContentQueue.java index a8f3a6d48633..fd508a67f21b 100644 --- a/platform/platform-impl/src/com/intellij/openapi/project/FileContentQueue.java +++ b/platform/platform-impl/src/com/intellij/openapi/project/FileContentQueue.java @@ -21,6 +21,8 @@ import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.progress.ProcessCanceledException; import com.intellij.openapi.progress.ProgressIndicator; import com.intellij.openapi.progress.ProgressManager; +import com.intellij.openapi.util.Computable; +import com.intellij.openapi.vfs.InvalidVirtualFileAccessException; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.vfs.newvfs.persistent.PersistentFS; import org.jetbrains.annotations.NotNull; @@ -74,60 +76,12 @@ public class FileContentQueue { } private void put(VirtualFile file) throws InterruptedException { - final ProgressIndicator indicator = ProgressManager.getInstance().getProgressIndicator(); - FileContent content = new FileContent(file); if (file.isValid()) { - final long contentLength = content.getLength(); - boolean counterUpdated = false; - try { - if (contentLength < PersistentFS.MAX_INTELLISENSE_FILESIZE) { - synchronized (this) { - while (myTotalSize > SIZE_THRESHOLD) { - if (indicator != null) { - indicator.checkCanceled(); - } - wait(300); - } - myTotalSize += contentLength; - counterUpdated = true; - } - - content.getBytes(); // Reads the content bytes and caches them. - } - } - catch (IOException e) { - LOG.info(e); - if (counterUpdated) { - synchronized (this) { - myTotalSize -= contentLength; // revert size counter - notifyAll(); - } - } + if (!doLoadContent(content)) { content.setEmptyContent(); } - catch (ProcessCanceledException e) { - if (counterUpdated) { - synchronized (this) { - myTotalSize -= contentLength; // revert size counter - notifyAll(); - } - } - throw e; - } - catch (InterruptedException e) { - if (counterUpdated) { - synchronized (this) { - myTotalSize -= contentLength; // revert size counter - notifyAll(); - } - } - return; - } - catch (Throwable e) { - LOG.error(e); - } } else { content.setEmptyContent(); @@ -136,6 +90,46 @@ public class FileContentQueue { myQueue.put(content); } + private boolean doLoadContent(final FileContent content) throws InterruptedException { + final ProgressIndicator indicator = ProgressManager.getInstance().getProgressIndicator(); + final long contentLength = content.getLength(); + + boolean counterUpdated = false; + try { + if (contentLength < PersistentFS.MAX_INTELLISENSE_FILESIZE) { + synchronized (this) { + while (myTotalSize > SIZE_THRESHOLD) { + if (indicator != null) { + indicator.checkCanceled(); + } + wait(300); + } + myTotalSize += contentLength; + counterUpdated = true; + } + + content.getBytes(); // Reads the content bytes and caches them. + } + + return true; + } + catch (Throwable e) { + if (counterUpdated) { + synchronized (this) { + myTotalSize -= contentLength; // revert size counter + notifyAll(); + } + } + if (e instanceof ProcessCanceledException) throw (ProcessCanceledException)e; + if (e instanceof InterruptedException) throw (InterruptedException)e; + + if (e instanceof IOException || e instanceof InvalidVirtualFileAccessException) LOG.info(e); + else LOG.error(e); + + return false; + } + } + public FileContent take() { FileContent result; synchronized (this) { diff --git a/platform/platform-impl/src/com/intellij/openapi/vfs/impl/VirtualFilePointerImpl.java b/platform/platform-impl/src/com/intellij/openapi/vfs/impl/VirtualFilePointerImpl.java index 697ce7a7cc9b..85a52d461d70 100644 --- a/platform/platform-impl/src/com/intellij/openapi/vfs/impl/VirtualFilePointerImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/vfs/impl/VirtualFilePointerImpl.java @@ -152,7 +152,7 @@ public class VirtualFilePointerImpl extends UserDataHolderBase implements Virtua myLastUpdated = fsModCount; if (myFile == null) { - LOG.assertTrue(myUrl != null); + LOG.assertTrue(myUrl != null, "Both file & url are null"); myFile = myVirtualFileManager.findFileByUrl(myUrl); if (myFile != null) { myUrl = null; diff --git a/platform/platform-resources-en/src/messages/IdeBundle.properties b/platform/platform-resources-en/src/messages/IdeBundle.properties index e45a14fc682f..c0cf915bbdad 100644 --- a/platform/platform-resources-en/src/messages/IdeBundle.properties +++ b/platform/platform-resources-en/src/messages/IdeBundle.properties @@ -1,4 +1,3 @@ -error.cannot.show.in.external.browser=Cannot show \"{0}\" in external browser title.cannot.start.browser=Cannot start browser error.malformed.url=Malformed url: {0} error.cannot.start.browser=Cannot start browser: {0} @@ -20,19 +19,15 @@ checkbox.transparent.native.to.ascii.conversion=Transparent native-to-ascii conv checkbox.autodetect.utf=Autodetect UTF-encoded files group.http.proxy=HTTP Proxy group.web.browser=Web Browser -radio.use.system.default.browser=Use system default browser -radio.use.specified.browser=Use group.general.options=General Options editbox.ignore.files.and.folders=Ignore files and folders: checkbox.confirm.application.exit=Confirm application exit -checkbox.use.cyclic.buffer.in.console=Limit run/debug consoles output to: label.inactive.timeout.sec= sec. search.in.the.background=Search in the background checkbox.save.files.automatically=Save files automatically if application is idle for checkbox.save.files.on.frame.deactivation=Save files on frame deactivation checkbox.synchronize.files.on.frame.activation=Synchronize files on frame activation checkbox.reopen.last.project.on.startup=Reopen last project on startup -label.kb= Kb. treenode.loading= loading... action.clear.list=_Clear List action.descriptor.action=Action: {0} @@ -59,17 +54,11 @@ button.remove=&Remove button.remove.all=Remove &All button.move.up=Move &Up button.move.down=Move &Down -column.bookmark=Bookmark column.description=Description -title.project.elements.bookmarks=Project Elements Bookmarks bookmark.file.X.line.Y={0}, line {1} button.view.source=&View Source -title.editor.bookmarks=Editor Bookmarks -bookmark.method.at.line={0}, {1}{2}, line {3} -action.show.bookmarks=_Show Bookmarks action.toggle.bookmark=Toggle _Bookmark action.toggle.bookmark.mnemonic=Toggle Bookmark With Mnemonic -action.set.bookmark=Set _Bookmark progress.deleting=Deleting select.in.commander=Commander errortree.information=Information: @@ -255,10 +244,7 @@ label.text.project.has.older.format=The project ''{0}'' has an older Old versions of project files will be saved to: ''{1}'' message.text.unlock.read.only.files=The following files are read only. IDEA will unlock them.
{0} error.message.cannot.make.files.writable=Cannot make the following files writable:\n{0} -label.text.project.was.succesfully.converted.old.version.was.saved.to.0=Your project was succesfully converted. \ -
Old versions of project files were saved to: ''{0}'' error.cannot.convert.project=Cannot convert project: {0} -error.some.file.is.corrupted.message=File ''{0}'' is corrupted:
{1} message.files.doesn.t.exists.0.so.the.corresponding.modules.won.t.be.converted.do.you.want.to.continue=The following files doesn''t exists:
\ {0}The corresponding modules won''t be converted. Do you want to continue? @@ -273,7 +259,6 @@ message.purchase.or.upgrade=To purchase {0}, refer to {1}.
To upgrade from a message.expiration.date=Expiration date: {0} message.educational.license=1-Year Educational License. {0} message.open.source.project.license=Open Source Project License. {0} -message.evaluation.expires=Evaluation expires: {0} message.non.commercial.use.only=Non-commercial use only message.personal.license=Personal License aboutbox.build.number=Build #{0} @@ -281,7 +266,6 @@ aboutbox.build.date=Built on {0} aboutbox.jdk=JDK: {0} aboutbox.vm=VM: {0} aboutbox.vendor=Vendor: {0} -message.license.you.will.need.upgrade=Your license key will expire on {0}.
You will need an upgrade to run {1}.
To learn more about upgrades, visit {2} title.warning=Warning message.upgrade.from.previous.required={0} requires an upgrade from previous versions.
You can purchase a license key from the JetBrains website at
{1} title.upgrade.needed=Upgrade Needed @@ -305,7 +289,6 @@ title.unable.to.save.data=Unable to Save Data link.click.here.to.license.server.info=More info link.purchase.commercial.license=To purchase a commercial license, please visit license.panel.current.license.description=The license will expire on {0,date,MMMM dd, yyyy} -license.panel.autodescovered.license.server.tile=Default license server license.panel.buildit.evaluation.expires.in=Remains {0} day(s) editbox.license.user.name=User name: editbox.license.license.key=License key: @@ -314,7 +297,6 @@ radio.license.data=Enter license &data radio.evaluate=&Evaluate for free for 30 days radio.default.license.server=&Default License Server action.activate.tool.window=Activate {0} window -action.change.splitter.orientations=Change Splitter O_rientations error.checkforupdates.connection.failed=Connection failed. Please check your network connection and try again. title.connection.error=Connection Error editbox.export.settings.to=Export settings to: @@ -336,7 +318,6 @@ action.create.annotation.type=Create @interface prompt.enter.annotation.type.name=Enter a new @interface name: title.new.annotation.type=New @interface title.cannot.create.annotation.type=Cannot Create @interface -progress.creating.annotation.type=Creating @interface {0}.{1} action.create.new.class=Create New Class action.create.new.filetype=Create New {0} action.description.create.new.file=Create New {0} @@ -363,10 +344,8 @@ progress.creating.package=Creating package {0}.{1} command.create.directory=Create directory command.create.package=Create package action.create.new.enum=Create New Enum -prompt.enter.new.enum.name=Enter a new enum name: title.new.enum=New Enum title.cannot.create.enum=Cannot Create Enum -progress.creating.enum=Creating enum {0}.{1} command.create.enum=Create enum action.create.new.file=Create New File prompt.enter.new.file.name=Enter a new file name: @@ -432,7 +411,6 @@ command.find.previous=Find Previous command.select.all=Select All message.no.targets.available=No targets available in this context title.popup.select.target=Select Target -title.popup.select.subtarget=Select in {0} title.popup.recent.files=Recent Files action.split.vertically=Split _Vertically action.split.horizontally=Split Hori_zontally @@ -452,8 +430,6 @@ action.toolwindow.hierarchy=Hierarch&y action.toolwindow.todo=TODO action.toolwindow.inspection=I&nspection action.toolwindow.favorites=Favorites -action.unsplit=Un_split -action.unsplit.all=U_nsplit All macro.classpath.entry=Entry in the classpath the element belongs to macro.project.classpath=Project's classpath macro.column.number=Column number @@ -633,7 +609,6 @@ node.todo.found.items={0} (found {1} {1,choice,1#item|2#items}) node.todo.items={0} ({1} {1, choice, 1#item|2#items}) title.appearance=Appearance group.window.options= Window Options -checkox.show.buttons.for.disabled.tool.windows=Show buttons for disabled tool windows checkbox.show.memory.indicator=Show memory indicator checkbox.show.tool.window.bars=Show tool window bars checkbox.show.tool.window.numbers=Show tool window numbers @@ -656,10 +631,6 @@ checkbox.use.antialiased.font.in.editor=Use antialiased font # this string must start with "IDEA" idea.default.look.and.feel=IDEA (4.5 default) error.cannot.set.look.and.feel=Cannot set {0} look and feel -action.add.customization.schema=Add customization scheme -customizations.schema.default=default -action.copy.customization.schema=Copy scheme -action.remove.customization.schema=Remove customization scheme error.adding.action.without.icon.to.toolbar=You are adding an action without icon to the toolbar. The default icon will be added to this action. title.unable.to.add.action.without.icon.to.toolbar=Unable to add action without icon to the toolbar error.please.specify.new.name.for.schema=Please, specify new name for scheme ''{0}''. @@ -670,13 +641,10 @@ action.choose.actions.to.add=Choose Actions To Add button.set.icon=&Set icon label.icon.path=Icon Path: button.edit.action.icon=Edit Action &Icon -editbox.schema.name=Scheme Name: -editbox.schema.description=Description: button.add.separator=Add &Separator button.move.up.u=Move &Up button.move.down.d=Move &Down button.add.action.after=Add a&fter... -customization.schema.default.description=Default unmodified customization scheme title.custom.actions.schemas=Custom Actions Schemes title.customizations=Menus and Toolbars label.choosebyname.no.matches.found=(no matches found) @@ -873,21 +841,12 @@ element.of.interface=of interface element.of.class=of class prompt.do.you.want.to.action_verb.the.method.from_class=Do you want to {0} the base {1,choice,1#method|2#methods}? jar.no.java.modules.in.project.error=There are no Java modules found in the project.\nOnly Java modules can be jarred. -jar.no.java.modules.in.project.title=No Modules To Jar Found jar.build.progress=Building jar {0}... -jar.build.success.message=Jar has been built in {0} -jar.build.cancelled=Jar creation has been cancelled jar.build.error.title=Error Creating Jar jar.build.progress.title=Building Jar -jar.build.processing.file.progress=Processing file {0} ... -error.message.jar.build.cannot.create.temporary.file.in.0=Cannot create temporary file in ''{0}'' -jar.build.cannot.overwrite.error=Cannot overwrite file ''{0}''. Copy has been saved to ''{1}''. -jar.build.module.presentable.name=Build Jars for module ''{0}'' jar.build.path=Jar file &path: jar.build.main.class=Main &class: jar.build.dialog.title=Build Jars -jar.build.save.title=Save Jar File -jar.build.main.class.title=Choose Main Class jar.build.class.not.found=Class ''{0}'' not found help.not.found.error=Help not found for {0} help.not.found.title=Help Not Found @@ -918,7 +877,6 @@ updates.check.period.on.startup=On every start updates.check.period.daily=Daily updates.check.period.weekly=Weekly updates.check.period.monthly=Monthly -updates.current.build.unknown=N/A updates.last.check.never=Never updates.settings.caption.1=$PRODUCT$ can automatically check for new and updated versions of itself, using your internet connection (when active). updates.settings.check.now.button=Check Now @@ -942,14 +900,11 @@ jar.build.on.make=Build jars on &make jar.build.modules.to.jar=Choose Modules to Jar jar.build.module.0.jar.settings=Module ''{0}'' Jar Settings jar.build.include.in.jar.file=Include in jar file: -jar.build.button=&Build search.textfield.title=&Search: select.in.scope=Scope scope.view.title=Scope select.in.title.project.view=Project View several.plugins.depend.on.0.continue.to.remove=Several plugins depend on {0}. Continue to remove? -project.view.combo.label=View &as: - child.tag.0.should.be.defined=''{0}'' child tag should be defined attribute.0.should.be.defined=''{0}'' attribute should be defined value.must.not.be.empty=Value must not be empty @@ -1060,7 +1015,6 @@ plugin.deleted.status.tooltip=Plugin will be deleted on next startup plugin.outdated.version.status.tooltip=Plugin has newer version plugin.download.status.tooltip=Plugin will be activated on next startup plugin.is.already.installed.status.tooltip=Plugin is installed -update.plugins.shutdown.action=Update and &Shutdown update.plugins.update.action=&Update fail.open.project.message=Unable to open project from ''{0}'' disabled.plugins.warning.message=
  • Plugin \"{0}\" won''t be able to load because required {2, choice, 1#plugin|2#plugins} {1} {2, choice, 1#is|2#are} disabled.
  • diff --git a/platform/platform-resources-en/src/messages/RefactoringBundle.properties b/platform/platform-resources-en/src/messages/RefactoringBundle.properties index 2ce788085ef2..909d679b0513 100644 --- a/platform/platform-resources-en/src/messages/RefactoringBundle.properties +++ b/platform/platform-resources-en/src/messages/RefactoringBundle.properties @@ -80,7 +80,7 @@ parameter.name.prompt=Pa&rameter name: no.class.name.specified=No class name specified no.parameter.name.specified=No parameter name specified pass.outer.class.instance.as.parameter=Pass &outer class' instance as a parameter -pull.up.members.to=&Pull up members of {0} to: +pull.up.members.to=P&ull up members of {0} to: members.to.be.pulled.up=Members to be pulled up make.abstract=Make abstract javadoc.for.abstracts=JavaDoc for abstracts diff --git a/platform/platform-resources-en/src/messages/UIBundle.properties b/platform/platform-resources-en/src/messages/UIBundle.properties index 6fcceb24c518..8c4ebe8683aa 100644 --- a/platform/platform-resources-en/src/messages/UIBundle.properties +++ b/platform/platform-resources-en/src/messages/UIBundle.properties @@ -98,6 +98,8 @@ welcome.script.text.not.specified.message=Not specified file.chooser.default.title=Select Path file.chooser.save.dialog.default.title=Select File to Save +file.chooser.save.dialog.confirmation={0} is already exists.\nDo you want to replace it? +file.chooser.save.dialog.confirmation.title=Confirm Save as file.chooser.select.object.title=Select {0} delete.dialog.title=Delete file.chooser.could.not.erase.file.or.folder.error.messabe=Could not erase file or folder: {0} diff --git a/platform/platform-resources/src/META-INF/XmlPlugin.xml b/platform/platform-resources/src/META-INF/XmlPlugin.xml index 98b39a2de6cf..0b04b7fe2ad7 100644 --- a/platform/platform-resources/src/META-INF/XmlPlugin.xml +++ b/platform/platform-resources/src/META-INF/XmlPlugin.xml @@ -289,7 +289,7 @@ implementationClass="com.intellij.codeInsight.hint.XmlImplementationTextSelectioner"/> - + diff --git a/platform/platform-resources/src/idea/Keymap_Default.xml b/platform/platform-resources/src/idea/Keymap_Default.xml index da2b2722d684..e98812cd9aa6 100644 --- a/platform/platform-resources/src/idea/Keymap_Default.xml +++ b/platform/platform-resources/src/idea/Keymap_Default.xml @@ -942,6 +942,8 @@ + + diff --git a/platform/testFramework/src/com/intellij/FileSetTestCase.java b/platform/testFramework/src/com/intellij/FileSetTestCase.java new file mode 100644 index 000000000000..86c846ea252e --- /dev/null +++ b/platform/testFramework/src/com/intellij/FileSetTestCase.java @@ -0,0 +1,147 @@ +/* + * Created by IntelliJ IDEA. + * User: user + * Date: Sep 22, 2002 + * Time: 2:45:20 PM + * To change template for new class use + * Code Style | Class Templates options (Tools | IDE Options). + */ +package com.intellij; + +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.io.FileUtil; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.testFramework.LightPlatformTestCase; +import com.intellij.util.ArrayUtil; +import junit.framework.TestSuite; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; + +public abstract class FileSetTestCase extends TestSuite { + private final File[] myFiles; + protected Project myProject; + + public FileSetTestCase(String path) { + File f = new File(path); + if (f.isDirectory()) { + myFiles = f.listFiles(); + } + else if (f.exists()) { + myFiles = new File[] {f}; + } + else { + throw new IllegalArgumentException("invalid path: " + path); + } + + + addAllTests(); + } + + protected void setUp() { + + } + + protected void tearDown() { + + } + + private void addAllTests() { + for (File file : myFiles) { + if (file.isFile()) { + addFileTest(file); + } + } + } + + public abstract String transform(String testName, String[] data) throws Exception; + + protected FileSetTestCase(File[] files) { + myFiles = files; + addAllTests(); + } + + public String getName() { + return getClass().getName(); + } + + private void addFileTest(File file) { + if (!StringUtil.startsWithChar(file.getName(), '_') && !"CVS".equals(file.getName())) { + final ActualTest t = new ActualTest(file); + addTest(t); + } + } + + private class ActualTest extends LightPlatformTestCase { + private final File myTestFile; + + public ActualTest(File testFile) { + myTestFile = testFile; + } + + protected void setUp() throws Exception { + super.setUp(); + FileSetTestCase.this.setUp(); + } + + protected void tearDown() throws Exception { + FileSetTestCase.this.tearDown(); + super.tearDown(); + } + + public int countTestCases() { + return 1; + } + + protected void runTest() throws Throwable { + String content = new String(FileUtil.loadFileText(myTestFile)); + assertNotNull(content); + + List input = new ArrayList(); + + int separatorIndex; + + content = StringUtil.replace(content, "\r", ""); + + while ((separatorIndex = content.indexOf("---")) >= 0) { + input.add(content.substring(0, separatorIndex)); + content = content.substring(separatorIndex); + while (StringUtil.startsWithChar(content, '-') || StringUtil.startsWithChar(content, '\n')) content = content.substring(1); + } + + String result = content; + + assertTrue("No data found in source file", input.size() > 0); + + while (StringUtil.startsWithChar(result, '-') || StringUtil.startsWithChar(result, '\n') || StringUtil.startsWithChar(result, '\r')) { + result = result.substring(1); + } + final String transformed; + FileSetTestCase.this.myProject = getProject(); + String testName = myTestFile.getName(); + final int dotIdx = testName.indexOf('.'); + if (dotIdx >= 0) { + testName = testName.substring(0, dotIdx); + } + + transformed = StringUtil.replace(transform(testName, ArrayUtil.toStringArray(input)), "\r", ""); + result = StringUtil.replace(result, "\r", ""); + + assertEquals(result.trim(),transformed.trim()); + } + + + public String toString() { + return myTestFile.getAbsolutePath() + " "; + } + + protected void resetAllFields() { + // Do nothing otherwise myTestFile will be nulled out before getName() is called. + } + + public String getName() { + return myTestFile.getAbsolutePath(); + } + } +} diff --git a/platform/testFramework/src/com/intellij/testFramework/EditorTestUtil.java b/platform/testFramework/src/com/intellij/testFramework/EditorTestUtil.java new file mode 100644 index 000000000000..64d6170f3ec8 --- /dev/null +++ b/platform/testFramework/src/com/intellij/testFramework/EditorTestUtil.java @@ -0,0 +1,52 @@ +/* + * Copyright 2000-2010 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.testFramework; + +import com.intellij.ide.DataManager; +import com.intellij.openapi.actionSystem.IdeActions; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.editor.actionSystem.EditorActionHandler; +import com.intellij.openapi.editor.actionSystem.EditorActionManager; +import com.intellij.openapi.editor.actionSystem.TypedAction; + +/** + * User: Maxim.Mossienko + * Date: 15.03.2010 + * Time: 21:00:49 + */ +public class EditorTestUtil { + public static final char BACKSPACE_FAKE_CHAR = '\uFFFF'; + public static final char SMART_ENTER_FAKE_CHAR = '\uFFFE'; + + public static void performTypingAction(Editor editor, char c) { + EditorActionManager actionManager = EditorActionManager.getInstance(); + if (c == BACKSPACE_FAKE_CHAR) { + EditorActionHandler actionHandler = actionManager.getActionHandler(IdeActions.ACTION_EDITOR_BACKSPACE); + actionHandler.execute(editor, DataManager.getInstance().getDataContext()); + } else if (c == SMART_ENTER_FAKE_CHAR) { + EditorActionHandler actionHandler = actionManager.getActionHandler(IdeActions.ACTION_EDITOR_COMPLETE_STATEMENT); + actionHandler.execute(editor, DataManager.getInstance().getDataContext()); + } + else if (c == '\n') { + EditorActionHandler actionHandler = actionManager.getActionHandler(IdeActions.ACTION_EDITOR_ENTER); + actionHandler.execute(editor, DataManager.getInstance().getDataContext()); + } + else { + TypedAction action = actionManager.getTypedAction(); + action.actionPerformed(editor, c, DataManager.getInstance().getDataContext()); + } + } +} diff --git a/platform/usageView/src/com/intellij/usages/UsageViewSettings.java b/platform/usageView/src/com/intellij/usages/UsageViewSettings.java index cdaf24566a3e..a5de4380aa78 100644 --- a/platform/usageView/src/com/intellij/usages/UsageViewSettings.java +++ b/platform/usageView/src/com/intellij/usages/UsageViewSettings.java @@ -49,6 +49,7 @@ public class UsageViewSettings implements PersistentStateComponent rules = new ArrayList(); rules.add(new NonCodeUsageGroupingRule()); + if (UsageViewSettings.getInstance().GROUP_BY_SCOPE) { + rules.add(new UsageScopeGroupingRule()); + } if (UsageViewSettings.getInstance().GROUP_BY_USAGE_TYPE) { rules.add(new UsageTypeGroupingRule()); } @@ -88,6 +91,7 @@ public class UsageGroupingRuleProviderImpl implements UsageGroupingRuleProvider } }); + final GroupByScopeAction groupByScopeAction = new GroupByScopeAction(impl); if(view.getPresentation().isCodeUsages()) { final GroupByUsageTypeAction groupByUsageTypeAction = new GroupByUsageTypeAction(impl); groupByUsageTypeAction.registerCustomShortcutSet(new CustomShortcutSet(KeyStroke.getKeyStroke(KeyEvent.VK_T, InputEvent.CTRL_DOWN_MASK)), component); @@ -104,15 +108,17 @@ public class UsageGroupingRuleProviderImpl implements UsageGroupingRuleProvider return new AnAction[] { groupByUsageTypeAction, + groupByScopeAction, groupByModuleTypeAction, groupByPackageAction, - groupByFileStructureAction + groupByFileStructureAction, }; } else { return new AnAction[] { + groupByScopeAction, groupByModuleTypeAction, - groupByFileStructureAction + groupByFileStructureAction, }; } } @@ -129,6 +135,18 @@ public class UsageGroupingRuleProviderImpl implements UsageGroupingRuleProvider } } + private static class GroupByScopeAction extends RuleAction { + private GroupByScopeAction(UsageViewImpl view) { + super(view, "Group by test/production", Icons.TEST_SOURCE_FOLDER); + } + protected boolean getOptionValue() { + return UsageViewSettings.getInstance().GROUP_BY_SCOPE; + } + protected void setOptionValue(boolean value) { + UsageViewSettings.getInstance().GROUP_BY_SCOPE = value; + } + } + private static class GroupByModuleTypeAction extends RuleAction { private GroupByModuleTypeAction(UsageViewImpl view) { super(view, UsageViewBundle.message("action.group.by.module"), IconLoader.getIcon("/objectBrowser/showModules.png")); diff --git a/platform/usageView/src/com/intellij/usages/impl/rules/UsageScopeGroupingRule.java b/platform/usageView/src/com/intellij/usages/impl/rules/UsageScopeGroupingRule.java new file mode 100644 index 000000000000..77e667f01aef --- /dev/null +++ b/platform/usageView/src/com/intellij/usages/impl/rules/UsageScopeGroupingRule.java @@ -0,0 +1,101 @@ +/* + * Copyright 2000-2010 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.usages.impl.rules; + +import com.intellij.openapi.roots.ProjectRootManager; +import com.intellij.openapi.vcs.FileStatus; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.psi.PsiElement; +import com.intellij.psi.util.PsiUtilBase; +import com.intellij.usages.Usage; +import com.intellij.usages.UsageGroup; +import com.intellij.usages.UsageView; +import com.intellij.usages.rules.PsiElementUsage; +import com.intellij.usages.rules.UsageGroupingRule; +import com.intellij.util.Icons; +import org.jetbrains.annotations.NotNull; + +import javax.swing.*; + +/** + * @author max + */ +public class UsageScopeGroupingRule implements UsageGroupingRule { + public UsageGroup groupUsage(Usage usage) { + if (!(usage instanceof PsiElementUsage)) { + return null; + } + PsiElementUsage elementUsage = (PsiElementUsage)usage; + + PsiElement element = elementUsage.getElement(); + VirtualFile virtualFile = PsiUtilBase.getVirtualFile(element); + + if (virtualFile == null) { + return null; + } + boolean isInTest = ProjectRootManager.getInstance(element.getProject()).getFileIndex().isInTestSourceContent(virtualFile); + return isInTest ? TEST : PRODUCTION; + } + + private static final UsageScopeGroup TEST = new UsageScopeGroup(true); + private static final UsageScopeGroup PRODUCTION = new UsageScopeGroup(false); + private static class UsageScopeGroup implements UsageGroup { + private final boolean isTest; + + private UsageScopeGroup(boolean isTest) { + this.isTest = isTest; + } + + public void update() { + } + + public Icon getIcon(boolean isOpen) { + return isTest ? Icons.TEST_SOURCE_FOLDER : Icons.SOURCE_FOLDERS_ICON; + } + + @NotNull + public String getText(UsageView view) { + return isTest ? "Test" : "Production"; + } + + public FileStatus getFileStatus() { + return null; + } + + public boolean isValid() { return true; } + public void navigate(boolean focus) { } + public boolean canNavigate() { return false; } + + public boolean canNavigateToSource() { + return false; + } + + public int compareTo(UsageGroup usageGroup) { + return getText(null).compareTo(usageGroup.getText(null)); + } + + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof UsageScopeGroup)) return false; + final UsageScopeGroup usageTypeGroup = (UsageScopeGroup)o; + return isTest == usageTypeGroup.isTest; + } + + public int hashCode() { + return isTest ? 0 : 1; + } + } +} diff --git a/platform/usageView/src/com/intellij/usages/impl/rules/UsageType.java b/platform/usageView/src/com/intellij/usages/impl/rules/UsageType.java index 09b96d088862..3a720fd16a3e 100644 --- a/platform/usageView/src/com/intellij/usages/impl/rules/UsageType.java +++ b/platform/usageView/src/com/intellij/usages/impl/rules/UsageType.java @@ -17,7 +17,7 @@ package com.intellij.usages.impl.rules; import com.intellij.usageView.UsageViewBundle; -public class UsageType { +public final class UsageType { public static final UsageType CLASS_INSTANCE_OF = new UsageType(UsageViewBundle.message("usage.type.instanceof")); public static final UsageType CLASS_IMPORT = new UsageType(UsageViewBundle.message("usage.type.import")); public static final UsageType CLASS_CAST_TO = new UsageType(UsageViewBundle.message("usage.type.cast.target")); diff --git a/platform/usageView/src/com/intellij/usages/impl/rules/UsageTypeGroupingRule.java b/platform/usageView/src/com/intellij/usages/impl/rules/UsageTypeGroupingRule.java index fbd45e4e071f..fba4bdf0b878 100644 --- a/platform/usageView/src/com/intellij/usages/impl/rules/UsageTypeGroupingRule.java +++ b/platform/usageView/src/com/intellij/usages/impl/rules/UsageTypeGroupingRule.java @@ -72,9 +72,6 @@ public class UsageTypeGroupingRule implements UsageGroupingRule { return null; } - - - private class UsageTypeGroup implements UsageGroup { private final UsageType myUsageType; diff --git a/platform/util/src/com/intellij/openapi/diff/impl/patch/PatchHunk.java b/platform/util/src/com/intellij/openapi/diff/impl/patch/PatchHunk.java new file mode 100644 index 000000000000..3ca09d8d68f3 --- /dev/null +++ b/platform/util/src/com/intellij/openapi/diff/impl/patch/PatchHunk.java @@ -0,0 +1,89 @@ +/* + * Copyright 2000-2010 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. + */ + +/* + * Created by IntelliJ IDEA. + * User: yole + * Date: 15.11.2006 + * Time: 20:20:15 + */ +package com.intellij.openapi.diff.impl.patch; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class PatchHunk { + private final int myStartLineBefore; + private final int myEndLineBefore; + private final int myStartLineAfter; + private final int myEndLineAfter; + private final List myLines = new ArrayList(); + + public PatchHunk(final int startLineBefore, final int endLineBefore, final int startLineAfter, final int endLineAfter) { + myStartLineBefore = startLineBefore; + myEndLineBefore = endLineBefore; + myStartLineAfter = startLineAfter; + myEndLineAfter = endLineAfter; + } + + public int getStartLineBefore() { + return myStartLineBefore; + } + + public int getEndLineBefore() { + return myEndLineBefore; + } + + public int getStartLineAfter() { + return myStartLineAfter; + } + + public int getEndLineAfter() { + return myEndLineAfter; + } + + public void addLine(final PatchLine line) { + myLines.add(line); + } + + public List getLines() { + return Collections.unmodifiableList(myLines); + } + + public boolean isNewContent() { + return myStartLineBefore == -1 && myEndLineBefore == -1; + } + + public boolean isDeletedContent() { + return myStartLineAfter == -1 && myEndLineAfter == -1; + } + + public String getText() { + StringBuilder builder = new StringBuilder(); + for(PatchLine line: myLines) { + builder.append(line.getText()).append("\n"); + } + return builder.toString(); + } + + public boolean isNoNewLineAtEnd() { + if (myLines.size() == 0) { + return false; + } + return myLines.get(myLines.size()-1).isSuppressNewLine(); + } +} diff --git a/platform/vcs-impl/src/com/intellij/openapi/diff/impl/patch/PatchLine.java b/platform/util/src/com/intellij/openapi/diff/impl/patch/PatchLine.java similarity index 100% rename from platform/vcs-impl/src/com/intellij/openapi/diff/impl/patch/PatchLine.java rename to platform/util/src/com/intellij/openapi/diff/impl/patch/PatchLine.java diff --git a/platform/platform-impl/src/com/intellij/openapi/diff/impl/processing/DiffFragmentsProcessor.java b/platform/util/src/com/intellij/openapi/diff/impl/processing/DiffFragmentsProcessor.java similarity index 100% rename from platform/platform-impl/src/com/intellij/openapi/diff/impl/processing/DiffFragmentsProcessor.java rename to platform/util/src/com/intellij/openapi/diff/impl/processing/DiffFragmentsProcessor.java diff --git a/platform/platform-impl/src/com/intellij/openapi/diff/impl/processing/LineFragmentsCollector.java b/platform/util/src/com/intellij/openapi/diff/impl/processing/LineFragmentsCollector.java similarity index 100% rename from platform/platform-impl/src/com/intellij/openapi/diff/impl/processing/LineFragmentsCollector.java rename to platform/util/src/com/intellij/openapi/diff/impl/processing/LineFragmentsCollector.java diff --git a/platform/util/src/com/intellij/openapi/util/text/StringUtil.java b/platform/util/src/com/intellij/openapi/util/text/StringUtil.java index c6c97d59bc19..ed99449c231a 100644 --- a/platform/util/src/com/intellij/openapi/util/text/StringUtil.java +++ b/platform/util/src/com/intellij/openapi/util/text/StringUtil.java @@ -17,7 +17,6 @@ package com.intellij.openapi.util.text; import com.intellij.CommonBundle; import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.util.Comparing; import com.intellij.openapi.util.TextRange; import com.intellij.util.ArrayUtil; import com.intellij.util.Function; @@ -35,6 +34,7 @@ import java.io.PrintWriter; import java.io.StringWriter; import java.util.*; +//TeamCity inherits StringUtil: do not add private constructors!!! public class StringUtil { private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.util.text.StringUtil"); @NonNls private static final String VOWELS = "aeiouy"; @@ -488,6 +488,40 @@ public class StringUtil { return s.substring(1, s.length() - 1); } + /** + * This is just an optimized version of Matcher.quoteReplacement + */ + public static String quoteReplacement(String s) { + boolean needReplacements = false; + + for (int i = 0; i < s.length(); i++) { + char c = s.charAt(i); + if (c == '\\' || c == '$') { + needReplacements = true; + break; + } + } + + if (!needReplacements) return s; + + StringBuilder sb = new StringBuilder(s.length() * 6 / 5); + for (int i = 0; i < s.length(); i++) { + char c = s.charAt(i); + if (c == '\\') { + sb.append('\\'); + sb.append('\\'); + } + else if (c == '$') { + sb.append('\\'); + sb.append('$'); + } + else { + sb.append(c); + } + } + return sb.toString(); + } + private static void unescapeStringCharacters(int length, @NotNull String s, @NotNull StringBuilder buffer) { boolean escaped = false; for (int idx = 0; idx < length; idx++) { diff --git a/platform/util/src/com/intellij/util/ui/UIUtil.java b/platform/util/src/com/intellij/util/ui/UIUtil.java index a70d4b8ab392..84fdb70b1581 100644 --- a/platform/util/src/com/intellij/util/ui/UIUtil.java +++ b/platform/util/src/com/intellij/util/ui/UIUtil.java @@ -42,10 +42,8 @@ import java.awt.*; import java.awt.event.*; import java.lang.reflect.Field; import java.lang.reflect.Method; +import java.util.*; import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.TreeSet; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; import java.util.regex.Pattern; @@ -1064,5 +1062,28 @@ public class UIUtil { return g instanceof PrintGraphics; } + public static int getSelectedButton(ButtonGroup group) { + Enumeration enumeration = group.getElements(); + int i = 0; + while (enumeration.hasMoreElements()) { + AbstractButton button = enumeration.nextElement(); + if (group.isSelected(button.getModel())) { + return i; + } + i++; + } + return -1; + } + + public static void setSelectedButton(ButtonGroup group, int index) { + Enumeration enumeration = group.getElements(); + int i = 0; + while (enumeration.hasMoreElements()) { + AbstractButton button = enumeration.nextElement(); + group.setSelected(button.getModel(), index == i); + i++; + } + } + } diff --git a/platform/vcs-impl/src/com/intellij/openapi/diff/impl/patch/BinaryFilePatch.java b/platform/vcs-api/src/com/intellij/openapi/diff/impl/patch/BinaryFilePatch.java similarity index 72% rename from platform/vcs-impl/src/com/intellij/openapi/diff/impl/patch/BinaryFilePatch.java rename to platform/vcs-api/src/com/intellij/openapi/diff/impl/patch/BinaryFilePatch.java index 31ee275760f8..6fe7ff86f336 100644 --- a/platform/vcs-impl/src/com/intellij/openapi/diff/impl/patch/BinaryFilePatch.java +++ b/platform/vcs-api/src/com/intellij/openapi/diff/impl/patch/BinaryFilePatch.java @@ -15,10 +15,6 @@ */ package com.intellij.openapi.diff.impl.patch; -import com.intellij.openapi.vfs.VirtualFile; - -import java.io.IOException; - /** * @author yole */ @@ -31,15 +27,6 @@ public class BinaryFilePatch extends FilePatch { myAfterContent = afterContent; } - protected void applyCreate(final VirtualFile newFile) throws IOException, ApplyPatchException { - newFile.setBinaryContent(myAfterContent); - } - - protected ApplyPatchStatus applyChange(final VirtualFile fileToPatch) throws IOException, ApplyPatchException { - fileToPatch.setBinaryContent(myAfterContent); - return ApplyPatchStatus.SUCCESS; - } - public boolean isNewFile() { return myBeforeContent == null; } @@ -47,4 +34,8 @@ public class BinaryFilePatch extends FilePatch { public boolean isDeletedFile() { return myAfterContent == null; } + + public byte[] getAfterContent() { + return myAfterContent; + } } diff --git a/platform/vcs-api/src/com/intellij/openapi/diff/impl/patch/FilePatch.java b/platform/vcs-api/src/com/intellij/openapi/diff/impl/patch/FilePatch.java new file mode 100644 index 000000000000..b1f8fb3dd621 --- /dev/null +++ b/platform/vcs-api/src/com/intellij/openapi/diff/impl/patch/FilePatch.java @@ -0,0 +1,83 @@ +/* + * Copyright 2000-2009 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * Created by IntelliJ IDEA. + * User: yole + * Date: 15.11.2006 + * Time: 17:53:24 + */ +package com.intellij.openapi.diff.impl.patch; + +import com.intellij.openapi.util.text.StringUtil; + +public abstract class FilePatch { + private String myBeforeName; + private String myAfterName; + private String myBeforeVersionId; + private String myAfterVersionId; + + public String getBeforeName() { + return myBeforeName; + } + + public String getAfterName() { + return myAfterName; + } + + public String getBeforeFileName() { + String[] pathNameComponents = myBeforeName.split("/"); + return pathNameComponents [pathNameComponents.length-1]; + } + + public String getAfterFileName() { + String[] pathNameComponents = myAfterName.split("/"); + return pathNameComponents [pathNameComponents.length-1]; + } + + public void setBeforeName(final String fileName) { + myBeforeName = fileName; + } + + public void setAfterName(final String fileName) { + myAfterName = fileName; + } + + public String getBeforeVersionId() { + return myBeforeVersionId; + } + + public void setBeforeVersionId(final String beforeVersionId) { + myBeforeVersionId = beforeVersionId; + } + + public String getAfterVersionId() { + return myAfterVersionId; + } + + public void setAfterVersionId(final String afterVersionId) { + myAfterVersionId = afterVersionId; + } + + public String getAfterNameRelative(int skipDirs) { + String[] components = myAfterName.split("/"); + return StringUtil.join(components, skipDirs, components.length, "/"); + } + + public abstract boolean isNewFile(); + + public abstract boolean isDeletedFile(); +} diff --git a/platform/vcs-impl/src/com/intellij/openapi/diff/impl/patch/PatchReader.java b/platform/vcs-api/src/com/intellij/openapi/diff/impl/patch/PatchReader.java similarity index 96% rename from platform/vcs-impl/src/com/intellij/openapi/diff/impl/patch/PatchReader.java rename to platform/vcs-api/src/com/intellij/openapi/diff/impl/patch/PatchReader.java index 7baf2fe36210..288b39586d53 100644 --- a/platform/vcs-impl/src/com/intellij/openapi/diff/impl/patch/PatchReader.java +++ b/platform/vcs-api/src/com/intellij/openapi/diff/impl/patch/PatchReader.java @@ -22,17 +22,14 @@ */ package com.intellij.openapi.diff.impl.patch; -import com.intellij.openapi.fileEditor.impl.LoadTextUtil; import com.intellij.openapi.util.text.LineTokenizer; -import com.intellij.openapi.vfs.VirtualFile; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.Nullable; -import java.io.IOException; +import java.util.ArrayList; +import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; -import java.util.List; -import java.util.ArrayList; public class PatchReader { @NonNls public static final String NO_NEWLINE_SIGNATURE = "\\ No newline at end of file"; @@ -48,12 +45,6 @@ public class PatchReader { @NonNls private static final Pattern ourContextBeforeHunkStartPattern = Pattern.compile("\\*\\*\\* (\\d+),(\\d+) \\*\\*\\*\\*"); @NonNls private static final Pattern ourContextAfterHunkStartPattern = Pattern.compile("--- (\\d+),(\\d+) ----"); - public PatchReader(VirtualFile virtualFile) throws IOException { - byte[] patchContents = virtualFile.contentsToByteArray(); - CharSequence patchText = LoadTextUtil.getTextByBinaryPresentation(patchContents, virtualFile); - myLines = LineTokenizer.tokenize(patchText, false); - } - public PatchReader(CharSequence patchContent) { myLines = LineTokenizer.tokenize(patchContent, false); } diff --git a/platform/vcs-impl/src/com/intellij/openapi/diff/impl/patch/PatchSyntaxException.java b/platform/vcs-api/src/com/intellij/openapi/diff/impl/patch/PatchSyntaxException.java similarity index 100% rename from platform/vcs-impl/src/com/intellij/openapi/diff/impl/patch/PatchSyntaxException.java rename to platform/vcs-api/src/com/intellij/openapi/diff/impl/patch/PatchSyntaxException.java diff --git a/platform/vcs-api/src/com/intellij/openapi/diff/impl/patch/TextFilePatch.java b/platform/vcs-api/src/com/intellij/openapi/diff/impl/patch/TextFilePatch.java new file mode 100644 index 000000000000..e92baf5dd8d2 --- /dev/null +++ b/platform/vcs-api/src/com/intellij/openapi/diff/impl/patch/TextFilePatch.java @@ -0,0 +1,63 @@ +/* + * Copyright 2000-2009 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.intellij.openapi.diff.impl.patch; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * @author yole + */ +public class TextFilePatch extends FilePatch { + private final List myHunks; + + public void addHunk(final PatchHunk hunk) { + myHunks.add(hunk); + } + + public List getHunks() { + return Collections.unmodifiableList(myHunks); + } + + public TextFilePatch() { + myHunks = new ArrayList(); + } + + private TextFilePatch(final TextFilePatch patch) { + setBeforeVersionId(patch.getBeforeVersionId()); + setAfterVersionId(patch.getAfterVersionId()); + setBeforeName(patch.getBeforeName()); + setAfterName(patch.getAfterName()); + myHunks = patch.myHunks; + } + + public TextFilePatch pathsOnlyCopy() { + return new TextFilePatch(this); + } + + public boolean isNewFile() { + return myHunks.size() == 1 && myHunks.get(0).isNewContent(); + } + + public String getNewFileText() { + return myHunks.get(0).getText(); + } + + public boolean isDeletedFile() { + return myHunks.size() == 1 && myHunks.get(0).isDeletedContent(); + } +} diff --git a/platform/vcs-impl/src/com/intellij/openapi/diff/impl/patch/PatchBuilder.java b/platform/vcs-api/src/com/intellij/openapi/diff/impl/patch/TextPatchBuilder.java similarity index 98% rename from platform/vcs-impl/src/com/intellij/openapi/diff/impl/patch/PatchBuilder.java rename to platform/vcs-api/src/com/intellij/openapi/diff/impl/patch/TextPatchBuilder.java index 606fa0bf8363..27be1b147901 100644 --- a/platform/vcs-impl/src/com/intellij/openapi/diff/impl/patch/PatchBuilder.java +++ b/platform/vcs-api/src/com/intellij/openapi/diff/impl/patch/TextPatchBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2009 JetBrains s.r.o. + * Copyright 2000-2010 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. @@ -44,11 +44,11 @@ import java.util.List; /** * @author yole */ -public class PatchBuilder { +public class TextPatchBuilder { private static final int CONTEXT_LINES = 3; @NonNls private static final String REVISION_NAME_TEMPLATE = "(revision {0})"; - private PatchBuilder() { + private TextPatchBuilder() { } private static void checkCanceled(final ProgressIndicator ind) { @@ -57,8 +57,7 @@ public class PatchBuilder { } } - public static List buildPatch(final Collection changes, final String basePath, final boolean allowRename, - final boolean reversePatch) throws VcsException { + public static List buildPatch(final Collection changes, final String basePath, final boolean reversePatch) throws VcsException { final ProgressIndicator ind = ProgressManager.getInstance().getProgressIndicator(); List result = new ArrayList(); diff --git a/platform/vcs-impl/src/com/intellij/openapi/diff/impl/patch/UnifiedDiffWriter.java b/platform/vcs-api/src/com/intellij/openapi/diff/impl/patch/UnifiedDiffWriter.java similarity index 100% rename from platform/vcs-impl/src/com/intellij/openapi/diff/impl/patch/UnifiedDiffWriter.java rename to platform/vcs-api/src/com/intellij/openapi/diff/impl/patch/UnifiedDiffWriter.java index 44d3a8475061..037cbb0c925e 100644 --- a/platform/vcs-impl/src/com/intellij/openapi/diff/impl/patch/UnifiedDiffWriter.java +++ b/platform/vcs-api/src/com/intellij/openapi/diff/impl/patch/UnifiedDiffWriter.java @@ -26,8 +26,8 @@ import org.jetbrains.annotations.NonNls; import java.io.IOException; import java.io.Writer; -import java.util.Collection; import java.text.MessageFormat; +import java.util.Collection; public class UnifiedDiffWriter { @NonNls private static final String INDEX_SIGNATURE = "Index: {0}{1}"; diff --git a/platform/vcs-api/src/com/intellij/openapi/vcs/VcsConfiguration.java b/platform/vcs-api/src/com/intellij/openapi/vcs/VcsConfiguration.java index 63159e43c35c..59dc044c95c5 100644 --- a/platform/vcs-api/src/com/intellij/openapi/vcs/VcsConfiguration.java +++ b/platform/vcs-api/src/com/intellij/openapi/vcs/VcsConfiguration.java @@ -63,6 +63,7 @@ public final class VcsConfiguration implements PersistentStateComponent public boolean PERFORM_ROLLBACK_IN_BACKGROUND = false; public volatile boolean CHECK_LOCALLY_CHANGED_CONFLICTS_IN_BACKGROUND = false; public VcsShowConfirmationOption.Value MOVE_TO_FAILED_COMMIT_CHANGELIST = VcsShowConfirmationOption.Value.SHOW_CONFIRMATION; + public VcsShowConfirmationOption.Value REMOVE_EMPTY_INACTIVE_CHANGELISTS = VcsShowConfirmationOption.Value.SHOW_CONFIRMATION; public boolean ENABLE_BACKGROUND_PROCESSES = false; public int CHANGED_ON_SERVER_INTERVAL = 60; diff --git a/platform/vcs-api/src/com/intellij/openapi/vcs/VcsShowConfirmationOption.java b/platform/vcs-api/src/com/intellij/openapi/vcs/VcsShowConfirmationOption.java index 4c43ad78a904..d077e7621ee4 100644 --- a/platform/vcs-api/src/com/intellij/openapi/vcs/VcsShowConfirmationOption.java +++ b/platform/vcs-api/src/com/intellij/openapi/vcs/VcsShowConfirmationOption.java @@ -27,7 +27,10 @@ public interface VcsShowConfirmationOption { myId = id; } - + public int getId() { + return myId; + } + public String toString() { return String.valueOf(myId); } diff --git a/platform/vcs-api/src/com/intellij/openapi/vcs/changes/ChangeListListener.java b/platform/vcs-api/src/com/intellij/openapi/vcs/changes/ChangeListListener.java index 3c5398f44f58..3d30fcc55261 100644 --- a/platform/vcs-api/src/com/intellij/openapi/vcs/changes/ChangeListListener.java +++ b/platform/vcs-api/src/com/intellij/openapi/vcs/changes/ChangeListListener.java @@ -21,6 +21,9 @@ import java.util.Collection; /** * @author max + * + * @see com.intellij.openapi.vcs.changes.ChangeListManager#addChangeListListener(ChangeListListener) + * @see com.intellij.openapi.vcs.changes.ChangeListManager#removeChangeListListener(ChangeListListener) */ public interface ChangeListListener extends EventListener { void changeListAdded(ChangeList list); diff --git a/platform/vcs-impl/src/com/intellij/openapi/diff/impl/patch/PatchVirtualFileReader.java b/platform/vcs-impl/src/com/intellij/openapi/diff/impl/patch/PatchVirtualFileReader.java new file mode 100644 index 000000000000..d416c384cb44 --- /dev/null +++ b/platform/vcs-impl/src/com/intellij/openapi/diff/impl/patch/PatchVirtualFileReader.java @@ -0,0 +1,32 @@ +/* + * Copyright 2000-2010 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.openapi.diff.impl.patch; + +import com.intellij.openapi.fileEditor.impl.LoadTextUtil; +import com.intellij.openapi.vfs.VirtualFile; + +import java.io.IOException; + +public class PatchVirtualFileReader { + private PatchVirtualFileReader() { + } + + public static PatchReader create(final VirtualFile virtualFile) throws IOException { + final byte[] patchContents = virtualFile.contentsToByteArray(); + final CharSequence patchText = LoadTextUtil.getTextByBinaryPresentation(patchContents, virtualFile); + return new PatchReader(patchText); + } +} diff --git a/platform/vcs-impl/src/com/intellij/openapi/diff/impl/patch/TextFilePatch.java b/platform/vcs-impl/src/com/intellij/openapi/diff/impl/patch/TextFilePatch.java deleted file mode 100644 index 502bac91a554..000000000000 --- a/platform/vcs-impl/src/com/intellij/openapi/diff/impl/patch/TextFilePatch.java +++ /dev/null @@ -1,114 +0,0 @@ -/* - * Copyright 2000-2009 JetBrains s.r.o. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.intellij.openapi.diff.impl.patch; - -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.openapi.fileEditor.impl.LoadTextUtil; -import com.intellij.openapi.fileEditor.FileDocumentManager; -import com.intellij.openapi.editor.Document; -import com.intellij.openapi.util.text.LineTokenizer; - -import java.util.List; -import java.util.ArrayList; -import java.util.Collections; -import java.io.IOException; - -/** - * @author yole - */ -public class TextFilePatch extends FilePatch { - private final List myHunks; - - public void addHunk(final PatchHunk hunk) { - myHunks.add(hunk); - } - - public List getHunks() { - return Collections.unmodifiableList(myHunks); - } - - public TextFilePatch() { - myHunks = new ArrayList(); - } - - private TextFilePatch(final TextFilePatch patch) { - setBeforeVersionId(patch.getBeforeVersionId()); - setAfterVersionId(patch.getAfterVersionId()); - setBeforeName(patch.getBeforeName()); - setAfterName(patch.getAfterName()); - myHunks = patch.myHunks; - } - - public TextFilePatch pathsOnlyCopy() { - return new TextFilePatch(this); - } - - protected ApplyPatchStatus applyChange(final VirtualFile fileToPatch) throws IOException, ApplyPatchException { - byte[] fileContents = fileToPatch.contentsToByteArray(); - CharSequence text = LoadTextUtil.getTextByBinaryPresentation(fileContents, fileToPatch); - StringBuilder newText = new StringBuilder(); - ApplyPatchStatus status = applyModifications(text, newText); - if (status != ApplyPatchStatus.ALREADY_APPLIED) { - final Document document = FileDocumentManager.getInstance().getDocument(fileToPatch); - if (document == null) { - throw new ApplyPatchException("Failed to set contents for updated file " + fileToPatch.getPath()); - } - document.setText(newText.toString()); - FileDocumentManager.getInstance().saveDocument(document); - } - return status; - } - - public ApplyPatchStatus applyModifications(final CharSequence text, final StringBuilder newText) throws ApplyPatchException { - if (myHunks.size() == 0) { - return ApplyPatchStatus.SUCCESS; - } - List lines = new ArrayList(); - Collections.addAll(lines, LineTokenizer.tokenize(text, false)); - ApplyPatchStatus result = null; - for(PatchHunk hunk: myHunks) { - result = ApplyPatchStatus.and(result, hunk.apply(lines)); - } - for(int i=0; i { + public ApplyBinaryFilePatch(BinaryFilePatch patch) { + super(patch); + } + + protected void applyCreate(final VirtualFile newFile) throws IOException, ApplyPatchException { + newFile.setBinaryContent(myPatch.getAfterContent()); + } + + protected ApplyPatchStatus applyChange(final VirtualFile fileToPatch) throws IOException, ApplyPatchException { + fileToPatch.setBinaryContent(myPatch.getAfterContent()); + return ApplyPatchStatus.SUCCESS; + } +} diff --git a/platform/vcs-impl/src/com/intellij/openapi/diff/impl/patch/apply/ApplyBinaryShelvedFilePatch.java b/platform/vcs-impl/src/com/intellij/openapi/diff/impl/patch/apply/ApplyBinaryShelvedFilePatch.java new file mode 100644 index 000000000000..076cf64dc1e7 --- /dev/null +++ b/platform/vcs-impl/src/com/intellij/openapi/diff/impl/patch/apply/ApplyBinaryShelvedFilePatch.java @@ -0,0 +1,38 @@ +/* + * Copyright 2000-2010 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.openapi.diff.impl.patch.apply; + +import com.intellij.openapi.diff.impl.patch.ApplyPatchException; +import com.intellij.openapi.diff.impl.patch.ApplyPatchStatus; +import com.intellij.openapi.vcs.changes.shelf.ShelveChangesManager; +import com.intellij.openapi.vfs.VirtualFile; + +import java.io.IOException; + +public class ApplyBinaryShelvedFilePatch extends ApplyFilePatchBase { + public ApplyBinaryShelvedFilePatch(ShelveChangesManager.ShelvedBinaryFilePatch patch) { + super(patch); + } + + @Override + protected ApplyPatchStatus applyChange(VirtualFile fileToPatch) throws IOException, ApplyPatchException { + return null; + } + + @Override + protected void applyCreate(VirtualFile newFile) throws IOException, ApplyPatchException { + } +} diff --git a/platform/vcs-impl/src/com/intellij/openapi/diff/impl/patch/apply/ApplyFilePatch.java b/platform/vcs-impl/src/com/intellij/openapi/diff/impl/patch/apply/ApplyFilePatch.java new file mode 100644 index 000000000000..03e8a5639362 --- /dev/null +++ b/platform/vcs-impl/src/com/intellij/openapi/diff/impl/patch/apply/ApplyFilePatch.java @@ -0,0 +1,35 @@ +/* + * Copyright 2000-2010 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.openapi.diff.impl.patch.apply; + +import com.intellij.openapi.diff.impl.patch.ApplyPatchContext; +import com.intellij.openapi.diff.impl.patch.ApplyPatchException; +import com.intellij.openapi.diff.impl.patch.ApplyPatchStatus; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.vfs.VirtualFile; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.io.IOException; + +public interface ApplyFilePatch { + ApplyPatchStatus apply(VirtualFile fileToPatch, ApplyPatchContext context, Project project) throws IOException, ApplyPatchException; + + ApplyPatchStatus applyImpl(VirtualFile fileToPatch, Project project) throws IOException, ApplyPatchException; + + @Nullable + VirtualFile findFileToPatch(@NotNull ApplyPatchContext context) throws IOException; +} diff --git a/platform/vcs-impl/src/com/intellij/openapi/diff/impl/patch/FilePatch.java b/platform/vcs-impl/src/com/intellij/openapi/diff/impl/patch/apply/ApplyFilePatchBase.java similarity index 74% rename from platform/vcs-impl/src/com/intellij/openapi/diff/impl/patch/FilePatch.java rename to platform/vcs-impl/src/com/intellij/openapi/diff/impl/patch/apply/ApplyFilePatchBase.java index d33a87f8c292..502dc684693f 100644 --- a/platform/vcs-impl/src/com/intellij/openapi/diff/impl/patch/FilePatch.java +++ b/platform/vcs-impl/src/com/intellij/openapi/diff/impl/patch/apply/ApplyFilePatchBase.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2009 JetBrains s.r.o. + * Copyright 2000-2010 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. @@ -13,18 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +package com.intellij.openapi.diff.impl.patch.apply; -/* - * Created by IntelliJ IDEA. - * User: yole - * Date: 15.11.2006 - * Time: 17:53:24 - */ -package com.intellij.openapi.diff.impl.patch; - +import com.intellij.openapi.diff.impl.patch.*; import com.intellij.openapi.fileEditor.impl.FileEditorManagerImpl; import com.intellij.openapi.project.Project; -import com.intellij.openapi.util.text.StringUtil; +import com.intellij.openapi.util.text.LineTokenizer; import com.intellij.openapi.vcs.FilePath; import com.intellij.openapi.vcs.FilePathImpl; import com.intellij.openapi.vfs.VirtualFile; @@ -32,77 +26,39 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; -public abstract class FilePatch { - private String myBeforeName; - private String myAfterName; - private String myBeforeVersionId; - private String myAfterVersionId; +public abstract class ApplyFilePatchBase implements ApplyFilePatch { + protected final T myPatch; - public String getBeforeName() { - return myBeforeName; + public ApplyFilePatchBase(T patch) { + myPatch = patch; } - public String getAfterName() { - return myAfterName; + public T getPatch() { + return myPatch; } - public String getBeforeFileName() { - String[] pathNameComponents = myBeforeName.split("/"); - return pathNameComponents [pathNameComponents.length-1]; - } - - public String getAfterFileName() { - String[] pathNameComponents = myAfterName.split("/"); - return pathNameComponents [pathNameComponents.length-1]; - } - - public void setBeforeName(final String fileName) { - myBeforeName = fileName; - } - - public void setAfterName(final String fileName) { - myAfterName = fileName; - } - - public String getBeforeVersionId() { - return myBeforeVersionId; - } - - public void setBeforeVersionId(final String beforeVersionId) { - myBeforeVersionId = beforeVersionId; - } - - public String getAfterVersionId() { - return myAfterVersionId; - } - - public void setAfterVersionId(final String afterVersionId) { - myAfterVersionId = afterVersionId; - } - - public String getAfterNameRelative(int skipDirs) { - String[] components = myAfterName.split("/"); - return StringUtil.join(components, skipDirs, components.length, "/"); - } - - public FilePath getTarget(final VirtualFile file) { - if (isNewFile()) { - return new FilePathImpl(file, getBeforeFileName(), false); + private FilePath getTarget(final VirtualFile file) { + if (myPatch.isNewFile()) { + return new FilePathImpl(file, myPatch.getBeforeFileName(), false); } return new FilePathImpl(file); } - public ApplyPatchStatus apply(final VirtualFile fileToPatch, final ApplyPatchContext context, final Project project) throws IOException, ApplyPatchException { + public ApplyPatchStatus apply(final VirtualFile fileToPatch, final ApplyPatchContext context, final Project project) throws + IOException, ApplyPatchException { context.addAffectedFile(getTarget(fileToPatch)); return applyImpl(fileToPatch, project); } public ApplyPatchStatus applyImpl(final VirtualFile fileToPatch, final Project project) throws IOException, ApplyPatchException { - if (isNewFile()) { + if (myPatch.isNewFile()) { applyCreate(fileToPatch); } - else if (isDeletedFile()) { + else if (myPatch.isDeletedFile()) { FileEditorManagerImpl.getInstance(project).closeFile(fileToPatch); fileToPatch.delete(this); } @@ -113,11 +69,12 @@ public abstract class FilePatch { } protected abstract void applyCreate(VirtualFile newFile) throws IOException, ApplyPatchException; + @Nullable protected abstract ApplyPatchStatus applyChange(VirtualFile fileToPatch) throws IOException, ApplyPatchException; @Nullable public VirtualFile findFileToPatch(@NotNull ApplyPatchContext context) throws IOException { - return findPatchTarget(context, myBeforeName, myAfterName, isNewFile()); + return findPatchTarget(context, myPatch.getBeforeName(), myPatch.getAfterName(), myPatch.isNewFile()); } @Nullable @@ -217,7 +174,25 @@ public abstract class FilePatch { return patchedDir; } - public abstract boolean isNewFile(); - - public abstract boolean isDeletedFile(); + @Nullable + public static ApplyPatchStatus applyModifications(final TextFilePatch patch, final CharSequence text, final StringBuilder newText) throws + ApplyPatchException { + final List hunks = patch.getHunks(); + if (hunks.isEmpty()) { + return ApplyPatchStatus.SUCCESS; + } + List lines = new ArrayList(); + Collections.addAll(lines, LineTokenizer.tokenize(text, false)); + ApplyPatchStatus result = null; + for(PatchHunk hunk: hunks) { + result = ApplyPatchStatus.and(result, new ApplyPatchHunk(hunk).apply(lines)); + } + for(int i=0; i myLines = new ArrayList(); +public class ApplyPatchHunk { + private final PatchHunk myHunk; - public PatchHunk(final int startLineBefore, final int endLineBefore, final int startLineAfter, final int endLineAfter) { - myStartLineBefore = startLineBefore; - myEndLineBefore = endLineBefore; - myStartLineAfter = startLineAfter; - myEndLineAfter = endLineAfter; + public ApplyPatchHunk(final PatchHunk hunk) { + myHunk = hunk; } - - public int getStartLineBefore() { - return myStartLineBefore; - } - - public int getEndLineBefore() { - return myEndLineBefore; - } - - public int getStartLineAfter() { - return myStartLineAfter; - } - - public int getEndLineAfter() { - return myEndLineAfter; - } - - public void addLine(final PatchLine line) { - myLines.add(line); - } - - public List getLines() { - return Collections.unmodifiableList(myLines); - } - + public ApplyPatchStatus apply(final List lines) throws ApplyPatchException { List originalLines = new ArrayList(lines); try { @@ -77,9 +43,10 @@ public class PatchHunk { } private ApplyPatchStatus tryApply(final List lines, boolean acceptPartial) throws ApplyPatchException { + final List hunkLines = myHunk.getLines(); ApplyPatchStatus result = null; - int curLine = findStartLine(lines); - for(PatchLine line: myLines) { + int curLine = findStartLine(hunkLines, lines); + for(PatchLine line: hunkLines) { final String patchLineText = line.getText(); switch (line.getType()) { case CONTEXT: @@ -130,15 +97,16 @@ public class PatchHunk { } } - private int findStartLine(final List lines) throws ApplyPatchException { - int totalContextLines = countContextLines(); - if (getLinesProcessingContext(lines, myStartLineBefore) == totalContextLines) { - return myStartLineBefore; + private int findStartLine(final List hunkLines, final List lines) throws ApplyPatchException { + int totalContextLines = countContextLines(hunkLines); + final int startLineBefore = myHunk.getStartLineBefore(); + if (getLinesProcessingContext(hunkLines, lines, startLineBefore) == totalContextLines) { + return startLineBefore; } int maxContextStartLine = -1; int maxContextLines = 0; for(int i=0;i< lines.size(); i++) { - int contextLines = getLinesProcessingContext(lines, i); + int contextLines = getLinesProcessingContext(hunkLines, lines, i); if (contextLines == totalContextLines) { return i; } @@ -153,9 +121,9 @@ public class PatchHunk { return maxContextStartLine; } - private int countContextLines() { + private int countContextLines(final List hunkLines) { int count = 0; - for(PatchLine line: myLines) { + for(PatchLine line: hunkLines) { if (line.getType() == PatchLine.Type.CONTEXT || line.getType() == PatchLine.Type.REMOVE) { count++; } @@ -163,9 +131,9 @@ public class PatchHunk { return count; } - private int getLinesProcessingContext(final List lines, int startLine) { + private int getLinesProcessingContext(final List hunkLines, final List lines, int startLine) { int count = 0; - for(PatchLine line: myLines) { + for(PatchLine line: hunkLines) { PatchLine.Type type = line.getType(); if (type == PatchLine.Type.REMOVE || type == PatchLine.Type.CONTEXT) { // TODO: smarter algorithm (search outward from non-context lines) @@ -178,27 +146,4 @@ public class PatchHunk { } return count; } - - public boolean isNewContent() { - return myStartLineBefore == -1 && myEndLineBefore == -1; - } - - public boolean isDeletedContent() { - return myStartLineAfter == -1 && myEndLineAfter == -1; - } - - public String getText() { - StringBuilder builder = new StringBuilder(); - for(PatchLine line: myLines) { - builder.append(line.getText()).append("\n"); - } - return builder.toString(); - } - - public boolean isNoNewLineAtEnd() { - if (myLines.size() == 0) { - return false; - } - return myLines.get(myLines.size()-1).isSuppressNewLine(); - } } diff --git a/platform/vcs-impl/src/com/intellij/openapi/diff/impl/patch/apply/ApplyTextFilePatch.java b/platform/vcs-impl/src/com/intellij/openapi/diff/impl/patch/apply/ApplyTextFilePatch.java new file mode 100644 index 000000000000..307cfda70cbd --- /dev/null +++ b/platform/vcs-impl/src/com/intellij/openapi/diff/impl/patch/apply/ApplyTextFilePatch.java @@ -0,0 +1,59 @@ +/* + * Copyright 2000-2010 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.openapi.diff.impl.patch.apply; + +import com.intellij.openapi.diff.impl.patch.ApplyPatchException; +import com.intellij.openapi.diff.impl.patch.ApplyPatchStatus; +import com.intellij.openapi.diff.impl.patch.TextFilePatch; +import com.intellij.openapi.editor.Document; +import com.intellij.openapi.fileEditor.FileDocumentManager; +import com.intellij.openapi.fileEditor.impl.LoadTextUtil; +import com.intellij.openapi.vfs.VirtualFile; +import org.jetbrains.annotations.Nullable; + +import java.io.IOException; + +public class ApplyTextFilePatch extends ApplyFilePatchBase { + public ApplyTextFilePatch(final TextFilePatch patch) { + super(patch); + } + + @Nullable + protected ApplyPatchStatus applyChange(final VirtualFile fileToPatch) throws IOException, ApplyPatchException { + byte[] fileContents = fileToPatch.contentsToByteArray(); + CharSequence text = LoadTextUtil.getTextByBinaryPresentation(fileContents, fileToPatch); + StringBuilder newText = new StringBuilder(); + ApplyPatchStatus status = ApplyFilePatchBase.applyModifications(myPatch, text, newText); + if (status != ApplyPatchStatus.ALREADY_APPLIED) { + final Document document = FileDocumentManager.getInstance().getDocument(fileToPatch); + if (document == null) { + throw new ApplyPatchException("Failed to set contents for updated file " + fileToPatch.getPath()); + } + document.setText(newText.toString()); + FileDocumentManager.getInstance().saveDocument(document); + } + return status; + } + + protected void applyCreate(final VirtualFile newFile) throws IOException, ApplyPatchException { + final Document document = FileDocumentManager.getInstance().getDocument(newFile); + if (document == null) { + throw new ApplyPatchException("Failed to set contents for new file " + newFile.getPath()); + } + document.setText(myPatch.getNewFileText()); + FileDocumentManager.getInstance().saveDocument(document); + } +} diff --git a/platform/vcs-impl/src/com/intellij/openapi/diff/impl/patch/formove/CustomBinaryPatchApplier.java b/platform/vcs-impl/src/com/intellij/openapi/diff/impl/patch/formove/CustomBinaryPatchApplier.java index cdbcfe35f13b..fc735ac8924f 100644 --- a/platform/vcs-impl/src/com/intellij/openapi/diff/impl/patch/formove/CustomBinaryPatchApplier.java +++ b/platform/vcs-impl/src/com/intellij/openapi/diff/impl/patch/formove/CustomBinaryPatchApplier.java @@ -17,6 +17,7 @@ package com.intellij.openapi.diff.impl.patch.formove; import com.intellij.openapi.diff.impl.patch.ApplyPatchStatus; import com.intellij.openapi.diff.impl.patch.FilePatch; +import com.intellij.openapi.diff.impl.patch.apply.ApplyFilePatchBase; import com.intellij.openapi.util.Pair; import com.intellij.openapi.vfs.VirtualFile; import org.jetbrains.annotations.NotNull; @@ -24,9 +25,9 @@ import org.jetbrains.annotations.NotNull; import java.io.IOException; import java.util.List; -public interface CustomBinaryPatchApplier { +public interface CustomBinaryPatchApplier { @NotNull - ApplyPatchStatus apply(List> patches) throws IOException; + ApplyPatchStatus apply(List>> patches) throws IOException; @NotNull List getAppliedPatches(); } diff --git a/platform/vcs-impl/src/com/intellij/openapi/diff/impl/patch/formove/PatchApplier.java b/platform/vcs-impl/src/com/intellij/openapi/diff/impl/patch/formove/PatchApplier.java index eaa713eb261b..7ff2c143a0a0 100644 --- a/platform/vcs-impl/src/com/intellij/openapi/diff/impl/patch/formove/PatchApplier.java +++ b/platform/vcs-impl/src/com/intellij/openapi/diff/impl/patch/formove/PatchApplier.java @@ -21,6 +21,8 @@ import com.intellij.openapi.command.CommandProcessor; import com.intellij.openapi.diff.impl.patch.ApplyPatchContext; import com.intellij.openapi.diff.impl.patch.ApplyPatchStatus; import com.intellij.openapi.diff.impl.patch.FilePatch; +import com.intellij.openapi.diff.impl.patch.apply.ApplyFilePatchBase; +import com.intellij.openapi.diff.impl.patch.apply.ApplyTextFilePatch; import com.intellij.openapi.fileTypes.FileType; import com.intellij.openapi.fileTypes.FileTypes; import com.intellij.openapi.fileTypes.ex.FileTypeChooser; @@ -52,25 +54,25 @@ import java.util.List; /** * for patches. for shelve. */ -public class PatchApplier { +public class PatchApplier { private final Project myProject; private final VirtualFile myBaseDirectory; private final List myPatches; - private final CustomBinaryPatchApplier myCustomForBinaries; + private final CustomBinaryPatchApplier myCustomForBinaries; private final LocalChangeList myTargetChangeList; private final List myRemainingPatches; - private final PathsVerifier myVerifier; + private final PathsVerifier myVerifier; public PatchApplier(final Project project, final VirtualFile baseDirectory, final List patches, - final LocalChangeList targetChangeList, final CustomBinaryPatchApplier customForBinaries) { + final LocalChangeList targetChangeList, final CustomBinaryPatchApplier customForBinaries) { myProject = project; myBaseDirectory = baseDirectory; myPatches = patches; myTargetChangeList = targetChangeList; myCustomForBinaries = customForBinaries; myRemainingPatches = new ArrayList(); - myVerifier = new PathsVerifier(myProject, myBaseDirectory, myPatches, new PathsVerifier.BaseMapper() { + myVerifier = new PathsVerifier(myProject, myBaseDirectory, myPatches, new PathsVerifier.BaseMapper() { @Nullable public VirtualFile getFile(FilePatch patch, String path) { return PathMerger.getFile(myBaseDirectory, path); @@ -137,7 +139,7 @@ public class PatchApplier { return; } - final List> textPatches = myVerifier.getTextPatches(); + final List> textPatches = myVerifier.getTextPatches(); if (! fileTypesAreOk(textPatches)) { return; } @@ -184,8 +186,8 @@ public class PatchApplier { } @Nullable - private ApplyPatchStatus actualApply(final PathsVerifier verifier) { - final List> textPatches = verifier.getTextPatches(); + private ApplyPatchStatus actualApply(final PathsVerifier verifier) { + final List> textPatches = verifier.getTextPatches(); final ApplyPatchContext context = new ApplyPatchContext(myBaseDirectory, 0, true, true); ApplyPatchStatus status = null; @@ -195,7 +197,7 @@ public class PatchApplier { if (myCustomForBinaries == null) { status = applyList(verifier.getBinaryPatches(), context, status); } else { - final List> binaryPatches = verifier.getBinaryPatches(); + final List>> binaryPatches = verifier.getBinaryPatches(); ApplyPatchStatus patchStatus = myCustomForBinaries.apply(binaryPatches); final List appliedPatches = myCustomForBinaries.getAppliedPatches(); moveForCustomBinaries(binaryPatches, appliedPatches); @@ -211,24 +213,24 @@ public class PatchApplier { return status; } - private void moveForCustomBinaries(final List> patches, + private void moveForCustomBinaries(final List>> patches, final List appliedPatches) throws IOException { - for (Pair patch : patches) { - if (appliedPatches.contains(patch.getSecond())) { + for (Pair> patch : patches) { + if (appliedPatches.contains(patch.getSecond().getPatch())) { myVerifier.doMoveIfNeeded(patch.getFirst()); } } } - private ApplyPatchStatus applyList(final List> patches, final ApplyPatchContext context, + private > ApplyPatchStatus applyList(final List> patches, final ApplyPatchContext context, ApplyPatchStatus status) throws IOException { - for (Pair patch : patches) { + for (Pair patch : patches) { ApplyPatchStatus patchStatus = ApplyPatchAction.applyOnly(myProject, patch.getSecond(), context, patch.getFirst()); myVerifier.doMoveIfNeeded(patch.getFirst()); status = ApplyPatchStatus.and(status, patchStatus); if (patchStatus != ApplyPatchStatus.FAILURE) { - myRemainingPatches.remove(patch.getSecond()); + myRemainingPatches.remove(patch.getSecond().getPatch()); } else { // interrupt if failure return status; @@ -259,8 +261,8 @@ public class PatchApplier { return (! readonlyStatus.hasReadonlyFiles()); } - private boolean fileTypesAreOk(final List> textPatches) { - for (Pair textPatch : textPatches) { + private boolean fileTypesAreOk(final List> textPatches) { + for (Pair textPatch : textPatches) { final VirtualFile file = textPatch.getFirst(); if (! file.isDirectory()) { FileType fileType = file.getFileType(); diff --git a/platform/vcs-impl/src/com/intellij/openapi/diff/impl/patch/formove/PathsVerifier.java b/platform/vcs-impl/src/com/intellij/openapi/diff/impl/patch/formove/PathsVerifier.java index c5bf4a99724c..d2d57c6ac303 100644 --- a/platform/vcs-impl/src/com/intellij/openapi/diff/impl/patch/formove/PathsVerifier.java +++ b/platform/vcs-impl/src/com/intellij/openapi/diff/impl/patch/formove/PathsVerifier.java @@ -15,8 +15,12 @@ */ package com.intellij.openapi.diff.impl.patch.formove; +import com.intellij.openapi.diff.impl.patch.BinaryFilePatch; import com.intellij.openapi.diff.impl.patch.FilePatch; import com.intellij.openapi.diff.impl.patch.TextFilePatch; +import com.intellij.openapi.diff.impl.patch.apply.ApplyFilePatchBase; +import com.intellij.openapi.diff.impl.patch.apply.ApplyFilePatchFactory; +import com.intellij.openapi.diff.impl.patch.apply.ApplyTextFilePatch; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Comparing; import com.intellij.openapi.util.Pair; @@ -25,6 +29,7 @@ import com.intellij.openapi.vcs.FilePathImpl; import com.intellij.openapi.vcs.ProjectLevelVcsManager; import com.intellij.openapi.vcs.VcsBundle; import com.intellij.openapi.vcs.changes.patch.RelativePathCalculator; +import com.intellij.openapi.vcs.changes.shelf.ShelveChangesManager; import com.intellij.openapi.vcs.impl.ExcludedFileIndex; import com.intellij.openapi.vfs.VfsUtil; import com.intellij.openapi.vfs.VirtualFile; @@ -34,7 +39,7 @@ import org.jetbrains.annotations.Nullable; import java.io.IOException; import java.util.*; -public class PathsVerifier { +public class PathsVerifier { // in private final Project myProject; private final VirtualFile myBaseDirectory; @@ -44,8 +49,8 @@ public class PathsVerifier { private final List myBeforePaths; private final List myCreatedDirectories; // out - private final List> myTextPatches; - private final List> myBinaryPatches; + private final List> myTextPatches; + private final List>> myBinaryPatches; private final List myWritableFiles; private final BaseMapper myBaseMapper; @@ -58,8 +63,8 @@ public class PathsVerifier { myMovedFiles = new HashMap(); myBeforePaths = new ArrayList(); myCreatedDirectories = new ArrayList(); - myTextPatches = new ArrayList>(); - myBinaryPatches = new ArrayList>(); + myTextPatches = new ArrayList>(); + myBinaryPatches = new ArrayList>>(); myWritableFiles = new ArrayList(); } @@ -284,11 +289,14 @@ public class PathsVerifier { } private void addPatch(final FilePatch patch, final VirtualFile file) { - final Pair patchPair = new Pair(file, patch); + final Pair patchPair = new Pair(file, ApplyFilePatchFactory.createGeneral(patch)); if (patch instanceof TextFilePatch) { - myTextPatches.add(patchPair); + myTextPatches.add(new Pair(file, ApplyFilePatchFactory.create((TextFilePatch) patch))); } else { - myBinaryPatches.add(patchPair); + final ApplyFilePatchBase applyBinaryPatch = (ApplyFilePatchBase) ((patch instanceof BinaryFilePatch) ? ApplyFilePatchFactory + .create((BinaryFilePatch) patch) : + ApplyFilePatchFactory.create((ShelveChangesManager.ShelvedBinaryFilePatch) patch)); + myBinaryPatches.add(new Pair>(file, applyBinaryPatch)); } myWritableFiles.add(file); } @@ -410,11 +418,11 @@ public class PathsVerifier { return child; } - public List> getTextPatches() { + public List> getTextPatches() { return myTextPatches; } - public List> getBinaryPatches() { + public List>> getBinaryPatches() { return myBinaryPatches; } diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/ChangeListManagerImpl.java b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/ChangeListManagerImpl.java index 6a201f6dda92..6b11ca594982 100644 --- a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/ChangeListManagerImpl.java +++ b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/ChangeListManagerImpl.java @@ -29,6 +29,7 @@ import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.Messages; import com.intellij.openapi.util.*; import com.intellij.openapi.util.io.FileUtil; +import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vcs.*; import com.intellij.openapi.vcs.changes.conflicts.ChangelistConflictTracker; import com.intellij.openapi.vcs.changes.ui.CommitHelper; @@ -107,7 +108,7 @@ public class ChangeListManagerImpl extends ChangeListManagerEx implements Projec return (ChangeListManagerImpl) project.getComponent(ChangeListManager.class); } - public ChangeListManagerImpl(final Project project) { + public ChangeListManagerImpl(Project project, final VcsConfiguration config) { myProject = project; myChangesViewManager = ChangesViewManager.getInstance(myProject); myFileStatusManager = FileStatusManager.getInstance(myProject); @@ -120,6 +121,45 @@ public class ChangeListManagerImpl extends ChangeListManagerEx implements Projec myModifier = new Modifier(myWorker, myDelayedNotificator); myConflictTracker = new ChangelistConflictTracker(project, this, myFileStatusManager, EditorNotifications.getInstance(project)); + + myListeners.addListener(new ChangeListAdapter() { + @Override + public void defaultListChanged(final ChangeList oldDefaultList, ChangeList newDefaultList) { + if (!ApplicationManager.getApplication().isUnitTestMode() && + oldDefaultList instanceof LocalChangeList && + oldDefaultList.getChanges().isEmpty()) { + + invokeAfterUpdate(new Runnable() { + public void run() { + switch (config.REMOVE_EMPTY_INACTIVE_CHANGELISTS) { + + case SHOW_CONFIRMATION: + VcsConfirmationDialog dialog = new VcsConfirmationDialog(myProject, new VcsShowConfirmationOption() { + public Value getValue() { + return config.REMOVE_EMPTY_INACTIVE_CHANGELISTS; + } + + public void setValue(Value value) { + config.REMOVE_EMPTY_INACTIVE_CHANGELISTS = value; + } + }, "The empty changelist '" + StringUtil.last(oldDefaultList.getName(), 30, true) + "' is no longer active.
    " + + "Do you want to remove it?", "&Remember my choice"); + dialog.show(); + if (!dialog.isOK()) { + return; + } + break; + case DO_NOTHING_SILENTLY: + return; + case DO_ACTION_SILENTLY: + break; + } + removeChangeList((LocalChangeList)oldDefaultList); + } + }, InvokeAfterUpdateMode.SILENT, null, null); + } + } + }); } public void projectOpened() { diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/VcsConfirmationDialog.java b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/VcsConfirmationDialog.java new file mode 100644 index 000000000000..149c6c9cb089 --- /dev/null +++ b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/VcsConfirmationDialog.java @@ -0,0 +1,90 @@ +/* + * Copyright 2000-2010 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.openapi.vcs.changes; + +import com.intellij.CommonBundle; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.Messages; +import com.intellij.openapi.vcs.VcsShowConfirmationOption; +import com.intellij.util.ui.OptionsDialog; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.ActionEvent; + +/** + * @author Dmitry Avdeev + */ +public class VcsConfirmationDialog extends OptionsDialog { + + private final VcsShowConfirmationOption myOption; + private final String myMessage; + private final String myDoNotShowMessage; + + protected VcsConfirmationDialog(Project project, VcsShowConfirmationOption option, String message, String doNotShowMessage) { + super(project); + myOption = option; + myMessage = message; + myDoNotShowMessage = doNotShowMessage; + setTitle("Confirmation"); + init(); + } + + @Override + protected boolean isToBeShown() { + return myOption.getValue() == VcsShowConfirmationOption.Value.SHOW_CONFIRMATION; + } + + @Override + protected void setToBeShown(boolean value, boolean onOk) { + myOption.setValue(value ? VcsShowConfirmationOption.Value.SHOW_CONFIRMATION : onOk ? VcsShowConfirmationOption.Value.DO_ACTION_SILENTLY : VcsShowConfirmationOption.Value.DO_NOTHING_SILENTLY); + } + + @Override + protected boolean shouldSaveOptionsOnCancel() { + return true; + } + + @Override + protected JComponent createCenterPanel() { + JPanel panel = new JPanel(new BorderLayout(15, 0)); + panel.add(new JLabel(myMessage)); + panel.add(new JLabel(Messages.getQuestionIcon()), BorderLayout.WEST); + return panel; + } + + @Override + protected String getDoNotShowMessage() { + return myDoNotShowMessage; + } + + @Override + protected Action[] createActions() { + return new Action[] { + new AbstractAction(CommonBundle.getYesButtonText()) { + { + putValue(DEFAULT_ACTION, Boolean.TRUE); + } + public void actionPerformed(ActionEvent e) { + doOKAction(); + } + }, new AbstractAction(CommonBundle.getNoButtonText()) { + public void actionPerformed(ActionEvent e) { + doCancelAction(); + } + }}; + } +} diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/actions/DiffRequestPresentableProxy.java b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/actions/DiffRequestPresentableProxy.java new file mode 100644 index 000000000000..74c6886fc7b5 --- /dev/null +++ b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/actions/DiffRequestPresentableProxy.java @@ -0,0 +1,62 @@ +/* + * Copyright 2000-2010 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.openapi.vcs.changes.actions; + +import com.intellij.openapi.actionSystem.AnAction; +import org.jetbrains.annotations.Nullable; + +import java.util.List; + +public abstract class DiffRequestPresentableProxy implements DiffRequestPresentable { + private DiffRequestPresentable myDelegate; + private List myCachedActions; + private MyResult myStepResult; + + @Nullable + protected abstract DiffRequestPresentable init(); + + @Nullable + private DiffRequestPresentable initRequest() { + if (myDelegate == null) { + myDelegate = init(); + } + return myDelegate; + } + + public List createActions(ShowDiffAction.DiffExtendUIFactory uiFactory) { + if (myCachedActions == null) { + myCachedActions = initRequest().createActions(uiFactory); + } + return myCachedActions; + } + + public MyResult step() { + final DiffRequestPresentable request = initRequest(); + if (request == null) { + return new MyResult(null, DiffPresentationReturnValue.removeFromList); + } + myStepResult = request.step(); + return myStepResult; + } + + public boolean haveStuff() { + final DiffRequestPresentable request = initRequest(); + if (request == null) { + return false; + } + return request.haveStuff(); + } +} diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/actions/RevertCommittedStuffAbstractAction.java b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/actions/RevertCommittedStuffAbstractAction.java index 395b4cbe6919..d953a1650c71 100644 --- a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/actions/RevertCommittedStuffAbstractAction.java +++ b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/actions/RevertCommittedStuffAbstractAction.java @@ -18,8 +18,9 @@ package com.intellij.openapi.vcs.changes.actions; import com.intellij.openapi.actionSystem.AnAction; import com.intellij.openapi.actionSystem.AnActionEvent; import com.intellij.openapi.actionSystem.PlatformDataKeys; +import com.intellij.openapi.diff.impl.patch.BinaryFilePatch; import com.intellij.openapi.diff.impl.patch.FilePatch; -import com.intellij.openapi.diff.impl.patch.PatchBuilder; +import com.intellij.openapi.diff.impl.patch.TextPatchBuilder; import com.intellij.openapi.diff.impl.patch.formove.PatchApplier; import com.intellij.openapi.project.DumbAware; import com.intellij.openapi.project.Project; @@ -70,13 +71,13 @@ abstract class RevertCommittedStuffAbstractAction extends AnAction implements Du List patches; try { - patches = PatchBuilder.buildPatch(changesList, baseDir.getPresentableUrl(), false, true); + patches = TextPatchBuilder.buildPatch(changesList, baseDir.getPresentableUrl(), true); } catch (VcsException ex) { Messages.showErrorDialog(project, "Failed to revert changes: " + ex.getMessage(), VcsBundle.message("revert.changes.title")); return; } - new PatchApplier(project, baseDir, patches, chooser.getSelectedList(), null).execute(); + new PatchApplier(project, baseDir, patches, chooser.getSelectedList(), null).execute(); } public void update(final AnActionEvent e) { diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/patch/ApplyPatchAction.java b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/patch/ApplyPatchAction.java index c96dacad9137..353b5223ce4f 100644 --- a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/patch/ApplyPatchAction.java +++ b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/patch/ApplyPatchAction.java @@ -32,6 +32,7 @@ import com.intellij.openapi.diff.DiffManager; import com.intellij.openapi.diff.DiffRequestFactory; import com.intellij.openapi.diff.MergeRequest; import com.intellij.openapi.diff.impl.patch.*; +import com.intellij.openapi.diff.impl.patch.apply.ApplyFilePatchBase; import com.intellij.openapi.diff.impl.patch.formove.PatchApplier; import com.intellij.openapi.fileEditor.FileDocumentManager; import com.intellij.openapi.fileEditor.impl.LoadTextUtil; @@ -72,7 +73,7 @@ public class ApplyPatchAction extends AnAction { final Collection appliers = new LinkedList(); for (VirtualFile base : patchGroups.keySet()) { final PatchApplier patchApplier = - new PatchApplier(project, base, ObjectsConvertor.convert(patchGroups.get(base), new Convertor() { + new PatchApplier(project, base, ObjectsConvertor.convert(patchGroups.get(base), new Convertor() { public FilePatch convert(FilePatchInProgress o) { return o.getPatch(); } @@ -107,20 +108,21 @@ public class ApplyPatchAction extends AnAction { return sb.toString(); } - public static ApplyPatchStatus applyOnly(final Project project, final FilePatch patch, final ApplyPatchContext context, final VirtualFile file) { + public static ApplyPatchStatus applyOnly(final Project project, final ApplyFilePatchBase patch, final ApplyPatchContext context, final VirtualFile file) { + final T patchBase = patch.getPatch(); try { return patch.apply(file, context, project); } catch(ApplyPatchException ex) { - if (!patch.isNewFile() && !patch.isDeletedFile() && patch instanceof TextFilePatch) { + if (!patchBase.isNewFile() && !patchBase.isDeletedFile() && patchBase instanceof TextFilePatch) { //final VirtualFile beforeRename = (pathBeforeRename == null) ? file : pathBeforeRename; - ApplyPatchStatus mergeStatus = mergeAgainstBaseVersion(project, file, new FilePathImpl(file), (TextFilePatch) patch, + ApplyPatchStatus mergeStatus = mergeAgainstBaseVersion(project, file, new FilePathImpl(file), (TextFilePatch) patchBase, ApplyPatchMergeRequestFactory.INSTANCE); if (mergeStatus != null) { return mergeStatus; } } - Messages.showErrorDialog(project, VcsBundle.message("patch.apply.error", patch.getBeforeName(), ex.getMessage()), + Messages.showErrorDialog(project, VcsBundle.message("patch.apply.error", patchBase.getBeforeName(), ex.getMessage()), VcsBundle.message("patch.apply.dialog.title")); } catch (Exception ex) { diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/patch/ApplyPatchDialog.form b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/patch/ApplyPatchDialog.form deleted file mode 100644 index fc880aeb17e9..000000000000 --- a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/patch/ApplyPatchDialog.form +++ /dev/null @@ -1,126 +0,0 @@ - -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/patch/ApplyPatchDialog.java b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/patch/ApplyPatchDialog.java deleted file mode 100644 index 136892925fe6..000000000000 --- a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/patch/ApplyPatchDialog.java +++ /dev/null @@ -1,735 +0,0 @@ -/* - * Copyright 2000-2009 JetBrains s.r.o. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.intellij.openapi.vcs.changes.patch; - -import com.intellij.openapi.actionSystem.AnAction; -import com.intellij.openapi.actionSystem.AnActionEvent; -import com.intellij.openapi.actionSystem.CommonShortcuts; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.command.CommandProcessor; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.diff.impl.patch.*; -import com.intellij.openapi.diff.impl.patch.formove.PathMerger; -import com.intellij.openapi.editor.Document; -import com.intellij.openapi.fileChooser.FileChooserDescriptor; -import com.intellij.openapi.fileEditor.FileDocumentManager; -import com.intellij.openapi.fileTypes.FileType; -import com.intellij.openapi.fileTypes.FileTypeManager; -import com.intellij.openapi.fileTypes.FileTypes; -import com.intellij.openapi.fileTypes.StdFileTypes; -import com.intellij.openapi.help.HelpManager; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.ui.DialogWrapper; -import com.intellij.openapi.ui.Messages; -import com.intellij.openapi.ui.TextFieldWithBrowseButton; -import com.intellij.openapi.util.Comparing; -import com.intellij.openapi.util.Computable; -import com.intellij.openapi.util.Pair; -import com.intellij.openapi.util.io.FileUtil; -import com.intellij.openapi.vcs.FilePath; -import com.intellij.openapi.vcs.FilePathImpl; -import com.intellij.openapi.vcs.FileStatus; -import com.intellij.openapi.vcs.VcsBundle; -import com.intellij.openapi.vcs.changes.*; -import com.intellij.openapi.vcs.changes.actions.ShowDiffAction; -import com.intellij.openapi.vcs.changes.ui.ChangeListChooserPanel; -import com.intellij.openapi.vfs.LocalFileSystem; -import com.intellij.openapi.vfs.VfsUtil; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.ui.CollectionListModel; -import com.intellij.ui.ColoredListCellRenderer; -import com.intellij.ui.DocumentAdapter; -import com.intellij.ui.SimpleTextAttributes; -import com.intellij.util.Alarm; -import com.intellij.util.ArrayUtil; -import com.intellij.util.Consumer; -import com.intellij.util.ui.UIUtil; -import org.jetbrains.annotations.NonNls; -import org.jetbrains.annotations.Nullable; -import org.jetbrains.annotations.PropertyKey; - -import javax.swing.*; -import javax.swing.event.ChangeEvent; -import javax.swing.event.ChangeListener; -import javax.swing.event.DocumentEvent; -import java.awt.*; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -import java.awt.event.MouseAdapter; -import java.awt.event.MouseEvent; -import java.io.File; -import java.io.IOException; -import java.util.*; -import java.util.List; - -/** - * @author yole - */ -public class ApplyPatchDialog extends DialogWrapper { - private final Logger LOG = Logger.getInstance("#com.intellij.openapi.vcs.changes.patch.ApplyPatchDialog"); - - private JPanel myRootPanel; - private TextFieldWithBrowseButton myFileNameField; - private JLabel myStatusLabel; - private TextFieldWithBrowseButton myBaseDirectoryField; - private JSpinner myStripLeadingDirectoriesSpinner; - private JList myPatchContentsList; - private ChangeListChooserPanel myChangeListChooser; - private JButton myShowDiffButton; - private List myPatches; - private Collection myPatchesFailedToLoad; - private final Alarm myLoadPatchAlarm = new Alarm(Alarm.ThreadToUse.SWING_THREAD); - private final Alarm myVerifyPatchAlarm = new Alarm(Alarm.ThreadToUse.SHARED_THREAD); - private String myLoadPatchError = null; - private String myDetectedBaseDirectory = null; - private int myDetectedStripLeadingDirs = -1; - private final Project myProject; - private boolean myInnerChange; - private LocalChangeList mySelectedChangeList; - - private final Map, String> myMoveRenameInfo; - - public ApplyPatchDialog(Project project) { - super(project, true); - myProject = project; - setTitle(VcsBundle.message("patch.apply.dialog.title")); - final FileChooserDescriptor descriptor = new FileChooserDescriptor(true, false, false, false, false, false) { - @Override - public boolean isFileSelectable(VirtualFile file) { - return file.getFileType() == StdFileTypes.PATCH || file.getFileType() == FileTypes.PLAIN_TEXT; - } - }; - myMoveRenameInfo = new HashMap, String>(); - myFileNameField.addBrowseFolderListener(VcsBundle.message("patch.apply.select.title"), "", project, descriptor); - myFileNameField.getTextField().getDocument().addDocumentListener(new DocumentAdapter() { - protected void textChanged(DocumentEvent e) { - updateOKAction(); - myStatusLabel.setForeground(UIUtil.getLabelForeground()); - myStatusLabel.setText(VcsBundle.message("patch.load.progress")); - myPatches = null; - myMoveRenameInfo.clear(); - myLoadPatchAlarm.cancelAllRequests(); - myLoadPatchAlarm.addRequest(new Runnable() { - public void run() { - checkLoadPatches(true); - } - }, 400); - } - }); - - myBaseDirectoryField.setText(project.getBaseDir().getPresentableUrl()); - myBaseDirectoryField.addBrowseFolderListener(VcsBundle.message("patch.apply.select.base.directory.title"), "", project, - new FileChooserDescriptor(false, true, false, false, false, false)); - myBaseDirectoryField.getTextField().getDocument().addDocumentListener(new DocumentAdapter() { - protected void textChanged(final DocumentEvent e) { - if (!myInnerChange) { - queueVerifyPatchPaths(); - } - } - }); - - myStripLeadingDirectoriesSpinner.setModel(new SpinnerNumberModel(0, 0, 256, 1)); - myStripLeadingDirectoriesSpinner.addChangeListener(new ChangeListener() { - public void stateChanged(final ChangeEvent e) { - if (!myInnerChange) { - queueVerifyPatchPaths(); - } - } - }); - - myPatchContentsList.setCellRenderer(new PatchCellRendererPanel()); - - ChangeListManager changeListManager = ChangeListManager.getInstance(project); - myChangeListChooser.setChangeLists(changeListManager.getChangeListsCopy()); - myChangeListChooser.setDefaultSelection(changeListManager.getDefaultChangeList()); - myChangeListChooser.init(project); - init(); - updateOKAction(); - myShowDiffButton.addActionListener(new ActionListener() { - public void actionPerformed(final ActionEvent e) { - showDiff(); - } - }); - myPatchContentsList.addMouseListener(new MouseAdapter() { - public void mouseClicked(final MouseEvent e) { - if (e.getButton() == 1 && e.getClickCount() == 2) { - showDiff(); - } - } - }); - - new AnAction() { - @Override - public void actionPerformed(AnActionEvent e) { - showDiff(); - } - }.registerCustomShortcutSet(CommonShortcuts.getDiff(), myRootPanel, myDisposable); - } - - private void showDiff() { - List changes = new ArrayList(); - ApplyPatchContext context = getApplyPatchContext().getPrepareContext(); - Object[] selection = myPatchContentsList.getSelectedValues(); - if (selection.length == 0) { - if (myPatches == null) return; - selection = ArrayUtil.toObjectArray(myPatches); - } - for(Object o: selection) { - final TextFilePatch patch = (TextFilePatch) o; - try { - if (patch.isNewFile()) { - final FilePath newFilePath = FilePathImpl.createNonLocal(patch.getAfterName(), false); - final String content = patch.getNewFileText(); - ContentRevision revision = new SimpleContentRevision(content, newFilePath, patch.getAfterVersionId()); - changes.add(new Change(null, revision)); - } else if ((! patch.isDeletedFile()) && (patch.getBeforeName() != null) && (patch.getAfterName() != null) && - (! patch.getBeforeName().equals(patch.getAfterName()))) { - - final VirtualFile baseDirectory = getBaseDirectory(); - final VirtualFile beforeFile = PathMerger.getFile(baseDirectory, patch.getBeforeName()); - - if (beforeFile != null) { - final List tail = new ArrayList(); - final VirtualFile partFile = PathMerger.getFile(baseDirectory, patch.getAfterName(), tail); - final StringBuilder sb = new StringBuilder(partFile.getPath()); - for (String s : tail) { - if (sb.charAt(sb.length() - 1) != '/') { - sb.append('/'); - } - sb.append(s); - } - - final Change change = - changeForPath(beforeFile, patch, FilePathImpl.createNonLocal(FileUtil.toSystemIndependentName(sb.toString()), false)); - if (change != null) { - changes.add(change); - } - } else { - Messages.showErrorDialog(myProject, "Cannot show difference: cannot find file " + patch.getBeforeName(), - VcsBundle.message("patch.apply.dialog.title")); - } - } - else { - final VirtualFile fileToPatch = patch.findFileToPatch(context); - if (fileToPatch != null) { - final FilePathImpl filePath = new FilePathImpl(fileToPatch); - final CurrentContentRevision currentRevision = new CurrentContentRevision(filePath); - if (patch.isDeletedFile()) { - changes.add(new Change(currentRevision, null)); - } - else { - final Change change = changeForPath(fileToPatch, patch, null); - if (change != null) { - changes.add(change); - } - } - } - } - } - catch (Exception e) { - Messages.showErrorDialog(myProject, "Error loading changes for " + patch.getAfterFileName() + ": " + e.getMessage(), - VcsBundle.message("patch.apply.dialog.title")); - return; - } - } - ShowDiffAction.showDiffForChange(changes.toArray(new Change[changes.size()]), 0, myProject, - ShowDiffAction.DiffExtendUIFactory.NONE, false); - } - - @Nullable - private Change changeForPath(final VirtualFile fileToPatch, final TextFilePatch patch, final FilePath newFilePath) { - try { - final FilePathImpl filePath = new FilePathImpl(fileToPatch); - final CurrentContentRevision currentRevision = new CurrentContentRevision(filePath); - final Document doc = FileDocumentManager.getInstance().getDocument(fileToPatch); - String baseContent = doc.getText(); - StringBuilder newText = new StringBuilder(); - patch.applyModifications(baseContent, newText); - ContentRevision revision = new SimpleContentRevision(newText.toString(), (newFilePath == null) ? filePath : newFilePath, patch.getAfterVersionId()); - return new Change(currentRevision, revision); - } catch (ApplyPatchException e) { - ApplyPatchContext context = new ApplyPatchContext(getBaseDirectory(), 0, false, false); - // just show diff here. maybe refactor further.. - ApplyPatchAction.mergeAgainstBaseVersion(myProject, fileToPatch, context, patch, ApplyPatchAction.ApplyPatchMergeRequestFactory.INSTANCE_READ_ONLY); - return null; - } - } - - @Override - @NonNls - protected String getDimensionServiceKey() { - return "vcs.ApplyPatchDialog"; - } - - private void queueVerifyPatchPaths() { - myStatusLabel.setForeground(UIUtil.getLabelForeground()); - myStatusLabel.setText(VcsBundle.message("apply.patch.progress.verifying")); - myVerifyPatchAlarm.cancelAllRequests(); - myVerifyPatchAlarm.addRequest(new Runnable() { - public void run() { - try { - if (myPatches != null) { - verifyPatchPaths(); - } - } - catch(Exception ex) { - LOG.error(ex); - } - } - }, 400); - } - - public void setFileName(String fileName) { - myFileNameField.setText(fileName); - checkLoadPatches(false); - } - - private void checkLoadPatches(final boolean async) { - final String fileName = myFileNameField.getText().replace(File.separatorChar, '/'); - final VirtualFile patchFile = ApplicationManager.getApplication().runWriteAction(new Computable() { - public VirtualFile compute() { - final VirtualFile file = LocalFileSystem.getInstance().refreshAndFindFileByPath(fileName); - if (file != null) { - file.refresh(false, false); - if (file.isDirectory()) { - // we are looking for file not directory - return null; - } - } - return file; - } - }); - if (patchFile == null) { - queueUpdateStatus("Cannot find patch file"); - return; - } - myChangeListChooser.setDefaultName(patchFile.getNameWithoutExtension().replace('_', ' ').trim()); - if (async) { - ApplicationManager.getApplication().executeOnPooledThread(new Runnable() { - public void run() { - loadPatchesFromFile(patchFile); - } - }); - } - else { - loadPatchesFromFile(patchFile); - } - } - - private void loadPatchesFromFile(final VirtualFile patchFile) { - myPatches = new ArrayList(); - myPatchesFailedToLoad = new HashSet(); - ApplicationManager.getApplication().runReadAction(new Runnable() { - public void run() { - if (!patchFile.isValid()) { - queueUpdateStatus("Cannot find patch file"); - return; - } - PatchReader reader; - try { - reader = new PatchReader(patchFile); - } - catch (IOException e) { - queueUpdateStatus(VcsBundle.message("patch.apply.open.error", e.getMessage())); - return; - } - while(true) { - FilePatch patch; - try { - patch = reader.readNextPatch(); - } - catch (PatchSyntaxException e) { - if (e.getLine() >= 0) { - queueUpdateStatus(VcsBundle.message("patch.apply.load.error.line", e.getMessage(), e.getLine())); - } - else { - queueUpdateStatus(VcsBundle.message("patch.apply.load.error", e.getMessage())); - } - return; - } - if (patch == null) { - break; - } - - final String beforeName = patch.getBeforeName(); - final String afterName = patch.getAfterName(); - final String movedMessage = RelativePathCalculator.getMovedString(beforeName, afterName); - if (movedMessage != null) { - myMoveRenameInfo.put(new Pair(beforeName, afterName), movedMessage); - } - myPatches.add(patch); - } - if (myPatches.isEmpty()) { - queueUpdateStatus(VcsBundle.message("patch.apply.no.patches.found")); - return; - } - - autoDetectBaseDirectory(); - queueUpdateStatus(null); - } - }); - } - - private void autoDetectBaseDirectory() { - boolean autodetectFailed = false; - for(FilePatch patch: myPatches) { - VirtualFile baseDir = myDetectedBaseDirectory == null - ? getBaseDirectory() - : LocalFileSystem.getInstance().findFileByPath(myDetectedBaseDirectory.replace(File.separatorChar, '/')); - int skipTopDirs = myDetectedStripLeadingDirs >= 0 ? myDetectedStripLeadingDirs : 0; - VirtualFile fileToPatch; - try { - fileToPatch = patch.findFileToPatch(new ApplyPatchContext(baseDir, skipTopDirs, false, false)); - } - catch (IOException e) { - myPatchesFailedToLoad.add(patch); - continue; - } - if (fileToPatch == null) { - boolean success = false; - if (!autodetectFailed) { - String oldDetectedBaseDirectory = myDetectedBaseDirectory; - int oldDetectedStripLeadingDirs = myDetectedStripLeadingDirs; - success = detectDirectory(patch); - if (success) { - if ((oldDetectedBaseDirectory != null && !Comparing.equal(oldDetectedBaseDirectory, myDetectedBaseDirectory)) || - (oldDetectedStripLeadingDirs >= 0 && oldDetectedStripLeadingDirs != myDetectedStripLeadingDirs)) { - myDetectedBaseDirectory = null; - myDetectedStripLeadingDirs = -1; - autodetectFailed = true; - } - } - } - if (!success) { - myPatchesFailedToLoad.add(patch); - } - } - } - } - - private boolean detectDirectory(final FilePatch patch) { - if (patch.getBeforeName().equals(patch.getAfterName()) && patch.isNewFile()) { - return false; - } else { - boolean success = detectDirectoryByName(patch.getBeforeName()); - if (! success) { - success = detectDirectoryByName(patch.getAfterName()); - } - return success; - } - } - - private Collection verifyPatchPaths() { - final ApplyPatchContext context = getApplyPatchContext(); - myPatchesFailedToLoad.clear(); - for(FilePatch patch: myPatches) { - try { - if (context.getBaseDir() == null || patch.findFileToPatch(context) == null) { - myPatchesFailedToLoad.add(patch); - } - } - catch (IOException e) { - myPatchesFailedToLoad.add(patch); - } - } - SwingUtilities.invokeLater(new Runnable() { - public void run() { - myPatchContentsList.repaint(); - myStatusLabel.setText(""); - } - }); - return context.getMissingDirectories(); - } - - private boolean detectDirectoryByName(final String patchFileName) { - PatchBaseDirectoryDetector detector = PatchBaseDirectoryDetector.getInstance(myProject); - if (detector == null) return false; - final PatchBaseDirectoryDetector.Result result = detector.detectBaseDirectory(patchFileName); - if (result == null) return false; - myDetectedBaseDirectory = result.baseDir; - myDetectedStripLeadingDirs = result.stripDirs; - return true; - } - - private void queueUpdateStatus(final String s) { - if (!SwingUtilities.isEventDispatchThread()) { - SwingUtilities.invokeLater(new Runnable() { - public void run() { - queueUpdateStatus(s); - } - }); - return; - } - updateStatus(s); - } - - private void updateStatus(String s) { - myInnerChange = true; - try { - if (myDetectedBaseDirectory != null) { - myBaseDirectoryField.setText(myDetectedBaseDirectory); - myDetectedBaseDirectory = null; - } - if (myDetectedStripLeadingDirs != -1) { - myStripLeadingDirectoriesSpinner.setValue(myDetectedStripLeadingDirs); - myDetectedStripLeadingDirs = -1; - } - } - finally { - myInnerChange = false; - } - myLoadPatchError = s; - if (s == null) { - myStatusLabel.setForeground(UIUtil.getLabelForeground()); - myStatusLabel.setText(buildPatchSummary()); - } - else { - myStatusLabel.setText(s); - myStatusLabel.setForeground(Color.red); - } - updatePatchTableModel(); - updateOKAction(); - } - - private void updatePatchTableModel() { - if (myPatches != null) { - myPatchContentsList.setModel(new CollectionListModel(myPatches)); - } - else { - myPatchContentsList.setModel(new DefaultListModel()); - } - myShowDiffButton.setEnabled(myPatches != null && myPatches.size() > 0); - } - - private String buildPatchSummary() { - int newFiles = 0; - int changedFiles = 0; - int deletedFiles = 0; - for(FilePatch patch: myPatches) { - if (patch.isNewFile()) { - newFiles++; - } - else if (patch.isDeletedFile()) { - deletedFiles++; - } - else { - changedFiles++; - } - } - StringBuilder summaryBuilder = new StringBuilder("").append(VcsBundle.message("apply.patch.summary.title")).append(" "); - appendSummary(changedFiles, 0, summaryBuilder, "patch.summary.changed.files"); - appendSummary(newFiles, changedFiles, summaryBuilder, "patch.summary.new.files"); - appendSummary(deletedFiles, changedFiles + newFiles, summaryBuilder, "patch.summary.deleted.files"); - summaryBuilder.append(""); - return summaryBuilder.toString(); - } - - private static void appendSummary(final int count, final int prevCount, final StringBuilder summaryBuilder, - @PropertyKey(resourceBundle = "messages.VcsBundle") final String key) { - if (count > 0) { - if (prevCount > 0) { - summaryBuilder.append(", "); - } - summaryBuilder.append(VcsBundle.message(key, count)); - } - } - - @Override - protected void dispose() { - myLoadPatchAlarm.dispose(); - myVerifyPatchAlarm.dispose(); - super.dispose(); - } - - private void updateOKAction() { - setOKActionEnabled(myFileNameField.getText().length() > 0 && myLoadPatchError == null); - } - - @Override - protected void doOKAction() { - if (myPatches == null) { - myLoadPatchAlarm.cancelAllRequests(); - checkLoadPatches(false); - } - if (myLoadPatchError == null) { - mySelectedChangeList = myChangeListChooser.getSelectedList(myProject); - if (mySelectedChangeList == null) return; - final Collection missingDirs = verifyPatchPaths(); - if (missingDirs.size() > 0 && !checkCreateMissingDirs(missingDirs)) return; - if (getBaseDirectory() == null) { - Messages.showErrorDialog(getContentPane(), "Could not find patch base directory " + myBaseDirectoryField.getText()); - return; - } - super.doOKAction(); - } - } - - private boolean checkCreateMissingDirs(final Collection missingDirs) { - StringBuilder messageBuilder = new StringBuilder(VcsBundle.message("apply.patch.create.dirs.prompt.header")); - for(String missingDir: missingDirs) { - messageBuilder.append(missingDir).append("\r\n"); - } - messageBuilder.append(VcsBundle.message("apply.patch.create.dirs.prompt.footer")); - int rc = Messages.showYesNoCancelDialog(myProject, messageBuilder.toString(), VcsBundle.message("patch.apply.dialog.title"), - Messages.getQuestionIcon()); - if (rc == 0) { - CommandProcessor.getInstance().executeCommand(myProject, new Runnable() { - public void run() { - for(String dir: missingDirs) { - try { - VfsUtil.createDirectories(dir); - } - catch (IOException e) { - Messages.showErrorDialog(myProject, "Error creating directories: " + e.getMessage(), - VcsBundle.message("patch.apply.dialog.title")); - } - } - } - }, "Creating directories for new files in patch", null); - } - else if (rc != 1) { - return false; - } - return true; - } - - @Nullable - protected JComponent createCenterPanel() { - return myRootPanel; - } - - public List getPatches() { - return myPatches; - } - - private VirtualFile getBaseDirectory() { - if (ApplicationManager.getApplication().isDispatchThread()) { - return LocalFileSystem.getInstance().refreshAndFindFileByPath(FileUtil.toSystemIndependentName(myBaseDirectoryField.getText())); - } - return LocalFileSystem.getInstance().findFileByPath(FileUtil.toSystemIndependentName(myBaseDirectoryField.getText())); - } - - private int getStripLeadingDirectories() { - return ((Integer) myStripLeadingDirectoriesSpinner.getValue()).intValue(); - } - - public ApplyPatchContext getApplyPatchContext() { - return new ApplyPatchContext(getBaseDirectory(), getStripLeadingDirectories(), false, false); - } - - public LocalChangeList getSelectedChangeList() { - return mySelectedChangeList; - } - - private static String getChangeType(final FilePatch filePatch) { - if (filePatch.isNewFile()) return VcsBundle.message("change.type.new"); - if (filePatch.isDeletedFile()) return VcsBundle.message("change.type.deleted"); - return VcsBundle.message("change.type.modified"); - } - - protected void doHelpAction() { - HelpManager.getInstance().invokeHelp("reference.dialogs.vcs.patch.apply"); - } - - protected Action[] createActions() { - return new Action[]{ getOKAction(), getCancelAction(), getHelpAction() }; - } - - private void createUIComponents() { - myChangeListChooser = new ChangeListChooserPanel(null, new Consumer() { - public void consume(final String errorMessage) { - setOKActionEnabled(errorMessage == null); - setErrorText(errorMessage); - } - }); - } - - private class PatchCellRendererPanel extends JPanel implements ListCellRenderer { - private final PatchCellRenderer myRenderer; - private final JLabel myFileTypeLabel; - - public PatchCellRendererPanel() { - super(new BorderLayout()); - setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 2)); - myRenderer = new PatchCellRenderer(); - add(myRenderer, BorderLayout.CENTER); - myFileTypeLabel = new JLabel(); - myFileTypeLabel.setHorizontalAlignment(JLabel.RIGHT); - add(myFileTypeLabel, BorderLayout.EAST); - } - - public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { - FilePatch patch = (FilePatch) value; - myRenderer.getListCellRendererComponent(list, value, index, isSelected, false); - myFileTypeLabel.setText("(" + getChangeType(patch) + ")"); - if (isSelected) { - setBackground(UIUtil.getListSelectionBackground()); - setForeground(UIUtil.getListSelectionForeground()); - myFileTypeLabel.setForeground(UIUtil.getListSelectionForeground()); - } - else { - setBackground(UIUtil.getListBackground()); - setForeground(UIUtil.getListForeground()); - myFileTypeLabel.setForeground(Color.gray); - } - return this; - } - } - - private class PatchCellRenderer extends ColoredListCellRenderer { - private final SimpleTextAttributes myNewAttributes = new SimpleTextAttributes(0, FileStatus.ADDED.getColor()); - private final SimpleTextAttributes myDeletedAttributes = new SimpleTextAttributes(0, FileStatus.DELETED.getColor()); - private final SimpleTextAttributes myModifiedAttributes = new SimpleTextAttributes(0, FileStatus.MODIFIED.getColor()); - - private boolean assumeProblemWillBeFixed(final FilePatch filePatch) { - // if some of the files are valid, assume that "red" new files will be fixed by creating directories - if (myPatches == null || myPatchesFailedToLoad == null) return false; - return (filePatch.isNewFile() && myPatchesFailedToLoad.size() != myPatches.size()); - } - - protected void customizeCellRenderer(JList list, Object value, int index, boolean selected, boolean hasFocus) { - FilePatch filePatch = (FilePatch) value; - String name = filePatch.getAfterNameRelative(getStripLeadingDirectories()); - - final FileType fileType = FileTypeManager.getInstance().getFileTypeByFileName(name); - setIcon(fileType.getIcon()); - - if (myPatchesFailedToLoad.contains(filePatch) && !assumeProblemWillBeFixed(filePatch)) { - append(name, SimpleTextAttributes.ERROR_ATTRIBUTES); - } - else if (filePatch.isNewFile()) { - append(name, myNewAttributes); - } - else if (filePatch.isDeletedFile()) { - append(name, myDeletedAttributes); - } - else { - append(name, myModifiedAttributes); - } - - final String afterPath = filePatch.getAfterName(); - final String beforePath = filePatch.getBeforeName(); - - if ((beforePath != null) && (afterPath != null) && (! beforePath.equals(afterPath))) { - final String message = myMoveRenameInfo.get(new Pair(beforePath, afterPath)); - if (message != null) { - append(message, SimpleTextAttributes.REGULAR_ATTRIBUTES); - } - } - } - } -} diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/patch/ApplyPatchDifferentiatedDialog.java b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/patch/ApplyPatchDifferentiatedDialog.java index 9b68b9efec02..d0a1e0f6508a 100644 --- a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/patch/ApplyPatchDifferentiatedDialog.java +++ b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/patch/ApplyPatchDifferentiatedDialog.java @@ -15,11 +15,9 @@ */ package com.intellij.openapi.vcs.changes.patch; +import com.intellij.ide.util.PropertiesComponent; import com.intellij.openapi.actionSystem.*; -import com.intellij.openapi.diff.impl.patch.FilePatch; -import com.intellij.openapi.diff.impl.patch.PatchReader; -import com.intellij.openapi.diff.impl.patch.PatchSyntaxException; -import com.intellij.openapi.diff.impl.patch.TextFilePatch; +import com.intellij.openapi.diff.impl.patch.*; import com.intellij.openapi.fileChooser.FileChooser; import com.intellij.openapi.fileChooser.FileChooserDescriptor; import com.intellij.openapi.fileChooser.FileChooserDialog; @@ -212,7 +210,7 @@ public class ApplyPatchDifferentiatedDialog extends DialogWrapper { } PatchReader reader; try { - reader = new PatchReader(patchFile); + reader = PatchVirtualFileReader.create(patchFile); } catch (IOException e) { //todo @@ -817,8 +815,10 @@ public class ApplyPatchDifferentiatedDialog extends DialogWrapper { } private class MyShowDiff extends AnAction { + private final MyChangeComparator myMyChangeComparator; private MyShowDiff() { super("Show Diff", "Show Diff", IconLoader.getIcon("/actions/diff.png")); + myMyChangeComparator = new MyChangeComparator(); } public void update(AnActionEvent e) { @@ -828,7 +828,7 @@ public class ApplyPatchDifferentiatedDialog extends DialogWrapper { public void actionPerformed(AnActionEvent e) { if (myPatches.isEmpty() || (! myContainBasedChanges)) return; final List changes = getAllChanges(); - Collections.sort(changes, MyChangeComparator.getInstance()); + Collections.sort(changes, myMyChangeComparator); final List selectedChanges = myChangesTreeList.getSelectedChanges(); int selectedIdx = 0; @@ -852,14 +852,11 @@ public class ApplyPatchDifferentiatedDialog extends DialogWrapper { } } - private static class MyChangeComparator implements Comparator { - private static final MyChangeComparator ourInstance = new MyChangeComparator(); - - public static MyChangeComparator getInstance() { - return ourInstance; - } - + private class MyChangeComparator implements Comparator { public int compare(FilePatchInProgress.PatchChange o1, FilePatchInProgress.PatchChange o2) { + if (PropertiesComponent.getInstance(myProject).isTrueValue("ChangesBrowser.SHOW_FLATTEN")) { + return o1.getPatchInProgress().getIoCurrentBase().getName().compareTo(o2.getPatchInProgress().getIoCurrentBase().getName()); + } return o1.getPatchInProgress().getIoCurrentBase().compareTo(o2.getPatchInProgress().getIoCurrentBase()); } } diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/patch/ApplyPatchForBaseRevisionTexts.java b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/patch/ApplyPatchForBaseRevisionTexts.java index 9e7722e1135a..d1c5902efc6d 100644 --- a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/patch/ApplyPatchForBaseRevisionTexts.java +++ b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/patch/ApplyPatchForBaseRevisionTexts.java @@ -18,13 +18,13 @@ package com.intellij.openapi.vcs.changes.patch; import com.intellij.openapi.diff.impl.patch.ApplyPatchException; import com.intellij.openapi.diff.impl.patch.ApplyPatchStatus; import com.intellij.openapi.diff.impl.patch.TextFilePatch; +import com.intellij.openapi.diff.impl.patch.apply.ApplyFilePatchBase; import com.intellij.openapi.fileEditor.impl.LoadTextUtil; import com.intellij.openapi.project.Project; import com.intellij.openapi.vcs.FilePath; import com.intellij.openapi.vcs.VcsException; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.util.Processor; -import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; public class ApplyPatchForBaseRevisionTexts { @@ -58,7 +58,7 @@ public class ApplyPatchForBaseRevisionTexts { public boolean process(final CharSequence text) { newText.setLength(0); try { - myStatus = patch.applyModifications(text, newText); + myStatus = ApplyFilePatchBase.applyModifications(patch, text, newText); } catch(ApplyPatchException ex) { return true; // continue to older versions diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/patch/CreatePatchCommitExecutor.java b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/patch/CreatePatchCommitExecutor.java index 71d2d5312857..c6b80cebd11f 100644 --- a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/patch/CreatePatchCommitExecutor.java +++ b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/patch/CreatePatchCommitExecutor.java @@ -22,7 +22,7 @@ import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.components.ProjectComponent; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.diff.impl.patch.FilePatch; -import com.intellij.openapi.diff.impl.patch.PatchBuilder; +import com.intellij.openapi.diff.impl.patch.TextPatchBuilder; import com.intellij.openapi.diff.impl.patch.UnifiedDiffWriter; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.Messages; @@ -153,7 +153,7 @@ public class CreatePatchCommitExecutor implements CommitExecutor, ProjectCompone REVERSE_PATCH = myPanel.isReversePatch(); Writer writer = new OutputStreamWriter(new FileOutputStream(fileName)); try { - List patches = PatchBuilder.buildPatch(changes, myProject.getBaseDir().getPresentableUrl(), false, REVERSE_PATCH); + List patches = TextPatchBuilder.buildPatch(changes, myProject.getBaseDir().getPresentableUrl(), REVERSE_PATCH); final String lineSeparator = CodeStyleFacade.getInstance(myProject).getLineSeparator(); UnifiedDiffWriter.write(patches, writer, lineSeparator); } diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/patch/FilePatchInProgress.java b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/patch/FilePatchInProgress.java index a785c66343d6..780784f9e287 100644 --- a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/patch/FilePatchInProgress.java +++ b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/patch/FilePatchInProgress.java @@ -32,6 +32,7 @@ import com.intellij.openapi.vcs.changes.CurrentContentRevision; import com.intellij.openapi.vcs.changes.SimpleContentRevision; import com.intellij.openapi.vcs.changes.actions.ChangeDiffRequestPresentable; import com.intellij.openapi.vcs.changes.actions.DiffRequestPresentable; +import com.intellij.openapi.vcs.changes.actions.DiffRequestPresentableProxy; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.vcsUtil.VcsUtil; import org.jetbrains.annotations.NotNull; @@ -220,19 +221,24 @@ public class FilePatchInProgress implements Strippable { @Nullable public DiffRequestPresentable createDiffRequestPresentable(final Project project) { - if (myPatchInProgress.isConflictingChange()) { - final ApplyPatchForBaseRevisionTexts texts = ApplyPatchForBaseRevisionTexts - .create(project, myPatchInProgress.getCurrentBase(), new FilePathImpl(myPatchInProgress.getCurrentBase()), - myPatchInProgress.getPatch()); - if ((texts != null) && - (ApplyPatchStatus.SUCCESS.equals(texts.getStatus()) || ApplyPatchStatus.ALREADY_APPLIED.equals(texts.getStatus()))) { - return new MergedDiffRequestPresentable(project, texts, - myPatchInProgress.getCurrentBase(), myPatchInProgress.getPatch().getAfterVersionId()); + return new DiffRequestPresentableProxy() { + @Override + protected DiffRequestPresentable init() { + if (myPatchInProgress.isConflictingChange()) { + final ApplyPatchForBaseRevisionTexts texts = ApplyPatchForBaseRevisionTexts + .create(project, myPatchInProgress.getCurrentBase(), new FilePathImpl(myPatchInProgress.getCurrentBase()), + myPatchInProgress.getPatch()); + if ((texts != null) && + (ApplyPatchStatus.SUCCESS.equals(texts.getStatus()) || ApplyPatchStatus.ALREADY_APPLIED.equals(texts.getStatus()))) { + return new MergedDiffRequestPresentable(project, texts, + myPatchInProgress.getCurrentBase(), myPatchInProgress.getPatch().getAfterVersionId()); + } + return null; + } else { + return new ChangeDiffRequestPresentable(project, PatchChange.this); + } } - return null; - } else { - return new ChangeDiffRequestPresentable(project, this); - } + }; } } diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/patch/LazyPatchContentRevision.java b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/patch/LazyPatchContentRevision.java index 4d027bfde718..4c1bbe049c16 100644 --- a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/patch/LazyPatchContentRevision.java +++ b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/patch/LazyPatchContentRevision.java @@ -17,6 +17,7 @@ package com.intellij.openapi.vcs.changes.patch; import com.intellij.openapi.diff.impl.patch.ApplyPatchException; import com.intellij.openapi.diff.impl.patch.TextFilePatch; +import com.intellij.openapi.diff.impl.patch.apply.ApplyFilePatchBase; import com.intellij.openapi.editor.Document; import com.intellij.openapi.fileEditor.FileDocumentManager; import com.intellij.openapi.vcs.FilePath; @@ -50,7 +51,7 @@ public class LazyPatchContentRevision implements ContentRevision { } final String baseContent = doc.getText(); final StringBuilder newText = new StringBuilder(); - myPatch.applyModifications(baseContent, newText); + ApplyFilePatchBase.applyModifications(myPatch, baseContent, newText); myContent = newText.toString(); } catch (ApplyPatchException e) { diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/shelf/DiffShelvedChangesAction.java b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/shelf/DiffShelvedChangesAction.java index 8133ecc3c6b2..cfbb0c06b09d 100644 --- a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/shelf/DiffShelvedChangesAction.java +++ b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/shelf/DiffShelvedChangesAction.java @@ -19,6 +19,7 @@ import com.intellij.openapi.actionSystem.*; import com.intellij.openapi.diff.DiffRequestFactory; import com.intellij.openapi.diff.MergeRequest; import com.intellij.openapi.diff.impl.patch.*; +import com.intellij.openapi.diff.impl.patch.apply.ApplyTextFilePatch; import com.intellij.openapi.project.DumbAware; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.MessageType; @@ -29,6 +30,7 @@ import com.intellij.openapi.vcs.changes.Change; import com.intellij.openapi.vcs.changes.ContentRevision; import com.intellij.openapi.vcs.changes.actions.ChangeDiffRequestPresentable; import com.intellij.openapi.vcs.changes.actions.DiffRequestPresentable; +import com.intellij.openapi.vcs.changes.actions.DiffRequestPresentableProxy; import com.intellij.openapi.vcs.changes.actions.ShowDiffAction; import com.intellij.openapi.vcs.changes.patch.ApplyPatchForBaseRevisionTexts; import com.intellij.openapi.vcs.changes.patch.MergedDiffRequestPresentable; @@ -51,7 +53,7 @@ public class DiffShelvedChangesAction extends AnAction implements DumbAware { } public static void showShelvedChangesDiff(final DataContext dc) { - Project project = PlatformDataKeys.PROJECT.getData(dc); + final Project project = PlatformDataKeys.PROJECT.getData(dc); ShelvedChangeList[] changeLists = ShelvedChangesViewManager.SHELVED_CHANGELIST_KEY.getData(dc); if (changeLists == null) { changeLists = ShelvedChangesViewManager.SHELVED_RECYCLED_CHANGELIST_KEY.getData(dc); @@ -68,7 +70,7 @@ public class DiffShelvedChangesAction extends AnAction implements DumbAware { int toSelectIdx = 0; final ArrayList diffRequestPresentables = new ArrayList(); - ApplyPatchContext context = new ApplyPatchContext(project.getBaseDir(), 0, false, false); + final ApplyPatchContext context = new ApplyPatchContext(project.getBaseDir(), 0, false, false); final PatchesPreloader preloader = new PatchesPreloader(); final List missing = new LinkedList(); @@ -76,7 +78,7 @@ public class DiffShelvedChangesAction extends AnAction implements DumbAware { final Change change = shelvedChange.getChange(project); final String beforePath = shelvedChange.getBeforePath(); try { - VirtualFile f = FilePatch.findPatchTarget(context, beforePath, shelvedChange.getAfterPath(), beforePath == null); + final VirtualFile f = ApplyTextFilePatch.findPatchTarget(context, beforePath, shelvedChange.getAfterPath(), beforePath == null); if ((f == null) || (! f.exists())) { if (beforePath != null) { missing.add(beforePath); @@ -84,22 +86,27 @@ public class DiffShelvedChangesAction extends AnAction implements DumbAware { continue; } - if (isConflictingChange(change)) { - TextFilePatch patch = preloader.getPatch(shelvedChange); - if (patch == null) continue; + diffRequestPresentables.add(new DiffRequestPresentableProxy() { + @Override + protected DiffRequestPresentable init() { + if (isConflictingChange(change)) { + TextFilePatch patch = preloader.getPatch(shelvedChange); + if (patch == null) return null; - final FilePath pathBeforeRename = context.getPathBeforeRename(f); + final FilePath pathBeforeRename = context.getPathBeforeRename(f); - final ApplyPatchForBaseRevisionTexts threeTexts = ApplyPatchForBaseRevisionTexts.create(project, f, pathBeforeRename, patch); - if ((threeTexts == null) || (threeTexts.getStatus() == null) || (ApplyPatchStatus.FAILURE.equals(threeTexts.getStatus()))) { - continue; + final ApplyPatchForBaseRevisionTexts threeTexts = ApplyPatchForBaseRevisionTexts.create(project, f, pathBeforeRename, patch); + if ((threeTexts == null) || (threeTexts.getStatus() == null) || (ApplyPatchStatus.FAILURE.equals(threeTexts.getStatus()))) { + return null; + } + + return new MergedDiffRequestPresentable(project, threeTexts, f, "Shelved Version"); + } + else { + return new ChangeDiffRequestPresentable(project, change); + } } - - diffRequestPresentables.add(new MergedDiffRequestPresentable(project, threeTexts, f, "Shelved Version")); - } - else { - diffRequestPresentables.add(new ChangeDiffRequestPresentable(project, change)); - } + }); } catch (IOException e) { continue; @@ -114,7 +121,7 @@ public class DiffShelvedChangesAction extends AnAction implements DumbAware { ChangesViewBalloonProblemNotifier.showMe(project, "Show Diff: Cannot find base for: " + StringUtil.join(missing, ",\n"), MessageType.WARNING); } - ShowDiffAction.showDiffImpl(project, diffRequestPresentables, toSelectIdx, ShowDiffAction.DiffExtendUIFactory.NONE, true); + ShowDiffAction.showDiffImpl(project, diffRequestPresentables, toSelectIdx, ShowDiffAction.DiffExtendUIFactory.NONE, false); } private static class PatchesPreloader { diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/shelf/ShelveChangesManager.java b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/shelf/ShelveChangesManager.java index 167e4c63e80a..d7c2ac6570b3 100644 --- a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/shelf/ShelveChangesManager.java +++ b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/shelf/ShelveChangesManager.java @@ -27,12 +27,13 @@ import com.intellij.openapi.application.PathManager; import com.intellij.openapi.components.ProjectComponent; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.diff.impl.patch.*; +import com.intellij.openapi.diff.impl.patch.apply.ApplyFilePatchBase; import com.intellij.openapi.diff.impl.patch.formove.CustomBinaryPatchApplier; import com.intellij.openapi.diff.impl.patch.formove.PatchApplier; import com.intellij.openapi.options.StreamProvider; -import com.intellij.openapi.progress.ProgressManager; -import com.intellij.openapi.progress.ProgressIndicator; import com.intellij.openapi.progress.ProcessCanceledException; +import com.intellij.openapi.progress.ProgressIndicator; +import com.intellij.openapi.progress.ProgressManager; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.*; import com.intellij.openapi.util.io.FileUtil; @@ -170,7 +171,7 @@ public class ShelveChangesManager implements ProjectComponent, JDOMExternalizabl if (ind != null && ind.isCanceled()) { throw new ProcessCanceledException(); } - final List patches = PatchBuilder.buildPatch(textChanges, myProject.getBaseDir().getPresentableUrl(), true, false); + final List patches = TextPatchBuilder.buildPatch(textChanges, myProject.getBaseDir().getPresentableUrl(), false); if (ind != null && ind.isCanceled()) { throw new ProcessCanceledException(); } @@ -269,7 +270,7 @@ public class ShelveChangesManager implements ProjectComponent, JDOMExternalizabl } final BinaryPatchApplier binaryPatchApplier = new BinaryPatchApplier(binaryFilesToUnshelve.size()); - final PatchApplier patchApplier = new PatchApplier(myProject, myProject.getBaseDir(), patches, targetChangeList, binaryPatchApplier); + final PatchApplier patchApplier = new PatchApplier(myProject, myProject.getBaseDir(), patches, targetChangeList, binaryPatchApplier); patchApplier.execute(); remainingPatches.addAll(patchApplier.getRemainingPatches()); @@ -299,7 +300,7 @@ public class ShelveChangesManager implements ProjectComponent, JDOMExternalizabl return textFilePatches; } - private class BinaryPatchApplier implements CustomBinaryPatchApplier { + private class BinaryPatchApplier implements CustomBinaryPatchApplier { private final List myAppliedPatches; private BinaryPatchApplier(final int binaryCount) { @@ -307,9 +308,9 @@ public class ShelveChangesManager implements ProjectComponent, JDOMExternalizabl } @NotNull - public ApplyPatchStatus apply(final List> patches) throws IOException { - for (Pair patch : patches) { - final ShelvedBinaryFilePatch shelvedPatch = (ShelvedBinaryFilePatch) patch.getSecond(); + public ApplyPatchStatus apply(final List>> patches) throws IOException { + for (Pair> patch : patches) { + final ShelvedBinaryFilePatch shelvedPatch = patch.getSecond().getPatch(); unshelveBinaryFile(shelvedPatch.getShelvedBinaryFile(), patch.getFirst()); myAppliedPatches.add(shelvedPatch); } @@ -522,7 +523,7 @@ public class ShelveChangesManager implements ProjectComponent, JDOMExternalizabl return reader.readAllPatches(); } - private static class ShelvedBinaryFilePatch extends FilePatch { + public static class ShelvedBinaryFilePatch extends FilePatch { private final ShelvedBinaryFile myShelvedBinaryFile; public ShelvedBinaryFilePatch(final ShelvedBinaryFile shelvedBinaryFile) { @@ -543,11 +544,6 @@ public class ShelveChangesManager implements ProjectComponent, JDOMExternalizabl return pathNameComponents [pathNameComponents.length-1]; } - protected void applyCreate(final VirtualFile newFile) throws IOException, ApplyPatchException { - } - protected ApplyPatchStatus applyChange(final VirtualFile fileToPatch) throws IOException, ApplyPatchException { - return null; - } public boolean isNewFile() { return myShelvedBinaryFile.BEFORE_PATH == null; } diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/shelf/ShelvedChange.java b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/shelf/ShelvedChange.java index 62210f3339a3..94b705d598ee 100644 --- a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/shelf/ShelvedChange.java +++ b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/shelf/ShelvedChange.java @@ -26,6 +26,7 @@ import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.diff.impl.patch.ApplyPatchException; import com.intellij.openapi.diff.impl.patch.PatchSyntaxException; import com.intellij.openapi.diff.impl.patch.TextFilePatch; +import com.intellij.openapi.diff.impl.patch.apply.ApplyFilePatchBase; import com.intellij.openapi.editor.Document; import com.intellij.openapi.fileEditor.FileDocumentManager; import com.intellij.openapi.project.Project; @@ -203,7 +204,7 @@ public class ShelvedChange { return null; } StringBuilder newText = new StringBuilder(); - patch.applyModifications(getBaseContent(), newText); + ApplyFilePatchBase.applyModifications(patch, getBaseContent(), newText); return newText.toString(); } diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/configurable/VcsGeneralConfigurationPanel.form b/platform/vcs-impl/src/com/intellij/openapi/vcs/configurable/VcsGeneralConfigurationPanel.form index 21e3f625ca9f..6167748fc95a 100644 --- a/platform/vcs-impl/src/com/intellij/openapi/vcs/configurable/VcsGeneralConfigurationPanel.form +++ b/platform/vcs-impl/src/com/intellij/openapi/vcs/configurable/VcsGeneralConfigurationPanel.form @@ -1,6 +1,6 @@
    - + @@ -8,24 +8,15 @@ - - - - - - - - - - + - + @@ -92,42 +83,88 @@ - + - - - + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - + - + - + - + - + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -135,7 +172,7 @@ - + @@ -144,7 +181,7 @@ - + @@ -155,49 +192,22 @@ - + - + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + @@ -211,5 +221,10 @@ + + + + + diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/configurable/VcsGeneralConfigurationPanel.java b/platform/vcs-impl/src/com/intellij/openapi/vcs/configurable/VcsGeneralConfigurationPanel.java index 0ccc62d5fad6..fcfa0d99a237 100644 --- a/platform/vcs-impl/src/com/intellij/openapi/vcs/configurable/VcsGeneralConfigurationPanel.java +++ b/platform/vcs-impl/src/com/intellij/openapi/vcs/configurable/VcsGeneralConfigurationPanel.java @@ -15,7 +15,6 @@ */ package com.intellij.openapi.vcs.configurable; -import com.intellij.openapi.application.ApplicationNamesInfo; import com.intellij.openapi.options.Configurable; import com.intellij.openapi.options.ConfigurationException; import com.intellij.openapi.project.Project; @@ -60,8 +59,7 @@ public class VcsGeneralConfigurationPanel implements Configurable { private JPanel myAddConfirmationPanel; private JCheckBox myCbOfferToMoveChanges; private JComboBox myFailedCommitChangelistCombo; - private JLabel myRemoveConfirmationLabel; - private JLabel myAddConfirmationLabel; + private ButtonGroup myEmptyChangelistRemovingGroup; public VcsGeneralConfigurationPanel(final Project project) { @@ -93,8 +91,6 @@ public class VcsGeneralConfigurationPanel implements Configurable { myPromptsPanel.setSize(myPromptsPanel.getPreferredSize()); - myAddConfirmationLabel.setText(VcsBundle.message("add.confirmation.label.text", ApplicationNamesInfo.getInstance().getProductName())); - myRemoveConfirmationLabel.setText(VcsBundle.message("remove.confirmation.label.text", ApplicationNamesInfo.getInstance().getProductName())); } public void apply() throws ConfigurationException { @@ -103,6 +99,7 @@ public class VcsGeneralConfigurationPanel implements Configurable { settings.FORCE_NON_EMPTY_COMMENT = myForceNonEmptyComment.isSelected(); settings.OFFER_MOVE_TO_ANOTHER_CHANGELIST_ON_PARTIAL_COMMIT = myCbOfferToMoveChanges.isSelected(); + settings.REMOVE_EMPTY_INACTIVE_CHANGELISTS = getSelected(myEmptyChangelistRemovingGroup); settings.MOVE_TO_FAILED_COMMIT_CHANGELIST = getFailedCommitConfirm(); for (VcsShowOptionsSettingImpl setting : myPromptOptions.keySet()) { @@ -141,6 +138,15 @@ public class VcsGeneralConfigurationPanel implements Configurable { return VcsShowConfirmationOption.Value.DO_NOTHING_SILENTLY; } + private static VcsShowConfirmationOption.Value getSelected(ButtonGroup group) { + switch (UIUtil.getSelectedButton(group)) { + case 0: + return VcsShowConfirmationOption.Value.SHOW_CONFIRMATION; + case 1: + return VcsShowConfirmationOption.Value.DO_ACTION_SILENTLY; + } + return VcsShowConfirmationOption.Value.DO_NOTHING_SILENTLY; + } private ReadonlyStatusHandlerImpl getReadOnlyStatusHandler() { return ((ReadonlyStatusHandlerImpl)ReadonlyStatusHandler.getInstance(myProject)); @@ -155,6 +161,9 @@ public class VcsGeneralConfigurationPanel implements Configurable { if (settings.OFFER_MOVE_TO_ANOTHER_CHANGELIST_ON_PARTIAL_COMMIT != myCbOfferToMoveChanges.isSelected()){ return true; } + if (settings.REMOVE_EMPTY_INACTIVE_CHANGELISTS != getSelected(myEmptyChangelistRemovingGroup)){ + return true; + } if (!Comparing.equal(getFailedCommitConfirm(), settings.MOVE_TO_FAILED_COMMIT_CHANGELIST)) { return true; @@ -178,6 +187,8 @@ public class VcsGeneralConfigurationPanel implements Configurable { VcsConfiguration settings = VcsConfiguration.getInstance(myProject); myForceNonEmptyComment.setSelected(settings.FORCE_NON_EMPTY_COMMENT); myCbOfferToMoveChanges.setSelected(settings.OFFER_MOVE_TO_ANOTHER_CHANGELIST_ON_PARTIAL_COMMIT); + int id = settings.REMOVE_EMPTY_INACTIVE_CHANGELISTS.getId(); + UIUtil.setSelectedButton(myEmptyChangelistRemovingGroup, id == 0 ? 0 : id == 1 ? 2 : 1); myShowReadOnlyStatusDialog.setSelected(getReadOnlyStatusHandler().getState().SHOW_DIALOG); if (settings.MOVE_TO_FAILED_COMMIT_CHANGELIST == VcsShowConfirmationOption.Value.DO_ACTION_SILENTLY) { myFailedCommitChangelistCombo.setSelectedIndex(0); diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/impl/ProjectLevelVcsManagerImpl.java b/platform/vcs-impl/src/com/intellij/openapi/vcs/impl/ProjectLevelVcsManagerImpl.java index 0d6ddf1fc974..3eb7739eda6d 100644 --- a/platform/vcs-impl/src/com/intellij/openapi/vcs/impl/ProjectLevelVcsManagerImpl.java +++ b/platform/vcs-impl/src/com/intellij/openapi/vcs/impl/ProjectLevelVcsManagerImpl.java @@ -332,7 +332,7 @@ public void addMessageToConsoleWindow(final String message, final TextAttributes Content content = contentManager.findContent(displayName); if (content == null) { final EditorFactory editorFactory = EditorFactory.getInstance(); - final Editor editor = editorFactory.createViewer(editorFactory.createDocument("")); + final Editor editor = editorFactory.createViewer(editorFactory.createDocument(""), myProject); EditorSettings editorSettings = editor.getSettings(); editorSettings.setLineMarkerAreaShown(false); editorSettings.setIndentGuidesShown(false); diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/update/RefreshVFsSynchronously.java b/platform/vcs-impl/src/com/intellij/openapi/vcs/update/RefreshVFsSynchronously.java index 8852e9acf99f..74465ff77ed1 100644 --- a/platform/vcs-impl/src/com/intellij/openapi/vcs/update/RefreshVFsSynchronously.java +++ b/platform/vcs-impl/src/com/intellij/openapi/vcs/update/RefreshVFsSynchronously.java @@ -203,9 +203,13 @@ public class RefreshVFsSynchronously { ApplicationManager.getApplication().invokeLater(new Runnable() { public void run() { - // common lock for all refreshes inside - runnable.run(); - semaphore.up(); + try { + // common lock for all refreshes inside + runnable.run(); + } + finally { + semaphore.up(); + } } }); semaphore.waitFor(); diff --git a/plugins/InspectionGadgets/src/com/siyeh/ig/abstraction/StaticMethodOnlyUsedInOneClassInspection.java b/plugins/InspectionGadgets/src/com/siyeh/ig/abstraction/StaticMethodOnlyUsedInOneClassInspection.java index 7713f9969ef8..d63719f8ecbf 100644 --- a/plugins/InspectionGadgets/src/com/siyeh/ig/abstraction/StaticMethodOnlyUsedInOneClassInspection.java +++ b/plugins/InspectionGadgets/src/com/siyeh/ig/abstraction/StaticMethodOnlyUsedInOneClassInspection.java @@ -39,7 +39,7 @@ import com.siyeh.ig.psiutils.ClassUtils; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; public class StaticMethodOnlyUsedInOneClassInspection extends BaseInspection { @@ -105,10 +105,10 @@ public class StaticMethodOnlyUsedInOneClassInspection } public BaseInspectionVisitor buildVisitor() { - return new StaticmethodOnlyUsedInOneClassVisitor(); + return new StaticMethodOnlyUsedInOneClassVisitor(); } - private static class StaticmethodOnlyUsedInOneClassVisitor + private static class StaticMethodOnlyUsedInOneClassVisitor extends BaseInspectionVisitor { @Override public void visitMethod(PsiMethod method) { @@ -151,23 +151,16 @@ public class StaticMethodOnlyUsedInOneClassInspection } private static class UsageProcessor implements Processor { - - private PsiClass usageClass = null; + private final AtomicReference foundClass = new AtomicReference(); public boolean process(PsiReference reference) { ProgressManager.checkCanceled(); final PsiElement element = reference.getElement(); - final PsiClass usageClass = - ClassUtils.getContainingClass(element); - synchronized (this) { - if (this.usageClass != null && - !this.usageClass.equals(usageClass)) { - this.usageClass = null; - return false; - } - this.usageClass = usageClass; - } - return true; + PsiClass usageClass = ClassUtils.getContainingClass(element); + if (usageClass == null) return true; + if (foundClass.compareAndSet(null, usageClass)) return true; + PsiClass aClass = foundClass.get(); + return usageClass.getManager().areElementsEquivalent(aClass, usageClass); } /** @@ -183,42 +176,18 @@ public class StaticMethodOnlyUsedInOneClassInspection final String name = method.getName(); final GlobalSearchScope scope = GlobalSearchScope.allScope(method.getProject()); - final FindUsagesCostProcessor costProcessor = - new FindUsagesCostProcessor(); - searchHelper.processAllFilesWithWord(name, scope, - costProcessor, true); - if (costProcessor.isCostTooHigh()) { - return null; - } + if (searchHelper.isCheapEnoughToSearch(name, scope, null) + == PsiSearchHelper.SearchCostResult.TOO_MANY_OCCURRENCES) return null; progressManager.runProcess(new Runnable() { public void run() { final Query query = MethodReferencesSearch.search(method); - query.forEach(UsageProcessor.this); + if (!query.forEach(UsageProcessor.this)) { + foundClass.set(null); + } } }, null); - synchronized (this) { - return usageClass; - } - } - - private static class FindUsagesCostProcessor - implements Processor { - - private final AtomicInteger counter = new AtomicInteger(); - private volatile boolean costTooHigh = false; - - public boolean process(PsiFile psiFile) { - if (counter.incrementAndGet() >= 10) { - costTooHigh = true; - return false; - } - return true; - } - - public boolean isCostTooHigh() { - return costTooHigh; - } + return foundClass.get(); } } } diff --git a/plugins/InspectionGadgets/src/com/siyeh/ig/classlayout/UtilityClassWithoutPrivateConstructorInspection.java b/plugins/InspectionGadgets/src/com/siyeh/ig/classlayout/UtilityClassWithoutPrivateConstructorInspection.java index cc371bfd5a81..997a5792b9b1 100644 --- a/plugins/InspectionGadgets/src/com/siyeh/ig/classlayout/UtilityClassWithoutPrivateConstructorInspection.java +++ b/plugins/InspectionGadgets/src/com/siyeh/ig/classlayout/UtilityClassWithoutPrivateConstructorInspection.java @@ -18,6 +18,7 @@ package com.siyeh.ig.classlayout; import com.intellij.codeInspection.ProblemDescriptor; import com.intellij.codeInspection.ui.SingleCheckboxOptionsPanel; import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.Messages; import com.intellij.psi.*; import com.intellij.psi.codeStyle.CodeStyleManager; import com.intellij.psi.search.GlobalSearchScope; @@ -70,18 +71,6 @@ public class UtilityClassWithoutPrivateConstructorInspection final PsiClass aClass = (PsiClass)infos[0]; final PsiMethod constructor = getNullArgConstructor(aClass); if (constructor == null) { - final Query query = - ReferencesSearch.search(aClass, aClass.getUseScope()); - for (PsiReference reference : query) { - if (reference == null) { - continue; - } - final PsiElement element = reference.getElement(); - final PsiElement parent = element.getParent(); - if (parent instanceof PsiNewExpression) { - return null; - } - } return new CreateEmptyPrivateConstructor(); } else { final Query query = @@ -113,6 +102,25 @@ public class UtilityClassWithoutPrivateConstructorInspection if (aClass == null) { return; } + final Query query = + ReferencesSearch.search(aClass, aClass.getUseScope()); + for (PsiReference reference : query) { + if (reference == null) { + continue; + } + final PsiElement element = reference.getElement(); + final PsiElement parent = element.getParent(); + if (parent instanceof PsiNewExpression) { + SwingUtilities.invokeLater(new Runnable() { + public void run() { + Messages.showInfoMessage(aClass.getProject(), + "Utility class has instantiations, private constructor will not be created", + "Can't generate constructor"); + } + }); + return; + } + } final JavaPsiFacade psiFacade = JavaPsiFacade.getInstance(project); final PsiElementFactory factory = psiFacade.getElementFactory(); final PsiMethod constructor = factory.createConstructor(); @@ -253,4 +261,4 @@ public class UtilityClassWithoutPrivateConstructorInspection } return null; } -} \ No newline at end of file +} diff --git a/plugins/InspectionGadgets/src/com/siyeh/ig/j2me/InterfaceWithOnlyOneDirectInheritorInspection.java b/plugins/InspectionGadgets/src/com/siyeh/ig/j2me/InterfaceWithOnlyOneDirectInheritorInspection.java index 00b0c57e7f0a..42d104d30096 100644 --- a/plugins/InspectionGadgets/src/com/siyeh/ig/j2me/InterfaceWithOnlyOneDirectInheritorInspection.java +++ b/plugins/InspectionGadgets/src/com/siyeh/ig/j2me/InterfaceWithOnlyOneDirectInheritorInspection.java @@ -26,8 +26,6 @@ import com.siyeh.ig.BaseInspection; import com.siyeh.ig.BaseInspectionVisitor; import org.jetbrains.annotations.NotNull; -import java.util.Collection; - public class InterfaceWithOnlyOneDirectInheritorInspection extends BaseInspection { @@ -71,8 +69,7 @@ public class InterfaceWithOnlyOneDirectInheritorInspection ClassInheritorsSearch.search(aClass, searchScope, false).forEach(new PsiElementProcessorAdapter(processor)); } }, null); - final Collection collection = processor.getCollection(); - return collection.size() == 1; + return !processor.isOverflow(); } } } diff --git a/plugins/git4idea/src/git4idea/changes/ChangeCollector.java b/plugins/git4idea/src/git4idea/changes/ChangeCollector.java index 58f637e1fc6e..06dac8e4d58a 100644 --- a/plugins/git4idea/src/git4idea/changes/ChangeCollector.java +++ b/plugins/git4idea/src/git4idea/changes/ChangeCollector.java @@ -304,7 +304,7 @@ class ChangeCollector { if ('M' == status) { sc.boundedToken('\t'); String file = GitUtil.unescapePath(sc.line()); - VirtualFile vFile = myVcsRoot.findFileByRelativePath(GitUtil.unescapePath(sc.line())); + VirtualFile vFile = myVcsRoot.findFileByRelativePath(file); if (GitUtil.gitRootOrNull(vFile) != myVcsRoot) { continue; } diff --git a/plugins/git4idea/src/org/jetbrains/git4idea/ssh/GitSSHService.java b/plugins/git4idea/src/org/jetbrains/git4idea/ssh/GitSSHService.java index 3522b2fc96c2..db8584376b4d 100644 --- a/plugins/git4idea/src/org/jetbrains/git4idea/ssh/GitSSHService.java +++ b/plugins/git4idea/src/org/jetbrains/git4idea/ssh/GitSSHService.java @@ -115,7 +115,7 @@ public class GitSSHService implements ApplicationComponent { */ @NotNull public synchronized File getScriptPath() throws IOException { - if (myScriptPath == null) { + if (myScriptPath == null || !myScriptPath.exists()) { ScriptGenerator generator = new ScriptGenerator(GIT_SSH_PREFIX, SSHMain.class); generator.addInternal(Integer.toString(myXmlRpcServer.getPortNumber())); generator.addClasses(XmlRpcClientLite.class, DecoderException.class); diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/actions/GroovyTemplatesFactory.java b/plugins/groovy/src/org/jetbrains/plugins/groovy/actions/GroovyTemplatesFactory.java index 228fbddfeb83..960e458a0424 100644 --- a/plugins/groovy/src/org/jetbrains/plugins/groovy/actions/GroovyTemplatesFactory.java +++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/actions/GroovyTemplatesFactory.java @@ -40,13 +40,12 @@ public class GroovyTemplatesFactory implements FileTemplateGroupDescriptorFactor } } - private static GroovyTemplatesFactory myInstance = null; + private static class GroovyTemplatesFactoryHolder { + private static final GroovyTemplatesFactory myInstance = new GroovyTemplatesFactory(); + } public static GroovyTemplatesFactory getInstance() { - if (myInstance == null) { - myInstance = new GroovyTemplatesFactory(); - } - return myInstance; + return GroovyTemplatesFactoryHolder.myInstance; } private final ArrayList myCustomTemplates = new ArrayList(); diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/parameterInfo/GroovyParameterInfoHandler.java b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/parameterInfo/GroovyParameterInfoHandler.java index f3f788d6616a..c4b445113d5f 100644 --- a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/parameterInfo/GroovyParameterInfoHandler.java +++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/parameterInfo/GroovyParameterInfoHandler.java @@ -315,7 +315,14 @@ public class GroovyParameterInfoHandler implements ParameterInfoHandler 0) { diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/dataFlow/types/TypesSemilattice.java b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/dataFlow/types/TypesSemilattice.java index c6ae0505d6b3..c85d8486118f 100644 --- a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/dataFlow/types/TypesSemilattice.java +++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/dataFlow/types/TypesSemilattice.java @@ -36,7 +36,7 @@ public class TypesSemilattice implements Semilattice> { public Map join(ArrayList> ins) { if (ins.size() == 0) return new HashMap(); - + Map result = new HashMap(ins.get(0)); for (int i = 1; i < ins.size(); i++) { @@ -48,10 +48,9 @@ public class TypesSemilattice implements Semilattice> { if (result.containsKey(name)) { final PsiType t2 = result.get(name); if (t1 != null && t2 != null) { - if (t1.isAssignableFrom(t2)) result.put(name, t1); - else if (t2.isAssignableFrom(t1)) result.put(name, t2); - else result.put(name, TypesUtil.getLeastUpperBound(t1, t2, myManager)); - } else { + result.put(name, TypesUtil.getLeastUpperBound(t1, t2, myManager)); + } + else { result.put(name, null); } } @@ -71,7 +70,8 @@ public class TypesSemilattice implements Semilattice> { final PsiType t2 = e2.get(name); if (t1 == null || t2 == null) { if (t1 != null || t2 != null) return false; - } else { + } + else { if (!t1.equals(t2)) return false; } } diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/impl/GrClosureSignatureUtil.java b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/impl/GrClosureSignatureUtil.java index 39d508ae4c76..1a1ceda96e20 100644 --- a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/impl/GrClosureSignatureUtil.java +++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/impl/GrClosureSignatureUtil.java @@ -56,9 +56,10 @@ public class GrClosureSignatureUtil { if (signature.isVarargs()) { if (isApplicable(params, args, params.length - 1, args.length, manager, scope)) return true; - PsiType lastType = params[params.length - 1].getType(); assert lastType instanceof PsiArrayType; + if (TypesUtil.isAssignableByMethodCallConversion(lastType, args[args.length - 1], manager, scope)) return true; + PsiType varargType = ((PsiArrayType)lastType).getComponentType(); for (int argCount = args.length - 1; argCount >= notOptional; argCount--) { diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/impl/statements/GrFieldImpl.java b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/impl/statements/GrFieldImpl.java index e40babd90f56..10ef8c47d9bf 100644 --- a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/impl/statements/GrFieldImpl.java +++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/impl/statements/GrFieldImpl.java @@ -190,26 +190,20 @@ public class GrFieldImpl extends GrVariableBaseImpl implements GrFi final PsiClass clazz = getContainingClass(); name = GroovyPropertyUtils.capitalize(name); GrAccessorMethod getter1 = new GrAccessorMethodImpl(this, false, "get" + name); - if (hasContradictingMethods(getter1, clazz)) getter1 = null; + if (!hasContradictingMethods(getter1, clazz)) { + GrAccessorMethod getter2 = null; + if (PsiType.BOOLEAN.equals(getDeclaredType())) { + getter2 = new GrAccessorMethodImpl(this, false, "is" + name); + if (hasContradictingMethods(getter2, clazz)) getter2 = null; + } - GrAccessorMethod getter2 = null; - if (PsiType.BOOLEAN.equals(getDeclaredType())) { - getter2 = new GrAccessorMethodImpl(this, false, "is" + name); - if (hasContradictingMethods(getter2, clazz)) getter2 = null; - } - - if (getter1 != null || getter2 != null) { - if (getter1 != null && getter2 != null) { + if (getter2 != null) { myGetters = new GrAccessorMethod[]{getter1, getter2}; } - else if (getter1 != null) { + else { myGetters = new GrAccessorMethod[]{getter1}; } - else { - myGetters = new GrAccessorMethod[]{getter2}; - } } - } myGettersInitialized = true; diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/impl/statements/expressions/GrReferenceExpressionImpl.java b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/impl/statements/expressions/GrReferenceExpressionImpl.java index 8f19b418b1f1..36b6584c70ed 100644 --- a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/impl/statements/expressions/GrReferenceExpressionImpl.java +++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/impl/statements/expressions/GrReferenceExpressionImpl.java @@ -443,13 +443,12 @@ public class GrReferenceExpressionImpl extends GrReferenceElementImpl implements final boolean isLValue = PsiUtil.isLValue(refExpr); String[] names; names = isLValue ? GroovyPropertyUtils.suggestSettersName(name) : GroovyPropertyUtils.suggestGettersName(name); - List list = new ArrayList(); for (String getterName : names) { AccessorResolverProcessor accessorResolver = new AccessorResolverProcessor(getterName, refExpr, !isLValue); resolveImpl(refExpr, accessorResolver); - list.addAll(Arrays.asList(accessorResolver.getCandidates())); + final GroovyResolveResult[] candidates = accessorResolver.getCandidates(); + if (candidates.length > 0) return candidates; } - if (list.size() > 0) return list.toArray(new GroovyResolveResult[list.size()]); if (fieldCandidates.length > 0) return fieldCandidates; EnumSet kinds = refExpr.getParent() instanceof GrReferenceExpression diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/impl/statements/params/GrParameterImpl.java b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/impl/statements/params/GrParameterImpl.java index 5b3e5fa90e5f..938626ec586a 100644 --- a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/impl/statements/params/GrParameterImpl.java +++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/impl/statements/params/GrParameterImpl.java @@ -200,7 +200,7 @@ public class GrParameterImpl extends GrVariableImpl implements GrParameter { private static PsiType getEntryForMap(PsiType map, PsiElementFactory factory, PsiElement context) { PsiType key = PsiUtil.substituteTypeParameter(map, CommonClassNames.JAVA_UTIL_MAP, 0, true); PsiType value = PsiUtil.substituteTypeParameter(map, CommonClassNames.JAVA_UTIL_MAP, 1, true); - if (key != null && value != null) { + if (key != null && key != PsiType.NULL && value != null && value != PsiType.NULL) { return factory.createTypeFromText("java.util.Map.Entry<" + key.getCanonicalText() + ", " + value.getCanonicalText() + ">", context); } return null; diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/impl/statements/typedef/members/GrMethodBaseImpl.java b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/impl/statements/typedef/members/GrMethodBaseImpl.java index 15384571999d..64ffa0d45b51 100644 --- a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/impl/statements/typedef/members/GrMethodBaseImpl.java +++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/impl/statements/typedef/members/GrMethodBaseImpl.java @@ -154,7 +154,7 @@ public abstract class GrMethodBaseImpl extends GroovyBaseEl private static final Function ourTypesCalculator = new NullableFunction() { public PsiType fun(GrMethod method) { PsiType nominal = getNominalType(method); - if (nominal != null && nominal.equals(PsiType.VOID)) return nominal; + if (PsiType.VOID.equals(nominal)) return nominal; PsiType inferred = getInferredType(method); if (nominal == null) { if (inferred == null) { diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/impl/types/GrClosureSignatureImpl.java b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/impl/types/GrClosureSignatureImpl.java index d3e06d35bcec..9f1e46289497 100644 --- a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/impl/types/GrClosureSignatureImpl.java +++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/impl/types/GrClosureSignatureImpl.java @@ -40,7 +40,7 @@ public class GrClosureSignatureImpl implements GrClosureSignature { myParameters[i] = new GrClosureParameterImpl(parameters[i], substitutor); } if (length > 0) { - myIsVarargs = parameters[length - 1].isVarArgs() || myParameters[length - 1].getType() instanceof PsiArrayType; + myIsVarargs = /*parameters[length - 1].isVarArgs() ||*/ myParameters[length - 1].getType() instanceof PsiArrayType; } else { myIsVarargs = false; diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/util/GroovyPropertyUtils.java b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/util/GroovyPropertyUtils.java index e8ede41cdbda..2cf7df077a28 100644 --- a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/util/GroovyPropertyUtils.java +++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/util/GroovyPropertyUtils.java @@ -24,6 +24,7 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.plugins.groovy.lang.psi.api.statements.GrField; import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.members.GrAccessorMethod; +import org.jetbrains.plugins.groovy.lang.psi.impl.statements.expressions.TypesUtil; import java.beans.Introspector; @@ -31,6 +32,9 @@ import java.beans.Introspector; * @author ilyas */ public class GroovyPropertyUtils { + private static final String IS_PREFIX = "is"; + private static final String GET_PREFIX = "get"; + private GroovyPropertyUtils() { } @@ -112,6 +116,11 @@ public class GroovyPropertyUtils { if (method == null || method.isConstructor()) return false; if (method.getParameterList().getParametersCount() != 0) return false; if (!isGetterName(method.getName())) return false; + if (method.getName().startsWith(IS_PREFIX) && + !CommonClassNames.JAVA_LANG_BOOLEAN + .equals(TypesUtil.boxPrimitiveType(method.getReturnType(), method.getManager(), method.getResolveScope()).getCanonicalText())) { + return false; + } return (propertyName == null || propertyName.equals(getPropertyNameByGetter(method))) && method.getReturnType() != PsiType.VOID; } @@ -132,10 +141,13 @@ public class GroovyPropertyUtils { } @NonNls String methodName = getterMethod.getName(); - if (methodName.startsWith("get") && methodName.length() > 3) { + if (methodName.startsWith(GET_PREFIX) && methodName.length() > 3) { return decapitalize(methodName.substring(3)); } - else if (methodName.startsWith("is") && methodName.length() > 2 && PsiType.BOOLEAN.equals(getterMethod.getReturnType())) { + else if (methodName.startsWith(IS_PREFIX) && + methodName.length() > 2 && + CommonClassNames.JAVA_LANG_BOOLEAN.equals(TypesUtil.boxPrimitiveType(getterMethod.getReturnType(), getterMethod.getManager(), + getterMethod.getResolveScope()).getCanonicalText())) { return decapitalize(methodName.substring(2)); } return methodName; @@ -163,13 +175,18 @@ public class GroovyPropertyUtils { } public static boolean isGetterName(@NotNull String name) { - if (name.startsWith("get") && name.length() > 3 && isUpperCase(name.charAt(3))) return true; - if (name.startsWith("is") && name.length() > 2 && isUpperCase(name.charAt(2))) return true; + if (name.startsWith(GET_PREFIX) && name.length() > 3 && isUpperCase(name.charAt(3))) return true; + if (name.startsWith(IS_PREFIX) && name.length() > 2 && isUpperCase(name.charAt(2))) return true; return false; } + /** + * Returns getter names in priority order + * @param name property name + * @return getter names + */ public static String[] suggestGettersName(@NotNull String name) { - return new String[]{"get"+capitalize(name), "is"+capitalize(name)}; + return new String[]{IS_PREFIX + capitalize(name), GET_PREFIX + capitalize(name)}; } public static String[] suggestSettersName(@NotNull String name) { diff --git a/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/GroovyHighlightingTest.java b/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/GroovyHighlightingTest.java index eb385138a5f9..f38280c2924c 100644 --- a/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/GroovyHighlightingTest.java +++ b/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/GroovyHighlightingTest.java @@ -205,6 +205,7 @@ public class GroovyHighlightingTest extends LightCodeInsightFixtureTestCase { public void testStringAndGStringUpperBound() throws Exception {doTest();} public void testWithMethod() throws Exception {doTest();} + public void testByteArrayArgument() throws Exception {doTest();} public void testForLoopWithNestedEndlessLoop() throws Exception {doTest(new UnassignedVariableAccessInspection());} public void testIfIncrementElseReturn() throws Exception {doTest(new UnusedDefInspection()); } diff --git a/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/resolve/ResolvePropertyTest.groovy b/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/resolve/ResolvePropertyTest.groovy index a9a472839db6..eabeba791a07 100644 --- a/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/resolve/ResolvePropertyTest.groovy +++ b/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/resolve/ResolvePropertyTest.groovy @@ -326,4 +326,27 @@ print bar PsiElement resolved = ref.resolve(); assertTrue(resolved instanceof GrReferenceExpression); } + + public void testBooleanProperty() throws Exception { + myFixture.configureByText("Abc.groovy", """class A{ + boolean getFoo(){return true} + boolean isFoo(){return false} + } + print new A().foo"""); + def ref = findReference() + def resolved = ref.resolve(); + assertNotNull resolved + assertEquals resolved.getName(), "isFoo" + } + + public void testExplicitBooleanProperty() throws Exception { + myFixture.configureByText("Abc.groovy", """class A{ + boolean foo + } + print new A().foo"""); + def ref = findReference() + def resolved = ref.resolve(); + assertNotNull resolved + assertEquals resolved.getName(), "isFoo" + } } \ No newline at end of file diff --git a/plugins/groovy/testdata/highlighting/ByteArrayArgument.groovy b/plugins/groovy/testdata/highlighting/ByteArrayArgument.groovy new file mode 100644 index 000000000000..e20f290d0fc9 --- /dev/null +++ b/plugins/groovy/testdata/highlighting/ByteArrayArgument.groovy @@ -0,0 +1,5 @@ +def foo(byte[] b) {} +def bar(byte[] b) { + foo(b) + foo(239) +} \ No newline at end of file diff --git a/plugins/junit_rt/src/com/intellij/junit4/JUnit4TestRunnerUtil.java b/plugins/junit_rt/src/com/intellij/junit4/JUnit4TestRunnerUtil.java index b75212516c5f..cb94f048ac6f 100644 --- a/plugins/junit_rt/src/com/intellij/junit4/JUnit4TestRunnerUtil.java +++ b/plugins/junit_rt/src/com/intellij/junit4/JUnit4TestRunnerUtil.java @@ -25,7 +25,6 @@ import org.junit.runner.manipulation.Filter; import org.junit.runner.notification.RunNotifier; import org.junit.runners.BlockJUnit4ClassRunner; import org.junit.runners.model.FrameworkMethod; -import org.junit.runners.model.InitializationError; import java.io.BufferedReader; import java.io.FileReader; @@ -114,18 +113,24 @@ public class JUnit4TestRunnerUtil { else { int index = suiteClassName.indexOf(','); if (index != -1) { - final Class clazz = loadTestClass(suiteClassName.substring(0, index)); - final Request classRequest = new ClassRequest(clazz){ - public Runner getRunner() { - try { - return new IgnoreIgnoredTestJUnit4ClassRunner(clazz); + try { + Class.forName("org.junit.runners.model.InitializationError"); + final Class clazz = loadTestClass(suiteClassName.substring(0, index)); + final Request classRequest = new ClassRequest(clazz) { + public Runner getRunner() { + try { + return new IgnoreIgnoredTestJUnit4ClassRunner(clazz); + } + catch (Exception ignored) { + } + return super.getRunner(); } - catch (InitializationError ignored) { - } - return super.getRunner(); - } - }; - return classRequest.filterWith(Description.createTestDescription(clazz, suiteClassName.substring(index + 1))); + }; + return classRequest.filterWith(Description.createTestDescription(clazz, suiteClassName.substring(index + 1))); + } + catch (ClassNotFoundException e) { + return Request.method(loadTestClass(suiteClassName.substring(0, index)), suiteClassName.substring(index + 1)); + } } appendTestClass(result, suiteClassName); } @@ -174,7 +179,7 @@ public class JUnit4TestRunnerUtil { private static class IgnoreIgnoredTestJUnit4ClassRunner extends BlockJUnit4ClassRunner { - public IgnoreIgnoredTestJUnit4ClassRunner(Class clazz) throws InitializationError { + public IgnoreIgnoredTestJUnit4ClassRunner(Class clazz) throws Exception { super(clazz); } diff --git a/plugins/maven/src/main/java/org/jetbrains/idea/maven/dom/MavenDomUtil.java b/plugins/maven/src/main/java/org/jetbrains/idea/maven/dom/MavenDomUtil.java index fa84377f670e..a9ebd1a1e336 100644 --- a/plugins/maven/src/main/java/org/jetbrains/idea/maven/dom/MavenDomUtil.java +++ b/plugins/maven/src/main/java/org/jetbrains/idea/maven/dom/MavenDomUtil.java @@ -41,6 +41,7 @@ import org.jetbrains.annotations.Nullable; import org.jetbrains.idea.maven.dom.model.*; import org.jetbrains.idea.maven.project.*; import org.jetbrains.idea.maven.utils.MavenConstants; +import org.jetbrains.idea.maven.utils.MavenLog; import org.jetbrains.idea.maven.vfs.MavenPropertiesVirtualFileSystem; import java.io.File; @@ -87,7 +88,11 @@ public class MavenDomUtil { } public static String calcRelativePath(VirtualFile parent, VirtualFile child) { - String result = FileUtil.getRelativePath(new File(parent.getPath()), new File(child.getPath())); + String result = FileUtil.getRelativePath(parent.getPath(), child.getPath(), '/'); + if (result == null) { + MavenLog.LOG.warn("cannot calculate relative path for\nparent: " + parent + "\nchild: " + child); + result = child.getPath(); + } return FileUtil.toSystemIndependentName(result); } diff --git a/plugins/maven/src/main/java/org/jetbrains/idea/maven/dom/generate/GenerateOverridingDependencyAction.java b/plugins/maven/src/main/java/org/jetbrains/idea/maven/dom/generate/GenerateAddManagedDependencyAction.java similarity index 95% rename from plugins/maven/src/main/java/org/jetbrains/idea/maven/dom/generate/GenerateOverridingDependencyAction.java rename to plugins/maven/src/main/java/org/jetbrains/idea/maven/dom/generate/GenerateAddManagedDependencyAction.java index 5e9ebb68ed12..cb316659e318 100644 --- a/plugins/maven/src/main/java/org/jetbrains/idea/maven/dom/generate/GenerateOverridingDependencyAction.java +++ b/plugins/maven/src/main/java/org/jetbrains/idea/maven/dom/generate/GenerateAddManagedDependencyAction.java @@ -33,8 +33,8 @@ import org.jetbrains.idea.maven.utils.MavenIcons; import java.util.List; import java.util.Set; -public class GenerateOverridingDependencyAction extends GenerateDomElementAction { - public GenerateOverridingDependencyAction() { +public class GenerateAddManagedDependencyAction extends GenerateDomElementAction { + public GenerateAddManagedDependencyAction() { super(new MavenOverridingDependencyGenerateProvider(), MavenIcons.DEPENDENCY_ICON); } @@ -45,7 +45,7 @@ public class GenerateOverridingDependencyAction extends GenerateDomElementAction private static class MavenOverridingDependencyGenerateProvider extends MavenGenerateProvider { public MavenOverridingDependencyGenerateProvider() { - super(MavenDomBundle.message("override.dependency"), MavenDomDependency.class); + super(MavenDomBundle.message("generate.managed.dependency"), MavenDomDependency.class); } @Override diff --git a/plugins/maven/src/main/java/org/jetbrains/idea/maven/dom/references/MavenPropertyPsiReference.java b/plugins/maven/src/main/java/org/jetbrains/idea/maven/dom/references/MavenPropertyPsiReference.java index 37ebc09c5559..9921ceb0cc14 100644 --- a/plugins/maven/src/main/java/org/jetbrains/idea/maven/dom/references/MavenPropertyPsiReference.java +++ b/plugins/maven/src/main/java/org/jetbrains/idea/maven/dom/references/MavenPropertyPsiReference.java @@ -50,10 +50,16 @@ import org.jetbrains.idea.maven.vfs.MavenPropertiesVirtualFileSystem; import javax.swing.*; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.Set; public class MavenPropertyPsiReference extends MavenPsiReference { + private static final Set BASEDIR_PROPS = + new THashSet(Arrays.asList("basedir", "project.basedir", "pom.basedir", "baseUri", "project.baseUri", "pom.baseUri")); + + private static final String TIMESTAMP_PROP = "maven.build.timestamp"; + protected final MavenDomProjectModel myProjectDom; protected final MavenProject myMavenProject; private final boolean mySoft; @@ -95,10 +101,14 @@ public class MavenPropertyPsiReference extends MavenPsiReference { return resolveEnvPropety(); } - if (myText.equals("basedir") || myText.equals("project.basedir") || myText.equals("pom.basedir")) { + if (BASEDIR_PROPS.contains(myText)) { return resolveBasedir(); } + if (myText.equals(TIMESTAMP_PROP)) { + return myElement; + } + PsiElement result = resolveSystemPropety(); if (result != null) return result; @@ -204,7 +214,7 @@ public class MavenPropertyPsiReference extends MavenPsiReference { } protected void collectVariants(List result) { - collectBasedirVariants(result); + collectStandardVariants(result); collectProjectSchemaVariants(result); collectSettingsXmlSchemaVariants(result); collectPropertiesVariants(result); @@ -212,11 +222,12 @@ public class MavenPropertyPsiReference extends MavenPsiReference { collectSystemEnvProperties(MavenPropertiesVirtualFileSystem.ENV_PROPERTIES_FILE, "env.", result); } - private void collectBasedirVariants(List result) { + private void collectStandardVariants(List result) { PsiDirectory basedir = getBaseDir(); - result.add(createLookupElement(basedir, "basedir", MavenIcons.MAVEN_ICON)); - result.add(createLookupElement(basedir, "pom.basedir", MavenIcons.MAVEN_ICON)); - result.add(createLookupElement(basedir, "project.basedir", MavenIcons.MAVEN_ICON)); + for (String each : BASEDIR_PROPS) { + result.add(createLookupElement(basedir, each, MavenIcons.MAVEN_ICON)); + } + result.add(createLookupElement(myElement, TIMESTAMP_PROP, MavenIcons.MAVEN_ICON)); } private void collectProjectSchemaVariants(final List result) { @@ -245,7 +256,7 @@ public class MavenPropertyPsiReference extends MavenPsiReference { public LookupElement fun(XmlTag xmlTag) { return createLookupElement(xmlTag, xmlTag.getName()); } - })) ; + })); } private void collectSystemEnvProperties(String propertiesFileName, String prefix, List result) { @@ -276,7 +287,7 @@ public class MavenPropertyPsiReference extends MavenPsiReference { VirtualFile file = MavenSchemaProvider.getSchemaFile(schema); PsiFile psiFile = PsiManager.getInstance(myProject).findFile(file); if (!(psiFile instanceof XmlFile)) return null; - + XmlFile xmlFile = (XmlFile)psiFile; XmlDocument document = xmlFile.getDocument(); XmlNSDescriptor desc = (XmlNSDescriptor)document.getMetaData(); @@ -285,9 +296,9 @@ public class MavenPropertyPsiReference extends MavenPsiReference { } private static T doProcessSchema(XmlElementDescriptor[] descriptors, - String prefix, - SchemaProcessor processor, - Set recursionGuard) { + String prefix, + SchemaProcessor processor, + Set recursionGuard) { for (XmlElementDescriptor each : descriptors) { if (isCollection(each)) continue; diff --git a/plugins/maven/src/main/java/org/jetbrains/idea/maven/navigator/MavenProjectsNavigatorPanel.java b/plugins/maven/src/main/java/org/jetbrains/idea/maven/navigator/MavenProjectsNavigatorPanel.java index 8266dba43e00..ea966e527f33 100644 --- a/plugins/maven/src/main/java/org/jetbrains/idea/maven/navigator/MavenProjectsNavigatorPanel.java +++ b/plugins/maven/src/main/java/org/jetbrains/idea/maven/navigator/MavenProjectsNavigatorPanel.java @@ -56,13 +56,12 @@ public class MavenProjectsNavigatorPanel extends SimpleToolWindowPanel implement myTree = tree; final ActionManager actionManager = ActionManager.getInstance(); - ActionToolbar actionToolbar = actionManager.createActionToolbar("New Maven Toolbar", - (ActionGroup)actionManager.getAction("Maven.NavigatorToolbar"), + ActionToolbar actionToolbar = actionManager.createActionToolbar("Maven Navigator Toolbar", + (DefaultActionGroup)actionManager.getAction("Maven.NavigatorActionsToolbar"), true); - actionToolbar.setTargetComponent(tree); - JComponent toolbar = actionToolbar.getComponent(); - setToolbar(toolbar); + actionToolbar.setTargetComponent(tree); + setToolbar(actionToolbar.getComponent()); setContent(new JScrollPane(myTree)); myTree.addMouseListener(new PopupHandler() { @@ -114,18 +113,10 @@ public class MavenProjectsNavigatorPanel extends SimpleToolWindowPanel implement return null; } - private List extractProjectNodes() { - List result = new ArrayList(); - for (MavenProjectsStructure.ProjectNode each : getSelectedProjectNodes()) { - result.add(each.getMavenProject()); - } - return result.isEmpty() ? null : result; - } - private VirtualFile extractVirtualFile() { for (MavenProjectsStructure.MavenSimpleNode each : getSelectedNodes(MavenProjectsStructure.MavenSimpleNode.class)) { VirtualFile file = each.getVirtualFile(); - if (file != null) return file; + if (file != null && file.isValid()) return file; } final MavenProjectsStructure.ProjectNode projectNode = getContextProjectNode(); @@ -139,7 +130,7 @@ public class MavenProjectsNavigatorPanel extends SimpleToolWindowPanel implement final List files = new ArrayList(); for (MavenProjectsStructure.MavenSimpleNode each : getSelectedNodes(MavenProjectsStructure.MavenSimpleNode.class)) { VirtualFile file = each.getVirtualFile(); - if (file != null) files.add(file); + if (file != null && file.isValid()) files.add(file); } return files.isEmpty() ? null : VfsUtil.toVirtualFileArray(files); } diff --git a/plugins/maven/src/main/java/org/jetbrains/idea/maven/project/MavenProjectsManager.java b/plugins/maven/src/main/java/org/jetbrains/idea/maven/project/MavenProjectsManager.java index 22b401b455a1..f73ccb86389c 100644 --- a/plugins/maven/src/main/java/org/jetbrains/idea/maven/project/MavenProjectsManager.java +++ b/plugins/maven/src/main/java/org/jetbrains/idea/maven/project/MavenProjectsManager.java @@ -170,23 +170,25 @@ public class MavenProjectsManager extends SimpleProjectComponent } private void doInit(final boolean isNew) { - if (isInitialized.getAndSet(true)) return; + synchronized (isInitialized) { + if (isInitialized.getAndSet(true)) return; - initProjectsTree(!isNew); + initProjectsTree(!isNew); - initWorkers(); - listenForSettingsChanges(); - listenForProjectsTreeChanges(); + initWorkers(); + listenForSettingsChanges(); + listenForProjectsTreeChanges(); - MavenUtil.runWhenInitialized(myProject, new DumbAwareRunnable() { - public void run() { - if (!isUnitTestMode()) { - fireActivated(); - listenForExternalChanges(); + MavenUtil.runWhenInitialized(myProject, new DumbAwareRunnable() { + public void run() { + if (!isUnitTestMode()) { + fireActivated(); + listenForExternalChanges(); + } + scheduleUpdateAllProjects(isNew); } - scheduleUpdateAllProjects(isNew); - } - }); + }); + } } private void initProjectsTree(boolean tryToLoadExisting) { @@ -380,24 +382,26 @@ public class MavenProjectsManager extends SimpleProjectComponent @Override public void projectClosed() { - if (!isInitialized.getAndSet(false)) return; + synchronized (isInitialized) { + if (!isInitialized.getAndSet(false)) return; - Disposer.dispose(mySchedulesQueue); - Disposer.dispose(myImportingQueue); + Disposer.dispose(mySchedulesQueue); + Disposer.dispose(myImportingQueue); - myWatcher.stop(); + myWatcher.stop(); - myReadingProcessor.stop(); - myResolvingProcessor.stop(); - myPluginsResolvingProcessor.stop(); - myFoldersResolvingProcessor.stop(); - myArtifactsDownloadingProcessor.stop(); - myPostProcessor.stop(); + myReadingProcessor.stop(); + myResolvingProcessor.stop(); + myPluginsResolvingProcessor.stop(); + myFoldersResolvingProcessor.stop(); + myArtifactsDownloadingProcessor.stop(); + myPostProcessor.stop(); - myEmbeddersManager.release(); + myEmbeddersManager.release(); - if (isUnitTestMode()) { - FileUtil.delete(getProjectsTreesDir()); + if (isUnitTestMode()) { + FileUtil.delete(getProjectsTreesDir()); + } } } diff --git a/plugins/maven/src/main/java/org/jetbrains/idea/maven/project/MavenProjectsTree.java b/plugins/maven/src/main/java/org/jetbrains/idea/maven/project/MavenProjectsTree.java index 50f9d6a4258b..6c63d266c203 100644 --- a/plugins/maven/src/main/java/org/jetbrains/idea/maven/project/MavenProjectsTree.java +++ b/plugins/maven/src/main/java/org/jetbrains/idea/maven/project/MavenProjectsTree.java @@ -669,7 +669,7 @@ public class MavenProjectsTree { } private long getFileTimestamp(VirtualFile file) { - if (file == null) return -1; + if (file == null || !file.isValid()) return -1; return file.getTimeStamp(); } diff --git a/plugins/maven/src/main/java/org/jetbrains/idea/maven/utils/MavenGotoSettingsFileContibutor.java b/plugins/maven/src/main/java/org/jetbrains/idea/maven/utils/MavenGotoSettingsFileContibutor.java index 8f85811c785e..6ae9412af293 100644 --- a/plugins/maven/src/main/java/org/jetbrains/idea/maven/utils/MavenGotoSettingsFileContibutor.java +++ b/plugins/maven/src/main/java/org/jetbrains/idea/maven/utils/MavenGotoSettingsFileContibutor.java @@ -32,6 +32,8 @@ import java.util.Set; public class MavenGotoSettingsFileContibutor implements ChooseByNameContributor, DumbAware { public String[] getNames(Project project, boolean includeNonProjectItems) { + if (!includeNonProjectItems) return ArrayUtil.EMPTY_STRING_ARRAY; + Set result = new THashSet(); for (VirtualFile each : getSettingsFiles(project)) { result.add(each.getName()); @@ -40,6 +42,8 @@ public class MavenGotoSettingsFileContibutor implements ChooseByNameContributor, } public NavigationItem[] getItemsByName(String name, String pattern, Project project, boolean includeNonProjectItems) { + if (!includeNonProjectItems) return NavigationItem.EMPTY_NAVIGATION_ITEM_ARRAY; + List result = new ArrayList(); for (VirtualFile each : getSettingsFiles(project)) { if (each.getName().equals(name)) { diff --git a/plugins/maven/src/main/java/org/jetbrains/idea/maven/utils/MavenMergingUpdateQueue.java b/plugins/maven/src/main/java/org/jetbrains/idea/maven/utils/MavenMergingUpdateQueue.java index 5bef4d9c4bed..e553c7c2abfb 100644 --- a/plugins/maven/src/main/java/org/jetbrains/idea/maven/utils/MavenMergingUpdateQueue.java +++ b/plugins/maven/src/main/java/org/jetbrains/idea/maven/utils/MavenMergingUpdateQueue.java @@ -86,16 +86,18 @@ public class MavenMergingUpdateQueue extends MergingUpdateQueue { } }, MavenMergingUpdateQueue.this); - if (CommandProcessor.getInstance().getCurrentCommand() != null) { - suspend(); - } - ProjectRootManager.getInstance(project).addModuleRootListener(new ModuleRootListener() { + boolean beforeCalled = false; + public void beforeRootsChange(ModuleRootEvent event) { suspend(); + beforeCalled = true; } public void rootsChanged(ModuleRootEvent event) { + if (!beforeCalled) return; + beforeCalled = false; + resume(); MavenMergingUpdateQueue.this.restartTimer(); } @@ -109,7 +111,6 @@ public class MavenMergingUpdateQueue extends MergingUpdateQueue { protected void run(Result result) throws Throwable { MessageBusConnection connection = project.getMessageBus().connect(MavenMergingUpdateQueue.this); connection.subscribe(DumbService.DUMB_MODE, new DumbService.DumbModeListener() { - public void enteredDumbMode() { suspend(); } @@ -118,6 +119,7 @@ public class MavenMergingUpdateQueue extends MergingUpdateQueue { resume(); } }); + if (DumbService.getInstance(project).isDumb()) { suspend(); } diff --git a/plugins/maven/src/main/resources/DomBundle.properties b/plugins/maven/src/main/resources/DomBundle.properties index c2375fb9e889..3c266a92241c 100644 --- a/plugins/maven/src/main/resources/DomBundle.properties +++ b/plugins/maven/src/main/resources/DomBundle.properties @@ -3,7 +3,7 @@ inspection.name=Maven Model Inspection fix.create.module=Create Module fix.create.module.with.parent=Create Module with Parent fix.update.indices=Update Maven Indices -fix.parent.path=Fix relative path +fix.parent.path=Fix Relative Path fix.open.file=Open ''{0}'' intention.choose.file=Choose File @@ -17,15 +17,15 @@ generate.dependency.group=Dependency (group) generate.plugin.artifact=Plugin (artifact) generate.plugin.group=Plugin (group) -overriden.dependency.title=Navigate to managing dependency -overriding.dependency.title=Navigate to dependency usages -navigate.parent.dependency.title=Choose dependency to navigate +overriden.dependency.title=Navigate to Managing Dependency +overriding.dependency.title=Navigate to Dependency Usages +navigate.parent.dependency.title=Choose Dependency to Navigate to -parent.pom.title=Navigate to parent -children.poms.title=Navigate to children projects -navigate.children.poms.title=Choose project -dependencies.chooser.title=Choose dependency +parent.pom.title=Navigate to Parent +children.poms.title=Navigate to Children Projects +navigate.children.poms.title=Choose Project +dependencies.chooser.title=Choose Dependency all.dependencies=All Dependencies chooser.show.project.files=Project Files -override.dependency=Override Parent Dependency generate.dependency=Dependency +generate.managed.dependency=Add Managed Dependency diff --git a/plugins/maven/src/main/resources/META-INF/plugin.xml b/plugins/maven/src/main/resources/META-INF/plugin.xml index 522d5b8e485c..581a7f640910 100644 --- a/plugins/maven/src/main/resources/META-INF/plugin.xml +++ b/plugins/maven/src/main/resources/META-INF/plugin.xml @@ -196,7 +196,6 @@ text="Remove Projects" description="Remove selected Maven projects from the project tree" icon="/general/remove.png"> - - - - - - - - - - - + @@ -308,13 +283,28 @@ - - - - + + + + @@ -355,7 +345,7 @@ - + diff --git a/plugins/maven/src/test/java/org/jetbrains/idea/maven/dom/MavenParentCompletionAndResolutionTest.java b/plugins/maven/src/test/java/org/jetbrains/idea/maven/dom/MavenParentCompletionAndResolutionTest.java index 57f87ecdd220..ed546a5843d0 100644 --- a/plugins/maven/src/test/java/org/jetbrains/idea/maven/dom/MavenParentCompletionAndResolutionTest.java +++ b/plugins/maven/src/test/java/org/jetbrains/idea/maven/dom/MavenParentCompletionAndResolutionTest.java @@ -353,7 +353,7 @@ public class MavenParentCompletionAndResolutionTest extends MavenDomWithIndicesT " xxx" + ""); - IntentionAction i = getIntentionAtCaret("Fix relative path"); + IntentionAction i = getIntentionAtCaret("Fix Relative Path"); assertNotNull(i); myFixture.launchAction(i); diff --git a/plugins/maven/src/test/java/org/jetbrains/idea/maven/dom/MavenPropertyCompletionAndResolutionTest.java b/plugins/maven/src/test/java/org/jetbrains/idea/maven/dom/MavenPropertyCompletionAndResolutionTest.java index edaed390f0c9..fdb8a71f9153 100644 --- a/plugins/maven/src/test/java/org/jetbrains/idea/maven/dom/MavenPropertyCompletionAndResolutionTest.java +++ b/plugins/maven/src/test/java/org/jetbrains/idea/maven/dom/MavenPropertyCompletionAndResolutionTest.java @@ -91,6 +91,24 @@ public class MavenPropertyCompletionAndResolutionTest extends MavenDomTestCase { "${pom.basedir}"); assertResolved(myProjectPom, baseDir); + + createProjectPom("testproject" + + "1" + + + "${baseUri}"); + + assertResolved(myProjectPom, baseDir); + } + + public void testBuiltInTimestampProperty() throws Exception { + createProjectPom("testproject" + + "1" + + + "${maven.build.timestamp}"); + + assertResolved(myProjectPom, findTag("project.name")); } public void testResolutionWithSeveralProperties() throws Exception { @@ -748,7 +766,8 @@ public class MavenPropertyCompletionAndResolutionTest extends MavenDomTestCase { "parentPomProfilesProp", "parentProfilesXmlProp"); assertContain(variants, "artifactId", "project.artifactId", "pom.artifactId"); - assertContain(variants, "basedir", "project.basedir", "pom.basedir"); + assertContain(variants, "basedir", "project.basedir", "pom.basedir", "baseUri", "project.baseUri", "pom.basedir"); + assertContain(variants, "maven.build.timestamp"); assertContain(variants, "settingsXmlProp"); assertContain(variants, "settings.localRepository"); assertContain(variants, "user.home", "env." + getEnvVar()); diff --git a/plugins/properties/src/com/intellij/lang/properties/UnusedPropertyInspection.java b/plugins/properties/src/com/intellij/lang/properties/UnusedPropertyInspection.java index 63720e08e1ec..c04f20f2333e 100644 --- a/plugins/properties/src/com/intellij/lang/properties/UnusedPropertyInspection.java +++ b/plugins/properties/src/com/intellij/lang/properties/UnusedPropertyInspection.java @@ -29,8 +29,8 @@ import com.intellij.openapi.progress.ProgressManager; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; import com.intellij.psi.PsiReference; -import com.intellij.psi.impl.search.PsiSearchHelperImpl; import com.intellij.psi.search.GlobalSearchScope; +import com.intellij.psi.search.PsiSearchHelper; import com.intellij.psi.search.searches.ReferencesSearch; import com.intellij.util.Processor; import com.intellij.util.SmartList; @@ -68,23 +68,26 @@ public class UnusedPropertyInspection extends PropertySuppressableInspectionBase original.setText(PropertiesBundle.message("searching.for.property.key.progress.text", property.getUnescapedKey())); } - PsiSearchHelperImpl.Result cheapEnough = ((PsiSearchHelperImpl)file.getManager().getSearchHelper()).isItCheapEnoughToSearch(file, property.getName(), searchScope); - if (cheapEnough == PsiSearchHelperImpl.Result.TOO_MANY_OCCURRENCES) return true; + String name = property.getName(); + if (name == null) return true; + PsiSearchHelper.SearchCostResult cheapEnough = file.getManager().getSearchHelper().isCheapEnoughToSearch(name, searchScope, file); + if (cheapEnough == PsiSearchHelper.SearchCostResult.TOO_MANY_OCCURRENCES) return true; - final PsiReference usage = cheapEnough == PsiSearchHelperImpl.Result.ZERO_OCCURRENCES ? null : + final PsiReference usage = cheapEnough == PsiSearchHelper.SearchCostResult.ZERO_OCCURRENCES ? null : ReferencesSearch.search(property, searchScope, false).findFirst(); - if (usage == null) { - final ASTNode propertyNode = property.getNode(); - assert propertyNode != null; + if (usage != null) { + return true; + } + final ASTNode propertyNode = property.getNode(); + assert propertyNode != null; - ASTNode[] nodes = propertyNode.getChildren(null); - PsiElement key = nodes.length == 0 ? property : nodes[0].getPsi(); - String description = PropertiesBundle.message("unused.property.problem.descriptor.name"); - ProblemDescriptor descriptor = manager.createProblemDescriptor(key, description, RemovePropertyLocalFix.INSTANCE, ProblemHighlightType.LIKE_UNUSED_SYMBOL, - isOnTheFly); - synchronized (descriptors) { - descriptors.add(descriptor); - } + ASTNode[] nodes = propertyNode.getChildren(null); + PsiElement key = nodes.length == 0 ? property : nodes[0].getPsi(); + String description = PropertiesBundle.message("unused.property.problem.descriptor.name"); + ProblemDescriptor descriptor = manager.createProblemDescriptor(key, description, RemovePropertyLocalFix.INSTANCE, ProblemHighlightType.LIKE_UNUSED_SYMBOL, + isOnTheFly); + synchronized (descriptors) { + descriptors.add(descriptor); } return true; diff --git a/plugins/spellchecker/lib-src/jazzy-0.5.2.src.zip b/plugins/spellchecker/lib-src/jazzy-0.5.2.src.zip deleted file mode 100644 index c41b55579596..000000000000 Binary files a/plugins/spellchecker/lib-src/jazzy-0.5.2.src.zip and /dev/null differ diff --git a/plugins/spellchecker/lib/jazzy-core.jar b/plugins/spellchecker/lib/jazzy-core.jar deleted file mode 100644 index 50d313541ea9..000000000000 Binary files a/plugins/spellchecker/lib/jazzy-core.jar and /dev/null differ diff --git a/plugins/spellchecker/lib/patricia-trie-0.1.jar b/plugins/spellchecker/lib/patricia-trie-0.1.jar deleted file mode 100644 index 5d1ea6b242f0..000000000000 Binary files a/plugins/spellchecker/lib/patricia-trie-0.1.jar and /dev/null differ diff --git a/plugins/spellchecker/spellchecker.iml b/plugins/spellchecker/spellchecker.iml index d81c1984675f..edcc0cf37fa4 100644 --- a/plugins/spellchecker/spellchecker.iml +++ b/plugins/spellchecker/spellchecker.iml @@ -14,6 +14,7 @@ + diff --git a/plugins/spellchecker/src/com/intellij/spellchecker/BaseSuggestionProvider.java b/plugins/spellchecker/src/com/intellij/spellchecker/BaseSuggestionProvider.java index 20657aa6be71..5e8a549406b8 100644 --- a/plugins/spellchecker/src/com/intellij/spellchecker/BaseSuggestionProvider.java +++ b/plugins/spellchecker/src/com/intellij/spellchecker/BaseSuggestionProvider.java @@ -33,7 +33,7 @@ public class BaseSuggestionProvider implements SuggestionProvider { @NotNull public List getSuggestions(@NotNull String text) { - + String[] words = NameUtil.nameToWords(text); int index = 0; @@ -54,7 +54,7 @@ public class BaseSuggestionProvider implements SuggestionProvider { index = end; } - String[] all = null; + String[] all; int[] counter = new int[i]; int size = 1; for (int j = 0; j < i; j++) { diff --git a/plugins/spellchecker/src/com/intellij/spellchecker/BundledDictionaryProvider.java b/plugins/spellchecker/src/com/intellij/spellchecker/BundledDictionaryProvider.java index 236ae4a39c9b..3c6ed60b35e2 100644 --- a/plugins/spellchecker/src/com/intellij/spellchecker/BundledDictionaryProvider.java +++ b/plugins/spellchecker/src/com/intellij/spellchecker/BundledDictionaryProvider.java @@ -17,9 +17,6 @@ package com.intellij.spellchecker; import com.intellij.openapi.extensions.ExtensionPointName; -/** - * @author oleg - */ public interface BundledDictionaryProvider { ExtensionPointName EP_NAME = ExtensionPointName.create("com.intellij.spellchecker.bundledDictionaryProvider"); diff --git a/plugins/spellchecker/src/com/intellij/spellchecker/DefaultBundledDictionariesProvider.java b/plugins/spellchecker/src/com/intellij/spellchecker/DefaultBundledDictionariesProvider.java index 482f3db7bd1d..fc8e77f90ce6 100644 --- a/plugins/spellchecker/src/com/intellij/spellchecker/DefaultBundledDictionariesProvider.java +++ b/plugins/spellchecker/src/com/intellij/spellchecker/DefaultBundledDictionariesProvider.java @@ -15,9 +15,7 @@ */ package com.intellij.spellchecker; -/** - * @author oleg - */ + public class DefaultBundledDictionariesProvider implements BundledDictionaryProvider { public String[] getBundledDictionaries() { return new String[]{"english.dic", "jetbrains.dic"}; diff --git a/plugins/spellchecker/src/com/intellij/spellchecker/FileLoader.java b/plugins/spellchecker/src/com/intellij/spellchecker/FileLoader.java index 2659bb5843d5..cd57fb467dab 100644 --- a/plugins/spellchecker/src/com/intellij/spellchecker/FileLoader.java +++ b/plugins/spellchecker/src/com/intellij/spellchecker/FileLoader.java @@ -25,14 +25,21 @@ import java.io.FileInputStream; import java.io.IOException; +@SuppressWarnings({"IOResourceOpenedButNotSafelyClosed"}) public class FileLoader implements Loader { private static final Logger LOG = Logger.getInstance("#com.intellij.spellchecker.FileLoader"); - private String url; + private final String url; + private final String name; - public FileLoader(String url) { + public FileLoader(String url, String name) { this.url = url; + this.name = name; + } + + public String getName() { + return name; } public void load(@NotNull Consumer consumer) { @@ -40,7 +47,7 @@ public class FileLoader implements Loader { FileInputStream stream = null; try { stream = new FileInputStream(file); - StreamLoader loader = new StreamLoader(stream); + StreamLoader loader = new StreamLoader(stream, file.getName()); loader.load(consumer); } catch (Exception e) { diff --git a/plugins/spellchecker/src/com/intellij/spellchecker/SpellCheckerManager.java b/plugins/spellchecker/src/com/intellij/spellchecker/SpellCheckerManager.java index 630135483e52..762863df385d 100644 --- a/plugins/spellchecker/src/com/intellij/spellchecker/SpellCheckerManager.java +++ b/plugins/spellchecker/src/com/intellij/spellchecker/SpellCheckerManager.java @@ -23,7 +23,7 @@ import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.extensions.Extensions; import com.intellij.openapi.project.Project; import com.intellij.openapi.project.ProjectManager; -import com.intellij.spellchecker.dictionary.Dictionary; +import com.intellij.spellchecker.dictionary.EditableDictionary; import com.intellij.spellchecker.dictionary.Loader; import com.intellij.spellchecker.engine.SpellCheckerEngine; import com.intellij.spellchecker.engine.SpellCheckerFactory; @@ -33,7 +33,6 @@ import com.intellij.spellchecker.state.StateLoader; import com.intellij.spellchecker.util.SPFileUtil; import com.intellij.spellchecker.util.Strings; import com.intellij.util.Consumer; -import com.sun.net.ssl.internal.ssl.SSLEngineImpl; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -51,8 +50,7 @@ public class SpellCheckerManager { private SpellCheckerEngine spellChecker; - private Dictionary userDictionary; - + private EditableDictionary userDictionary; @NotNull @@ -67,26 +65,73 @@ public class SpellCheckerManager { public SpellCheckerManager(Project project, SpellCheckerSettings settings) { this.project = project; this.settings = settings; - reloadConfiguration(); + fullConfigurationReload(); + } + public void fullConfigurationReload() { + spellChecker = SpellCheckerFactory.create(); + fillEngineDictionary(); + } + + + public void updateBundledDictionaries() { + for (BundledDictionaryProvider provider : Extensions.getExtensions(BundledDictionaryProvider.EP_NAME)) { + for (String dictionary : provider.getBundledDictionaries()) { + boolean dictionaryShouldBeLoad = this.settings == null || !this.settings.getBundledDisabledDictionariesPaths().contains(dictionary); + boolean dictionaryIsLoad = spellChecker.isDictionaryLoad(dictionary); + if (dictionaryIsLoad && !dictionaryShouldBeLoad) { + spellChecker.removeDictionary(dictionary); + } + else if (!dictionaryIsLoad && dictionaryShouldBeLoad) { + final Class loaderClass = provider.getClass(); + final InputStream stream = loaderClass.getResourceAsStream(dictionary); + if (stream != null) { + spellChecker.loadDictionary(new StreamLoader(stream, dictionary)); + } + else { + LOG.warn("Couldn't load dictionary '" + dictionary + "' with loader '" + loaderClass + "'"); + } + } + } + } + if (this.settings != null && this.settings.getDictionaryFoldersPaths() != null) { + final Set disabledDictionaries = settings.getDisabledDictionariesPaths(); + for (String folder : this.settings.getDictionaryFoldersPaths()) { + SPFileUtil.processFilesRecursively(folder, new Consumer() { + public void consume(final String s) { + boolean dictionaryShouldBeLoad =!disabledDictionaries.contains(s); + boolean dictionaryIsLoad = spellChecker.isDictionaryLoad(s); + if (dictionaryIsLoad && !dictionaryShouldBeLoad) { + spellChecker.removeDictionary(s); + } + else if (!dictionaryIsLoad && dictionaryShouldBeLoad) { + spellChecker.loadDictionary(new FileLoader(s, s)); + } + + } + }); + + } + } + restartInspections(); } public Project getProject() { return project; } - public Dictionary getUserDictionary() { + public EditableDictionary getUserDictionary() { return userDictionary; } - public void reloadConfiguration() { - spellChecker = SpellCheckerFactory.create(); - fillEngineDictionary(); - } - private void fillEngineDictionary() { spellChecker.reset(); final StateLoader stateLoader = new StateLoader(project); + stateLoader.load(new Consumer() { + public void consume(String s) { + //do nothing - in this loader we don't worry about word list itself - the whole dictionary will be restored + } + }); final List loaders = new ArrayList(); // Load bundled dictionaries from corresponding jars for (BundledDictionaryProvider provider : Extensions.getExtensions(BundledDictionaryProvider.EP_NAME)) { @@ -94,10 +139,11 @@ public class SpellCheckerManager { if (this.settings == null || !this.settings.getBundledDisabledDictionariesPaths().contains(dictionary)) { final Class loaderClass = provider.getClass(); final InputStream stream = loaderClass.getResourceAsStream(dictionary); - if (stream != null){ - loaders.add(new StreamLoader(stream)); - } else { - LOG.warn("Couldn't load dictionary '" + dictionary + "' for loader '" + loaderClass + "'"); + if (stream != null) { + loaders.add(new StreamLoader(stream, dictionary)); + } + else { + LOG.warn("Couldn't load dictionary '" + dictionary + "' with loader '" + loaderClass + "'"); } } } @@ -108,7 +154,7 @@ public class SpellCheckerManager { SPFileUtil.processFilesRecursively(folder, new Consumer() { public void consume(final String s) { if (!disabledDictionaries.contains(s)) { - loaders.add(new FileLoader(s)); + loaders.add(new FileLoader(s, s)); } } }); @@ -120,6 +166,7 @@ public class SpellCheckerManager { spellChecker.loadDictionary(loader); } userDictionary = stateLoader.getDictionary(); + } @@ -131,17 +178,16 @@ public class SpellCheckerManager { final String transformed = spellChecker.getTransformation().transform(word); if (transformed != null) { userDictionary.addToDictionary(transformed); - spellChecker.addToDictionary(transformed); } } - public void update(@Nullable Collection words, SpellCheckerSettings allDictionaries) { + public void updateUserDictionary(@Nullable Collection words) { userDictionary.replaceAll(words); - reloadConfiguration(); restartInspections(); } + @NotNull public List getBundledDictionaries() { final ArrayList dictionaries = new ArrayList(); @@ -162,7 +208,6 @@ public class SpellCheckerManager { } - @NotNull protected List getRawSuggestions(@NotNull String word) { if (!spellChecker.isCorrect(word)) { diff --git a/plugins/spellchecker/src/com/intellij/spellchecker/StreamLoader.java b/plugins/spellchecker/src/com/intellij/spellchecker/StreamLoader.java index 570967259cbb..72e760aa4ff9 100644 --- a/plugins/spellchecker/src/com/intellij/spellchecker/StreamLoader.java +++ b/plugins/spellchecker/src/com/intellij/spellchecker/StreamLoader.java @@ -25,18 +25,26 @@ import java.io.*; public class StreamLoader implements Loader { private static final Logger LOG = Logger.getInstance("#com.intellij.spellchecker.StreamLoader"); + private static final String ENCODING = "UTF-8"; private InputStream stream; + private String name; - public StreamLoader(InputStream stream) { + public StreamLoader(InputStream stream, String name) { this.stream = stream; + this.name=name; } + public String getName() { + return name; + } public void load(@NotNull Consumer consumer) { DataInputStream in = new DataInputStream(stream); - BufferedReader br = new BufferedReader(new InputStreamReader(in)); + BufferedReader br = null; + try { + br = new BufferedReader(new InputStreamReader(in, ENCODING)); String strLine; while ((strLine = br.readLine()) != null) { consumer.consume(strLine); diff --git a/plugins/spellchecker/src/com/intellij/spellchecker/compress/Alphabet.java b/plugins/spellchecker/src/com/intellij/spellchecker/compress/Alphabet.java new file mode 100644 index 000000000000..65c9aaf4e43a --- /dev/null +++ b/plugins/spellchecker/src/com/intellij/spellchecker/compress/Alphabet.java @@ -0,0 +1,72 @@ +package com.intellij.spellchecker.compress; + +import org.jetbrains.annotations.NotNull; + +public final class Alphabet { + + private final Character[] letters; + private int lastIndexUsed = 0; + private int maxIndex = 1023; + + public Character getLetter(int position) { + assert position < maxIndex; + return letters[position]; + } + + /* + @param forceAdd - if set to true - letter will be added to the alphabet if not present yet + @return index of the letter or -1 if letter was not found and could not be added (due to forceAdd property value) + */ + + public int getIndex(@NotNull Character letter, boolean forceAdd) { + return getNextIndex(0, letter, forceAdd); + } + + /* + @param forceAdd - if set to true - letter will be added to the alphabet if not present yet + @return index of the letter or -1 if letter was not found and could not be added (due to forceAdd property value) + */ + public int getNextIndex(int startFrom, @NotNull Character letter, boolean forceAdd) { + for (int i = startFrom; i <= lastIndexUsed; i++) { + if (letters[i] != null && letters[i].equals(letter)) { + return i; + } + } + if (!forceAdd) { + return -1; + } + return add(letter); + } + + public int getLastIndexUsed() { + return lastIndexUsed; + } + + public int getMaxIndex() { + return maxIndex; + } + + public int add(@NotNull Character c) { + lastIndexUsed++; + letters[lastIndexUsed] = c; + return lastIndexUsed; + } + + + Alphabet() { + letters = new Character[maxIndex]; + } + + Alphabet(int maxIndex) { + this.maxIndex = maxIndex; + letters = new Character[maxIndex]; + } + + public static Alphabet create(@NotNull CharSequence alphabet) { + Alphabet result = new Alphabet(alphabet.length() + 1); + for (int i = 0; i < alphabet.length(); i++) { + result.add(alphabet.charAt(i)); + } + return result; + } +} diff --git a/plugins/spellchecker/src/com/intellij/spellchecker/compress/CompressedDictionary.java b/plugins/spellchecker/src/com/intellij/spellchecker/compress/CompressedDictionary.java new file mode 100644 index 000000000000..65fe3c1c9b22 --- /dev/null +++ b/plugins/spellchecker/src/com/intellij/spellchecker/compress/CompressedDictionary.java @@ -0,0 +1,242 @@ +/* + * Copyright 2000-2010 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.spellchecker.compress; + +import com.intellij.spellchecker.dictionary.Dictionary; +import com.intellij.spellchecker.dictionary.Loader; +import com.intellij.spellchecker.engine.Transformation; +import com.intellij.util.Consumer; +import gnu.trove.THashMap; +import org.jetbrains.annotations.NotNull; + +import java.util.*; + +public final class CompressedDictionary implements Dictionary { + + private final Alphabet alphabet; + private int wordsCount; + private byte[][] words; + private int[] lengths; + + private Encoder encoder; + private String name; + private Compressor compressor; + + private final Map> rawData = new THashMap>(); + private static final Comparator COMPARATOR = new Comparator() { + public int compare(byte[] o1, byte[] o2) { + return compareArrays(o1, o2); + } + }; + + CompressedDictionary(@NotNull Alphabet alphabet, @NotNull Compressor compressor, @NotNull Encoder encoder, @NotNull String name) { + this.alphabet = alphabet; + this.compressor = compressor; + this.encoder = encoder; + this.name = name; + } + + void addToDictionary(byte[] word) { + SortedSet set = rawData.get(word.length); + if (set == null) { + set = createSet(); + rawData.put(word.length, set); + } + set.add(word); + wordsCount++; + } + + void pack() { + if (rawData == null) { + return; + } + lengths = new int[rawData.size()]; + this.words = new byte[rawData.size()][]; + int row = 0; + for (Map.Entry> entry : rawData.entrySet()) { + final Integer l = entry.getKey(); + lengths[row] = l; + this.words[row] = new byte[entry.getValue().size() * l]; + int k = 0; + for (byte[] bytes : entry.getValue()) { + for (byte aByte : bytes) { + this.words[row][k++] = aByte; + } + } + row++; + } + rawData.clear(); + } + + private static SortedSet createSet() { + return new TreeSet(COMPARATOR); + + } + + public List getWords(Character first, int minLength, int maxLength) { + int index = alphabet.getIndex(first, false); + List result = new ArrayList(); + if (index == -1) { + return result; + } + int i = 0; + for (byte[] data : words) { + int length = lengths[i]; + for (int x = 0; x < data.length; x += length) { + byte[] toTest = new byte[length]; + System.arraycopy(data, x, toTest, 0, length); + if (toTest[1] != index || toTest[0] > maxLength || toTest[0] < minLength) { + continue; + } + UnitBitSet set = compressor.decompress(toTest); + result.add(encoder.decode(set)); + } + i++; + } + return result; + } + + public List getWords(Character first) { + return getWords(first, 0, Integer.MAX_VALUE); + } + + public String getName() { + return name; + } + + public boolean contains(String word) { + if (word == null) { + return false; + } + try { + UnitBitSet bs = encoder.encode(word, false); + byte[] compressed = compressor.compress(bs); + int index = -1; + for (int i = 0; i < lengths.length; i++) { + if (lengths[i] == compressed.length) { + index = i; + break; + } + } + if (index == -1) { + return false; + } + return contains(compressed, words[index]); + } + catch (EncodingException ignored) { + return false; + } + + } + + public boolean isEmpty() { + return wordsCount <= 0; + } + + public void traverse(Consumer action) { + throw new UnsupportedOperationException(); + } + + public Set getWords() { + throw new UnsupportedOperationException(); + } + + public int getAlphabetLength() { + return alphabet.getLastIndexUsed(); + } + + public int size() { + return wordsCount; + } + + + public String toString() { + final StringBuffer sb = new StringBuffer(); + sb.append("CompressedDictionary"); + sb.append("{wordsCount=").append(wordsCount); + sb.append(", name='").append(name).append('\''); + sb.append('}'); + return sb.toString(); + } + + public static CompressedDictionary create(@NotNull Loader loader, @NotNull final Transformation transform) { + Alphabet alphabet = new Alphabet(); + final Encoder encoder = new Encoder(alphabet); + final Compressor compressor = new Compressor(2); + final CompressedDictionary dictionary = new CompressedDictionary(alphabet, compressor, encoder, loader.getName()); + loader.load(new Consumer() { + public void consume(String s) { + String transformed = transform.transform(s); + if (transformed != null) { + UnitBitSet bs = encoder.encode(transformed, true); + byte[] compressed = compressor.compress(bs); + dictionary.addToDictionary(compressed); + } + } + }); + dictionary.pack(); + return dictionary; + } + + public static int compareArrays(byte[] array1, byte[] array2) { + if (array1.length != array2.length) { + return (array1.length < array2.length ? -1 : 1); + } + //array1.length==array2.length + if (Arrays.equals(array1, array2)) { + return 0; + } + //compare elements values + for (int i = 0; i < array1.length; i++) { + if (array1[i] < array2[i]) { + return -1; + } + else if (array1[i] > array2[i]) { + return 1; + } + } + return 0; + } + + + public static boolean contains(byte[] goal, byte[] data) { + return binarySearchNew(goal, 0, data.length / goal.length, data) >= 0; + } + + public static int binarySearchNew(byte[] goal, int fromIndex, int toIndex, byte[] data) { + int unitLength = goal.length; + int low = fromIndex; + int high = toIndex - 1; + while (low <= high) { + int mid = (low + high) >>> 1; + byte[] toTest = new byte[unitLength]; + System.arraycopy(data, mid * unitLength, toTest, 0, unitLength); + int check = compareArrays(toTest, goal); + if (check == -1) { + low = mid + 1; + } + else if (check == 1) { + high = mid - 1; + } + else { + return mid; + } + } + return -(low + 1); // key not found. + } + + +} diff --git a/plugins/spellchecker/src/com/intellij/spellchecker/compress/Compressor.java b/plugins/spellchecker/src/com/intellij/spellchecker/compress/Compressor.java new file mode 100644 index 000000000000..e8e791121134 --- /dev/null +++ b/plugins/spellchecker/src/com/intellij/spellchecker/compress/Compressor.java @@ -0,0 +1,128 @@ +/* + * Copyright 2000-2010 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.spellchecker.compress; + +import com.intellij.util.Consumer; + +import java.util.ArrayList; +import java.util.List; + +public final class Compressor { + + public static int WORD_LENGTH = 8; + public static byte TRANSITION_WORD = new Double(-Math.pow(2, WORD_LENGTH - 1)).byteValue(); + public final int skipFirst; + + + public Compressor(int skipFirst) { + this.skipFirst = skipFirst; + } + + + public UnitBitSet decompress(byte[] value) { + UnitBitSet bs = new UnitBitSet(WORD_LENGTH - 1, true); + int index = 0; + int bitsPerUnitInOrigin = value[skipFirst]; + + for (int i = skipFirst + 1; i < value.length; i++) { + byte b = value[i]; + if (b > 0) { + //decompress verbose word + bs.setUnitValue(index++, b); + } + else { + int count = (-TRANSITION_WORD + b); + index = index + count + 1; + } + + } + UnitBitSet result = UnitBitSet.create(bs, bitsPerUnitInOrigin); + result.moveRight(skipFirst); + for (int i = 0; i < skipFirst; i++) { + result.setUnitValue(i, value[i]); + } + + return result; + } + + public byte[] compress(UnitBitSet origin) { + if (origin == null) { + return new byte[0]; + } + final List words = new ArrayList(); + final List compressed = new ArrayList(); + + final UnitBitSet copyOfOrigin = UnitBitSet.create(origin, origin.bitsPerUnit); + + for (int i = 0; i < skipFirst; i++) { + compressed.add(Integer.valueOf(copyOfOrigin.getUnitValue(i)).byteValue()); + } + copyOfOrigin.moveLeft(skipFirst); + + compressed.add(Integer.valueOf(copyOfOrigin.bitsPerUnit).byteValue()); + + UnitBitSet bs = UnitBitSet.create(copyOfOrigin, WORD_LENGTH - 1); + bs.iterateParUnits(new Consumer() { + + public void consume(Integer integer) { + if (integer != 0) { + //create verbatim word + words.add(integer.byteValue()); + } + else { + //create transition word + words.add(TRANSITION_WORD); + } + + } + }, 0, true); + + + int count = -1; + for (Byte word : words) { + if (word == TRANSITION_WORD && count == -1) { + count++; + } + else if (word == TRANSITION_WORD && count > -1) { + count++; + } + else if (word != TRANSITION_WORD && count > -1) { + //compress all transition words + while (count > 120) { + compressed.add((byte)(TRANSITION_WORD + 120)); + count -= 121; + } + if (count > -1) { + compressed.add((byte)(TRANSITION_WORD + count)); + } + compressed.add(word); + count = -1; + } + else if (word != TRANSITION_WORD && count == -1) { + compressed.add(word); + count = -1; + } + } + byte[] result = new byte[compressed.size()]; + int i = 0; + for (Byte word : compressed) { + result[i++] = word; + } + return result; + } + + +} diff --git a/plugins/spellchecker/src/com/intellij/spellchecker/compress/Encoder.java b/plugins/spellchecker/src/com/intellij/spellchecker/compress/Encoder.java new file mode 100644 index 000000000000..c2031ff794de --- /dev/null +++ b/plugins/spellchecker/src/com/intellij/spellchecker/compress/Encoder.java @@ -0,0 +1,61 @@ +package com.intellij.spellchecker.compress; + +import com.intellij.spellchecker.compress.Alphabet; +import com.intellij.spellchecker.compress.EncodingException; +import com.intellij.spellchecker.compress.UnitBitSet; +import com.intellij.util.Consumer; +import org.jetbrains.annotations.NotNull; + +import java.text.MessageFormat; + +public final class Encoder { + + private final Alphabet alphabet; + private final int offset = 2; + + public Encoder() { + alphabet = new Alphabet(); + } + + public Encoder(Alphabet alphabet) { + this.alphabet = alphabet; + } + + public Alphabet getAlphabet() { + return alphabet; + } + + public UnitBitSet encode(@NotNull CharSequence letters, boolean force) throws EncodingException { + UnitBitSet bs = new UnitBitSet(alphabet.getMaxIndex(), false); + bs.setUnitValue(0, letters.length()); + bs.setUnitValue(1, alphabet.getIndex(letters.charAt(0), force)); + for (int i = 0; i < letters.length(); i++) { + Character letter = letters.charAt(i); + int index = alphabet.getIndex(letter, force); + bs.setUnitValue(i + offset, index); + } + return bs; + } + + public String decode(@NotNull UnitBitSet bitSet) throws EncodingException { + int wordLength = bitSet.getUnitValue(0); + Character firstLetter = alphabet.getLetter(bitSet.getUnitValue(1)); + final StringBuffer result = new StringBuffer(); + bitSet.iterateParUnits(new Consumer() { + + public void consume(Integer value) { + if (value > 0 && value <= alphabet.getLastIndexUsed()) { + result.append(alphabet.getLetter(value)); + } + } + }, offset, true); + + final String word = result.toString(); + if (word.length() != wordLength || !word.startsWith(String.valueOf(firstLetter))) { + throw new EncodingException( + new MessageFormat("Error during encoding: required length - {0}, starts with {1}, but decoded: {2}") + .format(new Object[]{wordLength, firstLetter, word})); + } + return word; + } +} \ No newline at end of file diff --git a/plugins/spellchecker/src/com/intellij/spellchecker/compress/EncodingException.java b/plugins/spellchecker/src/com/intellij/spellchecker/compress/EncodingException.java new file mode 100644 index 000000000000..a7cd3ddaaaa4 --- /dev/null +++ b/plugins/spellchecker/src/com/intellij/spellchecker/compress/EncodingException.java @@ -0,0 +1,10 @@ +package com.intellij.spellchecker.compress; + +public class EncodingException extends RuntimeException { + + public EncodingException(String message) { + super(message); + } + + +} diff --git a/plugins/spellchecker/src/com/intellij/spellchecker/compress/UnitBitSet.java b/plugins/spellchecker/src/com/intellij/spellchecker/compress/UnitBitSet.java new file mode 100644 index 000000000000..020a5711ca73 --- /dev/null +++ b/plugins/spellchecker/src/com/intellij/spellchecker/compress/UnitBitSet.java @@ -0,0 +1,120 @@ +package com.intellij.spellchecker.compress; + +import com.intellij.util.Consumer; +import org.jetbrains.annotations.NotNull; + +import java.util.BitSet; + +public class UnitBitSet extends BitSet { + + public int bitsPerUnit = 10; + + public UnitBitSet() { + } + + public UnitBitSet(int value, boolean exact) { + if (exact) { + this.bitsPerUnit = value; + } + else { + this.bitsPerUnit = new Double(Math.log(value) / Math.log(2)).intValue() + 1; + } + } + + public void inc() { + boolean t = true; + for (int i = 0; i <= size(); i++) { + if (!get(i) && t) { + set(i); + t = false; + } + else if (get(i) && t) { + set(i, false); + } + } + } + + public int getUnitValue(int number) { + int startIndex = number * bitsPerUnit; + int result = 0; + for (int i = 0; i < bitsPerUnit; i++) { + if (get(startIndex + i)) { + result += Math.pow(2, i); + } + } + return result; + } + + public void setUnitValue(int number, int value) { + if (value > Math.pow(2, bitsPerUnit) - 1) { + throw new IllegalArgumentException(); + } + int startIndex = number * bitsPerUnit; + for (int i = 0; i < bitsPerUnit; i++) { + set(startIndex + i, value % 2 == 1); + value /= 2; + } + } + + public void setBits(int... bits) { + if (bits == null) { + return; + } + for (int bit : bits) { + set(bit); + } + } + + public void moveLeft(int n) { + if (n <= 0) { + return; + } + for (int i = 0; i < size() - n * bitsPerUnit; i++) { + set(i, get(n * bitsPerUnit + i)); + } + for (int i = size() - n * bitsPerUnit; i < size(); i++) { + set(i, false); + } + } + + public void moveRight(int n) { + if (n <= 0) { + return; + } + for (int i = size() - 1; i >= 0; i--) { + set(i + n * bitsPerUnit, get(i)); + } + for (int i = 0; i < n * bitsPerUnit; i++) { + set(i, false); + } + } + + + public void iterateParUnits(@NotNull Consumer consumer, int offset, boolean skipLastZero) { + int units = size() / bitsPerUnit; + for (int i = offset; i <= units; i++) { + consumer.consume(getUnitValue(i)); + if (skipLastZero && nextSetBit(i) == -1) { + break; + } + } + } + + public static UnitBitSet create(int... bits) { + UnitBitSet bs = new UnitBitSet(); + bs.setBits(bits); + return bs; + } + + public static UnitBitSet create(int bitsPerUnit, boolean exact, int... bits) { + UnitBitSet bs = new UnitBitSet(bitsPerUnit, exact); + bs.setBits(bits); + return bs; + } + + public static UnitBitSet create(@NotNull BitSet bitSet, int bitsPerUnit) { + UnitBitSet result = new UnitBitSet(bitsPerUnit, true); + result.or(bitSet); + return result; + } +} diff --git a/plugins/spellchecker/src/com/intellij/spellchecker/dictionary/AggregatedDictionary.java b/plugins/spellchecker/src/com/intellij/spellchecker/dictionary/AggregatedDictionary.java index ba886c936716..8ae6017fe808 100644 --- a/plugins/spellchecker/src/com/intellij/spellchecker/dictionary/AggregatedDictionary.java +++ b/plugins/spellchecker/src/com/intellij/spellchecker/dictionary/AggregatedDictionary.java @@ -15,7 +15,7 @@ */ package com.intellij.spellchecker.dictionary; -import com.intellij.spellchecker.trie.Action; +import com.intellij.util.Consumer; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -23,17 +23,17 @@ import java.util.Collection; import java.util.HashSet; import java.util.Set; -public class AggregatedDictionary implements Dictionary { +public class AggregatedDictionary implements EditableDictionary { private static final String DICTIONARY_NAME = "common"; - private Dictionary cachedDictionary; + private EditableDictionary cachedDictionary; private ProjectDictionary projectDictionary; public String getName() { return DICTIONARY_NAME; } - public AggregatedDictionary(@NotNull ProjectDictionary projectDictionary, @NotNull Dictionary cachedDictionary) { + public AggregatedDictionary(@NotNull ProjectDictionary projectDictionary, @NotNull EditableDictionary cachedDictionary) { this.projectDictionary = projectDictionary; this.cachedDictionary = cachedDictionary; this.cachedDictionary.addToDictionary(projectDictionary.getWords()); @@ -77,15 +77,17 @@ public class AggregatedDictionary implements Dictionary { getProjectDictionary().clear(); } - public void traverse(final Action action) { - cachedDictionary.traverse(action); + public void traverse(@NotNull final Consumer consumer) { + cachedDictionary.traverse(consumer); } public Set getWords() { - Set words = new HashSet(); - words.addAll(cachedDictionary.getWords()); - return words; + return cachedDictionary!=null?cachedDictionary.getWords():null; + } + + public int size() { + return (cachedDictionary!=null?cachedDictionary.size():0); } @Nullable @@ -93,22 +95,13 @@ public class AggregatedDictionary implements Dictionary { return getProjectDictionary().getEditableWords(); } - @Nullable - public Set getNotEditableWords() { - Set words = getWords(); - Set editable = getEditableWords(); - if (words != null && editable != null) { - words.removeAll(editable); - } - return words; - } public void addToDictionary(@Nullable Collection words) { getProjectDictionary().addToDictionary(words); getCachedDictionary().addToDictionary(words); } - public Dictionary getCachedDictionary() { + public EditableDictionary getCachedDictionary() { return cachedDictionary; } diff --git a/plugins/spellchecker/src/com/intellij/spellchecker/dictionary/Dictionary.java b/plugins/spellchecker/src/com/intellij/spellchecker/dictionary/Dictionary.java index 48b17c285a42..5c689b1714c6 100644 --- a/plugins/spellchecker/src/com/intellij/spellchecker/dictionary/Dictionary.java +++ b/plugins/spellchecker/src/com/intellij/spellchecker/dictionary/Dictionary.java @@ -15,10 +15,9 @@ */ package com.intellij.spellchecker.dictionary; -import com.intellij.spellchecker.trie.Action; +import com.intellij.util.Consumer; import org.jetbrains.annotations.Nullable; -import java.util.Collection; import java.util.Set; public interface Dictionary { @@ -29,26 +28,10 @@ public interface Dictionary { boolean isEmpty(); - void addToDictionary(String word); - - void removeFromDictionary(String word); - - void addToDictionary(@Nullable Collection words); - - void replaceAll(@Nullable Collection words); - - void clear(); - - void traverse(final Action action); + void traverse(final Consumer action); @Nullable Set getWords(); - @Nullable - Set getEditableWords(); - - @Nullable - Set getNotEditableWords(); - - + int size(); } diff --git a/plugins/spellchecker/src/com/intellij/spellchecker/dictionary/EditableDictionary.java b/plugins/spellchecker/src/com/intellij/spellchecker/dictionary/EditableDictionary.java new file mode 100644 index 000000000000..d2abc06519bc --- /dev/null +++ b/plugins/spellchecker/src/com/intellij/spellchecker/dictionary/EditableDictionary.java @@ -0,0 +1,39 @@ +/* + * Copyright 2000-2010 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.spellchecker.dictionary; + +import org.jetbrains.annotations.Nullable; + +import java.util.Collection; +import java.util.Set; + +public interface EditableDictionary extends Dictionary { + + void addToDictionary(@Nullable String word); + + void removeFromDictionary(@Nullable String word); + + void addToDictionary(@Nullable Collection words); + + void replaceAll(@Nullable Collection words); + + void clear(); + + @Nullable + Set getEditableWords(); + + +} diff --git a/plugins/spellchecker/src/com/intellij/spellchecker/trie/Action.java b/plugins/spellchecker/src/com/intellij/spellchecker/dictionary/EditableDictionaryLoader.java similarity index 74% rename from plugins/spellchecker/src/com/intellij/spellchecker/trie/Action.java rename to plugins/spellchecker/src/com/intellij/spellchecker/dictionary/EditableDictionaryLoader.java index 4391ff8ca6e2..2c0fe63b2f7d 100644 --- a/plugins/spellchecker/src/com/intellij/spellchecker/trie/Action.java +++ b/plugins/spellchecker/src/com/intellij/spellchecker/dictionary/EditableDictionaryLoader.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2009 JetBrains s.r.o. + * Copyright 2000-2010 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. @@ -13,11 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.intellij.spellchecker.trie; +package com.intellij.spellchecker.dictionary; -import java.util.Map; -public interface Action { +public interface EditableDictionaryLoader extends Loader{ - void run(Map.Entry entry); + EditableDictionary getDictionary(); } diff --git a/plugins/spellchecker/src/com/intellij/spellchecker/dictionary/Loader.java b/plugins/spellchecker/src/com/intellij/spellchecker/dictionary/Loader.java index e66a13933749..a15cdd5d6e8a 100644 --- a/plugins/spellchecker/src/com/intellij/spellchecker/dictionary/Loader.java +++ b/plugins/spellchecker/src/com/intellij/spellchecker/dictionary/Loader.java @@ -22,4 +22,6 @@ import org.jetbrains.annotations.NotNull; public interface Loader { void load(@NotNull Consumer consumer); + + String getName(); } diff --git a/plugins/spellchecker/src/com/intellij/spellchecker/dictionary/ProjectDictionary.java b/plugins/spellchecker/src/com/intellij/spellchecker/dictionary/ProjectDictionary.java index e31e10a74313..b3ac0e314d41 100644 --- a/plugins/spellchecker/src/com/intellij/spellchecker/dictionary/ProjectDictionary.java +++ b/plugins/spellchecker/src/com/intellij/spellchecker/dictionary/ProjectDictionary.java @@ -15,24 +15,27 @@ */ package com.intellij.spellchecker.dictionary; -import com.intellij.spellchecker.trie.Action; +import com.intellij.util.Consumer; +import gnu.trove.THashSet; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.util.*; +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; -public class ProjectDictionary implements Dictionary { +public class ProjectDictionary implements EditableDictionary { private static final String DEFAULT_CURRENT_USER_NAME = "default.user"; private static final String DEFAULT_PROJECT_DICTIONARY_NAME = "project"; private String activeName; - private Set dictionaries; + private Set dictionaries; public ProjectDictionary() { } - public ProjectDictionary(Set dictionaries) { + public ProjectDictionary(Set dictionaries) { this.dictionaries = dictionaries; } @@ -73,20 +76,20 @@ public class ProjectDictionary implements Dictionary { } @NotNull - private Dictionary getActiveDictionary() { + private EditableDictionary getActiveDictionary() { return ensureCurrentUserDictionary(); } @NotNull - private Dictionary ensureCurrentUserDictionary() { + private EditableDictionary ensureCurrentUserDictionary() { if (activeName == null) { activeName = DEFAULT_CURRENT_USER_NAME; } - Dictionary result = getDictionaryByName(activeName); + EditableDictionary result = getDictionaryByName(activeName); if (result == null) { result = new UserDictionary(this.activeName); if (dictionaries == null) { - dictionaries = new HashSet(); + dictionaries = new THashSet(); } dictionaries.add(result); } @@ -94,12 +97,12 @@ public class ProjectDictionary implements Dictionary { } @Nullable - private Dictionary getDictionaryByName(@NotNull String name) { + private EditableDictionary getDictionaryByName(@NotNull String name) { if (dictionaries == null) { return null; } - Dictionary result = null; - for (Dictionary dictionary : dictionaries) { + EditableDictionary result = null; + for (EditableDictionary dictionary : dictionaries) { if (dictionary.getName().equals(name)) { result = dictionary; break; @@ -130,13 +133,21 @@ public class ProjectDictionary implements Dictionary { return words; } - public void traverse(Action action) { + public int size(){ + int result = 0; + for (Dictionary dictionary : dictionaries) { + result+=dictionary.size(); + } + return result; + } + + public void traverse(final Consumer consumer) { if (dictionaries == null) { return; } - for (Dictionary dictionary : dictionaries) { - dictionary.traverse(action); + for (EditableDictionary dictionary : dictionaries) { + dictionary.traverse(consumer); } } @@ -146,21 +157,12 @@ public class ProjectDictionary implements Dictionary { return getActiveDictionary().getWords(); } - @Nullable - public Set getNotEditableWords() { - Set words = getWords(); - Set editable = getEditableWords(); - if (words != null && editable != null) { - words.removeAll(editable); - } - return words; - } public void addToDictionary(@Nullable Collection words) { getActiveDictionary().addToDictionary(words); } - public Set getDictionaries() { + public Set getDictionaries() { return dictionaries; } diff --git a/plugins/spellchecker/src/com/intellij/spellchecker/dictionary/UserDictionary.java b/plugins/spellchecker/src/com/intellij/spellchecker/dictionary/UserDictionary.java index 112372b6d664..87975558a9a8 100644 --- a/plugins/spellchecker/src/com/intellij/spellchecker/dictionary/UserDictionary.java +++ b/plugins/spellchecker/src/com/intellij/spellchecker/dictionary/UserDictionary.java @@ -15,20 +15,20 @@ */ package com.intellij.spellchecker.dictionary; -import com.intellij.spellchecker.trie.Action; -import com.intellij.spellchecker.trie.CharSequenceKeyAnalyzer; -import com.intellij.spellchecker.trie.PatriciaTrie; -import com.intellij.spellchecker.trie.Trie; +import com.intellij.util.Consumer; +import gnu.trove.THashSet; +import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.Collection; -import java.util.Map; import java.util.Set; -public class UserDictionary implements Dictionary { +public class UserDictionary implements EditableDictionary { private String name; - private Trie trie = new PatriciaTrie(new CharSequenceKeyAnalyzer()); + + @NotNull + private final THashSet words = new THashSet(); public UserDictionary(String name) { this.name = name; @@ -39,26 +39,25 @@ public class UserDictionary implements Dictionary { } public boolean contains(String word) { - return word != null && trie.containsKey(word); + return words.contains(word); + } + + public int size() { + return words == null ? 0 : words.size(); } @Nullable public Set getWords() { - return trie.keySet(); + return words; } @Nullable public Set getEditableWords() { - return trie.keySet(); - } - - @Nullable - public Set getNotEditableWords() { - return null; + return words; } public void clear() { - trie.clear(); + words.clear(); } @@ -66,14 +65,14 @@ public class UserDictionary implements Dictionary { if (word == null) { return; } - trie.put(word, word); + words.add(word); } public void removeFromDictionary(String word) { if (word == null) { return; } - trie.remove(word); + words.remove(word); } public void replaceAll(@Nullable Collection words) { @@ -91,7 +90,7 @@ public class UserDictionary implements Dictionary { } public boolean isEmpty() { - return (trie == null || trie.size() == 0); + return words.size() == 0; } @Override @@ -105,13 +104,10 @@ public class UserDictionary implements Dictionary { } - public void traverse(final Action action){ - trie.traverse(new PatriciaTrie.Cursor() { - public SelectStatus select(Map.Entry entry) { - action.run(entry); - return SelectStatus.CONTINUE; - } - }); + public void traverse(final Consumer consumer) { + for (String word : words) { + consumer.consume(word); + } } @Override @@ -121,6 +117,6 @@ public class UserDictionary implements Dictionary { @Override public String toString() { - return "UserDictionary{" + "name='" + name + '\'' + ", words.count=" + trie.size() + '}'; + return "UserDictionary{" + "name='" + name + '\'' + ", words.count=" + words.size() + '}'; } } diff --git a/plugins/spellchecker/src/com/intellij/spellchecker/engine/BaseSpellChecker.java b/plugins/spellchecker/src/com/intellij/spellchecker/engine/BaseSpellChecker.java index 5af0d0b6cc25..fcb9729df678 100644 --- a/plugins/spellchecker/src/com/intellij/spellchecker/engine/BaseSpellChecker.java +++ b/plugins/spellchecker/src/com/intellij/spellchecker/engine/BaseSpellChecker.java @@ -15,68 +15,157 @@ */ package com.intellij.spellchecker.engine; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.spellchecker.compress.CompressedDictionary; import com.intellij.spellchecker.dictionary.Dictionary; +import com.intellij.spellchecker.dictionary.EditableDictionary; +import com.intellij.spellchecker.dictionary.EditableDictionaryLoader; import com.intellij.spellchecker.dictionary.Loader; -import com.intellij.spellchecker.dictionary.UserDictionary; -import com.intellij.spellchecker.trie.Action; import com.intellij.util.Consumer; +import gnu.trove.THashSet; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Map; +import java.util.*; public class BaseSpellChecker implements SpellCheckerEngine { - private Dictionary engineDictionary; private Transformation transform = new Transformation(); + private final Set dictionaries = new THashSet(); + private final Set bundledDictionaries = new THashSet(); private Metrics metrics = new LevenshteinDistance(); - private Consumer consumer = new Consumer() { - public void consume(@Nullable String word) { - final String transformed = transform.transform(word); - if (transformed != null) { - engineDictionary.addToDictionary(transformed); - } - } - }; public BaseSpellChecker() { - ensureEngineDictionary(); } + public void loadDictionary(@NotNull Loader loader) { - loader.load(consumer); + if (loader instanceof EditableDictionaryLoader) { + final EditableDictionary dictionary = ((EditableDictionaryLoader)loader).getDictionary(); + if (dictionary != null) { + addModifiableDictionary(dictionary); + } + } + else { + loadFixedDictionary(loader); + } + + } + + private void loadFixedDictionary(final @NotNull Loader loader) { + /*if (ApplicationManager.getApplication().isUnitTestMode()) { + loadCompressedDictionary(loader); + } + else { + ApplicationManager.getApplication().executeOnPooledThread(new Runnable() { + public void run() { + loadCompressedDictionary(loader); + } + }); + }*/ + loadCompressedDictionary(loader); + } + + private void loadCompressedDictionary(@NotNull Loader loader) { + final CompressedDictionary dictionary = CompressedDictionary.create(loader, transform); + if (dictionary != null) { + addCompressedFixedDictionary(dictionary); + } + } + + private void addModifiableDictionary(@NotNull EditableDictionary dictionary) { + dictionaries.add(dictionary); + } + + private void addCompressedFixedDictionary(@NotNull Dictionary dictionary) { + bundledDictionaries.add(dictionary); } public Transformation getTransformation() { return transform; } - private void ensureEngineDictionary() { - if (engineDictionary == null) { - engineDictionary = new UserDictionary("engine"); + @NotNull + private static List restore(@NotNull Character startFrom, int i, int j, @Nullable Collection dictionaries) { + if (dictionaries == null) { + return Collections.emptyList(); } + List results = new ArrayList(); + + for (Object o : dictionaries) { + if (o instanceof Dictionary) { + results.addAll(restore(startFrom, i, j, (Dictionary)o)); + } + + } + return results; } - - public void addToDictionary(String word) { - final String transformed = transform.transform(word); - if (transformed != null) { - engineDictionary.addToDictionary(transformed); + @NotNull + private static List restore(@NotNull final Character first, final int i, final int j, @Nullable Dictionary dictionary) { + if (dictionary == null) { + return Collections.emptyList(); } + final List result = new ArrayList(); + if (dictionary instanceof CompressedDictionary) { + result.addAll(((CompressedDictionary)dictionary).getWords(first)); + } + else { + dictionary.traverse(new Consumer() { + public void consume(String s) { + if (StringUtil.isEmpty(s)) { + return; + } + if (s.charAt(0) == first && s.length() >= i && s.length() <= j) { + result.add(s); + } + } + }); + } + + return result; + } + + private static boolean isCorrect(@NotNull String transformed, @Nullable Collection dictionaries) { + if (dictionaries == null) { + return true; + } + + for (Object o : dictionaries) { + if (o instanceof Dictionary) { + boolean result = isCorrect(transformed, (Dictionary)o); + if (result) { + return true; + } + } + + } + return false; + } + + private static boolean isCorrect(@NotNull String transformed, @Nullable Dictionary dictionary) { + if (dictionary == null) { + return true; + } + + return dictionary.contains(transformed); } public boolean isCorrect(@NotNull String word) { final String transformed = transform.transform(word); - return transformed != null && engineDictionary.contains(transformed); + if (transformed == null) { + return true; + } + return isCorrect(transformed, bundledDictionaries) || isCorrect(transformed, dictionaries); + + } + @NotNull public List getSuggestions(final @NotNull String word, int threshold, int quality) { final String transformed = transform.transform(word); @@ -84,14 +173,12 @@ public class BaseSpellChecker implements SpellCheckerEngine { return Collections.emptyList(); } final List suggestions = new ArrayList(); - engineDictionary.traverse(new Action() { - public void run(Map.Entry entry) { - if (transformed.charAt(0) == entry.getKey().charAt(0)/* && Math.abs(mpw.length() - entry.getKey().length()) <= 1*/) { - final int distance = metrics.calculateMetrics(transformed, entry.getKey()); - suggestions.add(new Suggestion(entry.getKey(), distance)); - } - } - }); + List rawSuggestions = restore(transformed.charAt(0), 0, Integer.MAX_VALUE, bundledDictionaries); + rawSuggestions.addAll(restore(word.charAt(0), 0, Integer.MAX_VALUE, dictionaries)); + for (String rawSuggestion : rawSuggestions) { + final int distance = metrics.calculateMetrics(transformed, rawSuggestion); + suggestions.add(new Suggestion(rawSuggestion, distance)); + } List result = new ArrayList(); if (suggestions.isEmpty()) { return result; @@ -108,13 +195,44 @@ public class BaseSpellChecker implements SpellCheckerEngine { return result; } + @NotNull public List getVariants(@NotNull String prefix) { - return null; + //if (StringUtil.isEmpty(prefix)) { + return Collections.emptyList(); + //} + } public void reset() { - engineDictionary = null; - ensureEngineDictionary(); + bundledDictionaries.clear(); + dictionaries.clear(); + } + + public boolean isDictionaryLoad(@NotNull String name) { + return getBundledDictionaryByName(name) != null; + } + + public void removeDictionary(@NotNull String name) { + for (Iterator iterator = bundledDictionaries.iterator(); iterator.hasNext();) { + Dictionary dictionary = iterator.next(); + if (name.equals(dictionary.getName())) { + iterator.remove(); + break; + } + } + } + + @Nullable + public Dictionary getBundledDictionaryByName(@NotNull String name) { + if (bundledDictionaries == null) { + return null; + } + for (Dictionary dictionary : bundledDictionaries) { + if (name.equals(dictionary.getName())) { + return dictionary; + } + } + return null; } } diff --git a/plugins/spellchecker/src/com/intellij/spellchecker/engine/SpellCheckerEngine.java b/plugins/spellchecker/src/com/intellij/spellchecker/engine/SpellCheckerEngine.java index 05b6e0249788..80bb8dd87232 100644 --- a/plugins/spellchecker/src/com/intellij/spellchecker/engine/SpellCheckerEngine.java +++ b/plugins/spellchecker/src/com/intellij/spellchecker/engine/SpellCheckerEngine.java @@ -30,7 +30,6 @@ public interface SpellCheckerEngine { boolean isCorrect(@NotNull String word); - void addToDictionary(String word); @NotNull List getSuggestions(@NotNull String word, int threshold, int quality); @@ -38,9 +37,10 @@ public interface SpellCheckerEngine { @NotNull List getVariants(@NotNull String prefix); - /** - * This method must clean up user dictionary words and ignored words. - */ + void reset(); + boolean isDictionaryLoad(@NotNull String name); + + void removeDictionary(@NotNull String name); } diff --git a/plugins/spellchecker/src/com/intellij/spellchecker/engine/Transformation.java b/plugins/spellchecker/src/com/intellij/spellchecker/engine/Transformation.java index 6381a025b351..79a5fb88fde1 100644 --- a/plugins/spellchecker/src/com/intellij/spellchecker/engine/Transformation.java +++ b/plugins/spellchecker/src/com/intellij/spellchecker/engine/Transformation.java @@ -21,10 +21,11 @@ import org.jetbrains.annotations.Nullable; import java.util.Collection; import java.util.Set; +@SuppressWarnings({"ALL"}) public class Transformation { @Nullable - public String transform(@Nullable String word) { + public String transform(@Nullable String word) { if (word == null || word.trim().length() < 3) { return null; } @@ -32,7 +33,7 @@ public class Transformation { } @Nullable - public Set transform(@Nullable Collection words) { + public Set transform(@Nullable Collection words) { if (words == null || words.isEmpty()) { return null; } diff --git a/plugins/spellchecker/src/com/intellij/spellchecker/inspections/SpellCheckingInspection.java b/plugins/spellchecker/src/com/intellij/spellchecker/inspections/SpellCheckingInspection.java index 359f1f9783a8..2cc1e161567a 100644 --- a/plugins/spellchecker/src/com/intellij/spellchecker/inspections/SpellCheckingInspection.java +++ b/plugins/spellchecker/src/com/intellij/spellchecker/inspections/SpellCheckingInspection.java @@ -36,6 +36,7 @@ import com.intellij.spellchecker.tokenizer.Token; import com.intellij.spellchecker.tokenizer.Tokenizer; import com.intellij.spellchecker.util.SpellCheckerBundle; import com.intellij.util.containers.hash.HashMap; +import gnu.trove.THashSet; import org.jetbrains.annotations.Nls; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; @@ -43,14 +44,12 @@ import org.jetbrains.annotations.Nullable; import javax.swing.*; import java.awt.*; -import java.util.ArrayList; -import java.util.Collection; +import java.util.*; import java.util.List; -import java.util.Map; public class SpellCheckingInspection extends LocalInspectionTool { - + public static final String SPELL_CHECKING_INSPECTION_TOOL_NAME = "SpellCheckingInspection"; private static final AcceptWordAsCorrect BATCH_ACCEPT_FIX = new AcceptWordAsCorrect(); @@ -146,56 +145,68 @@ public class SpellCheckingInspection extends LocalInspectionTool { if (tokens == null) { return; } + SpellCheckerManager manager = SpellCheckerManager.getInstance(element.getProject()); + Set alreadyChecked = new THashSet(); for (Token token : tokens) { - inspect(token, holder, isOnTheFly, NAMES_VALIDATORS); + inspect(token, holder, isOnTheFly, alreadyChecked, manager,NAMES_VALIDATORS); } } }; } - private static void inspect(Token token, ProblemsHolder holder, boolean isOnTheFly, NamesValidator... validators) { + + private static void inspect(Token token, ProblemsHolder holder, boolean isOnTheFly,@NotNull Set alreadyChecked, @NotNull SpellCheckerManager manager, NamesValidator... validators) { List areaList = TextSplitter.splitText(token.getText()); if (areaList == null) { return; } for (CheckArea area : areaList) { boolean ignored = area.isIgnored(); - boolean keyword = isKeyword(validators, token.getElement(), area.getWord()); - if (!ignored && !keyword) { - inspect(area, token, holder, isOnTheFly); + final TextRange textRange = area.getTextRange(); + + if (ignored || textRange ==null){ + continue; } + + final String word = area.getWord(); + if (word==null || (!isOnTheFly && alreadyChecked.contains(word))){ + continue; + } + + boolean keyword = isKeyword(validators, token.getElement(), word); + if (keyword){ + continue; + } + + boolean hasProblems = manager.hasProblem(word); + if (hasProblems){ + if (!isOnTheFly){ + alreadyChecked.add(word); + addBatchDescriptor(textRange,token,holder); + } else{ + addRegularDescriptor(textRange,token,holder); + } + } + } } - private static void inspect(@NotNull CheckArea area, @NotNull Token token, @NotNull ProblemsHolder holder, boolean isOnTheFly) { - SpellCheckerManager manager = SpellCheckerManager.getInstance(token.getElement().getProject()); - - final TextRange textRange = area.getTextRange(); - final String word = area.getWord(); - - if (textRange == null || word == null) { - return; - } - - if (manager.hasProblem(word)) { + private static void addBatchDescriptor(@NotNull TextRange textRange, @NotNull Token token, @NotNull ProblemsHolder holder) { List fixes = new ArrayList(); - if (isOnTheFly) { - if (!token.isUseRename()) { - fixes.add(new ChangeTo()); - } - else { - fixes.add(new RenameTo()); - } - } + fixes.add(BATCH_ACCEPT_FIX); - final AcceptWordAsCorrect acceptWordAsCorrect = isOnTheFly ? new AcceptWordAsCorrect() : BATCH_ACCEPT_FIX; - fixes.add(acceptWordAsCorrect); - - final ProblemDescriptor problemDescriptor = createProblemDescriptor(token, holder, textRange, fixes, isOnTheFly); + final ProblemDescriptor problemDescriptor = createProblemDescriptor(token, holder, textRange, fixes, false); holder.registerProblem(problemDescriptor); - } + } + private static void addRegularDescriptor(@NotNull TextRange textRange, @NotNull Token token, @NotNull ProblemsHolder holder) { + List fixes = new ArrayList(); + fixes.add((token.isUseRename()?new RenameTo():new ChangeTo())); + fixes.add(new AcceptWordAsCorrect()); + + final ProblemDescriptor problemDescriptor = createProblemDescriptor(token, holder, textRange, fixes, true); + holder.registerProblem(problemDescriptor); } private static ProblemDescriptor createProblemDescriptor(Token token, diff --git a/plugins/spellchecker/src/com/intellij/spellchecker/inspections/TextSplitter.java b/plugins/spellchecker/src/com/intellij/spellchecker/inspections/TextSplitter.java index 39286a1407b8..9615f15995d6 100644 --- a/plugins/spellchecker/src/com/intellij/spellchecker/inspections/TextSplitter.java +++ b/plugins/spellchecker/src/com/intellij/spellchecker/inspections/TextSplitter.java @@ -15,7 +15,6 @@ */ package com.intellij.spellchecker.inspections; -import com.intellij.openapi.progress.ProgressManager; import com.intellij.openapi.util.TextRange; import com.intellij.openapi.util.text.StringUtil; import com.intellij.psi.codeStyle.NameUtil; @@ -29,19 +28,10 @@ import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; -/** - * @author shkate@jetbrains.com - */ public class TextSplitter { - @NonNls - private static final Pattern NON_SPACE = Pattern.compile("\\S+"); @NonNls - private static final Pattern HTML = Pattern.compile("<(0)>"); - - @NonNls - private static final Pattern WORD = Pattern.compile("\\b\\p{L}*'?\\p{L}*"); private static final Pattern EXTENDED_WORD = Pattern.compile("\\b\\p{L}*'?\\p{L}(_*\\p{L})*"); @@ -51,12 +41,15 @@ public class TextSplitter { @NonNls private static final Pattern URL = Pattern.compile("(https?|ftp|mailto)\\:\\/\\/"); + @NonNls private static final Pattern COMPLEX = Pattern.compile("(\\.[^\\.]+)|([@]+)"); @NonNls private static final Pattern SPECIAL = Pattern.compile("^&\\p{Alnum}{4};"); + private static final String delimiters = ".,;:!?*/&"; + private TextSplitter() { } @@ -73,8 +66,8 @@ public class TextSplitter { List results = new ArrayList(); String[] pieces = text.substring(i).split(WORD_SPLITTER); for (String s : pieces) { - ProgressManager.checkCanceled(); - if (s.length() > 0) { + //ProgressManager.checkCanceled(); + if (s.length() > 0 && startWithLetterOrDelimiter(s)) { int p1 = text.indexOf(s, i); TextRange range = TextRange.from(p1, s.length()); List areaList = splitNonSpace(text, range); @@ -87,6 +80,13 @@ public class TextSplitter { return (results.size() == 0) ? null : results; } + private static boolean startWithLetterOrDelimiter(@NotNull String s) { + // Matcher matcher = PRINTABLE.matcher(s); + // return matcher.find(); + return Character.isLetter(s.charAt(0)) || delimiters.contains(s.substring(0,1)); + // return true; + } + @Nullable private static List splitNonSpace(String text, TextRange range) { String nonSpaceArea = text.substring(range.getStartOffset(), range.getEndOffset()); @@ -100,12 +100,12 @@ public class TextSplitter { @NotNull private static List splitSimpleWord(String text, TextRange range) { List results = new ArrayList(); - if (text==null || range==null || range.getLength()<1){ + if (text == null || range == null || range.getLength() < 1) { return results; } String word = text.substring(range.getStartOffset(), range.getEndOffset()); String[] words = NameUtil.splitNameIntoWords(word); - if (words == null || words.length==0) { + if (words == null || words.length == 0) { return results; } @@ -132,7 +132,7 @@ public class TextSplitter { int start = word.indexOf(s, index); int end = start + s.length(); boolean isUpperCase = Strings.isUpperCase(s); - boolean flag = (isUpperCase && !isAllWordsAreUpperCased) || isKeyword(s); + boolean flag = (isUpperCase && !isAllWordsAreUpperCased); Matcher matcher = WORD.matcher(s); if (matcher.find()) { TextRange found = matcherRange(subRange(range, start, end), matcher); @@ -175,9 +175,6 @@ public class TextSplitter { results.add(new CheckArea(text, found, flag || tooShort)); } - private static boolean isKeyword(String s) { - return false; - } private static boolean isAllWordsAreUpperCased(String[] words) { if (words == null) return false; diff --git a/plugins/spellchecker/src/com/intellij/spellchecker/quickfixes/ChangeTo.java b/plugins/spellchecker/src/com/intellij/spellchecker/quickfixes/ChangeTo.java index 3c6188800565..8dac38ae1487 100644 --- a/plugins/spellchecker/src/com/intellij/spellchecker/quickfixes/ChangeTo.java +++ b/plugins/spellchecker/src/com/intellij/spellchecker/quickfixes/ChangeTo.java @@ -45,7 +45,7 @@ public class ChangeTo extends ShowSuggestions implements SpellCheckerQuickFix { @NotNull public String getName() { - return SpellCheckerBundle.message("change.to"); + return SpellCheckerBundle.message("change.to"); } @NotNull @@ -91,4 +91,4 @@ public class ChangeTo extends ShowSuggestions implements SpellCheckerQuickFix { } -} +} \ No newline at end of file diff --git a/plugins/spellchecker/src/com/intellij/spellchecker/quickfixes/RenameTo.java b/plugins/spellchecker/src/com/intellij/spellchecker/quickfixes/RenameTo.java index bf4b4b5846ea..0602561dc62a 100644 --- a/plugins/spellchecker/src/com/intellij/spellchecker/quickfixes/RenameTo.java +++ b/plugins/spellchecker/src/com/intellij/spellchecker/quickfixes/RenameTo.java @@ -20,10 +20,8 @@ import com.intellij.ide.DataManager; import com.intellij.openapi.actionSystem.*; import com.intellij.openapi.extensions.Extensions; import com.intellij.openapi.project.Project; -import com.intellij.openapi.util.TextRange; import com.intellij.refactoring.actions.RenameElementAction; import com.intellij.refactoring.rename.NameSuggestionProvider; -import com.intellij.spellchecker.quickfixes.DictionarySuggestionProvider; import com.intellij.spellchecker.util.SpellCheckerBundle; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -52,7 +50,6 @@ public class RenameTo extends ShowSuggestions implements SpellCheckerQuickFix { private static DictionarySuggestionProvider findProvider() { Object[] extensions = Extensions.getExtensions(NameSuggestionProvider.EP_NAME); - DictionarySuggestionProvider provider = null; if (extensions != null) { for (Object extension : extensions) { if (extension instanceof DictionarySuggestionProvider) { @@ -69,6 +66,7 @@ public class RenameTo extends ShowSuggestions implements SpellCheckerQuickFix { return Anchor.FIRST; } + @SuppressWarnings({"SSBasedInspection"}) public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) { SwingUtilities.invokeLater(new Runnable() { public void run() { diff --git a/plugins/spellchecker/src/com/intellij/spellchecker/settings/SpellCheckerSettingsPane.java b/plugins/spellchecker/src/com/intellij/spellchecker/settings/SpellCheckerSettingsPane.java index fcbbbbafb331..8cd686d3caab 100644 --- a/plugins/spellchecker/src/com/intellij/spellchecker/settings/SpellCheckerSettingsPane.java +++ b/plugins/spellchecker/src/com/intellij/spellchecker/settings/SpellCheckerSettingsPane.java @@ -25,7 +25,7 @@ import com.intellij.openapi.util.Pair; import com.intellij.openapi.util.io.FileUtil; import com.intellij.profile.codeInspection.ui.ErrorsConfigurable; import com.intellij.spellchecker.SpellCheckerManager; -import com.intellij.spellchecker.dictionary.Dictionary; +import com.intellij.spellchecker.dictionary.EditableDictionary; import com.intellij.spellchecker.util.SPFileUtil; import com.intellij.spellchecker.util.SpellCheckerBundle; import com.intellij.spellchecker.util.Strings; @@ -161,6 +161,13 @@ public class SpellCheckerSettingsPane implements Disposable { } public void apply() throws ConfigurationException { + if (wordsPanel.isModified()){ + manager.updateUserDictionary(wordsPanel.getWords()); + } + if (!optionalChooserComponent.isModified() && !pathsChooserComponent.isModified()){ + return; + } + optionalChooserComponent.apply(); pathsChooserComponent.apply(); settings.setDictionaryFoldersPaths(pathsChooserComponent.getValues()); @@ -182,7 +189,8 @@ public class SpellCheckerSettingsPane implements Disposable { settings.setDisabledDictionariesPaths(disabledDictionaries); settings.setBundledDisabledDictionariesPaths(bundledDisabledDictionaries); - manager.update(wordsPanel.getWords(), settings); + manager.updateBundledDictionaries(); + } private boolean isUserDictionary(final String dictionary) { @@ -232,9 +240,9 @@ public class SpellCheckerSettingsPane implements Disposable { } public static final class WordDescriber { - private Dictionary dictionary; + private EditableDictionary dictionary; - public WordDescriber(Dictionary dictionary) { + public WordDescriber(EditableDictionary dictionary) { this.dictionary = dictionary; } diff --git a/plugins/spellchecker/src/com/intellij/spellchecker/state/CachedDictionaryState.java b/plugins/spellchecker/src/com/intellij/spellchecker/state/CachedDictionaryState.java index 6877e61ec37e..461cf66ce2fe 100644 --- a/plugins/spellchecker/src/com/intellij/spellchecker/state/CachedDictionaryState.java +++ b/plugins/spellchecker/src/com/intellij/spellchecker/state/CachedDictionaryState.java @@ -18,29 +18,29 @@ package com.intellij.spellchecker.state; import com.intellij.openapi.components.PersistentStateComponent; import com.intellij.openapi.components.State; import com.intellij.openapi.components.Storage; -import com.intellij.spellchecker.dictionary.Dictionary; +import com.intellij.spellchecker.dictionary.EditableDictionary; @State( name = "CachedDictionaryState", storages = {@Storage( id = "spellchecker", file = "$APP_CONFIG$/cachedDictionary.xml")}) -public class CachedDictionaryState extends DictionaryState implements PersistentStateComponent{ +public class CachedDictionaryState extends DictionaryState implements PersistentStateComponent { public static final String DEFAULT_NAME = "cached"; public CachedDictionaryState() { name = DEFAULT_NAME; } - public CachedDictionaryState(Dictionary dictionary) { + public CachedDictionaryState(EditableDictionary dictionary) { super(dictionary); name = DEFAULT_NAME; } @Override public void loadState(DictionaryState state) { - if (state.name==null){ - state.name= DEFAULT_NAME; + if (state.name == null) { + state.name = DEFAULT_NAME; } super.loadState(state); } diff --git a/plugins/spellchecker/src/com/intellij/spellchecker/state/DictionaryState.java b/plugins/spellchecker/src/com/intellij/spellchecker/state/DictionaryState.java index 1339cd523c3f..d392b69d152d 100644 --- a/plugins/spellchecker/src/com/intellij/spellchecker/state/DictionaryState.java +++ b/plugins/spellchecker/src/com/intellij/spellchecker/state/DictionaryState.java @@ -16,7 +16,7 @@ package com.intellij.spellchecker.state; import com.intellij.openapi.components.PersistentStateComponent; -import com.intellij.spellchecker.dictionary.Dictionary; +import com.intellij.spellchecker.dictionary.EditableDictionary; import com.intellij.spellchecker.dictionary.UserDictionary; import com.intellij.util.xmlb.annotations.AbstractCollection; import com.intellij.util.xmlb.annotations.Attribute; @@ -39,24 +39,24 @@ public class DictionaryState implements PersistentStateComponent projectDictionaries = projectDictionary.getDictionaries(); + Set projectDictionaries = projectDictionary.getDictionaries(); if (projectDictionaries != null) { - for (Dictionary dic : projectDictionary.getDictionaries()) { + for (EditableDictionary dic : projectDictionary.getDictionaries()) { dictionaryStates.add(new DictionaryState(dic)); } } @@ -101,7 +103,7 @@ public class ProjectDictionaryState implements PersistentStateComponent dictionaries = new HashSet(); + Set dictionaries = new THashSet(); if (dictionaryStates != null) { for (DictionaryState dictionaryState : dictionaryStates) { dictionaryState.loadState(dictionaryState); diff --git a/plugins/spellchecker/src/com/intellij/spellchecker/state/StateLoader.java b/plugins/spellchecker/src/com/intellij/spellchecker/state/StateLoader.java index 86d163adc407..b31aaf0c5d44 100644 --- a/plugins/spellchecker/src/com/intellij/spellchecker/state/StateLoader.java +++ b/plugins/spellchecker/src/com/intellij/spellchecker/state/StateLoader.java @@ -17,40 +17,48 @@ package com.intellij.spellchecker.state; import com.intellij.openapi.components.ServiceManager; import com.intellij.openapi.project.Project; -import com.intellij.spellchecker.dictionary.Dictionary; -import com.intellij.spellchecker.dictionary.Loader; +import com.intellij.spellchecker.dictionary.EditableDictionary; +import com.intellij.spellchecker.dictionary.EditableDictionaryLoader; import com.intellij.util.Consumer; import org.jetbrains.annotations.NotNull; import java.util.Set; -public class StateLoader implements Loader { +public class StateLoader implements EditableDictionaryLoader { - private Project project; - private Dictionary dictionary; + private Project project; + private EditableDictionary dictionary; - public StateLoader(Project project) { - this.project = project; + public StateLoader(Project project) { + this.project = project; + } + + + public void load(@NotNull Consumer consumer) { + AggregatedDictionaryState state = ServiceManager.getService(project, AggregatedDictionaryState.class); + state.setProject(project); + state.loadState(); + dictionary = state.getDictionary(); + if (dictionary == null) { + return; + } + final Set storedWords = dictionary.getWords(); + if (storedWords != null) { + for (String word : storedWords) { + consumer.consume(word); + } } + } - public void load(@NotNull Consumer consumer) { - AggregatedDictionaryState state = ServiceManager.getService(project, AggregatedDictionaryState.class); - state.setProject(project); - state.loadState(); - dictionary = state.getDictionary(); - final Set storedWords = dictionary.getWords(); - if (storedWords!=null){ - for (String word : storedWords) { - consumer.consume(word); - } - } - } + public EditableDictionary getDictionary() { + return dictionary; + } - public Dictionary getDictionary() { - return dictionary; - } + public String getName() { + return (dictionary != null ? dictionary.getName() : ""); + } } diff --git a/plugins/spellchecker/src/com/intellij/spellchecker/tokenizer/CommentTokenizer.java b/plugins/spellchecker/src/com/intellij/spellchecker/tokenizer/CommentTokenizer.java index a9b47f0964bf..7b1b4360b065 100644 --- a/plugins/spellchecker/src/com/intellij/spellchecker/tokenizer/CommentTokenizer.java +++ b/plugins/spellchecker/src/com/intellij/spellchecker/tokenizer/CommentTokenizer.java @@ -19,11 +19,6 @@ import com.intellij.psi.PsiComment; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -/** - * Created by IntelliJ IDEA. - * - * @author shkate@jetbrains.com - */ public class CommentTokenizer extends Tokenizer { @Nullable diff --git a/plugins/spellchecker/src/com/intellij/spellchecker/tokenizer/HtmlSpellcheckingStrategy.java b/plugins/spellchecker/src/com/intellij/spellchecker/tokenizer/HtmlSpellcheckingStrategy.java index d57820623fce..150e46c7d103 100644 --- a/plugins/spellchecker/src/com/intellij/spellchecker/tokenizer/HtmlSpellcheckingStrategy.java +++ b/plugins/spellchecker/src/com/intellij/spellchecker/tokenizer/HtmlSpellcheckingStrategy.java @@ -22,11 +22,6 @@ import com.intellij.psi.xml.XmlAttributeValue; import com.intellij.psi.xml.XmlText; import org.jetbrains.annotations.NotNull; -/** - * Created by IntelliJ IDEA. - * - * @author shkate@jetbrains.com - */ public class HtmlSpellcheckingStrategy extends SpellcheckingStrategy { @NotNull @Override diff --git a/plugins/spellchecker/src/com/intellij/spellchecker/tokenizer/TextTokenizer.java b/plugins/spellchecker/src/com/intellij/spellchecker/tokenizer/TextTokenizer.java index dd47ae9e4932..bec720598155 100644 --- a/plugins/spellchecker/src/com/intellij/spellchecker/tokenizer/TextTokenizer.java +++ b/plugins/spellchecker/src/com/intellij/spellchecker/tokenizer/TextTokenizer.java @@ -19,14 +19,8 @@ import com.intellij.psi.PsiPlainText; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -/** - * Created by IntelliJ IDEA. - * - * @author shkate@jetbrains.com - */ public class TextTokenizer extends Tokenizer { - @Nullable @Override public Token[] tokenize(@NotNull PsiPlainText element) { diff --git a/plugins/spellchecker/src/com/intellij/spellchecker/tokenizer/Token.java b/plugins/spellchecker/src/com/intellij/spellchecker/tokenizer/Token.java index 903cabaecd07..774c67514043 100644 --- a/plugins/spellchecker/src/com/intellij/spellchecker/tokenizer/Token.java +++ b/plugins/spellchecker/src/com/intellij/spellchecker/tokenizer/Token.java @@ -17,11 +17,7 @@ package com.intellij.spellchecker.tokenizer; import com.intellij.psi.PsiElement; -/** - * Created by IntelliJ IDEA. - * - * @author shkate@jetbrains.com - */ + public class Token { private String text; diff --git a/plugins/spellchecker/src/com/intellij/spellchecker/tokenizer/Tokenizer.java b/plugins/spellchecker/src/com/intellij/spellchecker/tokenizer/Tokenizer.java index bb3583f27a78..483c2ff7d39f 100644 --- a/plugins/spellchecker/src/com/intellij/spellchecker/tokenizer/Tokenizer.java +++ b/plugins/spellchecker/src/com/intellij/spellchecker/tokenizer/Tokenizer.java @@ -19,11 +19,6 @@ import com.intellij.psi.PsiElement; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -/** - * Created by IntelliJ IDEA. - * - * @author shkate@jetbrains.com - */ public class Tokenizer { @Nullable diff --git a/plugins/spellchecker/src/com/intellij/spellchecker/tokenizer/XmlAttributeTokenizer.java b/plugins/spellchecker/src/com/intellij/spellchecker/tokenizer/XmlAttributeTokenizer.java index 4e538139dfc4..5925f4555c50 100644 --- a/plugins/spellchecker/src/com/intellij/spellchecker/tokenizer/XmlAttributeTokenizer.java +++ b/plugins/spellchecker/src/com/intellij/spellchecker/tokenizer/XmlAttributeTokenizer.java @@ -19,11 +19,6 @@ import com.intellij.psi.xml.XmlAttributeValue; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -/** - * Created by IntelliJ IDEA. - * - * @author shkate@jetbrains.com - */ public class XmlAttributeTokenizer extends Tokenizer{ diff --git a/plugins/spellchecker/src/com/intellij/spellchecker/tokenizer/XmlTextTokenizer.java b/plugins/spellchecker/src/com/intellij/spellchecker/tokenizer/XmlTextTokenizer.java index 8a89637d9b39..6b2de76f2d00 100644 --- a/plugins/spellchecker/src/com/intellij/spellchecker/tokenizer/XmlTextTokenizer.java +++ b/plugins/spellchecker/src/com/intellij/spellchecker/tokenizer/XmlTextTokenizer.java @@ -19,11 +19,6 @@ import com.intellij.psi.xml.XmlText; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -/** - * Created by IntelliJ IDEA. - * - * @author shkate@jetbrains.com - */ public class XmlTextTokenizer extends Tokenizer { diff --git a/plugins/spellchecker/src/com/intellij/spellchecker/trie/CharSequenceKeyAnalyzer.java b/plugins/spellchecker/src/com/intellij/spellchecker/trie/CharSequenceKeyAnalyzer.java deleted file mode 100644 index fd08f8843dbd..000000000000 --- a/plugins/spellchecker/src/com/intellij/spellchecker/trie/CharSequenceKeyAnalyzer.java +++ /dev/null @@ -1,147 +0,0 @@ -/* - * Copyright 2005-2009 Roger Kapsi, Sam Berlin - * - * 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.spellchecker.trie; - - -/** - * Analyzes CharSequence keys with case sensitivity. With - * CharSequenceKeyAnalyzer you can - * compare, check prefix, and determine the index of a bit. - *

    - * A typical use case for a CharSequenceKeyAnalyzer is with a - * {@link PatriciaTrie}. - *

    - * PatriciaTrie<String, String> trie = new PatriciaTrie<String, String>(new CharSequenceKeyAnalyzer());
    - * 

    - * trie.put("Lime", "Lime"); - * trie.put("LimeWire", "LimeWire"); - * trie.put("LimeRadio", "LimeRadio"); - * trie.put("Lax", "Lax"); - * trie.put("Lake", "Lake"); - * trie.put("Lovely", "Lovely"); - *

    - * System.out.println(trie.select("Lo")); - * System.out.println(trie.select("Lime")); - *

    - * System.out.println(trie.getPrefixedBy("La").toString()); - *

    - * Output: - * Lovely - * Lime - * {Lake=Lake, Lax=Lax} - *

    - *

    - * - * @author Sam Berlin - * @author Roger Kapsi - */ -public class CharSequenceKeyAnalyzer implements PatriciaTrie.KeyAnalyzer { - - private static final long serialVersionUID = -7032449491269434877L; - - private static final int[] BITS = createIntBitMask(16); - - public static final int[] createIntBitMask(int bitCount) { - int[] bits = new int[bitCount]; - for (int i = 0; i < bitCount; i++) { - bits[i] = 1 << (bitCount - i - 1); - } - return bits; - } - - public int length(CharSequence key) { - return (key != null ? key.length() * 16 : 0); - } - - public int bitIndex(CharSequence key, int keyOff, int keyLength, CharSequence found, int foundOff, int foundKeyLength) { - boolean allNull = true; - - if (keyOff % 16 != 0 || foundOff % 16 != 0 || keyLength % 16 != 0 || foundKeyLength % 16 != 0) { - throw new IllegalArgumentException("offsets & lengths must be at character boundaries"); - } - - int off1 = keyOff / 16; - int off2 = foundOff / 16; - int len1 = keyLength / 16 + off1; - int len2 = foundKeyLength / 16 + off2; - int length = Math.max(len1, len2); - - // Look at each character, and if they're different - // then figure out which bit makes the difference - // and return it. - char k = 0, f = 0; - for (int i = 0; i < length; i++) { - int kOff = i + off1; - int fOff = i + off2; - - if (kOff >= len1) { - k = 0; - } - else { - k = key.charAt(kOff); - } - - if (found == null || fOff >= len2) { - f = 0; - } - else { - f = found.charAt(fOff); - } - - if (k != f) { - int x = k ^ f; - return i * 16 + (Integer.numberOfLeadingZeros(x) - 16); - } - - if (k != 0) allNull = false; - - } - - if (allNull) { - return PatriciaTrie.KeyAnalyzer.NULL_BIT_KEY; - } - - return PatriciaTrie.KeyAnalyzer.EQUAL_BIT_KEY; - } - - public boolean isBitSet(CharSequence key, int keyLength, int bitIndex) { - if (key == null || bitIndex >= keyLength) { - return false; - } - - int index = bitIndex / BITS.length; - int bit = bitIndex - index * BITS.length; - return (key.charAt(index) & BITS[bit]) != 0; - } - - public int compare(CharSequence o1, CharSequence o2) { - return o1.toString().compareTo(o2.toString()); - } - - public int bitsPerElement() { - return 16; - } - - public boolean isPrefix(CharSequence prefix, int offset, int length, CharSequence key) { - if (offset % 16 != 0 || length % 16 != 0) throw new IllegalArgumentException("Cannot determine prefix outside of character boundaries"); - String s1 = prefix.subSequence(offset / 16, length / 16).toString(); - String s2 = key.toString(); - return s2.startsWith(s1); - } -} - diff --git a/plugins/spellchecker/src/com/intellij/spellchecker/trie/PatriciaTrie.java b/plugins/spellchecker/src/com/intellij/spellchecker/trie/PatriciaTrie.java deleted file mode 100644 index 96dc77a8d24e..000000000000 --- a/plugins/spellchecker/src/com/intellij/spellchecker/trie/PatriciaTrie.java +++ /dev/null @@ -1,2376 +0,0 @@ -/* - * Copyright 2005-2009 Roger Kapsi, Sam Berlin - * - * 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.spellchecker.trie; - - -import java.io.Serializable; -import java.util.AbstractCollection; -import java.util.AbstractMap; -import java.util.AbstractSet; -import java.util.Collection; -import java.util.Comparator; -import java.util.ConcurrentModificationException; -import java.util.Iterator; -import java.util.Map; -import java.util.NoSuchElementException; -import java.util.Set; -import java.util.SortedMap; - -/** - * A PATRICIA Trie. - *

    - * PATRICIA = Practical Algorithm to Retrieve Information Coded in Alphanumeric - *

    - * A PATRICIA Trie is a compressed Trie. Instead of storing all data at the - * edges of the Trie (and having empty internal nodes), PATRICIA stores data - * in every node. This allows for very efficient traversal, insert, delete, - * predecessor, successor, prefix, range, and 'select' operations. All operations - * are performed at worst in O(K) time, where K is the number of bits in the - * largest item in the tree. In practice, operations actually take O(A(K)) - * time, where A(K) is the average number of bits of all items in the tree. - *

    - * Most importantly, PATRICIA requires very few comparisons to keys while - * doing any operation. While performing a lookup, each comparison - * (at most K of them, described above) will perform a single bit comparison - * against the given key, instead of comparing the entire key to another key. - *

    - * The Trie can return operations in lexicographical order using the 'traverse', - * 'prefix', 'submap', or 'iterator' methods. The Trie can also scan for items - * that are 'bitwise' (using an XOR metric) by the 'select' method. Bitwise - * closeness is determined by the {@link KeyAnalyzer} returning true or - * false for a bit being set or not in a given key. - *

    - * This PATRICIA Trie supports both variable length & fixed length keys. - * Some methods, such as getPrefixedBy(...) are suited only to - * variable length keys, whereas getPrefixedByBits(...) is suited - * to fixed-size keys. - *

    - * Additionally see PATRICIA - * for more information. - *

    - * Any methods here that take an Object may throw a - * ClassCastException if the method is expecting an instance of K - * (and it isn't K). - *

    - *

    - * PatriciaTrie<String, String> trie = new PatriciaTrie<String, String>
    - * (new CharSequenceKeyAnalyzer());
    - * 

    - * trie.put("Lime", "Lime"); - * trie.put("LimeWire", "LimeWire"); - * trie.put("LimeRadio", "LimeRadio"); - * trie.put("Lax", "Lax"); - * trie.put("Lake", "Lake"); - * trie.put("Lovely", "Lovely"); - *

    - * System.out.println(trie.select("Lo")); - * System.out.println(trie.select("Lime")); - *

    - * System.out.println(trie.getPrefixedBy("La").toString()); - *

    - * Output: - * Lovely - * Lime - * {Lake=Lake, Lax=Lax} - *

    - *

    - * - * @author Roger Kapsi - * @author Sam Berlin - */ -public class PatriciaTrie extends AbstractMap implements Trie, Serializable { - - private static final long serialVersionUID = 110232526181493307L; - - /** - * The root element of the Trie. - */ - private final TrieEntry root = new TrieEntry(null, null, -1); - - /** - * The current size (total number of elements) of the Trie. - */ - private int size = 0; - - /** - * The number of times this has been modified (to fail-fast the iterators). - */ - private transient int modCount = 0; - - /** - * The keyAnalyzer used to analyze bit values of keys. - */ - private final KeyAnalyzer keyAnalyzer; - - /** - * Constructs a new PatriciaTrie using the given keyAnalyzer. - */ - public PatriciaTrie(KeyAnalyzer keyAnalyzer) { - this.keyAnalyzer = keyAnalyzer; - } - - /** - * Returns the KeyAnalyzer that constructed the trie. - */ - public KeyAnalyzer getKeyAnalyzer() { - return keyAnalyzer; - } - - /** - * Returns the KeyAnalyzer as a comparator. - */ - public Comparator comparator() { - return keyAnalyzer; - } - - /** - * Clears the Trie (i.e. removes all elements). - */ - public void clear() { - root.key = null; - root.bitIndex = -1; - root.value = null; - - root.parent = null; - root.left = root; - root.right = null; - root.predecessor = root; - - size = 0; - incrementModCount(); - } - - /** - * Returns true if the Trie is empty - */ - public boolean isEmpty() { - return size == 0; - } - - /** - * Returns the number items in the Trie - */ - public int size() { - return size; - } - - /** - * Increments both the size and mod counter. - */ - private void incrementSize() { - size++; - incrementModCount(); - } - - /** - * Decrements the size and increments the mod counter. - */ - private void decrementSize() { - size--; - incrementModCount(); - } - - /** - * Increments the mod counter. - */ - private void incrementModCount() { - modCount++; - } - - /** - * Adds a new pair to the Trie and if a pair already - * exists it will be replaced. In the latter case it will return - * the old value. - */ - public V put(K key, V value) { - if (key == null) { - throw new NullPointerException("Key cannot be null"); - } - - int keyLength = length(key); - - // The only place to store a key with a length - // of zero bits is the root node - if (keyLength == 0) { - if (root.isEmpty()) { - incrementSize(); - } - else { - incrementModCount(); - } - return root.setKeyValue(key, value); - } - - TrieEntry found = getNearestEntryForKey(key, keyLength); - if (key.equals(found.key)) { - if (found.isEmpty()) { // <- must be the root - incrementSize(); - } - else { - incrementModCount(); - } - return found.setKeyValue(key, value); - } - - int bitIndex = bitIndex(key, found.key); - if (isValidBitIndex(bitIndex)) { // in 99.999...9% the case - /* NEW KEY+VALUE TUPLE */ - TrieEntry t = new TrieEntry(key, value, bitIndex); - addEntry(t, keyLength); - incrementSize(); - return null; - } - else if (isNullBitKey(bitIndex)) { - // A bits of the Key are zero. The only place to - // store such a Key is the root Node! - - /* NULL BIT KEY */ - if (root.isEmpty()) { - incrementSize(); - } - else { - incrementModCount(); - } - return root.setKeyValue(key, value); - - } - else if (isEqualBitKey(bitIndex)) { - // This is a very special and rare case. - - /* REPLACE OLD KEY+VALUE */ - if (found != root) { - incrementModCount(); - return found.setKeyValue(key, value); - } - } - - throw new IndexOutOfBoundsException("Failed to put: " + key + " -> " + value + ", " + bitIndex); - } - - /** - * Adds the given entry into the Trie. - */ - private TrieEntry addEntry(TrieEntry toAdd, int keyLength) { - TrieEntry current = root.left; - TrieEntry path = root; - while (true) { - if (current.bitIndex >= toAdd.bitIndex || current.bitIndex <= path.bitIndex) { - toAdd.predecessor = toAdd; - - if (!isBitSet(toAdd.key, keyLength, toAdd.bitIndex)) { - toAdd.left = toAdd; - toAdd.right = current; - } - else { - toAdd.left = current; - toAdd.right = toAdd; - } - - toAdd.parent = path; - if (current.bitIndex >= toAdd.bitIndex) { - current.parent = toAdd; - } - - // if we inserted an uplink, set the predecessor on it - if (current.bitIndex <= path.bitIndex) { - current.predecessor = toAdd; - } - - if (path == root || !isBitSet(toAdd.key, keyLength, path.bitIndex)) { - path.left = toAdd; - } - else { - path.right = toAdd; - } - return toAdd; - } - - path = current; - if (!isBitSet(toAdd.key, keyLength, current.bitIndex)) { - current = current.left; - } - else { - current = current.right; - } - } - } - - @Override - public Set> entrySet() { - Set> es = entrySet; - return (es != null ? es : (entrySet = new EntrySet())); - } - - /** - * Returns the Value whose Key equals our lookup Key - * or null if no such key exists. - */ - public V get(Object k) { - TrieEntry entry = getEntry(k); - return entry != null ? entry.getValue() : null; - } - - /** - * Returns the entry associated with the specified key in the - * PatriciaTrie. Returns null if the map contains no mapping - * for this key. - *

    - * This may throw ClassCastException if the object is not of type K. - */ - TrieEntry getEntry(Object k) { - K key = asKey(k); - if (key == null) return null; - - int keyLength = length(key); - TrieEntry entry = getNearestEntryForKey(key, keyLength); - return !entry.isEmpty() && key.equals(entry.key) ? entry : null; - } - - /** - * Gets the key as a 'K'. - */ - @SuppressWarnings("unchecked") - protected final K asKey(Object key) { - try { - return (K)key; - } - catch (ClassCastException cce) { - // Because the type is erased, the cast & return are - // actually doing nothing, making this CCE impossible. - // However, it's still here on the off-chance it may - // work. - return null; - } - } - - /** - * Returns the nearest entry for a given key. This is useful - * for finding knowing if a given key exists (and finding the value - * for it), or for inserting the key. - *

    - * The actual get implementation. This is very similar to - * selectR but with the exception that it might return the - * root Entry even if it's empty. - */ - private TrieEntry getNearestEntryForKey(K key, int keyLength) { - TrieEntry current = root.left; - TrieEntry path = root; - while (true) { - if (current.bitIndex <= path.bitIndex) return current; - - path = current; - if (!isBitSet(key, keyLength, current.bitIndex)) { - current = current.left; - } - else { - current = current.right; - } - } - } - - /** - * Returns the Value whose Key has the longest prefix - * in common with our lookup key. - */ - @SuppressWarnings("unchecked") - public V select(K key) { - int keyLength = length(key); - TrieEntry[] result = new TrieEntry[1]; - if (!selectR(root.left, -1, key, keyLength, result)) { - TrieEntry e = result[0]; - return e.getValue(); - } - return null; - } - - /** - * This is equivalent to the other selectR() method but without - * its overhead because we're selecting only one best matching - * Entry from the Trie. - */ - private boolean selectR(TrieEntry h, int bitIndex, final K key, final int keyLength, final TrieEntry[] result) { - - if (h.bitIndex <= bitIndex) { - // If we hit the root Node and it is empty - // we have to look for an alternative best - // matching node. - if (!h.isEmpty()) { - result[0] = h; - return false; - } - return true; - } - - if (!isBitSet(key, keyLength, h.bitIndex)) { - if (selectR(h.left, h.bitIndex, key, keyLength, result)) { - return selectR(h.right, h.bitIndex, key, keyLength, result); - } - } - else { - if (selectR(h.right, h.bitIndex, key, keyLength, result)) { - return selectR(h.left, h.bitIndex, key, keyLength, result); - } - } - return false; - } - - @SuppressWarnings("unchecked") - public Map.Entry select(K key, Cursor cursor) { - int keyLength = length(key); - TrieEntry[] result = new TrieEntry[]{null}; - selectR(root.left, -1, key, keyLength, cursor, result); - return result[0]; - } - - private boolean selectR(TrieEntry h, - int bitIndex, - final K key, - final int keyLength, - final Cursor cursor, - final TrieEntry[] result) { - - if (h.bitIndex <= bitIndex) { - if (!h.isEmpty()) { - Cursor.SelectStatus ret = cursor.select(h); - switch (ret) { - case REMOVE: - throw new UnsupportedOperationException("cannot remove during select"); - case EXIT: - result[0] = h; - return false; // exit - case REMOVE_AND_EXIT: - TrieEntry entry = new TrieEntry(h.getKey(), h.getValue(), -1); - result[0] = entry; - removeEntry(h); - return false; - case CONTINUE: - // fall through. - } - } - return true; // continue - } - - if (!isBitSet(key, keyLength, h.bitIndex)) { - if (selectR(h.left, h.bitIndex, key, keyLength, cursor, result)) { - return selectR(h.right, h.bitIndex, key, keyLength, cursor, result); - } - } - else { - if (selectR(h.right, h.bitIndex, key, keyLength, cursor, result)) { - return selectR(h.left, h.bitIndex, key, keyLength, cursor, result); - } - } - - return false; - } - - /** - * Returns a view of this Trie of all elements that are - * prefixed by the given key. - *

    - * In a fixed-keysize Trie, this is essentially a 'get' operation. - *

    - * For example, if the trie contains 'Lime', 'LimeWire', - * 'LimeRadio', 'Lax', 'Later', 'Lake', and 'Lovely', then - * a lookup of 'Lime' would return 'Lime', 'LimeRadio', and 'LimeWire'. - *

    - * The view that this returns is optimized to have a very efficient - * Iterator. The firstKey, lastKey & size methods must iterate - * over all possible values in order to determine the results. This - * information is cached until the Patricia tree changes. All other - * methods (except Iterator) must compare the given key to the prefix - * to ensure that it is within the range of the view. The Iterator's - * remove method must also relocate the subtree that contains the - * prefixes if the entry holding the subtree is removed or changes. - * Changing the subtree takes O(K) time. - * - * @param key - */ - public SortedMap getPrefixedBy(K key) { - return getPrefixedByBits(key, 0, keyAnalyzer.length(key)); - } - - /** - * Returns a view of this Trie of all elements that are - * prefixed by the length of the key. - *

    - * Fixed-keysize Tries will not support this operation - * (because all keys will be the same length). - *

    - * For example, if the trie contains 'Lime', 'LimeWire', - * 'LimeRadio', 'Lax', 'Later', 'Lake', and 'Lovely', then - * a lookup of 'LimePlastics' with a length of 4 would - * return 'Lime', 'LimeRadio', and 'LimeWire'. - *

    - * The view that this returns is optimized to have a very efficient - * Iterator. The firstKey, lastKey & size methods must iterate - * over all possible values in order to determine the results. This - * information is cached until the Patricia tree changes. All other - * methods (except Iterator) must compare the given key to the prefix - * to ensure that it is within the range of the view. The Iterator's - * remove method must also relocate the subtree that contains the - * prefixes if the entry holding the subtree is removed or changes. - * Changing the subtree takes O(K) time. - * - * @param key - * @param length - */ - public SortedMap getPrefixedBy(K key, int length) { - return getPrefixedByBits(key, 0, length * keyAnalyzer.bitsPerElement()); - } - - /** - * Returns a view of this Trie of all elements that are prefixed - * by the key, starting at the given offset and for the given length. - *

    - * Fixed-keysize Tries will not support this operation - * (because all keys are the same length). - *

    - * For example, if the trie contains 'Lime', 'LimeWire', - * 'LimeRadio', 'Lax', 'Later', 'Lake', and 'Lovely', then - * a lookup of 'The Lime Plastics' with an offset of 4 and a - * length of 4 would return 'Lime', 'LimeRadio', and 'LimeWire'. - *

    - * The view that this returns is optimized to have a very efficient - * Iterator. The firstKey, lastKey & size methods must iterate - * over all possible values in order to determine the results. This - * information is cached until the Patricia tree changes. All other - * methods (except Iterator) must compare the given key to the prefix - * to ensure that it is within the range of the view. The Iterator's - * remove method must also relocate the subtree that contains the - * prefixes if the entry holding the subtree is removed or changes. - * Changing the subtree takes O(K) time. - * - * @param key - * @param offset - * @param length - */ - public SortedMap getPrefixedBy(K key, int offset, int length) { - return getPrefixedByBits(key, offset * keyAnalyzer.bitsPerElement(), length * keyAnalyzer.bitsPerElement()); - } - - /** - * Returns a view of this Trie of all elements that are prefixed - * by the number of bits in the given Key. - *

    - * Fixed-keysize Tries can support this operation as a way to do - * lookups of partial keys. That is, if the Trie is storing IP - * addresses, you can lookup all addresses that begin with - * '192.168' by providing the key '192.168.X.X' and a length of 16 - * would return all addresses that begin with '192.168'. - *

    - * The view that this returns is optimized to have a very efficient - * Iterator. The firstKey, lastKey & size methods must iterate - * over all possible values in order to determine the results. This - * information is cached until the Patricia tree changes. All other - * methods (except Iterator) must compare the given key to the prefix - * to ensure that it is within the range of the view. The Iterator's - * remove method must also relocate the subtree that contains the - * prefixes if the entry holding the subtree is removed or changes. - * Changing the subtree takes O(K) time. - * - * @param key - * @param bitLength - */ - public SortedMap getPrefixedByBits(K key, int bitLength) { - return getPrefixedByBits(key, 0, bitLength); - } - - /** - * Returns a view of this map, with entries containing only those that - * are prefixed by a value whose bits matches the bits between 'offset' - * and 'length' in the given key. - *

    - * The view that this returns is optimized to have a very efficient - * Iterator. The firstKey, lastKey & size methods must iterate - * over all possible values in order to determine the results. This - * information is cached until the Patricia tree changes. All other - * methods (except Iterator) must compare the given key to the prefix - * to ensure that it is within the range of the view. The Iterator's - * remove method must also relocate the subtree that contains the - * prefixes if the entry holding the subtree is removed or changes. - * Changing the subtree takes O(K) time. - * - * @param key - * @param offset - * @param length - */ - private SortedMap getPrefixedByBits(K key, int offset, int length) { - int offsetLength = offset + length; - if (offsetLength > length(key)) { - throw new IllegalArgumentException(offset + " + " + length + " > " + length(key)); - } - - if (offsetLength == 0) return this; - - return new PrefixSubMap(key, offset, length); - } - - /** - * Returns true if this trie contains the specified Key - *

    - * This may throw ClassCastException if the object is not - * of type K. - */ - public boolean containsKey(Object k) { - K key = asKey(k); - if (key == null) return false; - - int keyLength = length(key); - TrieEntry entry = getNearestEntryForKey(key, keyLength); - return !entry.isEmpty() && key.equals(entry.key); - } - - /** - * Returns true if this Trie contains the specified value. - */ - public boolean containsValue(Object o) { - for (V v : values()) { - if (valEquals(v, o)) return true; - } - return false; - } - - - /** - * Removes a Key from the Trie if one exists - *

    - * This may throw ClassCastException if the object is not of type K. - * - * @param k the Key to delete - * @return Returns the deleted Value - */ - public V remove(Object k) { - K key = asKey(k); - if (key == null) return null; - - int keyLength = length(key); - TrieEntry current = root.left; - TrieEntry path = root; - while (true) { - if (current.bitIndex <= path.bitIndex) { - if (!current.isEmpty() && key.equals(current.key)) { - return removeEntry(current); - } - else { - return null; - } - } - - path = current; - if (!isBitSet(key, keyLength, current.bitIndex)) { - current = current.left; - } - else { - current = current.right; - } - } - } - - /** - * Removes a single entry from the Trie. - *

    - * If we found a Key (Entry h) then figure out if it's - * an internal (hard to remove) or external Entry (easy - * to remove) - */ - private V removeEntry(TrieEntry h) { - if (h != root) { - if (h.isInternalNode()) { - removeInternalEntry(h); - } - else { - removeExternalEntry(h); - } - } - - decrementSize(); - return h.setKeyValue(null, null); - } - - /** - * Removes an external entry from the Trie. - *

    - * If it's an external Entry then just remove it. - * This is very easy and straight forward. - */ - private void removeExternalEntry(TrieEntry h) { - if (h == root) { - throw new IllegalArgumentException("Cannot delete root Entry!"); - } - else if (!h.isExternalNode()) { - throw new IllegalArgumentException(h + " is not an external Entry!"); - } - - TrieEntry parent = h.parent; - TrieEntry child = (h.left == h) ? h.right : h.left; - - if (parent.left == h) { - parent.left = child; - } - else { - parent.right = child; - } - - // either the parent is changing, or the predecessor is changing. - if (child.bitIndex > parent.bitIndex) { - child.parent = parent; - } - else { - child.predecessor = parent; - } - - } - - /** - * Removes an internal entry from the Trie. - *

    - * If it's an internal Entry then "good luck" with understanding - * this code. The Idea is essentially that Entry p takes Entry h's - * place in the trie which requires some re-wiring. - */ - private void removeInternalEntry(TrieEntry h) { - if (h == root) { - throw new IllegalArgumentException("Cannot delete root Entry!"); - } - else if (!h.isInternalNode()) { - throw new IllegalArgumentException(h + " is not an internal Entry!"); - } - - TrieEntry p = h.predecessor; - - // Set P's bitIndex - p.bitIndex = h.bitIndex; - - // Fix P's parent, predecessor and child Nodes - { - TrieEntry parent = p.parent; - TrieEntry child = (p.left == h) ? p.right : p.left; - - // if it was looping to itself previously, - // it will now be pointed from it's parent - // (if we aren't removing it's parent -- - // in that case, it remains looping to itself). - // otherwise, it will continue to have the same - // predecessor. - if (p.predecessor == p && p.parent != h) p.predecessor = p.parent; - - if (parent.left == p) { - parent.left = child; - } - else { - parent.right = child; - } - - if (child.bitIndex > parent.bitIndex) { - child.parent = parent; - } - } - ; - - // Fix H's parent and child Nodes - { - // If H is a parent of its left and right child - // then change them to P - if (h.left.parent == h) { - h.left.parent = p; - } - - if (h.right.parent == h) { - h.right.parent = p; - } - - // Change H's parent - if (h.parent.left == h) { - h.parent.left = p; - } - else { - h.parent.right = p; - } - } - ; - - // Copy the remaining fields from H to P - //p.bitIndex = h.bitIndex; - p.parent = h.parent; - p.left = h.left; - p.right = h.right; - - // Make sure that if h was pointing to any uplinks, - // p now points to them. - if (isValidUplink(p.left, p)) p.left.predecessor = p; - if (isValidUplink(p.right, p)) p.right.predecessor = p; - - } - - /** - * Returns the node lexicographically before the given node (or null if none). - *

    - * This follows four simple branches: - * - If the uplink that returned us was a right uplink: - * - If predecessor's left is a valid uplink from predecessor, return it. - * - Else, follow the right path from the predecessor's left. - * - If the uplink that returned us was a left uplink: - * - Loop back through parents until we encounter a node where - * node != node.parent.left. - * - If node.parent.left is uplink from node.parent: - * - If node.parent.left is not root, return it. - * - If it is root & root isEmpty, return null. - * - If it is root & root !isEmpty, return root. - * - If node.parent.left is not uplink from node.parent: - * - Follow right path for first right child from node.parent.left - * - * @param start - */ - private TrieEntry previousEntry(TrieEntry start) { - if (start.predecessor == null) throw new IllegalArgumentException("must have come from somewhere!"); - - if (start.predecessor.right == start) { - if (isValidUplink(start.predecessor.left, start.predecessor)) { - return start.predecessor.left; - } - else { - return followRight(start.predecessor.left); - } - } - else { - TrieEntry node = start.predecessor; - while (node.parent != null && node == node.parent.left) node = node.parent; - if (node.parent == null) // can be null if we're looking up root. - { - return null; - } - if (isValidUplink(node.parent.left, node.parent)) { - if (node.parent.left == root) { - if (root.isEmpty()) { - return null; - } - else { - return root; - } - } - else { - return node.parent.left; - } - } - else { - return followRight(node.parent.left); - } - } - } - - /** - * Returns the entry lexicographically after the given entry. - * If the given entry is null, returns the first node. - */ - private TrieEntry nextEntry(TrieEntry node) { - if (node == null) { - return firstEntry(); - } - else { - return nextEntryImpl(node.predecessor, node, null); - } - } - - /** - * Returns the entry lexicographically after the given entry. - * If the given entry is null, returns the first node. - *

    - * This will traverse only within the subtree. If the given node - * is not within the subtree, this will have undefined results. - */ - private TrieEntry nextEntryInSubtree(TrieEntry node, TrieEntry parentOfSubtree) { - if (node == null) { - return firstEntry(); - } - else { - return nextEntryImpl(node.predecessor, node, parentOfSubtree); - } - } - - /** - * Scans for the next node, starting at the specified point, and using 'previous' - * as a hint that the last node we returned was 'previous' (so we know not to return - * it again). If 'tree' is non-null, this will limit the search to the given tree. - *

    - * The basic premise is that each iteration can follow the following steps: - *

    - * 1) Scan all the way to the left. - * a) If we already started from this node last time, proceed to Step 2. - * b) If a valid uplink is found, use it. - * c) If the result is an empty node (root not set), break the scan. - * d) If we already returned the left node, break the scan. - *

    - * 2) Check the right. - * a) If we already returned the right node, proceed to Step 3. - * b) If it is a valid uplink, use it. - * c) Do Step 1 from the right node. - *

    - * 3) Back up through the parents until we encounter find a parent - * that we're not the right child of. - *

    - * 4) If there's no right child of that parent, the iteration is finished. - * Otherwise continue to Step 5. - *

    - * 5) Check to see if the right child is a valid uplink. - * a) If we already returned that child, proceed to Step 6. - * Otherwise, use it. - *

    - * 6) If the right child of the parent is the parent itself, we've - * already found & returned the end of the Trie, so exit. - *

    - * 7) Do Step 1 on the parent's right child. - */ - private TrieEntry nextEntryImpl(TrieEntry start, TrieEntry previous, TrieEntry tree) { - TrieEntry current = start; - - // Only look at the left if this was a recursive or - // the first check, otherwise we know we've already looked - // at the left. - if (previous == null || start != previous.predecessor) { - while (!current.left.isEmpty()) { - // stop traversing if we've already - // returned the left of this node. - if (previous == current.left) { - break; - } - - if (isValidUplink(current.left, current)) { - return current.left; - } - - current = current.left; - } - } - - // If there's no data at all, exit. - if (current.isEmpty()) { - return null; - } - - // If we've already returned the left, - // and the immediate right is null, - // there's only one entry in the Trie - // which is stored at the root. - // - // / ("") <-- root - // \_/ \ - // null <-- 'current' - // - if (current.right == null) return null; - - // If nothing valid on the left, try the right. - if (previous != current.right) { - // See if it immediately is valid. - if (isValidUplink(current.right, current)) { - return current.right; - } - - // Must search on the right's side if it wasn't initially valid. - return nextEntryImpl(current.right, previous, tree); - } - - // Neither left nor right are valid, find the first parent - // whose child did not come from the right & traverse it. - while (current == current.parent.right) { - // If we're going to traverse to above the subtree, stop. - if (current == tree) return null; - - current = current.parent; - } - - // If we're on the top of the subtree, we can't go any higher. - if (current == tree) return null; - - - // If there's no right, the parent must be root, so we're done. - if (current.parent.right == null) { - return null; - } - - - // If the parent's right points to itself, we've found one. - if (previous != current.parent.right && isValidUplink(current.parent.right, current.parent)) { - return current.parent.right; - } - - // If the parent's right is itself, there can't be any more nodes. - if (current.parent.right == current.parent) { - return null; - } - - // We need to traverse down the parent's right's path. - return nextEntryImpl(current.parent.right, previous, tree); - } - - /** - * Returns each entry as a string. - */ - public String toString() { - StringBuilder buffer = new StringBuilder(); - buffer.append("Trie[").append(size()).append("]={\n"); - for (Iterator> i = newEntryIterator(); i.hasNext();) { - buffer.append(" ").append(i.next().toString()).append("\n"); - } - buffer.append("}\n"); - return buffer.toString(); - } - - public Map.Entry traverse(Cursor cursor) { - TrieEntry entry = nextEntry(null); - while (entry != null) { - TrieEntry current = entry; - Cursor.SelectStatus ret = cursor.select(current); - entry = nextEntry(current); - switch (ret) { - case EXIT: - return current; - case REMOVE: - removeEntry(current); - break; // out of switch, stay in while loop - case REMOVE_AND_EXIT: - Map.Entry value = new TrieEntry(current.getKey(), current.getValue(), -1); - removeEntry(current); - return value; - case CONTINUE: // do nothing. - } - } - - return null; - } - - /** - * Returns true if 'next' is a valid uplink coming from 'from'. - */ - private boolean isValidUplink(TrieEntry next, TrieEntry from) { - return next != null && next.bitIndex <= from.bitIndex && !next.isEmpty(); - } - - /** - * Returns true if bitIndex is a valid index - */ - private static boolean isValidBitIndex(int bitIndex) { - return 0 <= bitIndex && bitIndex <= Integer.MAX_VALUE; - } - - /** - * Returns true if bitIndex is a NULL_BIT_KEY - */ - private static boolean isNullBitKey(int bitIndex) { - return bitIndex == KeyAnalyzer.NULL_BIT_KEY; - } - - /** - * Returns true if bitIndex is a EQUAL_BIT_KEY - */ - private static boolean isEqualBitKey(int bitIndex) { - return bitIndex == KeyAnalyzer.EQUAL_BIT_KEY; - } - - /** - * Returns the length of the key, or 0 if the key is null. - */ - private int length(K key) { - if (key == null) { - return 0; - } - - return keyAnalyzer.length(key); - } - - /** - * Returns whether or not the given bit on the - * key is set, or false if the key is null - */ - private boolean isBitSet(K key, int keyLength, int bitIndex) { - if (key == null) { // root's might be null! - return false; - } - return keyAnalyzer.isBitSet(key, keyLength, bitIndex); - } - - /** - * Utility method for calling - * keyAnalyzer.bitIndex(key, 0, length(key), foundKey, 0, length(foundKey)) - */ - private int bitIndex(K key, K foundKey) { - return keyAnalyzer.bitIndex(key, 0, length(key), foundKey, 0, length(foundKey)); - } - - /** - * The actual Trie nodes. - */ - private static class TrieEntry implements Map.Entry, Serializable { - - private static final long serialVersionUID = 4596023148184140013L; - - private K key; - private V value; - - /** - * The index this entry is comparing. - */ - private int bitIndex; - - /** - * The parent of this entry. - */ - private TrieEntry parent; - /** - * The left child of this entry. - */ - private TrieEntry left; - /** - * The right child of this entry. - */ - private TrieEntry right; - /** - * The entry who uplinks to this entry. - */ - private TrieEntry predecessor; - - private TrieEntry(K key, V value, int bitIndex) { - this.key = key; - this.value = value; - - this.bitIndex = bitIndex; - - this.parent = null; - this.left = this; - this.right = null; - this.predecessor = this; - } - - public boolean equals(Object o) { - if (o == this) { - return true; - } - else if (o instanceof Map.Entry) { - Map.Entry e = (Map.Entry)o; - Object k1 = getKey(); - Object k2 = e.getKey(); - if (k1 == k2 || (k1 != null && k1.equals(k2))) { - Object v1 = getValue(); - Object v2 = e.getValue(); - if (v1 == v2 || (v1 != null && v1.equals(v2))) return true; - } - return false; - } - else { - return false; - } - } - - /** - * Whether or not the entry is storing a key. - * Only the root can potentially be empty, all other - * nodes must have a key. - */ - public boolean isEmpty() { - return key == null; - } - - public K getKey() { - return key; - } - - public V getValue() { - return value; - } - - public V setValue(V value) { - V o = this.value; - this.value = value; - return o; - } - - /** - * Replaces the old key and value with the new ones. - * Returns the old vlaue. - */ - private V setKeyValue(K key, V value) { - this.key = key; - return setValue(value); - } - - /** - * Neither the left nor right child is a loopback - */ - private boolean isInternalNode() { - return left != this && right != this; - } - - /** - * Either the left or right child is a loopback - */ - private boolean isExternalNode() { - return !isInternalNode(); - } - - public String toString() { - StringBuilder buffer = new StringBuilder(); - - if (bitIndex == -1) { - buffer.append("RootEntry("); - } - else { - buffer.append("Entry("); - } - - buffer.append("key=").append(getKey()).append(" [").append(bitIndex).append("], "); - buffer.append("value=").append(getValue()).append(", "); - //buffer.append("bitIndex=").append(bitIndex).append(", "); - - if (parent != null) { - if (parent.bitIndex == -1) { - buffer.append("parent=").append("ROOT"); - } - else { - buffer.append("parent=").append(parent.getKey()).append(" [").append(parent.bitIndex).append("]"); - } - } - else { - buffer.append("parent=").append("null"); - } - buffer.append(", "); - - if (left != null) { - if (left.bitIndex == -1) { - buffer.append("left=").append("ROOT"); - } - else { - buffer.append("left=").append(left.getKey()).append(" [").append(left.bitIndex).append("]"); - } - } - else { - buffer.append("left=").append("null"); - } - buffer.append(", "); - - if (right != null) { - if (right.bitIndex == -1) { - buffer.append("right=").append("ROOT"); - } - else { - buffer.append("right=").append(right.getKey()).append(" [").append(right.bitIndex).append("]"); - } - } - else { - buffer.append("right=").append("null"); - } - buffer.append(", "); - - if (predecessor != null) { - if (predecessor.bitIndex == -1) { - buffer.append("predecessor=").append("ROOT"); - } - else { - buffer.append("predecessor=").append(predecessor.getKey()).append(" [").append(predecessor.bitIndex).append("]"); - } - } - - buffer.append(")"); - return buffer.toString(); - } - } - - /** - * Defines the interface to analyze {@link Trie} keys on a bit - * level. KeyAnalyzer's - * methods return the length of the key in bits, whether or not a bit is - * set, and bits per element in the key. - *

    - * Additionally, a method determines if a key is a prefix of another key and - * returns the bit index where one key is different from another key (if - * the key and found key are equal than the return value is EQUAL_BIT_KEY). - *

    - * KeyAnalyzer defines:
    - * - * - * - *
    NULL_BIT_KEYWhen key's bits are all zero
    EQUAL_BIT_KEY When keys are the same
    - */ - public static interface KeyAnalyzer extends Comparator, Serializable { - - /** - * Returned by bitIndex if key's bits are all 0 - */ - public static final int NULL_BIT_KEY = -1; - - /** - * Returned by bitIndex if key and found key are - * equal. This is a very very specific case and - * shouldn't happen on a regular basis - */ - public static final int EQUAL_BIT_KEY = -2; - - /** - * Returns the length of the Key in bits. - */ - public int length(K key); - - /** - * Returns whether or not a bit is set - */ - public boolean isBitSet(K key, int keyLength, int bitIndex); - - /** - * Returns the n-th different bit between key and found. - * This starts the comparison in key at 'keyStart' and goes - * for 'keyLength' bits, and compares to the found key - * starting at 'foundStart' and going for 'foundLength' bits. - */ - public int bitIndex(K key, int keyStart, int keyLength, K found, int foundStart, int foundLength); - - /** - * Returns the number of bits per element in the key. - * This is only useful for variable-length keys, such as Strings. - */ - public int bitsPerElement(); - - /** - * Determines whether or not the given prefix (from offset to length) - * is a prefix of the given key. - */ - public boolean isPrefix(K prefix, int offset, int length, K key); - } - - /** - * An iterator that stores a single TrieEntry. - */ - private class SingletonIterator implements Iterator> { - private final TrieEntry entry; - private int hit = 0; - - public SingletonIterator(TrieEntry entry) { - this.entry = entry; - } - - public boolean hasNext() { - return hit == 0; - } - - public Map.Entry next() { - if (hit != 0) throw new NoSuchElementException(); - hit++; - return entry; - } - - public void remove() { - if (hit != 1) throw new IllegalStateException(); - hit++; - PatriciaTrie.this.removeEntry(entry); - } - - } - - /** - * An iterator for the entries. - */ - private abstract class NodeIterator implements Iterator { - protected int expectedModCount = modCount; // For fast-fail - protected TrieEntry next; // the next node to return - protected TrieEntry current; // the current entry we're on - - // Starts iteration from the beginning. - protected NodeIterator() { - next = PatriciaTrie.this.nextEntry(null); - } - - // Starts iteration at the given entry. - protected NodeIterator(TrieEntry firstEntry) { - next = firstEntry; - } - - public boolean hasNext() { - return next != null; - } - - TrieEntry nextEntry() { - if (modCount != expectedModCount) throw new ConcurrentModificationException(); - TrieEntry e = next; - if (e == null) throw new NoSuchElementException(); - - next = findNext(e); - current = e; - return e; - } - - protected TrieEntry findNext(TrieEntry prior) { - return PatriciaTrie.this.nextEntry(prior); - } - - public void remove() { - if (current == null) throw new IllegalStateException(); - if (modCount != expectedModCount) throw new ConcurrentModificationException(); - - TrieEntry node = current; - current = null; - PatriciaTrie.this.removeEntry(node); - - expectedModCount = modCount; - } - } - - private class ValueIterator extends NodeIterator { - public V next() { - return nextEntry().value; - } - } - - private class KeyIterator extends NodeIterator { - public K next() { - return nextEntry().getKey(); - } - } - - private class EntryIterator extends NodeIterator> { - public Map.Entry next() { - return nextEntry(); - } - } - - /** - * An iterator for iterating over a prefix search. - */ - private class PrefixEntryIterator extends NodeIterator> { - // values to reset the subtree if we remove it. - protected final K prefix; - protected final int offset; - protected final int length; - protected boolean lastOne; - - protected TrieEntry subtree; // the subtree to search within - - // Starts iteration at the given entry & search only within the given subtree. - PrefixEntryIterator(TrieEntry startScan, K prefix, int offset, int length) { - subtree = startScan; - next = PatriciaTrie.this.followLeft(startScan); - this.prefix = prefix; - this.offset = offset; - this.length = length; - } - - public Map.Entry next() { - Map.Entry entry = nextEntry(); - if (lastOne) next = null; - return entry; - } - - @Override - protected TrieEntry findNext(TrieEntry prior) { - return PatriciaTrie.this.nextEntryInSubtree(prior, subtree); - } - - @Override - public void remove() { - // If the current entry we're removing is the subtree - // then we need to find a new subtree parent. - boolean needsFixing = false; - int bitIdx = subtree.bitIndex; - if (current == subtree) needsFixing = true; - - super.remove(); - - // If the subtree changed its bitIndex or we - // removed the old subtree, get a new one. - if (bitIdx != subtree.bitIndex || needsFixing) subtree = subtree(prefix, offset, length); - - // If the subtree's bitIndex is less than the - // length of our prefix, it's the last item - // in the prefix tree. - if (length >= subtree.bitIndex) lastOne = true; - } - - } - - /** - * An iterator for submaps. - */ - private class SubMapEntryIterator extends NodeIterator> { - private final K firstExcludedKey; - - SubMapEntryIterator(TrieEntry first, TrieEntry firstExcluded) { - super(first); - firstExcludedKey = (firstExcluded == null ? null : firstExcluded.key); - } - - public boolean hasNext() { - return next != null && next.key != firstExcludedKey; - } - - public Map.Entry next() { - if (next == null || next.key == firstExcludedKey) throw new NoSuchElementException(); - return nextEntry(); - } - } - - Iterator newKeyIterator() { - return new KeyIterator(); - } - - Iterator newValueIterator() { - return new ValueIterator(); - } - - Iterator> newEntryIterator() { - return new EntryIterator(); - } - - /** - * Each of these fields are initialized to contain an instance of the - * appropriate view the first time this view is requested. The views are - * stateless, so there's no reason to create more than one of each. - */ - private transient volatile Set keySet = null; - private transient volatile Collection values = null; - private transient volatile Set> entrySet = null; - - private class EntrySet extends AbstractSet> { - public Iterator> iterator() { - return newEntryIterator(); - } - - public boolean contains(Object o) { - if (!(o instanceof Map.Entry)) return false; - - TrieEntry candidate = getEntry(((Map.Entry)o).getKey()); - return candidate != null && candidate.equals(o); - } - - public boolean remove(Object o) { - int size = size(); - PatriciaTrie.this.remove(o); - return size != size(); - } - - public int size() { - return size; - } - - public void clear() { - PatriciaTrie.this.clear(); - } - } - - /** - * Returns a set view of the keys contained in this map. The set is - * backed by the map, so changes to the map are reflected in the set, and - * vice-versa. The set supports element removal, which removes the - * corresponding mapping from this map, via the Iterator.remove, - * Set.remove, removeAll, retainAll, and - * clear operations. It does not support the add or - * addAll operations. - * - * @return a set view of the keys contained in this map. - */ - public Set keySet() { - Set ks = keySet; - return (ks != null ? ks : (keySet = new KeySet())); - } - - private class KeySet extends AbstractSet { - public Iterator iterator() { - return newKeyIterator(); - } - - public int size() { - return size; - } - - public boolean contains(Object o) { - return containsKey(o); - } - - public boolean remove(Object o) { - int size = size(); - PatriciaTrie.this.remove(o); - return size != size(); - } - - public void clear() { - PatriciaTrie.this.clear(); - } - } - - /** - * Returns a collection view of the values contained in this map. The - * collection is backed by the map, so changes to the map are reflected in - * the collection, and vice-versa. The collection supports element - * removal, which removes the corresponding mapping from this map, via the - * Iterator.remove, Collection.remove, - * removeAll, retainAll, and clear operations. - * It does not support the add or addAll operations. - * - * @return a collection view of the values contained in this map. - */ - public Collection values() { - Collection vs = values; - return (vs != null ? vs : (values = new Values())); - } - - /** - * Test two values for equality. Works with null values. - */ - private static boolean valEquals(Object o1, Object o2) { - return (o1 == null ? o2 == null : o1.equals(o2)); - } - - private class Values extends AbstractCollection { - public Iterator iterator() { - return newValueIterator(); - } - - public int size() { - return size; - } - - public boolean contains(Object o) { - return containsValue(o); - } - - public void clear() { - PatriciaTrie.this.clear(); - } - - public boolean remove(Object o) { - for (Iterator i = iterator(); i.hasNext();) { - V v = i.next(); - if (valEquals(v, o)) { - i.remove(); - return true; - } - } - return false; - } - } - - /** - * Returns the first entry the Trie is storing. - *

    - * This is implemented by going always to the left until - * we encounter a valid uplink. That uplink is the first key. - */ - private TrieEntry firstEntry() { - // if Trie is empty, no first node. - if (isEmpty()) return null; - - return followLeft(root); - } - - /** - * Goes left through the tree until it finds a valid node. - */ - private TrieEntry followLeft(TrieEntry node) { - while (true) { - TrieEntry child = node.left; - // if we hit root and it didn't have a node, go right instead. - if (child.isEmpty()) child = node.right; - - if (child.bitIndex <= node.bitIndex) return child; - - node = child; - } - } - - /** - * Returns the last entry the Trie is storing. - *

    - * This is implemented by going always to the right until - * we encounter a valid uplink. That uplink is the last key. - */ - private TrieEntry lastEntry() { - return followRight(root.left); - } - - /** - * Traverses down the right path until it finds an uplink. - */ - protected TrieEntry followRight(TrieEntry node) { - // if Trie is empty, no last entry. - if (node.right == null) return null; - - // Go as far right as possible, until we encounter an uplink. - while (node.right.bitIndex > node.bitIndex) node = node.right; - - return node.right; - } - - public K firstKey() { - return firstEntry().getKey(); - } - - public SortedMap headMap(K toKey) { - return new SubMap(null, toKey); - } - - public K lastKey() { - TrieEntry entry = lastEntry(); - if (entry != null) { - return entry.getKey(); - } - else { - return null; - } - } - - - public SortedMap subMap(K fromKey, K toKey) { - return new SubMap(fromKey, toKey); - } - - public SortedMap tailMap(K fromKey) { - return new SubMap(fromKey, null); - } - - /** - * Returns an entry strictly higher than the given key, - * or null if no such entry exists. - */ - protected TrieEntry higherEntry(K key) { - // TODO: Cleanup so that we don't actually have to add/remove from the - // tree. (We do it here because there are other well-defined - // functions to perform the search.) - int keyLength = length(key); - - if (keyLength == 0) { - if (!root.isEmpty()) { - // If data in root, and more after -- return it. - if (size() > 1) { - return nextEntry(root); - } - else { // If no more after, no higher entry. - return null; - } - } - else { - // Root is empty & we want something after empty, return first. - return firstEntry(); - } - } - - TrieEntry found = getNearestEntryForKey(key, keyLength); - if (key.equals(found.key)) return nextEntry(found); - - int bitIndex = bitIndex(key, found.key); - if (isValidBitIndex(bitIndex)) { - TrieEntry added = new TrieEntry(key, null, bitIndex); - addEntry(added, keyLength); - incrementSize(); // must increment because remove will decrement - TrieEntry ceil = nextEntry(added); - removeEntry(added); - modCount -= 2; // we didn't really modify it. - return ceil; - } - else if (isNullBitKey(bitIndex)) { - if (!root.isEmpty()) { - return firstEntry(); - } - else if (size() > 1) { - return nextEntry(firstEntry()); - } - else { - return null; - } - } - else if (isEqualBitKey(bitIndex)) { - return nextEntry(found); - } - - // we should have exited above. - throw new IllegalStateException("invalid lookup: " + key); - } - - /** - * Returns a key-value mapping associated with the least key greater - * than or equal to the given key, or null if there is no such key. - */ - protected TrieEntry ceilingEntry(K key) { - // Basically: - // Follow the steps of adding an entry, but instead... - // - // - If we ever encounter a situation where we found an equal - // key, we return it immediately. - // - // - If we hit an empty root, return the first iterable item. - // - // - If we have to add a new item, we temporarily add it, - // find the successor to it, then remove the added item. - // - // These steps ensure that the returned value is either the - // entry for the key itself, or the first entry directly after - // the key. - - // TODO: Cleanup so that we don't actually have to add/remove from the - // tree. (We do it here because there are other well-defined - // functions to perform the search.) - int keyLength = length(key); - - if (keyLength == 0) { - if (!root.isEmpty()) { - return root; - } - else { - return firstEntry(); - } - } - - TrieEntry found = getNearestEntryForKey(key, keyLength); - if (key.equals(found.key)) return found; - - int bitIndex = bitIndex(key, found.key); - if (isValidBitIndex(bitIndex)) { - TrieEntry added = new TrieEntry(key, null, bitIndex); - addEntry(added, keyLength); - incrementSize(); // must increment because remove will decrement - TrieEntry ceil = nextEntry(added); - removeEntry(added); - modCount -= 2; // we didn't really modify it. - return ceil; - } - else if (isNullBitKey(bitIndex)) { - if (!root.isEmpty()) { - return root; - } - else { - return firstEntry(); - } - } - else if (isEqualBitKey(bitIndex)) { - return found; - } - - // we should have exited above. - throw new IllegalStateException("invalid lookup: " + key); - } - - /** - * Returns a key-value mapping associated with the greatest key - * strictly less than the given key, or null if there is no such key. - */ - protected TrieEntry lowerEntry(K key) { - // Basically: - // Follow the steps of adding an entry, but instead... - // - // - If we ever encounter a situation where we found an equal - // key, we return it's previousEntry immediately. - // - // - If we hit root (empty or not), return null. - // - // - If we have to add a new item, we temporarily add it, - // find the previousEntry to it, then remove the added item. - // - // These steps ensure that the returned value is always just before - // the key or null (if there was nothing before it). - - // TODO: Cleanup so that we don't actually have to add/remove from the - // tree. (We do it here because there are other well-defined - // functions to perform the search.) - int keyLength = length(key); - - if (keyLength == 0) { - return null; // there can never be anything before root. - } - - TrieEntry found = getNearestEntryForKey(key, keyLength); - if (key.equals(found.key)) return previousEntry(found); - - int bitIndex = bitIndex(key, found.key); - if (isValidBitIndex(bitIndex)) { - TrieEntry added = new TrieEntry(key, null, bitIndex); - addEntry(added, keyLength); - incrementSize(); // must increment because remove will decrement - TrieEntry prior = previousEntry(added); - removeEntry(added); - modCount -= 2; // we didn't really modify it. - return prior; - } - else if (isNullBitKey(bitIndex)) { - return null; - } - else if (isEqualBitKey(bitIndex)) { - return previousEntry(found); - } - - // we should have exited above. - throw new IllegalStateException("invalid lookup: " + key); - } - - /** - * Returns a key-value mapping associated with the greatest key - * less than or equal to the given key, or null if there is no such key. - */ - protected TrieEntry floorEntry(K key) { - // TODO: Cleanup so that we don't actually have to add/remove from the - // tree. (We do it here because there are other well-defined - // functions to perform the search.) - int keyLength = length(key); - - if (keyLength == 0) { - if (!root.isEmpty()) { - return root; - } - else { - return null; - } - } - - TrieEntry found = getNearestEntryForKey(key, keyLength); - if (key.equals(found.key)) return found; - - int bitIndex = bitIndex(key, found.key); - if (isValidBitIndex(bitIndex)) { - TrieEntry added = new TrieEntry(key, null, bitIndex); - addEntry(added, keyLength); - incrementSize(); // must increment because remove will decrement - TrieEntry floor = previousEntry(added); - removeEntry(added); - modCount -= 2; // we didn't really modify it. - return floor; - } - else if (isNullBitKey(bitIndex)) { - if (!root.isEmpty()) { - return root; - } - else { - return null; - } - } - else if (isEqualBitKey(bitIndex)) { - return found; - } - - // we should have exited above. - throw new IllegalStateException("invalid lookup: " + key); - } - - - /** - * Finds the subtree that contains the prefix. - *

    - * This is very similar to getR but with the difference that - * we stop the lookup if h.bitIndex > keyLength. - */ - private TrieEntry subtree(K prefix, int offset, int length) { - TrieEntry current = root.left; - TrieEntry path = root; - while (true) { - if (current.bitIndex <= path.bitIndex || length < current.bitIndex) break; - - path = current; - if (!isBitSet(prefix, length + offset, current.bitIndex + offset)) { - current = current.left; - } - else { - current = current.right; - } - } - - // Make sure the entry is valid for a subtree. - TrieEntry entry = current.isEmpty() ? path : current; - - // If entry is root, it can't be empty. - if (entry.isEmpty()) return null; - - int offsetLength = offset + length; - - // if root && length of root is less than length of lookup, - // there's nothing. - // (this prevents returning the whole subtree if root has an empty - // string and we want to lookup things with "\0") - if (entry == root && length(entry.getKey()) < offsetLength) return null; - - // Found key's length-th bit differs from our key - // which means it cannot be the prefix... - if (isBitSet(prefix, offsetLength, offsetLength) != isBitSet(entry.key, length(entry.key), length)) { - return null; - } - - // ... or there are less than 'length' equal bits - int bitIndex = keyAnalyzer.bitIndex(prefix, offset, length, entry.key, 0, length(entry.getKey())); - if (bitIndex >= 0 && bitIndex < length) return null; - - return entry; - } - - /** - * A submap used for prefix views over the Trie. - */ - private class PrefixSubMap extends SubMap { - protected final K prefix; - protected final int offset; - protected final int length; - private transient int keyModCount = 0; - protected int size; - - PrefixSubMap(K prefix, int offset, int length) { - this.prefix = prefix; - this.offset = offset; - this.length = length; - fromInclusive = false; - } - - public K firstKey() { - fixup(); - TrieEntry e; - if (fromKey == null) { - e = firstEntry(); - } - else { - e = higherEntry(fromKey); - } - - K first = e != null ? e.getKey() : null; - if (e == null || !keyAnalyzer.isPrefix(prefix, offset, length, first)) throw new NoSuchElementException(); - return first; - } - - public K lastKey() { - fixup(); - TrieEntry e; - if (toKey == null) { - e = lastEntry(); - } - else { - e = lowerEntry(toKey); - } - - K last = e != null ? e.getKey() : null; - if (e == null || !keyAnalyzer.isPrefix(prefix, offset, length, last)) throw new NoSuchElementException(); - return last; - } - - protected boolean inRange(K key) { - return keyAnalyzer.isPrefix(prefix, offset, length, key); - } - - protected boolean inRange2(K key) { - return keyAnalyzer.isPrefix(prefix, offset, length, key); - } - - protected boolean inToRange(K key, boolean forceInclusive) { - return keyAnalyzer.isPrefix(prefix, offset, length, key); - } - - protected boolean inFromRange(K key, boolean forceInclusive) { - return keyAnalyzer.isPrefix(prefix, offset, length, key); - } - - private void fixup() { - // The trie has changed since we last - // found our toKey / fromKey - if (modCount != keyModCount) { - Iterator> iter = entrySet().iterator(); - size = 0; - - Map.Entry entry = null; - if (iter.hasNext()) { - entry = iter.next(); - size = 1; - } - - fromKey = entry == null ? null : entry.getKey(); - if (fromKey != null) { - TrieEntry prior = previousEntry((TrieEntry)entry); - fromKey = prior == null ? null : prior.getKey(); - } - - toKey = fromKey; - - while (iter.hasNext()) { - size++; - entry = iter.next(); - } - - toKey = entry == null ? null : entry.getKey(); - - if (toKey != null) { - entry = nextEntry((TrieEntry)entry); - toKey = entry == null ? null : entry.getKey(); - } - - keyModCount = modCount; - } - } - - protected Set> newSubMapEntrySet() { - return new PrefixEntrySetView(); - } - - private class PrefixEntrySetView extends SubMap.EntrySetView { - private TrieEntry prefixStart; - private int iterModCount = 0; - - public int size() { - fixup(); - return PrefixSubMap.this.size; - } - - @SuppressWarnings("unchecked") - public Iterator> iterator() { - if (modCount != iterModCount) { - prefixStart = subtree(prefix, offset, length); - iterModCount = modCount; - } - - if (prefixStart == null) { - return new EmptyIterator(); - } - else if (length >= prefixStart.bitIndex) { - return new SingletonIterator(prefixStart); - } - else { - return new PrefixEntryIterator(prefixStart, prefix, offset, length); - } - } - } - } - - private class SubMap extends AbstractMap implements SortedMap, java.io.Serializable { - - // TODO: add serialVersionUID - - /** - * The key to start from, null if the beginning. - */ - protected K fromKey; - - /** - * The key to end at, null if till the end. - */ - protected K toKey; - - /** - * Whether or not the 'from' is inclusive. - */ - protected boolean fromInclusive; - - /** - * Whether or not the 'to' is inclusive. - */ - protected boolean toInclusive; - - /** - * Constructs a blank SubMap -- this should ONLY be used - * by subclasses that wish to lazily construct their - * fromKey or toKey - */ - protected SubMap() { - } - - SubMap(K fromKey, K toKey) { - if (fromKey == null && toKey == null) throw new IllegalArgumentException("must have a from or to!"); - if (fromKey != null && toKey != null && keyAnalyzer.compare(fromKey, toKey) > 0) { - throw new IllegalArgumentException("fromKey > toKey"); - } - this.fromKey = fromKey; - this.toKey = toKey; - fromInclusive = true; - } - - public boolean isEmpty() { - return entrySet().isEmpty(); - } - - @SuppressWarnings("unchecked") - public boolean containsKey(Object key) { - return inRange((K)key) && PatriciaTrie.this.containsKey(key); - } - - @SuppressWarnings("unchecked") - public V remove(Object key) { - if (!inRange((K)key)) return null; - return PatriciaTrie.this.remove(key); - } - - @SuppressWarnings("unchecked") - public V get(Object key) { - if (!inRange((K)key)) return null; - return PatriciaTrie.this.get(key); - } - - public V put(K key, V value) { - if (!inRange(key)) throw new IllegalArgumentException("key out of range"); - return PatriciaTrie.this.put(key, value); - } - - public Comparator comparator() { - return keyAnalyzer; - } - - public K firstKey() { - TrieEntry e; - if (fromKey == null) { - e = firstEntry(); - } - else { - if (fromInclusive) { - e = ceilingEntry(fromKey); - } - else { - e = higherEntry(fromKey); - } - } - - K first = e != null ? e.getKey() : null; - if (e == null || toKey != null && !inToRange(first, false)) throw new NoSuchElementException(); - return first; - } - - public K lastKey() { - TrieEntry e; - if (toKey == null) { - e = lastEntry(); - } - else { - if (toInclusive) { - e = floorEntry(toKey); - } - else { - e = lowerEntry(toKey); - } - } - - K last = e != null ? e.getKey() : null; - if (e == null || fromKey != null && !inFromRange(last, false)) throw new NoSuchElementException(); - return last; - } - - private transient Set> entrySet; - - public Set> entrySet() { - if (entrySet == null) entrySet = newSubMapEntrySet(); - return entrySet; - } - - protected Set> newSubMapEntrySet() { - return new EntrySetView(); - } - - class EntrySetView extends AbstractSet> { - private transient int size = -1, sizeModCount; - - public int size() { - if (size == -1 || sizeModCount != PatriciaTrie.this.modCount) { - size = 0; - sizeModCount = PatriciaTrie.this.modCount; - Iterator i = iterator(); - while (i.hasNext()) { - size++; - i.next(); - } - } - return size; - } - - public boolean isEmpty() { - return !iterator().hasNext(); - } - - @SuppressWarnings("unchecked") - public boolean contains(Object o) { - if (!(o instanceof Map.Entry)) return false; - Map.Entry entry = (Map.Entry)o; - K key = entry.getKey(); - if (!inRange(key)) return false; - TrieEntry node = getEntry(key); - return node != null && valEquals(node.getValue(), entry.getValue()); - } - - @SuppressWarnings("unchecked") - public boolean remove(Object o) { - if (!(o instanceof Map.Entry)) return false; - Map.Entry entry = (Map.Entry)o; - K key = entry.getKey(); - if (!inRange(key)) return false; - TrieEntry node = getEntry(key); - if (node != null && valEquals(node.getValue(), entry.getValue())) { - removeEntry(node); - return true; - } - return false; - } - - public Iterator> iterator() { - return new SubMapEntryIterator((fromKey == null ? firstEntry() : ceilingEntry(fromKey)), - (toKey == null ? null : ceilingEntry(toKey))); - } - } - - public SortedMap subMap(K fromKey, K toKey) { - if (!inRange2(fromKey)) throw new IllegalArgumentException("fromKey out of range"); - if (!inRange2(toKey)) throw new IllegalArgumentException("toKey out of range"); - return new SubMap(fromKey, toKey); - } - - public SortedMap headMap(K toKey) { - if (!inRange2(toKey)) throw new IllegalArgumentException("toKey out of range"); - return new SubMap(fromKey, toKey); - } - - public SortedMap tailMap(K fromKey) { - if (!inRange2(fromKey)) throw new IllegalArgumentException("fromKey out of range"); - return new SubMap(fromKey, toKey); - } - - protected boolean inRange(K key) { - return (fromKey == null || inFromRange(key, false)) && (toKey == null || inToRange(key, false)); - } - - // This form allows the high endpoint (as well as all legit keys) - protected boolean inRange2(K key) { - return (fromKey == null || inFromRange(key, false)) && (toKey == null || inToRange(key, true)); - } - - protected boolean inToRange(K key, boolean forceInclusive) { - int ret = keyAnalyzer.compare(key, toKey); - if (toInclusive || forceInclusive) { - return ret <= 0; - } - else { - return ret < 0; - } - } - - protected boolean inFromRange(K key, boolean forceInclusive) { - int ret = keyAnalyzer.compare(key, fromKey); - if (fromInclusive || forceInclusive) { - return ret >= 0; - } - else { - return ret > 0; - } - } - } - - private static class EmptyIterator implements Iterator { - public boolean hasNext() { - return false; - } - - public Object next() { - throw new NoSuchElementException(); - } - - public void remove() { - throw new IllegalStateException(); - } - - } -} - diff --git a/plugins/spellchecker/src/com/intellij/spellchecker/trie/Trie.java b/plugins/spellchecker/src/com/intellij/spellchecker/trie/Trie.java deleted file mode 100644 index 1f76191d4a3d..000000000000 --- a/plugins/spellchecker/src/com/intellij/spellchecker/trie/Trie.java +++ /dev/null @@ -1,168 +0,0 @@ -/* - * Copyright 2005-2009 Roger Kapsi, Sam Berlin - * - * 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.spellchecker.trie; - - -import java.util.Map; -import java.util.SortedMap; - -public interface Trie extends SortedMap { - - /** - * Returns a view of this Trie of all elements that are - * prefixed by the given key. - *

    - * In a fixed-keysize Trie, this is essentially a 'get' operation. - *

    - * For example, if the Trie contains 'Lime', 'LimeWire', - * 'LimeRadio', 'Lax', 'Later', 'Lake', and 'Lovely', then - * a lookup of 'Lime' would return 'Lime', 'LimeRadio', and 'LimeWire'. - */ - public SortedMap getPrefixedBy(K key); - - /** - * Returns a view of this Trie of all elements that are - * prefixed by the length of the key. - *

    - * Fixed-keysize Tries will not support this operation - * (because all keys will be the same length). - *

    - * For example, if the Trie contains 'Lime', 'LimeWire', - * 'LimeRadio', 'Lax', 'Later', 'Lake', and 'Lovely', then - * a lookup of 'LimePlastics' with a length of 4 would - * return 'Lime', 'LimeRadio', and 'LimeWire'. - */ - public SortedMap getPrefixedBy(K key, int length); - - /** - * Returns a view of this Trie of all elements that are prefixed - * by the key, starting at the given offset and for the given length. - *

    - * Fixed-keysize Tries will not support this operation - * (because all keys are the same length). - *

    - * For example, if the Trie contains 'Lime', 'LimeWire', - * 'LimeRadio', 'Lax', 'Later', 'Lake', and 'Lovely', then - * a lookup of 'The Lime Plastics' with an offset of 4 and a - * length of 4 would return 'Lime', 'LimeRadio', and 'LimeWire'. - */ - public SortedMap getPrefixedBy(K key, int offset, int length); - - /** - * Returns a view of this Trie of all elements that are prefixed - * by the number of bits in the given Key. - *

    - * Fixed-keysize Tries can support this operation as a way to do - * lookups of partial keys. That is, if the Trie is storing IP - * addresses, you can lookup all addresses that begin with - * '192.168' by providing the key '192.168.X.X' and a length of 16 - * would return all addresses that begin with '192.168'. - */ - public SortedMap getPrefixedByBits(K key, int bitLength); - - /** - * Returns the value for the entry whose key is closest in a bitwise - * XOR metric to the given key. This is NOT lexicographic closeness. - * For example, given the keys:
    - * D = 1000100
    - * H = 1001000
    - * L = 1001100
    - *

    - * If the Trie contained 'H' and 'L', a lookup of 'D' would return 'L', - * because the XOR distance between D & L is smaller than the XOR distance - * between D & H. - */ - public V select(K key); - - /** - * Iterates through the Trie, starting with the entry whose bitwise - * value is closest in an XOR metric to the given key. After the closest - * entry is found, the Trie will call select on that entry and continue - * calling select for each entry (traversing in order of XOR closeness, - * NOT lexicographically) until the cursor returns - * Cursor.SelectStatus.EXIT.
    - * The cursor can return Cursor.SelectStatus.CONTINUE to - * continue traversing.
    - * Cursor.SelectStatus.REMOVE_AND_EXIT is used to remove the current element - * and stop traversing. - *

    - * Note: The {@link Cursor.SelectStatus#REMOVE} operation is not supported. - * - * @return The entry the cursor returned EXIT on, or null if it continued - * till the end. - */ - public Map.Entry select(K key, Cursor cursor); - - /** - * Traverses the Trie in lexicographical order. Cursor.select - * will be called on each entry.

    - * The traversal will stop when the cursor returns Cursor.SelectStatus.EXIT.
    - * Cursor.SelectStatus.CONTINUE is used to continue traversing.
    - * Cursor.SelectStatus.REMOVE is used to remove the element that was - * selected and continue traversing.
    - * Cursor.SelectStatus.REMOVE_AND_EXIT is used to remove the current element - * and stop traversing. - * - * @return The entry the cursor returned EXIT on, or null if it continued - * till the end. - */ - public Map.Entry traverse(Cursor cursor); - - /** - * An interface used by a {@link Trie}. A {@link Trie} selects items by - * closeness and passes the items to the Cursor. You can then - * decide what to do with the key-value pair and the return value - * from {@link #select(java.util.Map.Entry)} tells the Trie - * what to do next. - *

    - * Cursor returns status/selection status might be: - * - * - * - * - * - * - *
    Return ValueStatus
    EXITFinish the Trie operation
    CONTINUELook at the next element in the traversal
    REMOVE_AND_EXITRemove the entry and stop iterating
    REMOVERemove the entry and continue iterating
    - *

    - * Note: {@link Trie#select(Object, org.limewire.collection.Trie.Cursor)} does - * not support REMOVE. - * - * @param Key Type - * @param Key Value - */ - public static interface Cursor { - - /** - * Notification that the Trie is currently looking at the given entry. - * Return EXIT to finish the Trie operation, - * CONTINUE to look at the next entry, REMOVE - * to remove the entry and continue iterating, or - * REMOVE_AND_EXIT to remove the entry and stop iterating. - * Not all operations support REMOVE. - */ - public SelectStatus select(Map.Entry entry); - - /** - * The mode during selection. - */ - public static enum SelectStatus { - EXIT, CONTINUE, REMOVE, REMOVE_AND_EXIT; - } - } -} - diff --git a/plugins/spellchecker/src/com/intellij/spellchecker/util/Strings.java b/plugins/spellchecker/src/com/intellij/spellchecker/util/Strings.java index 306ff3d17b5f..008d5ba10755 100644 --- a/plugins/spellchecker/src/com/intellij/spellchecker/util/Strings.java +++ b/plugins/spellchecker/src/com/intellij/spellchecker/util/Strings.java @@ -17,9 +17,6 @@ package com.intellij.spellchecker.util; import java.util.List; -/** - * Text utility. - */ public final class Strings { private Strings() { } diff --git a/plugins/spellchecker/testSrc/com/intellij/spellchecker/compress/CompressorTest.java b/plugins/spellchecker/testSrc/com/intellij/spellchecker/compress/CompressorTest.java new file mode 100644 index 000000000000..59da085f0f5e --- /dev/null +++ b/plugins/spellchecker/testSrc/com/intellij/spellchecker/compress/CompressorTest.java @@ -0,0 +1,68 @@ +/* + * Copyright 2000-2010 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.spellchecker.compress; + +import com.intellij.spellchecker.compress.Compressor; +import com.intellij.spellchecker.compress.UnitBitSet; +import junit.framework.TestCase; + +import java.util.BitSet; +import java.util.Random; + +public class CompressorTest extends TestCase { + + public void testCompressorWithDefinedData() { + Compressor compressor = new Compressor(0); + UnitBitSet bs = UnitBitSet.create(1, 3, 5, 10); + final byte[] compressed = compressor.compress(bs); + assertEquals(3, compressed.length); + assertEquals(42, compressed[1]); + assertEquals(8, compressed[2]); + final UnitBitSet restored = compressor.decompress(compressed); + assertEquals(bs, restored); + } + + public void testCompressorWithRandomData() { + Compressor compressor = new Compressor(0); + int max = 10; + for (int i = 0; i < 1000; i++) { + int[] values = getRandoms(1000, max); + final UnitBitSet bitSet = new UnitBitSet(); + for (int value : values) { + bitSet.set(value); + } + final byte[] compressed = compressor.compress(bitSet); + final BitSet decompressed = compressor.decompress(compressed); + boolean check = (bitSet.equals(decompressed)); + if (!check) { + System.out.println("bitSet: " + bitSet); + System.out.println("decompressed: " + decompressed); + compressor.compress(bitSet); + compressor.decompress(compressed); + } + assertEquals(bitSet, decompressed); + } + } + + private static int[] getRandoms(int max, int count) { + int[] result = new int[count]; + for (int i = 0; i < count; i++) { + result[i] = new Random().nextInt(max); + } + return result; + } + +} diff --git a/plugins/spellchecker/testSrc/com/intellij/spellchecker/compress/DictionaryTest.java b/plugins/spellchecker/testSrc/com/intellij/spellchecker/compress/DictionaryTest.java new file mode 100644 index 000000000000..47120df03721 --- /dev/null +++ b/plugins/spellchecker/testSrc/com/intellij/spellchecker/compress/DictionaryTest.java @@ -0,0 +1,150 @@ +/* + * Copyright 2000-2010 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.spellchecker.compress; + +import com.intellij.openapi.util.Pair; +import com.intellij.spellchecker.DefaultBundledDictionariesProvider; +import com.intellij.spellchecker.StreamLoader; +import com.intellij.spellchecker.dictionary.Dictionary; +import com.intellij.spellchecker.dictionary.Loader; +import com.intellij.spellchecker.engine.Transformation; +import com.intellij.testFramework.IdeaTestUtil; +import com.intellij.util.Consumer; +import gnu.trove.THashSet; +import junit.framework.TestCase; +import org.jetbrains.annotations.NotNull; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +@SuppressWarnings({"JUnitTestCaseWithNonTrivialConstructors"}) +public class DictionaryTest extends TestCase { + + private Dictionary dictionary; + + private Map sizes = new HashMap(); + private Map times = new HashMap(); + + { + sizes.put("jetbrains.dic", 1000); + sizes.put("english.dic", 140000); + sizes.put("russian-utf8.dic", 1200000); + } + + { + times.put("jetbrains.dic", 100); + times.put("english.dic", 2700); + times.put("russian-utf8.dic", 32000); + } + + public void testDictionary() throws IOException { + + DefaultBundledDictionariesProvider dp = new DefaultBundledDictionariesProvider(); + final String[] names = dp.getBundledDictionaries(); + for (String name : names) { + StreamLoader loader = createLoader(name); + loadDictionaryTest(loader, sizes.get(name), name); + loadHalfDictionaryTest(50000, name); + } + + } + + + public void loadDictionaryTest(final Loader loader, int wordCount, final String name) throws IOException { + final Transformation transform = new Transformation(); + + IdeaTestUtil + .assertTiming("Dictionary load depends on words count. Approximate word count: " + wordCount + ".", times.get(name), new Runnable() { + public void run() { + dictionary = CompressedDictionary.create(loader, transform); + } + }); + + final Set wordsToStoreAndCheck = createWordSets(name, 50000, 1).getFirst(); + IdeaTestUtil.assertTiming("Invoke 'contains' " + wordsToStoreAndCheck.size() + " times", 1000, new Runnable() { + public void run() { + for (String s : wordsToStoreAndCheck) { + assertTrue(dictionary.contains(s)); + } + } + }); + + } + + private static StreamLoader createLoader(String name) { + return new StreamLoader(DefaultBundledDictionariesProvider.class.getResourceAsStream(name), name); + } + + private static Loader createLoader(final Set words) { + return new Loader() { + public void load(@NotNull Consumer consumer) { + for (String word : words) { + consumer.consume(word); + } + } + + public String getName() { + return "test"; + } + }; + } + + @SuppressWarnings({"unchecked"}) + private static Pair, Set> createWordSets(String name, final int maxCount, final int mod) { + Loader loader = createLoader(name); + final Set wordsToStore = new THashSet(); + final Set wordsToCheck = new THashSet(); + final Transformation transform = new Transformation(); + loader.load(new Consumer() { + private int counter = 0; + + public void consume(String s) { + if (counter > maxCount) { + return; + } + String transformed = transform.transform(s); + if (transformed != null) { + + if (counter % mod == 0) { + wordsToStore.add(transformed); + } + else { + wordsToCheck.add(transformed); + } + counter++; + } + } + }); + + return new Pair(wordsToStore, wordsToCheck); + } + + + public void loadHalfDictionaryTest(final int maxCount, final String name) throws IOException { + + final Pair, Set> sets = createWordSets(name, maxCount, 2); + CompressedDictionary dictionary = CompressedDictionary.create(createLoader(sets.getFirst()), new Transformation()); + for (String s : sets.getSecond()) { + if (!sets.getFirst().contains(s)) { + assertFalse(dictionary.contains(s)); + } + } + } + + +} diff --git a/plugins/spellchecker/testSrc/com/intellij/spellchecker/compress/EncodeAndCompressTest.java b/plugins/spellchecker/testSrc/com/intellij/spellchecker/compress/EncodeAndCompressTest.java new file mode 100644 index 000000000000..37082acc133b --- /dev/null +++ b/plugins/spellchecker/testSrc/com/intellij/spellchecker/compress/EncodeAndCompressTest.java @@ -0,0 +1,23 @@ +package com.intellij.spellchecker.compress; + +import com.intellij.spellchecker.compress.Compressor; +import com.intellij.spellchecker.compress.Encoder; +import com.intellij.spellchecker.compress.UnitBitSet; +import junit.framework.TestCase; + + +public class EncodeAndCompressTest extends TestCase { + + public void testEncodeAndCompress() { + Encoder encoder = new Encoder(); + Compressor compressor = new Compressor(2); + String word = "example"; + UnitBitSet bs = encoder.encode(word, true); + byte[] compressed = compressor.compress(bs); + final UnitBitSet decompressed = compressor.decompress(compressed); + assertEquals(bs,decompressed); + String restored = encoder.decode(decompressed); + assertEquals(word,restored); + } + +} diff --git a/plugins/spellchecker/testSrc/com/intellij/spellchecker/compress/EncoderTest.java b/plugins/spellchecker/testSrc/com/intellij/spellchecker/compress/EncoderTest.java new file mode 100644 index 000000000000..23bff92b5494 --- /dev/null +++ b/plugins/spellchecker/testSrc/com/intellij/spellchecker/compress/EncoderTest.java @@ -0,0 +1,114 @@ +/* + * Copyright 2000-2010 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.spellchecker.compress; + +import com.intellij.spellchecker.compress.Alphabet; +import com.intellij.spellchecker.compress.Encoder; +import com.intellij.spellchecker.compress.UnitBitSet; +import junit.framework.TestCase; + +public class EncoderTest extends TestCase { + + + public void testSimple() { + Encoder encoder = new Encoder(); + final String wordToTest = "abc"; + final UnitBitSet bitSet = encoder.encode(wordToTest, true); + assertEquals(3, encoder.getAlphabet().getLastIndexUsed()); + assertEquals(3, bitSet.getUnitValue(0)); + assertEquals(1, bitSet.getUnitValue(1)); + assertEquals(1, bitSet.getUnitValue(2)); + assertEquals(2, bitSet.getUnitValue(3)); + assertEquals(3, bitSet.getUnitValue(4)); + + assertEquals(wordToTest, encoder.decode(bitSet)); + } + + + public void testDouble() { + Encoder encoder = new Encoder(); + final String wordToTest = "aaa"; + final UnitBitSet bitSet = encoder.encode(wordToTest, true); + assertEquals(1, encoder.getAlphabet().getLastIndexUsed()); + assertEquals(3, bitSet.getUnitValue(0)); + assertEquals(1, bitSet.getUnitValue(1)); + assertEquals(1, bitSet.getUnitValue(2)); + assertEquals(1, bitSet.getUnitValue(3)); + assertEquals(1, bitSet.getUnitValue(4)); + + assertEquals(wordToTest, encoder.decode(bitSet)); + } + + public void testLetterRepetition() { + Encoder encoder = new Encoder(); + final String wordToTest = "aba"; + final UnitBitSet bitSet = encoder.encode(wordToTest, true); + assertEquals(2, encoder.getAlphabet().getLastIndexUsed()); + assertEquals(3, bitSet.getUnitValue(0)); + assertEquals(1, bitSet.getUnitValue(1)); + assertEquals(1, bitSet.getUnitValue(2)); + assertEquals(2, bitSet.getUnitValue(3)); + assertEquals(1, bitSet.getUnitValue(4)); + + assertEquals(wordToTest, encoder.decode(bitSet)); + } + + public void testReverse() { + Encoder encoder = new Encoder(); + final String wordToTest1 = "abc"; + final UnitBitSet bitSet = encoder.encode(wordToTest1, true); + assertEquals(3, encoder.getAlphabet().getLastIndexUsed()); + assertEquals(3, bitSet.getUnitValue(0)); + assertEquals(1, bitSet.getUnitValue(1)); + assertEquals(1, bitSet.getUnitValue(2)); + assertEquals(2, bitSet.getUnitValue(3)); + assertEquals(3, bitSet.getUnitValue(4)); + + assertEquals(wordToTest1, encoder.decode(bitSet)); + + final String wordToTest2 = "cba"; + final UnitBitSet bitSet2 = encoder.encode(wordToTest2, true); + assertEquals(3, encoder.getAlphabet().getLastIndexUsed()); + assertEquals(3, bitSet2.getUnitValue(0)); + assertEquals(3, bitSet2.getUnitValue(1)); + assertEquals(3, bitSet2.getUnitValue(2)); + assertEquals(2, bitSet2.getUnitValue(3)); + assertEquals(1, bitSet2.getUnitValue(4)); + + assertEquals(wordToTest2, encoder.decode(bitSet2)); + } + + + public void testWithPredefinedAlphabet() { + Encoder encoder = new Encoder(Alphabet.create("abcdefghijklmnopqrst")); + final String wordToTest1 = "asia"; + //letter 'a' will be added at the end + final UnitBitSet bitSet = encoder.encode(wordToTest1, true); + assertEquals(20, encoder.getAlphabet().getLastIndexUsed()); + assertEquals(4, bitSet.getUnitValue(0)); + assertEquals(1, bitSet.getUnitValue(1)); + assertEquals(1, bitSet.getUnitValue(2)); + assertEquals(19, bitSet.getUnitValue(3)); + assertEquals(9, bitSet.getUnitValue(4)); + assertEquals(1, bitSet.getUnitValue(5)); + + assertEquals(wordToTest1, encoder.decode(bitSet)); + + + } + + +} diff --git a/plugins/spellchecker/testSrc/com/intellij/spellchecker/compress/UnitBitSetTests.java b/plugins/spellchecker/testSrc/com/intellij/spellchecker/compress/UnitBitSetTests.java new file mode 100644 index 000000000000..90234b66d753 --- /dev/null +++ b/plugins/spellchecker/testSrc/com/intellij/spellchecker/compress/UnitBitSetTests.java @@ -0,0 +1,62 @@ +package com.intellij.spellchecker.compress; + +import com.intellij.spellchecker.compress.UnitBitSet; +import junit.framework.TestCase; + +public class UnitBitSetTests extends TestCase { + + public void testInc0() { + UnitBitSet bs = UnitBitSet.create(); + bs.inc(); + assertEquals(UnitBitSet.create(0), bs); + } + + public void testInc1() { + UnitBitSet bs = UnitBitSet.create(0); + bs.inc(); + assertEquals(UnitBitSet.create(1), bs); + } + + public void testInc2() { + UnitBitSet bs = UnitBitSet.create(1); + bs.inc(); + assertEquals(UnitBitSet.create(0, 1), bs); + } + + public void testUnitValue() { + int bitsPerUnit = 256; + for (int i = 0; i < bitsPerUnit-1; i++) { + UnitBitSet bs = UnitBitSet.create(bitsPerUnit); + bs.setUnitValue(0, i); + assertEquals(i, bs.getUnitValue(0)); + assertEquals(0, bs.getUnitValue(1)); + } + } + + public void testCreateFromBitSet() { + UnitBitSet bs1 = UnitBitSet.create(1,5,10); + UnitBitSet bs2 = UnitBitSet.create(bs1,7); + assertEquals(bs2,bs1); + } + + public void testMoveLeft() { + UnitBitSet bs1 = UnitBitSet.create(3,true, 1,3,5,10); + bs1.moveLeft(2); + assertEquals(UnitBitSet.create(3,true,4),bs1); + } + + public void testMoveLeft2() { + UnitBitSet bs1 = UnitBitSet.create(10,true, 1,3,5,10); + bs1.moveLeft(2); + assertEquals(UnitBitSet.create(10,true),bs1); + } + + public void testMoveRight() { + UnitBitSet bs1 = UnitBitSet.create(3,true, 1,3,5,10); + bs1.moveRight(2); + assertEquals(UnitBitSet.create(3,true,7,9,11,16),bs1); + } + + + +} diff --git a/plugins/spellchecker/testSrc/com/intellij/spellchecker/inspection/SpellcheckerInspectionTestCase.java b/plugins/spellchecker/testSrc/com/intellij/spellchecker/inspection/SpellcheckerInspectionTestCase.java index a1086228b155..4851a41d26de 100644 --- a/plugins/spellchecker/testSrc/com/intellij/spellchecker/inspection/SpellcheckerInspectionTestCase.java +++ b/plugins/spellchecker/testSrc/com/intellij/spellchecker/inspection/SpellcheckerInspectionTestCase.java @@ -19,6 +19,7 @@ import com.intellij.codeInspection.LocalInspectionTool; import com.intellij.openapi.application.PathManager; import com.intellij.openapi.application.PluginPathManager; import com.intellij.openapi.util.io.FileUtil; + import com.intellij.testFramework.fixtures.CodeInsightFixtureTestCase; import org.jetbrains.annotations.NonNls; diff --git a/plugins/spellchecker/testSrc/com/intellij/spellchecker/inspector/SplitterTest.java b/plugins/spellchecker/testSrc/com/intellij/spellchecker/inspector/SplitterTest.java index b76cb2fcedbc..f7e45e32f626 100644 --- a/plugins/spellchecker/testSrc/com/intellij/spellchecker/inspector/SplitterTest.java +++ b/plugins/spellchecker/testSrc/com/intellij/spellchecker/inspector/SplitterTest.java @@ -22,6 +22,10 @@ import junit.framework.TestCase; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -81,7 +85,7 @@ public class SplitterTest extends TestCase { } - public void testWordWithApostrophe4() { + public void testWordWithApostrophe4() { String text = "we'll"; List checkAreas = TextSplitter.splitText(text); correctListToCheck(checkAreas, text, new String[]{"we'll"}); @@ -91,64 +95,64 @@ public class SplitterTest extends TestCase { public void testWordWithApostrophe5() { String text = "I'm you're we'll"; List checkAreas = TextSplitter.splitText(text); - correctListToCheck(checkAreas, text, new String[]{"you're","we'll"}); + correctListToCheck(checkAreas, text, new String[]{"you're", "we'll"}); correctIgnored(checkAreas, text, new String[]{"I'm"}); } public void testConstantName() { String text = "TEST_CONSTANT"; List checkAreas = TextSplitter.splitText(text); - correctListToCheck(checkAreas, text, new String[]{"TEST","CONSTANT"}); + correctListToCheck(checkAreas, text, new String[]{"TEST", "CONSTANT"}); correctIgnored(checkAreas, text, new String[]{}); } public void testLongConstantName() { String text = "TEST_VERY_VERY_LONG_AND_COMPLEX_CONSTANT"; List checkAreas = TextSplitter.splitText(text); - correctListToCheck(checkAreas, text, new String[]{"TEST","VERY","VERY","LONG","COMPLEX","CONSTANT"}); + correctListToCheck(checkAreas, text, new String[]{"TEST", "VERY", "VERY", "LONG", "COMPLEX", "CONSTANT"}); correctIgnored(checkAreas, text, new String[]{"AND"}); } - public void testJavaComments() { + public void testJavaComments() { String text = "/*special symbols*/"; List checkAreas = TextSplitter.splitText(text); - correctListToCheck(checkAreas, text, new String[]{"special","symbols"}); + correctListToCheck(checkAreas, text, new String[]{"special", "symbols"}); correctIgnored(checkAreas, text, new String[]{}); } public void testXmlComments() { - String text = ""; - List checkAreas = TextSplitter.splitText(text); - correctListToCheck(checkAreas, text, new String[]{"special","symbols"}); - correctIgnored(checkAreas, text, new String[]{}); - } + String text = ""; + List checkAreas = TextSplitter.splitText(text); + correctListToCheck(checkAreas, text, new String[]{"special", "symbols"}); + correctIgnored(checkAreas, text, new String[]{}); + } public void testCamelCaseInXmlComments() { - String text = ""; - List checkAreas = TextSplitter.splitText(text); - correctListToCheck(checkAreas, text, new String[]{"special","Case","symbols"}); - correctIgnored(checkAreas, text, new String[]{}); - } + String text = ""; + List checkAreas = TextSplitter.splitText(text); + correctListToCheck(checkAreas, text, new String[]{"special", "Case", "symbols"}); + correctIgnored(checkAreas, text, new String[]{}); + } public void testWordsWithNumbers() { - String text = "testCamelCase123"; - List checkAreas = TextSplitter.splitText(text); - correctListToCheck(checkAreas, text, new String[]{"test","Camel","Case"}); - correctIgnored(checkAreas, text, new String[]{}); - } + String text = "testCamelCase123"; + List checkAreas = TextSplitter.splitText(text); + correctListToCheck(checkAreas, text, new String[]{"test", "Camel", "Case"}); + correctIgnored(checkAreas, text, new String[]{}); + } public void testCommentsWithWordsWithNumbers() { String text = ""; List checkAreas = TextSplitter.splitText(text); - correctListToCheck(checkAreas, text, new String[]{"special","Case","symbols"}); + correctListToCheck(checkAreas, text, new String[]{"special", "Case", "symbols"}); correctIgnored(checkAreas, text, new String[]{}); } public void testCommentsWithAbr() { String text = ""; List checkAreas = TextSplitter.splitText(text); - correctListToCheck(checkAreas, text, new String[]{"Test","Class"}); + correctListToCheck(checkAreas, text, new String[]{"Test", "Class"}); correctIgnored(checkAreas, text, new String[]{"JSP"}); } @@ -162,7 +166,7 @@ public class SplitterTest extends TestCase { public void testCommentWithHtml() { String text = ""; List checkAreas = TextSplitter.splitText(text); - correctListToCheck(checkAreas, text, new String[]{"something","here","foooo","barrrr","text", "text"}); + correctListToCheck(checkAreas, text, new String[]{"something", "here", "foooo", "barrrr", "text", "text"}); correctIgnored(checkAreas, text, new String[]{"go"}); } @@ -177,68 +181,103 @@ public class SplitterTest extends TestCase { String text = "bgColor carLight"; List checkAreas = TextSplitter.splitText(text); correctListToCheck(checkAreas, text, new String[]{"Color", "Light"}); - correctIgnored(checkAreas, text, new String[]{"bg","car"}); + correctIgnored(checkAreas, text, new String[]{"bg", "car"}); } public void testComplex() { - String text = "shkate@gmail.com"; - List checkAreas = TextSplitter.splitText(text); - correctListToCheck(checkAreas, text, new String[]{}); - correctIgnored(checkAreas, text, new String[]{}); - } + String text = "shkate@gmail.com"; + List checkAreas = TextSplitter.splitText(text); + correctListToCheck(checkAreas, text, new String[]{}); + correctIgnored(checkAreas, text, new String[]{}); + } - public void testWordBeforeDelimeter() { + public void testWordBeforeDelimiter() { String text = "badd,"; List checkAreas = TextSplitter.splitText(text); correctListToCheck(checkAreas, text, new String[]{"badd"}); correctIgnored(checkAreas, text, new String[]{}); } - public void testWordAfterDelimeter() { + + public void testWordAfterDelimiter() { String text = ",badd"; List checkAreas = TextSplitter.splitText(text); correctListToCheck(checkAreas, text, new String[]{"badd"}); correctIgnored(checkAreas, text, new String[]{}); } - public void testWordInCapsBeforeDelimeter() { - String text = "BADD,"; - List checkAreas = TextSplitter.splitText(text); - correctListToCheck(checkAreas, text, new String[]{"BADD"}); - correctIgnored(checkAreas, text, new String[]{}); - } - public void testWordInCapsAfterDelimeter() { - String text = ",BADD"; - List checkAreas = TextSplitter.splitText(text); - correctListToCheck(checkAreas, text, new String[]{"BADD"}); - correctIgnored(checkAreas, text, new String[]{}); - } - public void testWordInCapsAfterDelimeter2() { - String text = "BADD;"; - List checkAreas = TextSplitter.splitText(text); - correctListToCheck(checkAreas, text, new String[]{"BADD"}); - correctIgnored(checkAreas, text, new String[]{}); - } - public void testWordInCapsAfterDelimeter3() { - String text = ";BADD;"; - List checkAreas = TextSplitter.splitText(text); - correctListToCheck(checkAreas, text, new String[]{"BADD"}); - correctIgnored(checkAreas, text, new String[]{}); - } + public void testWordInCapsBeforeDelimiter() { + String text = "BADD,"; + List checkAreas = TextSplitter.splitText(text); + correctListToCheck(checkAreas, text, new String[]{"BADD"}); + correctIgnored(checkAreas, text, new String[]{}); + } + + public void testWordInCapsAfterDelimiter() { + String text = ",BADD"; + List checkAreas = TextSplitter.splitText(text); + correctListToCheck(checkAreas, text, new String[]{"BADD"}); + correctIgnored(checkAreas, text, new String[]{}); + } + + public void testWordInCapsAfterDelimiter2() { + String text = "BADD;"; + List checkAreas = TextSplitter.splitText(text); + correctListToCheck(checkAreas, text, new String[]{"BADD"}); + correctIgnored(checkAreas, text, new String[]{}); + } + + public void testWordInCapsAfterDelimiter3() { + String text = ";BADD;"; + List checkAreas = TextSplitter.splitText(text); + correctListToCheck(checkAreas, text, new String[]{"BADD"}); + correctIgnored(checkAreas, text, new String[]{}); + } + + public void testWordWithUmlauts() { + String text = "rechtsb\u00FCndig"; + List checkAreas = TextSplitter.splitText(text); + correctListToCheck(checkAreas, text, new String[]{text}); + correctIgnored(checkAreas, text, new String[]{}); + } - public void testWordWithUmlauts() { - String text = "rechtsb\u00FCndig"; - List checkAreas = TextSplitter.splitText(text); - correctListToCheck(checkAreas, text, new String[]{text}); - correctIgnored(checkAreas, text, new String[]{}); - } - public void testWordUpperCasedWithUmlauts() { - String text = "RECHTSB\u00DCNDIG"; - List checkAreas = TextSplitter.splitText(text); - correctListToCheck(checkAreas, text, new String[]{text}); - correctIgnored(checkAreas, text, new String[]{}); - } + String text = "RECHTSB\u00DCNDIG"; + List checkAreas = TextSplitter.splitText(text); + correctListToCheck(checkAreas, text, new String[]{text}); + correctIgnored(checkAreas, text, new String[]{}); + } + + /* public void testWordsWithDots() { + String text = "properties.test.properties"; + List checkAreas = TextSplitter.splitText(text); + correctListToCheck(checkAreas, text, new String[]{"properties", "test", "properties"}); + correctIgnored(checkAreas, text, new String[]{}); + }*/ + + public void testWordUpperCasedWithUmlautsInTheBeginning() { + String text = "\u00DCNDIG"; + List checkAreas = TextSplitter.splitText(text); + correctListToCheck(checkAreas, text, new String[]{text}); + correctIgnored(checkAreas, text, new String[]{}); + } + + public void testNoWordsInHugeBinaryFile() { + final InputStream stream = SplitterTest.class.getResourceAsStream("crt.txt"); + + String text = convertStreamToString(stream); + List checkAreas = TextSplitter.splitText(text); + correctListToCheck(checkAreas, text, new String[]{}); + } + + public void testNoWordsInHugeBinaryWithXmlFile() { + final InputStream stream = SplitterTest.class.getResourceAsStream("contents.txt"); + String text = convertStreamToString(stream); + List checkAreas = TextSplitter.splitText(text); + List words = wordsToCheck(checkAreas, text); + assertNotNull(words); + assertEquals(45284, words.size()); + } @Nullable @@ -254,7 +293,7 @@ public class SplitterTest extends TestCase { } - @Nullable + @Nullable private static List wordsToIgnore(List toCheck, String text) { if (text == null || toCheck == null) return null; List words = new ArrayList(); @@ -275,7 +314,7 @@ public class SplitterTest extends TestCase { Assert.assertNotNull(words); Assert.assertEquals(expected.length, words.size()); List expectedWords = Arrays.asList(expected); - Assert.assertEquals( expectedWords,words); + Assert.assertEquals(expectedWords, words); } } @@ -288,7 +327,36 @@ public class SplitterTest extends TestCase { Assert.assertNotNull(words); Assert.assertEquals(expected.length, words.size()); List expectedWords = Arrays.asList(expected); - Assert.assertEquals( expectedWords,words); + Assert.assertEquals(expectedWords, words); + } + } + + private String convertStreamToString(InputStream is) { + if (is != null) { + StringBuilder sb = new StringBuilder(); + String line; + + try { + BufferedReader reader = new BufferedReader(new InputStreamReader(is, "UTF-8")); + while ((line = reader.readLine()) != null) { + sb.append(line).append("\n"); + } + } + catch (Exception e) { + throw new RuntimeException(e); + } + finally { + try { + is.close(); + } + catch (IOException ignore) { + + } + } + return sb.toString(); + } + else { + return ""; } } } diff --git a/plugins/spellchecker/testSrc/com/intellij/spellchecker/inspector/contents.txt b/plugins/spellchecker/testSrc/com/intellij/spellchecker/inspector/contents.txt new file mode 100644 index 000000000000..21733e0fda15 Binary files /dev/null and b/plugins/spellchecker/testSrc/com/intellij/spellchecker/inspector/contents.txt differ diff --git a/plugins/spellchecker/testSrc/com/intellij/spellchecker/inspector/crt.txt b/plugins/spellchecker/testSrc/com/intellij/spellchecker/inspector/crt.txt new file mode 100644 index 000000000000..03c3883d613f Binary files /dev/null and b/plugins/spellchecker/testSrc/com/intellij/spellchecker/inspector/crt.txt differ diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnCopiesRefreshManager.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnCopiesRefreshManager.java index 7d05232726f5..8610983764e6 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnCopiesRefreshManager.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnCopiesRefreshManager.java @@ -80,12 +80,12 @@ public class SvnCopiesRefreshManager { semaphore.up(); } }; + semaphore.down(); if (isOnlyInit) { myRequestMerger.ensureInitialization(waiter); } else { myRequestMerger.waitRefresh(waiter); } - semaphore.down(); while (true) { if (semaphore.waitFor(ourQueryInterval)) break; if (pi != null) { diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/history/MergeSourceDetailsAction.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/history/MergeSourceDetailsAction.java index e5a3dd32cebc..3eba3a97499f 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/history/MergeSourceDetailsAction.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/history/MergeSourceDetailsAction.java @@ -30,7 +30,6 @@ import javax.swing.*; import java.awt.event.KeyEvent; public class MergeSourceDetailsAction extends AnAction implements DumbAware { - private static Icon myIcon; @Override public void update(AnActionEvent e) { @@ -39,11 +38,12 @@ public class MergeSourceDetailsAction extends AnAction implements DumbAware { e.getPresentation().setEnabled(enabled(e)); } + private static class IconHolder { + private static final Icon myIcon = IconLoader.getIcon("/icons/mergeSourcesDetails.png"); + } + private Icon getIcon() { - if (myIcon == null) { - myIcon = IconLoader.getIcon("/icons/mergeSourcesDetails.png"); - } - return myIcon; + return IconHolder.myIcon; } public void registerSelf(final JComponent comp) { diff --git a/xml/impl/src/com/intellij/codeInsight/template/XmlZenCodingTemplate.java b/xml/impl/src/com/intellij/codeInsight/template/XmlZenCodingTemplate.java deleted file mode 100644 index 8da184ae3d46..000000000000 --- a/xml/impl/src/com/intellij/codeInsight/template/XmlZenCodingTemplate.java +++ /dev/null @@ -1,707 +0,0 @@ -/* - * Copyright 2000-2010 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; - -import com.intellij.application.options.editor.WebEditorOptions; -import com.intellij.codeInsight.template.impl.TemplateImpl; -import com.intellij.lang.xml.XMLLanguage; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.editor.Editor; -import com.intellij.openapi.util.Pair; -import com.intellij.psi.PsiElement; -import com.intellij.psi.PsiFile; -import com.intellij.psi.util.PsiTreeUtil; -import com.intellij.psi.xml.XmlTag; -import com.intellij.psi.xml.XmlToken; -import com.intellij.psi.xml.XmlTokenType; -import com.intellij.util.containers.HashMap; -import com.intellij.util.containers.HashSet; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.*; - -/** - * @author Eugene.Kudelevsky - */ -public class XmlZenCodingTemplate implements CustomLiveTemplate { - private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.template.XmlZenCodingTemplate"); - - private static final String ATTRS = "ATTRS"; - - private static final String OPERATIONS = ">+*"; - private static final String SELECTORS = ".#["; - private static final char MARKER = '$'; - private static final String ID = "id"; - private static final String CLASS = "class"; - private static final String NUMBER_IN_ITERATION_PLACE_HOLDER = "$"; - - private static enum MyState { - OPERATION, WORD, AFTER_NUMBER, NUMBER - } - - private static class MyToken { - } - - private static class MyMarkerToken extends MyToken { - } - - private static class MyTemplateToken extends MyToken { - final String myKey; - final List> myAttribute2Value; - - MyTemplateToken(String key, List> attribute2value) { - myKey = key; - myAttribute2Value = attribute2value; - } - } - - private static class MyNumberToken extends MyToken { - final int myNumber; - - MyNumberToken(int number) { - myNumber = number; - } - } - - private static class MyOperationToken extends MyToken { - final char mySign; - - MyOperationToken(char sign) { - mySign = sign; - } - } - - private static int parseNonNegativeInt(@NotNull String s) { - try { - return Integer.parseInt(s); - } - catch (Throwable ignored) { - } - return -1; - } - - private static String getPrefix(@NotNull String templateKey) { - for (int i = 0, n = templateKey.length(); i < n; i++) { - char c = templateKey.charAt(i); - if (SELECTORS.indexOf(c) >= 0) { - return templateKey.substring(0, i); - } - } - return templateKey; - } - - @Nullable - private static Pair parseAttrNameAndValue(@NotNull String text) { - int eqIndex = text.indexOf('='); - if (eqIndex > 0) { - return new Pair(text.substring(0, eqIndex), text.substring(eqIndex + 1)); - } - return null; - } - - @Nullable - private static MyTemplateToken parseSelectors(@NotNull String text) { - String templateKey = null; - List> attributes = new ArrayList>(); - Set definedAttrs = new HashSet(); - final List classes = new ArrayList(); - StringBuilder builder = new StringBuilder(); - char lastDelim = 0; - text += MARKER; - int classAttrPosition = -1; - for (int i = 0, n = text.length(); i < n; i++) { - char c = text.charAt(i); - if (c == '#' || c == '.' || c == '[' || c == ']' || i == n - 1) { - if (c != ']') { - switch (lastDelim) { - case 0: - templateKey = builder.toString(); - break; - case '#': - if (!definedAttrs.add(ID)) { - return null; - } - attributes.add(new Pair(ID, builder.toString())); - break; - case '.': - if (builder.length() <= 0) { - return null; - } - if (classAttrPosition < 0) { - classAttrPosition = attributes.size(); - } - classes.add(builder.toString()); - break; - case ']': - if (builder.length() > 0) { - return null; - } - break; - default: - return null; - } - } - else if (lastDelim != '[') { - return null; - } - else { - Pair pair = parseAttrNameAndValue(builder.toString()); - if (pair == null || !definedAttrs.add(pair.first)) { - return null; - } - attributes.add(pair); - } - lastDelim = c; - builder = new StringBuilder(); - } - else { - builder.append(c); - } - } - if (classes.size() > 0) { - if (definedAttrs.contains(CLASS)) { - return null; - } - StringBuilder classesAttrValue = new StringBuilder(); - for (int i = 0; i < classes.size(); i++) { - classesAttrValue.append(classes.get(i)); - if (i < classes.size() - 1) { - classesAttrValue.append(' '); - } - } - assert classAttrPosition >= 0; - attributes.add(classAttrPosition, new Pair(CLASS, classesAttrValue.toString())); - } - return new MyTemplateToken(templateKey, attributes); - } - - @Nullable - private static List parse(@NotNull String text, @NotNull CustomTemplateCallback callback) { - text += MARKER; - StringBuilder templateKeyBuilder = new StringBuilder(); - List result = new ArrayList(); - for (int i = 0, n = text.length(); i < n; i++) { - char c = text.charAt(i); - if (i == n - 1 || (i < n - 2 && OPERATIONS.indexOf(c) >= 0)) { - String key = templateKeyBuilder.toString(); - templateKeyBuilder = new StringBuilder(); - int num = parseNonNegativeInt(key); - if (num > 0) { - result.add(new MyNumberToken(num)); - } - else { - if (key.length() == 0) { - return null; - } - String prefix = getPrefix(key); - if (!callback.isLiveTemplateApplicable(prefix) && prefix.indexOf('<') >= 0 /*&& !XML11Char.isXML11ValidQName(prefix)*/) { - return null; - } - MyTemplateToken token = parseSelectors(key); - if (token == null) { - return null; - } - result.add(token); - } - result.add(i < n - 1 ? new MyOperationToken(c) : new MyMarkerToken()); - } - else if (!Character.isWhitespace(c)) { - templateKeyBuilder.append(c); - } - else { - return null; - } - } - return result; - } - - private static boolean check(@NotNull Collection tokens) { - MyState state = MyState.WORD; - for (MyToken token : tokens) { - if (token instanceof MyMarkerToken) { - break; - } - switch (state) { - case OPERATION: - if (token instanceof MyOperationToken) { - state = ((MyOperationToken)token).mySign == '*' ? MyState.NUMBER : MyState.WORD; - } - else { - return false; - } - break; - case WORD: - if (token instanceof MyTemplateToken) { - state = MyState.OPERATION; - } - else { - return false; - } - break; - case NUMBER: - if (token instanceof MyNumberToken) { - state = MyState.AFTER_NUMBER; - } - else { - return false; - } - break; - case AFTER_NUMBER: - if (token instanceof MyOperationToken && ((MyOperationToken)token).mySign != '*') { - state = MyState.WORD; - } - else { - return false; - } - break; - } - } - return state == MyState.OPERATION || state == MyState.AFTER_NUMBER; - } - - private static String computeKey(Editor editor, int startOffset) { - int offset = editor.getCaretModel().getOffset(); - String s = editor.getDocument().getCharsSequence().subSequence(startOffset, offset).toString(); - int index = 0; - while (index < s.length() && Character.isWhitespace(s.charAt(index))) { - index++; - } - String key = s.substring(index); - int lastWhitespaceIndex = -1; - for (int i = 0; i < key.length(); i++) { - if (Character.isWhitespace(key.charAt(i))) { - lastWhitespaceIndex = i; - } - } - if (lastWhitespaceIndex >= 0 && lastWhitespaceIndex < key.length() - 1) { - return key.substring(lastWhitespaceIndex + 1); - } - return key; - } - - public String computeTemplateKey(@NotNull CustomTemplateCallback callback) { - Editor editor = callback.getEditor(); - int offset = callback.getOffset(); - PsiElement element = callback.getFile().findElementAt(offset > 0 ? offset - 1 : offset); - int line = editor.getCaretModel().getLogicalPosition().line; - int lineStart = editor.getDocument().getLineStartOffset(line); - int parentStart; - do { - parentStart = element != null ? element.getTextRange().getStartOffset() : 0; - int startOffset = parentStart > lineStart ? parentStart : lineStart; - String key = computeKey(editor, startOffset); - List tokens = parse(key, callback); - if (tokens != null && check(tokens)) { - if (tokens.size() == 2) { - MyToken token = tokens.get(0); - if (token instanceof MyTemplateToken) { - if (key.equals(((MyTemplateToken)token).myKey) && callback.isLiveTemplateApplicable(key)) { - // do not activate only live template - return null; - } - } - } - return key; - } - if (element != null) { - element = element.getParent(); - } - } - while (element != null && parentStart > lineStart); - return null; - } - - public boolean isApplicable(PsiFile file, int offset, boolean selection) { - WebEditorOptions webEditorOptions = WebEditorOptions.getInstance(); - if (!webEditorOptions.isZenCodingEnabled()) { - return false; - } - if (file.getLanguage() instanceof XMLLanguage) { - PsiElement element = file.findElementAt(offset > 0 ? offset - 1 : offset); - if (element == null || element.getLanguage() instanceof XMLLanguage) { - return true; - } - } - return false; - } - - public void expand(String key, @NotNull CustomTemplateCallback callback, @Nullable TemplateInvokationListener listener) { - List tokens = parse(key, callback); - assert tokens != null; - MyInterpreter interpreter = new MyInterpreter(tokens, callback, MyState.WORD, listener); - interpreter.invoke(0); - } - - public void wrap(String selection, @NotNull CustomTemplateCallback callback, @Nullable TemplateInvokationListener listener) { - } - - private static void fail() { - LOG.error("Input string was checked incorrectly during isApplicable() invokation"); - } - - @NotNull - private static String buildAttributesString(List> attribute2value, int numberInIteration) { - StringBuilder result = new StringBuilder(); - for (Iterator> it = attribute2value.iterator(); it.hasNext();) { - Pair pair = it.next(); - String name = pair.first; - String value = pair.second.replace(NUMBER_IN_ITERATION_PLACE_HOLDER, Integer.toString(numberInIteration + 1)); - result.append(name).append("=\"").append(value).append('"'); - if (it.hasNext()) { - result.append(' '); - } - } - return result.toString(); - } - - private static boolean invokeTemplate(MyTemplateToken token, - final CustomTemplateCallback callback, - final TemplateInvokationListener listener, - int numberInIteration) { - String attributes = buildAttributesString(token.myAttribute2Value, numberInIteration); - attributes = attributes.length() > 0 ? ' ' + attributes : null; - Map predefinedValues = null; - if (attributes != null) { - predefinedValues = new HashMap(); - predefinedValues.put(ATTRS, attributes); - } - if (callback.isLiveTemplateApplicable(token.myKey)) { - if (attributes != null && !callback.templateContainsVars(token.myKey, ATTRS)) { - TemplateImpl newTemplate = generateTemplateWithAttributes(token.myKey, attributes, callback); - if (newTemplate != null) { - return callback.startTemplate(newTemplate, predefinedValues, listener); - } - } - return callback.startTemplate(token.myKey, predefinedValues, listener); - } - else { - TemplateImpl template = new TemplateImpl("", ""); - template.addTextSegment('<' + token.myKey); - if (attributes != null) { - template.addVariable(ATTRS, "", "", false); - template.addVariableSegment(ATTRS); - } - template.addTextSegment(">"); - template.addVariableSegment(TemplateImpl.END); - template.addTextSegment(""); - template.setToReformat(true); - return callback.startTemplate(template, predefinedValues, listener); - } - } - - private static int findPlaceToInsertAttrs(@NotNull TemplateImpl template) { - String s = template.getString(); - if (s.length() > 0) { - if (s.charAt(0) != '<') { - return -1; - } - int i = 1; - while (i < s.length() && !Character.isWhitespace(s.charAt(i)) && s.charAt(i) != '>') { - i++; - } - if (i == 1) { - return -1; - } - if (s.indexOf('>', i) >= i) { - return i; - } - } - return -1; - } - - @Nullable - private static TemplateImpl generateTemplateWithAttributes(String key, String attributes, CustomTemplateCallback callback) { - TemplateImpl template = callback.findApplicableTemplate(key); - assert template != null; - String templateString = template.getString(); - int offset = findPlaceToInsertAttrs(template); - if (offset >= 0) { - String newTemplateString = templateString.substring(0, offset) + attributes + templateString.substring(offset); - TemplateImpl newTemplate = template.copy(); - newTemplate.setString(newTemplateString); - return newTemplate; - } - return null; - } - - /*private static boolean hasClosingTag(CharSequence text, CharSequence tagName, int offset, int rightBound) { - if (offset + 1 < text.length() && text.charAt(offset) == '<' && text.charAt(offset + 1) == '/') { - CharSequence closingTagName = parseTagName(text, offset + 2, rightBound); - if (tagName.equals(closingTagName)) { - return true; - } - } - return false; - }*/ - - private class MyInterpreter { - private final List myTokens; - private final CustomTemplateCallback myCallback; - private final TemplateInvokationListener myListener; - private MyState myState; - - private MyInterpreter(List tokens, - CustomTemplateCallback callback, - MyState initialState, - TemplateInvokationListener listener) { - myTokens = tokens; - myCallback = callback; - myListener = listener; - myState = initialState; - } - - private void finish(boolean inSeparateEvent) { - myCallback.gotoEndOffset(); - if (myListener != null) { - myListener.finished(inSeparateEvent); - } - } - - private void gotoChild(Object templateBoundsKey) { - int startOfTemplate = myCallback.getStartOfTemplate(templateBoundsKey); - int endOfTemplate = myCallback.getEndOfTemplate(templateBoundsKey); - Editor editor = myCallback.getEditor(); - int offset = myCallback.getOffset(); - - PsiFile file = myCallback.getFile(); - - PsiElement element = file.findElementAt(offset); - if (element instanceof XmlToken && ((XmlToken)element).getTokenType() == XmlTokenType.XML_END_TAG_START) { - return; - } - - int newOffset = -1; - XmlTag tag = PsiTreeUtil.findElementOfClassAtRange(file, startOfTemplate, endOfTemplate, XmlTag.class); - if (tag != null) { - for (PsiElement child : tag.getChildren()) { - if (child instanceof XmlToken && ((XmlToken)child).getTokenType() == XmlTokenType.XML_END_TAG_START) { - newOffset = child.getTextOffset(); - } - } - } - - if (newOffset >= 0) { - myCallback.fixEndOffset(); - editor.getCaretModel().moveToOffset(newOffset); - } - - /*CharSequence tagName = getPrecedingTagName(text, offset, startOfTemplate); - if (tagName != null) { - *//*if (!hasClosingTag(text, tagName, offset, endOfTemplate)) { - document.insertString(offset, "'); - }*//* - } - else if (offset != endOfTemplate) { - tagName = getPrecedingTagName(text, endOfTemplate, startOfTemplate); - if (tagName != null) { - *//*fixEndOffset(); - document.insertString(endOfTemplate, "');*//* - editor.getCaretModel().moveToOffset(endOfTemplate); - } - }*/ - } - - public boolean invoke(int startIndex) { - final int n = myTokens.size(); - MyTemplateToken templateToken = null; - int number = -1; - for (int i = startIndex; i < n; i++) { - final int finalI = i; - MyToken token = myTokens.get(i); - switch (myState) { - case OPERATION: - if (templateToken != null) { - if (token instanceof MyMarkerToken || token instanceof MyOperationToken) { - final char sign = token instanceof MyOperationToken ? ((MyOperationToken)token).mySign : MARKER; - if (sign == MARKER || sign == '+') { - final Object key = new Object(); - myCallback.fixStartOfTemplate(key); - TemplateInvokationListener listener = new TemplateInvokationListener() { - public void finished(boolean inSeparateEvent) { - myState = MyState.WORD; - if (myCallback.getOffset() != myCallback.getEndOfTemplate(key)) { - myCallback.fixEndOffset(); - } - if (sign == '+') { - myCallback.gotoEndOfTemplate(key); - } - if (inSeparateEvent) { - invoke(finalI + 1); - } - } - }; - if (!invokeTemplate(templateToken, myCallback, listener, -1)) { - return false; - } - templateToken = null; - } - else if (sign == '>') { - if (!startTemplateAndGotoChild(templateToken, finalI)) { - return false; - } - templateToken = null; - } - else if (sign == '*') { - myState = MyState.NUMBER; - } - } - else { - fail(); - } - } - break; - case WORD: - if (token instanceof MyTemplateToken) { - templateToken = ((MyTemplateToken)token); - myState = MyState.OPERATION; - } - else { - fail(); - } - break; - case NUMBER: - if (token instanceof MyNumberToken) { - number = ((MyNumberToken)token).myNumber; - myState = MyState.AFTER_NUMBER; - } - else { - fail(); - } - break; - case AFTER_NUMBER: - if (token instanceof MyMarkerToken || token instanceof MyOperationToken) { - char sign = token instanceof MyOperationToken ? ((MyOperationToken)token).mySign : MARKER; - if (sign == MARKER || sign == '+') { - if (!invokeTemplateSeveralTimes(templateToken, 0, number, finalI)) { - return false; - } - templateToken = null; - } - else if (number > 1) { - return invokeTemplateAndProcessTail(templateToken, 0, number, i + 1); - } - else { - assert number == 1; - if (!startTemplateAndGotoChild(templateToken, finalI)) { - return false; - } - templateToken = null; - } - myState = MyState.WORD; - } - else { - fail(); - } - break; - } - } - finish(startIndex == n); - return true; - } - - private boolean startTemplateAndGotoChild(MyTemplateToken templateToken, final int index) { - final Object key = new Object(); - myCallback.fixStartOfTemplate(key); - TemplateInvokationListener listener = new TemplateInvokationListener() { - public void finished(boolean inSeparateEvent) { - myState = MyState.WORD; - gotoChild(key); - if (inSeparateEvent) { - invoke(index + 1); - } - } - }; - if (!invokeTemplate(templateToken, myCallback, listener, -1)) { - return false; - } - return true; - } - - private boolean invokeTemplateSeveralTimes(final MyTemplateToken templateToken, - final int startIndex, - final int count, - final int globalIndex) { - final Object key = new Object(); - myCallback.fixStartOfTemplate(key); - for (int i = startIndex; i < count; i++) { - final int finalI = i; - TemplateInvokationListener listener = new TemplateInvokationListener() { - public void finished(boolean inSeparateEvent) { - myState = MyState.WORD; - if (myCallback.getOffset() != myCallback.getEndOfTemplate(key)) { - myCallback.fixEndOffset(); - } - myCallback.gotoEndOfTemplate(key); - if (inSeparateEvent) { - if (finalI + 1 < count) { - invokeTemplateSeveralTimes(templateToken, finalI + 1, count, globalIndex); - } - else { - invoke(globalIndex + 1); - } - } - } - }; - if (!invokeTemplate(templateToken, myCallback, listener, i)) { - return false; - } - } - return true; - } - - private boolean invokeTemplateAndProcessTail(final MyTemplateToken templateToken, - final int startIndex, - final int count, - final int tailStart) { - final Object key = new Object(); - myCallback.fixStartOfTemplate(key); - for (int i = startIndex; i < count; i++) { - final int finalI = i; - final boolean[] flag = new boolean[]{false}; - TemplateInvokationListener listener = new TemplateInvokationListener() { - public void finished(boolean inSeparateEvent) { - gotoChild(key); - MyInterpreter interpreter = new MyInterpreter(myTokens, myCallback, MyState.WORD, new TemplateInvokationListener() { - public void finished(boolean inSeparateEvent) { - if (myCallback.getOffset() != myCallback.getEndOfTemplate(key)) { - myCallback.fixEndOffset(); - } - myCallback.gotoEndOfTemplate(key); - if (inSeparateEvent) { - invokeTemplateAndProcessTail(templateToken, finalI + 1, count, tailStart); - } - } - }); - if (interpreter.invoke(tailStart)) { - if (inSeparateEvent) { - invokeTemplateAndProcessTail(templateToken, finalI + 1, count, tailStart); - } - } - else { - flag[0] = true; - } - } - }; - if (!invokeTemplate(templateToken, myCallback, listener, i) || flag[0]) { - return false; - } - } - finish(count == 0); - return true; - } - } -} \ No newline at end of file diff --git a/xml/impl/src/com/intellij/codeInsight/template/zencoding/MarkerToken.java b/xml/impl/src/com/intellij/codeInsight/template/zencoding/MarkerToken.java new file mode 100644 index 000000000000..ba9c9af1b9c8 --- /dev/null +++ b/xml/impl/src/com/intellij/codeInsight/template/zencoding/MarkerToken.java @@ -0,0 +1,22 @@ +/* + * Copyright 2000-2010 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.zencoding; + +/** + * @author Eugene.Kudelevsky + */ +class MarkerToken extends Token { +} diff --git a/plugins/maven/src/main/java/org/jetbrains/idea/maven/project/actions/EditTemplateSettingsAction.java b/xml/impl/src/com/intellij/codeInsight/template/zencoding/NumberToken.java similarity index 58% rename from plugins/maven/src/main/java/org/jetbrains/idea/maven/project/actions/EditTemplateSettingsAction.java rename to xml/impl/src/com/intellij/codeInsight/template/zencoding/NumberToken.java index 5586e3aefa73..4adf4f763c59 100644 --- a/plugins/maven/src/main/java/org/jetbrains/idea/maven/project/actions/EditTemplateSettingsAction.java +++ b/xml/impl/src/com/intellij/codeInsight/template/zencoding/NumberToken.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2009 JetBrains s.r.o. + * Copyright 2000-2010 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. @@ -13,14 +13,15 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.jetbrains.idea.maven.project.actions; +package com.intellij.codeInsight.template.zencoding; -import com.intellij.openapi.actionSystem.AnActionEvent; -import com.intellij.openapi.project.ProjectManager; +/** + * @author Eugene.Kudelevsky + */ +class NumberToken extends Token { + final int myNumber; -public class EditTemplateSettingsAction extends EditSettingsAction { - @Override - public void actionPerformed(AnActionEvent e) { - showSettingsFor(ProjectManager.getInstance().getDefaultProject()); + NumberToken(int number) { + myNumber = number; } -} \ No newline at end of file +} diff --git a/xml/impl/src/com/intellij/codeInsight/template/zencoding/OperationToken.java b/xml/impl/src/com/intellij/codeInsight/template/zencoding/OperationToken.java new file mode 100644 index 000000000000..63bb2d343e6c --- /dev/null +++ b/xml/impl/src/com/intellij/codeInsight/template/zencoding/OperationToken.java @@ -0,0 +1,27 @@ +/* + * Copyright 2000-2010 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.zencoding; + +/** + * @author Eugene.Kudelevsky + */ +class OperationToken extends Token { + final char mySign; + + OperationToken(char sign) { + mySign = sign; + } +} diff --git a/xml/impl/src/com/intellij/codeInsight/template/zencoding/State.java b/xml/impl/src/com/intellij/codeInsight/template/zencoding/State.java new file mode 100644 index 000000000000..23ff80b9cc43 --- /dev/null +++ b/xml/impl/src/com/intellij/codeInsight/template/zencoding/State.java @@ -0,0 +1,23 @@ +/* + * Copyright 2000-2010 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.zencoding; + +/** + * @author Eugene.Kudelevsky + */ +enum State { + OPERATION, WORD, AFTER_NUMBER, NUMBER +} diff --git a/xml/impl/src/com/intellij/codeInsight/template/zencoding/TemplateToken.java b/xml/impl/src/com/intellij/codeInsight/template/zencoding/TemplateToken.java new file mode 100644 index 000000000000..bb9b2f96b0f6 --- /dev/null +++ b/xml/impl/src/com/intellij/codeInsight/template/zencoding/TemplateToken.java @@ -0,0 +1,35 @@ +/* + * Copyright 2000-2010 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.zencoding; + +import com.intellij.codeInsight.template.impl.TemplateImpl; +import com.intellij.openapi.util.Pair; + +import java.util.List; + +/** + * @author Eugene.Kudelevsky + */ +class TemplateToken extends Token { + final String myKey; + final List> myAttribute2Value; + TemplateImpl myTemplate; + + TemplateToken(String key, List> attribute2value) { + myKey = key; + myAttribute2Value = attribute2value; + } +} diff --git a/xml/impl/src/com/intellij/codeInsight/template/zencoding/Token.java b/xml/impl/src/com/intellij/codeInsight/template/zencoding/Token.java new file mode 100644 index 000000000000..082b83bf2f16 --- /dev/null +++ b/xml/impl/src/com/intellij/codeInsight/template/zencoding/Token.java @@ -0,0 +1,22 @@ +/* + * Copyright 2000-2010 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.zencoding; + +/** + * @author Eugene.Kudelevsky + */ +class Token { +} diff --git a/xml/impl/src/com/intellij/codeInsight/template/zencoding/XmlZenCodingInterpreter.java b/xml/impl/src/com/intellij/codeInsight/template/zencoding/XmlZenCodingInterpreter.java new file mode 100644 index 000000000000..eab488d1498d --- /dev/null +++ b/xml/impl/src/com/intellij/codeInsight/template/zencoding/XmlZenCodingInterpreter.java @@ -0,0 +1,439 @@ +/* + * Copyright 2000-2010 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.zencoding; + +import com.intellij.codeInsight.template.CustomTemplateCallback; +import com.intellij.codeInsight.template.TemplateInvokationListener; +import com.intellij.codeInsight.template.impl.TemplateImpl; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.util.Pair; +import com.intellij.openapi.util.TextRange; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiFile; +import com.intellij.psi.util.PsiTreeUtil; +import com.intellij.psi.xml.*; +import com.intellij.util.containers.HashMap; +import com.intellij.util.containers.HashSet; +import com.intellij.util.containers.IntArrayList; +import org.jetbrains.annotations.Nullable; + +import java.util.*; + +/** + * @author Eugene.Kudelevsky + */ +class XmlZenCodingInterpreter { + private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.template.zencoding.XmlZenCodingInterpreter"); + private static final String ATTRS = "ATTRS"; + + private final List myTokens; + private final CustomTemplateCallback myCallback; + private final TemplateInvokationListener myListener; + private static final String NUMBER_IN_ITERATION_PLACE_HOLDER = "$"; + private State myState; + + XmlZenCodingInterpreter(List tokens, + CustomTemplateCallback callback, + State initialState, + TemplateInvokationListener listener) { + myTokens = tokens; + myCallback = callback; + myListener = listener; + myState = initialState; + } + + private void finish(boolean inSeparateEvent) { + myCallback.gotoEndOffset(); + if (myListener != null) { + myListener.finished(inSeparateEvent); + } + } + + private void gotoChild(Object templateBoundsKey) { + int startOfTemplate = myCallback.getStartOfTemplate(templateBoundsKey); + int endOfTemplate = myCallback.getEndOfTemplate(templateBoundsKey); + Editor editor = myCallback.getEditor(); + int offset = myCallback.getOffset(); + + PsiFile file = myCallback.getFile(); + + PsiElement element = file.findElementAt(offset); + if (element instanceof XmlToken && ((XmlToken)element).getTokenType() == XmlTokenType.XML_END_TAG_START) { + return; + } + + int newOffset = -1; + XmlTag tag = PsiTreeUtil.findElementOfClassAtRange(file, startOfTemplate, endOfTemplate, XmlTag.class); + if (tag != null) { + for (PsiElement child : tag.getChildren()) { + if (child instanceof XmlToken && ((XmlToken)child).getTokenType() == XmlTokenType.XML_END_TAG_START) { + newOffset = child.getTextOffset(); + } + } + } + + if (newOffset >= 0) { + myCallback.fixEndOffset(); + editor.getCaretModel().moveToOffset(newOffset); + } + + /*CharSequence tagName = getPrecedingTagName(text, offset, startOfTemplate); + if (tagName != null) { + *//*if (!hasClosingTag(text, tagName, offset, endOfTemplate)) { + document.insertString(offset, "'); + }*//* + } + else if (offset != endOfTemplate) { + tagName = getPrecedingTagName(text, endOfTemplate, startOfTemplate); + if (tagName != null) { + *//*fixEndOffset(); + document.insertString(endOfTemplate, "');*//* + editor.getCaretModel().moveToOffset(endOfTemplate); + } + }*/ + } + + public boolean invoke(int startIndex) { + final int n = myTokens.size(); + TemplateToken templateToken = null; + int number = -1; + for (int i = startIndex; i < n; i++) { + final int finalI = i; + Token token = myTokens.get(i); + switch (myState) { + case OPERATION: + if (templateToken != null) { + if (token instanceof MarkerToken || token instanceof OperationToken) { + final char sign = token instanceof OperationToken ? ((OperationToken)token).mySign : XmlZenCodingTemplate.MARKER; + if (sign == XmlZenCodingTemplate.MARKER || sign == '+') { + final Object key = new Object(); + myCallback.fixStartOfTemplate(key); + TemplateInvokationListener listener = new TemplateInvokationListener() { + public void finished(boolean inSeparateEvent) { + myState = State.WORD; + if (myCallback.getOffset() != myCallback.getEndOfTemplate(key)) { + myCallback.fixEndOffset(); + } + if (sign == '+') { + myCallback.gotoEndOfTemplate(key); + } + if (inSeparateEvent) { + invoke(finalI + 1); + } + } + }; + if (!invokeTemplate(templateToken, myCallback, listener, 0)) { + return false; + } + templateToken = null; + } + else if (sign == '>') { + if (!startTemplateAndGotoChild(templateToken, finalI)) { + return false; + } + templateToken = null; + } + else if (sign == '*') { + myState = State.NUMBER; + } + } + else { + fail(); + } + } + break; + case WORD: + if (token instanceof TemplateToken) { + templateToken = ((TemplateToken)token); + myState = State.OPERATION; + } + else { + fail(); + } + break; + case NUMBER: + if (token instanceof NumberToken) { + number = ((NumberToken)token).myNumber; + myState = State.AFTER_NUMBER; + } + else { + fail(); + } + break; + case AFTER_NUMBER: + if (token instanceof MarkerToken || token instanceof OperationToken) { + char sign = token instanceof OperationToken ? ((OperationToken)token).mySign : XmlZenCodingTemplate.MARKER; + if (sign == XmlZenCodingTemplate.MARKER || sign == '+') { + if (!invokeTemplateSeveralTimes(templateToken, 0, number, finalI)) { + return false; + } + templateToken = null; + } + else if (number > 1) { + return invokeTemplateAndProcessTail(templateToken, 0, number, i + 1); + } + else { + assert number == 1; + if (!startTemplateAndGotoChild(templateToken, finalI)) { + return false; + } + templateToken = null; + } + myState = State.WORD; + } + else { + fail(); + } + break; + } + } + finish(startIndex == n); + return true; + } + + private boolean startTemplateAndGotoChild(TemplateToken templateToken, final int index) { + final Object key = new Object(); + myCallback.fixStartOfTemplate(key); + TemplateInvokationListener listener = new TemplateInvokationListener() { + public void finished(boolean inSeparateEvent) { + myState = State.WORD; + gotoChild(key); + if (inSeparateEvent) { + invoke(index + 1); + } + } + }; + if (!invokeTemplate(templateToken, myCallback, listener, 0)) { + return false; + } + return true; + } + + private boolean invokeTemplateSeveralTimes(final TemplateToken templateToken, + final int startIndex, + final int count, + final int globalIndex) { + final Object key = new Object(); + myCallback.fixStartOfTemplate(key); + for (int i = startIndex; i < count; i++) { + final int finalI = i; + TemplateInvokationListener listener = new TemplateInvokationListener() { + public void finished(boolean inSeparateEvent) { + myState = State.WORD; + if (myCallback.getOffset() != myCallback.getEndOfTemplate(key)) { + myCallback.fixEndOffset(); + } + myCallback.gotoEndOfTemplate(key); + if (inSeparateEvent) { + if (finalI + 1 < count) { + invokeTemplateSeveralTimes(templateToken, finalI + 1, count, globalIndex); + } + else { + invoke(globalIndex + 1); + } + } + } + }; + if (!invokeTemplate(templateToken, myCallback, listener, i)) { + return false; + } + } + return true; + } + + private boolean invokeTemplateAndProcessTail(final TemplateToken templateToken, + final int startIndex, + final int count, + final int tailStart) { + final Object key = new Object(); + myCallback.fixStartOfTemplate(key); + for (int i = startIndex; i < count; i++) { + final int finalI = i; + final boolean[] flag = new boolean[]{false}; + TemplateInvokationListener listener = new TemplateInvokationListener() { + public void finished(boolean inSeparateEvent) { + gotoChild(key); + XmlZenCodingInterpreter interpreter = + new XmlZenCodingInterpreter(myTokens, myCallback, State.WORD, new TemplateInvokationListener() { + public void finished(boolean inSeparateEvent) { + if (myCallback.getOffset() != myCallback.getEndOfTemplate(key)) { + myCallback.fixEndOffset(); + } + myCallback.gotoEndOfTemplate(key); + if (inSeparateEvent) { + invokeTemplateAndProcessTail(templateToken, finalI + 1, count, tailStart); + } + } + }); + if (interpreter.invoke(tailStart)) { + if (inSeparateEvent) { + invokeTemplateAndProcessTail(templateToken, finalI + 1, count, tailStart); + } + } + else { + flag[0] = true; + } + } + }; + if (!invokeTemplate(templateToken, myCallback, listener, i) || flag[0]) { + return false; + } + } + finish(count == 0); + return true; + } + + private static boolean containsAttrsVar(TemplateImpl template) { + for (int i = 0; i < template.getVariableCount(); i++) { + String varName = template.getVariableNameAt(i); + if (ATTRS.equals(varName)) { + return true; + } + } + return false; + } + + private static void removeVariablesWhichHasNoSegment(TemplateImpl template) { + Set segments = new HashSet(); + for (int i = 0; i < template.getSegmentsCount(); i++) { + segments.add(template.getSegmentName(i)); + } + IntArrayList varsToRemove = new IntArrayList(); + for (int i = 0; i < template.getVariableCount(); i++) { + String varName = template.getVariableNameAt(i); + if (!segments.contains(varName)) { + varsToRemove.add(i); + } + } + for (int i = 0; i < varsToRemove.size(); i++) { + template.removeVariable(varsToRemove.get(i)); + } + } + + @Nullable + private static Map buildPredefinedValues(List> attribute2value, int numberInIteration) { + StringBuilder result = new StringBuilder(); + for (Iterator> it = attribute2value.iterator(); it.hasNext();) { + Pair pair = it.next(); + String name = pair.first; + String value = getValue(pair, numberInIteration); + result.append(name).append("=\"").append(value).append('"'); + if (it.hasNext()) { + result.append(' '); + } + } + String attributes = result.toString(); + attributes = attributes.length() > 0 ? ' ' + attributes : null; + Map predefinedValues = null; + if (attributes != null) { + predefinedValues = new HashMap(); + predefinedValues.put(ATTRS, attributes); + } + return predefinedValues; + } + + private static String getValue(Pair pair, int numberInIteration) { + return pair.second.replace(NUMBER_IN_ITERATION_PLACE_HOLDER, Integer.toString(numberInIteration + 1)); + } + + @Nullable + private static String addAttrsVar(TemplateImpl modifiedTemplate, XmlTag tag) { + String text = tag.getContainingFile().getText(); + PsiElement[] children = tag.getChildren(); + if (children.length >= 1 && + children[0] instanceof XmlToken && + ((XmlToken)children[0]).getTokenType() == XmlTokenType.XML_START_TAG_START) { + PsiElement beforeAttrs = children[0]; + if (children.length >= 2 && children[1] instanceof XmlToken && ((XmlToken)children[1]).getTokenType() == XmlTokenType.XML_NAME) { + beforeAttrs = children[1]; + } + TextRange range = beforeAttrs.getTextRange(); + if (range == null) { + return null; + } + int offset = range.getEndOffset(); + text = text.substring(0, offset) + " $ATTRS$" + text.substring(offset); + modifiedTemplate.addVariable(ATTRS, "", "", false); + return text; + } + return null; + } + + private static boolean invokeTemplate(TemplateToken token, + final CustomTemplateCallback callback, + final TemplateInvokationListener listener, + int numberInIteration) { + List> attr2value = new ArrayList>(token.myAttribute2Value); + if (callback.isLiveTemplateApplicable(token.myKey)) { + if (token.myTemplate != null) { + TemplateImpl modifiedTemplate = token.myTemplate.copy(); + XmlTag tag = XmlZenCodingTemplate.parseXmlTagInTemplate(token.myTemplate.getString(), callback.getProject()); + assert tag != null; + for (Iterator> iterator = attr2value.iterator(); iterator.hasNext();) { + Pair pair = iterator.next(); + if (tag.getAttribute(pair.first) != null) { + tag.setAttribute(pair.first, getValue(pair, numberInIteration)); + iterator.remove(); + } + } + String text = null; + if (!containsAttrsVar(modifiedTemplate) && attr2value.size() > 0) { + String textWithAttrs = addAttrsVar(modifiedTemplate, tag); + if (textWithAttrs != null) { + text = textWithAttrs; + } + else { + for (Iterator> iterator = attr2value.iterator(); iterator.hasNext();) { + Pair pair = iterator.next(); + tag.setAttribute(pair.first, getValue(pair, numberInIteration)); + iterator.remove(); + } + } + } + if (text == null) { + text = tag.getContainingFile().getText(); + } + modifiedTemplate.setString(text); + removeVariablesWhichHasNoSegment(modifiedTemplate); + Map predefinedValues = buildPredefinedValues(attr2value, numberInIteration); + return callback.startTemplate(modifiedTemplate, predefinedValues, listener); + } + else { + Map predefinedValues = buildPredefinedValues(attr2value, numberInIteration); + return callback.startTemplate(token.myKey, predefinedValues, listener); + } + } + else { + TemplateImpl template = new TemplateImpl("", ""); + template.addTextSegment('<' + token.myKey); + if (attr2value.size() > 0) { + template.addVariable(ATTRS, "", "", false); + template.addVariableSegment(ATTRS); + } + template.addTextSegment(">"); + template.addVariableSegment(TemplateImpl.END); + template.addTextSegment(""); + template.setToReformat(true); + Map predefinedValues = buildPredefinedValues(attr2value, numberInIteration); + return callback.startTemplate(template, predefinedValues, listener); + } + } + + private static void fail() { + LOG.error("Input string was checked incorrectly during isApplicable() invokation"); + } +} diff --git a/xml/impl/src/com/intellij/codeInsight/template/zencoding/XmlZenCodingTemplate.java b/xml/impl/src/com/intellij/codeInsight/template/zencoding/XmlZenCodingTemplate.java new file mode 100644 index 000000000000..29fdbb908743 --- /dev/null +++ b/xml/impl/src/com/intellij/codeInsight/template/zencoding/XmlZenCodingTemplate.java @@ -0,0 +1,349 @@ +/* + * Copyright 2000-2010 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.zencoding; + +import com.intellij.application.options.editor.WebEditorOptions; +import com.intellij.codeInsight.template.CustomLiveTemplate; +import com.intellij.codeInsight.template.CustomTemplateCallback; +import com.intellij.codeInsight.template.TemplateInvokationListener; +import com.intellij.codeInsight.template.impl.TemplateImpl; +import com.intellij.lang.xml.XMLLanguage; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Pair; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiFile; +import com.intellij.psi.PsiFileFactory; +import com.intellij.psi.xml.*; +import com.intellij.util.containers.HashSet; +import org.apache.xerces.util.XML11Char; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.*; + +/** + * @author Eugene.Kudelevsky + */ +public class XmlZenCodingTemplate implements CustomLiveTemplate { + static final char MARKER = '$'; + private static final String OPERATIONS = ">+*"; + private static final String SELECTORS = ".#["; + private static final String ID = "id"; + private static final String CLASS = "class"; + + private static int parseNonNegativeInt(@NotNull String s) { + try { + return Integer.parseInt(s); + } + catch (Throwable ignored) { + } + return -1; + } + + private static String getPrefix(@NotNull String templateKey) { + for (int i = 0, n = templateKey.length(); i < n; i++) { + char c = templateKey.charAt(i); + if (SELECTORS.indexOf(c) >= 0) { + return templateKey.substring(0, i); + } + } + return templateKey; + } + + @Nullable + private static Pair parseAttrNameAndValue(@NotNull String text) { + int eqIndex = text.indexOf('='); + if (eqIndex > 0) { + return new Pair(text.substring(0, eqIndex), text.substring(eqIndex + 1)); + } + return null; + } + + @Nullable + private static TemplateToken parseSelectors(@NotNull String text) { + String templateKey = null; + List> attributes = new ArrayList>(); + Set definedAttrs = new HashSet(); + final List classes = new ArrayList(); + StringBuilder builder = new StringBuilder(); + char lastDelim = 0; + text += MARKER; + int classAttrPosition = -1; + for (int i = 0, n = text.length(); i < n; i++) { + char c = text.charAt(i); + if (c == '#' || c == '.' || c == '[' || c == ']' || i == n - 1) { + if (c != ']') { + switch (lastDelim) { + case 0: + templateKey = builder.toString(); + break; + case '#': + if (!definedAttrs.add(ID)) { + return null; + } + attributes.add(new Pair(ID, builder.toString())); + break; + case '.': + if (builder.length() <= 0) { + return null; + } + if (classAttrPosition < 0) { + classAttrPosition = attributes.size(); + } + classes.add(builder.toString()); + break; + case ']': + if (builder.length() > 0) { + return null; + } + break; + default: + return null; + } + } + else if (lastDelim != '[') { + return null; + } + else { + Pair pair = parseAttrNameAndValue(builder.toString()); + if (pair == null || !definedAttrs.add(pair.first)) { + return null; + } + attributes.add(pair); + } + lastDelim = c; + builder = new StringBuilder(); + } + else { + builder.append(c); + } + } + if (classes.size() > 0) { + if (definedAttrs.contains(CLASS)) { + return null; + } + StringBuilder classesAttrValue = new StringBuilder(); + for (int i = 0; i < classes.size(); i++) { + classesAttrValue.append(classes.get(i)); + if (i < classes.size() - 1) { + classesAttrValue.append(' '); + } + } + assert classAttrPosition >= 0; + attributes.add(classAttrPosition, new Pair(CLASS, classesAttrValue.toString())); + } + return new TemplateToken(templateKey, attributes); + } + + private static boolean isXML11ValidQName(String str) { + final int colon = str.indexOf(':'); + if (colon == 0 || colon == str.length() - 1) { + return false; + } + if (colon > 0) { + final String prefix = str.substring(0, colon); + final String localPart = str.substring(colon + 1); + return XML11Char.isXML11ValidNCName(prefix) && XML11Char.isXML11ValidNCName(localPart); + } + return XML11Char.isXML11ValidNCName(str); + } + + private static boolean generateTemplateAndAddToToken(TemplateToken token, CustomTemplateCallback callback) { + TemplateImpl template = callback.findApplicableTemplate(token.myKey); + assert template != null; + XmlTag tag = parseXmlTagInTemplate(template.getString(), callback.getProject()); + if (tag == null) { + return false; + } + token.myTemplate = template; + return true; + } + + @Nullable + private static List parse(@NotNull String text, @NotNull CustomTemplateCallback callback) { + text += MARKER; + StringBuilder templateKeyBuilder = new StringBuilder(); + List result = new ArrayList(); + for (int i = 0, n = text.length(); i < n; i++) { + char c = text.charAt(i); + if (i == n - 1 || (i < n - 2 && OPERATIONS.indexOf(c) >= 0)) { + String key = templateKeyBuilder.toString(); + templateKeyBuilder = new StringBuilder(); + int num = parseNonNegativeInt(key); + if (num > 0) { + result.add(new NumberToken(num)); + } + else { + if (key.length() == 0) { + return null; + } + String prefix = getPrefix(key); + boolean applicable = callback.isLiveTemplateApplicable(prefix); + if (!applicable && !isXML11ValidQName(prefix)) { + return null; + } + TemplateToken token = parseSelectors(key); + if (token == null) { + return null; + } + if (applicable && token.myAttribute2Value.size() > 0) { + assert prefix.equals(token.myKey); + if (!generateTemplateAndAddToToken(token, callback)) { + return null; + } + } + result.add(token); + } + result.add(i < n - 1 ? new OperationToken(c) : new MarkerToken()); + } + else if (!Character.isWhitespace(c)) { + templateKeyBuilder.append(c); + } + else { + return null; + } + } + return result; + } + + private static boolean check(@NotNull Collection tokens) { + State state = State.WORD; + for (Token token : tokens) { + if (token instanceof MarkerToken) { + break; + } + switch (state) { + case OPERATION: + if (token instanceof OperationToken) { + state = ((OperationToken)token).mySign == '*' ? State.NUMBER : State.WORD; + } + else { + return false; + } + break; + case WORD: + if (token instanceof TemplateToken) { + state = State.OPERATION; + } + else { + return false; + } + break; + case NUMBER: + if (token instanceof NumberToken) { + state = State.AFTER_NUMBER; + } + else { + return false; + } + break; + case AFTER_NUMBER: + if (token instanceof OperationToken && ((OperationToken)token).mySign != '*') { + state = State.WORD; + } + else { + return false; + } + break; + } + } + return state == State.OPERATION || state == State.AFTER_NUMBER; + } + + private static String computeKey(Editor editor, int startOffset) { + int offset = editor.getCaretModel().getOffset(); + String s = editor.getDocument().getCharsSequence().subSequence(startOffset, offset).toString(); + int index = 0; + while (index < s.length() && Character.isWhitespace(s.charAt(index))) { + index++; + } + String key = s.substring(index); + int lastWhitespaceIndex = -1; + for (int i = 0; i < key.length(); i++) { + if (Character.isWhitespace(key.charAt(i))) { + lastWhitespaceIndex = i; + } + } + if (lastWhitespaceIndex >= 0 && lastWhitespaceIndex < key.length() - 1) { + return key.substring(lastWhitespaceIndex + 1); + } + return key; + } + + public String computeTemplateKey(@NotNull CustomTemplateCallback callback) { + Editor editor = callback.getEditor(); + int offset = callback.getOffset(); + PsiElement element = callback.getFile().findElementAt(offset > 0 ? offset - 1 : offset); + int line = editor.getCaretModel().getLogicalPosition().line; + int lineStart = editor.getDocument().getLineStartOffset(line); + int parentStart; + do { + parentStart = element != null ? element.getTextRange().getStartOffset() : 0; + int startOffset = parentStart > lineStart ? parentStart : lineStart; + String key = computeKey(editor, startOffset); + List tokens = parse(key, callback); + if (tokens != null && check(tokens)) { + if (tokens.size() == 2) { + Token token = tokens.get(0); + if (token instanceof TemplateToken) { + if (key.equals(((TemplateToken)token).myKey) && callback.isLiveTemplateApplicable(key)) { + // do not activate only live template + return null; + } + } + } + return key; + } + if (element != null) { + element = element.getParent(); + } + } + while (element != null && parentStart > lineStart); + return null; + } + + public boolean isApplicable(PsiFile file, int offset, boolean selection) { + WebEditorOptions webEditorOptions = WebEditorOptions.getInstance(); + if (!webEditorOptions.isZenCodingEnabled()) { + return false; + } + if (file.getLanguage() instanceof XMLLanguage) { + PsiElement element = file.findElementAt(offset > 0 ? offset - 1 : offset); + if (element == null || element.getLanguage() instanceof XMLLanguage) { + return true; + } + } + return false; + } + + public void expand(String key, @NotNull CustomTemplateCallback callback, @Nullable TemplateInvokationListener listener) { + List tokens = parse(key, callback); + assert tokens != null; + XmlZenCodingInterpreter interpreter = new XmlZenCodingInterpreter(tokens, callback, State.WORD, listener); + interpreter.invoke(0); + } + + public void wrap(String selection, @NotNull CustomTemplateCallback callback, @Nullable TemplateInvokationListener listener) { + } + + @Nullable + static XmlTag parseXmlTagInTemplate(String templateString, Project project) { + XmlFile xmlFile = (XmlFile)PsiFileFactory.getInstance(project).createFileFromText("dummy.xml", templateString); + XmlDocument document = xmlFile.getDocument(); + return document == null ? null : document.getRootTag(); + } +} \ No newline at end of file diff --git a/xml/impl/src/com/intellij/codeInspection/htmlInspections/HtmlUnknownAttributeInspection.java b/xml/impl/src/com/intellij/codeInspection/htmlInspections/HtmlUnknownAttributeInspection.java index ad654d123812..8d6ffbfa17bc 100644 --- a/xml/impl/src/com/intellij/codeInspection/htmlInspections/HtmlUnknownAttributeInspection.java +++ b/xml/impl/src/com/intellij/codeInspection/htmlInspections/HtmlUnknownAttributeInspection.java @@ -16,8 +16,9 @@ package com.intellij.codeInspection.htmlInspections; -import com.intellij.codeInspection.ProblemsHolder; +import com.intellij.codeInsight.daemon.XmlErrorMessages; import com.intellij.codeInspection.ProblemHighlightType; +import com.intellij.codeInspection.ProblemsHolder; import com.intellij.lang.ASTNode; import com.intellij.openapi.diagnostic.Logger; import com.intellij.psi.PsiElement; @@ -30,7 +31,6 @@ import com.intellij.xml.XmlBundle; import com.intellij.xml.XmlElementDescriptor; import com.intellij.xml.impl.schema.AnyXmlElementDescriptor; import com.intellij.xml.util.XmlUtil; -import com.intellij.codeInsight.daemon.XmlErrorMessages; import org.jetbrains.annotations.Nls; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; @@ -87,8 +87,8 @@ public class HtmlUnknownAttributeInspection extends HtmlUnknownTagInspection { XmlAttributeDescriptor attributeDescriptor = elementDescriptor.getAttributeDescriptor(attribute); final String name = attribute.getName(); - - if (attributeDescriptor == null) { + + if (attributeDescriptor == null && !attribute.isNamespaceDeclaration()) { if (!XmlUtil.attributeFromTemplateFramework(name, tag) && (!isCustomValuesEnabled() || !isCustomValue(name))) { final ASTNode node = attribute.getNode(); assert node != null; diff --git a/xml/impl/src/com/intellij/psi/impl/source/html/dtd/HtmlElementDescriptorImpl.java b/xml/impl/src/com/intellij/psi/impl/source/html/dtd/HtmlElementDescriptorImpl.java index d831839b0bc9..70ed9f06ff02 100644 --- a/xml/impl/src/com/intellij/psi/impl/source/html/dtd/HtmlElementDescriptorImpl.java +++ b/xml/impl/src/com/intellij/psi/impl/source/html/dtd/HtmlElementDescriptorImpl.java @@ -22,6 +22,8 @@ import com.intellij.xml.XmlAttributeDescriptor; import com.intellij.xml.XmlElementDescriptor; import com.intellij.xml.XmlNSDescriptor; import com.intellij.xml.impl.dtd.BaseXmlElementDescriptorImpl; +import com.intellij.xml.impl.schema.XmlNSDescriptorImpl; +import com.intellij.xml.util.XmlUtil; import java.util.HashMap; @@ -96,6 +98,18 @@ public class HtmlElementDescriptorImpl extends BaseXmlElementDescriptorImpl { if (!myCaseSensitive) attributeName = attributeName.toLowerCase(); XmlAttributeDescriptor descriptor = super.getAttributeDescriptor(attributeName, context); if (descriptor == null) descriptor = RelaxedHtmlFromSchemaElementDescriptor.getAttributeDescriptorFromFacelets(attributeName, context); + + if (descriptor == null) { + String prefix = XmlUtil.findPrefixByQualifiedName(attributeName); + + if ("xml".equals(prefix)) { // todo this is not technically correct dtd document references namespaces but we should handle it at least for xml stuff + XmlNSDescriptor nsdescriptor = context.getNSDescriptor(XmlUtil.XML_NAMESPACE_URI, true); + if (nsdescriptor instanceof XmlNSDescriptorImpl) { + descriptor = ((XmlNSDescriptorImpl)nsdescriptor).getAttribute( + XmlUtil.findLocalNameByQualifiedName(attributeName), XmlUtil.XML_NAMESPACE_URI, context); + } + } + } return descriptor; } diff --git a/xml/impl/src/com/intellij/xml/util/CheckEmptyTagInspection.java b/xml/impl/src/com/intellij/xml/util/CheckEmptyTagInspection.java index 76e1624c89cd..728e91eab011 100644 --- a/xml/impl/src/com/intellij/xml/util/CheckEmptyTagInspection.java +++ b/xml/impl/src/com/intellij/xml/util/CheckEmptyTagInspection.java @@ -49,7 +49,7 @@ import java.util.Set; public class CheckEmptyTagInspection extends XmlSuppressableInspectionTool { private static final Logger LOG = Logger.getInstance("#com.intellij.xml.util.CheckEmptyTagInspection"); @NonNls private static final String SCRIPT_TAG_NAME = "script"; - private static Set ourTagsWithEmptyEndsNotAllowed = new THashSet(Arrays.asList(SCRIPT_TAG_NAME, "div", "iframe")); + private static final Set ourTagsWithEmptyEndsNotAllowed = new THashSet(Arrays.asList(SCRIPT_TAG_NAME, "div", "iframe")); public boolean isEnabledByDefault() { return true; @@ -59,55 +59,57 @@ public class CheckEmptyTagInspection extends XmlSuppressableInspectionTool { public PsiElementVisitor buildVisitor(@NotNull final ProblemsHolder holder, boolean isOnTheFly) { return new XmlElementVisitor() { @Override public void visitXmlTag(final XmlTag tag) { - if (isTagWithEmptyEndNotAllowed(tag)) { - final ASTNode child = XmlChildRole.EMPTY_TAG_END_FINDER.findChild(tag.getNode()); - - if (child != null) { - - final LocalQuickFix fix = new LocalQuickFix() { - @NotNull - public String getName() { - return XmlBundle.message("html.inspections.check.empty.script.tag.fix.message"); - } - - public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) { - final XmlTag tag = (XmlTag)descriptor.getPsiElement(); - if (tag == null) return; - final PsiFile psiFile = tag.getContainingFile(); - - if (psiFile == null) return; - ReadonlyStatusHandler.getInstance(project).ensureFilesWritable(psiFile.getVirtualFile()); - - final StringBuilder builder = new StringBuilder(tag.getText()); - builder.replace(builder.length() - 2, builder.length(), ">"); - - try { - final FileType fileType = psiFile.getFileType(); - PsiFile file = PsiFileFactory.getInstance(tag.getProject()).createFileFromText( - "dummy." + (fileType == StdFileTypes.JSP || tag.getContainingFile().getLanguage() == HTMLLanguage.INSTANCE ? "html" : "xml"), builder.toString()); - - tag.replace(((XmlFile)file).getDocument().getRootTag()); - } - catch (IncorrectOperationException e) { - LOG.error(e); - } - } - - //to appear in "Apply Fix" statement when multiple Quick Fixes exist - @NotNull - public String getFamilyName() { - return getName(); - } - }; - - holder.registerProblem(tag, - XmlBundle.message("html.inspections.check.empty.script.message"), - tag.getContainingFile().getContext() != null ? - ProblemHighlightType.INFORMATION: - ProblemHighlightType.GENERIC_ERROR_OR_WARNING, - fix); - } + if (!isTagWithEmptyEndNotAllowed(tag)) { + return; } + final ASTNode child = XmlChildRole.EMPTY_TAG_END_FINDER.findChild(tag.getNode()); + + if (child == null) { + return; + } + + final LocalQuickFix fix = new LocalQuickFix() { + @NotNull + public String getName() { + return XmlBundle.message("html.inspections.check.empty.script.tag.fix.message"); + } + + public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) { + final XmlTag tag = (XmlTag)descriptor.getPsiElement(); + if (tag == null) return; + final PsiFile psiFile = tag.getContainingFile(); + + if (psiFile == null) return; + ReadonlyStatusHandler.getInstance(project).ensureFilesWritable(psiFile.getVirtualFile()); + + final StringBuilder builder = new StringBuilder(tag.getText()); + builder.replace(builder.length() - 2, builder.length(), ">"); + + try { + final FileType fileType = psiFile.getFileType(); + PsiFile file = PsiFileFactory.getInstance(tag.getProject()).createFileFromText( + "dummy." + (fileType == StdFileTypes.JSP || tag.getContainingFile().getLanguage() == HTMLLanguage.INSTANCE ? "html" : "xml"), builder.toString()); + + tag.replace(((XmlFile)file).getDocument().getRootTag()); + } + catch (IncorrectOperationException e) { + LOG.error(e); + } + } + + //to appear in "Apply Fix" statement when multiple Quick Fixes exist + @NotNull + public String getFamilyName() { + return getName(); + } + }; + + holder.registerProblem(tag, + XmlBundle.message("html.inspections.check.empty.script.message"), + tag.getContainingFile().getContext() != null ? + ProblemHighlightType.INFORMATION: + ProblemHighlightType.GENERIC_ERROR_OR_WARNING, + fix); } }; } @@ -117,11 +119,8 @@ public class CheckEmptyTagInspection extends XmlSuppressableInspectionTool { if (tag instanceof HtmlTag) tagName = tagName.toLowerCase(); Language language = tag.getLanguage(); - return (ourTagsWithEmptyEndsNotAllowed.contains(tagName) && - language != XMLLanguage.INSTANCE) || - (language == HTMLLanguage.INSTANCE && - ( !HtmlUtil.isSingleHtmlTagL(tagName) && tagName.indexOf(':') == -1)) - ; + return ourTagsWithEmptyEndsNotAllowed.contains(tagName) && language != XMLLanguage.INSTANCE || + language == HTMLLanguage.INSTANCE && !HtmlUtil.isSingleHtmlTagL(tagName) && tagName.indexOf(':') == -1; } @NotNull