diff --git a/java/execution/impl/src/com/intellij/execution/junit/JUnitUtil.java b/java/execution/impl/src/com/intellij/execution/junit/JUnitUtil.java index 704938e4555b..1ea21942f526 100644 --- a/java/execution/impl/src/com/intellij/execution/junit/JUnitUtil.java +++ b/java/execution/impl/src/com/intellij/execution/junit/JUnitUtil.java @@ -21,6 +21,7 @@ import com.intellij.execution.junit2.info.MethodLocation; import com.intellij.execution.testframework.SourceScope; import com.intellij.openapi.module.Module; import com.intellij.openapi.module.ModuleUtil; +import com.intellij.openapi.progress.ProgressManager; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Condition; import com.intellij.psi.*; @@ -109,6 +110,7 @@ public class JUnitUtil { if (AnnotationUtil.isAnnotated(psiClass, RUN_WITH, true)) return true; for (final PsiMethod method : psiClass.getAllMethods()) { + ProgressManager.checkCanceled(); if (isSuiteMethod(method)) return true; if (isTestAnnotated(method)) return true; } @@ -131,6 +133,7 @@ public class JUnitUtil { if (modifierList == null) return false; if (AnnotationUtil.isAnnotated(psiClass, RUN_WITH, true)) return true; for (final PsiMethod method : psiClass.getAllMethods()) { + ProgressManager.checkCanceled(); if (isTestAnnotated(method)) return true; } diff --git a/java/java-impl/src/com/intellij/lang/java/parser/DeclarationParser.java b/java/java-impl/src/com/intellij/lang/java/parser/DeclarationParser.java index 34f5ae8bda41..cd7a35024b76 100644 --- a/java/java-impl/src/com/intellij/lang/java/parser/DeclarationParser.java +++ b/java/java-impl/src/com/intellij/lang/java/parser/DeclarationParser.java @@ -24,6 +24,7 @@ import com.intellij.psi.impl.source.tree.JavaElementType; import com.intellij.psi.tree.IElementType; import com.intellij.psi.tree.ILazyParseableElementType; import com.intellij.psi.tree.TokenSet; +import com.intellij.util.text.CharArrayUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -43,6 +44,9 @@ public class DeclarationParser { private static final TokenSet APPEND_TO_METHOD_SET = TokenSet.create( JavaTokenType.IDENTIFIER, JavaTokenType.COMMA, JavaTokenType.THROWS_KEYWORD); + private static final String WHITESPACES = "\n\r \t"; + private static final String LINE_ENDS = "\n\r"; + private DeclarationParser() { } public static void parseClassBodyWithBraces(final PsiBuilder builder, final boolean isAnnotation, final boolean isEnum) { @@ -209,6 +213,7 @@ public class DeclarationParser { } final PsiBuilder.Marker declaration = builder.mark(); + final int declarationStart = builder.getCurrentOffset(); final Pair modListInfo = parseModifierList(builder); final PsiBuilder.Marker modList = modListInfo.first; @@ -225,7 +230,7 @@ public class DeclarationParser { else if (ElementType.CLASS_KEYWORD_BIT_SET.contains(builder.getTokenType())) { final PsiBuilder.Marker root = parseClassFromKeyword(builder, declaration, false); if (context == Context.FILE) { - // todo: append following declarations to root + // todo: append following declarations to root (?) boolean declarationsAfterEnd = false; while (builder.getTokenType() != null && builder.getTokenType() != JavaTokenType.RBRACE) { @@ -344,7 +349,7 @@ public class DeclarationParser { if (typeParams != null) { typeParams.precede().errorBefore(JavaErrorMessages.message("unexpected.token"), type); } - return parseFieldOrLocalVariable(builder, declaration, context); + return parseFieldOrLocalVariable(builder, declaration, declarationStart, context); } @NotNull @@ -538,7 +543,7 @@ public class DeclarationParser { @Nullable private static PsiBuilder.Marker parseFieldOrLocalVariable(final PsiBuilder builder, final PsiBuilder.Marker declaration, - final Context context) { + final int declarationStart, final Context context) { final IElementType varType; if (context == Context.CLASS || context == Context.ANNOTATION_INTERFACE) { varType = JavaElementType.FIELD; @@ -553,46 +558,66 @@ public class DeclarationParser { } PsiBuilder.Marker variable = declaration; - boolean openMarker = true; + boolean unclosed = false; boolean eatSemicolon = true; - boolean expectSemicolon = true; + boolean shouldRollback; + boolean openMarker = true; while (true) { + shouldRollback = true; + if (!eatBrackets(builder)) { - expectSemicolon = false; + unclosed = true; } if (expect(builder, JavaTokenType.EQ)) { final PsiBuilder.Marker expr = ExpressionParser.parse(builder); - if (expr == null) { + if (expr != null) { + shouldRollback = false; + } + else { error(builder, JavaErrorMessages.message("expected.expression")); - expectSemicolon = false; + unclosed = true; break; } } - if (builder.getTokenType() == JavaTokenType.COMMA) { - variable.done(varType); - builder.advanceLexer(); - variable = builder.mark(); - } - else { + if (builder.getTokenType() != JavaTokenType.COMMA) break; + variable.done(varType); + builder.advanceLexer(); + + if (builder.getTokenType() != JavaTokenType.IDENTIFIER) { + error(builder, JavaErrorMessages.message("expected.identifier")); + unclosed = true; + eatSemicolon = false; + openMarker = false; break; } - if (!expect(builder, JavaTokenType.IDENTIFIER)) { - variable.drop(); - error(builder, JavaErrorMessages.message("expected.identifier")); - openMarker = false; - eatSemicolon = false; - break; - } + variable = builder.mark(); + builder.advanceLexer(); } - if (eatSemicolon) { - if (!expect(builder, JavaTokenType.SEMICOLON) && expectSemicolon) { + if (builder.getTokenType() == JavaTokenType.SEMICOLON && eatSemicolon) { + builder.advanceLexer(); + } + else { + // special treatment (see DeclarationParserTest.testMultiLineUnclosed()) + if (!builder.eof() && shouldRollback) { + final CharSequence text = builder.getOriginalText(); + final int spaceEnd = builder.getCurrentOffset(); + final int spaceStart = CharArrayUtil.shiftBackward(text, spaceEnd-1, WHITESPACES); + final int lineStart = CharArrayUtil.shiftBackwardUntil(text, spaceEnd, LINE_ENDS); + + if (declarationStart < lineStart && lineStart < spaceStart) { + final int newBufferEnd = CharArrayUtil.shiftForward(text, lineStart, WHITESPACES); + declaration.rollbackTo(); + return parse(stoppingBuilder(builder, newBufferEnd), context); + } + } + + if (!unclosed) { error(builder, JavaErrorMessages.message("expected.semicolon")); } - // todo: special treatment - see DeclarationParserTest.testMultiLineUnclosed() } if (openMarker) { diff --git a/java/java-impl/src/com/intellij/lang/java/parser/JavaParserUtil.java b/java/java-impl/src/com/intellij/lang/java/parser/JavaParserUtil.java index 6f949120096a..21e08de575e1 100644 --- a/java/java-impl/src/com/intellij/lang/java/parser/JavaParserUtil.java +++ b/java/java-impl/src/com/intellij/lang/java/parser/JavaParserUtil.java @@ -107,6 +107,25 @@ public class JavaParserUtil { } return (braceCount == 0 ? null : tokenType); } + + @Override + public boolean eof() { + return braceCount == 0 || super.eof(); + } + }; + } + + public static PsiBuilder stoppingBuilder(final PsiBuilder builder, final int stopAt) { + return new PsiBuilderAdapter(builder) { + @Override + public IElementType getTokenType() { + return getCurrentOffset() < stopAt ? super.getTokenType() : null; + } + + @Override + public boolean eof() { + return getCurrentOffset() < stopAt || super.eof(); + } }; } 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 c7b5702785e1..1f065c2513a6 100644 --- a/java/java-impl/src/com/intellij/psi/impl/PsiClassImplUtil.java +++ b/java/java-impl/src/com/intellij/psi/impl/PsiClassImplUtil.java @@ -21,7 +21,6 @@ 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; @@ -39,10 +38,7 @@ import com.intellij.psi.search.SearchScope; import com.intellij.psi.util.*; import com.intellij.ui.IconDeferrer; import com.intellij.ui.RowIcon; -import com.intellij.util.Function; -import com.intellij.util.IncorrectOperationException; -import com.intellij.util.ReflectionCache; -import com.intellij.util.SmartList; +import com.intellij.util.*; import com.intellij.util.containers.HashMap; import gnu.trove.THashSet; import org.jetbrains.annotations.NonNls; @@ -864,6 +860,7 @@ public class PsiClassImplUtil { } public static boolean isClassEquivalentTo(PsiClass aClass, PsiElement another) { + if (aClass == another) return true; if (!(another instanceof PsiClass)) return false; String name1 = aClass.getName(); if (name1 == null) return false; @@ -900,17 +897,13 @@ public class PsiClassImplUtil { final PsiFile file1 = aClass.getContainingFile().getOriginalFile(); final PsiFile file2 = another.getContainingFile().getOriginalFile(); - if (file1.equals(file2)) { - return true; - } //see com.intellij.openapi.vcs.changes.PsiChangeTracker //see com.intellij.psi.impl.PsiFileFactoryImpl#createFileFromText(CharSequence,PsiFile) final PsiFile original1 = file1.getUserData(PsiFileFactory.ORIGINAL_FILE); final PsiFile original2 = file2.getUserData(PsiFileFactory.ORIGINAL_FILE); - if (original1 == original2 && original1 != null - || original1 == file2 || original2 == file1) { - return true; + if (original1 == original2 && original1 != null || original1 == file2 || original2 == file1 || file1 == file2) { + return compareClassSeqNumber(aClass, (PsiClass)another); } final ProjectFileIndex fileIndex = ProjectRootManager.getInstance(aClass.getProject()).getFileIndex(); @@ -920,6 +913,28 @@ public class PsiClassImplUtil { (fileIndex.isInSource(vfile2) || fileIndex.isInLibraryClasses(vfile2)); } + private static boolean compareClassSeqNumber(PsiClass aClass, PsiClass another) { + // there may be several classes in one file, they must not be equal + int index1 = getSeqNumber(aClass); + if (index1 == -1) return true; + int index2 = getSeqNumber(another); + return index1 == index2; + } + + private static int getSeqNumber(PsiClass aClass) { + // sequence number of this class among its parent' child classes named the same + PsiElement parent = aClass.getParent(); + if (parent == null) return -1; + int seqNo = 0; + for (PsiElement child : parent.getChildren()) { + if (child == aClass) return seqNo; + if (child instanceof PsiClass && Comparing.strEqual(aClass.getName(), ((PsiClass)child).getName())) { + seqNo++; + } + } + return -1; + } + private static PsiElement originalElement(PsiClass aClass) { final PsiElement originalElement = aClass.getOriginalElement(); final PsiCompiledElement compiled = originalElement.getUserData(ClsElementImpl.COMPILED_ELEMENT); diff --git a/java/java-impl/src/com/intellij/psi/impl/compiled/ClsJavaCodeReferenceElementImpl.java b/java/java-impl/src/com/intellij/psi/impl/compiled/ClsJavaCodeReferenceElementImpl.java index 7238fb8feccd..eb37c848fbcb 100644 --- a/java/java-impl/src/com/intellij/psi/impl/compiled/ClsJavaCodeReferenceElementImpl.java +++ b/java/java-impl/src/com/intellij/psi/impl/compiled/ClsJavaCodeReferenceElementImpl.java @@ -20,6 +20,7 @@ import com.intellij.openapi.util.text.StringUtil; import com.intellij.psi.*; import com.intellij.psi.impl.PsiManagerEx; import com.intellij.psi.impl.PsiSubstitutorImpl; +import com.intellij.psi.impl.search.JavaDirectInheritorsSearcher; import com.intellij.psi.impl.source.resolve.ResolveCache; import com.intellij.psi.impl.source.tree.JavaElementType; import com.intellij.psi.impl.source.tree.TreeElement; @@ -167,7 +168,15 @@ public class ClsJavaCodeReferenceElementImpl extends ClsElementImpl implements P for (PsiTypeParameter parameter : PsiUtil.typeParametersIterable((PsiTypeParameterListOwner)element)) { if (myQualifiedName.equals(parameter.getName())) return parameter; } - return JavaPsiFacade.getInstance(getProject()).findClass(myQualifiedName, getResolveScope()); + return resolveClassPreferringMyJar(); + } + + private PsiClass resolveClassPreferringMyJar() { + PsiClass[] classes = JavaPsiFacade.getInstance(getProject()).findClasses(myQualifiedName, getResolveScope()); + for (PsiClass aClass : classes) { + if (JavaDirectInheritorsSearcher.isFromTheSameJar(aClass, this)) return aClass; + } + return classes.length == 0 ? null : classes[0]; } public void processVariants(PsiScopeProcessor processor) { diff --git a/java/java-impl/src/com/intellij/psi/impl/light/LightVariableBuilder.java b/java/java-impl/src/com/intellij/psi/impl/light/LightVariableBuilder.java index 94bb7ea3e64a..6932cb320c0e 100644 --- a/java/java-impl/src/com/intellij/psi/impl/light/LightVariableBuilder.java +++ b/java/java-impl/src/com/intellij/psi/impl/light/LightVariableBuilder.java @@ -48,6 +48,7 @@ public class LightVariableBuilder extends LightElement implements PsiVariable { } @Override + @NotNull public PsiModifierList getModifierList() { return myModifierList; } diff --git a/java/java-impl/src/com/intellij/psi/impl/search/JavaDirectInheritorsSearcher.java b/java/java-impl/src/com/intellij/psi/impl/search/JavaDirectInheritorsSearcher.java index 5340828d80fe..aa16822b166b 100644 --- a/java/java-impl/src/com/intellij/psi/impl/search/JavaDirectInheritorsSearcher.java +++ b/java/java-impl/src/com/intellij/psi/impl/search/JavaDirectInheritorsSearcher.java @@ -8,6 +8,8 @@ import com.intellij.openapi.fileTypes.StdFileTypes; import com.intellij.openapi.progress.ProgressManager; import com.intellij.openapi.util.Computable; import com.intellij.openapi.util.text.StringUtil; +import com.intellij.openapi.vfs.JarFileSystem; +import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.*; import com.intellij.psi.impl.PsiManagerImpl; import com.intellij.psi.impl.java.stubs.index.JavaAnonymousClassBaseRefOccurenceIndex; @@ -19,14 +21,17 @@ import com.intellij.psi.search.searches.AllClassesSearch; import com.intellij.psi.search.searches.DirectClassInheritorsSearch; import com.intellij.util.Processor; import com.intellij.util.QueryExecutor; +import com.intellij.util.containers.HashMap; +import java.util.ArrayList; import java.util.Collection; +import java.util.List; +import java.util.Map; /** * @author max */ public class JavaDirectInheritorsSearcher implements QueryExecutor { - public boolean execute(final DirectClassInheritorsSearch.SearchParameters p, final Processor consumer) { final PsiClass aClass = p.getClassToProcess(); final PsiManagerImpl psiManager = (PsiManagerImpl)PsiManager.getInstance(aClass.getProject()); @@ -73,10 +78,26 @@ public class JavaDirectInheritorsSearcher implements QueryExecutor> classes = new HashMap>(); + for (PsiReferenceList referenceList : candidates) { ProgressManager.checkCanceled(); - PsiClass candidate = (PsiClass)referenceList.getParent(); - if (!consumer.process(candidate)) return false; + final PsiClass candidate = (PsiClass)referenceList.getParent(); + String fqn = ApplicationManager.getApplication().runReadAction(new Computable() { + public String compute() { + return candidate.getQualifiedName(); + } + }); + List list = classes.get(fqn); + if (list == null) { + list = new ArrayList(); + classes.put(fqn, list); + } + list.add(candidate); + } + + for (List sameNamedClasses : classes.values()) { + if (!processSameNamedClasses(consumer, aClass, sameNamedClasses)) return false; } if (p.includeAnonymous()) { @@ -116,4 +137,37 @@ public class JavaDirectInheritorsSearcher implements QueryExecutor consumer, PsiClass aClass, List sameNamedClasses) { + // if there is a class from the same jar, prefer it + boolean sameJarClassFound = false; + for (PsiClass sameNamedClass : sameNamedClasses) { + boolean fromSameJar = isFromTheSameJar(sameNamedClass, aClass); + if (fromSameJar) { + sameJarClassFound = true; + if (!consumer.process(sameNamedClass)) return false; + } + } + + if (!sameJarClassFound) { + for (PsiClass sameNamedClass : sameNamedClasses) { + if (!consumer.process(sameNamedClass)) return false; + } + } + return true; + } + + private static VirtualFile getJarFile(PsiElement candidate) { + VirtualFile file = candidate.getContainingFile().getVirtualFile(); + if (file != null && file.getFileSystem() instanceof JarFileSystem) { + return JarFileSystem.getInstance().getVirtualFileForJar(file); + } + return file; + } + public static boolean isFromTheSameJar(PsiElement candidate, PsiElement other) { + VirtualFile c1 = getJarFile(candidate); + VirtualFile c2 = getJarFile(other); + return c1 != null && c1 == c2; + } + } diff --git a/java/java-tests/testData/psi/parser-partial/declarations/MultiLineUnclosed.txt b/java/java-tests/testData/psi/parser-partial/declarations/MultiLineUnclosed.txt index d44d8913422d..0f35986c3364 100644 --- a/java/java-tests/testData/psi/parser-partial/declarations/MultiLineUnclosed.txt +++ b/java/java-tests/testData/psi/parser-partial/declarations/MultiLineUnclosed.txt @@ -7,7 +7,7 @@ PsiJavaFile:MultiLineUnclosed.java PsiKeyword:int('int') PsiErrorElement:Identifier expected - PsiWhiteSpace(' \n ') + PsiWhiteSpace(' \n ') PsiField:o PsiModifierList: diff --git a/java/java-tests/testSrc/com/intellij/lang/java/parser/partial/DeclarationParserTest.java b/java/java-tests/testSrc/com/intellij/lang/java/parser/partial/DeclarationParserTest.java index 8a51afbf9b6d..99a5853442a8 100644 --- a/java/java-tests/testSrc/com/intellij/lang/java/parser/partial/DeclarationParserTest.java +++ b/java/java-tests/testSrc/com/intellij/lang/java/parser/partial/DeclarationParserTest.java @@ -21,7 +21,6 @@ import com.intellij.lang.java.parser.JavaParsingTestCase; import com.intellij.pom.java.LanguageLevel; -// todo: fix parser and uncomment tests public class DeclarationParserTest extends JavaParsingTestCase { public DeclarationParserTest() { super("parser-partial/declarations"); @@ -78,7 +77,7 @@ public class DeclarationParserTest extends JavaParsingTestCase { public void testUnclosedComma() { doParserTest("{ int field, }", false, false); } public void testUnclosedSemicolon() { doParserTest("{ int field }", false, false); } public void testMissingInitializerExpression() { doParserTest("{ int field=; }", false, false); } - //public void testMultiLineUnclosed() { doParserTest("{ int \n Object o; }", false, false); } + public void testMultiLineUnclosed() { doParserTest("{ int \n Object o; }", false, false); } public void testMethodNormal0() { doParserTest("{ void f() {} }", false, false); } public void testMethodNormal1() { doParserTest("{ void f(); }", false, false); } diff --git a/java/openapi/src/com/intellij/psi/util/TypeConversionUtil.java b/java/openapi/src/com/intellij/psi/util/TypeConversionUtil.java index fde48c62adb2..8124ac523fe1 100644 --- a/java/openapi/src/com/intellij/psi/util/TypeConversionUtil.java +++ b/java/openapi/src/com/intellij/psi/util/TypeConversionUtil.java @@ -919,8 +919,9 @@ public class TypeConversionUtil { substitutor = getSuperClassSubstitutorInner(superClass, derivedClass, derivedSubstitutor, visited, manager); } if (substitutor == null) { - LOG.error("Not inheritor: " + derivedClass + "(" + derivedClass.getClass().getName() + ");" + - " super: " + superClass + "(" + derivedClass.getClass().getName() + ")"); + LOG.error( + "Not inheritor: " + derivedClass + "(" + derivedClass.getClass().getName() + "; " + PsiUtil.getVirtualFile(derivedClass) + ");" + + "\n super: " + superClass + "(" + superClass.getClass().getName() + "; " + PsiUtil.getVirtualFile(superClass) + ")"); } return substitutor; } diff --git a/platform/lang-impl/src/com/intellij/codeInsight/generation/CommentByLineCommentHandler.java b/platform/lang-impl/src/com/intellij/codeInsight/generation/CommentByLineCommentHandler.java index 012b2e419daf..71b1d89fac7c 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/generation/CommentByLineCommentHandler.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/generation/CommentByLineCommentHandler.java @@ -224,7 +224,7 @@ public class CommentByLineCommentHandler implements CodeInsightActionHandler { } boolean allLineCommented = true; - boolean commentWithIndent = CodeStyleSettingsManager.getSettings(myProject).LINE_COMMENT_AT_FIRST_COLUMN; + boolean commentWithIndent = !CodeStyleSettingsManager.getSettings(myProject).LINE_COMMENT_AT_FIRST_COLUMN; for (int line = myStartLine; line <= myEndLine; line++) { Commenter commenter = blockSuitableCommenter != null ? blockSuitableCommenter : findCommenter(line); diff --git a/platform/lang-impl/src/com/intellij/psi/impl/source/PsiFileImpl.java b/platform/lang-impl/src/com/intellij/psi/impl/source/PsiFileImpl.java index dbca10b833aa..c78054c1ec27 100644 --- a/platform/lang-impl/src/com/intellij/psi/impl/source/PsiFileImpl.java +++ b/platform/lang-impl/src/com/intellij/psi/impl/source/PsiFileImpl.java @@ -76,7 +76,7 @@ public abstract class PsiFileImpl extends ElementBase implements PsiFileEx, PsiF protected PsiFile myOriginalFile = null; private final FileViewProvider myViewProvider; private static final Key HARD_REFERENCE_TO_DOCUMENT = new Key("HARD_REFERENCE_TO_DOCUMENT"); - private final Object myStubLock = new String("file's stub lock"); + private final Object myStubLock = new Object(); private SoftReference myStub; protected final PsiManagerEx myManager; private volatile Object myTreeElementPointer; // SoftReference/WeakReference to RepositoryTreeElement when has repository id, RepositoryTreeElement otherwise diff --git a/platform/lang-impl/src/com/intellij/psi/impl/source/codeStyle/CodeEditUtil.java b/platform/lang-impl/src/com/intellij/psi/impl/source/codeStyle/CodeEditUtil.java index 7c062d1eb52e..5379241f800f 100644 --- a/platform/lang-impl/src/com/intellij/psi/impl/source/codeStyle/CodeEditUtil.java +++ b/platform/lang-impl/src/com/intellij/psi/impl/source/codeStyle/CodeEditUtil.java @@ -40,9 +40,9 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; public class CodeEditUtil { - private static final Key GENERATED_FLAG = new Key("CREATED BY IDEA"); - private static final Key INDENT_INFO = new Key("INDENTATION"); - private static final Key REFORMAT_KEY = new Key("REFORMAT BEFORE THIS ELEMENT"); + private static final Key GENERATED_FLAG = new Key("GENERATED_FLAG"); + private static final Key INDENT_INFO = new Key("INDENT_INFO"); + private static final Key REFORMAT_KEY = new Key("REFORMAT_KEY"); public static final Key OUTER_OK = new Key("OUTER_OK"); @@ -247,9 +247,15 @@ public class CodeEditUtil { final int leftBlankLines = getBlankLines(left.getText()); final int rightBlankLines = getBlankLines(right.getText()); final boolean leaveRightText = leftBlankLines < rightBlankLines; - if (leftBlankLines == 0 && rightBlankLines == 0) text = left.getText() + right.getText(); - else if (leaveRightText) text = right.getText(); - else text = left.getText(); + if (leftBlankLines == 0 && rightBlankLines == 0) { + text = left.getText() + right.getText(); + } + else if (leaveRightText) { + text = right.getText(); + } + else { + text = left.getText(); + } if(leaveRightText || forceReformat){ final LeafElement merged = ASTFactory.whitespace(text); if(!leaveRightText){ @@ -373,9 +379,13 @@ public class CodeEditUtil { } public static void setNodeGenerated(final ASTNode next, final boolean value) { - if(next == null) return; - if(value) next.putCopyableUserData(GENERATED_FLAG, true); - else next.putCopyableUserData(GENERATED_FLAG, null); + if (next == null) return; + if (value) { + next.putCopyableUserData(GENERATED_FLAG, true); + } + else { + next.putCopyableUserData(GENERATED_FLAG, null); + } } public static void setOldIndentation(final TreeElement treeElement, final int oldIndentation) { diff --git a/platform/platform-api/src/com/intellij/openapi/util/KeyedExtensionCollector.java b/platform/platform-api/src/com/intellij/openapi/util/KeyedExtensionCollector.java index 871782338a7e..d17505cbb238 100644 --- a/platform/platform-api/src/com/intellij/openapi/util/KeyedExtensionCollector.java +++ b/platform/platform-api/src/com/intellij/openapi/util/KeyedExtensionCollector.java @@ -38,10 +38,9 @@ public abstract class KeyedExtensionCollector { private final Map> myExplicitExtensions = new THashMap>(); private final Map> myCache = new HashMap>(); - private final JBReentrantReadWriteLock mutex = LockFactory.createReadWriteLock(); - private final JBLock r = mutex.readLock(); - private final JBLock w = mutex.writeLock(); + private final JBLock w; + private final JBLock r; private ExtensionPoint> myPoint; private final String myEpName; @@ -49,6 +48,9 @@ public abstract class KeyedExtensionCollector { private final List> myListeners = ContainerUtil.createEmptyCOWList(); public KeyedExtensionCollector(@NonNls String epName) { + JBReentrantReadWriteLock mutex = LockFactory.createReadWriteLock(); + r = mutex.readLock(); + w = mutex.writeLock(); myEpName = epName; resetAreaListener(); } diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/actions/DuplicateAction.java b/platform/platform-impl/src/com/intellij/openapi/editor/actions/DuplicateAction.java index bc1d90c8e724..6d9cd2083c40 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/actions/DuplicateAction.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/actions/DuplicateAction.java @@ -61,6 +61,9 @@ public class DuplicateAction extends EditorAction { VisualPosition caret = editor.getCaretModel().getVisualPosition(); LogicalPosition lineStart = editor.visualToLogicalPosition(new VisualPosition(caret.line, 0)); LogicalPosition nextLineStart = editor.visualToLogicalPosition(new VisualPosition(caret.line + 1, 0)); + if (nextLineStart.line == lineStart.line) { + nextLineStart = new LogicalPosition(lineStart.line+1, 0); + } int start = editor.logicalPositionToOffset(lineStart); int end = editor.logicalPositionToOffset(nextLineStart); @@ -86,4 +89,4 @@ public class DuplicateAction extends EditorAction { presentation.setText(EditorBundle.message("action.duplicate.line"), true); } } -} \ No newline at end of file +} diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/ex/util/EditorUtil.java b/platform/platform-impl/src/com/intellij/openapi/editor/ex/util/EditorUtil.java index 494eb95ac112..7e7378af51f4 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/ex/util/EditorUtil.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/ex/util/EditorUtil.java @@ -296,7 +296,7 @@ public class EditorUtil { int fontType = state.getMergedAttributes().getFontType(); int column = currentColumn.get(); int spaceSize = getSpaceWidth(fontType, editorImpl); - for (; column <= columnNumber && offset < end; offset++) { + for (; column < columnNumber && offset < end; offset++) { if (offset >= state.getEndOffset()) { state.advance(); fontType = state.getMergedAttributes().getFontType(); @@ -407,6 +407,9 @@ public class EditorUtil { } public static int nextTabStop(int x, Editor editor, int tabSize) { + if (tabSize <= 0) { + return x + getSpaceWidth(Font.PLAIN, editor); + } tabSize *= getSpaceWidth(Font.PLAIN, editor); int nTabs = x / tabSize; diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/impl/CaretModelImpl.java b/platform/platform-impl/src/com/intellij/openapi/editor/impl/CaretModelImpl.java index 2870ab265133..d45828c5a68b 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/impl/CaretModelImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/impl/CaretModelImpl.java @@ -480,11 +480,11 @@ public class CaretModelImpl implements CaretModel, PrioritizedDocumentListener { int y = myEditor.visualPositionToXY(visualPosition).y; int height = myEditor.getLineHeight(); int visualLine = visualPosition.line + 1; - while (true) { + int lastVisualLine = myEditor.offsetToVisualPosition(document.getTextLength() - 1).line; + for (; visualLine <= lastVisualLine; visualLine++) { LogicalPosition logical = myEditor.visualToLogicalPosition(new VisualPosition(visualLine, 0)); if (logical.line == logicalLine) { height += myEditor.getLineHeight(); - visualLine++; } else { break; diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/impl/EditorImpl.java b/platform/platform-impl/src/com/intellij/openapi/editor/impl/EditorImpl.java index 5bda19660b94..c6e6602858aa 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/impl/EditorImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/impl/EditorImpl.java @@ -105,6 +105,7 @@ import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; public final class EditorImpl extends UserDataHolderBase implements EditorEx, HighlighterClient, Queryable { private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.editor.impl.EditorImpl"); @@ -122,7 +123,7 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi ComplementaryFontsRegistry registry; // load costly font info } - private final CommandProcessor myCommandProcessor; + private final CommandProcessor myCommandProcessor; private final MyScrollBar myVerticalScrollBar; private final CopyOnWriteArrayList myMouseListeners = ContainerUtil.createEmptyCOWList(); @@ -1318,8 +1319,6 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi } void paint(Graphics g) { - //TODO den remove - System.out.printf("EditorImpl.paint(): (%d; %d) - [%d; %d]%n", g.getClipBounds().x, g.getClipBounds().y, g.getClipBounds().width, g.getClipBounds().height); startOptimizedScrolling(); if (myCursorUpdater != null) { @@ -1750,7 +1749,7 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi if (backColor != null && !backColor.equals(defaultBackground) && clip.intersects(position.x, position.y, w, getLineHeight())) { if (backColor.equals(myLastBackgroundColor) && myLastBackgroundPosition.y == position.y && - myLastBackgroundPosition.x + myLastBackgroundWidth == position.x) { + myLastBackgroundPosition.x + myLastBackgroundWidth == position.x) { myLastBackgroundWidth += w; } else { @@ -1795,8 +1794,14 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi int visibleLineNumber = clip.y / lineHeight; - LogicalPosition logicalPosition = xyToLogicalPosition(new Point(0, clip.y)); - int startLineNumber = logicalPosition.line; + // We use AtomicReference here just as a holder for LogicalPosition + // The main idea is that there is a possible case that we need to perform painting starting from soft-wrapped logical line. + // We may want to skip necessary of visual lines then. Hence, we remember logical position that corresponds to the starting + // visual line in order to use it for further processing. As soon as necessary number of visual lines is skipped, logical + // position is expected to be set to null as an indication that no soft wrap-introduced visual lines should be skipped on + // current painting iteration. + AtomicReference logicalPosition = new AtomicReference(xyToLogicalPosition(new Point(0, clip.y))); + int startLineNumber = logicalPosition.get().line; Point position = new Point(0, visibleLineNumber * lineHeight); if (startLineNumber == 0 && myPrefixText != null) { @@ -1851,10 +1856,19 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi else { FoldRegion collapsedFolderAt = iterationState.getCurrentFold(); if (collapsedFolderAt != null) { + TextChange softWrap = mySoftWrapModel.getSoftWrap(collapsedFolderAt.getStartOffset()); + if (softWrap != null && logicalPosition.get() != null) { + position.x = drawStringWithSoftWraps( + g, chars, collapsedFolderAt.getStartOffset(), collapsedFolderAt.getStartOffset(), position, clip, effectColor, effectType, + fontType, currentColor, logicalPosition + ); + } int foldingXStart = position.x; - position.x = - drawStringWithSoftWraps(g, collapsedFolderAt.getPlaceholderText(), position, clip, effectColor, effectType, - fontType, currentColor, logicalPosition); + position.x = drawString( + g, collapsedFolderAt.getPlaceholderText(), position, clip, effectColor, effectType, fontType, currentColor + ); + //drawStringWithSoftWraps(g, collapsedFolderAt.getPlaceholderText(), position, clip, effectColor, effectType, + // fontType, currentColor, logicalPosition); BorderEffect.paintFoldedEffect(g, foldingXStart, position.y, position.x, getLineHeight(), effectColor, effectType); } @@ -1964,7 +1978,7 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi final int lastCount = count - 1; final Color lastColor = color[lastCount]; if (_data == myLastData && _start == ends[lastCount] && (_color == null || lastColor == null || _color == lastColor) - && _y == y[lastCount] /* there is a possible case that vertical position is adjusted because of soft wrap */) + && _y == y[lastCount] /* there is a possible case that vertical position is adjusted because of soft wrap */) { ends[lastCount] = _end; if (lastColor == null) color[lastCount] = _color; @@ -1997,7 +2011,7 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi private void paintCaretCursor(Graphics g) { // There is a possible case that visual caret position is changed because of newly added or removed soft wraps. // We check if that's the case and ask caret model to recalculate visual position if necessary. - + myCaretCursor.paint(g); } @@ -2047,7 +2061,7 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi EffectType effectType, int fontType, Color fontColor, - LogicalPosition startDrawingLogicalPosition) + AtomicReference startDrawingLogicalPosition) { return drawStringWithSoftWraps(g, text.toCharArray(), 0, text.length(), position, clip, effectColor, effectType, fontType, fontColor, startDrawingLogicalPosition); @@ -2063,16 +2077,19 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi EffectType effectType, int fontType, Color fontColor, - LogicalPosition startDrawingLogicalPosition) + AtomicReference startDrawingLogicalPosition) { int startToUse = start; // There is a possible case that starting logical line is split by soft-wraps and it's part after the split should be drawn. // We need to skip necessary number of visual lines then. - int softWrapLinesToSkip = startDrawingLogicalPosition.softWrapLinesOnCurrentLogicalLine; + int softWrapLinesToSkip = 0; + if (startDrawingLogicalPosition.get() != null) { + softWrapLinesToSkip = startDrawingLogicalPosition.get().softWrapLinesOnCurrentLogicalLine; + } TextChange lastSkippedSoftWrap = null; if (softWrapLinesToSkip > 0) { - List softWraps = getSoftWrapModel().getSoftWrapsForLine(startDrawingLogicalPosition.line); + List softWraps = getSoftWrapModel().getSoftWrapsForLine(startDrawingLogicalPosition.get().line); for (TextChange softWrap : softWraps) { softWrapLinesToSkip -= StringUtil.countNewLines(softWrap.getText()); if (softWrapLinesToSkip <= 0) { @@ -2081,13 +2098,14 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi break; } } - } + } startToUse = Math.max(startToUse, start); if (startToUse >= end) { return position.x; } + startDrawingLogicalPosition.set(null); outer: for (TextChange softWrap : getSoftWrapModel().getSoftWrapsForRange(startToUse, end)) { @@ -2741,7 +2759,7 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi int column = EditorUtil.calcColumnNumber(this, text, start, offset, EditorUtil.getTabSize(this)); if (softWrapAware) { - int line = calcLogicalLineNumber(offset, false); + int line = calcLogicalLineNumber(offset, false); return mySoftWrapModel.adjustLogicalPosition(new LogicalPosition(line, column), offset).column; } else { @@ -2881,22 +2899,22 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi int x = myGutterComponent.convertX(e.getX()); if (x >= myGutterComponent.getLineNumberAreaOffset() && - x < myGutterComponent.getLineNumberAreaOffset() + myGutterComponent.getLineNumberAreaWidth()) { + x < myGutterComponent.getLineNumberAreaOffset() + myGutterComponent.getLineNumberAreaWidth()) { return EditorMouseEventArea.LINE_NUMBERS_AREA; } if (x >= myGutterComponent.getAnnotationsAreaOffset() && - x <= myGutterComponent.getAnnotationsAreaOffset() + myGutterComponent.getAnnotationsAreaWidth()) { + x <= myGutterComponent.getAnnotationsAreaOffset() + myGutterComponent.getAnnotationsAreaWidth()) { return EditorMouseEventArea.ANNOTATIONS_AREA; } if (x >= myGutterComponent.getLineMarkerAreaOffset() && - x < myGutterComponent.getLineMarkerAreaOffset() + myGutterComponent.getLineMarkerAreaWidth()) { + x < myGutterComponent.getLineMarkerAreaOffset() + myGutterComponent.getLineMarkerAreaWidth()) { return EditorMouseEventArea.LINE_MARKERS_AREA; } if (x >= myGutterComponent.getFoldingAreaOffset() && - x < myGutterComponent.getFoldingAreaOffset() + myGutterComponent.getFoldingAreaWidth()) { + x < myGutterComponent.getFoldingAreaOffset() + myGutterComponent.getFoldingAreaWidth()) { return EditorMouseEventArea.FOLDING_OUTLINE_AREA; } @@ -2988,7 +3006,7 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi int caretShift = newCaretOffset - mySavedSelectionStart; if (myMousePressedEvent != null && getMouseEventArea(myMousePressedEvent) != EditorMouseEventArea.EDITING_AREA && - getMouseEventArea(myMousePressedEvent) != EditorMouseEventArea.LINE_NUMBERS_AREA) { + getMouseEventArea(myMousePressedEvent) != EditorMouseEventArea.LINE_NUMBERS_AREA) { selectionModel.setSelection(oldSelectionStart, newCaretOffset); } else { @@ -4020,8 +4038,8 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi public void mouseReleased(MouseEvent e) { runMouseReleasedCommand(e); if (!e.isConsumed() && myMousePressedEvent != null && !myMousePressedEvent.isConsumed() && - Math.abs(e.getX() - myMousePressedEvent.getX()) < EditorUtil.getSpaceWidth(Font.PLAIN, EditorImpl.this) && - Math.abs(e.getY() - myMousePressedEvent.getY()) < getLineHeight()) { + Math.abs(e.getX() - myMousePressedEvent.getX()) < EditorUtil.getSpaceWidth(Font.PLAIN, EditorImpl.this) && + Math.abs(e.getY() - myMousePressedEvent.getY()) < getLineHeight()) { runMouseClickedCommand(e); } myMousePressedEvent = null; @@ -4177,7 +4195,7 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi myMouseSelectedRegion = myFoldingModel.getFoldingPlaceholderAt(new Point(x, y)); myMousePressedInsideSelection = mySelectionModel.hasSelection() && caretOffset >= mySelectionModel.getSelectionStart() && - caretOffset <= mySelectionModel.getSelectionEnd(); + caretOffset <= mySelectionModel.getSelectionEnd(); if (!myMousePressedInsideSelection && mySelectionModel.hasBlockSelection()) { int[] starts = mySelectionModel.getBlockSelectionStarts(); @@ -4563,7 +4581,7 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi final Editor editor = getEditor(source); if (action == MOVE && !editor.isViewer()) { if (!FileDocumentManager.getInstance().requestWriting(editor.getDocument(), editor.getProject())) { - return; + return; } CommandProcessor.getInstance().executeCommand(((EditorImpl)editor).myProject, new Runnable() { public void run() { @@ -4636,7 +4654,7 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi myMaxWidth = mySize != null ? mySize.width : -1; } - myOldEndLine = getVisualPositionLine(e.getOffset() + e.getOldLength()); + myOldEndLine = offsetToLogicalPosition(e.getOffset() + e.getOldLength()).line; } private int getVisualPositionLine(int offset) { @@ -4667,15 +4685,15 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi myLineWidths.insert(oldEndLine + 1, delta); } else if (oldEndLine > newEndLine && !toAddNewLines && newEndLine + 1 < lineWidthSize) { - myLineWidths.remove(newEndLine + 1, Math.min(oldEndLine, lineWidthSize) - newEndLine); + myLineWidths.remove(newEndLine + 1, Math.min(oldEndLine, lineWidthSize) - newEndLine - 1); } myIsDirty = true; } } public synchronized void changedUpdate(DocumentEvent e) { - int startLine = e.getOldLength() == 0 ? myOldEndLine : getVisualPositionLine(e.getOffset()); - int newEndLine = e.getNewLength() == 0 ? startLine : getVisualPositionLine(e.getOffset() + e.getNewLength()); + int startLine = e.getOldLength() == 0 ? myOldEndLine : offsetToLogicalPosition(e.getOffset()).line; + int newEndLine = e.getNewLength() == 0 ? startLine : offsetToLogicalPosition(e.getOffset() + e.getNewLength()).line; int oldEndLine = myOldEndLine; update(startLine, newEndLine, oldEndLine); @@ -4877,7 +4895,7 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi : e.isControlDown() && !e.isMetaDown() && !e.isAltDown() && !e.isShiftDown(); if (changeFontSize) { setFontSize(myScheme.getEditorFontSize() + e.getWheelRotation()); - return; + return; } } diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/impl/softwrap/LineOrientedDocumentChangeAdapter.java b/platform/platform-impl/src/com/intellij/openapi/editor/impl/softwrap/LineOrientedDocumentChangeAdapter.java index 5c52c7f6a1a0..3e37600e14e6 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/impl/softwrap/LineOrientedDocumentChangeAdapter.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/impl/softwrap/LineOrientedDocumentChangeAdapter.java @@ -28,25 +28,29 @@ import com.intellij.openapi.editor.event.DocumentListener; */ public abstract class LineOrientedDocumentChangeAdapter implements DocumentListener { - private enum ChangeType { - BEFORE, AFTER - } - @Override public void beforeDocumentChange(DocumentEvent event) { - onChange(event, ChangeType.BEFORE); + Document document = event.getDocument(); + int startLine = document.getLineNumber(normalize(event.getDocument(), event.getOffset())); + int endLine = document.getLineNumber(normalize(event.getDocument(), event.getOffset() + event.getOldLength())); + int symbolsDifference = event.getNewLength() - event.getOldLength(); + beforeDocumentChange(startLine, endLine, symbolsDifference); } @Override public void documentChanged(DocumentEvent event) { - onChange(event, ChangeType.AFTER); + Document document = event.getDocument(); + int startLine = document.getLineNumber(normalize(event.getDocument(), event.getOffset())); + int endLine = document.getLineNumber(normalize(event.getDocument(), event.getOffset() + event.getNewLength())); + int symbolsDifference = event.getNewLength() - event.getOldLength(); + afterDocumentChange(startLine, endLine, symbolsDifference); } /** * Callback adapter method for {@link DocumentListener#beforeDocumentChange(DocumentEvent)} event. * * @param startLine first logical document line affected by the target event (inclusive) - * @param endLine last logical document line affected by the target event (inclusive) + * @param endLine old last logical document line affected by the target event (inclusive) * @param symbolsDifference difference in number in symbols applied to the target document */ public abstract void beforeDocumentChange(int startLine, int endLine, int symbolsDifference); @@ -55,26 +59,11 @@ public abstract class LineOrientedDocumentChangeAdapter implements DocumentListe * Callback adapter method for {@link DocumentListener#documentChanged(DocumentEvent)} event. * * @param startLine first logical document line affected by the target event (inclusive) - * @param endLine last logical document line affected by the target event (inclusive) + * @param endLine new last logical document line affected by the target event (inclusive) * @param symbolsDifference difference in number in symbols applied to the target document */ public abstract void afterDocumentChange(int startLine, int endLine, int symbolsDifference); - private void onChange(DocumentEvent event, ChangeType type) { - Document document = event.getDocument(); - int startLine = document.getLineNumber(normalize(event.getDocument(), event.getOffset())); - int endLine = document.getLineNumber( - normalize(event.getDocument(), Math.max(event.getOffset() + event.getNewLength(), event.getOffset() + event.getOldLength())) - ); - - int symbolsDifference = event.getNewLength() - event.getOldLength(); - switch (type) { - case AFTER: beforeDocumentChange(startLine, endLine, symbolsDifference); break; - case BEFORE: afterDocumentChange(startLine, endLine, symbolsDifference); break; - default: throw new IllegalStateException("Unsupported event change type: " + type); - } - } - private static int normalize(Document document, int offset) { if (offset < 0) { return 0; diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/impl/softwrap/SoftWrapDataMapper.java b/platform/platform-impl/src/com/intellij/openapi/editor/impl/softwrap/SoftWrapDataMapper.java index cc965b8ff252..9250d2cc4be1 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/impl/softwrap/SoftWrapDataMapper.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/impl/softwrap/SoftWrapDataMapper.java @@ -240,6 +240,10 @@ public class SoftWrapDataMapper { @Nullable private LogicalPosition process(@NotNull FoldRegion region) { + int endDocumentOffset = myEditor.getDocument().getTextLength(); + if (region.getEndOffset() >= endDocumentOffset) { + return advanceToOffset(endDocumentOffset).build(); + } if (region.getStartOffset() > context.offset) { Context newContext = advanceToOffset(region.getStartOffset()); if (strategy.exceeds(newContext)) { @@ -289,6 +293,10 @@ public class SoftWrapDataMapper { @Nullable private LogicalPosition process(@NotNull TextChange softWrap) { + int endDocumentOffset = myEditor.getDocument().getTextLength(); + if (softWrap.getStart() >= endDocumentOffset) { + return advanceToOffset(endDocumentOffset).build(); + } Context newContext = advanceToOffset(softWrap.getStart()); if (strategy.exceeds(newContext)) { return strategy.build(context); diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/impl/softwrap/SoftWrapDocumentChangeManager.java b/platform/platform-impl/src/com/intellij/openapi/editor/impl/softwrap/SoftWrapDocumentChangeManager.java index 251e7ed3709e..ac32e135de49 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/impl/softwrap/SoftWrapDocumentChangeManager.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/impl/softwrap/SoftWrapDocumentChangeManager.java @@ -32,16 +32,6 @@ import java.util.List; */ public class SoftWrapDocumentChangeManager { - /** - * Holds logical lines where soft wraps should be removed. - *

- * The general idea is to do the following: - *

    - *
  • listen for document changes, mark all soft wraps that belong to modified logical line as 'dirty';
  • - *
  • remove soft wraps marked as 'dirty' on repaint;
  • - *
- */ - private final TIntHashSet myDirtyLines = new TIntHashSet(); private final List myDeferredChanges = new ArrayList(); private final SoftWrapsStorage myStorage; @@ -76,20 +66,19 @@ public class SoftWrapDocumentChangeManager { // Update offsets for soft wraps that remain after the changed line(s). List softWraps = myStorage.getSoftWraps(); for (DeferredChange change : myDeferredChanges) { - if (change.startLine >= document.getLineCount()) { + if (change.startOffset >= document.getTextLength()) { continue; } - int index = myStorage.getSoftWrapIndex(document.getLineStartOffset(change.startLine)); + int index = myStorage.getSoftWrapIndex(change.startOffset); if (index < 0) { index = -index -1; } for (int i = index; i < softWraps.size(); i++) { TextChangeImpl softWrap = softWraps.get(i); - if (softWrap.getStart() >= document.getTextLength()) { + if (softWrapsToRemoveIndices.contains(i)) { continue; } - int softWrapLine = document.getLineNumber(softWrap.getStart()); - if (myDirtyLines.contains(softWrapLine)) { + if (softWrap.getStart() < change.endOffset || softWrap.getStart() >= document.getTextLength()) { softWrapsToRemoveIndices.add(i); continue; } @@ -106,44 +95,38 @@ public class SoftWrapDocumentChangeManager { } }); - myDirtyLines.clear(); myDeferredChanges.clear(); } - private void init(Document document) { + private void init(final Document document) { document.addDocumentListener(new LineOrientedDocumentChangeAdapter() { @Override public void beforeDocumentChange(int startLine, int endLine, int symbolsDifference) { + myDeferredChanges.add( + new DeferredChange(document.getLineStartOffset(startLine), document.getLineEndOffset(endLine), symbolsDifference) + ); } @Override public void afterDocumentChange(int startLine, int endLine, int symbolsDifference) { - updateDeferredData(startLine, endLine, symbolsDifference); } }); } - private void updateDeferredData(int startLine, int endLine, int symbolsDifference) { - for (int i = startLine; i <= endLine; i++) { - myDirtyLines.add(i); - } - myDeferredChanges.add(new DeferredChange(startLine, endLine, symbolsDifference)); - } - private static class DeferredChange { - final int startLine; - final int endLine; + final int startOffset; + final int endOffset; final int symbolsDifference; - DeferredChange(int startLine, int endLine, int symbolsDifference) { - this.startLine = startLine; - this.endLine = endLine; + DeferredChange(int startOffset, int endOffset, int symbolsDifference) { + this.startOffset = startOffset; + this.endOffset = endOffset; this.symbolsDifference = symbolsDifference; } @Override public String toString() { - return startLine + "-" + endLine + ": " + symbolsDifference; + return startOffset + "-" + endOffset + ": " + symbolsDifference; } } } diff --git a/platform/platform-resources-en/src/messages/CodeInsightBundle.properties b/platform/platform-resources-en/src/messages/CodeInsightBundle.properties index 66a1abdcdd33..a805e1f0be06 100644 --- a/platform/platform-resources-en/src/messages/CodeInsightBundle.properties +++ b/platform/platform-resources-en/src/messages/CodeInsightBundle.properties @@ -224,11 +224,11 @@ javadoc.external.fetch.error.message=Cannot fetch remote documentation: {0} searching.for.implementations=Searching For Implementations... goto.implementation.chooserTitle=Choose Implementation of {0} ({1} found) -goto.implementation.notFound=Not implementations found +goto.implementation.notFound=No implementations found goto.test.chooserTitle.test=Choose Test for {0} ({1} found) goto.test.chooserTitle.subject=Choose Test Subject for {0} ({1} found) -goto.test.notFound=Not test subjects found +goto.test.notFound=No test subjects found incremental.search.tooltip.prefix=Search for: goto.super.method.chooser.title=Choose super method diff --git a/platform/platform-resources-en/src/messages/PsiBundle.properties b/platform/platform-resources-en/src/messages/PsiBundle.properties index dc1b7f09915e..9c5f405feee4 100644 --- a/platform/platform-resources-en/src/messages/PsiBundle.properties +++ b/platform/platform-resources-en/src/messages/PsiBundle.properties @@ -32,14 +32,14 @@ el.lparen.expected=( expected el.rparen.expected=) expected el.colon.expected=: expected el.value.expected=Value expected -el.cannot.resolve.function=Cannot resolve function {0} #loc -el.cannot.resolve.namespace=Cannot resolve namespace {0} #loc -el.cannot.resolve.property=Cannot resolve property or method {0} (dynamic property?) #loc +el.cannot.resolve.function=Cannot resolve function ''{0}'' #loc +el.cannot.resolve.namespace=Cannot resolve namespace ''{0}'' #loc +el.cannot.resolve.property=Cannot resolve property or method ''{0}'' (dynamic property?) #loc el.mismatched.parameters.count=Different number of formal and actual parameters el.declare.variable.intention.family=Declare Variable As External Data el.declare.variable.via.usebean.intention.name=Declare external variable as el.declare.variable.via.comment.annotation.intention.name=Declare external variable in comment annotation -el.cannot.resolve.variable=Cannot resolve variable {0} #loc +el.cannot.resolve.variable=Cannot resolve variable ''{0}'' #loc jsf.el.out.of.attribute=JSF EL out of attribute #loc jsf.method.call.is.nonstd.extension=Method call is nonstandard extension #loc diff --git a/platform/util/src/com/intellij/util/containers/LockPoolSynchronizedMap.java b/platform/util/src/com/intellij/util/containers/LockPoolSynchronizedMap.java index 4a5c5fb39bcf..e75471b6910b 100644 --- a/platform/util/src/com/intellij/util/containers/LockPoolSynchronizedMap.java +++ b/platform/util/src/com/intellij/util/containers/LockPoolSynchronizedMap.java @@ -35,9 +35,8 @@ public class LockPoolSynchronizedMap extends THashMap implements Con private static final JBReentrantReadWriteLock[] ourLocks = new JBReentrantReadWriteLock[NUM_LOCKS]; private static int ourLockAllocationCounter = 0; - private final JBReentrantReadWriteLock mutex = allocateLock(); - private final JBLock r = mutex.readLock(); - private final JBLock w = mutex.writeLock(); + private final JBLock r; + private final JBLock w; static { for (int i = 0; i < ourLocks.length; i++) { @@ -45,6 +44,11 @@ public class LockPoolSynchronizedMap extends THashMap implements Con } } + { + final JBReentrantReadWriteLock mutex = allocateLock(); + r = mutex.readLock(); + w = mutex.writeLock(); + } public LockPoolSynchronizedMap() { } @@ -250,14 +254,11 @@ public class LockPoolSynchronizedMap extends THashMap implements Con public boolean remove(Object key, Object oldValue) { w.lock(); try { - if (!Comparing.equal(oldValue, get(key))) { - return false; - } - return super.remove(key) != null; + V currentValue = get(key); + return Comparing.equal(oldValue, currentValue) && super.remove(key) != null; } finally { w.unlock(); } } - } diff --git a/platform/vcs-api/src/com/intellij/openapi/vcs/ConcurrentTasks.java b/platform/vcs-api/src/com/intellij/openapi/vcs/ConcurrentTasks.java index 8ad878e809e7..cbded4673733 100644 --- a/platform/vcs-api/src/com/intellij/openapi/vcs/ConcurrentTasks.java +++ b/platform/vcs-api/src/com/intellij/openapi/vcs/ConcurrentTasks.java @@ -22,6 +22,7 @@ import com.intellij.openapi.progress.ProgressManager; import com.intellij.util.Consumer; import com.intellij.util.concurrency.Semaphore; +import java.util.Arrays; import java.util.LinkedList; import java.util.List; import java.util.concurrent.Future; @@ -33,7 +34,7 @@ public class ConcurrentTasks { private volatile T myResult; private volatile int myCntAlive; private final ProgressIndicator myParentIndicator; - private final Consumer>[] myTasks; + private final List>> myTasks; public void compute() { final EmptyProgressIndicator pi = new EmptyProgressIndicator() { @@ -45,7 +46,7 @@ public class ConcurrentTasks { super.checkCanceled(); } }; - myCntAlive = myTasks.length; + myCntAlive = myTasks.size(); mySemaphore.down(); final List> futures = new LinkedList>(); @@ -85,7 +86,7 @@ public class ConcurrentTasks { if (myResultKnown) break; if (myCntAlive <= 0) break; pi.checkCanceled(); - mySemaphore.waitFor(1000); + mySemaphore.waitFor(300); } // in it possible to even interrupt() threads involved, but at the moment it's better for tasks themselves to check cancel status for (Future future : futures) { @@ -104,6 +105,12 @@ public class ConcurrentTasks { } public ConcurrentTasks(final ProgressIndicator parentIndicator, final Consumer>... tasks) { + myParentIndicator = parentIndicator; + myTasks = Arrays.asList(tasks); + mySemaphore = new Semaphore(); + } + + public ConcurrentTasks(final ProgressIndicator parentIndicator, final List>> tasks) { myParentIndicator = parentIndicator; myTasks = tasks; mySemaphore = new Semaphore(); diff --git a/platform/vcs-api/src/com/intellij/openapi/vcs/impl/VcsDescriptor.java b/platform/vcs-api/src/com/intellij/openapi/vcs/impl/VcsDescriptor.java index 13b30cb0ea90..aee403b937d9 100644 --- a/platform/vcs-api/src/com/intellij/openapi/vcs/impl/VcsDescriptor.java +++ b/platform/vcs-api/src/com/intellij/openapi/vcs/impl/VcsDescriptor.java @@ -84,6 +84,6 @@ public class VcsDescriptor implements Comparable { @Override public String toString() { - return myDisplayName; + return getDisplayName(); } } 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 295cea12e625..f836b3627bff 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 @@ -45,6 +45,7 @@ import com.intellij.ui.EditorNotifications; import com.intellij.util.ConcurrencyUtil; import com.intellij.util.Consumer; import com.intellij.util.EventDispatcher; +import com.intellij.util.NullableFunction; import com.intellij.util.containers.MultiMap; import com.intellij.util.messages.Topic; import org.jdom.Element; @@ -852,7 +853,7 @@ public class ChangeListManagerImpl extends ChangeListManagerEx implements Projec private boolean doCommit(final LocalChangeList changeList, final List changes, final boolean synchronously) { return new CommitHelper(myProject, changeList, changes, changeList.getName(), - changeList.getComment(), new ArrayList(), false, synchronously, null).doCommit(); + changeList.getComment(), new ArrayList(), false, synchronously, NullableFunction.NULL).doCommit(); } public void commitChangesSynchronously(LocalChangeList changeList, List changes) { diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/committed/CommittedChangesViewManager.java b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/committed/CommittedChangesViewManager.java index 9d5b5cafa560..907df4296519 100644 --- a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/committed/CommittedChangesViewManager.java +++ b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/committed/CommittedChangesViewManager.java @@ -24,6 +24,7 @@ package com.intellij.openapi.vcs.changes.committed; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Disposer; import com.intellij.openapi.vcs.*; import com.intellij.openapi.vcs.changes.ui.ChangesViewContentProvider; import com.intellij.openapi.vcs.versionBrowser.CommittedChangeList; @@ -89,6 +90,7 @@ public class CommittedChangesViewManager implements ChangesViewContentProvider { public void disposeContent() { myVcsManager.removeVcsListener(myVcsListener); myConnection.disconnect(); + Disposer.dispose(myComponent); myComponent = null; } diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/impl/AbstractVcsHelperImpl.java b/platform/vcs-impl/src/com/intellij/openapi/vcs/impl/AbstractVcsHelperImpl.java index 7bb00f7efbfb..7d931e509fea 100644 --- a/platform/vcs-impl/src/com/intellij/openapi/vcs/impl/AbstractVcsHelperImpl.java +++ b/platform/vcs-impl/src/com/intellij/openapi/vcs/impl/AbstractVcsHelperImpl.java @@ -192,6 +192,10 @@ public class AbstractVcsHelperImpl extends AbstractVcsHelper { myBuffer.flush(); ApplicationManager.getApplication().invokeLater(new Runnable() { public void run() { + if (mySession == null) { + // nothing to be done, exit + return; + } ensureHistoryPanelCreated().getHistoryPanelRefresh().finished(); } }); diff --git a/plugins/git4idea/src/git4idea/checkin/GitPushActiveBranchesDialog.java b/plugins/git4idea/src/git4idea/checkin/GitPushActiveBranchesDialog.java index a92a8336da29..df3603fec33f 100644 --- a/plugins/git4idea/src/git4idea/checkin/GitPushActiveBranchesDialog.java +++ b/plugins/git4idea/src/git4idea/checkin/GitPushActiveBranchesDialog.java @@ -232,6 +232,9 @@ public class GitPushActiveBranchesDialog extends DialogWrapper { Set unchecked = new HashSet(); uncheckedCommits.put(r.root, unchecked); if (r.commits.size() == 0) { + if (r.remoteCommits > 0) { + roots.add(r.root); + } continue; } boolean seenCheckedNode = false; diff --git a/plugins/git4idea/src/git4idea/checkout/branches/GitBranchConfigurationChangedDialog.java b/plugins/git4idea/src/git4idea/checkout/branches/GitBranchConfigurationChangedDialog.java index 717ae4931284..3e545187dfbf 100644 --- a/plugins/git4idea/src/git4idea/checkout/branches/GitBranchConfigurationChangedDialog.java +++ b/plugins/git4idea/src/git4idea/checkout/branches/GitBranchConfigurationChangedDialog.java @@ -89,7 +89,7 @@ public class GitBranchConfigurationChangedDialog extends DialogWrapper { myTable.setModel(new DescriptorTableModel()); myNameTextField.setText(config.getName()); myNewAction = new DialogWrapperExitAction("New Configuration", NEW_CONFIGURATION); - myNameTextField.getDocument().addDocumentListener(new DocumentAdapter() { + final DocumentAdapter l = new DocumentAdapter() { @Override protected void textChanged(DocumentEvent e) { String text = myNameTextField.getText().trim(); @@ -113,8 +113,9 @@ public class GitBranchConfigurationChangedDialog extends DialogWrapper { setOKActionEnabled(s == null); myNewAction.setEnabled(s == null); } - }); - + }; + myNameTextField.getDocument().addDocumentListener(l); + l.changedUpdate(null); setOKButtonText("Update"); init(); } diff --git a/plugins/git4idea/src/git4idea/checkout/branches/GitBranchConfigurations.java b/plugins/git4idea/src/git4idea/checkout/branches/GitBranchConfigurations.java index ef2bc44d9d85..a5a87a32635d 100644 --- a/plugins/git4idea/src/git4idea/checkout/branches/GitBranchConfigurations.java +++ b/plugins/git4idea/src/git4idea/checkout/branches/GitBranchConfigurations.java @@ -38,6 +38,7 @@ import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.wm.ToolWindowManager; import com.intellij.util.EventDispatcher; import git4idea.GitBranch; +import git4idea.GitRevisionNumber; import git4idea.GitUtil; import git4idea.GitVcs; import git4idea.commands.GitCommand; @@ -525,7 +526,21 @@ public class GitBranchConfigurations implements PersistentStateComponent rc = new ArrayList(); for (final String c : myConfigurations.getRemotesCandidates()) { - rc.add(new DumbAwareAction(c) { + rc.add(new DumbAwareAction(escapeActionText(c)) { @Override public void actionPerformed(AnActionEvent e) { myConfigurations.startCheckout(null, c, false); @@ -273,13 +276,13 @@ public class GitBranchesWidget extends TextPanel implements CustomStatusBarWidge } /** + * Ensure that action for checking out configurations are crated + * * @return get or create selectable configuration group */ - private AnAction[] getSelectable() { + private AnAction[] ensureSelectableCreated() { assert myPopupEnabled : "pop should be enabled"; - if (mySelectableConfigurations == null) { - ArrayList rc = new ArrayList(); - + if (mySelectableConfigurations == null || mySelectableWithChangesConfigurations == null) { GitBranchConfiguration current; try { current = myConfigurations.getCurrentConfiguration(); @@ -287,35 +290,50 @@ public class GitBranchesWidget extends TextPanel implements CustomStatusBarWidge catch (VcsException e) { LOG.error("Unexpected error at this point", e); mySelectableConfigurations = new AnAction[0]; + mySelectableWithChangesConfigurations = new AnAction[0]; + return mySelectableConfigurations; } String name = current == null ? "" : current.getName(); - for (final String c : myConfigurations.getConfigurationNames()) { - if (name.equals(c)) { - // skip current config - continue; - } - rc.add(new DumbAwareAction(c) { - @Override - public void actionPerformed(AnActionEvent e) { - try { - final GitBranchConfiguration toCheckout = myConfigurations.getConfiguration(c); - if (toCheckout == null) { - throw new VcsException("The configuration " + c + " cannot be found."); - } - myConfigurations.startCheckout(toCheckout, null, true); - } - catch (VcsException e1) { - GitUIUtil.showOperationError(myProject, e1, "Unable to load: " + c); - } - } - }); - } - mySelectableConfigurations = rc.toArray(new AnAction[rc.size()]); + mySelectableConfigurations = checkoutActions(name, true); + mySelectableWithChangesConfigurations = checkoutActions(name, false); } return mySelectableConfigurations; } + /** + * Checkout actions + * + * @param name the excluded name + * @param quick true, if quick checkout actions + * @return an array of actions for the configurations + */ + private AnAction[] checkoutActions(String name, final boolean quick) { + ArrayList rc = new ArrayList(); + for (final String c : myConfigurations.getConfigurationNames()) { + if (name.equals(c)) { + // skip current config + continue; + } + rc.add(new DumbAwareAction(escapeActionText(c)) { + @Override + public void actionPerformed(AnActionEvent e) { + try { + final GitBranchConfiguration toCheckout = myConfigurations.getConfiguration(c); + if (toCheckout == null) { + throw new VcsException("The configuration " + c + " cannot be found."); + } + myConfigurations.startCheckout(toCheckout, null, quick); + } + catch (VcsException e1) { + GitUIUtil.showOperationError(myProject, e1, "Unable to load: " + c); + } + } + }); + } + return rc.toArray(new AnAction[rc.size()]); + } + /** * @return the action group for popup */ @@ -347,12 +365,24 @@ public class GitBranchesWidget extends TextPanel implements CustomStatusBarWidge } }); myPopupActionGroup.add(new MyRemotesActionGroup()); + myPopupActionGroup.add(new MySelectableWithChangesActionGroup()); myPopupActionGroup.addSeparator("Branch Configurations"); myPopupActionGroup.add(new MySelectableActionGroup()); } return myPopupActionGroup; } + + /** + * Escape action text (underscores) + * + * @param t the text to escape + * @return escaped text + */ + private static String escapeActionText(String t) { + return t.replaceAll("_", "__"); + } + /** * Update label on the widget */ @@ -466,7 +496,29 @@ public class GitBranchesWidget extends TextPanel implements CustomStatusBarWidge @NotNull @Override public AnAction[] getChildren(@Nullable AnActionEvent e) { - return getSelectable(); + return ensureSelectableCreated(); + } + } + + /** + * Remotes action group + */ + class MySelectableWithChangesActionGroup extends ActionGroup { + /** + * The constructor + */ + public MySelectableWithChangesActionGroup() { + super("Check out with Selected Changes...", true); + } + + /** + * {@inheritDoc} + */ + @NotNull + @Override + public AnAction[] getChildren(@Nullable AnActionEvent e) { + ensureSelectableCreated(); + return mySelectableWithChangesConfigurations; } } @@ -504,6 +556,7 @@ public class GitBranchesWidget extends TextPanel implements CustomStatusBarWidge @Override public void run() { mySelectableConfigurations = null; + mySelectableWithChangesConfigurations = null; } }); } @@ -535,6 +588,7 @@ public class GitBranchesWidget extends TextPanel implements CustomStatusBarWidge @Override public void run() { mySelectableConfigurations = null; + mySelectableWithChangesConfigurations = null; updateLabel(); } }); diff --git a/plugins/git4idea/src/git4idea/checkout/branches/GitCheckoutProcess.java b/plugins/git4idea/src/git4idea/checkout/branches/GitCheckoutProcess.java index 2b61f51044d3..db69c8745ac1 100644 --- a/plugins/git4idea/src/git4idea/checkout/branches/GitCheckoutProcess.java +++ b/plugins/git4idea/src/git4idea/checkout/branches/GitCheckoutProcess.java @@ -210,7 +210,7 @@ public class GitCheckoutProcess { // preparation phase finished. do actual checkout. assert myNewConfiguration != null; List checkoutRoots = rootsToCheckout(); - if (checkoutRoots.size() > 0) { + if (myNewConfiguration != oldConfiguration || checkoutRoots.size() > 0) { // TODO disable saving myProgress.setText("Shelving changes..."); Pair changesPair = shelveChanges(myProgress, oldConfiguration.getName(), selected); @@ -218,15 +218,20 @@ public class GitCheckoutProcess { // save changes in old root, it also may be aliased with new root oldConfiguration.setChanges(changesPair.first); try { - HashSet startedRoots = new HashSet(); - boolean failed = !checkoutAndRefreshRoots(checkoutRoots, startedRoots); - myProgress.setText2(""); - if (!failed) { - myConfig.setCurrentConfiguration(myNewConfiguration); + if (checkoutRoots.size() > 0) { + HashSet startedRoots = new HashSet(); + boolean failed = !checkoutAndRefreshRoots(checkoutRoots, startedRoots); + myProgress.setText2(""); + if (!failed) { + myConfig.setCurrentConfiguration(myNewConfiguration); + } + else { + myNewConfiguration = oldConfiguration; + rollbackRootCheckout(startedRoots); + } } else { - myNewConfiguration = oldConfiguration; - rollbackRootCheckout(startedRoots); + myConfig.setCurrentConfiguration(myNewConfiguration); } } finally { @@ -241,9 +246,6 @@ public class GitCheckoutProcess { } // TODO enable saving } - else { - myConfig.setCurrentConfiguration(myNewConfiguration); - } } finally { myProjectManager.unblockReloadingProjectOnExternalChanges(); @@ -253,6 +255,9 @@ public class GitCheckoutProcess { catch (VcsException e) { myExceptions.add(e); } + catch (Throwable e) { + myExceptions.add(new VcsException("The checkout process failed: " + e.getMessage(), e)); + } finally { saveAll(); // saves configuration changes } diff --git a/plugins/git4idea/src/git4idea/checkout/branches/GitManageConfigurationsDialog.java b/plugins/git4idea/src/git4idea/checkout/branches/GitManageConfigurationsDialog.java index 4103b5a826de..20bad4ea7882 100644 --- a/plugins/git4idea/src/git4idea/checkout/branches/GitManageConfigurationsDialog.java +++ b/plugins/git4idea/src/git4idea/checkout/branches/GitManageConfigurationsDialog.java @@ -168,12 +168,12 @@ public class GitManageConfigurationsDialog extends DialogWrapper { myShelveNameLabel.setText(d); myShelveNameLabel.setToolTipText(""); } } - boolean isNonCurrent = selected != null || selected != current; + boolean isNonCurrent = selected != null && selected != current; myDeleteButton.setEnabled(isNonCurrent); setOKActionEnabled(isNonCurrent && myConfigurations.getSpecialStatus() == GitBranchConfigurations.SpecialStatus.NORMAL); } diff --git a/plugins/git4idea/src/git4idea/checkout/branches/GitSwitchBranchesDialog.java b/plugins/git4idea/src/git4idea/checkout/branches/GitSwitchBranchesDialog.java index b7e2965e8596..9a1b4ad5ec0c 100644 --- a/plugins/git4idea/src/git4idea/checkout/branches/GitSwitchBranchesDialog.java +++ b/plugins/git4idea/src/git4idea/checkout/branches/GitSwitchBranchesDialog.java @@ -35,6 +35,7 @@ import com.intellij.ui.table.JBTable; import com.intellij.util.ui.AbstractTableCellEditor; import git4idea.GitBranch; import git4idea.GitRevisionNumber; +import git4idea.GitUtil; import git4idea.validators.GitBranchNameValidator; import org.jetbrains.annotations.Nullable; @@ -58,6 +59,10 @@ import java.util.List; * The switch branches dialog */ public class GitSwitchBranchesDialog extends DialogWrapper { + /** + * The prefix for remote references + */ + public static final String REMOTES_PREFIX = "remotes/"; /** * The branch configuration name text field */ @@ -168,6 +173,12 @@ public class GitSwitchBranchesDialog extends DialogWrapper { else { myBranches = prepareBranchDescriptors(target, roots); } + Collections.sort(myBranches, new Comparator() { + @Override + public int compare(BranchDescriptor o1, BranchDescriptor o2) { + return o1.getRoot().compareTo(o2.getRoot()); + } + }); if (target == null) { myNameTextField.setText(generateNewConfigurationName()); } @@ -205,6 +216,7 @@ public class GitSwitchBranchesDialog extends DialogWrapper { verify(); } }); + verify(); init(); } @@ -218,7 +230,7 @@ public class GitSwitchBranchesDialog extends DialogWrapper { * @param remoteBranch the remote branch * @param config the configuration * @param isModify the modify mode flag - * @return the pair of selected changes and + * @return the dialog result object * @throws VcsException if there is a problem with accessing git */ @Nullable @@ -256,7 +268,11 @@ public class GitSwitchBranchesDialog extends DialogWrapper { for (BranchDescriptor d : myBranches) { if (d.root != null) { if (!StringUtil.isEmpty(d.newBranchName)) { - rc.referencesToUse.put(d.root, d.referenceToCheckout.trim()); + String ref = d.referenceToCheckout.trim(); + if (!d.existingBranches.contains(ref)) { + ref = myConfig.detectTag(d.root, ref); + } + rc.referencesToUse.put(d.root, ref); rc.target.setBranch(d.root.getPath(), d.newBranchName.trim()); rc.checkoutNeeded.add(d.root); } @@ -334,11 +350,11 @@ public class GitSwitchBranchesDialog extends DialogWrapper { } } if (name == null) { - name = "Unnamed"; + name = "untitled"; } if (myExistingConfigNames.contains(name)) { for (int i = 2; i < Integer.MAX_VALUE; i++) { - String t = name + " " + i; + String t = name + i; if (!myExistingConfigNames.contains(t)) { name = t; break; @@ -361,19 +377,31 @@ public class GitSwitchBranchesDialog extends DialogWrapper { assert roots.size() > 0; List rc = new ArrayList(); HashSet allBranches = new HashSet(); + allBranches.addAll(myConfig.getConfigurationNames()); + final String qualifiedBranch = "remotes/" + remoteBranch; + String firstRemote = remoteBranch.endsWith("/HEAD") ? null : qualifiedBranch; for (VirtualFile root : roots) { BranchDescriptor d = new BranchDescriptor(); d.root = root; d.currentReference = myConfig.describeRoot(root); - d.referenceToCheckout = "remotes/" + remoteBranch; + if (firstRemote == null) { + firstRemote = resolveHead(qualifiedBranch, d.root.getPath()); + } + d.referenceToCheckout = qualifiedBranch; GitBranch.listAsStrings(myProject, root, false, true, d.existingBranches, null); GitBranch.listAsStrings(myProject, root, true, true, d.referencesToSelect, null); allBranches.addAll(d.existingBranches); rc.add(d); } - int p = remoteBranch.indexOf('/'); - assert p > 0 && p < remoteBranch.length() - 1 : "Unexpected format for remote branch: " + remoteBranch; - String candidate = remoteBranch.substring(p + 1); + String candidate; + if (firstRemote == null) { + candidate = "untitled"; + } + else { + int p = firstRemote.indexOf('/', REMOTES_PREFIX.length() + 1); + assert p > 0 && p < firstRemote.length() - 1 : "Unexpected format for remote branch: " + firstRemote; + candidate = firstRemote.substring(p + 1); + } String actual = null; if (!allBranches.contains(candidate)) { actual = candidate; @@ -395,6 +423,73 @@ public class GitSwitchBranchesDialog extends DialogWrapper { return rc; } + /** + * Get candidate new branch name from remote branch + * + * @param value the value used to guess new reference + * @param d a description to use + * @return the candidate branch name + */ + @Nullable + private String getCandidateLocal(String value, BranchDescriptor d) { + if (StringUtil.isEmpty(value) || !value.startsWith(REMOTES_PREFIX)) { + return null; + } + int p = value.indexOf('/', REMOTES_PREFIX.length() + 1); + String candidate = null; + if (p != -1) { + String c = value.substring(p + 1); + if (!d.existingBranches.contains(c)) { + candidate = c; + } + else { + for (int i = 2; i < Integer.MAX_VALUE; i++) { + String cn = c + i; + if (!d.existingBranches.contains(cn)) { + candidate = cn; + break; + } + } + } + if ("HEAD".equals(candidate)) { + final String rootPath = d.root.getPath(); + String newRef = resolveHead(value, rootPath); + candidate = newRef == null ? null : getCandidateLocal(newRef, d); + } + } + return candidate; + } + + /** + * Resolve remote had reference + * + * @param value the reference to resolve + * @param rootPath the root path + * @return the resolved reference or null + */ + @Nullable + private static String resolveHead(String value, String rootPath) { + if (!value.startsWith("remotes/")) { + return null; + } + String newRef; + try { + final String refText = + new String(FileUtil.loadFileText(new File(rootPath, ".git/refs/" + value), GitUtil.UTF8_ENCODING)).trim(); + String refsPrefix = "ref: refs/"; + if (refText.endsWith("/HEAD") || !refText.startsWith(refsPrefix)) { + newRef = null; + } + else { + newRef = refText.substring(refsPrefix.length()); + } + } + catch (Exception e) { + newRef = null; + } + return newRef; + } + /** * Prepare branch descriptors for existing configuration * @@ -441,7 +536,9 @@ public class GitSwitchBranchesDialog extends DialogWrapper { return rc; } - + /** + * {@inheritDoc} + */ @Override protected JComponent createCenterPanel() { return myRoot; @@ -472,16 +569,25 @@ public class GitSwitchBranchesDialog extends DialogWrapper { */ static final int COLUMNS = STATUS_COLUMN + 1; + /** + * {@inheritDoc} + */ @Override public int getRowCount() { return myBranches.size(); } + /** + * {@inheritDoc} + */ @Override public int getColumnCount() { return COLUMNS; } + /** + * {@inheritDoc} + */ @Override public boolean isCellEditable(int rowIndex, int columnIndex) { BranchDescriptor d = myBranches.get(rowIndex); @@ -491,6 +597,9 @@ public class GitSwitchBranchesDialog extends DialogWrapper { return columnIndex == REVISION_COLUMN || columnIndex == NEW_BRANCH_COLUMN; } + /** + * {@inheritDoc} + */ @Override public void setValueAt(Object aValue, int rowIndex, int columnIndex) { String t = (String)aValue; @@ -499,24 +608,15 @@ public class GitSwitchBranchesDialog extends DialogWrapper { return; } if (columnIndex == REVISION_COLUMN) { + String currentCandidate = getCandidateLocal(d.referenceToCheckout, d); + boolean isCurrentMatchCandidate = currentCandidate != null && currentCandidate.equals(d.newBranchName); d.referenceToCheckout = t; - String remotesPrefix = "remotes/"; - if (StringUtil.isEmpty(d.newBranchName) && t.startsWith(remotesPrefix) && d.referencesToSelect.contains(t)) { - int p = t.indexOf(t.indexOf('/'), remotesPrefix.length() + 1); - if (p != -1) { - String c = t.substring(p); - if (!d.existingBranches.contains(c)) { - d.newBranchName = c; - } - else { - for (int i = 2; i < Integer.MAX_VALUE; i++) { - String candidate = c + i; - if (!d.existingBranches.contains(c)) { - d.newBranchName = candidate; - break; - } - } - } + if ((StringUtil.isEmpty(d.newBranchName) || isCurrentMatchCandidate) && + t.startsWith(REMOTES_PREFIX) && + d.referencesToSelect.contains(t)) { + String candidate = getCandidateLocal(t, d); + if (candidate != null) { + d.newBranchName = candidate; } } } @@ -527,6 +627,10 @@ public class GitSwitchBranchesDialog extends DialogWrapper { fireTableRowsUpdated(rowIndex, rowIndex); } + + /** + * {@inheritDoc} + */ @Override public Object getValueAt(int rowIndex, int columnIndex) { BranchDescriptor d = myBranches.get(rowIndex); @@ -561,6 +665,9 @@ public class GitSwitchBranchesDialog extends DialogWrapper { } } + /** + * {@inheritDoc} + */ @Override public String getColumnName(int column) { switch (column) { @@ -819,8 +926,7 @@ public class GitSwitchBranchesDialog extends DialogWrapper { @Override protected void textChanged(DocumentEvent e) { String s = myTextField.getText(); - if (s.length() == 0 && - (myInvalidValues == null || !myInvalidValues.contains(s)) && + if ((myInvalidValues == null || !myInvalidValues.contains(s)) && (s.length() == 0 || GitBranchNameValidator.INSTANCE.checkInput(s))) { myTextField.setForeground(myDefaultForeground); } diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/actions/generate/constructors/ConstructorGenerateHandler.java b/plugins/groovy/src/org/jetbrains/plugins/groovy/actions/generate/constructors/ConstructorGenerateHandler.java index 45dd61030793..ca7f92f6df4d 100644 --- a/plugins/groovy/src/org/jetbrains/plugins/groovy/actions/generate/constructors/ConstructorGenerateHandler.java +++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/actions/generate/constructors/ConstructorGenerateHandler.java @@ -17,12 +17,16 @@ package org.jetbrains.plugins.groovy.actions.generate.constructors; import com.intellij.codeInsight.generation.*; import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.text.StringUtil; import com.intellij.psi.*; import com.intellij.util.ArrayUtil; import com.intellij.util.IncorrectOperationException; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import org.jetbrains.plugins.groovy.compiler.generator.GroovyToJavaGenerator; import org.jetbrains.plugins.groovy.lang.psi.GroovyPsiElementFactory; +import org.jetbrains.plugins.groovy.lang.psi.api.statements.GrField; +import org.jetbrains.plugins.groovy.lang.psi.api.statements.params.GrParameter; import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.members.GrMethod; import org.jetbrains.plugins.groovy.lang.psi.util.PsiUtil; @@ -35,55 +39,50 @@ import java.util.List; */ public class ConstructorGenerateHandler extends GenerateConstructorHandler { + private static final String DEF_PSEUDO_ANNO = "_____intellij_idea_rulez_def_"; + @Nullable protected ClassMember[] chooseOriginalMembers(PsiClass aClass, Project project) { - final ClassMember[] classMembers = super.chooseOriginalMembers(aClass, project); - + final ClassMember[] classMembers = chooseOriginalMembersImpl(aClass, project); if (classMembers == null) return null; List res = new ArrayList(); final PsiElementFactory factory = JavaPsiFacade.getInstance(aClass.getProject()).getElementFactory(); - String text; - for (ClassMember classMember : classMembers) { - if (classMember instanceof PsiMethodMember) { - PsiMethod constructorImpl; - final PsiMethod method = ((PsiMethodMember) classMember).getElement(); - - //TODO: rewrite it like fine java method - text = method.getText(); - try { - constructorImpl = factory.createMethodFromText(text, aClass); - res.add(new PsiMethodMember(constructorImpl)); - } catch (IncorrectOperationException e) { - e.printStackTrace(); + final PsiMethod method = ((PsiMethodMember)classMember).getElement(); + final PsiMethod copy = (PsiMethod)method.copy(); + if (copy instanceof GrMethod) { + for (GrParameter parameter : ((GrMethod)copy).getParameterList().getParameters()) { + if (parameter.getTypeElementGroovy() == null) { + parameter.setName(DEF_PSEUDO_ANNO + parameter.getName()); + } + } } + res.add(new PsiMethodMember(factory.createMethodFromText(GroovyToJavaGenerator.generateMethodStub(copy), aClass))); } else if (classMember instanceof PsiFieldMember) { - final PsiFieldMember fieldMember = (PsiFieldMember) classMember; - PsiField fieldImpl; + final PsiField field = ((PsiFieldMember) classMember).getElement(); - final PsiField field = fieldMember.getElement(); - try { - fieldImpl = factory.createFieldFromText(field.getType().getCanonicalText() + " " + field.getName(), aClass); - res.add(new PsiFieldMember(fieldImpl)); - } catch (IncorrectOperationException e) { - e.printStackTrace(); - } + String prefix = field instanceof GrField && ((GrField)field).getTypeElementGroovy() == null ? DEF_PSEUDO_ANNO : ""; + res.add(new PsiFieldMember(factory.createFieldFromText(field.getType().getCanonicalText() + " " + prefix + field.getName(), aClass))); } } return res.toArray(new ClassMember[res.size()]); } + @Nullable + protected ClassMember[] chooseOriginalMembersImpl(PsiClass aClass, Project project) { + return super.chooseOriginalMembers(aClass, project); + } + @NotNull protected List generateMemberPrototypes(PsiClass aClass, ClassMember[] members) throws IncorrectOperationException { final List list = super.generateMemberPrototypes(aClass, members); List> grConstructors = new ArrayList>(); - GrMethod grConstructor; for (GenerationInfo generationInfo : list) { final PsiMember constructorMember = generationInfo.getPsiMember(); assert constructorMember instanceof PsiMethod; @@ -93,22 +92,26 @@ public class ConstructorGenerateHandler extends GenerateConstructorHandler { assert block != null; final String constructorName = aClass.getName(); - final String body = block.getText(); + final String body = StringUtil.replace(StringUtil.replace(block.getText(), DEF_PSEUDO_ANNO, ""), ";", ""); final PsiParameterList list1 = constructor.getParameterList(); List parametersNames = new ArrayList(); + List parametersTypes = new ArrayList(); for (PsiParameter parameter : list1.getParameters()) { - parametersNames.add(parameter.getName()); + final String fullName = parameter.getName(); + parametersNames.add(StringUtil.trimStart(fullName, DEF_PSEUDO_ANNO)); + parametersTypes.add(fullName.startsWith(DEF_PSEUDO_ANNO) ? null : parameter.getType().getCanonicalText()); } final String[] paramNames = ArrayUtil.toStringArray(parametersNames); + final String[] paramTypes = ArrayUtil.toStringArray(parametersTypes); assert constructorName != null; - grConstructor = GroovyPsiElementFactory.getInstance(aClass.getProject()).createConstructorFromText(constructorName, null, paramNames, body); + GrMethod grConstructor = + GroovyPsiElementFactory.getInstance(aClass.getProject()).createConstructorFromText(constructorName, paramTypes, paramNames, body); PsiUtil.shortenReferences(grConstructor); - final PsiGenerationInfo psiGenerationInfo = new GroovyGenerationInfo(grConstructor); - grConstructors.add(psiGenerationInfo); + grConstructors.add(new GroovyGenerationInfo(grConstructor)); } return grConstructors; diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/codeInspection/assignment/GroovyAssignabilityCheckInspection.java b/plugins/groovy/src/org/jetbrains/plugins/groovy/codeInspection/assignment/GroovyAssignabilityCheckInspection.java index f2be2a783d20..3bd71167d4e7 100644 --- a/plugins/groovy/src/org/jetbrains/plugins/groovy/codeInspection/assignment/GroovyAssignabilityCheckInspection.java +++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/codeInspection/assignment/GroovyAssignabilityCheckInspection.java @@ -18,6 +18,7 @@ package org.jetbrains.plugins.groovy.codeInspection.assignment; import com.intellij.psi.*; import com.intellij.psi.tree.IElementType; +import com.intellij.psi.util.PsiTreeUtil; import com.intellij.psi.util.TypeConversionUtil; import org.jetbrains.annotations.Nls; import org.jetbrains.annotations.NotNull; @@ -31,6 +32,7 @@ import org.jetbrains.plugins.groovy.lang.psi.GroovyPsiElement; import org.jetbrains.plugins.groovy.lang.psi.api.statements.GrVariable; import org.jetbrains.plugins.groovy.lang.psi.api.statements.arguments.GrArgumentLabel; import org.jetbrains.plugins.groovy.lang.psi.api.statements.arguments.GrNamedArgument; +import org.jetbrains.plugins.groovy.lang.psi.api.statements.blocks.GrClosableBlock; import org.jetbrains.plugins.groovy.lang.psi.api.statements.blocks.GrOpenBlock; import org.jetbrains.plugins.groovy.lang.psi.api.statements.branch.GrReturnStatement; import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrAssignmentExpression; @@ -109,11 +111,8 @@ public class GroovyAssignabilityCheckInspection extends BaseInspection { public void visitReturnStatement(GrReturnStatement returnStatement) { super.visitReturnStatement(returnStatement); - final PsiElement parent = returnStatement.getParent(); - if (!(parent instanceof GrOpenBlock)) return; - final PsiElement element = parent.getParent(); - if (!(element instanceof GrMethod)) return; - GrMethod method = (GrMethod)element; + final GrMethod method = PsiTreeUtil.getParentOfType(returnStatement, GrMethod.class, true, GrClosableBlock.class); + if (method == null) return; final GrExpression value = returnStatement.getReturnValue(); final PsiType expectedType = method.getReturnType(); diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/codeInspection/unusedDef/UnusedDefInspection.java b/plugins/groovy/src/org/jetbrains/plugins/groovy/codeInspection/unusedDef/UnusedDefInspection.java index 5468100a25b4..76964a7a145e 100644 --- a/plugins/groovy/src/org/jetbrains/plugins/groovy/codeInspection/unusedDef/UnusedDefInspection.java +++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/codeInspection/unusedDef/UnusedDefInspection.java @@ -123,15 +123,14 @@ public class UnusedDefInspection extends GroovyLocalInspectionBase { if (element instanceof GrReferenceExpression) { PsiElement parent = element.getParent(); if (parent instanceof GrAssignmentExpression) { - toHighlight = ((GrAssignmentExpression)parent).getRValue(); + toHighlight = ((GrAssignmentExpression)parent).getLValue(); } if (parent instanceof GrPostfixExpression) { toHighlight = parent; } } else if (element instanceof GrVariable) { - toHighlight = ((GrVariable)element).getInitializerGroovy(); - if (toHighlight == null) toHighlight = ((GrVariable)element).getNameIdentifierGroovy(); + toHighlight = ((GrVariable)element).getNameIdentifierGroovy(); } if (toHighlight == null) toHighlight = element; problemsHolder.registerProblem(toHighlight, GroovyInspectionBundle.message("unused.assignment.tooltip"), diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/compiler/generator/GroovyToJavaGenerator.java b/plugins/groovy/src/org/jetbrains/plugins/groovy/compiler/generator/GroovyToJavaGenerator.java index fd11d3e49540..8191bd68cd05 100644 --- a/plugins/groovy/src/org/jetbrains/plugins/groovy/compiler/generator/GroovyToJavaGenerator.java +++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/compiler/generator/GroovyToJavaGenerator.java @@ -38,6 +38,7 @@ import com.intellij.psi.util.MethodSignatureBackedByPsiMethod; import com.intellij.psi.util.MethodSignatureUtil; import com.intellij.psi.util.TypeConversionUtil; import com.intellij.util.ArrayUtil; +import com.intellij.util.Function; import com.intellij.util.containers.CollectionFactory; import com.intellij.util.containers.ContainerUtil; import com.intellij.util.containers.HashSet; @@ -49,6 +50,7 @@ import org.jetbrains.plugins.groovy.compiler.GroovyCompilerConfiguration; import org.jetbrains.plugins.groovy.lang.psi.GrClassSubstitutor; import org.jetbrains.plugins.groovy.lang.psi.GroovyFile; import org.jetbrains.plugins.groovy.lang.psi.GroovyFileBase; +import org.jetbrains.plugins.groovy.lang.psi.GroovyPsiElement; import org.jetbrains.plugins.groovy.lang.psi.api.GroovyResolveResult; import org.jetbrains.plugins.groovy.lang.psi.api.auxiliary.modifiers.GrModifierList; import org.jetbrains.plugins.groovy.lang.psi.api.statements.GrConstructorInvocation; @@ -107,11 +109,13 @@ public class GroovyToJavaGenerator { private static final CharSequence PREFIX_SEPARATOR = "/"; private final CompileContext myContext; + private final List myAllToCompile; private final Project myProject; - public GroovyToJavaGenerator(Project project, CompileContext context) { + public GroovyToJavaGenerator(Project project, CompileContext context, List allToCompile) { myProject = project; myContext = context; + myAllToCompile = allToCompile; } public GenerationItem[] getGenerationItems(CompileContext context) { @@ -420,7 +424,7 @@ public class GroovyToJavaGenerator { text.append("}"); } - private static void writeAllMethods(StringBuffer text, List methods, PsiClass aClass) { + private void writeAllMethods(StringBuffer text, List methods, PsiClass aClass) { Set methodSignatures = new HashSet(); for (PsiMethod method : methods) { if (LightMethodBuilder.isLightMethod(method, GrClassImplUtil.SYNTHETIC_METHOD_IMPLEMENTATION)) { @@ -508,7 +512,7 @@ public class GroovyToJavaGenerator { return psiClass != null && GrClassSubstitutor.getSubstitutedClass(psiClass).isInterface(); } - private static void appendTypeParameters(StringBuffer text, PsiTypeParameterListOwner typeParameterListOwner) { + private void appendTypeParameters(StringBuffer text, PsiTypeParameterListOwner typeParameterListOwner) { if (typeParameterListOwner.hasTypeParameters()) { text.append("<"); PsiTypeParameter[] parameters = typeParameterListOwner.getTypeParameters(); @@ -529,7 +533,7 @@ public class GroovyToJavaGenerator { } } - private static void writeEnumConstants(StringBuffer text, GrEnumTypeDefinition enumDefinition) { + private void writeEnumConstants(StringBuffer text, GrEnumTypeDefinition enumDefinition) { text.append("\n "); GrEnumConstant[] enumConstants = enumDefinition.getEnumConstants(); for (int i = 0; i < enumConstants.length; i++) { @@ -555,7 +559,7 @@ public class GroovyToJavaGenerator { text.append(";"); } - private static void writeStubConstructorInvocation(StringBuffer text, PsiMethod constructor, PsiSubstitutor substitutor) { + private void writeStubConstructorInvocation(StringBuffer text, PsiMethod constructor, PsiSubstitutor substitutor) { final PsiParameter[] superParams = constructor.getParameterList().getParameters(); for (int j = 0; j < superParams.length; j++) { if (j > 0) text.append(", "); @@ -574,7 +578,7 @@ public class GroovyToJavaGenerator { } } - private static void writeConstructor(final StringBuffer text, final GrConstructor constructor, boolean isEnum) { + private void writeConstructor(final StringBuffer text, final GrConstructor constructor, boolean isEnum) { text.append("\n"); text.append(" "); if (!isEnum) { @@ -613,7 +617,7 @@ public class GroovyToJavaGenerator { text.append("\n }\n"); } - private static Set collectThrowsTypes(GrConstructor constructor, Set visited) { + private Set collectThrowsTypes(GrConstructor constructor, Set visited) { final GroovyResolveResult resolveResult = resolveChainingConstructor(constructor); if (resolveResult == null) { return Collections.emptySet(); @@ -673,7 +677,7 @@ public class GroovyToJavaGenerator { return result; } - private static void writeVariableDeclarations(StringBuffer text, GrVariableDeclaration variableDeclaration) { + private void writeVariableDeclarations(StringBuffer text, GrVariableDeclaration variableDeclaration) { GrTypeElement typeElement = variableDeclaration.getTypeElementGroovy(); final String type = typeElement == null ? CommonClassNames.JAVA_LANG_OBJECT : getTypeText(typeElement.getType(), typeElement, false); final String initializer = getDefaultValueText(type); @@ -694,7 +698,23 @@ public class GroovyToJavaGenerator { } } - private static void writeMethod(StringBuffer text, PsiMethod method, final PsiParameter[] parameters) { + public static String generateMethodStub(@NotNull PsiMethod method) { + if (!(method instanceof GroovyPsiElement)) { + return method.getText(); + } + + final GroovyToJavaGenerator generator = new GroovyToJavaGenerator(method.getProject(), null, Collections.emptyList()); + final StringBuffer buffer = new StringBuffer(); + if (method instanceof GrConstructor) { + generator.writeConstructor(buffer, (GrConstructor)method, false); + } + else { + generator.writeMethod(buffer, method, method.getParameterList().getParameters()); + } + return buffer.toString(); + } + + private void writeMethod(StringBuffer text, PsiMethod method, final PsiParameter[] parameters) { if (method == null) return; String name = method.getName(); if (!JavaPsiFacade.getInstance(method.getProject()).getNameHelper().isIdentifier(name)) @@ -739,7 +759,7 @@ public class GroovyToJavaGenerator { text.append("\n"); } - private static void writeParameterList(StringBuffer text, PsiParameter[] parameters) { + private void writeParameterList(StringBuffer text, PsiParameter[] parameters) { text.append("("); //writes myParameters @@ -805,14 +825,7 @@ public class GroovyToJavaGenerator { } } - private static String getTypeText(@Nullable PsiType type, @Nullable PsiElement context, boolean allowVarargs) { - if (context != null && type instanceof PsiClassType) { - final String accessible = findAccessibleSuperClass(context, ((PsiClassType)type).resolve()); - if (accessible != null) { - return accessible; - } - } - + private String getTypeText(@Nullable PsiType type, @Nullable final PsiElement context, boolean allowVarargs) { if (type instanceof PsiArrayType) { String componentText = getTypeText(((PsiArrayType)type).getComponentType(), context, false); if (allowVarargs && type instanceof PsiEllipsisType) { @@ -825,28 +838,57 @@ public class GroovyToJavaGenerator { return CommonClassNames.JAVA_LANG_OBJECT; } + if (type instanceof PsiClassType) { + final PsiClass raw = ((PsiClassType)type).resolve(); + if (raw != null) { + final String qname = getClassQualifiedName(raw, context); + if (qname != null) { + final PsiType[] parameters = ((PsiClassType)type).getParameters(); + if (parameters.length > 0) { + return qname + "<" + StringUtil.join(parameters, new Function() { + @Override + public String fun(PsiType type) { + return getTypeText(type, context, false); + } + }, ", ") + ">"; + } + return qname; + } + } + } + String canonicalText = type.getCanonicalText(); return canonicalText != null ? canonicalText : type.getPresentableText(); } @Nullable - private static String findAccessibleSuperClass(PsiElement context, @Nullable PsiClass initialClass) { - if (initialClass == null) { + private String getClassQualifiedName(PsiClass psiClass, @Nullable PsiElement context) { + if (context != null) { + psiClass = findAccessibleSuperClass(context, psiClass); + } + if (psiClass == null) { return null; } + if (psiClass instanceof GrTypeDefinition) { + if (!myAllToCompile.contains(psiClass.getContainingFile().getVirtualFile())) { + final PsiClass container = psiClass.getContainingClass(); + if (container != null) { + return getClassQualifiedName(container, null) + "$" + psiClass.getName(); + } + } + } + return psiClass.getQualifiedName(); + } + + @Nullable + private static PsiClass findAccessibleSuperClass(@NotNull PsiElement context, @NotNull PsiClass initialClass) { PsiClass curClass = initialClass; final PsiResolveHelper resolveHelper = JavaPsiFacade.getInstance(context.getProject()).getResolveHelper(); while (curClass != null && !resolveHelper.isAccessible(curClass, context, null)) { curClass = curClass.getSuperClass(); } - if (curClass != null && !initialClass.isEquivalentTo(curClass)) { - final String qname = curClass.getQualifiedName(); - if (qname != null) { - return qname; - } - } - return null; + return curClass; } CharTrie myTrie = new CharTrie(); diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/compiler/generator/GroovycStubGenerator.java b/plugins/groovy/src/org/jetbrains/plugins/groovy/compiler/generator/GroovycStubGenerator.java index 2571e658fd8e..635e5082657a 100644 --- a/plugins/groovy/src/org/jetbrains/plugins/groovy/compiler/generator/GroovycStubGenerator.java +++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/compiler/generator/GroovycStubGenerator.java @@ -111,7 +111,7 @@ public class GroovycStubGenerator extends GroovyCompilerBase { if (GroovyCompilerConfiguration.getInstance(myProject).isUseGroovycStubs()) { runGroovycCompiler(compileContext, module, toCompile, true, tempOutput, sink, tests); } else { - final GroovyToJavaGenerator generator = new GroovyToJavaGenerator(myProject, compileContext); + final GroovyToJavaGenerator generator = new GroovyToJavaGenerator(myProject, compileContext, toCompile); for (VirtualFile file : toCompile) { final String outPath = tempOutput.getPath(); final List relPaths = generator.generateItems(file, tempOutput); diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/config/GroovySupportConfigurable.java b/plugins/groovy/src/org/jetbrains/plugins/groovy/config/GroovySupportConfigurable.java index 1a74feb92f26..b8e9c93dc8d7 100644 --- a/plugins/groovy/src/org/jetbrains/plugins/groovy/config/GroovySupportConfigurable.java +++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/config/GroovySupportConfigurable.java @@ -18,16 +18,27 @@ package org.jetbrains.plugins.groovy.config; import com.intellij.ide.util.frameworkSupport.FrameworkSupportConfigurable; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.module.Module; +import com.intellij.openapi.roots.LibraryOrderEntry; import com.intellij.openapi.roots.ModifiableRootModel; import com.intellij.openapi.roots.ModuleRootManager; +import com.intellij.openapi.roots.OrderEntry; import com.intellij.openapi.roots.libraries.Library; +import com.intellij.openapi.roots.ui.configuration.projectRoot.LibrariesContainer; import com.intellij.openapi.roots.ui.configuration.projectRoot.LibrariesContainerFactory; +import com.intellij.openapi.ui.Messages; +import com.intellij.openapi.util.io.FileUtil; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.openapi.vfs.LocalFileSystem; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.util.containers.CollectionFactory; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.plugins.groovy.config.ui.GroovyFacetEditor; +import org.jetbrains.plugins.groovy.config.ui.ManagedLibrariesEditor; import org.jetbrains.plugins.groovy.util.LibrariesUtil; import javax.swing.*; +import java.util.List; /** * @author peter @@ -48,11 +59,72 @@ public class GroovySupportConfigurable extends FrameworkSupportConfigurable { addGroovySupport(module, rootModel); } + private boolean cleanDuplicates(List managers, ModifiableRootModel rootModel, final LibrariesContainer container) { + if (managers.isEmpty()) return true; + + for (OrderEntry entry : rootModel.getOrderEntries()) { + if (entry instanceof LibraryOrderEntry) { + final Library library = ((LibraryOrderEntry)entry).getLibrary(); + if (library == null) { + cleanUndefinedGroovyLibrary(rootModel, (LibraryOrderEntry)entry); + } else { + final LibraryManager manager = ManagedLibrariesEditor.findManagerFor(library, managers.toArray(new LibraryManager[managers.size()]), container); + if (manager != null) { + @SuppressWarnings({"NonConstantStringShouldBeStringBuffer"}) + String message = "There is already a " + manager.getLibraryCategoryName() + " library"; + final String version = manager.getLibraryVersion(library, container); + if (StringUtil.isNotEmpty(version)) { + message += " of version " + version; + } + message += ".\n Do you want to replace the existing one?"; + final String replace = "&Replace"; + final int result = + Messages + .showDialog(facetEditor.getComponent(), message, "Library already exists", new String[]{replace, "&Add", "&Cancel"}, 0, + null); + if (result == 2 || result < 0) { + return false; //cancel or escape + } + + if (result == 0) { + rootModel.removeOrderEntry(entry); + } + } + } + } + } + return true; + } + + private static void cleanUndefinedGroovyLibrary(ModifiableRootModel rootModel, LibraryOrderEntry entry) { + final String libraryName = entry.getLibraryName(); + if (libraryName == null) { + return; + } + + for (AbstractGroovyLibraryManager each : AbstractGroovyLibraryManager.EP_NAME.getExtensions()) { + if (each.managesName(libraryName)) { + rootModel.removeOrderEntry(entry); + return; + } + } + } + public void addGroovySupport(final Module module, ModifiableRootModel rootModel) { + final LibrariesContainer container = LibrariesContainerFactory.createContainer(rootModel); + if (!facetEditor.addNewSdk()) { final Library selectedLibrary = facetEditor.getSelectedLibrary(); if (selectedLibrary != null) { - LibrariesUtil.placeEntryToCorrectPlace(rootModel, rootModel.addLibraryEntry(selectedLibrary)); + List suitable = CollectionFactory.arrayList(); + for (final LibraryManager manager : AbstractGroovyLibraryManager.EP_NAME.getExtensions()) { + if (manager.managesLibrary(selectedLibrary, container)) { + suitable.add(manager); + } + } + if (cleanDuplicates(suitable, rootModel, container)) { + LibrariesUtil.placeEntryToCorrectPlace(rootModel, rootModel.addLibraryEntry(selectedLibrary)); + } } return; } @@ -60,6 +132,18 @@ public class GroovySupportConfigurable extends FrameworkSupportConfigurable { final String path = facetEditor.getNewSdkPath(); final AbstractGroovyLibraryManager libraryManager = facetEditor.getChosenManager(); if (path != null && libraryManager != null) { + List suitable = CollectionFactory.arrayList(); + suitable.add(libraryManager); + final VirtualFile vfile = LocalFileSystem.getInstance().refreshAndFindFileByPath(FileUtil.toSystemIndependentName(path)); + if (vfile != null) { + for (final LibraryManager manager : AbstractGroovyLibraryManager.EP_NAME.getExtensions()) { + if (manager != libraryManager && manager.isSDKHome(vfile)) { + suitable.add(manager); + } + } + } + + if (!cleanDuplicates(suitable, rootModel, container)) return; ApplicationManager.getApplication().invokeLater(new Runnable() { public void run() { if (module.isDisposed()) { diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/debugger/GroovyPositionManager.java b/plugins/groovy/src/org/jetbrains/plugins/groovy/debugger/GroovyPositionManager.java index 3131a744f477..39ad3c5aac26 100644 --- a/plugins/groovy/src/org/jetbrains/plugins/groovy/debugger/GroovyPositionManager.java +++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/debugger/GroovyPositionManager.java @@ -148,7 +148,7 @@ public class GroovyPositionManager implements PositionManager { public String compute() { GrTypeDefinition typeDefinition = findEnclosingTypeDefinition(position); if (typeDefinition != null) { - return typeDefinition.getQualifiedName(); + return getClassNameForJvm(typeDefinition); } return getScriptQualifiedName(position); } @@ -171,6 +171,7 @@ public class GroovyPositionManager implements PositionManager { }); } + @Nullable private static String getClassNameForJvm(final PsiClass typeDefinition) { final PsiClass psiClass = typeDefinition.getContainingClass(); if (psiClass != null) { @@ -311,21 +312,15 @@ public class GroovyPositionManager implements PositionManager { List result = ApplicationManager.getApplication().runReadAction(new Computable>() { public List compute() { GroovyPsiElement sourceImage = findReferenceTypeSourceImage(position); - final String scriptName = getScriptQualifiedName(position); if (sourceImage instanceof GrTypeDefinition && !((GrTypeDefinition)sourceImage).isAnonymous()) { String qName = getClassNameForJvm((GrTypeDefinition)sourceImage); if (qName != null) return myDebugProcess.getVirtualMachineProxy().classesByName(qName); } else if (sourceImage == null) { + final String scriptName = getScriptQualifiedName(position); if (scriptName != null) return myDebugProcess.getVirtualMachineProxy().classesByName(scriptName); } else { - final GrTypeDefinition typeDefinition = findEnclosingTypeDefinition(position); - String enclosingName; - if (typeDefinition != null) { - enclosingName = typeDefinition.getQualifiedName(); - } else { - enclosingName = scriptName; - } + String enclosingName = findEnclosingName(position); if (enclosingName == null) return Collections.emptyList(); final List outers = myDebugProcess.getVirtualMachineProxy().classesByName(enclosingName); diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/gpp/GppExpectedTypesContributor.java b/plugins/groovy/src/org/jetbrains/plugins/groovy/gpp/GppExpectedTypesContributor.java index 5937224292b1..2a59c0f12b6f 100644 --- a/plugins/groovy/src/org/jetbrains/plugins/groovy/gpp/GppExpectedTypesContributor.java +++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/gpp/GppExpectedTypesContributor.java @@ -47,9 +47,9 @@ public class GppExpectedTypesContributor extends GroovyExpectedTypesContributor assert argIndex >= 0; final ArrayList result = new ArrayList(); - for (PsiType type : GroovyExpectedTypesProvider.getDefaultExpectedTypes(expression)) { + for (PsiType type : GroovyExpectedTypesProvider.getDefaultExpectedTypes(list)) { if (type instanceof PsiClassType) { - for (GroovyResolveResult resolveResult : PsiUtil.getConstructorCandidates((PsiClassType)type, argTypes, expression)) { + for (GroovyResolveResult resolveResult : PsiUtil.getConstructorCandidates((PsiClassType)type, argTypes, list)) { final PsiElement method = resolveResult.getElement(); if (method instanceof PsiMethod && ((PsiMethod)method).isConstructor()) { final PsiParameter[] constructorParameters = ((PsiMethod)method).getParameterList().getParameters(); diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/api/statements/params/GrParameter.java b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/api/statements/params/GrParameter.java index f695d8174b18..2d0d042f0189 100644 --- a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/api/statements/params/GrParameter.java +++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/api/statements/params/GrParameter.java @@ -16,6 +16,8 @@ package org.jetbrains.plugins.groovy.lang.psi.api.statements.params; +import com.intellij.psi.PsiModifierList; +import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.plugins.groovy.lang.psi.api.types.GrTypeElement; import org.jetbrains.plugins.groovy.lang.psi.api.statements.GrVariable; @@ -35,5 +37,8 @@ public interface GrParameter extends PsiParameter, GrVariable { @Nullable GrExpression getDefaultInitializer(); + @NotNull + PsiModifierList getModifierList(); + boolean isOptional(); } diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/impl/GroovyPsiElementFactoryImpl.java b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/impl/GroovyPsiElementFactoryImpl.java index 5e1a9da3c021..bf538c0048d1 100644 --- a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/impl/GroovyPsiElementFactoryImpl.java +++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/impl/GroovyPsiElementFactoryImpl.java @@ -528,12 +528,14 @@ public class GroovyPsiElementFactoryImpl extends GroovyPsiElementFactory { builder.append("("); for (int i = 0; i < paramNames.length; i++) { - String paramType = paramTypes == null ? "" : paramTypes[i]; + String paramType = paramTypes == null ? null : paramTypes[i]; if (i > 0) builder.append(", "); - builder.append(paramType); - builder.append(" "); + if (paramType != null) { + builder.append(paramType); + builder.append(" "); + } builder.append(paramNames[i]); } diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/impl/statements/expressions/path/GrIndexPropertyImpl.java b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/impl/statements/expressions/path/GrIndexPropertyImpl.java index 4e9f16abf393..9d9a610c2eb7 100644 --- a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/impl/statements/expressions/path/GrIndexPropertyImpl.java +++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/impl/statements/expressions/path/GrIndexPropertyImpl.java @@ -75,7 +75,12 @@ public class GrIndexPropertyImpl extends GrExpressionImpl implements GrIndexProp argTypes[i] = argType; } - if (thisType instanceof GrTupleType && + if (thisType instanceof PsiArrayType && + argTypes.length == 1 && + TypesUtil.isAssignable(PsiType.INT, argTypes[0], getManager(), getResolveScope())) { + return TypesUtil.boxPrimitiveType(((PsiArrayType)thisType).getComponentType(), getManager(), getResolveScope()); + } + else if (thisType instanceof GrTupleType && argTypes.length == 1 && TypesUtil.isAssignable(PsiType.INT, argTypes[0], getManager(), getResolveScope())) { PsiType[] types = ((GrTupleType)thisType).getParameters(); @@ -96,12 +101,7 @@ public class GrIndexPropertyImpl extends GrExpressionImpl implements GrIndexProp } PsiType componentType = null; - if (thisType instanceof PsiArrayType && - argTypes.length == 1 && - TypesUtil.isAssignable(PsiType.INT, argTypes[0], getManager(), getResolveScope())) { - componentType = TypesUtil.boxPrimitiveType(((PsiArrayType)thisType).getComponentType(), getManager(), getResolveScope()); - } - else if (InheritanceUtil.isInheritor(thisType, CommonClassNames.JAVA_UTIL_MAP) && argTypes.length == 1) { + if (InheritanceUtil.isInheritor(thisType, CommonClassNames.JAVA_UTIL_MAP) && argTypes.length == 1) { componentType = TypesUtil .boxPrimitiveType(substituteTypeParameter(thisType, CommonClassNames.JAVA_UTIL_MAP, 1, true), getManager(), getResolveScope()); } 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 d3965f74fa6e..fbcad0d19794 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 @@ -185,9 +185,9 @@ public class GrParameterImpl extends GrVariableImpl implements GrParameter { return getNameIdentifierGroovy().getTextRange().getStartOffset(); } - @Nullable + @NotNull public GrModifierList getModifierList() { - return findChildByClass(GrModifierList.class); + return findNotNullChildByClass(GrModifierList.class); } @NotNull diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/overrideImplement/GroovyOverrideImplementUtil.java b/plugins/groovy/src/org/jetbrains/plugins/groovy/overrideImplement/GroovyOverrideImplementUtil.java index 025d5388da36..3a66ee2098a1 100644 --- a/plugins/groovy/src/org/jetbrains/plugins/groovy/overrideImplement/GroovyOverrideImplementUtil.java +++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/overrideImplement/GroovyOverrideImplementUtil.java @@ -27,6 +27,7 @@ import com.intellij.openapi.editor.Editor; import com.intellij.openapi.editor.ScrollType; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Condition; +import com.intellij.openapi.util.text.StringUtil; import com.intellij.psi.*; import com.intellij.psi.infos.CandidateInfo; import com.intellij.psi.util.PsiTreeUtil; @@ -314,7 +315,7 @@ public class GroovyOverrideImplementUtil { JavaTemplateUtil.setClassAndMethodNameProperties(properties, method.getContainingClass(), resultMethod); try { - String bodyText = template.getText(properties); + String bodyText = StringUtil.replace(template.getText(properties), ";", ""); final GrCodeBlock newBody = GroovyPsiElementFactory.getInstance(project).createMethodBodyFromText("\n" + bodyText + "\n"); resultMethod.setBlock(newBody); diff --git a/plugins/groovy/test/org/jetbrains/plugins/groovy/compiler/GeneratorTest.java b/plugins/groovy/test/org/jetbrains/plugins/groovy/compiler/GeneratorTest.java index c89ce67f8bce..5600d29f05ee 100644 --- a/plugins/groovy/test/org/jetbrains/plugins/groovy/compiler/GeneratorTest.java +++ b/plugins/groovy/test/org/jetbrains/plugins/groovy/compiler/GeneratorTest.java @@ -16,6 +16,7 @@ import org.jetbrains.plugins.groovy.util.TestUtils; import java.io.BufferedReader; import java.io.File; import java.io.FileReader; +import java.util.Collections; import java.util.List; /** @@ -131,7 +132,7 @@ public void testArrayType1() throws Throwable { doTest(); } private final String myFileContent; public GroovyToJavaGeneratorTester(String relTestPath, String fileContent, Project project) { - super(project, null); + super(project, null, Collections.emptyList()); myRelTestPath = relTestPath; myFileContent = fileContent; } diff --git a/plugins/groovy/test/org/jetbrains/plugins/groovy/compiler/GroovyCompilerTest.groovy b/plugins/groovy/test/org/jetbrains/plugins/groovy/compiler/GroovyCompilerTest.groovy index f1a86ac96a56..0ac2c58c78f1 100644 --- a/plugins/groovy/test/org/jetbrains/plugins/groovy/compiler/GroovyCompilerTest.groovy +++ b/plugins/groovy/test/org/jetbrains/plugins/groovy/compiler/GroovyCompilerTest.groovy @@ -348,5 +348,46 @@ class Foo extends Bar { assertEmpty make() } + public void testDollarGroovyInnerClassUsagesInStubs() throws Exception { + def javaFile = myFixture.addClass(""" + public class JavaClass { + public static class InnerJavaClass {} + } +""") + myFixture.addFileToProject("WithInner.groovy", """ +class WithInner { + static class Inner {} +} +""") + assertEmpty make() + + myFixture.addFileToProject("Usage.groovy", """ +class Usage { + def foo(WithInner.Inner i) {} + def foo(JavaClass.InnerJavaClass i) {} +} +""") + + touch(javaFile.containingFile.virtualFile) + assertEmpty make() + } + + public void testDollarGroovyInnerClassUsagesInStubs2() throws Exception { + myFixture.addClass(""" public class JavaClass { } """) + myFixture.addFileToProject("WithInner.groovy", """ +class WithInner { + static class Inner {} +} +""") + + myFixture.addFileToProject("Usage.groovy", """ +class Usage { + def foo(WithInner.Inner i) {} +} +""") + + assertEmpty make() + } + } diff --git a/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/GppFunctionalTest.groovy b/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/GppFunctionalTest.groovy index 0b6592de7fb8..9dc6436c8373 100644 --- a/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/GppFunctionalTest.groovy +++ b/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/GppFunctionalTest.groovy @@ -424,6 +424,23 @@ s.doSmth() @Typed File foo() { ['super':['a']] } @Typed File goo() { [:] } File bar() { [:] } +""" + myFixture.enableInspections new GroovyAssignabilityCheckInspection() + myFixture.checkHighlighting(true, false, false) + } + + public void testNestedLiteralConstructors() throws Exception { + configureGppScript """ +class Foo { + def Foo(Bar b) { } +} + +class Bar { + def Bar(int i) { } +} + +Foo x = [[2]] +println x """ myFixture.enableInspections new GroovyAssignabilityCheckInspection() myFixture.checkHighlighting(true, false, false) 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 5334185f8ba6..ef725d71b557 100644 --- a/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/GroovyHighlightingTest.java +++ b/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/GroovyHighlightingTest.java @@ -274,13 +274,10 @@ public class GroovyHighlightingTest extends LightCodeInsightFixtureTestCase { doTest(); } - public void testCollectionAssignments() throws Exception { - doTest(new GroovyAssignabilityCheckInspection()); - } + public void testCollectionAssignments() {doTest(new GroovyAssignabilityCheckInspection()); } + public void testReturnAssignability() {doTest(new GroovyAssignabilityCheckInspection()); } public void testNumberDuplicatesInMaps() throws Exception {doTest();} - public void testMapNotAcceptedAsStringParameter() { - doTest(); - } + public void testMapNotAcceptedAsStringParameter() {doTest();} } \ No newline at end of file diff --git a/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/actions/generate/GroovyGenerateMembersTest.groovy b/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/actions/generate/GroovyGenerateMembersTest.groovy new file mode 100644 index 000000000000..a0d9cac99121 --- /dev/null +++ b/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/actions/generate/GroovyGenerateMembersTest.groovy @@ -0,0 +1,97 @@ +/* + * Copyright (c) 2000-2005 by JetBrains s.r.o. All Rights Reserved. + * Use is subject to license terms. + */ +package org.jetbrains.plugins.groovy.lang.actions.generate; + + +import com.intellij.codeInsight.generation.ClassMember +import com.intellij.codeInsight.generation.PsiFieldMember +import com.intellij.openapi.application.Result +import com.intellij.openapi.application.RunResult +import com.intellij.openapi.command.WriteCommandAction +import com.intellij.openapi.project.Project +import com.intellij.psi.PsiClass +import com.intellij.psi.PsiFile +import com.intellij.testFramework.fixtures.LightCodeInsightFixtureTestCase +import org.jetbrains.plugins.groovy.actions.generate.constructors.ConstructorGenerateHandler +import org.jetbrains.plugins.groovy.util.TestUtils +import com.intellij.codeInsight.generation.PsiMethodMember + +/** + * @author peter + */ +public class GroovyGenerateMembersTest extends LightCodeInsightFixtureTestCase { + + public void testConstructorAtOffset() throws Throwable { + doTest(); + } + + public void testConstructorAtEnd() throws Throwable { + doTest(); + } + + public void testLonelyConstructor() throws Throwable { + doTest(); + } + + public void testExplicitArgumentTypes() throws Exception { + myFixture.configureByText("a.groovy", """ +class Super { + def Super(a, int b) {} +} + +class Foo extends Super { + int c + Object d + final e + +} +""") + generateConstructor() + myFixture.checkResult """ +class Super { + def Super(a, int b) {} +} + +class Foo extends Super { + int c + Object d + final e + + def Foo(a, int b, int c, Object d, e) { + super(a, b) + this.c = c + this.d = d + this.e = e + } +} +""" + } + + private void doTest() throws Throwable { + myFixture.configureByFile(getTestName(false) + ".groovy"); + generateConstructor(); + myFixture.checkResultByFile(getTestName(false) + "_after.groovy"); + } + + RunResult generateConstructor() { + return new WriteCommandAction(getProject(), new PsiFile[0]) { + protected void run(Result result) throws Throwable { + new ConstructorGenerateHandler() { + @Override protected ClassMember[] chooseOriginalMembersImpl(PsiClass aClass, Project project) { + List members = aClass.fields.collect { new PsiFieldMember(it) } + members << new PsiMethodMember(aClass.superClass.constructors[0]) + return members as ClassMember[] + } + + }.invoke(getProject(), myFixture.getEditor(), myFixture.getFile()); + } + }.execute() + } + + @Override + protected String getBasePath() { + return TestUtils.getTestDataPath() + "generate"; + } +} diff --git a/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/actions/generate/GroovyGenerateMembersTest.java b/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/actions/generate/GroovyGenerateMembersTest.java deleted file mode 100644 index c7b637905ad3..000000000000 --- a/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/actions/generate/GroovyGenerateMembersTest.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright (c) 2000-2005 by JetBrains s.r.o. All Rights Reserved. - * Use is subject to license terms. - */ -package org.jetbrains.plugins.groovy.lang.actions.generate; - -import com.intellij.openapi.application.Result; -import com.intellij.openapi.command.WriteCommandAction; -import com.intellij.testFramework.fixtures.LightCodeInsightFixtureTestCase; -import org.jetbrains.plugins.groovy.actions.generate.constructors.ConstructorGenerateHandler; -import org.jetbrains.plugins.groovy.util.TestUtils; - -/** - * @author peter - */ -public class GroovyGenerateMembersTest extends LightCodeInsightFixtureTestCase { - - public void testConstructorAtOffset() throws Throwable { - doTest(); - } - - public void testConstructorAtEnd() throws Throwable { - doTest(); - } - - public void testLonelyConstructor() throws Throwable { - doTest(); - } - - private void doTest() throws Throwable { - myFixture.configureByFile(getTestName(false) + ".groovy"); - new WriteCommandAction(getProject()) { - protected void run(Result result) throws Throwable { - new ConstructorGenerateHandler().invoke(getProject(), myFixture.getEditor(), myFixture.getFile()); - } - }.execute(); - myFixture.checkResultByFile(getTestName(false) + "_after.groovy"); - } - - @Override - protected String getBasePath() { - return TestUtils.getTestDataPath() + "generate"; - } -} diff --git a/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/overriding/GroovyOverrideImplementTest.groovy b/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/overriding/GroovyOverrideImplementTest.groovy index 4c2b80c599af..10887d400c7a 100644 --- a/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/overriding/GroovyOverrideImplementTest.groovy +++ b/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/overriding/GroovyOverrideImplementTest.groovy @@ -24,7 +24,7 @@ class Test {} class Test { @Override boolean equals(Object obj) { - return super.equals(obj); //To change body of overridden methods use File | Settings | File Templates. + return super.equals(obj) //To change body of overridden methods use File | Settings | File Templates. } } """ @@ -38,7 +38,7 @@ class Test {} myFixture.checkResult """ class Test { def Test() { - super(); //To change body of overridden methods use File | Settings | File Templates. + super() //To change body of overridden methods use File | Settings | File Templates. } } """ @@ -59,7 +59,7 @@ class Test {} myFixture.checkResult """ class Test { @Override def foo() { - return super.foo(); //To change body of overridden methods use File | Settings | File Templates. + return super.foo() //To change body of overridden methods use File | Settings | File Templates. } } """ diff --git a/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/resolve/TypeInferenceTest.java b/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/resolve/TypeInferenceTest.java index bfe4adfa098a..42afcc4f608c 100644 --- a/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/resolve/TypeInferenceTest.java +++ b/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/resolve/TypeInferenceTest.java @@ -15,11 +15,14 @@ */ package org.jetbrains.plugins.groovy.lang.resolve; +import com.intellij.psi.CommonClassNames; import com.intellij.psi.PsiIntersectionType; +import com.intellij.psi.PsiReference; import com.intellij.psi.PsiType; import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrReferenceExpression; import org.jetbrains.plugins.groovy.lang.psi.impl.GrClosureType; import org.jetbrains.plugins.groovy.util.TestUtils; +import org.jmock.lib.CamelCaseNamingScheme; /** * @author ven @@ -122,4 +125,9 @@ public class TypeInferenceTest extends GroovyResolveTestCase { final GrReferenceExpression ref = (GrReferenceExpression)configureByFile("arrayLikeAccessWithIntSequence/A.groovy").getElement(); assertEquals("java.util.List", ref.getType().getCanonicalText()); } + + public void testArrayAccess() { + final GrReferenceExpression ref = (GrReferenceExpression)configureByFile("arrayAccess/A.groovy"); + assertEquals(CommonClassNames.JAVA_LANG_STRING, ref.getType().getCanonicalText()); + } } diff --git a/plugins/groovy/testdata/highlighting/IfIncrementElseReturn.groovy b/plugins/groovy/testdata/highlighting/IfIncrementElseReturn.groovy index 7067d0b17666..04e1d86bc2e5 100644 --- a/plugins/groovy/testdata/highlighting/IfIncrementElseReturn.groovy +++ b/plugins/groovy/testdata/highlighting/IfIncrementElseReturn.groovy @@ -10,4 +10,4 @@ int numPermutationsPrinted = 1; } -def x = 2 \ No newline at end of file +def x = 2 \ No newline at end of file diff --git a/plugins/groovy/testdata/highlighting/PrefixIncrementCfa.groovy b/plugins/groovy/testdata/highlighting/PrefixIncrementCfa.groovy index 2fd6f971ca1f..711bb7b8c062 100644 --- a/plugins/groovy/testdata/highlighting/PrefixIncrementCfa.groovy +++ b/plugins/groovy/testdata/highlighting/PrefixIncrementCfa.groovy @@ -1,5 +1,5 @@ int idx -idx = 2 +idx = 2 idx = 3 if (++idx == 8) { //Assignment is used here idx = 33 diff --git a/plugins/groovy/testdata/highlighting/ReturnAssignability.groovy b/plugins/groovy/testdata/highlighting/ReturnAssignability.groovy new file mode 100644 index 000000000000..78d70d3417d6 --- /dev/null +++ b/plugins/groovy/testdata/highlighting/ReturnAssignability.groovy @@ -0,0 +1,10 @@ +File foo() { + List ints = [] + if (ints.empty) { + print {return 42} + for (x in ints) { + return 43 + } + } + 67 +} \ No newline at end of file diff --git a/plugins/groovy/testdata/highlighting/UnusedVariable.groovy b/plugins/groovy/testdata/highlighting/UnusedVariable.groovy index bf0f533ac9c8..0ea69f992bde 100644 --- a/plugins/groovy/testdata/highlighting/UnusedVariable.groovy +++ b/plugins/groovy/testdata/highlighting/UnusedVariable.groovy @@ -1 +1 @@ -Class clazz = null \ No newline at end of file +Class clazz = null \ No newline at end of file diff --git a/plugins/groovy/testdata/resolve/inference/arrayAccess/A.groovy b/plugins/groovy/testdata/resolve/inference/arrayAccess/A.groovy new file mode 100644 index 000000000000..4ae80232e1c1 --- /dev/null +++ b/plugins/groovy/testdata/resolve/inference/arrayAccess/A.groovy @@ -0,0 +1,7 @@ +def foo(String[] a) { + def var = a[0] + + print var +} + +def bar(String a) {} \ No newline at end of file diff --git a/plugins/hg4idea/src/org/zmlx/hg4idea/HgExecutableValidator.java b/plugins/hg4idea/src/org/zmlx/hg4idea/HgExecutableValidator.java index a2d81894faf1..a417beafdd96 100644 --- a/plugins/hg4idea/src/org/zmlx/hg4idea/HgExecutableValidator.java +++ b/plugins/hg4idea/src/org/zmlx/hg4idea/HgExecutableValidator.java @@ -60,9 +60,9 @@ public class HgExecutableValidator { } }); } catch (InvocationTargetException e) { - LOG.error(e); + LOG.info(e); } catch (InterruptedException e) { - LOG.error(e); + LOG.info(e); } return myValidHgExecutable; } diff --git a/plugins/hg4idea/src/org/zmlx/hg4idea/HgGlobalSettings.java b/plugins/hg4idea/src/org/zmlx/hg4idea/HgGlobalSettings.java index 63b7c999014b..10fdb708e214 100644 --- a/plugins/hg4idea/src/org/zmlx/hg4idea/HgGlobalSettings.java +++ b/plugins/hg4idea/src/org/zmlx/hg4idea/HgGlobalSettings.java @@ -16,7 +16,9 @@ import com.intellij.openapi.components.PersistentStateComponent; import com.intellij.openapi.components.State; import com.intellij.openapi.components.Storage; import com.intellij.util.containers.HashMap; +import org.apache.commons.lang.StringUtils; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import java.util.LinkedList; import java.util.List; @@ -47,10 +49,16 @@ public class HgGlobalSettings implements PersistentStateComponent list = myRememberedUrls.get(stringUrl); if (list == null) { list = new LinkedList(); diff --git a/plugins/hg4idea/src/org/zmlx/hg4idea/HgProjectSettings.java b/plugins/hg4idea/src/org/zmlx/hg4idea/HgProjectSettings.java index 448a1161864e..e4f8818024fa 100644 --- a/plugins/hg4idea/src/org/zmlx/hg4idea/HgProjectSettings.java +++ b/plugins/hg4idea/src/org/zmlx/hg4idea/HgProjectSettings.java @@ -31,7 +31,11 @@ public class HgProjectSettings implements PersistentStateComponent hgOptions, - String operation, List arguments, Charset charset, boolean suppressCommandOutput) { - + @Nullable + HgCommandResult execute(VirtualFile repo, List hgOptions, String operation, List arguments, Charset charset, boolean suppressCommandOutput) { + if (myProject.isDisposed()) { + return null; + } if (!validator.check(mySettings)) { return null; } @@ -117,7 +119,7 @@ public final class HgCommandService { cmdLine.add("extensions.mq="); } catch (IOException e) { showError(e); - LOG.error("IOException during preparing command", e); + LOG.info("IOException during preparing command", e); return null; } cmdLine.addAll(hgOptions); @@ -128,12 +130,14 @@ public final class HgCommandService { ShellCommand shellCommand = new ShellCommand(); HgCommandResult result; try { - LOG.debug(cmdLine.toString()); String workingDir = repo != null ? repo.getPath() : null; result = shellCommand.execute(cmdLine, workingDir, charset); } catch (ShellCommandException e) { showError(e); - LOG.error(e.getMessage(), e); + LOG.info(e.getMessage(), e); + return null; + } catch (InterruptedException e) { // this may happen during project closing, no need to notify the user. + LOG.info(e.getMessage(), e); return null; } finally { promptServer.stop(); diff --git a/plugins/hg4idea/src/org/zmlx/hg4idea/command/HgCommitCommand.java b/plugins/hg4idea/src/org/zmlx/hg4idea/command/HgCommitCommand.java index eb4aba2d55f4..0cf99091f8ad 100644 --- a/plugins/hg4idea/src/org/zmlx/hg4idea/command/HgCommitCommand.java +++ b/plugins/hg4idea/src/org/zmlx/hg4idea/command/HgCommitCommand.java @@ -67,7 +67,7 @@ public class HgCommitCommand { ensureSuccess(HgCommandService.getInstance(project).execute(repo, "commit", parameters)); project.getMessageBus().syncPublisher(HgVcs.OUTGOING_TOPIC).update(project); } catch (IOException e) { - LOG.error(e); + LOG.info(e); } } diff --git a/plugins/hg4idea/src/org/zmlx/hg4idea/command/HgErrorUtil.java b/plugins/hg4idea/src/org/zmlx/hg4idea/command/HgErrorUtil.java index 654d13f537c3..dca18dda0910 100644 --- a/plugins/hg4idea/src/org/zmlx/hg4idea/command/HgErrorUtil.java +++ b/plugins/hg4idea/src/org/zmlx/hg4idea/command/HgErrorUtil.java @@ -16,11 +16,14 @@ import org.apache.commons.lang.StringUtils; import java.util.List; -final class HgErrorUtil { +public final class HgErrorUtil { private HgErrorUtil() { } - static boolean isAbort(HgCommandResult result) { + public static boolean isAbort(HgCommandResult result) { + if (result == null) { + return true; + } String line = getLastErrorLine(result); return StringUtils.isNotBlank(line) && StringUtils.contains(line, "abort:"); } diff --git a/plugins/hg4idea/src/org/zmlx/hg4idea/command/HgWorkingCopyRevisionsCommand.java b/plugins/hg4idea/src/org/zmlx/hg4idea/command/HgWorkingCopyRevisionsCommand.java index 76b58df3ca1e..a1b2d6603709 100644 --- a/plugins/hg4idea/src/org/zmlx/hg4idea/command/HgWorkingCopyRevisionsCommand.java +++ b/plugins/hg4idea/src/org/zmlx/hg4idea/command/HgWorkingCopyRevisionsCommand.java @@ -47,6 +47,7 @@ public class HgWorkingCopyRevisionsCommand { } } + @Nullable public HgRevisionNumber tip(@NotNull VirtualFile repo) { List tips = getRevisions(repo, "tip"); if (tips.size() > 1) { @@ -58,13 +59,17 @@ public class HgWorkingCopyRevisionsCommand { else return null; } + @Nullable public HgRevisionNumber identify(@NotNull VirtualFile repo) { HgCommandService commandService = HgCommandService.getInstance(project); HgCommandResult result = commandService.execute( repo, "identify", Arrays.asList("--num", "--id") ); - List lines = result.getOutputLines(); - if (!lines.isEmpty()) { + if (result == null) { + return null; + } + final List lines = result.getOutputLines(); + if (lines != null && !lines.isEmpty()) { String[] parts = StringUtils.split(lines.get(0), ' '); if (parts.length >= 2) { return HgRevisionNumber.getInstance(parts[1], parts[0]); @@ -73,11 +78,16 @@ public class HgWorkingCopyRevisionsCommand { return null; } + @NotNull private List getRevisions(VirtualFile repo, String command) { HgCommandService commandService = HgCommandService.getInstance(project); HgCommandResult result = commandService.execute( repo, command, Arrays.asList("--template", "{rev}|{node|short}\\n") ); + + if (result == null) { + return new ArrayList(0); + } List lines = result.getOutputLines(); List revisions = new ArrayList(lines.size()); diff --git a/plugins/hg4idea/src/org/zmlx/hg4idea/command/ShellCommand.java b/plugins/hg4idea/src/org/zmlx/hg4idea/command/ShellCommand.java index 1cd92f7be6ce..28a15d871b3c 100644 --- a/plugins/hg4idea/src/org/zmlx/hg4idea/command/ShellCommand.java +++ b/plugins/hg4idea/src/org/zmlx/hg4idea/command/ShellCommand.java @@ -25,8 +25,7 @@ final class ShellCommand { private static final int BUFFER_SIZE = 1024; - public HgCommandResult execute(List commandLine, String dir, Charset charset) - throws ShellCommandException { + public HgCommandResult execute(List commandLine, String dir, Charset charset) throws ShellCommandException, InterruptedException { if (commandLine == null || commandLine.isEmpty()) { throw new IllegalArgumentException("commandLine is empty"); } @@ -51,8 +50,6 @@ final class ShellCommand { return new HgCommandResult(out, err, exitValue ); } catch (IOException e) { throw new ShellCommandException(e); - } catch (InterruptedException e) { - throw new ShellCommandException(e); } } diff --git a/plugins/hg4idea/src/org/zmlx/hg4idea/provider/annotate/HgAnnotation.java b/plugins/hg4idea/src/org/zmlx/hg4idea/provider/annotate/HgAnnotation.java index cb500142ffa1..2ebc46f62f52 100644 --- a/plugins/hg4idea/src/org/zmlx/hg4idea/provider/annotate/HgAnnotation.java +++ b/plugins/hg4idea/src/org/zmlx/hg4idea/provider/annotate/HgAnnotation.java @@ -86,7 +86,7 @@ public class HgAnnotation implements FileAnnotation { try { return CurrentContentRevision.create(hgFile.toFilePath()).getContent(); } catch (VcsException e) { - LOG.error(e); + LOG.info(e); return StringUtils.EMPTY; } } diff --git a/plugins/hg4idea/src/org/zmlx/hg4idea/provider/update/HgHeadMerger.java b/plugins/hg4idea/src/org/zmlx/hg4idea/provider/update/HgHeadMerger.java index 835b579bb05b..039fce499955 100644 --- a/plugins/hg4idea/src/org/zmlx/hg4idea/provider/update/HgHeadMerger.java +++ b/plugins/hg4idea/src/org/zmlx/hg4idea/provider/update/HgHeadMerger.java @@ -89,7 +89,7 @@ final class HgHeadMerger { private static void throwException(Exception e) throws VcsException { String msg = "Exception during marking directory dirty: " + e; - LOG.error(msg, e); + LOG.info(msg, e); throw new VcsException(msg); } diff --git a/plugins/hg4idea/testSrc/org/zmlx/hg4idea/test/HgAbstractTestCase.java b/plugins/hg4idea/testSrc/org/zmlx/hg4idea/test/HgAbstractTestCase.java index bf6c44932ee2..d29c536e8b59 100644 --- a/plugins/hg4idea/testSrc/org/zmlx/hg4idea/test/HgAbstractTestCase.java +++ b/plugins/hg4idea/testSrc/org/zmlx/hg4idea/test/HgAbstractTestCase.java @@ -23,6 +23,7 @@ import com.intellij.testFramework.AbstractVcsTestCase; import com.intellij.testFramework.fixtures.IdeaTestFixtureFactory; import com.intellij.testFramework.fixtures.TempDirTestFixture; import com.intellij.vcsUtil.VcsUtil; +import org.jetbrains.annotations.Nullable; import org.testng.annotations.BeforeMethod; import org.zmlx.hg4idea.HgFile; import org.zmlx.hg4idea.HgVcs; @@ -38,10 +39,6 @@ public abstract class HgAbstractTestCase extends AbstractVcsTestCase { public static final String HG_EXECUTABLE_PATH = "IDEA_TEST_HG_EXECUTABLE_PATH"; - protected File myProjectRepo; - protected TempDirTestFixture myTempDirTestFixture; - protected TestChangeListManager myChangeListManager; - // some shortcuts to use in tests protected static final String AFILE = "a.txt"; protected static final String BDIR = "b"; @@ -50,83 +47,92 @@ public abstract class HgAbstractTestCase extends AbstractVcsTestCase { protected static final String FILE_CONTENT = "Sample file content."; protected static final String FILE_CONTENT_2 = "some other file content"; + protected File myProjectDir; // location of the project repository. Initialized differently in each test: by init or by clone. + protected HgTestChangeListManager myChangeListManager; + @BeforeMethod protected void setUp() throws Exception { - setHGExecutablePath(); - - myTempDirTestFixture = IdeaTestFixtureFactory.getFixtureFactory().createTempDirTestFixture(); - myTempDirTestFixture.setUp(); - myProjectRepo = new File(myTempDirTestFixture.getTempDirPath()); - - ProcessOutput processOutput = runHg(myProjectRepo, "init"); - verify(processOutput); - initProject(myProjectRepo); - activateVCS(HgVcs.VCS_NAME); - - myChangeListManager = new TestChangeListManager(myProject); - - enableSilentOperation(VcsConfiguration.StandardConfirmation.ADD); - enableSilentOperation(VcsConfiguration.StandardConfirmation.REMOVE); - } - - protected void setHGExecutablePath() { // setting hg executable String exec = System.getenv(HG_EXECUTABLE_PATH); - System.out.println("exec: " + exec); if (exec != null) { - System.out.println("Using external"); myClientBinaryPath = new File(exec); } if (exec == null || !myClientBinaryPath.exists()) { - System.out.println("Using checked in"); - File pluginRoot = new File(PluginPathManager.getPluginHomePath(HgVcs.VCS_NAME)); + final File pluginRoot = new File(PluginPathManager.getPluginHomePath(HgVcs.VCS_NAME)); myClientBinaryPath = new File(pluginRoot, "testData/bin"); } - HgVcs.setTestHgExecutablePath(myClientBinaryPath.getPath()); + + myTraceClient = true; + } + + /** + * Creates a new Mercurial repository in a temporary test directory. + * @return created repository + */ + protected HgTestRepository createRepository() throws Exception { + final TempDirTestFixture dirFixture = createFixtureDir(); + final File repo = new File(dirFixture.getTempDirPath()); + ProcessOutput processOutput = runHg(repo, "init"); + verify(processOutput); + return new HgTestRepository(this, dirFixture); + } + + protected static TempDirTestFixture createFixtureDir() throws Exception { + final TempDirTestFixture fixture = IdeaTestFixtureFactory.getFixtureFactory().createTempDirTestFixture(); + fixture.setUp(); + return fixture; + } + + protected void enableSilentOperation(final VcsConfiguration.StandardConfirmation op) { + setStandardConfirmation(HgVcs.VCS_NAME, op, VcsShowConfirmationOption.Value.DO_ACTION_SILENTLY); + } + + protected void disableSilentOperation(final VcsConfiguration.StandardConfirmation op) { + setStandardConfirmation(HgVcs.VCS_NAME, op, VcsShowConfirmationOption.Value.DO_NOTHING_SILENTLY); } /** * Runs the hg command. + * * @param commandLine the name of the command and its arguments. */ protected ProcessOutput runHgOnProjectRepo(String... commandLine) throws IOException { - return runHg(myProjectRepo, commandLine); + return runHg(myProjectDir, commandLine); } + /** + * Verifies the status of the file calling native 'hg status' command. + * + * @param status status as returned by {@link #added(java.lang.String)} and other methods. + * @throws IOException + */ + protected void verifyStatus(String... status) throws IOException { + verify(runHg(myProjectDir, "status"), status); +} /** * Calls "hg add ." to add everything to the index. */ protected ProcessOutput addAll() throws IOException { - return runHgOnProjectRepo("add", "."); + return runHg(myProjectDir, "add", "."); } /** * Calls "hg commit -m <commitMessage>" to commit the index. */ protected ProcessOutput commitAll(String commitMessage) throws IOException { - return runHgOnProjectRepo("commit", "-m", commitMessage); + return runHg(myProjectDir, "commit", "-m", commitMessage); } protected HgFile getHgFile(String... filepath) { - File fileToInclude = myProjectRepo; + File fileToInclude = myProjectDir; for (String path : filepath) { fileToInclude = new File(fileToInclude, path); } return new HgFile(myWorkingCopyDir, fileToInclude); } + - protected void enableSilentOperation(final VcsConfiguration.StandardConfirmation op) { - setStandardConfirmation( - HgVcs.VCS_NAME, op, VcsShowConfirmationOption.Value.DO_ACTION_SILENTLY - ); - } - - protected void disableSilentOperation(final VcsConfiguration.StandardConfirmation op) { - setStandardConfirmation( - HgVcs.VCS_NAME, op, VcsShowConfirmationOption.Value.DO_NOTHING_SILENTLY - ); - } protected VirtualFile makeFile(File file) throws IOException { file.createNewFile(); @@ -135,8 +141,13 @@ public abstract class HgAbstractTestCase extends AbstractVcsTestCase { return VcsUtil.getVirtualFile(file); } - protected ProcessOutput runHg(File aHgRepository, String... commandLine) throws IOException { - return runClient(HgVcs.HG_EXECUTABLE_FILE_NAME, null, aHgRepository, commandLine); + /** + * Executes the given native Mercurial command with parameters in the given working directory. + * @param workingDir working directory where the command will be executed. May be null. + * @param commandLine command and parameters (e.g. 'status, -m'). + */ + protected ProcessOutput runHg(@Nullable File workingDir, String... commandLine) throws IOException { + return runClient(HgVcs.HG_EXECUTABLE_FILE_NAME, null, workingDir, commandLine); } protected File fillFile(File aParentDir, String[] filePath, String fileContents) throws FileNotFoundException { @@ -157,49 +168,4 @@ public abstract class HgAbstractTestCase extends AbstractVcsTestCase { return outputFile; } - /** - * Verifies the status of the file calling native 'hg status' command. - * @param status status as returned by {@link #added(java.lang.String)} and other methods. - * @throws IOException - */ - protected void verifyStatus(String... status) throws IOException { - verify(runHgOnProjectRepo("status"), status); - } - - public static String added(String... path) { - return "A " + path(path); - } - - public static String removed(String... path) { - return "R " + path(path); - } - - public static String unknown(String... path) { - return "? " + path(path); - } - - public static String modified(String... path) { - return "M " + path(path); - } - - public static String missing(String... path) { - return "! " + path(path); - } - - public static String path(String... line) { - StringBuilder builder = new StringBuilder(); - - int linePartCount = line.length; - - for (int i = 0; i < linePartCount; i++) { - String linePart = line[i]; - builder.append(linePart); - - if (i < linePartCount - 1) { - builder.append(File.separator); - } - } - - return builder.toString(); - } } diff --git a/plugins/hg4idea/testSrc/org/zmlx/hg4idea/test/HgAddTestCase.java b/plugins/hg4idea/testSrc/org/zmlx/hg4idea/test/HgAddTestCase.java index 9f945b127033..4d0918f582af 100644 --- a/plugins/hg4idea/testSrc/org/zmlx/hg4idea/test/HgAddTestCase.java +++ b/plugins/hg4idea/testSrc/org/zmlx/hg4idea/test/HgAddTestCase.java @@ -15,10 +15,12 @@ package org.zmlx.hg4idea.test; import com.intellij.openapi.vfs.VirtualFile; import org.testng.annotations.Test; +import static org.zmlx.hg4idea.test.HgTestOutputParser.added; + /** * Tests adding files to the Mercurial repository. */ -public class HgAddTestCase extends HgAbstractTestCase { +public class HgAddTestCase extends HgSingleUserTestCase { /** * 1. Create a file in the file system. @@ -27,7 +29,7 @@ public class HgAddTestCase extends HgAbstractTestCase { */ @Test public void fileAddedViaChangeListShouldBeAddedToHg() throws Exception { - final VirtualFile vf = myTempDirTestFixture.createFile(AFILE); + final VirtualFile vf = myRepo.getDirFixture().createFile(AFILE); myChangeListManager.addUnversionedFilesToVcs(vf); verifyStatus(added(AFILE)); myChangeListManager.checkFilesAreInList(true, vf); @@ -51,9 +53,9 @@ public class HgAddTestCase extends HgAbstractTestCase { */ @Test public void filesInDirsAddedViaChangeListShouldBeAddedToHg() throws Exception { - final VirtualFile afile = myTempDirTestFixture.createFile(AFILE); - final VirtualFile bdir = myTempDirTestFixture.findOrCreateDir(BDIR); - final VirtualFile bfile = myTempDirTestFixture.createFile(BFILE_PATH); + final VirtualFile afile = myRepo.getDirFixture().createFile(AFILE); + final VirtualFile bdir = myRepo.getDirFixture().findOrCreateDir(BDIR); + final VirtualFile bfile = myRepo.getDirFixture().createFile(BFILE_PATH); myChangeListManager.addUnversionedFilesToVcs(afile, bdir, bfile); verifyStatus(added(AFILE), added(BFILE_PATH)); myChangeListManager.checkFilesAreInList(true, afile, bfile); diff --git a/plugins/hg4idea/testSrc/org/zmlx/hg4idea/test/HgCollaborativeTestCase.java b/plugins/hg4idea/testSrc/org/zmlx/hg4idea/test/HgCollaborativeTestCase.java new file mode 100644 index 000000000000..b5c2925f804d --- /dev/null +++ b/plugins/hg4idea/testSrc/org/zmlx/hg4idea/test/HgCollaborativeTestCase.java @@ -0,0 +1,66 @@ +/* + * 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 org.zmlx.hg4idea.test; + +import com.intellij.execution.process.ProcessOutput; +import com.intellij.openapi.vcs.VcsConfiguration; +import com.intellij.testFramework.fixtures.TempDirTestFixture; +import org.testng.annotations.BeforeMethod; +import org.zmlx.hg4idea.HgVcs; + +import java.io.File; + +/** + * The parent of all tests, where at least two repositories communicate with each other. + * This is used to test collaborative tasks, such as push, pull, merge and others. + * @author Kirill Likhodedov + */ +public class HgCollaborativeTestCase extends HgAbstractTestCase { + + protected HgTestRepository myParentRepo; + protected HgTestRepository myRepo; + + @BeforeMethod + @Override + protected void setUp() throws Exception { + super.setUp(); + + myParentRepo = createRepository(); + myRepo = cloneFrom(myParentRepo); + + myProjectDir = new File(myRepo.getDirFixture().getTempDirPath()); + + initProject(myProjectDir); + activateVCS(HgVcs.VCS_NAME); + myChangeListManager = new HgTestChangeListManager(myProject); + + enableSilentOperation(VcsConfiguration.StandardConfirmation.ADD); + enableSilentOperation(VcsConfiguration.StandardConfirmation.REMOVE); + } + + /** + * Clones a repository from the given one. New repository is located in a temporary test directory. + * @param parent repository to clone from. + * @return New repository cloned from the given parent. + */ + protected HgTestRepository cloneFrom(HgTestRepository parent) throws Exception { + final TempDirTestFixture dirFixture = createFixtureDir(); + final ProcessOutput processOutput = runHg(null, "clone", parent.getDirFixture().getTempDirPath(), dirFixture.getTempDirPath()); + verify(processOutput); + return new HgTestRepository(this, dirFixture); + } + +} diff --git a/plugins/hg4idea/testSrc/org/zmlx/hg4idea/test/HgCopyTestCase.java b/plugins/hg4idea/testSrc/org/zmlx/hg4idea/test/HgCopyTestCase.java index f58ce982a6b2..9dfbab6b8bf3 100644 --- a/plugins/hg4idea/testSrc/org/zmlx/hg4idea/test/HgCopyTestCase.java +++ b/plugins/hg4idea/testSrc/org/zmlx/hg4idea/test/HgCopyTestCase.java @@ -17,14 +17,14 @@ import org.testng.annotations.Test; import java.io.File; -public class HgCopyTestCase extends HgAbstractTestCase { +public class HgCopyTestCase extends HgSingleUserTestCase { @Test public void testCopyUnmodifiedFile() throws Exception { VirtualFile file = createFileInCommand("a.txt", "new file content"); runHgOnProjectRepo("commit", "-m", "added file"); copyFileInCommand(file, "b.txt"); - verify(runHgOnProjectRepo("status"), added("b.txt")); + verify(runHgOnProjectRepo("status"), HgTestOutputParser.added("b.txt")); } @Test @@ -32,16 +32,16 @@ public class HgCopyTestCase extends HgAbstractTestCase { VirtualFile file = createFileInCommand("a.txt", "new file content"); runHgOnProjectRepo("commit", "-m", "added file"); editFileInCommand(myProject, file, "newer content"); - verify(runHgOnProjectRepo("status"), modified("a.txt")); + verify(runHgOnProjectRepo("status"), HgTestOutputParser.modified("a.txt")); copyFileInCommand(file, "b.txt"); - verify(runHgOnProjectRepo("status"), modified("a.txt"), added("b.txt")); + verify(runHgOnProjectRepo("status"), HgTestOutputParser.modified("a.txt"), HgTestOutputParser.added("b.txt")); } @Test public void testCopyUnversionedFile() throws Exception { VirtualFile file = makeFile(new File(myWorkingCopyDir.getPath(), "a.txt")); copyFileInCommand(file, "b.txt"); - verify(runHgOnProjectRepo("status"), unknown("a.txt"), unknown("b.txt")); + verify(runHgOnProjectRepo("status"), HgTestOutputParser.unknown("a.txt"), HgTestOutputParser.unknown("b.txt")); } @Test @@ -50,7 +50,7 @@ public class HgCopyTestCase extends HgAbstractTestCase { runHgOnProjectRepo("commit", "-m", "added file"); copyFileInCommand(file, "b.txt"); copyFileInCommand(file, "c.txt"); - verify(runHgOnProjectRepo("status"), added("b.txt"), added("c.txt")); + verify(runHgOnProjectRepo("status"), HgTestOutputParser.added("b.txt"), HgTestOutputParser.added("c.txt")); } @Test @@ -59,7 +59,7 @@ public class HgCopyTestCase extends HgAbstractTestCase { createFileInCommand(parent, "a.txt", "new file content"); runHgOnProjectRepo("commit", "-m", "added file"); copyFileInCommand(parent, "org"); - verify(runHgOnProjectRepo("status"), added("org", "a.txt")); + verify(runHgOnProjectRepo("status"), HgTestOutputParser.added("org", "a.txt")); } } diff --git a/plugins/hg4idea/testSrc/org/zmlx/hg4idea/test/HgDeleteTestCase.java b/plugins/hg4idea/testSrc/org/zmlx/hg4idea/test/HgDeleteTestCase.java index b118ffc62d24..070cf7ea14e9 100644 --- a/plugins/hg4idea/testSrc/org/zmlx/hg4idea/test/HgDeleteTestCase.java +++ b/plugins/hg4idea/testSrc/org/zmlx/hg4idea/test/HgDeleteTestCase.java @@ -18,20 +18,20 @@ import org.testng.annotations.Test; import java.io.File; -public class HgDeleteTestCase extends HgAbstractTestCase { +public class HgDeleteTestCase extends HgSingleUserTestCase { @Test public void testDeleteUnmodifiedFile() throws Exception { VirtualFile file = createFileInCommand("a.txt", "new file content"); runHgOnProjectRepo("commit", "-m", "added file"); deleteFileInCommand(file); - verify(runHgOnProjectRepo("status"), removed("a.txt")); + verify(runHgOnProjectRepo("status"), HgTestOutputParser.removed("a.txt")); } @Test public void testDeleteUnversionedFile() throws Exception { VirtualFile file = makeFile(new File(myWorkingCopyDir.getPath(), "a.txt")); - verify(runHgOnProjectRepo("status"), unknown("a.txt")); + verify(runHgOnProjectRepo("status"), HgTestOutputParser.unknown("a.txt")); deleteFileInCommand(file); Assert.assertFalse(file.exists()); } @@ -48,9 +48,9 @@ public class HgDeleteTestCase extends HgAbstractTestCase { VirtualFile file = createFileInCommand("a.txt", "new file content"); runHgOnProjectRepo("commit", "-m", "added file"); editFileInCommand(myProject, file, "even newer content"); - verify(runHgOnProjectRepo("status"), modified("a.txt")); + verify(runHgOnProjectRepo("status"), HgTestOutputParser.modified("a.txt")); deleteFileInCommand(file); - verify(runHgOnProjectRepo("status"), removed("a.txt")); + verify(runHgOnProjectRepo("status"), HgTestOutputParser.removed("a.txt")); } @Test @@ -59,7 +59,7 @@ public class HgDeleteTestCase extends HgAbstractTestCase { createFileInCommand(parent, "a.txt", "new file content"); runHgOnProjectRepo("commit", "-m", "added file"); deleteFileInCommand(parent); - verify(runHgOnProjectRepo("status"), removed("com", "a.txt")); + verify(runHgOnProjectRepo("status"), HgTestOutputParser.removed("com", "a.txt")); } } diff --git a/plugins/hg4idea/testSrc/org/zmlx/hg4idea/test/HgFromClonedTestCase.java b/plugins/hg4idea/testSrc/org/zmlx/hg4idea/test/HgFromClonedTestCase.java index 05a632198a3b..2d00b2ee7bf5 100644 --- a/plugins/hg4idea/testSrc/org/zmlx/hg4idea/test/HgFromClonedTestCase.java +++ b/plugins/hg4idea/testSrc/org/zmlx/hg4idea/test/HgFromClonedTestCase.java @@ -12,7 +12,6 @@ // limitations under the License. package org.zmlx.hg4idea.test; -import com.intellij.openapi.vcs.VcsConfiguration; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.testFramework.fixtures.IdeaTestFixtureFactory; import com.intellij.testFramework.fixtures.TempDirTestFixture; @@ -25,7 +24,10 @@ import java.io.File; import static org.testng.Assert.assertTrue; -public class HgFromClonedTestCase extends HgAbstractTestCase { +/** + * TODO: substitute by new interface from HgCollaborativeTestCase and HgTestRepository. + */ +public class HgFromClonedTestCase extends HgCollaborativeTestCase { protected File remoteRepo; protected File projectRepo; @@ -34,8 +36,9 @@ public class HgFromClonedTestCase extends HgAbstractTestCase { protected VirtualFile projectRepoVirtualFile; @BeforeMethod + @Override public void setUp() throws Exception { - setHGExecutablePath(); + super.setUp(); remoteRepoDir = IdeaTestFixtureFactory.getFixtureFactory().createTempDirTestFixture(); projectRepoDir = IdeaTestFixtureFactory.getFixtureFactory().createTempDirTestFixture(); @@ -47,9 +50,6 @@ public class HgFromClonedTestCase extends HgAbstractTestCase { activateVCS(HgVcs.VCS_NAME); projectRepoVirtualFile = VcsUtil.getVirtualFile(projectRepo); - - enableSilentOperation(VcsConfiguration.StandardConfirmation.ADD); - enableSilentOperation(VcsConfiguration.StandardConfirmation.REMOVE); } private File cloneRemoteRepository(TempDirTestFixture projectRepoDir, File remoteRepo, String destination) throws Exception { @@ -73,7 +73,7 @@ public class HgFromClonedTestCase extends HgAbstractTestCase { File aFile = fillFile(remoteRepo, new String[]{"com", "a.txt"}, "file contents"); verify(runHg(remoteRepo, "add", aFile.getPath())); - verify(runHg(remoteRepo, "status"), added("com", "a.txt")); + verify(runHg(remoteRepo, "status"), HgTestOutputParser.added("com", "a.txt")); verify(runHg(remoteRepo, "commit", "-m", "initial contents")); return remoteRepo; diff --git a/plugins/hg4idea/testSrc/org/zmlx/hg4idea/test/HgHistoryTestCase.java b/plugins/hg4idea/testSrc/org/zmlx/hg4idea/test/HgHistoryTestCase.java index b0d3be8157d0..cf398c9c4687 100644 --- a/plugins/hg4idea/testSrc/org/zmlx/hg4idea/test/HgHistoryTestCase.java +++ b/plugins/hg4idea/testSrc/org/zmlx/hg4idea/test/HgHistoryTestCase.java @@ -17,7 +17,7 @@ import static org.testng.Assert.assertTrue; /** * HgHistoryTestCase tests retrieving file history and specific revisions. */ -public class HgHistoryTestCase extends HgAbstractTestCase { +public class HgHistoryTestCase extends HgSingleUserTestCase { /** * 1. Make two versions of a file (create, add, commit, modify, commit). @@ -27,11 +27,11 @@ public class HgHistoryTestCase extends HgAbstractTestCase { @Test public void testCurrentAndPreviousRevisions() throws Exception { int versions = 0; - fillFile(myProjectRepo, new String[]{ AFILE }, FILE_CONTENT); + fillFile(myProjectDir, new String[]{ AFILE }, FILE_CONTENT); addAll(); commitAll("initial content"); versions++; - fillFile(myProjectRepo, new String[] { AFILE} , FILE_CONTENT_2); + fillFile(myProjectDir, new String[] { AFILE} , FILE_CONTENT_2); commitAll("updated content"); versions++; @@ -58,7 +58,7 @@ public class HgHistoryTestCase extends HgAbstractTestCase { public void renameShouldPreserveFileHistory() throws Exception { int versions = 0; - fillFile(myProjectRepo, new String[]{ AFILE }, FILE_CONTENT); + fillFile(myProjectDir, new String[]{ AFILE }, FILE_CONTENT); addAll(); commitAll("initial content"); versions++; @@ -67,7 +67,7 @@ public class HgHistoryTestCase extends HgAbstractTestCase { commitAll("file renamed"); versions++; - fillFile(myProjectRepo, new String[]{ BFILE }, FILE_CONTENT_2); + fillFile(myProjectDir, new String[]{ BFILE }, FILE_CONTENT_2); commitAll("updated content"); versions++; @@ -88,7 +88,7 @@ public class HgHistoryTestCase extends HgAbstractTestCase { } private VcsHistorySession getHistorySession(String relativePath) throws VcsException { - return HgVcs.getInstance(myProject).getVcsHistoryProvider().createSessionFor(VcsUtil.getFilePath(new File(myProjectRepo, relativePath), false)); + return HgVcs.getInstance(myProject).getVcsHistoryProvider().createSessionFor(VcsUtil.getFilePath(new File(myProjectDir, relativePath), false)); } } diff --git a/plugins/hg4idea/testSrc/org/zmlx/hg4idea/test/HgLogTestCase.java b/plugins/hg4idea/testSrc/org/zmlx/hg4idea/test/HgLogTestCase.java index 581cb62e38fa..65e02297031a 100644 --- a/plugins/hg4idea/testSrc/org/zmlx/hg4idea/test/HgLogTestCase.java +++ b/plugins/hg4idea/testSrc/org/zmlx/hg4idea/test/HgLogTestCase.java @@ -20,15 +20,15 @@ import java.util.List; import static org.testng.Assert.assertEquals; -public class HgLogTestCase extends HgAbstractTestCase { +public class HgLogTestCase extends HgSingleUserTestCase { @Test public void testCommitMessagesWithMultipleLines() throws Exception { - fillFile(myProjectRepo, new String[]{"file.txt"}, "initial contents"); + fillFile(myProjectDir, new String[]{"file.txt"}, "initial contents"); runHgOnProjectRepo("add", "."); runHgOnProjectRepo("commit", "-m", "initial\ncontents"); - fillFile(myProjectRepo, new String[]{"file.txt"}, "updated contents"); + fillFile(myProjectDir, new String[]{"file.txt"}, "updated contents"); runHgOnProjectRepo("commit", "-m", "updated\ncontents"); List fileLog = new HgLogCommand(myProject).execute(getHgFile("file.txt"), 10, false); diff --git a/plugins/hg4idea/testSrc/org/zmlx/hg4idea/test/HgMergeTestCase.java b/plugins/hg4idea/testSrc/org/zmlx/hg4idea/test/HgMergeTestCase.java new file mode 100644 index 000000000000..99f3a191c781 --- /dev/null +++ b/plugins/hg4idea/testSrc/org/zmlx/hg4idea/test/HgMergeTestCase.java @@ -0,0 +1,29 @@ +// Copyright 2008-2010 Victor Iacoban +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software distributed under +// the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, +// either express or implied. See the License for the specific language governing permissions and +// limitations under the License. +package org.zmlx.hg4idea.test; + +import org.testng.annotations.Test; + +/** + * Tests adding files to the Mercurial repository. + */ +public class HgMergeTestCase extends HgCollaborativeTestCase { + + @Test + public void testRevertUncommittedMerge() throws Exception { + HgTestRepository central = createRepository(); + HgTestRepository worker = cloneFrom(central); + } + + +} diff --git a/plugins/hg4idea/testSrc/org/zmlx/hg4idea/test/HgMoveTestCase.java b/plugins/hg4idea/testSrc/org/zmlx/hg4idea/test/HgMoveTestCase.java index af9e863d2bef..97dfb4a7d82f 100644 --- a/plugins/hg4idea/testSrc/org/zmlx/hg4idea/test/HgMoveTestCase.java +++ b/plugins/hg4idea/testSrc/org/zmlx/hg4idea/test/HgMoveTestCase.java @@ -17,7 +17,7 @@ import org.testng.annotations.Test; import java.io.File; -public class HgMoveTestCase extends HgAbstractTestCase { +public class HgMoveTestCase extends HgSingleUserTestCase { @Test public void testMoveNewFile() throws Exception { @@ -27,7 +27,7 @@ public class HgMoveTestCase extends HgAbstractTestCase { VirtualFile parent2 = createDirInCommand(myWorkingCopyDir, "org"); moveFileInCommand(file, parent2); - verify(runHgOnProjectRepo("status"), added("org", "a.txt")); + verify(runHgOnProjectRepo("status"), HgTestOutputParser.added("org", "a.txt")); } @Test @@ -39,7 +39,7 @@ public class HgMoveTestCase extends HgAbstractTestCase { VirtualFile parent2 = createDirInCommand(myWorkingCopyDir, "org"); moveFileInCommand(file, parent2); - verify(runHgOnProjectRepo("status"), added("org", "a.txt"), removed("com", "a.txt")); + verify(runHgOnProjectRepo("status"), HgTestOutputParser.added("org", "a.txt"), HgTestOutputParser.removed("com", "a.txt")); } @Test @@ -52,7 +52,7 @@ public class HgMoveTestCase extends HgAbstractTestCase { VirtualFile parent2 = createDirInCommand(myWorkingCopyDir, "org"); moveFileInCommand(dir, parent2); - verify(runHgOnProjectRepo("status"), added("org", "zzz", "a.txt"), removed("com", "zzz", "a.txt")); + verify(runHgOnProjectRepo("status"), HgTestOutputParser.added("org", "zzz", "a.txt"), HgTestOutputParser.removed("com", "zzz", "a.txt")); } @Test @@ -62,12 +62,12 @@ public class HgMoveTestCase extends HgAbstractTestCase { File unversionedFile = new File(parent1.getPath(), "a.txt"); VirtualFile file = makeFile(unversionedFile); - verify(runHgOnProjectRepo("status"), unknown("com", "a.txt")); + verify(runHgOnProjectRepo("status"), HgTestOutputParser.unknown("com", "a.txt")); VirtualFile parent2 = createDirInCommand(myWorkingCopyDir, "org"); moveFileInCommand(file, parent2); - verify(runHgOnProjectRepo("status"), unknown("org", "a.txt")); + verify(runHgOnProjectRepo("status"), HgTestOutputParser.unknown("org", "a.txt")); } } diff --git a/plugins/hg4idea/testSrc/org/zmlx/hg4idea/test/HgPushTestCase.java b/plugins/hg4idea/testSrc/org/zmlx/hg4idea/test/HgPushTestCase.java new file mode 100644 index 000000000000..d7328ac3e01f --- /dev/null +++ b/plugins/hg4idea/testSrc/org/zmlx/hg4idea/test/HgPushTestCase.java @@ -0,0 +1,70 @@ +/* + * 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 org.zmlx.hg4idea.test; + +import com.intellij.openapi.vfs.VirtualFile; +import org.testng.annotations.Test; +import org.zmlx.hg4idea.command.HgCommandResult; +import org.zmlx.hg4idea.command.HgErrorUtil; +import org.zmlx.hg4idea.command.HgPushCommand; + +import static org.testng.Assert.assertFalse; +import static org.testng.Assert.assertNotNull; + +/** + * @author Kirill Likhodedov + */ +public class HgPushTestCase extends HgCollaborativeTestCase { + + /** + * Tests 'push' using only native commands. + * This is to test the harness. + */ + @Test + public void testNativeCommands() throws Exception { + myRepo.getDirFixture().createFile(AFILE); + myRepo.add(); + myRepo.commit(); + myRepo.push(); + + myParentRepo.update(); + assertNotNull(myParentRepo.getDirFixture().getFile(AFILE)); + } + + /** + * Testing HgPushCommand: + * 1. Create a file, add to the VCS and commit via ChangeListManager. + * 2. Push via HgPushCommand. + * 3. Natively update parent repository. + * 4. Verify that the changes appeared there. + */ + @Test + public void testHgPushCommand() throws Exception { + final VirtualFile vf = myRepo.getDirFixture().createFile(AFILE); + myChangeListManager.addUnversionedFilesToVcs(vf); + myChangeListManager.checkFilesAreInList(true, vf); + myChangeListManager.commitFiles(vf); + + final HgPushCommand command = new HgPushCommand(myProject, myRepo.getDir(), myParentRepo.getDir().getUrl()); + final HgCommandResult result = command.execute(); + assertNotNull(result); + assertFalse(HgErrorUtil.isAbort(result)); + + myParentRepo.update(); + assertNotNull(myParentRepo.getDirFixture().getFile(AFILE)); + } + +} diff --git a/plugins/hg4idea/testSrc/org/zmlx/hg4idea/test/HgRenameTestCase.java b/plugins/hg4idea/testSrc/org/zmlx/hg4idea/test/HgRenameTestCase.java index 8d8310071b1f..fb02a4371fa2 100644 --- a/plugins/hg4idea/testSrc/org/zmlx/hg4idea/test/HgRenameTestCase.java +++ b/plugins/hg4idea/testSrc/org/zmlx/hg4idea/test/HgRenameTestCase.java @@ -17,14 +17,14 @@ import org.testng.annotations.Test; import java.io.File; -public class HgRenameTestCase extends HgAbstractTestCase { +public class HgRenameTestCase extends HgSingleUserTestCase { @Test public void testRenameUnmodifiedFile() throws Exception { VirtualFile file = createFileInCommand("a.txt", "new file content"); runHgOnProjectRepo("commit", "-m", "added file"); renameFileInCommand(file, "b.txt"); - verify(runHgOnProjectRepo("status"), added("b.txt"), removed("a.txt")); + verify(runHgOnProjectRepo("status"), HgTestOutputParser.added("b.txt"), HgTestOutputParser.removed("a.txt")); } @Test @@ -32,16 +32,16 @@ public class HgRenameTestCase extends HgAbstractTestCase { VirtualFile file = createFileInCommand("a.txt", "new file content"); runHgOnProjectRepo("commit", "-m", "added file"); editFileInCommand(myProject, file, "modified new file content"); - verify(runHgOnProjectRepo("status"), modified("a.txt")); + verify(runHgOnProjectRepo("status"), HgTestOutputParser.modified("a.txt")); renameFileInCommand(file, "b.txt"); - verify(runHgOnProjectRepo("status"), added("b.txt"), removed("a.txt")); + verify(runHgOnProjectRepo("status"), HgTestOutputParser.added("b.txt"), HgTestOutputParser.removed("a.txt")); } @Test public void testRenameNewFile() throws Exception { VirtualFile file = createFileInCommand("a.txt", "new file content"); renameFileInCommand(file, "b.txt"); - verify(runHgOnProjectRepo("status"), added("b.txt")); + verify(runHgOnProjectRepo("status"), HgTestOutputParser.added("b.txt")); } @Test @@ -50,7 +50,7 @@ public class HgRenameTestCase extends HgAbstractTestCase { runHgOnProjectRepo("commit", "-m", "added file"); renameFileInCommand(file, "b.txt"); renameFileInCommand(file, "c.txt"); - verify(runHgOnProjectRepo("status"), added("c.txt"), removed("a.txt")); + verify(runHgOnProjectRepo("status"), HgTestOutputParser.added("c.txt"), HgTestOutputParser.removed("a.txt")); } @Test @@ -59,7 +59,7 @@ public class HgRenameTestCase extends HgAbstractTestCase { createFileInCommand(parent, "a.txt", "new file content"); runHgOnProjectRepo("commit", "-m", "added file"); renameFileInCommand(parent, "org"); - verify(runHgOnProjectRepo("status"), added("org", "a.txt"), removed("com", "a.txt")); + verify(runHgOnProjectRepo("status"), HgTestOutputParser.added("org", "a.txt"), HgTestOutputParser.removed("com", "a.txt")); } @Test @@ -68,20 +68,20 @@ public class HgRenameTestCase extends HgAbstractTestCase { File unversionedFile = new File(parent.getPath(), "a.txt"); makeFile(unversionedFile); - verify(runHgOnProjectRepo("status"), unknown("com", "a.txt")); + verify(runHgOnProjectRepo("status"), HgTestOutputParser.unknown("com", "a.txt")); renameFileInCommand(parent, "org"); - verify(runHgOnProjectRepo("status"), unknown("org", "a.txt")); + verify(runHgOnProjectRepo("status"), HgTestOutputParser.unknown("org", "a.txt")); } @Test public void testRenameUnversionedFile() throws Exception { File unversionedFile = new File(myWorkingCopyDir.getPath(), "a.txt"); VirtualFile file = makeFile(unversionedFile); - verify(runHgOnProjectRepo("status"), unknown("a.txt")); + verify(runHgOnProjectRepo("status"), HgTestOutputParser.unknown("a.txt")); renameFileInCommand(file, "b.txt"); - verify(runHgOnProjectRepo("status"), unknown("b.txt")); + verify(runHgOnProjectRepo("status"), HgTestOutputParser.unknown("b.txt")); } } diff --git a/plugins/hg4idea/testSrc/org/zmlx/hg4idea/test/HgResolveConflictTestCase.java b/plugins/hg4idea/testSrc/org/zmlx/hg4idea/test/HgResolveConflictTestCase.java index bf5a0df75e61..cb0c76ce9492 100644 --- a/plugins/hg4idea/testSrc/org/zmlx/hg4idea/test/HgResolveConflictTestCase.java +++ b/plugins/hg4idea/testSrc/org/zmlx/hg4idea/test/HgResolveConflictTestCase.java @@ -17,7 +17,7 @@ import org.testng.Assert; import org.testng.annotations.Test; import org.zmlx.hg4idea.command.HgResolveCommand; -public class HgResolveConflictTestCase extends HgAbstractTestCase { +public class HgResolveConflictTestCase extends HgSingleUserTestCase { public static final String BASE = "one\n" + "conflicting\n" + @@ -47,7 +47,7 @@ public class HgResolveConflictTestCase extends HgAbstractTestCase { runHgOnProjectRepo("--config", "ui.merge=internal:merge", "merge"); - VirtualFile repoFile = makeFile(myProjectRepo); + VirtualFile repoFile = makeFile(myProjectDir); HgResolveCommand.MergeData data = new HgResolveCommand(myProject).getResolveData(repoFile, repoFile.findChild("conflicting")); Assert.assertEquals(data.getBase(), BASE.getBytes(), diff --git a/plugins/hg4idea/testSrc/org/zmlx/hg4idea/test/HgRevertTestCase.java b/plugins/hg4idea/testSrc/org/zmlx/hg4idea/test/HgRevertTestCase.java index 674835517c0c..5450ab4e7c27 100644 --- a/plugins/hg4idea/testSrc/org/zmlx/hg4idea/test/HgRevertTestCase.java +++ b/plugins/hg4idea/testSrc/org/zmlx/hg4idea/test/HgRevertTestCase.java @@ -9,14 +9,14 @@ import java.nio.charset.Charset; import static org.testng.Assert.assertEquals; -public class HgRevertTestCase extends HgAbstractTestCase { +public class HgRevertTestCase extends HgSingleUserTestCase { @Test public void testRevertToCurrentRevision() throws Exception { - fillFile(myProjectRepo, new String[]{"file.txt"}, "initial contents"); + fillFile(myProjectDir, new String[]{"file.txt"}, "initial contents"); runHgOnProjectRepo("add", "."); runHgOnProjectRepo("commit", "-m", "initial contents"); - fillFile(myProjectRepo, new String[]{"file.txt"}, "new contents"); + fillFile(myProjectDir, new String[]{"file.txt"}, "new contents"); HgRevertCommand revertCommand = new HgRevertCommand(myProject); revertCommand.execute(getHgFile("file.txt"), null, false); @@ -30,11 +30,11 @@ public class HgRevertTestCase extends HgAbstractTestCase { @Test public void testRevertToGivenRevision() throws Exception { - fillFile(myProjectRepo, new String[]{"file.txt"}, "initial contents"); + fillFile(myProjectDir, new String[]{"file.txt"}, "initial contents"); runHgOnProjectRepo("add", "."); runHgOnProjectRepo("commit", "-m", "initial contents"); - fillFile(myProjectRepo, new String[]{"file.txt"}, "new contents"); + fillFile(myProjectDir, new String[]{"file.txt"}, "new contents"); runHgOnProjectRepo("commit", "-m", "new contents"); HgRevertCommand revertCommand = new HgRevertCommand(myProject); diff --git a/plugins/hg4idea/testSrc/org/zmlx/hg4idea/test/HgSingleUserTestCase.java b/plugins/hg4idea/testSrc/org/zmlx/hg4idea/test/HgSingleUserTestCase.java new file mode 100644 index 000000000000..abea09e61ca4 --- /dev/null +++ b/plugins/hg4idea/testSrc/org/zmlx/hg4idea/test/HgSingleUserTestCase.java @@ -0,0 +1,49 @@ +/* + * 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 org.zmlx.hg4idea.test; + +import com.intellij.openapi.vcs.VcsConfiguration; +import org.testng.annotations.BeforeMethod; +import org.zmlx.hg4idea.HgVcs; + +import java.io.File; + +/** + * HgSingleUserTestCase is the parent of test cases for single user workflow. + * It doesn't include collaborate tasks such as cloning, pushing, etc. + * @author Kirill Likhodedov + */ +public class HgSingleUserTestCase extends HgAbstractTestCase { + + protected HgTestRepository myRepo; + + @BeforeMethod + @Override + protected void setUp() throws Exception { + super.setUp(); + + myRepo = createRepository(); + myProjectDir = new File(myRepo.getDirFixture().getTempDirPath()); + + initProject(myProjectDir); + activateVCS(HgVcs.VCS_NAME); + myChangeListManager = new HgTestChangeListManager(myProject); + + enableSilentOperation(VcsConfiguration.StandardConfirmation.ADD); + enableSilentOperation(VcsConfiguration.StandardConfirmation.REMOVE); + } + +} \ No newline at end of file diff --git a/plugins/hg4idea/testSrc/org/zmlx/hg4idea/test/TestChangeListManager.java b/plugins/hg4idea/testSrc/org/zmlx/hg4idea/test/HgTestChangeListManager.java similarity index 90% rename from plugins/hg4idea/testSrc/org/zmlx/hg4idea/test/TestChangeListManager.java rename to plugins/hg4idea/testSrc/org/zmlx/hg4idea/test/HgTestChangeListManager.java index 791699be0f9f..96bbc3ff97db 100644 --- a/plugins/hg4idea/testSrc/org/zmlx/hg4idea/test/TestChangeListManager.java +++ b/plugins/hg4idea/testSrc/org/zmlx/hg4idea/test/HgTestChangeListManager.java @@ -19,6 +19,7 @@ import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.project.Project; import com.intellij.openapi.vcs.changes.Change; import com.intellij.openapi.vcs.changes.ChangeListManagerImpl; +import com.intellij.openapi.vcs.changes.LocalChangeList; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.util.containers.HashSet; import org.testng.Assert; @@ -29,15 +30,17 @@ import java.util.Arrays; import java.util.Collection; import java.util.List; +import static org.testng.Assert.assertNotNull; + /** * The ChangeListManagerImpl extension with some useful helper methods for tests. * @author Kirill Likhodedov */ -public class TestChangeListManager { +public class HgTestChangeListManager { private ChangeListManagerImpl peer; - public TestChangeListManager(Project project) { + public HgTestChangeListManager(Project project) { peer = ChangeListManagerImpl.getInstanceImpl(project); } @@ -79,7 +82,10 @@ public class TestChangeListManager { for (VirtualFile f : files) { changes.addAll(peer.getChangesIn(f)); } - Assert.assertTrue(peer.commitChangesSynchronouslyWithResult(peer.getDefaultChangeList(), changes)); + final LocalChangeList list = peer.getDefaultChangeList(); + assertNotNull(list); + list.setComment("A comment to a commit"); + Assert.assertTrue(peer.commitChangesSynchronouslyWithResult(list, changes)); } public void removeFiles(final VirtualFile file) { diff --git a/plugins/hg4idea/testSrc/org/zmlx/hg4idea/test/HgTestOutputParser.java b/plugins/hg4idea/testSrc/org/zmlx/hg4idea/test/HgTestOutputParser.java new file mode 100644 index 000000000000..d619efb474b1 --- /dev/null +++ b/plugins/hg4idea/testSrc/org/zmlx/hg4idea/test/HgTestOutputParser.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 org.zmlx.hg4idea.test; + +import java.io.File; + +/** + * Helper class for parsing outputs of mercurial commands. + * @author Kirill Likhodedov + */ +public class HgTestOutputParser { + + public static String added(String... path) { + return "A " + path(path); + } + + public static String removed(String... path) { + return "R " + path(path); + } + + public static String unknown(String... path) { + return "? " + path(path); + } + + public static String modified(String... path) { + return "M " + path(path); + } + + public static String missing(String... path) { + return "! " + path(path); + } + + public static String path(String... line) { + StringBuilder builder = new StringBuilder(); + + int linePartCount = line.length; + + for (int i = 0; i < linePartCount; i++) { + String linePart = line[i]; + builder.append(linePart); + + if (i < linePartCount - 1) { + builder.append(File.separator); + } + } + + return builder.toString(); + } +} diff --git a/plugins/hg4idea/testSrc/org/zmlx/hg4idea/test/HgTestRepository.java b/plugins/hg4idea/testSrc/org/zmlx/hg4idea/test/HgTestRepository.java new file mode 100644 index 000000000000..2cf7d01b036c --- /dev/null +++ b/plugins/hg4idea/testSrc/org/zmlx/hg4idea/test/HgTestRepository.java @@ -0,0 +1,82 @@ +/* + * 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 org.zmlx.hg4idea.test; + +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.testFramework.fixtures.TempDirTestFixture; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.io.File; +import java.io.IOException; + +/** + * Representation of a Mercurial repository for tests purposes. + * @author Kirill Likhodedov + */ +class HgTestRepository { + @NotNull private final HgAbstractTestCase myTest; + @NotNull private final TempDirTestFixture myDirFixture; + @Nullable private final HgTestRepository myParent; // cloned from + + HgTestRepository(@NotNull HgAbstractTestCase test, @NotNull TempDirTestFixture dir) { + this(test, dir, null); + } + + /** + * @param test test instance + * @param dir repository root + * @param parent parent repository where this repository is cloned from, if one exists. + */ + HgTestRepository(@NotNull HgAbstractTestCase test, @NotNull TempDirTestFixture dir, @Nullable HgTestRepository parent) { + myTest = test; + myDirFixture = dir; + myParent = parent; + } + + @NotNull + TempDirTestFixture getDirFixture() { + return myDirFixture; + } + + /** + * Natively executes the given mercurial command. + * @param commandWithParameters Mercurial command with parameters. E.g. ["status", "-a"] + */ + void execute(String... commandWithParameters) throws IOException { + myTest.runHg(new File(myDirFixture.getTempDirPath()), commandWithParameters); + } + + void add() throws IOException { + execute("add"); + } + + void commit() throws IOException { + execute("commit", "-m", "Sample commit message"); + } + + void update() throws IOException { + execute("update"); + } + + void push() throws IOException { + execute("push"); + } + + public VirtualFile getDir() { + return myDirFixture.getFile("."); + } +} diff --git a/plugins/relaxng/src/org/intellij/plugins/relaxNG/compact/RngCompactLanguage.java b/plugins/relaxng/src/org/intellij/plugins/relaxNG/compact/RngCompactLanguage.java index 03a00b29fb11..776968c42f60 100644 --- a/plugins/relaxng/src/org/intellij/plugins/relaxNG/compact/RngCompactLanguage.java +++ b/plugins/relaxng/src/org/intellij/plugins/relaxNG/compact/RngCompactLanguage.java @@ -101,11 +101,11 @@ public class RngCompactLanguage extends Language { public static class MyNamesValidator implements NamesValidator { public boolean isKeyword(String name, Project project) { - return RenameUtil.isKeyword(PsiManager.getInstance(project), name); + return RenameUtil.isKeyword(name); } public boolean isIdentifier(String name, Project project) { - return RenameUtil.isIdentifier(PsiManager.getInstance(project), name); + return RenameUtil.isIdentifier(name); } } diff --git a/plugins/relaxng/src/org/intellij/plugins/relaxNG/compact/psi/util/RenameUtil.java b/plugins/relaxng/src/org/intellij/plugins/relaxNG/compact/psi/util/RenameUtil.java index ce4da3cae34c..5261d48a57ce 100644 --- a/plugins/relaxng/src/org/intellij/plugins/relaxNG/compact/psi/util/RenameUtil.java +++ b/plugins/relaxng/src/org/intellij/plugins/relaxNG/compact/psi/util/RenameUtil.java @@ -17,33 +17,44 @@ package org.intellij.plugins.relaxNG.compact.psi.util; import com.intellij.lang.ASTNode; -import com.intellij.lang.LanguageParserDefinitions; -import com.intellij.lang.ParserDefinition; -import com.intellij.lexer.Lexer; import com.intellij.psi.PsiFileFactory; import com.intellij.psi.PsiManager; -import com.intellij.psi.tree.IElementType; -import com.intellij.psi.tree.TokenSet; import com.intellij.util.IncorrectOperationException; +import com.intellij.util.containers.HashSet; +import com.sun.org.apache.xerces.internal.util.XML11Char; import org.intellij.plugins.relaxNG.compact.RncElementTypes; import org.intellij.plugins.relaxNG.compact.RncFileType; import org.intellij.plugins.relaxNG.compact.RncTokenTypes; -import org.intellij.plugins.relaxNG.compact.RngCompactLanguage; import org.intellij.plugins.relaxNG.compact.psi.RncFile; import org.intellij.plugins.relaxNG.compact.psi.RncGrammar; import org.jetbrains.annotations.NotNull; +import java.util.Collections; +import java.util.Set; + /** * Created by IntelliJ IDEA. * User: sweinreuter * Date: 14.08.2007 */ public class RenameUtil { + + private static Set ourRncKeywords = new HashSet(); + + static { + Collections.addAll(ourRncKeywords, "attribute", "default", "datatypes", "div", "element", "empty", "external", + "grammar", "include", "inherit", "list", "mixed", "namespace", "notAllowed", "parent", "start", + "string", "text", "token"); + } + + private RenameUtil() { + } + @NotNull public static ASTNode createIdentifierNode(PsiManager manager, String name) throws IncorrectOperationException { - if (isKeyword(manager, name)) { + if (isKeyword(name)) { name = "\\" + name; - } else if (!isIdentifier(manager, name)) { + } else if (!isIdentifier(name)) { throw new IncorrectOperationException("Illegal identifier: " + name); } @@ -55,15 +66,23 @@ public class RenameUtil { return newNode; } - public static boolean isIdentifier(PsiManager manager, String name) { - return isTokenOfType(manager, name, RncTokenTypes.IDENTIFIER_OR_KEYWORD); + public static boolean isIdentifier(String name) { + //return isTokenOfType(manager, name, RncTokenTypes.IDENTIFIER_OR_KEYWORD); + if (name == null) { + return false; + } + if (XML11Char.isXML11ValidNCName(name)) { + return true; + } + return name.length() >= 2 && name.startsWith("\\") && XML11Char.isXML11ValidNCName(name.substring(1)); } - public static boolean isKeyword(PsiManager manager, String name) { - return isTokenOfType(manager, name, RncTokenTypes.KEYWORDS); + public static boolean isKeyword(String name) { + //return isTokenOfType(manager, name, RncTokenTypes.KEYWORDS); + return ourRncKeywords.contains(name); } - private static boolean isTokenOfType(PsiManager manager, String name, TokenSet set) { + /*private static boolean isTokenOfType(PsiManager manager, String name, TokenSet set) { final ParserDefinition definition = LanguageParserDefinitions.INSTANCE.forLanguage(RngCompactLanguage.INSTANCE); assert definition != null; final Lexer lexer = definition.createLexer(manager.getProject()); @@ -72,7 +91,7 @@ public class RenameUtil { final IElementType t = lexer.getTokenType(); lexer.advance(); return lexer.getTokenType() == null && set.contains(t); - } + }*/ public static ASTNode createPrefixedNode(PsiManager manager, String prefix, String localPart) { final PsiFileFactory f = PsiFileFactory.getInstance(manager.getProject()); diff --git a/plugins/relaxng/test/org/intellij/plugins/relaxNG/HighlightingTestBase.java b/plugins/relaxng/test/org/intellij/plugins/relaxNG/HighlightingTestBase.java index 08dc74d42a96..183daee3f4a8 100644 --- a/plugins/relaxng/test/org/intellij/plugins/relaxNG/HighlightingTestBase.java +++ b/plugins/relaxng/test/org/intellij/plugins/relaxNG/HighlightingTestBase.java @@ -16,22 +16,20 @@ package org.intellij.plugins.relaxNG; -import com.intellij.codeHighlighting.TextEditorHighlightingPass; import com.intellij.codeInsight.daemon.QuickFixProvider; -import com.intellij.codeInsight.daemon.impl.*; +import com.intellij.codeInsight.daemon.impl.HighlightInfo; +import com.intellij.codeInsight.daemon.impl.HighlightInfoType; import com.intellij.codeInsight.intention.IntentionAction; import com.intellij.codeInspection.InspectionToolProvider; import com.intellij.codeInspection.htmlInspections.RequiredAttributesInspection; import com.intellij.javaee.ExternalResourceManagerEx; import com.intellij.mock.MockProgressIndicator; -import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.application.PluginPathManager; import com.intellij.openapi.application.Result; import com.intellij.openapi.application.WriteAction; import com.intellij.openapi.command.WriteCommandAction; import com.intellij.openapi.editor.Document; import com.intellij.openapi.editor.Editor; -import com.intellij.openapi.progress.ProgressManager; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.*; import com.intellij.openapi.util.io.FileUtil; @@ -44,7 +42,7 @@ import com.intellij.testFramework.fixtures.CodeInsightTestFixture; import com.intellij.testFramework.fixtures.IdeaProjectTestFixture; import com.intellij.testFramework.fixtures.IdeaTestFixtureFactory; import com.intellij.testFramework.fixtures.TestFixtureBuilder; -import com.intellij.util.ArrayUtil; +import com.intellij.testFramework.fixtures.impl.CodeInsightTestFixtureImpl; import junit.framework.TestCase; import org.intellij.plugins.relaxNG.inspections.RngDomInspection; import org.intellij.plugins.testUtil.IdeaCodeInsightTestCase; @@ -54,10 +52,7 @@ import org.jetbrains.annotations.Nullable; import java.io.File; import java.io.IOException; -import java.util.ArrayList; import java.util.Collection; -import java.util.Collections; -import java.util.List; import static com.intellij.openapi.util.io.FileUtil.delete; @@ -196,44 +191,23 @@ public abstract class HighlightingTestBase extends TestCase implements IdeaCodeI } @NotNull - private Collection doHighlighting(final Boolean includeExternalToolPass) { + private Collection doHighlighting(final Boolean externalToolPass) { final Project project = myTestFixture.getProject(); PsiDocumentManager.getInstance(project).commitAllDocuments(); + final Editor editor = myTestFixture.getEditor(); - return ApplicationManager.getApplication().runReadAction(new Computable>() { - public Collection compute() { - final Editor editor = myTestFixture.getEditor(); - final List passes = - TextEditorHighlightingPassRegistrarEx.getInstanceEx(project).instantiatePasses(myTestFixture.getFile(), editor, ArrayUtil.EMPTY_INT_ARRAY); - final MockProgressIndicator progress = new MyMockProgressIndicator(); - - Runnable runnable = new Runnable() { - public void run() { - for (TextEditorHighlightingPass pass : passes) { - if (includeExternalToolPass == null) { - pass.collectInformation(progress); - } else { - if (includeExternalToolPass == pass instanceof ExternalToolPass) { - pass.collectInformation(progress); - } - } - } - for (TextEditorHighlightingPass pass : passes) { - if (includeExternalToolPass == null) { - pass.applyInformationToEditor(); - } else { - if (includeExternalToolPass == pass instanceof ExternalToolPass) { - pass.applyInformationToEditor(); - } - } - } - } - }; - ProgressManager.getInstance().runProcess(runnable, progress); - List infos = DaemonCodeAnalyzerImpl.getHighlights(editor.getDocument(), project); - return infos == null ? Collections.emptyList() : new ArrayList(infos); - } - }); + int[] ignore = externalToolPass == null || externalToolPass ? new int[]{ + com.intellij.codeHighlighting.Pass.LINE_MARKERS, + com.intellij.codeHighlighting.Pass.LOCAL_INSPECTIONS, + com.intellij.codeHighlighting.Pass.POPUP_HINTS, + com.intellij.codeHighlighting.Pass.POST_UPDATE_ALL, + com.intellij.codeHighlighting.Pass.UPDATE_ALL, + com.intellij.codeHighlighting.Pass.UPDATE_FOLDING, + com.intellij.codeHighlighting.Pass.UPDATE_OVERRIDEN_MARKERS, + com.intellij.codeHighlighting.Pass.UPDATE_VISIBLE, + com.intellij.codeHighlighting.Pass.VISIBLE_LINE_MARKERS, + } : new int[]{com.intellij.codeHighlighting.Pass.EXTERNAL_TOOLS}; + return CodeInsightTestFixtureImpl.instantiateAndRun(myTestFixture.getFile(), editor, ignore, false); } protected void doTestCompletion(String name, String ext) throws Throwable { diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnAuthenticationManager.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnAuthenticationManager.java index 937be282dd2b..98616184b325 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnAuthenticationManager.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnAuthenticationManager.java @@ -16,17 +16,25 @@ package org.jetbrains.idea.svn; import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.application.ModalityState; import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.progress.ProgressIndicator; +import com.intellij.openapi.progress.ProgressManager; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.MessageType; import com.intellij.openapi.ui.Messages; +import com.intellij.openapi.util.Getter; import com.intellij.openapi.util.SystemInfo; import com.intellij.openapi.util.io.FileUtil; +import com.intellij.openapi.vcs.CalledInAwt; +import com.intellij.openapi.vcs.changes.committed.AbstractCalledLater; import com.intellij.openapi.vcs.changes.ui.ChangesViewBalloonProblemNotifier; -import com.intellij.ui.GuiUtils; -import com.intellij.util.containers.SoftHashMap; +import com.intellij.util.EventDispatcher; import com.intellij.util.net.HttpConfigurable; import org.jetbrains.annotations.Nullable; +import org.jetbrains.idea.svn.auth.ProviderType; +import org.jetbrains.idea.svn.auth.SvnAuthenticationInteraction; +import org.jetbrains.idea.svn.auth.SvnAuthenticationListener; import org.tmatesoft.svn.core.SVNErrorMessage; import org.tmatesoft.svn.core.SVNException; import org.tmatesoft.svn.core.SVNURL; @@ -37,43 +45,74 @@ import org.tmatesoft.svn.core.io.SVNRepository; import java.io.File; import java.io.IOException; -import java.lang.reflect.InvocationTargetException; -import java.util.Iterator; -import java.util.Map; -import java.util.StringTokenizer; -import java.util.concurrent.atomic.AtomicBoolean; +import java.util.*; /** * @author alex */ -public class SvnAuthenticationManager extends DefaultSVNAuthenticationManager { +public class SvnAuthenticationManager extends DefaultSVNAuthenticationManager implements SvnAuthenticationListener { private static final Logger LOG = Logger.getInstance(SvnAuthenticationManager.class.getName()); private final Project myProject; private final File myConfigDirectory; private PersistentAuthenticationProviderProxy myPersistentAuthenticationProviderProxy; private SvnConfiguration myConfig; - private AtomicBoolean myStoreInPlainTextAnyway = new AtomicBoolean(false); + // instead of ThreadLocal + private final Set myPlainTextAllowed; + private static final ThreadLocal ourJustEntered = new ThreadLocal(); + private SvnAuthenticationInteraction myInteraction; + private final EventDispatcher myListener; public SvnAuthenticationManager(final Project project, final File configDirectory) { super(configDirectory, true, null, null); myProject = project; myConfigDirectory = configDirectory; + myListener = EventDispatcher.create(SvnAuthenticationListener.class); myConfig = SvnConfiguration.getInstance(myProject); if (myPersistentAuthenticationProviderProxy != null) { myPersistentAuthenticationProviderProxy.setProject(myProject); } + myPlainTextAllowed = Collections.synchronizedSet(new HashSet()); + myInteraction = new MySvnAuthenticationInteraction(myProject); + } + + public void addListener(final SvnAuthenticationListener listener) { + myListener.addListener(listener); + } + + @Override + public void actualSaveWillBeTried(ProviderType type, SVNURL url, String realm, String kind) { + myListener.getMulticaster().actualSaveWillBeTried(type, url, realm, kind); + } + + @Override + public void requested(ProviderType type, SVNURL url, String realm, String kind, boolean canceled) { + if (ProviderType.interactive.equals(type) && (! canceled)) { + ourJustEntered.set(true); + } + myListener.getMulticaster().requested(type, url, realm, kind, canceled); } @Override protected ISVNAuthenticationProvider createCacheAuthenticationProvider(File authDir, String userName) { - myStoreInPlainTextAnyway = new AtomicBoolean(false); myPersistentAuthenticationProviderProxy = new PersistentAuthenticationProviderProxy(super.createCacheAuthenticationProvider(authDir, userName), authDir); return myPersistentAuthenticationProviderProxy; } + @Override + public void acknowledgeAuthentication(boolean accepted, + String kind, + String realm, + SVNErrorMessage errorMessage, + SVNAuthentication authentication) throws SVNException { + try { + super.acknowledgeAuthentication(accepted, kind, realm, errorMessage, authentication); + } finally { + final Thread currentThread = Thread.currentThread(); + myPlainTextAllowed.remove(currentThread); + } + } + private class PersistentAuthenticationProviderProxy implements ISVNAuthenticationProvider, IPersistentAuthenticationProvider { - private final Map myRewritePreventer; - private static final long ourRefreshInterval = 6000 * 1000; private final ISVNAuthenticationProvider myDelegate; private final File myAuthDir; private Project myProject; @@ -81,7 +120,6 @@ public class SvnAuthenticationManager extends DefaultSVNAuthenticationManager { private PersistentAuthenticationProviderProxy(final ISVNAuthenticationProvider delegate, final File authDir) { myDelegate = delegate; myAuthDir = authDir; - myRewritePreventer = new SoftHashMap(); } public void setProject(Project project) { @@ -89,9 +127,11 @@ public class SvnAuthenticationManager extends DefaultSVNAuthenticationManager { } public SVNAuthentication requestClientAuthentication(final String kind, final SVNURL url, final String realm, final SVNErrorMessage errorMessage, - final SVNAuthentication previousAuth, - final boolean authMayBeStored) { - return myDelegate.requestClientAuthentication(kind, url, realm, errorMessage, previousAuth, false); + final SVNAuthentication previousAuth, final boolean authMayBeStored) { + final SVNAuthentication svnAuthentication = + myDelegate.requestClientAuthentication(kind, url, realm, errorMessage, previousAuth, authMayBeStored); + myListener.getMulticaster().requested(ProviderType.persistent, url, realm, kind, svnAuthentication == null); + return svnAuthentication; } public int acceptServerAuthentication(final SVNURL url, final String realm, final Object certificate, final boolean resultMayBeStored) { @@ -99,44 +139,37 @@ public class SvnAuthenticationManager extends DefaultSVNAuthenticationManager { } public void saveAuthentication(final SVNAuthentication auth, final String kind, final String realm) throws SVNException { - try { - final SvnAuthWrapperEqualable newKey = new SvnAuthWrapperEqualable(auth); - final Long recent = myRewritePreventer.get(newKey); - final long currTime = System.currentTimeMillis(); - File dir = new File(myAuthDir, kind); - String fileName = SVNFileUtil.computeChecksum(realm); - File authFile = new File(dir, fileName); + final Boolean fromInteractive = ourJustEntered.get(); + ourJustEntered.set(null); + if (! Boolean.TRUE.equals(fromInteractive)) { + // not what user entered + return; + } - if ((! authFile.exists()) || recent == null || ((recent != null) && ((currTime - recent.longValue()) > ourRefreshInterval))) { + if ((!auth.isStorageAllowed()) || (USERNAME.equals(kind))) return; - if (auth.isStorageAllowed() && ISVNAuthenticationManager.USERNAME != kind) { - try { - GuiUtils.runOrInvokeAndWait(new Runnable() { - public void run() { - checkContinueSaveCredentials(auth, kind, realm); + saveCredentialsIfAllowed(auth, kind, realm, new Runnable() { + @Override + public void run() { + File dir = new File(myAuthDir, kind); + String fileName = SVNFileUtil.computeChecksum(realm); + File authFile = new File(dir, fileName); + + myListener.getMulticaster().actualSaveWillBeTried(ProviderType.persistent, auth.getURL(), realm, kind); + try { + ((IPersistentAuthenticationProvider) myDelegate).saveAuthentication(auth, kind, realm); + } + catch (SVNException e) { + if (myProject != null) { + ApplicationManager.getApplication().invokeLater(new ChangesViewBalloonProblemNotifier(myProject, + "Problem when storing Subversion credentials: " + e.getMessage(), MessageType.ERROR)); } - }); - } catch (InvocationTargetException e) { - LOG.error(e); - } catch (InterruptedException e) { - LOG.error(e); + } finally { + // do not make password file readonly + setWriteable(authFile); + } } - } - - ((DefaultSVNAuthenticationManager.IPersistentAuthenticationProvider)myDelegate).saveAuthentication(auth, kind, realm); - - // do not make password file readonly - setWriteable(authFile); - - myRewritePreventer.put(newKey, currTime); - } - } - catch (final SVNException e) { - // show notification so that user was aware his credentials were not saved - if (myProject == null) return; - ApplicationManager.getApplication().invokeLater(new ChangesViewBalloonProblemNotifier(myProject, - "Problem when storing Subversion credentials: " + e.getMessage(), MessageType.ERROR)); - } + }); } private final static int maxAttempts = 10; @@ -163,13 +196,6 @@ public class SvnAuthenticationManager extends DefaultSVNAuthenticationManager { } } - public boolean haveStoredCredentials(final String kind, final SVNURL url, final String realm, final SVNErrorMessage errorMessage, - final SVNAuthentication previousAuth) { - final SVNAuthentication result = - myPersistentAuthenticationProviderProxy.requestClientAuthentication(kind, url, realm, errorMessage, previousAuth, false); - return result != null; - } - public ISVNProxyManager getProxyManager(SVNURL url) throws SVNException { // this code taken from default manager (changed for system properties reading) String host = url.getHost(); @@ -358,27 +384,6 @@ public class SvnAuthenticationManager extends DefaultSVNAuthenticationManager { return null; } - private static class SvnAuthWrapperEqualable extends Wrapper { - private SvnAuthWrapperEqualable(SVNAuthentication svnAuthentication) { - super(svnAuthentication); - } - - @Override - public boolean equals(Object obj) { - if (obj == null) return false; - if (this == obj) return true; - if (obj instanceof SvnAuthWrapperEqualable) { - return SvnAuthEquals.equals(this.getT(), ((SvnAuthWrapperEqualable) obj).getT()); - } - return false; - } - - @Override - public int hashCode() { - return SvnAuthEquals.hashCode(getT()); - } - } - private void setPropertyForHost(final String host, final String property, final String value) { final SVNConfigFile userConfig = new SVNConfigFile(new File(myConfigDirectory, "servers")); @@ -418,84 +423,176 @@ public class SvnAuthenticationManager extends DefaultSVNAuthenticationManager { return "yes".equalsIgnoreCase(storeAuthCreds) || "on".equalsIgnoreCase(storeAuthCreds) || "true".equalsIgnoreCase(storeAuthCreds); } - public boolean checkContinueSaveCredentials(final SVNAuthentication auth, final String kind, final String realm) { + public void saveCredentialsIfAllowed(final SVNAuthentication auth, final String kind, final String realm, + final Runnable saveRunnable) { final SVNURL url = auth.getURL(); final String storeCredentials = getConfigFile().getPropertyValue("auth", "store-auth-creds"); if ((Boolean.FALSE.equals(isAuthStorageEnabledMy(url))) || (! isTurned(storeCredentials))) { - ChangesViewBalloonProblemNotifier.showMe(myProject, "Cannot store credentials: forbidden by \"store-auth-creds=no\"", MessageType.ERROR); - return false; + myInteraction.warnOnAuthStorageDisabled(url); + return; } final boolean passwordStorageEnabled = isStorePasswords(url); // check can store if ((! ISVNAuthenticationManager.SSL.equals(kind)) && (! passwordStorageEnabled)) { // but it should be - //userConfig.setPropertyValue("auth", "store-passwords", "yes", true); - ChangesViewBalloonProblemNotifier.showMe(myProject, "Cannot store password: forbidden by \"store-passwords=no\"", MessageType.ERROR); - return false; + myInteraction.warnOnPasswordStorageDisabled(url); + return; } if (ISVNAuthenticationManager.SSL.equals(kind) && (! isStoreSSLClientCertificatePassphrases(url))) { - //setPropertyForHost(url.getHost(), "store-ssl-client-cert-pp", "yes"); - ChangesViewBalloonProblemNotifier.showMe(myProject, "Cannot store passphrase: forbidden by \"store-ssl-client-cert-pp=no\"", MessageType.ERROR); - return false; + myInteraction.warnOnSSLPassphraseStorageDisabled(url); + return; } // check can encrypt if (!(SystemInfo.isWindows && SVNJNAUtil.isWinCryptEnabled())) { - if (ISVNAuthenticationManager.SSL.equals(kind)) { - try { + try { + if (ISVNAuthenticationManager.SSL.equals(kind)) { if (!isStorePlainTextPassphrases(realm, auth)) { - return askToStoreUnencrypted("Store the passphrase in plaintext?", - String.format("Your passphrase for client certificate:\n%s\ncan only be stored to disk unencrypted. Would you like to store it in plaintext?", - ((SVNSSLAuthentication)auth).getCertificateFile().getPath())); + promptAndSaveWhenWeLackEncryption(saveRunnable, + new Getter() { + @Override + public Boolean get() { + return myInteraction.promptForSSLPlaintextPassphraseSaving(url, realm, ((SVNSSLAuthentication)auth).getCertificateFile()); + } + }); + return; } - } catch (SVNException e) { - LOG.error(e); // should not occur, anyway means not allowed - } - } else { - try { + } else { if (!isStorePlainTextPasswords(realm, auth)) { - return askToStoreUnencrypted("Store the password in plaintext?", - String.format("Your password for authentication realm:\n%s\ncan only be stored to disk unencrypted. Would you like to store it in plaintext?", - realm)); + promptAndSaveWhenWeLackEncryption(saveRunnable, + new Getter() { + @Override + public Boolean get() { + return myInteraction.promptForPlaintextPasswordSaving(url, realm); + } + }); + return; } - } catch (SVNException e) { - LOG.error(e); } + } catch (SVNException e) { + LOG.info(e); + return; } } - return true; + saveRunnable.run(); } @Override protected boolean isStorePlainTextPasswords(String realm, SVNAuthentication auth) throws SVNException { - // normally check user preferences, but the user may override them via the dialog from checkContinueSaveCredentials() - return myStoreInPlainTextAnyway.get() || super.isStorePlainTextPasswords(realm, auth); + return myPlainTextAllowed.contains(Thread.currentThread()) || super.isStorePlainTextPasswords(realm, auth); } @Override protected boolean isStorePlainTextPassphrases(String realm, SVNAuthentication auth) throws SVNException { - return myStoreInPlainTextAnyway.get() || super.isStorePlainTextPassphrases(realm, auth); + return myPlainTextAllowed.contains(Thread.currentThread()) || super.isStorePlainTextPassphrases(realm, auth); + } + + private ModalityState getCurrent() { + if (ApplicationManager.getApplication().isDispatchThread()) { + return ModalityState.current(); + } + final ProgressIndicator pi = ProgressManager.getInstance().getProgressIndicator(); + if (pi == null) { + return ModalityState.defaultModalityState(); + } + return pi.getModalityState(); + } + + private void promptAndSaveWhenWeLackEncryption( + final Runnable saveRunnable, + final Getter prompt) { + + final Runnable actualSave = new Runnable() { + @Override + public void run() { + final Thread currentThread = Thread.currentThread(); + myPlainTextAllowed.add(currentThread); + try { + saveRunnable.run(); + } + finally { + myPlainTextAllowed.remove(currentThread); + } + } + }; + + if (myInteraction.promptInAwt()) { + new AbstractCalledLater(myProject, getCurrent()) { + @Override + public void run() { + final boolean saveOnce = Boolean.TRUE.equals(prompt.get()); + if (saveOnce) { + ApplicationManager.getApplication().executeOnPooledThread(actualSave); + } else { + } + } + }.callMe(); + } else { + final boolean saveOnce = Boolean.TRUE.equals(prompt.get()); + if (saveOnce) { + actualSave.run(); + } + } } /** * Shows a yes/no question whether user wants to store his password in plain text and returns his answer. - * Also updates the 'myStoreInPlainTextAnyway' variable correspondingly. - * This method shuld be called from the event dispatching thread. * @param title title of the questioning dialog. * @param message questioning message to be displayed. * @return true if user agrees to store his password in plaintext, false if he doesn't. */ + @CalledInAwt private boolean askToStoreUnencrypted(String title, String message) { final int answer = Messages.showYesNoDialog(myProject, message, title, Messages.getQuestionIcon()); - if (answer == 0) { - myStoreInPlainTextAnyway.set(true); - } else { - myStoreInPlainTextAnyway.set(false); - ChangesViewBalloonProblemNotifier.showMe(myProject, "Cannot store password", MessageType.ERROR); - } - return myStoreInPlainTextAnyway.get(); - + return answer == 0; } -} \ No newline at end of file + public void setInteraction(SvnAuthenticationInteraction interaction) { + myInteraction = interaction; + } + + private static class MySvnAuthenticationInteraction implements SvnAuthenticationInteraction { + private final Project myProject; + + private MySvnAuthenticationInteraction(Project project) { + myProject = project; + } + + @Override + public void warnOnAuthStorageDisabled(SVNURL url) { + ChangesViewBalloonProblemNotifier.showMe(myProject, "Cannot store credentials: forbidden by \"store-auth-creds=no\"", MessageType.ERROR); + } + + @Override + public void warnOnPasswordStorageDisabled(SVNURL url) { + ChangesViewBalloonProblemNotifier.showMe(myProject, "Cannot store password: forbidden by \"store-passwords=no\"", MessageType.ERROR); + } + + @Override + public void warnOnSSLPassphraseStorageDisabled(SVNURL url) { + ChangesViewBalloonProblemNotifier.showMe(myProject, "Cannot store passphrase: forbidden by \"store-ssl-client-cert-pp=no\"", MessageType.ERROR); + } + + @Override + public boolean promptForPlaintextPasswordSaving(SVNURL url, String realm) { + final int answer = Messages.showYesNoDialog(myProject, String.format("Your password for authentication realm:\n" + + "%s\ncan only be stored to disk unencrypted. Would you like to store it in plaintext?", realm), + "Store the password in plaintext?", Messages.getQuestionIcon()); + return answer == 0; + } + + @Override + public boolean promptInAwt() { + return true; + } + + @Override + public boolean promptForSSLPlaintextPassphraseSaving(SVNURL url, String realm, File certificateFile) { + final int answer = Messages.showYesNoDialog(myProject, String.format("Your passphrase for client certificate:\n%s\ncan only be stored to disk unencrypted. Would you like to store it in plaintext?", + certificateFile.getPath()), + "Store the passphrase in plaintext?", Messages.getQuestionIcon()); + return answer == 0; + } + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnConfiguration.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnConfiguration.java index d21e7f7f5bd4..fd822108b8a2 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnConfiguration.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnConfiguration.java @@ -93,6 +93,7 @@ public class SvnConfiguration implements ProjectComponent, JDOMExternalizable { private final Map myMergeRootInfos = new HashMap(); private final Map myUpdateRootInfos = new HashMap(); private final List myAnnotationListeners; + private SvnInteractiveAuthenticationProvider myInteractiveProvider; public static SvnConfiguration getInstance(Project project) { return project.getComponent(SvnConfiguration.class); @@ -200,6 +201,7 @@ public class SvnConfiguration implements ProjectComponent, JDOMExternalizable { myAuthManager = null; myPassiveAuthManager = null; myInteractiveManager = null; + myInteractiveProvider = null; RUNTIME_AUTH_CACHE.clear(); } @@ -209,6 +211,7 @@ public class SvnConfiguration implements ProjectComponent, JDOMExternalizable { myAuthManager = null; myPassiveAuthManager = null; myInteractiveManager = null; + myInteractiveProvider = null; RUNTIME_AUTH_CACHE.clear(); } @@ -224,8 +227,10 @@ public class SvnConfiguration implements ProjectComponent, JDOMExternalizable { if (myAuthManager == null) { // reloaded when configuration directory changes myAuthManager = new SvnAuthenticationManager(myProject, new File(getConfigurationDirectory())); - myAuthManager.setAuthenticationProvider(new SvnAuthenticationProvider(svnVcs, getInteractiveManager(svnVcs))); - myAuthManager.setRuntimeStorage(RUNTIME_AUTH_CACHE); + getInteractiveManager(svnVcs); + // to init + myAuthManager.setAuthenticationProvider(new SvnAuthenticationProvider(svnVcs, myInteractiveProvider)); + myAuthManager.setRuntimeStorage(RUNTIME_AUTH_CACHE); } return myAuthManager; } @@ -242,7 +247,8 @@ public class SvnConfiguration implements ProjectComponent, JDOMExternalizable { if (myInteractiveManager == null) { myInteractiveManager = new SvnAuthenticationManager(myProject, new File(getConfigurationDirectory())); myInteractiveManager.setRuntimeStorage(RUNTIME_AUTH_CACHE); - myInteractiveManager.setAuthenticationProvider(new SvnInteractiveAuthenticationProvider(svnVcs, myInteractiveManager)); + myInteractiveProvider = new SvnInteractiveAuthenticationProvider(svnVcs, myInteractiveManager); + myInteractiveManager.setAuthenticationProvider(myInteractiveProvider); } return myInteractiveManager; } diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/auth/ProviderType.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/auth/ProviderType.java new file mode 100644 index 000000000000..c0cedda1fda4 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/auth/ProviderType.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 org.jetbrains.idea.svn.auth; + +public enum ProviderType { + dumb, + interactive, + persistent, + memory_cache +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/auth/SvnAuthenticationInteraction.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/auth/SvnAuthenticationInteraction.java new file mode 100644 index 000000000000..a43f6fad829d --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/auth/SvnAuthenticationInteraction.java @@ -0,0 +1,29 @@ +/* + * 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 org.jetbrains.idea.svn.auth; + +import org.tmatesoft.svn.core.SVNURL; + +import java.io.File; + +public interface SvnAuthenticationInteraction { + void warnOnAuthStorageDisabled(final SVNURL url); + void warnOnPasswordStorageDisabled(final SVNURL url); + void warnOnSSLPassphraseStorageDisabled(final SVNURL url); + boolean promptForSSLPlaintextPassphraseSaving(final SVNURL url, String realm, File certificateFile); + boolean promptForPlaintextPasswordSaving(final SVNURL url, String realm); + boolean promptInAwt(); +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/auth/SvnAuthenticationListener.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/auth/SvnAuthenticationListener.java new file mode 100644 index 000000000000..b8975b19d3c9 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/auth/SvnAuthenticationListener.java @@ -0,0 +1,25 @@ +/* + * 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 org.jetbrains.idea.svn.auth; + +import org.tmatesoft.svn.core.SVNURL; + +import java.util.EventListener; + +public interface SvnAuthenticationListener extends EventListener { + void requested(final ProviderType type, final SVNURL url, String realm, String kind, boolean canceled); + void actualSaveWillBeTried(final ProviderType type, final SVNURL url, String realm, String kind); +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/SvnAuthenticationProvider.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/SvnAuthenticationProvider.java index 850d8f524f46..51470af6826e 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/SvnAuthenticationProvider.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/SvnAuthenticationProvider.java @@ -20,7 +20,6 @@ import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.project.Project; import com.intellij.ui.GuiUtils; import com.intellij.util.SystemProperties; -import org.jetbrains.idea.svn.SvnAuthenticationManager; import org.jetbrains.idea.svn.SvnAuthenticationNotifier; import org.jetbrains.idea.svn.SvnVcs; import org.tmatesoft.svn.core.SVNErrorMessage; @@ -40,12 +39,12 @@ public class SvnAuthenticationProvider implements ISVNAuthenticationProvider { private static final Logger LOG = Logger.getInstance("#org.jetbrains.idea.svn.dialogs.SvnAuthenticationProvider"); private final Project myProject; private final SvnAuthenticationNotifier myAuthenticationNotifier; - private final SvnInteractiveAuthenticationProvider mySvnInteractiveAuthenticationProvider; + private final ISVNAuthenticationProvider mySvnInteractiveAuthenticationProvider; - public SvnAuthenticationProvider(final SvnVcs svnVcs, final SvnAuthenticationManager manager) { + public SvnAuthenticationProvider(final SvnVcs svnVcs, final ISVNAuthenticationProvider provider) { myProject = svnVcs.getProject(); myAuthenticationNotifier = svnVcs.getAuthNotifier(); - mySvnInteractiveAuthenticationProvider = new SvnInteractiveAuthenticationProvider(svnVcs, manager); + mySvnInteractiveAuthenticationProvider = provider; } private void log(final String s) { diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/SvnInteractiveAuthenticationProvider.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/SvnInteractiveAuthenticationProvider.java index e26528930dda..376bcc49b037 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/SvnInteractiveAuthenticationProvider.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/SvnInteractiveAuthenticationProvider.java @@ -24,6 +24,7 @@ import org.jetbrains.idea.svn.SvnAuthenticationManager; import org.jetbrains.idea.svn.SvnBundle; import org.jetbrains.idea.svn.SvnConfiguration; import org.jetbrains.idea.svn.SvnVcs; +import org.jetbrains.idea.svn.auth.ProviderType; import org.tmatesoft.svn.core.SVNErrorMessage; import org.tmatesoft.svn.core.SVNURL; import org.tmatesoft.svn.core.auth.*; @@ -35,11 +36,9 @@ public class SvnInteractiveAuthenticationProvider implements ISVNAuthenticationP private static final Logger LOG = Logger.getInstance("#org.jetbrains.idea.svn.dialogs.SvnInteractiveAuthenticationProvider"); private final Project myProject; private static final ThreadLocal myCallState = new ThreadLocal(); - private final SvnVcs myVcs; private final SvnAuthenticationManager myManager; public SvnInteractiveAuthenticationProvider(final SvnVcs vcs, SvnAuthenticationManager manager) { - myVcs = vcs; myManager = manager; myProject = vcs.getProject(); } @@ -176,6 +175,9 @@ public class SvnInteractiveAuthenticationProvider implements ISVNAuthenticationP final boolean wasCanceled = result[0] == null; callState.setWasCancelled(wasCanceled); + if (! ISVNAuthenticationManager.USERNAME.equals(kind)) { + myManager.requested(ProviderType.interactive, url, realm, kind, wasCanceled); + } return result[0]; } diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/browserCache/RepositoryLoader.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/browserCache/RepositoryLoader.java index a40ae967c14a..68da5c966a0a 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/browserCache/RepositoryLoader.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/browserCache/RepositoryLoader.java @@ -16,6 +16,9 @@ package org.jetbrains.idea.svn.dialogs.browserCache; import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.application.ModalityState; +import com.intellij.openapi.progress.EmptyProgressIndicator; +import com.intellij.openapi.progress.ProgressManager; import com.intellij.openapi.util.Pair; import org.jetbrains.idea.svn.SvnVcs; import org.jetbrains.idea.svn.dialogs.RepositoryTreeNode; @@ -45,7 +48,7 @@ class RepositoryLoader extends Loader { final Pair data = new Pair(node, afterRefreshExpander); if (! myQueueProcessorActive) { - ApplicationManager.getApplication().executeOnPooledThread(new LoadTask(data)); + startLoadTask(data); myQueueProcessorActive = true; } else { myLoadQueue.offer(data); @@ -74,10 +77,25 @@ class RepositoryLoader extends Loader { // ignore if node is already disposed startNext(); } else { - ApplicationManager.getApplication().executeOnPooledThread(new LoadTask(data)); + startLoadTask(data); } } + private void startLoadTask(final Pair data) { + final ModalityState state = ModalityState.current(); + ApplicationManager.getApplication().executeOnPooledThread(new Runnable() { + @Override + public void run() { + ProgressManager.getInstance().runProcess(new LoadTask(data), new EmptyProgressIndicator() { + @Override + public ModalityState getModalityState() { + return state; + } + }); + } + }); + } + public void forceRefresh(final String repositoryRootUrl) { // ? remove } diff --git a/plugins/svn4idea/testSource/org/jetbrains/idea/svn/SvnAuthenticationTest.java b/plugins/svn4idea/testSource/org/jetbrains/idea/svn/SvnAuthenticationTest.java new file mode 100644 index 000000000000..93e4ff3716f0 --- /dev/null +++ b/plugins/svn4idea/testSource/org/jetbrains/idea/svn/SvnAuthenticationTest.java @@ -0,0 +1,1039 @@ +/* + * 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 org.jetbrains.idea.svn; + +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.util.Pair; +import com.intellij.openapi.util.Trinity; +import com.intellij.openapi.util.io.FileUtil; +import com.intellij.testFramework.PlatformTestCase; +import com.intellij.util.concurrency.Semaphore; +import junit.framework.Assert; +import org.jetbrains.idea.svn.auth.ProviderType; +import org.jetbrains.idea.svn.auth.SvnAuthenticationInteraction; +import org.jetbrains.idea.svn.auth.SvnAuthenticationListener; +import org.jetbrains.idea.svn.dialogs.SvnAuthenticationProvider; +import org.tmatesoft.svn.core.SVNErrorMessage; +import org.tmatesoft.svn.core.SVNException; +import org.tmatesoft.svn.core.SVNURL; +import org.tmatesoft.svn.core.auth.*; +import org.tmatesoft.svn.core.internal.util.jna.SVNJNAUtil; +import org.tmatesoft.svn.core.internal.wc.SVNConfigFile; + +import java.io.File; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +public class SvnAuthenticationTest extends PlatformTestCase { + private SvnAuthenticationManager myAuthenticationManager; + private TestInteraction myTestInteraction; + private SvnVcs myVcs; + private final Object mySynchObject = new Object(); + private SvnTestInteractiveAuthentication myInteractiveProvider; + private SvnConfiguration myConfiguration; + + @Override + protected void setUp() throws Exception { + super.setUp(); + + myConfiguration = SvnConfiguration.getInstance(myProject); + final String configPath = myProject.getBaseDir().getPath() + File.separator + "Subversion"; + myConfiguration.setConfigurationDirectory(configPath); + + final File configFile = new File(configPath); + myFilesToDelete.add(configFile); + + myVcs = SvnVcs.getInstance(myProject); + + myAuthenticationManager = new SvnAuthenticationManager(myProject, configFile); + + myInteractiveProvider = new SvnTestInteractiveAuthentication(myAuthenticationManager); + myAuthenticationManager.setAuthenticationProvider(new SvnAuthenticationProvider(myVcs, myInteractiveProvider)); + myAuthenticationManager.setRuntimeStorage(SvnConfiguration.RUNTIME_AUTH_CACHE); + + myTestInteraction = new TestInteraction(); + myAuthenticationManager.setInteraction(myTestInteraction); + + SVNConfigFile.createDefaultConfiguration(configFile); + } + + public void testSavedAndRead() throws Exception { + final TestListener listener = new TestListener(mySynchObject); + myAuthenticationManager.addListener(listener); + final SavedOnceListener savedOnceListener = new SavedOnceListener(); + myAuthenticationManager.addListener(savedOnceListener); + + final SVNURL url = SVNURL.parseURIEncoded("http://some.host.com/repo"); + + final SVNException[] exception = new SVNException[1]; + final Boolean[] result = new Boolean[1]; + synchronousBackground(new Runnable() { + @Override + public void run() { + try { + + listener.addStep(new Trinity(ProviderType.persistent, url, Type.request)); + listener.addStep(new Trinity(ProviderType.interactive, url, Type.request)); + listener.addStep(new Trinity(ProviderType.persistent, url, Type.save)); + + commonScheme(url, false, null); + Assert.assertEquals(3, listener.getCnt()); + //long start = System.currentTimeMillis(); + //waitListenerStep(start, listener, 3); + + SvnConfiguration.RUNTIME_AUTH_CACHE.clear(); + listener.addStep(new Trinity(ProviderType.persistent, url, Type.request)); + commonScheme(url, false, null); + //start = System.currentTimeMillis(); + //waitListenerStep(start, listener, 4); + Assert.assertEquals(4, listener.getCnt()); + } + catch (SVNException e) { + exception[0] = e; + } + result[0] = true; + } + }); + + Assert.assertTrue(result[0]); + myTestInteraction.assertNothing(); + Assert.assertEquals(4, listener.getCnt()); + listener.assertForAwt(); + savedOnceListener.assertForAwt(); + savedOnceListener.assertSaved(url, ISVNAuthenticationManager.PASSWORD); + + if (exception[0] != null) { + throw exception[0]; + } + } + + private void waitListenerStep(long start, TestListener listener, final int stepNoNext) { + while ((listener.getCnt() < stepNoNext) && ((System.currentTimeMillis() - start) < 10000)) { + synchronized (mySynchObject) { + try { + mySynchObject.wait(50); + } + catch (InterruptedException e) { + // + } + } + } + Assert.assertEquals(stepNoNext, listener.getCnt()); + } + + public void testWhenNotSaved() throws Exception { + final TestListener listener = new TestListener(mySynchObject); + myAuthenticationManager.addListener(listener); + final SavedOnceListener savedOnceListener = new SavedOnceListener(); + myAuthenticationManager.addListener(savedOnceListener); + + myInteractiveProvider.setSaveData(false); + + final SVNURL url = SVNURL.parseURIEncoded("http://some.host.com/repo"); + + final SVNException[] exception = new SVNException[1]; + final Boolean[] result = new Boolean[1]; + synchronousBackground(new Runnable() { + @Override + public void run() { + try { + listener.addStep(new Trinity(ProviderType.persistent, url, Type.request)); + listener.addStep(new Trinity(ProviderType.interactive, url, Type.request)); + + commonScheme(url, false, null); + //long start = System.currentTimeMillis(); + //waitListenerStep(start, listener, 2); + Assert.assertEquals(2, listener.getCnt()); + + savedOnceListener.assertNotSaved(url, ISVNAuthenticationManager.PASSWORD); + // cause is not cleared though + savedOnceListener.reset(); + + SvnConfiguration.RUNTIME_AUTH_CACHE.clear(); + listener.addStep(new Trinity(ProviderType.persistent, url, Type.request)); + listener.addStep(new Trinity(ProviderType.interactive, url, Type.request)); + commonScheme(url, false, null); + Assert.assertEquals(4, listener.getCnt()); + //start = System.currentTimeMillis(); + //waitListenerStep(start, listener, 4); + } + catch (SVNException e) { + exception[0] = e; + } + result[0] = true; + } + }); + + Assert.assertTrue(result[0]); + myTestInteraction.assertNothing(); + Assert.assertEquals(4, listener.getCnt()); + listener.assertForAwt(); + savedOnceListener.assertForAwt(); + savedOnceListener.assertNotSaved(url, ISVNAuthenticationManager.PASSWORD); + + if (exception[0] != null) { + throw exception[0]; + } + } + + public void testWhenAuthCredsNoInConfig() throws Exception { + final TestListener listener = new TestListener(mySynchObject); + myAuthenticationManager.addListener(listener); + final SavedOnceListener savedOnceListener = new SavedOnceListener(); + myAuthenticationManager.addListener(savedOnceListener); + + final File config = new File(myConfiguration.getConfigurationDirectory(), "config"); + final char[] chars = FileUtil.loadFileText(config); + final String contents = String.valueOf(chars); + final String auth = "[auth]"; + final int idx = contents.indexOf(auth); + Assert.assertTrue(idx != -1); + final String newContents = contents.substring(0, idx + auth.length()) + "\nstore-auth-creds=no\n" + contents.substring(idx + auth.length()); + + final File oldConfig = new File(myConfiguration.getConfigurationDirectory(), "config_old"); + FileUtil.rename(config, oldConfig); + try { + config.createNewFile(); + FileUtil.appendToFile(config, newContents); + + final SVNURL url = SVNURL.parseURIEncoded("http://some.host.com/repo"); + + final SVNException[] exception = new SVNException[1]; + final Boolean[] result = new Boolean[1]; + synchronousBackground(new Runnable() { + @Override + public void run() { + try { + listener.addStep(new Trinity(ProviderType.persistent, url, Type.request)); + listener.addStep(new Trinity(ProviderType.interactive, url, Type.request)); + + commonScheme(url, false, null); + Assert.assertEquals(2, listener.getCnt()); + Assert.assertEquals(1, myTestInteraction.getNumAuthWarn()); + myTestInteraction.reset(); + savedOnceListener.assertForAwt(); + savedOnceListener.reset(); + + SvnConfiguration.RUNTIME_AUTH_CACHE.clear(); + listener.addStep(new Trinity(ProviderType.persistent, url, Type.request)); + listener.addStep(new Trinity(ProviderType.interactive, url, Type.request)); + commonScheme(url, false, null); + Assert.assertEquals(4, listener.getCnt()); + Assert.assertEquals(1, myTestInteraction.getNumAuthWarn()); + } + catch (SVNException e) { + exception[0] = e; + } + result[0] = true; + } + }); + + Assert.assertTrue(result[0]); + Assert.assertEquals(1, myTestInteraction.getNumAuthWarn()); + Assert.assertEquals(4, listener.getCnt()); + listener.assertForAwt(); + savedOnceListener.assertForAwt(); + savedOnceListener.assertNotSaved(url, ISVNAuthenticationManager.PASSWORD); + + if (exception[0] != null) { + throw exception[0]; + } + } finally { + FileUtil.delete(config); + FileUtil.rename(oldConfig, config); + } + } + + public void testWhenAuthCredsNoInServers() throws Exception { + final TestListener listener = new TestListener(mySynchObject); + myAuthenticationManager.addListener(listener); + final SavedOnceListener savedOnceListener = new SavedOnceListener(); + myAuthenticationManager.addListener(savedOnceListener); + + final File servers = new File(myConfiguration.getConfigurationDirectory(), "servers"); + + final File oldServers = new File(myConfiguration.getConfigurationDirectory(), "config_old"); + FileUtil.copy(servers, oldServers); + try { + FileUtil.appendToFile(servers, "\nstore-auth-creds=no\n"); + + final SVNURL url = SVNURL.parseURIEncoded("http://some.host.com/repo"); + + final SVNException[] exception = new SVNException[1]; + final Boolean[] result = new Boolean[1]; + synchronousBackground(new Runnable() { + @Override + public void run() { + try { + listener.addStep(new Trinity(ProviderType.persistent, url, Type.request)); + listener.addStep(new Trinity(ProviderType.interactive, url, Type.request)); + + commonScheme(url, false, null); + Assert.assertEquals(2, listener.getCnt()); + Assert.assertEquals(1, myTestInteraction.getNumAuthWarn()); + myTestInteraction.reset(); + savedOnceListener.assertForAwt(); + savedOnceListener.reset(); + + SvnConfiguration.RUNTIME_AUTH_CACHE.clear(); + listener.addStep(new Trinity(ProviderType.persistent, url, Type.request)); + listener.addStep(new Trinity(ProviderType.interactive, url, Type.request)); + commonScheme(url, false, null); + Assert.assertEquals(4, listener.getCnt()); + Assert.assertEquals(1, myTestInteraction.getNumAuthWarn()); + } + catch (SVNException e) { + exception[0] = e; + } + result[0] = true; + } + }); + + Assert.assertTrue(result[0]); + Assert.assertEquals(1, myTestInteraction.getNumAuthWarn()); + Assert.assertEquals(4, listener.getCnt()); + listener.assertForAwt(); + savedOnceListener.assertForAwt(); + savedOnceListener.assertNotSaved(url, ISVNAuthenticationManager.PASSWORD); + + if (exception[0] != null) { + throw exception[0]; + } + } finally { + FileUtil.delete(servers); + FileUtil.rename(oldServers, servers); + } + } + + public void testWhenPassSaveNoInConfig() throws Exception { + final TestListener listener = new TestListener(mySynchObject); + myAuthenticationManager.addListener(listener); + final SavedOnceListener savedOnceListener = new SavedOnceListener(); + myAuthenticationManager.addListener(savedOnceListener); + + final File config = new File(myConfiguration.getConfigurationDirectory(), "config"); + final char[] chars = FileUtil.loadFileText(config); + final String contents = String.valueOf(chars); + final String auth = "[auth]"; + final int idx = contents.indexOf(auth); + Assert.assertTrue(idx != -1); + final String newContents = contents.substring(0, idx + auth.length()) + "\nstore-passwords=no\n" + contents.substring(idx + auth.length()); + + final File oldConfig = new File(myConfiguration.getConfigurationDirectory(), "config_old"); + FileUtil.rename(config, oldConfig); + try { + config.createNewFile(); + FileUtil.appendToFile(config, newContents); + + final SVNURL url = SVNURL.parseURIEncoded("http://some.host.com/repo"); + + final SVNException[] exception = new SVNException[1]; + final Boolean[] result = new Boolean[1]; + synchronousBackground(new Runnable() { + @Override + public void run() { + try { + listener.addStep(new Trinity(ProviderType.persistent, url, Type.request)); + listener.addStep(new Trinity(ProviderType.interactive, url, Type.request)); + + commonScheme(url, false, null); + Assert.assertEquals(2, listener.getCnt()); + Assert.assertEquals(1, myTestInteraction.getNumPasswordsWarn()); + myTestInteraction.reset(); + savedOnceListener.assertForAwt(); + savedOnceListener.reset(); + + SvnConfiguration.RUNTIME_AUTH_CACHE.clear(); + listener.addStep(new Trinity(ProviderType.persistent, url, Type.request)); + listener.addStep(new Trinity(ProviderType.interactive, url, Type.request)); + commonScheme(url, false, null); + Assert.assertEquals(4, listener.getCnt()); + Assert.assertEquals(1, myTestInteraction.getNumPasswordsWarn()); + } + catch (SVNException e) { + exception[0] = e; + } + result[0] = true; + } + }); + + Assert.assertTrue(result[0]); + Assert.assertEquals(1, myTestInteraction.getNumPasswordsWarn()); + Assert.assertEquals(4, listener.getCnt()); + listener.assertForAwt(); + savedOnceListener.assertForAwt(); + savedOnceListener.assertNotSaved(url, ISVNAuthenticationManager.PASSWORD); + + if (exception[0] != null) { + throw exception[0]; + } + } finally { + FileUtil.delete(config); + FileUtil.rename(oldConfig, config); + } + } + + public void testWhenPassSaveNoInServers() throws Exception { + final TestListener listener = new TestListener(mySynchObject); + myAuthenticationManager.addListener(listener); + final SavedOnceListener savedOnceListener = new SavedOnceListener(); + myAuthenticationManager.addListener(savedOnceListener); + + final File servers = new File(myConfiguration.getConfigurationDirectory(), "servers"); + + final File oldServers = new File(myConfiguration.getConfigurationDirectory(), "config_old"); + FileUtil.copy(servers, oldServers); + try { + FileUtil.appendToFile(servers, "\nstore-passwords=no\n"); + + final SVNURL url = SVNURL.parseURIEncoded("http://some.host.com/repo"); + + final SVNException[] exception = new SVNException[1]; + final Boolean[] result = new Boolean[1]; + synchronousBackground(new Runnable() { + @Override + public void run() { + try { + listener.addStep(new Trinity(ProviderType.persistent, url, Type.request)); + listener.addStep(new Trinity(ProviderType.interactive, url, Type.request)); + + commonScheme(url, false, null); + Assert.assertEquals(2, listener.getCnt()); + Assert.assertEquals(1, myTestInteraction.getNumPasswordsWarn()); + myTestInteraction.reset(); + savedOnceListener.assertForAwt(); + savedOnceListener.reset(); + + SvnConfiguration.RUNTIME_AUTH_CACHE.clear(); + listener.addStep(new Trinity(ProviderType.persistent, url, Type.request)); + listener.addStep(new Trinity(ProviderType.interactive, url, Type.request)); + commonScheme(url, false, null); + Assert.assertEquals(4, listener.getCnt()); + Assert.assertEquals(1, myTestInteraction.getNumPasswordsWarn()); + } + catch (SVNException e) { + exception[0] = e; + } + result[0] = true; + } + }); + + Assert.assertTrue(result[0]); + Assert.assertEquals(1, myTestInteraction.getNumPasswordsWarn()); + Assert.assertEquals(4, listener.getCnt()); + listener.assertForAwt(); + savedOnceListener.assertForAwt(); + savedOnceListener.assertNotSaved(url, ISVNAuthenticationManager.PASSWORD); + + if (exception[0] != null) { + throw exception[0]; + } + } finally { + FileUtil.delete(servers); + FileUtil.rename(oldServers, servers); + } + } + + public void testWhenPassSaveNoForGroup() throws Exception { + final TestListener listener = new TestListener(mySynchObject); + myAuthenticationManager.addListener(listener); + final SavedOnceListener savedOnceListener = new SavedOnceListener(); + myAuthenticationManager.addListener(savedOnceListener); + + final File servers = new File(myConfiguration.getConfigurationDirectory(), "servers"); + final char[] chars = FileUtil.loadFileText(servers); + final String contents = String.valueOf(chars); + final String groups = "[groups]"; + final int idx = contents.indexOf(groups); + Assert.assertTrue(idx != -1); + final String newContents = contents.substring(0, idx + groups.length()) + "\nsomegroup=some*\n" + contents.substring(idx + groups.length()) + + "\n[somegroup]\nstore-passwords=no\n"; + + final File oldServers = new File(myConfiguration.getConfigurationDirectory(), "config_old"); + FileUtil.rename(servers, oldServers); + try { + servers.createNewFile(); + FileUtil.appendToFile(servers, newContents); + + final SVNURL url = SVNURL.parseURIEncoded("http://some.host.com/repo"); + + final SVNException[] exception = new SVNException[1]; + final Boolean[] result = new Boolean[1]; + synchronousBackground(new Runnable() { + @Override + public void run() { + try { + listener.addStep(new Trinity(ProviderType.persistent, url, Type.request)); + listener.addStep(new Trinity(ProviderType.interactive, url, Type.request)); + + commonScheme(url, false, null); + Assert.assertEquals(2, listener.getCnt()); + Assert.assertEquals(1, myTestInteraction.getNumPasswordsWarn()); + myTestInteraction.reset(); + savedOnceListener.assertForAwt(); + savedOnceListener.reset(); + + SvnConfiguration.RUNTIME_AUTH_CACHE.clear(); + listener.addStep(new Trinity(ProviderType.persistent, url, Type.request)); + listener.addStep(new Trinity(ProviderType.interactive, url, Type.request)); + commonScheme(url, false, null); + Assert.assertEquals(4, listener.getCnt()); + Assert.assertEquals(1, myTestInteraction.getNumPasswordsWarn()); + } + catch (SVNException e) { + exception[0] = e; + } + result[0] = true; + } + }); + + Assert.assertTrue(result[0]); + Assert.assertEquals(1, myTestInteraction.getNumPasswordsWarn()); + Assert.assertEquals(4, listener.getCnt()); + listener.assertForAwt(); + savedOnceListener.assertForAwt(); + savedOnceListener.assertNotSaved(url, ISVNAuthenticationManager.PASSWORD); + + if (exception[0] != null) { + throw exception[0]; + } + } finally { + FileUtil.delete(servers); + FileUtil.rename(oldServers, servers); + } + } + + public void testWhenPassPhraseSaveNo() throws Exception { + final TestListener listener = new TestListener(mySynchObject); + myAuthenticationManager.addListener(listener); + final SavedOnceListener savedOnceListener = new SavedOnceListener(); + myAuthenticationManager.addListener(savedOnceListener); + + final File servers = new File(myConfiguration.getConfigurationDirectory(), "servers"); + + final File oldServers = new File(myConfiguration.getConfigurationDirectory(), "config_old"); + FileUtil.copy(servers, oldServers); + try { + FileUtil.appendToFile(servers, "\nstore-ssl-client-cert-pp=no\n"); + + final SVNURL url = SVNURL.parseURIEncoded("https://some.host.com/repo"); + + final SVNException[] exception = new SVNException[1]; + final Boolean[] result = new Boolean[1]; + synchronousBackground(new Runnable() { + @Override + public void run() { + try { + listener.addStep(new Trinity(ProviderType.persistent, url, Type.request)); + listener.addStep(new Trinity(ProviderType.interactive, url, Type.request)); + + commonScheme(url, false, null); + Assert.assertEquals(2, listener.getCnt()); + Assert.assertEquals(1, myTestInteraction.getNumSSLWarn()); + myTestInteraction.reset(); + savedOnceListener.assertForAwt(); + savedOnceListener.reset(); + + SvnConfiguration.RUNTIME_AUTH_CACHE.clear(); + listener.addStep(new Trinity(ProviderType.persistent, url, Type.request)); + listener.addStep(new Trinity(ProviderType.interactive, url, Type.request)); + commonScheme(url, false, null); + Assert.assertEquals(4, listener.getCnt()); + Assert.assertEquals(1, myTestInteraction.getNumSSLWarn()); + } + catch (SVNException e) { + exception[0] = e; + } + result[0] = true; + } + }); + + Assert.assertTrue(result[0]); + Assert.assertEquals(1, myTestInteraction.getNumSSLWarn()); + Assert.assertEquals(4, listener.getCnt()); + listener.assertForAwt(); + savedOnceListener.assertForAwt(); + savedOnceListener.assertNotSaved(url, ISVNAuthenticationManager.PASSWORD); + + if (exception[0] != null) { + throw exception[0]; + } + } finally { + FileUtil.delete(servers); + FileUtil.rename(oldServers, servers); + } + } + + public void testPlaintextPrompt() throws Exception { + SVNJNAUtil.setJNAEnabled(false); + + // yes, no + final TestListener listener = new TestListener(mySynchObject); + myAuthenticationManager.addListener(listener); + final SavedOnceListener savedOnceListener = new SavedOnceListener(); + myAuthenticationManager.addListener(savedOnceListener); + myTestInteraction.setPlaintextAnswer(true); + + final SVNURL url = SVNURL.parseURIEncoded("http://some.host.com/repo"); + + final SVNException[] exception = new SVNException[1]; + final Boolean[] result = new Boolean[1]; + synchronousBackground(new Runnable() { + @Override + public void run() { + try { + listener.addStep(new Trinity(ProviderType.persistent, url, Type.request)); + listener.addStep(new Trinity(ProviderType.interactive, url, Type.request)); + listener.addStep(new Trinity(ProviderType.persistent, url, Type.save)); + + commonScheme(url, false, null); + long start = System.currentTimeMillis(); + waitListenerStep(start, listener, 3); + Assert.assertEquals(1, myTestInteraction.getNumPlaintextPrompt()); + savedOnceListener.assertSaved(url, ISVNAuthenticationManager.PASSWORD); + savedOnceListener.reset(); + myTestInteraction.reset(); + + myConfiguration.clearAuthenticationDirectory(); + myTestInteraction.setPlaintextAnswer(false); + SvnConfiguration.RUNTIME_AUTH_CACHE.clear(); + + listener.addStep(new Trinity(ProviderType.persistent, url, Type.request)); + listener.addStep(new Trinity(ProviderType.interactive, url, Type.request)); + commonScheme(url, false, null); + start = System.currentTimeMillis(); + waitListenerStep(start, listener, 5); + Assert.assertEquals(1, myTestInteraction.getNumPlaintextPrompt()); + } + catch (SVNException e) { + exception[0] = e; + } + result[0] = true; + } + }); + + Assert.assertTrue(result[0]); + Assert.assertEquals(1, myTestInteraction.getNumPlaintextPrompt()); + Assert.assertEquals(5, listener.getCnt()); + listener.assertForAwt(); + savedOnceListener.assertForAwt(); + savedOnceListener.assertNotSaved(url, ISVNAuthenticationManager.PASSWORD); + + if (exception[0] != null) { + throw exception[0]; + } + SVNJNAUtil.setJNAEnabled(true); + } + + public void testPlaintextPromptAndSecondPrompt() throws Exception { + SVNJNAUtil.setJNAEnabled(false); + + // yes, no + final TestListener listener = new TestListener(mySynchObject); + myAuthenticationManager.addListener(listener); + final SavedOnceListener savedOnceListener = new SavedOnceListener(); + myAuthenticationManager.addListener(savedOnceListener); + myTestInteraction.setPlaintextAnswer(false); + + final SVNURL url = SVNURL.parseURIEncoded("http://some.host.com/repo"); + final SVNURL url2 = SVNURL.parseURIEncoded("http://some.other.host.com/repo"); + + final SVNException[] exception = new SVNException[1]; + final Boolean[] result = new Boolean[1]; + synchronousBackground(new Runnable() { + @Override + public void run() { + try { + listener.addStep(new Trinity(ProviderType.persistent, url, Type.request)); + listener.addStep(new Trinity(ProviderType.interactive, url, Type.request)); + + commonScheme(url, false, null); + long start = System.currentTimeMillis(); + waitListenerStep(start, listener, 2); + Assert.assertEquals(1, myTestInteraction.getNumPlaintextPrompt()); + savedOnceListener.assertNotSaved(url, ISVNAuthenticationManager.PASSWORD); + savedOnceListener.reset(); + myTestInteraction.reset(); + + listener.addStep(new Trinity(ProviderType.persistent, url2, Type.request)); + listener.addStep(new Trinity(ProviderType.interactive, url2, Type.request)); + commonScheme(url2, false, "anotherRealm"); + start = System.currentTimeMillis(); + waitListenerStep(start, listener, 4); + Assert.assertEquals(1, myTestInteraction.getNumPlaintextPrompt()); + } + catch (SVNException e) { + exception[0] = e; + } + result[0] = true; + } + }); + + Assert.assertTrue(result[0]); + Assert.assertEquals(1, myTestInteraction.getNumPlaintextPrompt()); + Assert.assertEquals(4, listener.getCnt()); + listener.assertForAwt(); + savedOnceListener.assertForAwt(); + savedOnceListener.assertNotSaved(url, ISVNAuthenticationManager.PASSWORD); + + if (exception[0] != null) { + throw exception[0]; + } + SVNJNAUtil.setJNAEnabled(true); + } + + public void testPlaintextSSLPrompt() throws Exception { + SVNJNAUtil.setJNAEnabled(false); + + // yes, no + final TestListener listener = new TestListener(mySynchObject); + myAuthenticationManager.addListener(listener); + final SavedOnceListener savedOnceListener = new SavedOnceListener(); + myAuthenticationManager.addListener(savedOnceListener); + myTestInteraction.setSSLPlaintextAnswer(true); + + final SVNURL url = SVNURL.parseURIEncoded("https://some.host.com/repo"); + + final SVNException[] exception = new SVNException[1]; + final Boolean[] result = new Boolean[1]; + synchronousBackground(new Runnable() { + @Override + public void run() { + try { + listener.addStep(new Trinity(ProviderType.persistent, url, Type.request)); + listener.addStep(new Trinity(ProviderType.interactive, url, Type.request)); + listener.addStep(new Trinity(ProviderType.persistent, url, Type.save)); + + commonScheme(url, false, null); + long start = System.currentTimeMillis(); + waitListenerStep(start, listener, 3); + Assert.assertEquals(1, myTestInteraction.getNumSSLPlaintextPrompt()); + savedOnceListener.assertSaved(url, ISVNAuthenticationManager.SSL); + savedOnceListener.reset(); + myTestInteraction.reset(); + + myConfiguration.clearAuthenticationDirectory(); + myTestInteraction.setSSLPlaintextAnswer(false); + SvnConfiguration.RUNTIME_AUTH_CACHE.clear(); + + listener.addStep(new Trinity(ProviderType.persistent, url, Type.request)); + listener.addStep(new Trinity(ProviderType.interactive, url, Type.request)); + commonScheme(url, false, null); + start = System.currentTimeMillis(); + waitListenerStep(start, listener, 5); + Assert.assertEquals(1, myTestInteraction.getNumSSLPlaintextPrompt()); + } + catch (SVNException e) { + exception[0] = e; + } + result[0] = true; + } + }); + + Assert.assertTrue(result[0]); + Assert.assertEquals(1, myTestInteraction.getNumSSLPlaintextPrompt()); + Assert.assertEquals(5, listener.getCnt()); + listener.assertForAwt(); + savedOnceListener.assertForAwt(); + savedOnceListener.assertNotSaved(url, ISVNAuthenticationManager.SSL); + + if (exception[0] != null) { + throw exception[0]; + } + SVNJNAUtil.setJNAEnabled(true); + } + + private void synchronousBackground(final Runnable runnable) throws InterruptedException { + final Semaphore semaphore = new Semaphore(); + semaphore.down(); + ApplicationManager.getApplication().executeOnPooledThread(new Runnable() { + @Override + public void run() { + try { + runnable.run(); + } finally { + semaphore.up(); + } + } + }); + semaphore.waitFor(); + } + + private void commonScheme(final SVNURL url, final boolean username, final String realm) throws SVNException { + String kind = null; + + final String actualRealm = realm == null ? "realm" : realm; + final String protocol = url.getProtocol(); + if (username) { + kind = ISVNAuthenticationManager.USERNAME; + } else if ("svn+ssh".equals(protocol)) { + kind = ISVNAuthenticationManager.SSH; + } else if ("http".equals(protocol)) { + kind = ISVNAuthenticationManager.PASSWORD; + } else if ("https".equals(protocol)) { + kind = ISVNAuthenticationManager.SSL; + } else if ("file".equals(protocol)) { + kind = ISVNAuthenticationManager.USERNAME; + } + SVNAuthentication authentication = null; + try { + authentication = myAuthenticationManager.getFirstAuthentication(kind, actualRealm, url); + } finally { + myAuthenticationManager.acknowledgeAuthentication(authentication != null, kind, actualRealm, null, authentication); + } + } + + private static class SvnTestInteractiveAuthentication implements ISVNAuthenticationProvider { + private final SvnAuthenticationManager myManager; + private boolean mySaveData; + + public SvnTestInteractiveAuthentication(SvnAuthenticationManager manager) { + myManager = manager; + mySaveData = true; + } + + public void setSaveData(boolean saveData) { + mySaveData = saveData; + } + + @Override + public int acceptServerAuthentication(SVNURL url, String realm, Object certificate, boolean resultMayBeStored) { + return ISVNAuthenticationProvider.REJECTED; + } + + @Override + public SVNAuthentication requestClientAuthentication(String kind, + SVNURL url, + String realm, + SVNErrorMessage errorMessage, + SVNAuthentication previousAuth, + boolean authMayBeStored) { + authMayBeStored = authMayBeStored & mySaveData; + SVNAuthentication result = null; + if (ISVNAuthenticationManager.USERNAME.equals(kind)) { + result = new SVNUserNameAuthentication("username", authMayBeStored); + } else if (ISVNAuthenticationManager.PASSWORD.equals(kind)) { + result = new SVNPasswordAuthentication("username", "abc", authMayBeStored, url, false); + } else if (ISVNAuthenticationManager.SSH.equals(kind)) { + result = new SVNSSHAuthentication("username", "abc", -1, authMayBeStored, url, false); + } else if (ISVNAuthenticationManager.SSL.equals(kind)) { + result = new SVNSSLAuthentication(new File("aaa"), "abc", authMayBeStored, url, false); + } + if (! ISVNAuthenticationManager.USERNAME.equals(kind)) { + myManager.requested(ProviderType.interactive, url, realm, kind, result == null); + } + return result; + } + } + + private static class TestListener implements SvnAuthenticationListener { + private List> myExpectedSequence; + private int myCnt; + private final Object mySynchObject; + private boolean mySuccess; + + private TestListener(final Object synchObject) { + mySynchObject = synchObject; + myExpectedSequence = new ArrayList>(); + myCnt = 0; + mySuccess = true; + } + + public void addStep(Trinity step) { + myExpectedSequence.add(step); + } + + public int getCnt() { + return myCnt; + } + + @Override + public void actualSaveWillBeTried(ProviderType type, SVNURL url, String realm, String kind) { + if (! mySuccess) return; + + mySuccess = myExpectedSequence.get(myCnt).equals(new Trinity(type, url, Type.save)); + if (mySuccess) { + ++ myCnt; + } + synchronized (mySynchObject) { + mySynchObject.notifyAll(); + } + } + + @Override + public void requested(ProviderType type, SVNURL url, String realm, String kind, boolean canceled) { + if (! mySuccess) return; + + mySuccess = myExpectedSequence.get(myCnt).equals(new Trinity(type, url, Type.request)); + if (mySuccess) { + ++ myCnt; + } + synchronized (mySynchObject) { + mySynchObject.notifyAll(); + } + } + + public void assertForAwt() { + Assert.assertTrue("last cnt = " + myCnt, mySuccess); + } + } + + private static class SavedOnceListener implements SvnAuthenticationListener { + private final Set> myClientRequested; + private final Set> mySaved; + private String myCause; + + private SavedOnceListener() { + myClientRequested = new HashSet>(); + mySaved = new HashSet>(); + } + + public void reset() { + mySaved.clear(); + myClientRequested.clear(); + } + + @Override + public void actualSaveWillBeTried(ProviderType type, SVNURL url, String realm, String kind) { + final Pair pair = new Pair(url, kind); + if (mySaved.contains(pair)) { + myCause = "saved twice"; + } + mySaved.add(pair); + } + + public boolean isSaved(final SVNURL url, final String kind) { + return mySaved.contains(new Pair(url, kind)); + } + + @Override + public void requested(ProviderType type, SVNURL url, String realm, String kind, boolean canceled) { + if (ProviderType.interactive.equals(type)) { + final Pair pair = new Pair(url, kind); + if (myClientRequested.contains(pair)) { + myCause = "client requested twice"; + } + myClientRequested.add(pair); + } + } + + public void assertForAwt() { + Assert.assertTrue(myCause, myCause == null); + } + + public void assertSaved(final SVNURL url, final String kind) { + Assert.assertTrue("not saved", mySaved.contains(new Pair(url, kind))); + } + + public void assertNotSaved(final SVNURL url, final String kind) { + Assert.assertTrue("saved", ! mySaved.contains(new Pair(url, kind))); + } + } + + private static enum Type { + request, + save + } + + private static class TestInteraction implements SvnAuthenticationInteraction { + private int myNumAuthWarn; + private int myNumPasswordsWarn; + private int myNumSSLWarn; + private int myNumPlaintextPrompt; + private int myNumSSLPlaintextPrompt; + + private boolean myPlaintextAnswer; + private boolean mySSLPlaintextAnswer; + + public void setPlaintextAnswer(boolean plaintextAnswer) { + myPlaintextAnswer = plaintextAnswer; + } + + public void setSSLPlaintextAnswer(boolean SSLPlaintextAnswer) { + mySSLPlaintextAnswer = SSLPlaintextAnswer; + } + + public void assertNothing() { + Assert.assertEquals("myNumAuthWarn", myNumAuthWarn, 0); + Assert.assertEquals("myNumPasswordsWarn", myNumPasswordsWarn, 0); + Assert.assertEquals("myNumSSLWarn", myNumSSLWarn, 0); + Assert.assertEquals("myNumPlaintextPrompt", myNumPlaintextPrompt, 0); + Assert.assertEquals("myNumSSLPlaintextPrompt", myNumSSLPlaintextPrompt, 0); + } + + public void reset() { + myNumAuthWarn = 0; + myNumPasswordsWarn = 0; + myNumSSLWarn = 0; + myNumPlaintextPrompt = 0; + myNumSSLPlaintextPrompt = 0; + } + + @Override + public boolean promptForPlaintextPasswordSaving(SVNURL url, String realm) { + ++ myNumPlaintextPrompt; + return myPlaintextAnswer; + } + + @Override + public boolean promptInAwt() { + return false; + } + + @Override + public void warnOnAuthStorageDisabled(SVNURL url) { + ++ myNumAuthWarn; + } + + @Override + public void warnOnPasswordStorageDisabled(SVNURL url) { + ++ myNumPasswordsWarn; + } + + @Override + public void warnOnSSLPassphraseStorageDisabled(SVNURL url) { + ++ myNumSSLWarn; + } + + @Override + public boolean promptForSSLPlaintextPassphraseSaving(SVNURL url, String realm, File certificateFile) { + ++ myNumSSLPlaintextPrompt; + return mySSLPlaintextAnswer; + } + + public int getNumAuthWarn() { + return myNumAuthWarn; + } + + public int getNumPasswordsWarn() { + return myNumPasswordsWarn; + } + + public int getNumPlaintextPrompt() { + return myNumPlaintextPrompt; + } + + public int getNumSSLPlaintextPrompt() { + return myNumSSLPlaintextPrompt; + } + + public int getNumSSLWarn() { + return myNumSSLWarn; + } + } +} diff --git a/plugins/xpath/xpath-lang/src/org/intellij/lang/xpath/xslt/impl/references/TemplateReference.java b/plugins/xpath/xpath-lang/src/org/intellij/lang/xpath/xslt/impl/references/TemplateReference.java index a8debdf3573b..8048ce911bec 100644 --- a/plugins/xpath/xpath-lang/src/org/intellij/lang/xpath/xslt/impl/references/TemplateReference.java +++ b/plugins/xpath/xpath-lang/src/org/intellij/lang/xpath/xslt/impl/references/TemplateReference.java @@ -83,7 +83,7 @@ class TemplateReference extends AttributeReference implements EmptyResolveMessag } public void registerQuickfix(HighlightInfo highlightInfo, TemplateReference psiReference) { - QuickFixAction.registerQuickFixAction(highlightInfo, new CreateTemplateFix(myAttribute.getParent(), myName), null, null); + QuickFixAction.registerQuickFixAction(highlightInfo, new CreateTemplateFix(myAttribute.getParent(), myName)); } public String getUnresolvedMessagePattern() { diff --git a/xml/impl/src/com/intellij/ide/actions/CreateHtmlFileAction.java b/xml/impl/src/com/intellij/ide/actions/CreateHtmlFileAction.java index cd1d5d130e0f..7bc5112917cc 100644 --- a/xml/impl/src/com/intellij/ide/actions/CreateHtmlFileAction.java +++ b/xml/impl/src/com/intellij/ide/actions/CreateHtmlFileAction.java @@ -17,6 +17,7 @@ package com.intellij.ide.actions; import com.intellij.ide.fileTemplates.FileTemplateManager; import com.intellij.openapi.fileTypes.StdFileTypes; +import com.intellij.openapi.project.DumbAware; import com.intellij.openapi.project.Project; import com.intellij.psi.PsiDirectory; import com.intellij.xml.XmlBundle; @@ -25,7 +26,7 @@ import org.jetbrains.annotations.NonNls; /** * @author Eugene.Kudelevsky */ -public class CreateHtmlFileAction extends CreateFileFromTemplateAction { +public class CreateHtmlFileAction extends CreateFileFromTemplateAction implements DumbAware { @NonNls private static final String DEFAULT_HTML_TEMPLATE_PROPERTY = "DefaultHtmlFileTemplate";
Shelve Path:" + StringUtil.escapeXml(ch.SHELVE_PATH) + - "
Shelve Path:" + + "
Shelve Name:" + StringUtil.escapeXml(d) + "