diff --git a/java/java-impl/src/com/intellij/refactoring/util/RefactoringConflictsUtil.java b/java/java-impl/src/com/intellij/refactoring/util/RefactoringConflictsUtil.java index b2dfd0fa9fbc..2ec39e07542c 100644 --- a/java/java-impl/src/com/intellij/refactoring/util/RefactoringConflictsUtil.java +++ b/java/java-impl/src/com/intellij/refactoring/util/RefactoringConflictsUtil.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2011 JetBrains s.r.o. + * Copyright 2000-2013 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -74,7 +74,7 @@ public class RefactoringConflictsUtil { public static void checkAccessibilityConflicts(@NotNull PsiMember member, @PsiModifier.ModifierConstant @Nullable String newVisibility, @Nullable PsiClass targetClass, - @NotNull Set membersToMove, + @NotNull Set membersToMove, @NotNull MultiMap conflicts) { PsiModifierList modifierListCopy = member.getModifierList(); if (modifierListCopy != null) { @@ -97,7 +97,7 @@ public class RefactoringConflictsUtil { public static void checkAccessibilityConflicts(@NotNull PsiMember member, @Nullable PsiModifierList modifierListCopy, @Nullable PsiClass targetClass, - @NotNull Set membersToMove, + @NotNull Set membersToMove, @NotNull MultiMap conflicts) { for (PsiReference psiReference : ReferencesSearch.search(member)) { checkAccessibilityConflicts(psiReference, member, modifierListCopy, targetClass, membersToMove, conflicts); @@ -108,7 +108,7 @@ public class RefactoringConflictsUtil { @NotNull PsiMember member, @Nullable PsiModifierList modifierListCopy, @Nullable PsiClass targetClass, - @NotNull Set membersToMove, + @NotNull Set membersToMove, @NotNull MultiMap conflicts) { JavaPsiFacade manager = JavaPsiFacade.getInstance(member.getProject()); PsiElement ref = reference.getElement(); diff --git a/java/java-impl/src/com/intellij/refactoring/util/RefactoringHierarchyUtil.java b/java/java-impl/src/com/intellij/refactoring/util/RefactoringHierarchyUtil.java index 87ab9b218b2c..d5c85588a322 100644 --- a/java/java-impl/src/com/intellij/refactoring/util/RefactoringHierarchyUtil.java +++ b/java/java-impl/src/com/intellij/refactoring/util/RefactoringHierarchyUtil.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2009 JetBrains s.r.o. + * Copyright 2000-2013 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -47,7 +47,7 @@ public class RefactoringHierarchyUtil { private RefactoringHierarchyUtil() {} public static boolean willBeInTargetClass(PsiElement place, - @NotNull Set membersToMove, + @NotNull Set membersToMove, @Nullable PsiClass targetClass, boolean includeSubclasses) { PsiElement parent = place; diff --git a/platform/lang-impl/src/com/intellij/refactoring/memberPullUp/PullUpDialogBase.java b/platform/lang-impl/src/com/intellij/refactoring/memberPullUp/PullUpDialogBase.java index e923abb900e5..b6775a253931 100644 --- a/platform/lang-impl/src/com/intellij/refactoring/memberPullUp/PullUpDialogBase.java +++ b/platform/lang-impl/src/com/intellij/refactoring/memberPullUp/PullUpDialogBase.java @@ -113,6 +113,7 @@ public abstract class PullUpDialogBase= textRange.getEndOffset()) { + if (textRange.getLength() > 0 && (selectionStart <= textRange.getStartOffset() || selectionEnd >= textRange.getEndOffset())) { return null; } + + if (elementType == GroovyElementTypes.GSTRING_CONTENT) { + elementAtSelectionStart = elementAtSelectionStart.getFirstChild(); + } + return elementAtSelectionStart; } diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/intentions/conversions/RemoveUnnecessaryBracesInGStringIntention.java b/plugins/groovy/src/org/jetbrains/plugins/groovy/intentions/conversions/RemoveUnnecessaryBracesInGStringIntention.java index efdec757750b..4f4baf546522 100644 --- a/plugins/groovy/src/org/jetbrains/plugins/groovy/intentions/conversions/RemoveUnnecessaryBracesInGStringIntention.java +++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/intentions/conversions/RemoveUnnecessaryBracesInGStringIntention.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2009 JetBrains s.r.o. + * Copyright 2000-2013 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -35,30 +35,24 @@ public class RemoveUnnecessaryBracesInGStringIntention extends Intention { @NotNull @Override protected PsiElementPredicate getElementPredicate() { - return new MyPredicate(); + return new PsiElementPredicate() { + public boolean satisfiedBy(PsiElement element) { + if (!(element instanceof GrString)) return false; + + if (ErrorUtil.containsError(element)) return false; + + for (GrStringInjection child : ((GrString)element).getInjections()) { + if (GrStringUtil.checkGStringInjectionForUnnecessaryBraces(child)) return true; + } + return false; + } + }; } @Override protected void processIntention(@NotNull PsiElement element, Project project, Editor editor) throws IncorrectOperationException { GrStringUtil.removeUnnecessaryBracesInGString((GrString)element); } - - public static class MyPredicate implements PsiElementPredicate { - public boolean satisfiedBy(PsiElement element) { - return isIntentionAvailable(element); - } - - public static boolean isIntentionAvailable(PsiElement element) { - if (!(element instanceof GrString)) return false; - - if (ErrorUtil.containsError(element)) return false; - - for (GrStringInjection child : ((GrString)element).getInjections()) { - if (GrStringUtil.checkGStringInjectionForUnnecessaryBraces(child)) return true; - } - return false; - } - } } diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/completion/ClosureCompleter.java b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/completion/ClosureCompleter.java index b66d441ebc63..64e2c6675990 100644 --- a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/completion/ClosureCompleter.java +++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/completion/ClosureCompleter.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2012 JetBrains s.r.o. + * Copyright 2000-2013 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -47,15 +47,13 @@ import java.util.List; * @author Max Medvedev */ public abstract class ClosureCompleter { - private static ExtensionPointName EP_NAME = ExtensionPointName.create("org.intellij.groovy.closureCompleter"); + private static final ExtensionPointName EP_NAME = ExtensionPointName.create("org.intellij.groovy.closureCompleter"); @Nullable protected abstract List getParameterInfos(InsertionContext context, PsiMethod method, PsiSubstitutor substitutor, - Document document, - int offset, - PsiElement parent); + PsiElement place); public static boolean runClosureCompletion(InsertionContext context, PsiMethod method, @@ -64,7 +62,7 @@ public abstract class ClosureCompleter { int offset, PsiElement parent) { for (ClosureCompleter completer : EP_NAME.getExtensions()) { - final List parameterInfos = completer.getParameterInfos(context, method, substitutor, document, offset, parent); + final List parameterInfos = completer.getParameterInfos(context, method, substitutor, parent); if (parameterInfos != null) { runClosureTemplate(context, document, offset, substitutor, method, parameterInfos); return true; diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/completion/EachWithIndexClosureCompleter.java b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/completion/EachWithIndexClosureCompleter.java index 54962a1e33c7..a6876211f06d 100644 --- a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/completion/EachWithIndexClosureCompleter.java +++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/completion/EachWithIndexClosureCompleter.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2012 JetBrains s.r.o. + * Copyright 2000-2013 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,7 +16,6 @@ package org.jetbrains.plugins.groovy.lang.completion; import com.intellij.codeInsight.completion.InsertionContext; -import com.intellij.openapi.editor.Document; import com.intellij.openapi.project.Project; import com.intellij.psi.*; import com.intellij.psi.util.InheritanceUtil; @@ -42,9 +41,7 @@ public class EachWithIndexClosureCompleter extends ClosureCompleter { protected List getParameterInfos(InsertionContext context, PsiMethod method, PsiSubstitutor substitutor, - Document document, - int offset, - PsiElement parent) { + PsiElement place) { final String name = method.getName(); if (!"eachWithIndex".equals(name)) return null; @@ -63,7 +60,7 @@ public class EachWithIndexClosureCompleter extends ClosureCompleter { final PsiType type = parameters[0].getType(); final PsiType collection = substitutor.substitute(type); - final PsiType iterable = getIteratedType(parent, collection); + final PsiType iterable = getIteratedType(place, collection); if (iterable != null) { return Arrays.asList( new ClosureParameterInfo(iterable.getCanonicalText(), "entry"), @@ -76,7 +73,7 @@ public class EachWithIndexClosureCompleter extends ClosureCompleter { final Project project = context.getProject(); - final PsiClass entry = JavaPsiFacade.getInstance(project).findClass(CommonClassNames.JAVA_UTIL_MAP_ENTRY, parent.getResolveScope()); + final PsiClass entry = JavaPsiFacade.getInstance(project).findClass(CommonClassNames.JAVA_UTIL_MAP_ENTRY, place.getResolveScope()); if (entry == null) return null; final PsiClassType entryType = JavaPsiFacade.getElementFactory(project).createType(entry, typeParams); diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/completion/GdslClosureCompleter.java b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/completion/GdslClosureCompleter.java index 333f91a7d552..a7f6e342cd60 100644 --- a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/completion/GdslClosureCompleter.java +++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/completion/GdslClosureCompleter.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2012 JetBrains s.r.o. + * Copyright 2000-2013 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,7 +16,6 @@ package org.jetbrains.plugins.groovy.lang.completion; import com.intellij.codeInsight.completion.InsertionContext; -import com.intellij.openapi.editor.Document; import com.intellij.psi.*; import com.intellij.psi.scope.BaseScopeProcessor; import org.jetbrains.annotations.NotNull; @@ -42,8 +41,6 @@ public class GdslClosureCompleter extends ClosureCompleter { protected List getParameterInfos(InsertionContext context, PsiMethod method, PsiSubstitutor substitutor, - Document document, - int offset, PsiElement place) { final ArrayList descriptors = new ArrayList(); GrReferenceExpression ref = (GrReferenceExpression)place; diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/groovydoc/psi/impl/GrDocCommentUtil.java b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/groovydoc/psi/impl/GrDocCommentUtil.java index 4f974b364907..a470ed7a08b0 100644 --- a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/groovydoc/psi/impl/GrDocCommentUtil.java +++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/groovydoc/psi/impl/GrDocCommentUtil.java @@ -93,9 +93,9 @@ public abstract class GrDocCommentUtil { PsiElement parent = owner.getParent(); ASTNode node = owner.getNode(); - parent.getNode().addLeaf(GroovyTokenTypes.mNLS, "\n", node); + parent.getNode().addLeaf(GroovyTokenTypes.mNLS, "\n ", node); - PsiElement added = parent.addBefore(comment, node.getPsi()); + PsiElement added = parent.addBefore(comment, owner); assert added instanceof GrDocComment; return (GrDocComment)added; diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/parser/parsing/statements/expressions/primary/CompoundStringExpression.java b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/parser/parsing/statements/expressions/primary/CompoundStringExpression.java index fcb7080a7ce3..e9f72ad055b4 100644 --- a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/parser/parsing/statements/expressions/primary/CompoundStringExpression.java +++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/parser/parsing/statements/expressions/primary/CompoundStringExpression.java @@ -19,6 +19,7 @@ package org.jetbrains.plugins.groovy.lang.parser.parsing.statements.expressions. import com.intellij.lang.PsiBuilder; import com.intellij.openapi.diagnostic.Logger; import com.intellij.psi.tree.IElementType; +import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.plugins.groovy.GroovyBundle; import org.jetbrains.plugins.groovy.lang.lexer.GroovyElementType; @@ -33,6 +34,128 @@ import org.jetbrains.plugins.groovy.lang.parser.parsing.util.ParserUtils; */ public class CompoundStringExpression implements GroovyElementTypes { private static final Logger LOG = Logger.getInstance(CompoundStringExpression.class); + private final PsiBuilder myBuilder; + private final GroovyParser myParser; + private final boolean myForRefExpr; + private final IElementType myBegin; + private final IElementType myContent; + private final IElementType myEnd; + private final IElementType mySimpleLiteral; + private final GroovyElementType myCompoundLiteral; + private final String myMessage; + + private CompoundStringExpression(PsiBuilder builder, + GroovyParser parser, + boolean forRefExpr, + IElementType begin, + IElementType content, + IElementType end, + IElementType literal, + GroovyElementType compoundLiteral, + String message) { + + myBuilder = builder; + myParser = parser; + myForRefExpr = forRefExpr; + myBegin = begin; + myContent = content; + myEnd = end; + mySimpleLiteral = literal; + myCompoundLiteral = compoundLiteral; + myMessage = message; + } + + private boolean parse() { + PsiBuilder.Marker marker = myBuilder.mark(); + final PsiBuilder.Marker marker2 = myBuilder.mark(); + LOG.assertTrue(ParserUtils.getToken(myBuilder, myBegin)); + + + if (mySimpleLiteral != null && myBuilder.getTokenType() == myEnd) { + myBuilder.advanceLexer(); + finishSimpleLiteral(marker, marker2); + return true; + } + + if (myBuilder.getTokenType() == myContent) { + final PsiBuilder.Marker contentMarker = myBuilder.mark(); + myBuilder.advanceLexer(); + if (myBuilder.getTokenType() == mDOLLAR || mySimpleLiteral == null) { + contentMarker.done(GSTRING_CONTENT); + } + else { + contentMarker.drop(); + } + } + else { + processContent(); + } + + boolean hasInjection = myBuilder.getTokenType() == mDOLLAR; + while (myBuilder.getTokenType() == mDOLLAR) { + parseInjection(); + processContent(); + } + + if (!ParserUtils.getToken(myBuilder, myEnd)) { + myBuilder.error(myMessage); + } + + if (hasInjection || mySimpleLiteral == null) { + marker2.drop(); + marker.done(myCompoundLiteral); + } + else { + finishSimpleLiteral(marker, marker2); + } + return hasInjection; + } + + private void processContent() { + PsiBuilder.Marker marker = myBuilder.mark(); + if (myBuilder.getTokenType() == myContent) { + myBuilder.advanceLexer(); + } + else { + myBuilder.mark().done(myContent); + } + marker.done(GSTRING_CONTENT); + } + + private void finishSimpleLiteral(PsiBuilder.Marker marker, PsiBuilder.Marker marker2) { + marker2.done(mySimpleLiteral); + if (myForRefExpr) { + marker.drop(); + } + else { + marker.done(LITERAL); + } + } + + /** + * Parses heredoc's content in GString + * + * @return nothing + */ + private boolean parseInjection() { + if (myBuilder.getTokenType() != mDOLLAR) return false; + + final PsiBuilder.Marker injection = myBuilder.mark(); + ParserUtils.getToken(myBuilder, mDOLLAR); + + if (mIDENT.equals(myBuilder.getTokenType())) { + PathExpression.parse(myBuilder, myParser); + } + else if (mLCURLY.equals(myBuilder.getTokenType())) { + OpenOrClosableBlock.parseClosableBlock(myBuilder, myParser); + } + else { + ParserUtils.wrapError(myBuilder, GroovyBundle.message("identifier.or.block.expected")); + } + + injection.done(GSTRING_INJECTION); + return true; + } /** * Groovy lexer does not smart enough to understand whether a regex contents injections or not. So the parser should do this job. @@ -48,82 +171,15 @@ public class CompoundStringExpression implements GroovyElementTypes { * * @return true if there are any injections */ - public static boolean parse(PsiBuilder builder, - GroovyParser parser, + public static boolean parse(@NotNull PsiBuilder builder, + @NotNull GroovyParser parser, boolean forRefExpr, - IElementType begin, - IElementType content, - IElementType end, + @NotNull IElementType begin, + @NotNull IElementType content, + @NotNull IElementType end, @Nullable IElementType literal, - GroovyElementType compoundLiteral, String message) { - PsiBuilder.Marker marker = builder.mark(); - final PsiBuilder.Marker marker2 = builder.mark(); - LOG.assertTrue(ParserUtils.getToken(builder, begin)); - - if (builder.getTokenType() == content) { - final PsiBuilder.Marker contentMarker = builder.mark(); - builder.advanceLexer(); - if (builder.getTokenType() == mDOLLAR || literal == null) { - contentMarker.done(GSTRING_CONTENT); - } - else { - contentMarker.drop(); - } - } - - boolean inj = builder.getTokenType() == mDOLLAR; - while (builder.getTokenType() == mDOLLAR || builder.getTokenType() == content) { - if (builder.getTokenType() == mDOLLAR) { - parseInjection(builder, parser); - } - else { - ParserUtils.eatElement(builder, GSTRING_CONTENT); - } - } - - if (!ParserUtils.getToken(builder, end)) { - builder.error(message); - } - - if (inj || literal == null) { - marker2.drop(); - marker.done(compoundLiteral); - } - else { - marker2.done(literal); - if (forRefExpr) { - marker.drop(); - } - else { - marker.done(LITERAL); - } - } - return inj; - } - - /** - * Parses heredoc's content in GString - * - * @param builder given builder - * @return nothing - */ - private static boolean parseInjection(PsiBuilder builder, GroovyParser parser) { - if (builder.getTokenType() != mDOLLAR) return false; - - final PsiBuilder.Marker injection = builder.mark(); - ParserUtils.getToken(builder, mDOLLAR); - - if (mIDENT.equals(builder.getTokenType())) { - PathExpression.parse(builder, parser); - } - else if (mLCURLY.equals(builder.getTokenType())) { - OpenOrClosableBlock.parseClosableBlock(builder, parser); - } - else { - ParserUtils.wrapError(builder, GroovyBundle.message("identifier.or.block.expected")); - } - - injection.done(GSTRING_INJECTION); - return true; + @NotNull GroovyElementType compoundLiteral, + @NotNull String message) { + return new CompoundStringExpression(builder, parser, forRefExpr, begin, content, end, literal, compoundLiteral, message).parse(); } } \ No newline at end of file diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/impl/statements/expressions/literals/GrStringContentImpl.java b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/impl/statements/expressions/literals/GrStringContentImpl.java index 8b6889455625..3b37bdabebf1 100644 --- a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/impl/statements/expressions/literals/GrStringContentImpl.java +++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/impl/statements/expressions/literals/GrStringContentImpl.java @@ -19,8 +19,7 @@ import com.intellij.lang.ASTNode; import com.intellij.psi.LiteralTextEscaper; import com.intellij.psi.PsiLanguageInjectionHost; import org.jetbrains.annotations.NotNull; -import org.jetbrains.plugins.groovy.lang.psi.GroovyPsiElementFactory; -import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.literals.GrString; +import org.jetbrains.plugins.groovy.lang.lexer.GroovyTokenTypes; import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.literals.GrStringContent; import org.jetbrains.plugins.groovy.lang.psi.impl.GroovyPsiElementImpl; import org.jetbrains.plugins.groovy.lang.psi.util.GrStringUtil; @@ -48,10 +47,10 @@ public class GrStringContentImpl extends GroovyPsiElementImpl implements GrStrin @Override public GrStringContentImpl updateText(@NotNull String text) { - - final GrString fromText = (GrString)GroovyPsiElementFactory.getInstance(getProject()).createExpressionFromText("\"${0}" + text + "\""); - final GrStringContent content = fromText.getContents()[0]; - getNode().replaceChild(getFirstChild().getNode(), content.getFirstChild().getNode()); + if (getFirstChild() != null) { + getFirstChild().delete(); + } + getNode().addLeaf(GroovyTokenTypes.mGSTRING_CONTENT, text, null); return this; } diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/impl/statements/typedef/GrReferenceListImpl.java b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/impl/statements/typedef/GrReferenceListImpl.java index 0b3f95b8f3d4..6d0c579b03a8 100644 --- a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/impl/statements/typedef/GrReferenceListImpl.java +++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/impl/statements/typedef/GrReferenceListImpl.java @@ -61,7 +61,10 @@ public abstract class GrReferenceListImpl extends GrStubElementBase 0) { - if (injections[injections.length - 1].getExpression() != null) { - if (!checkBraceIsUnnecessary(injections[injections.length - 1].getExpression(), injection.getNextSibling())) { - wrapInjection(injections[injections.length - 1]); + GrStringInjection last = injections[injections.length - 1]; + if (last.getExpression() != null) { + if (!checkBraceIsUnnecessary(last.getExpression(), injection.getNextSibling())) { + wrapInjection(last); } } } @@ -488,8 +489,9 @@ public class GrStringUtil { final GrExpression expression = factory.createExpressionFromText("\"\"\"${}" + literalText + "\"\"\""); - expression.getFirstChild().delete(); - expression.getFirstChild().delete(); + expression.getFirstChild().delete();//quote + expression.getFirstChild().delete();//empty gstring content + expression.getFirstChild().delete();//empty injection final ASTNode node = grString.getNode(); if (expression.getFirstChild() != null) { @@ -541,12 +543,12 @@ public class GrStringUtil { if (!(statements[0] instanceof GrReferenceExpression)) return false; - final PsiElement next = injection.getNextSibling(); - - return checkBraceIsUnnecessary(statements[0], next); + return checkBraceIsUnnecessary(statements[0], injection.getNextSibling()); } private static boolean checkBraceIsUnnecessary(GrStatement injected, PsiElement next) { + if (next.getTextLength() == 0) next = next.getNextSibling(); + char nextChar = next.getText().charAt(0); if (nextChar == '"' || nextChar == '$') { return true; @@ -561,8 +563,14 @@ public class GrStringUtil { } if (!(gString instanceof GrString)) return false; - final PsiElement child = gString.getChildren()[0]; - if (!(child instanceof GrStringInjection)) return false; + PsiElement child = gString.getFirstChild(); + if (!(child.getNode().getElementType() == mGSTRING_BEGIN)) return false; + + child = child.getNextSibling(); + if (child == null || !(child instanceof GrStringContent)) return false; + + child = child.getNextSibling(); + if (child == null || !(child instanceof GrStringInjection)) return false; final PsiElement refExprCopy = ((GrStringInjection)child).getExpression(); if (!(refExprCopy instanceof GrReferenceExpression)) return false; diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/refactoring/GrRefactoringConflictsUtil.java b/plugins/groovy/src/org/jetbrains/plugins/groovy/refactoring/GrRefactoringConflictsUtil.java new file mode 100644 index 000000000000..bb3a5f107283 --- /dev/null +++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/refactoring/GrRefactoringConflictsUtil.java @@ -0,0 +1,262 @@ +/* + * Copyright 2000-2013 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jetbrains.plugins.groovy.refactoring; + +import com.intellij.openapi.module.Module; +import com.intellij.openapi.module.ModuleUtilCore; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.roots.ModuleRootManager; +import com.intellij.openapi.roots.ProjectRootManager; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.psi.*; +import com.intellij.psi.impl.light.LightElement; +import com.intellij.psi.search.GlobalSearchScope; +import com.intellij.psi.search.PsiSearchScopeUtil; +import com.intellij.psi.util.PsiTreeUtil; +import com.intellij.psi.util.PsiUtilCore; +import com.intellij.refactoring.RefactoringBundle; +import com.intellij.refactoring.util.*; +import com.intellij.usageView.UsageInfo; +import com.intellij.util.VisibilityUtil; +import com.intellij.util.containers.ContainerUtil; +import com.intellij.util.containers.FilteringIterator; +import com.intellij.util.containers.HashSet; +import com.intellij.util.containers.MultiMap; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.plugins.groovy.lang.psi.GrReferenceElement; +import org.jetbrains.plugins.groovy.lang.psi.GroovyPsiElement; +import org.jetbrains.plugins.groovy.lang.psi.GroovyRecursiveElementVisitor; +import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrExpression; +import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrNewExpression; +import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrReferenceExpression; +import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.GrAnonymousClassDefinition; +import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.members.GrMember; +import org.jetbrains.plugins.groovy.lang.psi.api.toplevel.imports.GrImportStatement; +import org.jetbrains.plugins.groovy.lang.psi.api.types.GrCodeReferenceElement; +import org.jetbrains.plugins.groovy.lang.psi.util.PsiUtil; + +import java.util.Collection; +import java.util.List; +import java.util.Set; + + +/** + * Created by Max Medvedev on 9/28/13 + */ +public class GrRefactoringConflictsUtil { + private GrRefactoringConflictsUtil() { } + + public static void analyzeAccessibilityConflicts(@NotNull Set membersToMove, + @NotNull PsiClass targetClass, + @NotNull MultiMap conflicts, + @Nullable String newVisibility) { + analyzeAccessibilityConflicts(membersToMove, targetClass, conflicts, newVisibility, targetClass, null); + } + + public static void analyzeAccessibilityConflicts(@NotNull Set membersToMove, + @Nullable PsiClass targetClass, + @NotNull MultiMap conflicts, + @Nullable String newVisibility, + @NotNull PsiElement context, + @Nullable Set abstractMethods) { + if (VisibilityUtil.ESCALATE_VISIBILITY.equals(newVisibility)) { //Still need to check for access object + newVisibility = PsiModifier.PUBLIC; + } + + for (GrMember member : membersToMove) { + checkUsedElements(member, member, membersToMove, abstractMethods, targetClass, context, conflicts); + RefactoringConflictsUtil.checkAccessibilityConflicts(member, newVisibility, targetClass, membersToMove, conflicts); + } + } + + public static void checkUsedElements(PsiMember member, + PsiElement scope, + @NotNull Set membersToMove, + @Nullable Set abstractMethods, + @Nullable PsiClass targetClass, + @NotNull PsiElement context, + MultiMap conflicts) { + final Set moving = new HashSet(membersToMove); + if (abstractMethods != null) { + moving.addAll(abstractMethods); + } + if (scope instanceof GrReferenceExpression) { + GrReferenceExpression refExpr = (GrReferenceExpression)scope; + PsiElement refElement = refExpr.resolve(); + if (refElement instanceof PsiMember) { + if (!RefactoringHierarchyUtil.willBeInTargetClass(refElement, moving, targetClass, false)) { + GrExpression qualifier = refExpr.getQualifierExpression(); + PsiClass accessClass = (PsiClass)(qualifier != null ? PsiUtil.getAccessObjectClass( + qualifier).getElement() : null); + RefactoringConflictsUtil.checkAccessibility((PsiMember)refElement, context, accessClass, member, conflicts); + } + } + } + else if (scope instanceof GrNewExpression) { + final GrNewExpression newExpression = (GrNewExpression)scope; + final GrAnonymousClassDefinition anonymousClass = newExpression.getAnonymousClassDefinition(); + if (anonymousClass != null) { + if (!RefactoringHierarchyUtil.willBeInTargetClass(anonymousClass, moving, targetClass, false)) { + RefactoringConflictsUtil.checkAccessibility(anonymousClass, context, anonymousClass, member, conflicts); + } + } + else { + final PsiMethod refElement = newExpression.resolveMethod(); + if (refElement != null) { + if (!RefactoringHierarchyUtil.willBeInTargetClass(refElement, moving, targetClass, false)) { + RefactoringConflictsUtil.checkAccessibility(refElement, context, null, member, conflicts); + } + } + } + } + else if (scope instanceof GrCodeReferenceElement) { + GrCodeReferenceElement refExpr = (GrCodeReferenceElement)scope; + PsiElement refElement = refExpr.resolve(); + if (refElement instanceof PsiMember) { + if (!RefactoringHierarchyUtil.willBeInTargetClass(refElement, moving, targetClass, false)) { + RefactoringConflictsUtil.checkAccessibility((PsiMember)refElement, context, null, member, conflicts); + } + } + } + + for (PsiElement child : scope.getChildren()) { + if (child instanceof PsiWhiteSpace || child instanceof PsiComment) continue; + checkUsedElements(member, child, membersToMove, abstractMethods, targetClass, context, conflicts); + } + } + + + + public static void analyzeModuleConflicts(final Project project, + final Collection scopes, + final UsageInfo[] usages, + final PsiElement target, + final MultiMap conflicts) { + if (scopes == null) return; + final VirtualFile vFile = PsiUtilCore.getVirtualFile(target); + if (vFile == null) return; + + + List groovyScopes = + ContainerUtil.collect(scopes.iterator(), new FilteringIterator.InstanceOf(GroovyPsiElement.class)); + analyzeModuleConflicts(project, groovyScopes, usages, vFile, conflicts); + scopes.removeAll(groovyScopes); + RefactoringConflictsUtil.analyzeModuleConflicts(project, scopes, usages, vFile, conflicts); + } + + public static void analyzeModuleConflicts(final Project project, + final Collection scopes, + final UsageInfo[] usages, + final VirtualFile vFile, + final MultiMap conflicts) { + if (scopes == null) return; + for (final PsiElement scope : scopes) { + if (scope instanceof PsiPackage) return; + } + + final Module targetModule = ModuleUtilCore.findModuleForFile(vFile, project); + if (targetModule == null) return; + final GlobalSearchScope resolveScope = GlobalSearchScope.moduleWithDependenciesAndLibrariesScope(targetModule); + final HashSet reported = new HashSet(); + for (final GroovyPsiElement scope : scopes) { + scope.accept(new GroovyRecursiveElementVisitor() { + @Override + public void visitCodeReferenceElement(GrCodeReferenceElement refElement) { + super.visitCodeReferenceElement(refElement); + visit(refElement); + } + + @Override + public void visitReferenceExpression(GrReferenceExpression reference) { + super.visitReferenceExpression(reference); + visit(reference); + } + + private void visit(GrReferenceElement reference) { + final PsiElement resolved = reference.resolve(); + if (resolved != null && + !reported.contains(resolved) && + !CommonRefactoringUtil.isAncestor(resolved, scopes) && + !PsiSearchScopeUtil.isInScope(resolveScope, resolved) && + !(resolved instanceof LightElement)) { + final String scopeDescription = RefactoringUIUtil.getDescription(ConflictsUtil.getContainer(reference), true); + final String message = RefactoringBundle.message("0.referenced.in.1.will.not.be.accessible.in.module.2", + RefactoringUIUtil.getDescription(resolved, true), + scopeDescription, + CommonRefactoringUtil.htmlEmphasize(targetModule.getName())); + conflicts.putValue(resolved, CommonRefactoringUtil.capitalize(message)); + reported.add(resolved); + } + } + }); + } + + boolean isInTestSources = ModuleRootManager.getInstance(targetModule).getFileIndex().isInTestSourceContent(vFile); + NextUsage: + for (UsageInfo usage : usages) { + final PsiElement element = usage.getElement(); + if (element != null && PsiTreeUtil.getParentOfType(element, GrImportStatement.class, false) == null) { + + for (PsiElement scope : scopes) { + if (PsiTreeUtil.isAncestor(scope, element, false)) continue NextUsage; + } + + final GlobalSearchScope resolveScope1 = element.getResolveScope(); + if (!resolveScope1.isSearchInModuleContent(targetModule, isInTestSources)) { + final PsiFile usageFile = element.getContainingFile(); + PsiElement container; + if (usageFile instanceof PsiJavaFile) { + container = ConflictsUtil.getContainer(element); + } + else { + container = usageFile; + } + final String scopeDescription = RefactoringUIUtil.getDescription(container, true); + final VirtualFile usageVFile = usageFile.getVirtualFile(); + if (usageVFile != null) { + Module module = ProjectRootManager.getInstance(project).getFileIndex().getModuleForFile(usageVFile); + if (module != null) { + final String message; + final PsiElement referencedElement; + if (usage instanceof MoveRenameUsageInfo) { + referencedElement = ((MoveRenameUsageInfo)usage).getReferencedElement(); + } + else { + referencedElement = usage.getElement(); + } + assert referencedElement != null : usage; + if (module == targetModule && isInTestSources) { + message = RefactoringBundle.message("0.referenced.in.1.will.not.be.accessible.from.production.of.module.2", + RefactoringUIUtil.getDescription(referencedElement, true), + scopeDescription, + CommonRefactoringUtil.htmlEmphasize(module.getName())); + } + else { + message = RefactoringBundle.message("0.referenced.in.1.will.not.be.accessible.from.module.2", + RefactoringUIUtil.getDescription(referencedElement, true), + scopeDescription, + CommonRefactoringUtil.htmlEmphasize(module.getName())); + } + conflicts.putValue(referencedElement, CommonRefactoringUtil.capitalize(message)); + } + } + } + } + } + } + +} diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/refactoring/changeSignature/GrChangeSignatureConflictSearcher.java b/plugins/groovy/src/org/jetbrains/plugins/groovy/refactoring/changeSignature/GrChangeSignatureConflictSearcher.java index c5f8ebcd07c9..b679c77918c4 100644 --- a/plugins/groovy/src/org/jetbrains/plugins/groovy/refactoring/changeSignature/GrChangeSignatureConflictSearcher.java +++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/refactoring/changeSignature/GrChangeSignatureConflictSearcher.java @@ -31,14 +31,13 @@ import com.intellij.util.IncorrectOperationException; import com.intellij.util.VisibilityUtil; import com.intellij.util.containers.HashSet; import com.intellij.util.containers.MultiMap; -import org.jetbrains.annotations.Nullable; import org.jetbrains.plugins.groovy.lang.psi.GroovyPsiElement; import org.jetbrains.plugins.groovy.lang.psi.GroovyPsiElementFactory; -import org.jetbrains.plugins.groovy.lang.psi.api.statements.GrConstructorInvocation; import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrExpression; import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrReferenceExpression; 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; import java.util.Arrays; import java.util.Iterator; @@ -92,7 +91,7 @@ class GrChangeSignatureConflictSearcher { PsiClass accessObjectClass = null; GrExpression qualifier = ((GrReferenceExpression)element).getQualifierExpression(); if (qualifier != null) { - accessObjectClass = getAccessObjectClass(qualifier); + accessObjectClass = (PsiClass)PsiUtil.getAccessObjectClass(qualifier).getElement(); } PsiResolveHelper helper = JavaPsiFacade.getInstance(element.getProject()).getResolveHelper(); @@ -112,23 +111,6 @@ class GrChangeSignatureConflictSearcher { } } - @Nullable - private static PsiClass getAccessObjectClass(GrExpression expression) { - if (expression instanceof GrConstructorInvocation) return null; - PsiType type = expression.getType(); - if (type instanceof PsiClassType) { - return ((PsiClassType)type).resolveGenerics().getElement(); - } - if (type == null && expression instanceof PsiReferenceExpression) { - JavaResolveResult resolveResult = ((PsiReferenceExpression)expression).advancedResolve(false); - if (resolveResult.getElement() instanceof PsiClass) { - return (PsiClass)resolveResult.getElement(); - } - } - return null; - } - - private void addMethodConflicts(MultiMap conflicts) { try { GrMethod prototype; diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/refactoring/memberPullUp/GrPullUpConflictsUtil.java b/plugins/groovy/src/org/jetbrains/plugins/groovy/refactoring/memberPullUp/GrPullUpConflictsUtil.java new file mode 100644 index 000000000000..6cf223cec996 --- /dev/null +++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/refactoring/memberPullUp/GrPullUpConflictsUtil.java @@ -0,0 +1,376 @@ +/* + * Copyright 2000-2013 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jetbrains.plugins.groovy.refactoring.memberPullUp; + +import com.intellij.openapi.util.Comparing; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.psi.*; +import com.intellij.psi.search.searches.ClassInheritorsSearch; +import com.intellij.psi.util.*; +import com.intellij.refactoring.RefactoringBundle; +import com.intellij.refactoring.classMembers.MemberInfoBase; +import com.intellij.refactoring.util.CommonRefactoringUtil; +import com.intellij.refactoring.util.RefactoringHierarchyUtil; +import com.intellij.refactoring.util.RefactoringUIUtil; +import com.intellij.refactoring.util.classMembers.InterfaceContainmentVerifier; +import com.intellij.usageView.UsageInfo; +import com.intellij.util.VisibilityUtil; +import com.intellij.util.containers.ContainerUtil; +import com.intellij.util.containers.MultiMap; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.plugins.groovy.lang.psi.GrReferenceElement; +import org.jetbrains.plugins.groovy.lang.psi.GroovyPsiElement; +import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.members.GrMember; +import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.members.GrMethod; +import org.jetbrains.plugins.groovy.refactoring.GrRefactoringConflictsUtil; +import org.jetbrains.plugins.groovy.refactoring.classMembers.GrClassMemberReferenceVisitor; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +/** + * Created by Max Medvedev on 9/28/13 + */ +public class GrPullUpConflictsUtil { + private GrPullUpConflictsUtil() {} + + public static MultiMap checkConflicts(MemberInfoBase[] infos, + PsiClass subclass, + @Nullable PsiClass superClass, + @NotNull PsiPackage targetPackage, + @NotNull PsiDirectory targetDirectory, + final InterfaceContainmentVerifier interfaceContainmentVerifier) { + return checkConflicts(infos, subclass, superClass, targetPackage, targetDirectory, interfaceContainmentVerifier, true); + } + + public static MultiMap checkConflicts(final MemberInfoBase[] infos, + @NotNull final PsiClass subclass, + @Nullable PsiClass superClass, + @NotNull final PsiPackage targetPackage, + @NotNull PsiDirectory targetDirectory, + final InterfaceContainmentVerifier interfaceContainmentVerifier, + boolean movedMembers2Super) { + final PsiElement targetRepresentativeElement; + final boolean isInterfaceTarget; + if (superClass != null) { + isInterfaceTarget = superClass.isInterface(); + targetRepresentativeElement = superClass; + } + else { + isInterfaceTarget = false; + targetRepresentativeElement = targetDirectory; + } + + final Set movedMembers = ContainerUtil.newHashSet(); + final Set abstractMethods = ContainerUtil.newHashSet(); + for (MemberInfoBase info : infos) { + GrMember member = info.getMember(); + if (member instanceof GrMethod) { + if (!info.isToAbstract() && !isInterfaceTarget) { + movedMembers.add(member); + } + else { + abstractMethods.add((GrMethod)member); + } + } + else { + movedMembers.add(member); + } + } + + final Set allAbstractMethods = new HashSet(abstractMethods); + if (superClass != null) { + for (PsiMethod method : subclass.getMethods()) { + if (!movedMembers.contains(method) && !method.hasModifierProperty(PsiModifier.PRIVATE)) { + if (method.findSuperMethods(superClass).length > 0) { + allAbstractMethods.add(method); + } + } + } + } + + final MultiMap conflicts = new MultiMap(); + + GrRefactoringConflictsUtil.analyzeAccessibilityConflicts(movedMembers, superClass, conflicts, VisibilityUtil.ESCALATE_VISIBILITY, targetRepresentativeElement, + allAbstractMethods); + + if (superClass != null) { + if (movedMembers2Super) { + checkSuperclassMembers(superClass, infos, conflicts); + if (isInterfaceTarget) { + checkInterfaceTarget(infos, conflicts); + } + } else { + final String qualifiedName = superClass.getQualifiedName(); + assert qualifiedName != null; + if (superClass.hasModifierProperty(PsiModifier.PACKAGE_LOCAL)) { + if (!Comparing.strEqual(StringUtil.getPackageName(qualifiedName), targetPackage.getQualifiedName())) { + conflicts.putValue(superClass, RefactoringUIUtil.getDescription(superClass, true) + " won't be accessible from " +RefactoringUIUtil.getDescription(targetPackage, true)); + } + } + } + } + // check if moved methods use other members in the classes between Subclass and Superclass + List checkModuleConflictsList = new ArrayList(); + for (PsiMember member : movedMembers) { + if (member instanceof PsiMethod || member instanceof PsiClass && !(member instanceof PsiCompiledElement)) { + GrClassMemberReferenceVisitor visitor = + movedMembers2Super? new ConflictingUsagesOfSubClassMembers(member, movedMembers, abstractMethods, subclass, superClass, + superClass != null ? null : targetPackage, conflicts, + interfaceContainmentVerifier) + : new ConflictingUsagesOfSuperClassMembers(member, subclass, targetPackage, movedMembers, conflicts); + ((GroovyPsiElement)member).accept(visitor); + } + checkModuleConflictsList.add(member); + } + for (final PsiMethod method : abstractMethods) { + ContainerUtil.addIfNotNull(checkModuleConflictsList, method.getParameterList()); + ContainerUtil.addIfNotNull(checkModuleConflictsList, method.getReturnTypeElement()); + ContainerUtil.addIfNotNull(checkModuleConflictsList, method.getTypeParameterList()); + } + GrRefactoringConflictsUtil.analyzeModuleConflicts(subclass.getProject(), checkModuleConflictsList, new UsageInfo[0], targetRepresentativeElement, conflicts); + final String fqName = subclass.getQualifiedName(); + final String packageName; + if (fqName != null) { + packageName = StringUtil.getPackageName(fqName); + } else { + final PsiFile psiFile = PsiTreeUtil.getParentOfType(subclass, PsiFile.class); + if (psiFile instanceof PsiClassOwner) { + packageName = ((PsiClassOwner)psiFile).getPackageName(); + } else { + packageName = null; + } + } + final boolean toDifferentPackage = !Comparing.strEqual(targetPackage.getQualifiedName(), packageName); + for (final GrMethod abstractMethod : abstractMethods) { + abstractMethod.accept(new GrClassMemberReferenceVisitor(subclass) { + @Override + protected void visitClassMemberReferenceElement(GrMember classMember, GrReferenceElement classMemberReference) { + if (classMember != null && willBeMoved(classMember, movedMembers)) { + boolean isAccessible = false; + if (classMember.hasModifierProperty(PsiModifier.PRIVATE)) { + isAccessible = true; + } + else if (classMember.hasModifierProperty(PsiModifier.PACKAGE_LOCAL) && + toDifferentPackage) { + isAccessible = true; + } + if (isAccessible) { + String message = RefactoringUIUtil.getDescription(abstractMethod, false) + + " uses " + + RefactoringUIUtil.getDescription(classMember, true) + + " which won't be accessible from the subclass."; + message = CommonRefactoringUtil.capitalize(message); + conflicts.putValue(classMember, message); + } + } + } + }); + if (abstractMethod.hasModifierProperty(PsiModifier.PACKAGE_LOCAL) && toDifferentPackage) { + if (!isInterfaceTarget) { + String message = "Can't make " + RefactoringUIUtil.getDescription(abstractMethod, false) + + " abstract as it won't be accessible from the subclass."; + message = CommonRefactoringUtil.capitalize(message); + conflicts.putValue(abstractMethod, message); + } + } + } + return conflicts; + } + + private static void checkInterfaceTarget(MemberInfoBase[] infos, MultiMap conflictsList) { + for (MemberInfoBase info : infos) { + GrMember member = info.getMember(); + + if (member instanceof PsiField || member instanceof PsiClass) { + if (!member.hasModifierProperty(PsiModifier.STATIC) && !(member instanceof PsiClass && ((PsiClass)member).isInterface())) { + String message = RefactoringBundle.message("0.is.not.static.it.cannot.be.moved.to.the.interface", RefactoringUIUtil.getDescription(member, false)); + message = CommonRefactoringUtil.capitalize(message); + conflictsList.putValue(member, message); + } + } + + if (member instanceof PsiField && ((PsiField)member).getInitializer() == null) { + String message = RefactoringBundle.message("0.is.not.initialized.in.declaration.such.fields.are.not.allowed.in.interfaces", + RefactoringUIUtil.getDescription(member, false)); + conflictsList.putValue(member, CommonRefactoringUtil.capitalize(message)); + } + } + } + + private static void checkSuperclassMembers(PsiClass superClass, + MemberInfoBase[] infos, + MultiMap conflictsList) { + for (MemberInfoBase info : infos) { + GrMember member = info.getMember(); + boolean isConflict = false; + if (member instanceof PsiField) { + String name = member.getName(); + + isConflict = superClass.findFieldByName(name, false) != null; + } + else if (member instanceof PsiMethod) { + PsiSubstitutor superSubstitutor = TypeConversionUtil + .getSuperClassSubstitutor(superClass, member.getContainingClass(), PsiSubstitutor.EMPTY); + MethodSignature signature = ((PsiMethod) member).getSignature(superSubstitutor); + final PsiMethod superClassMethod = MethodSignatureUtil.findMethodBySignature(superClass, signature, false); + isConflict = superClassMethod != null; + } + + if (isConflict) { + String message = RefactoringBundle.message("0.already.contains.a.1", + RefactoringUIUtil.getDescription(superClass, false), + RefactoringUIUtil.getDescription(member, false)); + message = CommonRefactoringUtil.capitalize(message); + conflictsList.putValue(superClass, message); + } + + if (member instanceof PsiMethod) { + final PsiMethod method = (PsiMethod)member; + final PsiModifierList modifierList = method.getModifierList(); + if (!modifierList.hasModifierProperty(PsiModifier.PRIVATE)) { + for (PsiClass subClass : ClassInheritorsSearch.search(superClass)) { + if (method.getContainingClass() != subClass) { + MethodSignature signature = ((PsiMethod) member).getSignature(TypeConversionUtil.getSuperClassSubstitutor(superClass, subClass, PsiSubstitutor.EMPTY)); + final PsiMethod wouldBeOverriden = MethodSignatureUtil.findMethodBySignature(subClass, signature, false); + if (wouldBeOverriden != null && VisibilityUtil.compare(VisibilityUtil.getVisibilityModifier(wouldBeOverriden.getModifierList()), + VisibilityUtil.getVisibilityModifier(modifierList)) > 0) { + conflictsList.putValue(wouldBeOverriden, CommonRefactoringUtil.capitalize(RefactoringUIUtil.getDescription(method, true) + " in super class would clash with local method from " + RefactoringUIUtil.getDescription(subClass, true))); + } + } + } + } + } + } + + } + + private static boolean willBeMoved(PsiElement element, Set movedMembers) { + PsiElement parent = element; + while (parent != null) { + if (movedMembers.contains(parent)) return true; + parent = parent.getParent(); + } + return false; + } + + private static class ConflictingUsagesOfSuperClassMembers extends GrClassMemberReferenceVisitor { + + private PsiMember myMember; + private PsiClass mySubClass; + private PsiPackage myTargetPackage; + private Set myMovedMembers; + private MultiMap myConflicts; + + public ConflictingUsagesOfSuperClassMembers(PsiMember member, PsiClass aClass, + PsiPackage targetPackage, + Set movedMembers, + MultiMap conflicts) { + super(aClass); + myMember = member; + mySubClass = aClass; + myTargetPackage = targetPackage; + myMovedMembers = movedMembers; + myConflicts = conflicts; + } + + @Override + protected void visitClassMemberReferenceElement(GrMember classMember, GrReferenceElement ref) { + if (classMember != null && !willBeMoved(classMember, myMovedMembers)) { + final PsiClass containingClass = classMember.getContainingClass(); + if (containingClass != null && + !PsiUtil.isAccessibleFromPackage(classMember, myTargetPackage) && + (classMember.hasModifierProperty(PsiModifier.PACKAGE_LOCAL) || + classMember.hasModifierProperty(PsiModifier.PROTECTED) && !mySubClass.isInheritor(containingClass, true))) { + myConflicts.putValue(myMember, RefactoringUIUtil.getDescription(classMember, true) + " won't be accessible"); + } + } + + } + + } + + private static class ConflictingUsagesOfSubClassMembers extends GrClassMemberReferenceVisitor { + private final PsiElement myScope; + private final Set myMovedMembers; + private final Set myAbstractMethods; + private final PsiClass mySubclass; + private final PsiClass mySuperClass; + private final PsiPackage myTargetPackage; + private final MultiMap myConflictsList; + private final InterfaceContainmentVerifier myInterfaceContainmentVerifier; + + ConflictingUsagesOfSubClassMembers(PsiElement scope, + Set movedMembers, Set abstractMethods, + PsiClass subclass, PsiClass superClass, + PsiPackage targetPackage, MultiMap conflictsList, + InterfaceContainmentVerifier interfaceContainmentVerifier) { + super(subclass); + myScope = scope; + myMovedMembers = movedMembers; + myAbstractMethods = abstractMethods; + mySubclass = subclass; + mySuperClass = superClass; + myTargetPackage = targetPackage; + myConflictsList = conflictsList; + myInterfaceContainmentVerifier = interfaceContainmentVerifier; + } + + @Override + protected void visitClassMemberReferenceElement(GrMember classMember, GrReferenceElement ref) { + if (classMember != null && RefactoringHierarchyUtil.isMemberBetween(mySuperClass, mySubclass, classMember)) { + if (classMember.hasModifierProperty(PsiModifier.STATIC) && !willBeMoved(classMember, myMovedMembers)) { + final boolean isAccessible = mySuperClass != null ? PsiUtil.isAccessible(classMember, mySuperClass, null) : + myTargetPackage != null ? PsiUtil.isAccessibleFromPackage(classMember, myTargetPackage): + classMember.hasModifierProperty(PsiModifier.PUBLIC); + if (!isAccessible) { + String message = RefactoringBundle.message("0.uses.1.which.is.not.accessible.from.the.superclass", + RefactoringUIUtil.getDescription(myScope, false), + RefactoringUIUtil.getDescription(classMember, true)); + message = CommonRefactoringUtil.capitalize(message); + myConflictsList.putValue(classMember, message); + + } + return; + } + if (!myAbstractMethods.contains(classMember) && !willBeMoved(classMember, myMovedMembers)) { + if (!existsInSuperClass(classMember)) { + String message = RefactoringBundle.message("0.uses.1.which.is.not.moved.to.the.superclass", + RefactoringUIUtil.getDescription(myScope, false), + RefactoringUIUtil.getDescription(classMember, true)); + message = CommonRefactoringUtil.capitalize(message); + myConflictsList.putValue(classMember, message); + } + } + } + + } + + private boolean existsInSuperClass(PsiElement classMember) { + if (!(classMember instanceof PsiMethod)) return false; + final PsiMethod method = ((PsiMethod)classMember); + if (myInterfaceContainmentVerifier.checkedInterfacesContain(method)) return true; + if (mySuperClass == null) return false; + final PsiMethod methodBySignature = mySuperClass.findMethodBySignature(method, true); + return methodBySignature != null; + } + } + + +} diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/refactoring/memberPullUp/GrPullUpDialog.java b/plugins/groovy/src/org/jetbrains/plugins/groovy/refactoring/memberPullUp/GrPullUpDialog.java index 5fc3aa9dbf80..89619ad38528 100644 --- a/plugins/groovy/src/org/jetbrains/plugins/groovy/refactoring/memberPullUp/GrPullUpDialog.java +++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/refactoring/memberPullUp/GrPullUpDialog.java @@ -46,8 +46,6 @@ import org.jetbrains.plugins.groovy.refactoring.classMembers.GrMemberSelectionTa import javax.swing.*; import java.awt.*; -import java.awt.event.ItemEvent; -import java.awt.event.ItemListener; import java.util.List; /** @@ -93,20 +91,19 @@ class GrPullUpDialog extends PullUpDialogBase infos = getSelectedMemberInfos(); - GrPullUpHelper processor = - new GrPullUpHelper(myClass, superClass, infos.toArray(new GrMemberInfo[infos.size()]), new DocCommentPolicy(getJavaDocPolicy())); + GrPullUpHelper processor = new GrPullUpHelper(myClass, superClass, infos.toArray(new GrMemberInfo[infos.size()]), new DocCommentPolicy(getJavaDocPolicy())); invokeRefactoring(processor); close(OK_EXIT_CODE); } diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/refactoring/memberPullUp/GrPullUpHandler.java b/plugins/groovy/src/org/jetbrains/plugins/groovy/refactoring/memberPullUp/GrPullUpHandler.java index 533615048f0a..c067d03ee40c 100644 --- a/plugins/groovy/src/org/jetbrains/plugins/groovy/refactoring/memberPullUp/GrPullUpHandler.java +++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/refactoring/memberPullUp/GrPullUpHandler.java @@ -29,7 +29,6 @@ import com.intellij.refactoring.RefactoringActionHandler; import com.intellij.refactoring.RefactoringBundle; import com.intellij.refactoring.classMembers.MemberInfoBase; import com.intellij.refactoring.lang.ElementsHandler; -import com.intellij.refactoring.memberPullUp.PullUpConflictsUtil; import com.intellij.refactoring.ui.ConflictsDialog; import com.intellij.refactoring.util.CommonRefactoringUtil; import com.intellij.refactoring.util.RefactoringHierarchyUtil; @@ -168,8 +167,7 @@ public class GrPullUpHandler implements RefactoringActionHandler, GrPullUpDialog public void run() { final PsiDirectory targetDirectory = superClass.getContainingFile().getContainingDirectory(); final PsiPackage targetPackage = targetDirectory != null ? JavaDirectoryService.getInstance().getPackage(targetDirectory) : null; - conflicts.putAllValues(PullUpConflictsUtil.checkConflicts(infos, mySubclass, superClass, targetPackage, targetDirectory, - dialog.getContainmentVerifier())); + conflicts.putAllValues(GrPullUpConflictsUtil.checkConflicts(infos, mySubclass, superClass, targetPackage, targetDirectory, dialog.getContainmentVerifier())); } }, RefactoringBundle.message("detecting.possible.conflicts"), true, myProject)) { return false; diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/refactoring/memberPullUp/GrPullUpHelper.java b/plugins/groovy/src/org/jetbrains/plugins/groovy/refactoring/memberPullUp/GrPullUpHelper.java index 028e61301631..53a1307a55ba 100644 --- a/plugins/groovy/src/org/jetbrains/plugins/groovy/refactoring/memberPullUp/GrPullUpHelper.java +++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/refactoring/memberPullUp/GrPullUpHelper.java @@ -25,7 +25,6 @@ import com.intellij.psi.codeStyle.CodeStyleSettings; import com.intellij.psi.codeStyle.CodeStyleSettingsManager; import com.intellij.psi.codeStyle.JavaCodeStyleManager; import com.intellij.psi.search.LocalSearchScope; -import com.intellij.psi.search.searches.OverridingMethodsSearch; import com.intellij.psi.search.searches.ReferencesSearch; import com.intellij.psi.util.MethodSignatureUtil; import com.intellij.psi.util.PsiTreeUtil; @@ -45,6 +44,9 @@ import com.intellij.util.IncorrectOperationException; import com.intellij.util.containers.ContainerUtil; import org.codehaus.groovy.runtime.DefaultGroovyMethods; import org.jetbrains.annotations.NotNull; +import org.jetbrains.plugins.groovy.lang.groovydoc.psi.api.GrDocComment; +import org.jetbrains.plugins.groovy.lang.groovydoc.psi.api.GrDocCommentOwner; +import org.jetbrains.plugins.groovy.lang.groovydoc.psi.impl.GrDocCommentUtil; import org.jetbrains.plugins.groovy.lang.psi.GrReferenceElement; import org.jetbrains.plugins.groovy.lang.psi.GroovyPsiElement; import org.jetbrains.plugins.groovy.lang.psi.GroovyPsiElementFactory; @@ -187,13 +189,13 @@ public class GrPullUpHelper extends BaseRefactoringProcessor { // do actual move for (GrMemberInfo info : myMembersToMove) { if (info.getMember() instanceof PsiMethod) { - doMoveMethod(movedMembers, substitutor, info); + doMoveMethod(substitutor, info); } else if (info.getMember() instanceof GrField) { - doMoveField(movedMembers, substitutor, info); + doMoveField(substitutor, info); } else if (info.getMember() instanceof PsiClass) { - doMoveClass(movedMembers, substitutor, info); + doMoveClass(substitutor, info); } } @@ -315,7 +317,7 @@ public class GrPullUpHelper extends BaseRefactoringProcessor { return false; } - private void doMoveMethod(Set movedMembers, PsiSubstitutor substitutor, GrMemberInfo info) { + private void doMoveMethod(PsiSubstitutor substitutor, GrMemberInfo info) { GroovyPsiElementFactory elementFactory = GroovyPsiElementFactory.getInstance(myProject); GrMethod method = (GrMethod)info.getMember(); PsiMethod sibling = method; @@ -346,10 +348,8 @@ public class GrPullUpHelper extends BaseRefactoringProcessor { } replaceMovedMemberTypeParameters(methodCopy, PsiUtil.typeParametersIterable(mySourceClass), substitutor, elementFactory); - myDocCommentPolicy.processCopiedJavaDoc(methodCopy.getDocComment(), method.getDocComment(), isOriginalMethodAbstract); - - final PsiMember movedElement = - anchor != null ? (PsiMember)myTargetSuperClass.addBefore(methodCopy, anchor) : (PsiMember)myTargetSuperClass.add(methodCopy); + final GrMethod movedElement = + anchor != null ? (GrMethod)myTargetSuperClass.addBefore(methodCopy, anchor) : (GrMethod)myTargetSuperClass.add(methodCopy); CodeStyleSettings styleSettings = CodeStyleSettingsManager.getSettings(method.getProject()); if (styleSettings.INSERT_OVERRIDE_ANNOTATION) { if (PsiUtil.isLanguageLevel5OrHigher(mySourceClass) && !myTargetSuperClass.isInterface() || @@ -359,19 +359,16 @@ public class GrPullUpHelper extends BaseRefactoringProcessor { } } - if (!PsiUtil.isLanguageLevel6OrHigher(mySourceClass) && myTargetSuperClass.isInterface()) { - if (isOriginalMethodAbstract) { - for (PsiMethod oMethod : OverridingMethodsSearch.search(method)) { - deleteOverrideAnnotationIfFound(oMethod); - } - } - - deleteOverrideAnnotationIfFound(method); + GrDocComment oldDoc = method.getDocComment(); + if (oldDoc != null) { + GrDocCommentUtil.setDocComment(movedElement, oldDoc); } + myDocCommentPolicy.processCopiedJavaDoc(methodCopy.getDocComment(), oldDoc, isOriginalMethodAbstract); + myMembersAfterMove.add(movedElement); if (isOriginalMethodAbstract) { - method.delete(); + deleteMemberWithDocComment(method); } } else { @@ -382,19 +379,30 @@ public class GrPullUpHelper extends BaseRefactoringProcessor { //fixReferencesToStatic(methodCopy, movedMembers); replaceMovedMemberTypeParameters(methodCopy, PsiUtil.typeParametersIterable(mySourceClass), substitutor, elementFactory); final PsiMethod superClassMethod = myTargetSuperClass.findMethodBySignature(methodCopy, false); + final GrMethod movedElement; if (superClassMethod != null && superClassMethod.hasModifierProperty(PsiModifier.ABSTRACT)) { - superClassMethod.replace(methodCopy); + movedElement = (GrMethod)superClassMethod.replace(methodCopy); } else { - final PsiMember movedElement = - anchor != null ? (PsiMember)myTargetSuperClass.addBefore(methodCopy, anchor) : (PsiMember)myTargetSuperClass.add(methodCopy); + movedElement = + anchor != null ? (GrMethod)myTargetSuperClass.addBefore(methodCopy, anchor) : (GrMethod)myTargetSuperClass.add(methodCopy); myMembersAfterMove.add(movedElement); } - method.delete(); + GrDocCommentUtil.setDocComment(movedElement, method.getDocComment()); + + deleteMemberWithDocComment(method); } } + private static void deleteMemberWithDocComment(GrDocCommentOwner docCommentOwner) { + GrDocComment oldDoc = docCommentOwner.getDocComment(); + if (oldDoc != null) { + oldDoc.delete(); + } + docCommentOwner.delete(); + } + private static void deleteOverrideAnnotationIfFound(PsiMethod oMethod) { final PsiAnnotation annotation = AnnotationUtil.findAnnotation(oMethod, CommonClassNames.JAVA_LANG_OVERRIDE); if (annotation != null) { @@ -594,7 +602,7 @@ public class GrPullUpHelper extends BaseRefactoringProcessor { } } - private void doMoveField(Set movedMembers, PsiSubstitutor substitutor, GrMemberInfo info) { + private void doMoveField(PsiSubstitutor substitutor, GrMemberInfo info) { GroovyPsiElementFactory elementFactory = GroovyPsiElementFactory.getInstance(myProject); GrField field = (GrField)info.getMember(); field.normalizeDeclaration(); @@ -605,10 +613,10 @@ public class GrPullUpHelper extends BaseRefactoringProcessor { } final PsiMember movedElement = (PsiMember)myTargetSuperClass.add(field); myMembersAfterMove.add(movedElement); - field.delete(); + deleteMemberWithDocComment(field); } - private void doMoveClass(Set movedMembers, PsiSubstitutor substitutor, GrMemberInfo info) { + private void doMoveClass(PsiSubstitutor substitutor, GrMemberInfo info) { GroovyPsiElementFactory elementFactory = GroovyPsiElementFactory.getInstance(myProject); GrTypeDefinition aClass = (GrTypeDefinition)info.getMember(); if (Boolean.FALSE.equals(info.getOverrides())) { @@ -654,7 +662,7 @@ public class GrPullUpHelper extends BaseRefactoringProcessor { PsiMember movedElement = (PsiMember)myTargetSuperClass.addAfter(aClass, null); //movedElement = (PsiMember)CodeStyleManager.getInstance(myProject).reformat(movedElement); myMembersAfterMove.add(movedElement); - aClass.delete(); + deleteMemberWithDocComment(aClass); } } diff --git a/plugins/groovy/test/org/jetbrains/plugins/groovy/refactoring/memberPullUp/GrPullUpTest.groovy b/plugins/groovy/test/org/jetbrains/plugins/groovy/refactoring/memberPullUp/GrPullUpTest.groovy index e3d95a7b5c8d..8671950e9015 100644 --- a/plugins/groovy/test/org/jetbrains/plugins/groovy/refactoring/memberPullUp/GrPullUpTest.groovy +++ b/plugins/groovy/test/org/jetbrains/plugins/groovy/refactoring/memberPullUp/GrPullUpTest.groovy @@ -137,12 +137,24 @@ class GrPullUpTest extends LightGroovyTestCase { doTest(false, new MemberDescriptor("foo", PsiMethod)); } - public void _testExtensionMethod() { + public void testPreserveOverride() { doTest(false, new MemberDescriptor("foo", PsiMethod)); } - public void testPreserveOverride() { - doTest(false, new MemberDescriptor("foo", PsiMethod)); + void testImplementsList1() { + doTest(false, new MemberDescriptor("I1", PsiClass)) + } + + void testImplementsList2() { + doTest(false, new MemberDescriptor("I2", PsiClass)) + } + + void testImplementsList3() { + doTest(false, new MemberDescriptor("I1", PsiClass), new MemberDescriptor("I2", PsiClass)) + } + + void testDocCommentInMethod() { + doTest(false, new MemberDescriptor("foo", PsiMethod)) } private void doTest(MemberDescriptor... membersToFind) { diff --git a/plugins/groovy/testdata/parsing/groovy/expressions/gstring/str7.test b/plugins/groovy/testdata/parsing/groovy/expressions/gstring/str7.test index 0b391b71d296..50c4209d7559 100644 --- a/plugins/groovy/testdata/parsing/groovy/expressions/gstring/str7.test +++ b/plugins/groovy/testdata/parsing/groovy/expressions/gstring/str7.test @@ -3,6 +3,9 @@ Groovy script Compound Gstring PsiElement(Gstring begin)('"') + GrStringContentImpl(GString content element) + ASTWrapperPsiElement(Gstring content) + GString injection PsiElement($)('$') Reference expression diff --git a/plugins/groovy/testdata/parsing/groovy/expressions/gstring/str8.test b/plugins/groovy/testdata/parsing/groovy/expressions/gstring/str8.test index 5ab631c32cd4..819cd4b5b810 100644 --- a/plugins/groovy/testdata/parsing/groovy/expressions/gstring/str8.test +++ b/plugins/groovy/testdata/parsing/groovy/expressions/gstring/str8.test @@ -10,6 +10,9 @@ Groovy script PsiElement($)('$') Reference expression PsiElement(identifier)('str') + GrStringContentImpl(GString content element) + ASTWrapperPsiElement(Gstring content) + GString injection PsiElement($)('$') Reference expression @@ -31,6 +34,9 @@ Groovy script Reference expression PsiElement(identifier)('str') PsiElement(})('}') + GrStringContentImpl(GString content element) + ASTWrapperPsiElement(Gstring content) + GString injection PsiElement($)('$') Reference expression diff --git a/plugins/groovy/testdata/parsing/groovy/expressions/gstring/str_error1.test b/plugins/groovy/testdata/parsing/groovy/expressions/gstring/str_error1.test index 231123f3a330..1a7dcbcf15fb 100644 --- a/plugins/groovy/testdata/parsing/groovy/expressions/gstring/str_error1.test +++ b/plugins/groovy/testdata/parsing/groovy/expressions/gstring/str_error1.test @@ -9,5 +9,8 @@ Groovy script PsiWhiteSpace(' ') Compound Gstring PsiElement(Gstring begin)('"') + GrStringContentImpl(GString content element) + ASTWrapperPsiElement(Gstring content) + PsiErrorElement:String end expected \ No newline at end of file diff --git a/plugins/groovy/testdata/parsing/groovy/expressions/gstring/str_error2.test b/plugins/groovy/testdata/parsing/groovy/expressions/gstring/str_error2.test index d8129d9d1f15..7333a8350eb2 100644 --- a/plugins/groovy/testdata/parsing/groovy/expressions/gstring/str_error2.test +++ b/plugins/groovy/testdata/parsing/groovy/expressions/gstring/str_error2.test @@ -10,6 +10,9 @@ Groovy script PsiWhiteSpace(' ') Compound Gstring PsiElement(Gstring begin)('"') + GrStringContentImpl(GString content element) + ASTWrapperPsiElement(Gstring content) + PsiErrorElement:String end expected PsiElement(new line)('\n') diff --git a/plugins/groovy/testdata/parsing/groovy/expressions/gstring/triple/quote_and_slash.test b/plugins/groovy/testdata/parsing/groovy/expressions/gstring/triple/quote_and_slash.test index 09e20dbb688e..afb32495619f 100644 --- a/plugins/groovy/testdata/parsing/groovy/expressions/gstring/triple/quote_and_slash.test +++ b/plugins/groovy/testdata/parsing/groovy/expressions/gstring/triple/quote_and_slash.test @@ -14,8 +14,14 @@ Groovy script Reference expression PsiElement(identifier)('href') PsiElement(})('}') + GrStringContentImpl(GString content element) + ASTWrapperPsiElement(Gstring content) + GString injection PsiElement($)('$') Reference expression PsiElement(identifier)('x') + GrStringContentImpl(GString content element) + ASTWrapperPsiElement(Gstring content) + PsiElement(Gstring end)('"""') \ No newline at end of file diff --git a/plugins/groovy/testdata/parsing/groovy/expressions/path/regexp.test b/plugins/groovy/testdata/parsing/groovy/expressions/path/regexp.test index 36e10294be63..286c6e475ff3 100644 --- a/plugins/groovy/testdata/parsing/groovy/expressions/path/regexp.test +++ b/plugins/groovy/testdata/parsing/groovy/expressions/path/regexp.test @@ -23,6 +23,9 @@ Groovy script PsiElement(.)('.') Compound regular expression PsiElement(regex begin)('/') + GrStringContentImpl(GString content element) + ASTWrapperPsiElement(regex content) + GString injection PsiElement($)('$') Closable block @@ -32,6 +35,9 @@ Groovy script Reference expression PsiElement(identifier)('a') PsiElement(})('}') + GrStringContentImpl(GString content element) + ASTWrapperPsiElement(regex content) + PsiElement(regex end)('/') PsiElement(.)('.') Compound regular expression @@ -42,4 +48,7 @@ Groovy script PsiElement($)('$') Reference expression PsiElement(identifier)('g') + GrStringContentImpl(GString content element) + ASTWrapperPsiElement($/ regex content) + PsiElement($/ regex end)('/$') \ No newline at end of file diff --git a/plugins/groovy/testdata/parsing/groovy/expressions/regex/dollarSlashyEof.test b/plugins/groovy/testdata/parsing/groovy/expressions/regex/dollarSlashyEof.test index fd25456c4b83..11225a164998 100644 --- a/plugins/groovy/testdata/parsing/groovy/expressions/regex/dollarSlashyEof.test +++ b/plugins/groovy/testdata/parsing/groovy/expressions/regex/dollarSlashyEof.test @@ -13,5 +13,8 @@ Groovy script Literal GroovyASTPsiElementImpl($/ regex literal) PsiElement($/ regex begin)('$/') + GrStringContentImpl(GString content element) + ASTWrapperPsiElement($/ regex content) + PsiErrorElement:Dollar slash ending expected \ No newline at end of file diff --git a/plugins/groovy/testdata/parsing/groovy/expressions/regex/regex10.test b/plugins/groovy/testdata/parsing/groovy/expressions/regex/regex10.test index d0647051ea3e..c4658dfa217d 100644 --- a/plugins/groovy/testdata/parsing/groovy/expressions/regex/regex10.test +++ b/plugins/groovy/testdata/parsing/groovy/expressions/regex/regex10.test @@ -38,6 +38,9 @@ Groovy script Reference expression PsiElement(identifier)('b') PsiElement(})('}') + GrStringContentImpl(GString content element) + ASTWrapperPsiElement(Gstring content) + PsiElement(Gstring end)('"') PsiElement(})('}') GrStringContentImpl(GString content element) diff --git a/plugins/groovy/testdata/parsing/groovy/expressions/regex/regex16.test b/plugins/groovy/testdata/parsing/groovy/expressions/regex/regex16.test index b05b726d84a8..afe512535929 100644 --- a/plugins/groovy/testdata/parsing/groovy/expressions/regex/regex16.test +++ b/plugins/groovy/testdata/parsing/groovy/expressions/regex/regex16.test @@ -3,9 +3,15 @@ Groovy script Compound regular expression PsiElement(regex begin)('/') + GrStringContentImpl(GString content element) + ASTWrapperPsiElement(regex content) + GString injection PsiElement($)('$') PsiErrorElement:Identifier or code block expected + GrStringContentImpl(GString content element) + ASTWrapperPsiElement(regex content) + PsiErrorElement:Regex ending expected \ No newline at end of file diff --git a/plugins/groovy/testdata/parsing/groovy/expressions/regex/regex20.test b/plugins/groovy/testdata/parsing/groovy/expressions/regex/regex20.test index e275546ae4bb..b5d112573546 100644 --- a/plugins/groovy/testdata/parsing/groovy/expressions/regex/regex20.test +++ b/plugins/groovy/testdata/parsing/groovy/expressions/regex/regex20.test @@ -20,5 +20,8 @@ Groovy script Literal PsiElement(Integer)('2') PsiElement(})('}') + GrStringContentImpl(GString content element) + ASTWrapperPsiElement(regex content) + PsiElement(regex end)('/') PsiElement())(')') \ No newline at end of file diff --git a/plugins/groovy/testdata/parsing/groovy/expressions/regex/regex22.test b/plugins/groovy/testdata/parsing/groovy/expressions/regex/regex22.test index f13ddd70ad61..34a45ff5fcf1 100644 --- a/plugins/groovy/testdata/parsing/groovy/expressions/regex/regex22.test +++ b/plugins/groovy/testdata/parsing/groovy/expressions/regex/regex22.test @@ -3,6 +3,9 @@ Groovy script Compound regular expression PsiElement(regex begin)('/') + GrStringContentImpl(GString content element) + ASTWrapperPsiElement(regex content) + GString injection PsiElement($)('$') Closable block @@ -12,6 +15,9 @@ Groovy script Literal PsiElement(Integer)('1') PsiElement(})('}') + GrStringContentImpl(GString content element) + ASTWrapperPsiElement(regex content) + GString injection PsiElement($)('$') Closable block @@ -21,4 +27,7 @@ Groovy script Literal PsiElement(Integer)('2') PsiElement(})('}') + GrStringContentImpl(GString content element) + ASTWrapperPsiElement(regex content) + PsiElement(regex end)('/') \ No newline at end of file diff --git a/plugins/groovy/testdata/parsing/groovy/expressions/regex/regex23.test b/plugins/groovy/testdata/parsing/groovy/expressions/regex/regex23.test index 00e27cd6ae22..725c940c2f2d 100644 --- a/plugins/groovy/testdata/parsing/groovy/expressions/regex/regex23.test +++ b/plugins/groovy/testdata/parsing/groovy/expressions/regex/regex23.test @@ -3,6 +3,9 @@ $/${1}${2}/$ Groovy script Compound regular expression PsiElement($/ regex begin)('$/') + GrStringContentImpl(GString content element) + ASTWrapperPsiElement($/ regex content) + GString injection PsiElement($)('$') Closable block @@ -12,6 +15,9 @@ Groovy script Literal PsiElement(Integer)('1') PsiElement(})('}') + GrStringContentImpl(GString content element) + ASTWrapperPsiElement($/ regex content) + GString injection PsiElement($)('$') Closable block @@ -21,4 +27,7 @@ Groovy script Literal PsiElement(Integer)('2') PsiElement(})('}') + GrStringContentImpl(GString content element) + ASTWrapperPsiElement($/ regex content) + PsiElement($/ regex end)('/$') \ No newline at end of file diff --git a/plugins/groovy/testdata/parsing/groovy/expressions/regex/regex8.test b/plugins/groovy/testdata/parsing/groovy/expressions/regex/regex8.test index 92c95df6163c..e93ba9b41268 100644 --- a/plugins/groovy/testdata/parsing/groovy/expressions/regex/regex8.test +++ b/plugins/groovy/testdata/parsing/groovy/expressions/regex/regex8.test @@ -39,6 +39,9 @@ Groovy script Reference expression PsiElement(identifier)('b') PsiElement(})('}') + GrStringContentImpl(GString content element) + ASTWrapperPsiElement(Gstring content) + PsiElement(Gstring end)('"') PsiElement(})('}') GrStringContentImpl(GString content element) diff --git a/plugins/groovy/testdata/parsing/groovy/expressions/regex/regex9.test b/plugins/groovy/testdata/parsing/groovy/expressions/regex/regex9.test index 94e1bb1fbce7..437e647a1229 100644 --- a/plugins/groovy/testdata/parsing/groovy/expressions/regex/regex9.test +++ b/plugins/groovy/testdata/parsing/groovy/expressions/regex/regex9.test @@ -40,6 +40,9 @@ Groovy script Reference expression PsiElement(identifier)('b') PsiElement(})('}') + GrStringContentImpl(GString content element) + ASTWrapperPsiElement(Gstring content) + PsiElement(Gstring end)('"') PsiElement(})('}') GrStringContentImpl(GString content element) diff --git a/plugins/groovy/testdata/parsing/groovy/expressions/special/map5.test b/plugins/groovy/testdata/parsing/groovy/expressions/special/map5.test index 4846811bff19..44036dbd7de5 100644 --- a/plugins/groovy/testdata/parsing/groovy/expressions/special/map5.test +++ b/plugins/groovy/testdata/parsing/groovy/expressions/special/map5.test @@ -7,6 +7,9 @@ Groovy script Argument label Compound Gstring PsiElement(Gstring begin)('"') + GrStringContentImpl(GString content element) + ASTWrapperPsiElement(Gstring content) + GString injection PsiElement($)('$') Reference expression @@ -21,6 +24,9 @@ Groovy script PsiWhiteSpace(' ') Compound Gstring PsiElement(Gstring begin)('"') + GrStringContentImpl(GString content element) + ASTWrapperPsiElement(Gstring content) + GString injection PsiElement($)('$') Reference expression diff --git a/plugins/groovy/testdata/parsing/groovy/statements/blocks/clos3.test b/plugins/groovy/testdata/parsing/groovy/statements/blocks/clos3.test index 35bc8311b51b..f592aba3901d 100644 --- a/plugins/groovy/testdata/parsing/groovy/statements/blocks/clos3.test +++ b/plugins/groovy/testdata/parsing/groovy/statements/blocks/clos3.test @@ -39,6 +39,9 @@ Groovy script Literal PsiElement(Integer)('3') PsiElement(})('}') + GrStringContentImpl(GString content element) + ASTWrapperPsiElement(Gstring content) + PsiErrorElement:String end expected PsiElement(new line)('\n ') @@ -48,5 +51,8 @@ Groovy script Command arguments Compound Gstring PsiElement(Gstring begin)('"""') + GrStringContentImpl(GString content element) + ASTWrapperPsiElement(Gstring content) + PsiErrorElement:String end expected \ No newline at end of file diff --git a/plugins/groovy/testdata/refactoring/pullUp/DocCommentInMethod.groovy b/plugins/groovy/testdata/refactoring/pullUp/DocCommentInMethod.groovy new file mode 100644 index 000000000000..6eff8a1dd94a --- /dev/null +++ b/plugins/groovy/testdata/refactoring/pullUp/DocCommentInMethod.groovy @@ -0,0 +1,9 @@ +class Super { +} + +class Foo extends Super { + /** + * doc + */ + def foo() {} +} diff --git a/plugins/groovy/testdata/refactoring/pullUp/DocCommentInMethod_after.groovy b/plugins/groovy/testdata/refactoring/pullUp/DocCommentInMethod_after.groovy new file mode 100644 index 000000000000..d68f5fdd63cb --- /dev/null +++ b/plugins/groovy/testdata/refactoring/pullUp/DocCommentInMethod_after.groovy @@ -0,0 +1,11 @@ +class Super { + + /** + * doc + */ + def foo() {} +} + +class Foo extends Super { + +} diff --git a/plugins/groovy/testdata/refactoring/pullUp/ImplementsList1.groovy b/plugins/groovy/testdata/refactoring/pullUp/ImplementsList1.groovy new file mode 100644 index 000000000000..ef1ca5f73e1d --- /dev/null +++ b/plugins/groovy/testdata/refactoring/pullUp/ImplementsList1.groovy @@ -0,0 +1,6 @@ +interface I1 {} +interface I2 {} + +class Super {} + +class Foo extends Super implements I1, I2 {} \ No newline at end of file diff --git a/plugins/groovy/testdata/refactoring/pullUp/ImplementsList1_after.groovy b/plugins/groovy/testdata/refactoring/pullUp/ImplementsList1_after.groovy new file mode 100644 index 000000000000..c19959bc231f --- /dev/null +++ b/plugins/groovy/testdata/refactoring/pullUp/ImplementsList1_after.groovy @@ -0,0 +1,6 @@ +interface I1 {} +interface I2 {} + +class Super implements I1 {} + +class Foo extends Super implements I2 {} \ No newline at end of file diff --git a/plugins/groovy/testdata/refactoring/pullUp/ImplementsList2.groovy b/plugins/groovy/testdata/refactoring/pullUp/ImplementsList2.groovy new file mode 100644 index 000000000000..4b93cd12d08f --- /dev/null +++ b/plugins/groovy/testdata/refactoring/pullUp/ImplementsList2.groovy @@ -0,0 +1,6 @@ +interface I1 {} +interface I2 {} + +class Super {} + +class Foo extends Super implements I1, I2 {} \ No newline at end of file diff --git a/plugins/groovy/testdata/refactoring/pullUp/ImplementsList2_after.groovy b/plugins/groovy/testdata/refactoring/pullUp/ImplementsList2_after.groovy new file mode 100644 index 000000000000..d6c79355b72d --- /dev/null +++ b/plugins/groovy/testdata/refactoring/pullUp/ImplementsList2_after.groovy @@ -0,0 +1,6 @@ +interface I1 {} +interface I2 {} + +class Super implements I2 {} + +class Foo extends Super implements I1 {} \ No newline at end of file diff --git a/plugins/groovy/testdata/refactoring/pullUp/ImplementsList3.groovy b/plugins/groovy/testdata/refactoring/pullUp/ImplementsList3.groovy new file mode 100644 index 000000000000..6208d4438ae0 --- /dev/null +++ b/plugins/groovy/testdata/refactoring/pullUp/ImplementsList3.groovy @@ -0,0 +1,6 @@ +interface I1 {} +interface I2 {} + +class Super {} + +class Foo extends Super implements I1, I2 {} \ No newline at end of file diff --git a/plugins/groovy/testdata/refactoring/pullUp/ImplementsList3_after.groovy b/plugins/groovy/testdata/refactoring/pullUp/ImplementsList3_after.groovy new file mode 100644 index 000000000000..3165cdf5c6de --- /dev/null +++ b/plugins/groovy/testdata/refactoring/pullUp/ImplementsList3_after.groovy @@ -0,0 +1,6 @@ +interface I1 {} +interface I2 {} + +class Super implements I1, I2 {} + +class Foo extends Super {} \ No newline at end of file diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/WorkingCopyFormat.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/WorkingCopyFormat.java index 595abe10a83c..26069480a341 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/WorkingCopyFormat.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/WorkingCopyFormat.java @@ -101,6 +101,10 @@ public enum WorkingCopyFormat { return myFormat; } + public boolean isOrGreater(@NotNull WorkingCopyFormat format) { + return myVersion.isOrGreaterThan(format.getVersion().major, format.getVersion().minor); + } + @NotNull public static WorkingCopyFormat from(@NotNull Version version) { WorkingCopyFormat result = UNKNOWN; diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/api/BaseSvnClient.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/api/BaseSvnClient.java index e6bca33d617f..4076a729d88a 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/api/BaseSvnClient.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/api/BaseSvnClient.java @@ -3,10 +3,17 @@ package org.jetbrains.idea.svn.api; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vcs.VcsException; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import org.jetbrains.idea.svn.SvnVcs; import org.jetbrains.idea.svn.WorkingCopyFormat; +import org.jetbrains.idea.svn.commandLine.SvnBindException; +import org.tmatesoft.svn.core.SVNException; +import org.tmatesoft.svn.core.wc.ISVNEventHandler; +import org.tmatesoft.svn.core.wc.SVNEvent; +import org.tmatesoft.svn.core.wc.SVNEventAction; import org.tmatesoft.svn.core.wc2.SvnTarget; +import java.io.File; import java.util.Collection; /** @@ -56,4 +63,20 @@ public abstract class BaseSvnClient implements SvnClient { String.format("%s format is not supported. Supported formats are: %s.", format.getName(), StringUtil.join(supported, ","))); } } + + protected static void callHandler(@Nullable ISVNEventHandler handler, @NotNull SVNEvent event) throws VcsException { + if (handler != null) { + try { + handler.handleEvent(event, 0); + } + catch (SVNException e) { + throw new SvnBindException(e); + } + } + } + + @NotNull + protected static SVNEvent createEvent(@NotNull File path, @Nullable SVNEventAction action) { + return new SVNEvent(path, null, null, 0, null, null, null, null, action, null, null, null, null, null, null); + } } diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/api/ClientFactory.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/api/ClientFactory.java index 27c0d1e30885..7f16724806b2 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/api/ClientFactory.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/api/ClientFactory.java @@ -22,6 +22,7 @@ import org.jetbrains.idea.svn.portable.SvnWcClientI; import org.jetbrains.idea.svn.properties.PropertyClient; import org.jetbrains.idea.svn.revert.RevertClient; import org.jetbrains.idea.svn.update.RelocateClient; +import org.jetbrains.idea.svn.upgrade.UpgradeClient; /** * @author Konstantin Kolosovsky. @@ -51,6 +52,7 @@ public abstract class ClientFactory { protected VersionClient myVersionClient; protected ImportClient myImportClient; protected ExportClient myExportClient; + protected UpgradeClient myUpgradeClient; protected ClientFactory(@NotNull SvnVcs vcs) { myVcs = vcs; @@ -166,6 +168,11 @@ public abstract class ClientFactory { return prepare(myExportClient); } + @NotNull + public UpgradeClient createUpgradeClient() { + return prepare(myUpgradeClient); + } + @NotNull protected T prepare(@NotNull T client) { client.setVcs(myVcs); diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/api/CmdClientFactory.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/api/CmdClientFactory.java index 8b73377b6f9f..de5c33e2b9eb 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/api/CmdClientFactory.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/api/CmdClientFactory.java @@ -23,6 +23,7 @@ import org.jetbrains.idea.svn.update.UpdateClient; import org.jetbrains.idea.svn.properties.CmdPropertyClient; import org.jetbrains.idea.svn.revert.CmdRevertClient; import org.jetbrains.idea.svn.update.CmdRelocateClient; +import org.jetbrains.idea.svn.upgrade.CmdUpgradeClient; /** * @author Konstantin Kolosovsky. @@ -53,6 +54,7 @@ public class CmdClientFactory extends ClientFactory { myVersionClient = new CmdVersionClient(); myImportClient = new CmdImportClient(); myExportClient = new CmdExportClient(); + myUpgradeClient = new CmdUpgradeClient(); statusClient = new SvnCommandLineStatusClient(myVcs); infoClient = new SvnCommandLineInfoClient(myVcs); } diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/api/Repository.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/api/Repository.java new file mode 100644 index 000000000000..4a697c2d7f34 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/api/Repository.java @@ -0,0 +1,21 @@ +package org.jetbrains.idea.svn.api; + +import org.jetbrains.annotations.NotNull; +import org.tmatesoft.svn.core.SVNURL; + +/** + * @author Konstantin Kolosovsky. + */ +public class Repository { + + @NotNull private final SVNURL myUrl; + + public Repository(@NotNull SVNURL url) { + myUrl = url; + } + + @NotNull + public SVNURL getUrl() { + return myUrl; + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/api/SvnKitClientFactory.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/api/SvnKitClientFactory.java index 002820d47f9a..27e025cc57f3 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/api/SvnKitClientFactory.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/api/SvnKitClientFactory.java @@ -23,6 +23,7 @@ import org.jetbrains.idea.svn.portable.SvnkitSvnWcClient; import org.jetbrains.idea.svn.properties.SvnKitPropertyClient; import org.jetbrains.idea.svn.revert.SvnKitRevertClient; import org.jetbrains.idea.svn.update.SvnKitRelocateClient; +import org.jetbrains.idea.svn.upgrade.SvnKitUpgradeClient; /** * @author Konstantin Kolosovsky. @@ -53,6 +54,7 @@ public class SvnKitClientFactory extends ClientFactory { myVersionClient = new SvnKitVersionClient(); myImportClient = new SvnKitImportClient(); myExportClient = new SvnKitExportClient(); + myUpgradeClient = new SvnKitUpgradeClient(); statusClient = new SvnkitSvnStatusClient(myVcs, null); infoClient = new SvnkitSvnWcClient(myVcs); } diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/checkout/SvnCheckoutProvider.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/checkout/SvnCheckoutProvider.java index 4e703ddee543..891471e291c1 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/checkout/SvnCheckoutProvider.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/checkout/SvnCheckoutProvider.java @@ -63,6 +63,7 @@ import java.util.concurrent.atomic.AtomicReference; public class SvnCheckoutProvider implements CheckoutProvider { public void doCheckout(@NotNull final Project project, Listener listener) { + // TODO: Several dialogs is invoked while dialog.show() - seems code should be rewritten to be more transparent CheckoutDialog dialog = new CheckoutDialog(project, listener); dialog.show(); } @@ -308,8 +309,9 @@ public class SvnCheckoutProvider implements CheckoutProvider { private WorkingCopyFormat displayUpgradeDialog(@NotNull WorkingCopyFormat defaultSelection) { final UpgradeFormatDialog dialog = new UpgradeFormatDialog(myProject, myPath, false); - dialog.startLoading(); + final ModalityState dialogState = ModalityState.any(); + dialog.startLoading(); ApplicationManager.getApplication().executeOnPooledThread(new Runnable() { @Override public void run() { @@ -331,7 +333,7 @@ public class SvnCheckoutProvider implements CheckoutProvider { dialog.stopLoading(); } } - }, ModalityState.stateForComponent(dialog.getWindow())); + }, dialogState); } }); diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/checkout/SvnKitCheckoutClient.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/checkout/SvnKitCheckoutClient.java index 54e9063e1937..796d30a80a3a 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/checkout/SvnKitCheckoutClient.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/checkout/SvnKitCheckoutClient.java @@ -24,7 +24,7 @@ import java.util.List; */ public class SvnKitCheckoutClient extends BaseSvnClient implements CheckoutClient { - private static final List SUPPORTED_FORMATS; + public static final List SUPPORTED_FORMATS; static { List supportedFormats = new ArrayList(); diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/CommandUtil.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/CommandUtil.java index 0279ebaf3467..db82214b2b18 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/CommandUtil.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/CommandUtil.java @@ -10,6 +10,7 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.idea.svn.RootUrlInfo; import org.jetbrains.idea.svn.SvnVcs; +import org.jetbrains.idea.svn.api.Repository; import org.jetbrains.idea.svn.checkin.IdeaSvnkitBasedAuthenticationCallback; import org.tmatesoft.svn.core.SVNDepth; import org.tmatesoft.svn.core.SVNException; @@ -215,19 +216,15 @@ public class CommandUtil { } private static SVNURL resolveRepositoryUrl(@NotNull SvnVcs vcs, @NotNull SvnCommandName name, @NotNull SvnTarget target) { - RootUrlInfo rootInfo = target.isFile() - ? vcs.getSvnFileUrlMapping().getWcRootForFilePath(target.getFile()) - : vcs.getSvnFileUrlMapping().getWcRootForUrl(target.getURL().toDecodedString()); - SVNURL repositoryUrl = rootInfo != null ? rootInfo.getRepositoryUrlUrl() : null; + UrlMappingRepositoryProvider urlMappingProvider = new UrlMappingRepositoryProvider(vcs, target); + InfoCommandRepositoryProvider infoCommandProvider = new InfoCommandRepositoryProvider(vcs, target); - // resolve repository url using "svn info" command except the case when that command is executing right now. - if (repositoryUrl == null && !SvnCommandName.info.equals(name)) { - SVNInfo info = getInfo(vcs, target); - - repositoryUrl = info != null ? info.getRepositoryRootURL() : null; + Repository repository = urlMappingProvider.get(); + if (repository == null && !SvnCommandName.info.equals(name)) { + repository = infoCommandProvider.get(); } - return repositoryUrl; + return repository != null ? repository.getUrl() : null; } @NotNull @@ -296,4 +293,67 @@ public class CommandUtil { } return contentsStatus; } + + public interface RepositoryProvider { + + @Nullable + Repository get(); + } + + public static abstract class BaseRepositoryProvider implements RepositoryProvider { + + @NotNull protected final SvnVcs myVcs; + @NotNull protected final SvnTarget myTarget; + + protected BaseRepositoryProvider(@NotNull SvnVcs vcs, @NotNull SvnTarget target) { + myVcs = vcs; + myTarget = target; + } + } + + public static class UrlMappingRepositoryProvider extends BaseRepositoryProvider { + + public UrlMappingRepositoryProvider(@NotNull SvnVcs vcs, @NotNull SvnTarget target) { + super(vcs, target); + } + + @Nullable + @Override + public Repository get() { + RootUrlInfo rootInfo = null; + + if (!myVcs.getProject().isDefault()) { + rootInfo = myTarget.isFile() + ? myVcs.getSvnFileUrlMapping().getWcRootForFilePath(myTarget.getFile()) + : myVcs.getSvnFileUrlMapping().getWcRootForUrl(myTarget.getURL().toDecodedString()); + } + + return rootInfo != null ? new Repository(rootInfo.getRepositoryUrlUrl()) : null; + } + } + + public static class InfoCommandRepositoryProvider extends BaseRepositoryProvider { + + public InfoCommandRepositoryProvider(@NotNull SvnVcs vcs, @NotNull SvnTarget target) { + super(vcs, target); + } + + @Nullable + @Override + public Repository get() { + Repository result; + + if (myTarget.isURL()) { + // TODO: Also could still execute info when target is url - either to use info for authentication or to just get correct repository + // TODO: url in case of "read" operations are allowed anonymously. + result = new Repository(myTarget.getURL()); + } + else { + SVNInfo info = getInfo(myVcs, myTarget); + result = info != null ? new Repository(info.getRepositoryRootURL()) : null; + } + + return result; + } + } } diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/SvnCommandName.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/SvnCommandName.java index 9182d9371c4e..1932d041c26c 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/SvnCommandName.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/SvnCommandName.java @@ -49,7 +49,8 @@ public enum SvnCommandName { lock("lock", true), unlock("unlock", true), importFolder("import", false), - export("export", false); + export("export", false), + upgrade("upgrade", true); private final String myName; private final boolean myWriteable; diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/CopiesPanel.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/CopiesPanel.java index f88c8ebf13eb..1ec176990c87 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/CopiesPanel.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/CopiesPanel.java @@ -17,12 +17,14 @@ package org.jetbrains.idea.svn.dialogs; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.application.ModalityState; +import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.progress.ProgressManager; import com.intellij.openapi.progress.Task; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.Messages; import com.intellij.openapi.util.Comparing; import com.intellij.openapi.vcs.ObjectsConvertor; +import com.intellij.openapi.vcs.VcsException; import com.intellij.openapi.vfs.LocalFileSystem; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.wm.IdeFocusManager; @@ -61,11 +63,13 @@ import java.awt.event.FocusEvent; import java.awt.event.KeyAdapter; import java.awt.event.KeyEvent; import java.io.File; -import java.util.Collections; -import java.util.Comparator; +import java.util.*; import java.util.List; public class CopiesPanel { + + private static final Logger LOG = Logger.getInstance(CopiesPanel.class); + private final Project myProject; private MessageBusConnection myConnection; private SvnVcs myVcs; @@ -98,6 +102,7 @@ public class CopiesPanel { @Override public void run() { final List infoList = myVcs.getAllWcInfos(); + final List supportedFormats = getSupportedFormats(); Runnable runnable = new Runnable() { @Override public void run() { @@ -117,7 +122,7 @@ public class CopiesPanel { myCurrentInfoList = newList; } Collections.sort(infoList, WCComparator.getInstance()); - updateList(infoList); + updateList(infoList, supportedFormats); myRefreshLabel.setEnabled(true); SwingUtilities.invokeLater(focus); } @@ -178,7 +183,7 @@ public class CopiesPanel { return myRefreshLabel; } - private void updateList(final List infoList) { + private void updateList(@NotNull final List infoList, @NotNull final List supportedFormats) { myPanel.removeAll(); final Insets nullIndent = new Insets(1, 3, 1, 0); final GridBagConstraints gb = @@ -217,7 +222,7 @@ public class CopiesPanel { SVNDepth.INFINITY, false, null, wcInfo.getFormat()); } } else if (CHANGE_FORMAT.equals(e.getDescription())) { - changeFormat(wcInfo); + changeFormat(wcInfo, supportedFormats); } else if (MERGE_FROM.equals(e.getDescription())) { if (! checkRoot(root, wcInfo.getPath(), " invoke Merge From")) return; mergeFrom(wcInfo, root, editorPane); @@ -237,7 +242,7 @@ public class CopiesPanel { } }); editorPane.setBorder(null); - editorPane.setText(formatWc(wcInfo)); + editorPane.setText(formatWc(wcInfo, supportedFormats)); final JPanel copyPanel = new JPanel(new GridBagLayout()); @@ -263,14 +268,14 @@ public class CopiesPanel { } @SuppressWarnings("MethodMayBeStatic") - private String formatWc(WCInfo info) { + private String formatWc(@NotNull WCInfo info, @NotNull List supportedFormats) { final StringBuilder sb = new StringBuilder().append("").append(UIUtil.getCssFontDeclaration(UIUtil.getLabelFont())) .append(""); sb.append(""); sb.append(""); - if (! WorkingCopyFormat.ONE_DOT_SEVEN.equals(info.getFormat())) { - // can convert + Collection upgradeFormats = getUpgradeFormats(info, supportedFormats); + if (upgradeFormats.size() > 1) { sb.append(""); } else { @@ -302,6 +307,33 @@ public class CopiesPanel { return sb.toString(); } + @NotNull + private List getSupportedFormats() { + List result = Collections.emptyList(); + + try { + result = myVcs.getFactory().createUpgradeClient().getSupportedFormats(); + } + catch (VcsException e) { + LOG.info(e); + } + + return result; + } + + public static Set getUpgradeFormats(@NotNull WCInfo info, @NotNull List supportedFormats) { + Set canUpgradeTo = EnumSet.noneOf(WorkingCopyFormat.class); + + for (WorkingCopyFormat format : supportedFormats) { + if (format.isOrGreater(info.getFormat())) { + canUpgradeTo.add(format); + } + } + canUpgradeTo.add(info.getFormat()); + + return canUpgradeTo; + } + private void mergeFrom(@NotNull final WCInfo wcInfo, @NotNull final VirtualFile root, @Nullable final Component mergeLabel) { SelectBranchPopup.showForBranchRoot(myProject, root, new SelectBranchPopup.BranchSelectedCallback() { @Override @@ -338,8 +370,10 @@ public class CopiesPanel { }); } - private void changeFormat(final WCInfo wcInfo) { + private void changeFormat(@NotNull final WCInfo wcInfo, @NotNull final List supportedFormats) { ChangeFormatDialog dialog = new ChangeFormatDialog(myProject, new File(wcInfo.getPath()), false, ! wcInfo.isIsWcRoot()); + + dialog.setSupported(supportedFormats); dialog.setData(wcInfo.getFormat()); dialog.show(); if (! dialog.isOK()) { diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/SvnFormatWorker.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/SvnFormatWorker.java index f1310cdd7fd9..d6ccdcf13e7d 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/SvnFormatWorker.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/SvnFormatWorker.java @@ -37,7 +37,6 @@ import org.tmatesoft.svn.core.SVNException; import org.tmatesoft.svn.core.wc.ISVNEventHandler; import org.tmatesoft.svn.core.wc.SVNEvent; import org.tmatesoft.svn.core.wc.SVNEventAction; -import org.tmatesoft.svn.core.wc.SVNWCClient; import java.io.File; import java.util.ArrayList; @@ -122,20 +121,7 @@ public class SvnFormatWorker extends Task.Backgroundable { if (supportsChangelists) { myBeforeChangeLists = ChangeListManager.getInstance(myProject).getChangeListsCopy(); } - final SVNWCClient wcClient = myVcs.createWCClient(); - wcClient.setEventHandler(new ISVNEventHandler() { - @Override - public void handleEvent(SVNEvent event, double progress) throws SVNException { - if (SVNEventAction.UPGRADED_PATH.equals(event.getAction()) && event.getFile() != null) { - indicator.setText2("Upgraded path " + VcsUtil.getPathForProgressPresentation(event.getFile())); - } - } - @Override - public void checkCancelled() throws SVNCancelException { - indicator.checkCanceled(); - } - }); try { for (WCInfo wcInfo : myWcInfos) { File path = new File(wcInfo.getPath()); @@ -143,13 +129,13 @@ public class SvnFormatWorker extends Task.Backgroundable { path = SvnUtil.getWorkingCopyRoot(path); } try { - if (WorkingCopyFormat.ONE_DOT_SEVEN.equals(myNewFormat)) { - indicator.setText(SvnBundle.message("action.Subversion.cleanup.progress.text", path.getAbsolutePath())); - wcClient.doCleanup(path); - } - indicator.setText(SvnBundle.message("action.change.wcopy.format.task.progress.text", path.getAbsolutePath(), - SvnUtil.formatRepresentation(wcInfo.getFormat()), SvnUtil.formatRepresentation(myNewFormat))); - wcClient.doSetWCFormat(path, myNewFormat.getFormat()); + String cleanupMessage = SvnBundle.message("action.Subversion.cleanup.progress.text", path.getAbsolutePath()); + String upgradeMessage = SvnBundle.message("action.change.wcopy.format.task.progress.text", path.getAbsolutePath(), + SvnUtil.formatRepresentation(wcInfo.getFormat()), + SvnUtil.formatRepresentation(myNewFormat)); + ISVNEventHandler handler = createUpgradeHandler(indicator, cleanupMessage, upgradeMessage); + + myVcs.getFactory(path).createUpgradeClient().upgrade(path, myNewFormat, handler); } catch (Throwable e) { myExceptions.add(e); } @@ -166,4 +152,32 @@ public class SvnFormatWorker extends Task.Backgroundable { ApplicationManager.getApplication().getMessageBus().syncPublisher(SvnVcs.WC_CONVERTED).run(); } } + + private static ISVNEventHandler createUpgradeHandler(@NotNull final ProgressIndicator indicator, + @NotNull final String cleanupMessage, + @NotNull final String upgradeMessage) { + return new ISVNEventHandler() { + @Override + public void handleEvent(SVNEvent event, double progress) throws SVNException { + if (event.getFile() != null) { + if (SVNEventAction.UPGRADED_PATH.equals(event.getAction())) { + indicator.setText2("Upgraded path " + VcsUtil.getPathForProgressPresentation(event.getFile())); + } + // fake event indicating cleanup start + if (SVNEventAction.UPDATE_STARTED.equals(event.getAction())) { + indicator.setText(cleanupMessage); + } + // fake event indicating upgrade start + if (SVNEventAction.UPDATE_COMPLETED.equals(event.getAction())) { + indicator.setText(upgradeMessage); + } + } + } + + @Override + public void checkCancelled() throws SVNCancelException { + indicator.checkCanceled(); + } + }; + } } diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/revert/CmdRevertClient.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/revert/CmdRevertClient.java index 2548cecc9a5b..89626215fb1b 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/revert/CmdRevertClient.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/revert/CmdRevertClient.java @@ -60,8 +60,7 @@ public class CmdRevertClient extends BaseSvnClient implements RevertClient { String statusMessage = matcher.group(1); String path = matcher.group(2); - return new SVNEvent(new File(path), null, null, 0, null, null, null, null, createAction(statusMessage), null, null, null, null, null, - null); + return createEvent(new File(path), createAction(statusMessage)); } @Nullable diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/upgrade/CmdUpgradeClient.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/upgrade/CmdUpgradeClient.java new file mode 100644 index 000000000000..0e1a672bb1ef --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/upgrade/CmdUpgradeClient.java @@ -0,0 +1,118 @@ +package org.jetbrains.idea.svn.upgrade; + +import com.intellij.execution.process.ProcessOutputTypes; +import com.intellij.openapi.util.Key; +import com.intellij.openapi.vcs.VcsException; +import com.intellij.util.containers.Convertor; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.idea.svn.WorkingCopyFormat; +import org.jetbrains.idea.svn.api.BaseSvnClient; +import org.jetbrains.idea.svn.api.FileStatusResultParser; +import org.jetbrains.idea.svn.commandLine.CommandUtil; +import org.jetbrains.idea.svn.commandLine.LineCommandListener; +import org.jetbrains.idea.svn.commandLine.SvnCommandName; +import org.tmatesoft.svn.core.wc.ISVNEventHandler; +import org.tmatesoft.svn.core.wc.SVNEvent; +import org.tmatesoft.svn.core.wc.SVNEventAction; +import org.tmatesoft.svn.core.wc2.SvnTarget; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.atomic.AtomicReference; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * @author Konstantin Kolosovsky. + */ +public class CmdUpgradeClient extends BaseSvnClient implements UpgradeClient { + + private static final String STATUS = "\\s*(.+?)\\s*"; + private static final String PATH = "\\s*\'(.*?)\'\\s*"; + private static final Pattern CHANGED_PATH = Pattern.compile(STATUS + PATH); + + @Override + public void upgrade(@NotNull File path, @NotNull WorkingCopyFormat format, @Nullable ISVNEventHandler handler) throws VcsException { + validateFormat(format, getSupportedFormats()); + + // fake event indicating upgrade start + callHandler(handler, createEvent(path, SVNEventAction.UPDATE_COMPLETED)); + + List parameters = new ArrayList(); + + CommandUtil.put(parameters, path); + + // TODO: Add general possibility to invoke "handler.checkCancelled" (process should be killed). But currently upgrade process is not + // TODO: cancellable from UI - and this makes sense. + // for 1.8 - no output + // for 1.7 - output in format "Upgraded ''" + FileStatusResultParser parser = new FileStatusResultParser(CHANGED_PATH, handler, new UpgradeStatusConvertor()); + UpgradeLineCommandListener listener = new UpgradeLineCommandListener(parser); + + CommandUtil.execute(myVcs, SvnTarget.fromFile(path), SvnCommandName.upgrade, parameters, listener); + listener.throwIfException(); + } + + @Override + public List getSupportedFormats() throws VcsException { + List result = new ArrayList(); + + result.add(WorkingCopyFormat.from(myFactory.createVersionClient().getVersion())); + + return result; + } + + private static class UpgradeStatusConvertor implements Convertor { + + public SVNEvent convert(@NotNull Matcher matcher) { + String statusMessage = matcher.group(1); + String path = matcher.group(2); + + return createEvent(new File(path), createAction(statusMessage)); + } + + @Nullable + public static SVNEventAction createAction(@NotNull String code) { + SVNEventAction result = null; + + if ("Upgraded".equals(code)) { + result = SVNEventAction.UPGRADED_PATH; + } + + return result; + } + } + + private static class UpgradeLineCommandListener extends LineCommandListener { + + @NotNull private final FileStatusResultParser parser; + @NotNull private final AtomicReference exception; + + private UpgradeLineCommandListener(@NotNull FileStatusResultParser parser) { + this.parser = parser; + exception = new AtomicReference(); + } + + @Override + public void onLineAvailable(String line, Key outputType) { + if (ProcessOutputTypes.STDOUT.equals(outputType)) { + try { + parser.onLine(line); + } + catch (VcsException e) { + exception.set(e); + } + } + } + + public void throwIfException() throws VcsException { + VcsException e = exception.get(); + + if (e != null) { + throw e; + } + } + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/upgrade/SvnKitUpgradeClient.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/upgrade/SvnKitUpgradeClient.java new file mode 100644 index 000000000000..1aa2ebd687ac --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/upgrade/SvnKitUpgradeClient.java @@ -0,0 +1,64 @@ +package org.jetbrains.idea.svn.upgrade; + +import com.intellij.openapi.vcs.VcsException; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.idea.svn.WorkingCopyFormat; +import org.jetbrains.idea.svn.api.BaseSvnClient; +import org.jetbrains.idea.svn.checkout.SvnKitCheckoutClient; +import org.jetbrains.idea.svn.commandLine.SvnBindException; +import org.tmatesoft.svn.core.SVNException; +import org.tmatesoft.svn.core.wc.ISVNEventHandler; +import org.tmatesoft.svn.core.wc.SVNEventAction; +import org.tmatesoft.svn.core.wc.SVNWCClient; + +import java.io.File; +import java.util.List; + +/** + * @author Konstantin Kolosovsky. + */ +public class SvnKitUpgradeClient extends BaseSvnClient implements UpgradeClient { + + @Override + public void upgrade(@NotNull File path, @NotNull WorkingCopyFormat format, @Nullable ISVNEventHandler handler) throws VcsException { + validateFormat(format, getSupportedFormats()); + + SVNWCClient client = myVcs.createWCClient(); + + client.setEventHandler(handler); + try { + cleanupIfNecessary(path, format, client, handler); + upgrade(path, format, client, handler); + } + catch (SVNException e) { + throw new SvnBindException(e); + } + } + + @Override + public List getSupportedFormats() throws VcsException { + return SvnKitCheckoutClient.SUPPORTED_FORMATS; + } + + private static void cleanupIfNecessary(@NotNull File path, + @NotNull WorkingCopyFormat format, + @NotNull SVNWCClient client, + @Nullable ISVNEventHandler handler) throws SVNException, VcsException { + // cleanup is executed only for SVNKit as it could handle both 1.6 and 1.7 formats + if (WorkingCopyFormat.ONE_DOT_SEVEN.equals(format)) { + // fake event indicating cleanup start + callHandler(handler, createEvent(path, SVNEventAction.UPDATE_STARTED)); + client.doCleanup(path); + } + } + + private static void upgrade(@NotNull File path, + @NotNull WorkingCopyFormat format, + @NotNull SVNWCClient client, + @Nullable ISVNEventHandler handler) throws SVNException, VcsException { + // fake event indicating upgrade start + callHandler(handler, createEvent(path, SVNEventAction.UPDATE_COMPLETED)); + client.doSetWCFormat(path, format.getFormat()); + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/upgrade/UpgradeClient.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/upgrade/UpgradeClient.java new file mode 100644 index 000000000000..2d22acf08c45 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/upgrade/UpgradeClient.java @@ -0,0 +1,21 @@ +package org.jetbrains.idea.svn.upgrade; + +import com.intellij.openapi.vcs.VcsException; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.idea.svn.WorkingCopyFormat; +import org.jetbrains.idea.svn.api.SvnClient; +import org.tmatesoft.svn.core.wc.ISVNEventHandler; + +import java.io.File; +import java.util.List; + +/** + * @author Konstantin Kolosovsky. + */ +public interface UpgradeClient extends SvnClient { + + void upgrade(@NotNull File path, @NotNull WorkingCopyFormat format, @Nullable ISVNEventHandler handler) throws VcsException; + + List getSupportedFormats() throws VcsException; +}
").append(info.getPath()).append("
URL:").append(info.getRootUrl()).append("
Format:").append(info.getFormat().getName()).append("Change