diff --git a/.idea/libraries/Kryo.xml b/.idea/libraries/Kryo.xml new file mode 100644 index 000000000000..b11f03b83ef2 --- /dev/null +++ b/.idea/libraries/Kryo.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml index ca7b52d58ab5..2805b881df85 100644 --- a/.idea/modules.xml +++ b/.idea/modules.xml @@ -65,6 +65,7 @@ + diff --git a/build/scripts/layouts.gant b/build/scripts/layouts.gant index b0315724e8e7..f91ac85d7449 100644 --- a/build/scripts/layouts.gant +++ b/build/scripts/layouts.gant @@ -336,6 +336,10 @@ public def layoutCommunityPlugins(String home) { } layoutPlugin("gradle") { + jar("gradle-jps-plugin.jar") { + module("gradle-jps-plugin") + } + jar("gradle-tooling-extension-api.jar") { module("gradle-tooling-extension-api") } @@ -344,6 +348,13 @@ public def layoutCommunityPlugins(String home) { module("gradle-tooling-extension-impl") } fileset(dir: "$home/plugins/gradle/lib") { include(name: "*.jar") } + // add kryo lib + fileset(dir: "$home/lib/") { + include(name: "kryo-*.jar") + include(name: "reflectasm-*.jar") + include(name: "objenesis-*.jar") + include(name: "minlog-*.jar") + } } layoutPlugin("git4idea") { @@ -753,6 +764,7 @@ def layoutJps(String home, String targetDir, String buildNumber, Closure additio jar("maven-jps-plugin.jar") { module("maven-jps-plugin") } + jar("gradle-jps-plugin.jar") { module("gradle-jps-plugin") } fileset(dir: "$home/plugins/maven/maven3-server-impl/lib/maven3/lib") {include(name: "plexus-utils-*.jar")} jar("eclipse-jps-plugin.jar") { diff --git a/java/compiler/impl/compiler-impl.iml b/java/compiler/impl/compiler-impl.iml index 08d3ca179fc0..c6e3e4f81eab 100644 --- a/java/compiler/impl/compiler-impl.iml +++ b/java/compiler/impl/compiler-impl.iml @@ -28,6 +28,7 @@ + diff --git a/java/compiler/impl/src/org/jetbrains/builtInWebServer/ArtifactProjectRootsProvider.java b/java/compiler/impl/src/org/jetbrains/builtInWebServer/ArtifactProjectRootsProvider.java new file mode 100644 index 000000000000..c8d253141180 --- /dev/null +++ b/java/compiler/impl/src/org/jetbrains/builtInWebServer/ArtifactProjectRootsProvider.java @@ -0,0 +1,40 @@ +package org.jetbrains.builtInWebServer; + +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Pair; +import com.intellij.openapi.vfs.VfsUtilCore; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.packaging.artifacts.Artifact; +import com.intellij.packaging.artifacts.ArtifactManager; +import com.intellij.util.PairFunction; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +final class ArtifactProjectRootsProvider extends ProjectRootsProvider.PrefixlessProjectRootsProvider { + @Nullable + @Override + public Pair> resolve(@NotNull String path, @NotNull Project project, @NotNull PairFunction resolver) { + for (Artifact artifact : ArtifactManager.getInstance(project).getArtifacts()) { + VirtualFile root = artifact.getOutputFile(); + if (root != null) { + VirtualFile file = root.findFileByRelativePath(path); + if (file != null) { + return Pair.create(file, new Pair(root, null)); + } + } + } + return null; + } + + @Nullable + @Override + public Pair getRoot(@NotNull VirtualFile file, @NotNull Project project) { + for (Artifact artifact : ArtifactManager.getInstance(project).getArtifacts()) { + VirtualFile root = artifact.getOutputFile(); + if (root != null && VfsUtilCore.isAncestor(root, file, true)) { + return Pair.create(root, null); + } + } + return null; + } +} \ No newline at end of file diff --git a/java/debugger/impl/src/com/intellij/debugger/settings/CompoundRendererConfigurable.java b/java/debugger/impl/src/com/intellij/debugger/settings/CompoundRendererConfigurable.java index 58b1be02b18f..a4484cccc1fe 100644 --- a/java/debugger/impl/src/com/intellij/debugger/settings/CompoundRendererConfigurable.java +++ b/java/debugger/impl/src/com/intellij/debugger/settings/CompoundRendererConfigurable.java @@ -25,13 +25,10 @@ import com.intellij.debugger.impl.DebuggerUtilsEx; import com.intellij.debugger.ui.DebuggerExpressionTextField; import com.intellij.debugger.ui.JavaDebuggerSupport; import com.intellij.debugger.ui.tree.render.*; -import com.intellij.openapi.Disposable; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.application.ModalityState; import com.intellij.openapi.editor.Document; -import com.intellij.openapi.options.UnnamedConfigurable; import com.intellij.openapi.project.Project; -import com.intellij.openapi.util.Disposer; import com.intellij.openapi.util.Pair; import com.intellij.psi.*; import com.intellij.psi.search.GlobalSearchScope; @@ -51,54 +48,33 @@ import java.awt.event.*; import java.util.ArrayList; import java.util.List; -/** - * @author Eugene Zhuravlev - * Date: Feb 24, 2005 - */ -public class CompoundRendererConfigurable implements UnnamedConfigurable { +class CompoundRendererConfigurable extends JPanel { private CompoundReferenceRenderer myRenderer; private CompoundReferenceRenderer myOriginalRenderer; private Project myProject; - private ClassNameEditorWithBrowseButton myClassNameField; - private JRadioButton myRbDefaultLabel; - private JRadioButton myRbExpressionLabel; - private JRadioButton myRbDefaultChildrenRenderer; - private JRadioButton myRbExpressionChildrenRenderer; - private JRadioButton myRbListChildrenRenderer; - private DebuggerExpressionTextField myLabelEditor; - private DebuggerExpressionTextField myChildrenEditor; - private DebuggerExpressionTextField myChildrenExpandedEditor; + private final ClassNameEditorWithBrowseButton myClassNameField; + private final JRadioButton myRbDefaultLabel; + private final JRadioButton myRbExpressionLabel; + private final JRadioButton myRbDefaultChildrenRenderer; + private final JRadioButton myRbExpressionChildrenRenderer; + private final JRadioButton myRbListChildrenRenderer; + private final DebuggerExpressionTextField myLabelEditor; + private final DebuggerExpressionTextField myChildrenEditor; + private final DebuggerExpressionTextField myChildrenExpandedEditor; private DebuggerExpressionTextField myListChildrenEditor; - private JLabel myExpandedLabel; - private JPanel myMainPanel; + private final JLabel myExpandedLabel; private JBTable myTable; @NonNls private static final String EMPTY_PANEL_ID = "EMPTY"; @NonNls private static final String DATA_PANEL_ID = "DATA"; private static final int NAME_TABLE_COLUMN = 0; private static final int EXPRESSION_TABLE_COLUMN = 1; - public void setRenderer(NodeRenderer renderer) { - if (renderer instanceof CompoundReferenceRenderer) { - myRenderer = (CompoundReferenceRenderer)renderer; - myOriginalRenderer = (CompoundReferenceRenderer)renderer.clone(); - } - else { - myRenderer = myOriginalRenderer = null; - } - reset(); - } + public CompoundRendererConfigurable() { + super(new CardLayout()); - public CompoundReferenceRenderer getRenderer() { - return myRenderer; - } - - @Override - @NotNull - public JComponent createComponent() { if (myProject == null) { myProject = JavaDebuggerSupport.getContextProjectForEditorFieldsInDebuggerConfigurables(); } - final JPanel panel = new JPanel(new GridBagLayout()); myRbDefaultLabel = new JRadioButton(DebuggerBundle.message("label.compound.renderer.configurable.use.default.renderer")); myRbExpressionLabel = new JRadioButton(DebuggerBundle.message("label.compound.renderer.configurable.use.expression")); @@ -141,21 +117,14 @@ public class CompoundRendererConfigurable implements UnnamedConfigurable { } } }, myProject); - final EditorTextField textField = myClassNameField.getEditorTextField(); - final FocusAdapter updateContextListener = new FocusAdapter() { + myClassNameField.getEditorTextField().addFocusListener(new FocusAdapter() { @Override public void focusLost(@NotNull FocusEvent e) { updateContext(myClassNameField.getText()); } - }; - textField.addFocusListener(updateContextListener); - Disposer.register(myClassNameField, new Disposable() { - @Override - public void dispose() { - textField.removeFocusListener(updateContextListener); - } }); + JPanel panel = new JPanel(new GridBagLayout()); panel.add(new JLabel(DebuggerBundle.message("label.compound.renderer.configurable.apply.to")), new GridBagConstraints(0, GridBagConstraints.RELATIVE, 1, 1, 1.0, 0.0, GridBagConstraints.NORTHWEST, GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 0)); @@ -196,11 +165,23 @@ public class CompoundRendererConfigurable implements UnnamedConfigurable { panel.add(myChildrenListEditor, new GridBagConstraints(0, GridBagConstraints.RELATIVE, 1, 1, 1.0, 1.0, GridBagConstraints.NORTHWEST, GridBagConstraints.BOTH, new Insets(4, 30, 0, 0), 0, 0)); + add(new JPanel(), EMPTY_PANEL_ID); + add(panel, DATA_PANEL_ID); + } - myMainPanel = new JPanel(new CardLayout()); - myMainPanel.add(new JPanel(), EMPTY_PANEL_ID); - myMainPanel.add(panel, DATA_PANEL_ID); - return myMainPanel; + public void setRenderer(NodeRenderer renderer) { + if (renderer instanceof CompoundReferenceRenderer) { + myRenderer = (CompoundReferenceRenderer)renderer; + myOriginalRenderer = (CompoundReferenceRenderer)renderer.clone(); + } + else { + myRenderer = myOriginalRenderer = null; + } + reset(); + } + + public CompoundReferenceRenderer getRenderer() { + return myRenderer; } private void updateContext(final String qName) { @@ -305,7 +286,6 @@ public class CompoundRendererConfigurable implements UnnamedConfigurable { }).createPanel(); } - @Override public boolean isModified() { if (myRenderer == null) { return false; @@ -315,7 +295,6 @@ public class CompoundRendererConfigurable implements UnnamedConfigurable { return !DebuggerUtilsEx.externalizableEqual(cloned, myOriginalRenderer); } - @Override public void apply() { if (myRenderer == null) { return; @@ -347,10 +326,9 @@ public class CompoundRendererConfigurable implements UnnamedConfigurable { renderer.setClassName(myClassNameField.getText()); } - @Override public void reset() { final TextWithImports emptyExpressionFragment = new TextWithImportsImpl(CodeFragmentKind.EXPRESSION, ""); - ((CardLayout)myMainPanel.getLayout()).show(myMainPanel, myRenderer == null ? EMPTY_PANEL_ID : DATA_PANEL_ID); + ((CardLayout)getLayout()).show(this, myRenderer == null ? EMPTY_PANEL_ID : DATA_PANEL_ID); if (myRenderer == null) { return; } @@ -398,23 +376,6 @@ public class CompoundRendererConfigurable implements UnnamedConfigurable { updateContext(className); } - @Override - public void disposeUIResources() { - myRenderer = null; - myOriginalRenderer = null; - myLabelEditor.dispose(); - myChildrenEditor.dispose(); - myChildrenExpandedEditor.dispose(); - myListChildrenEditor.dispose(); - Disposer.dispose(myClassNameField); - myLabelEditor = null; - myChildrenEditor = null; - myChildrenExpandedEditor = null; - myListChildrenEditor = null; - myClassNameField = null; - myProject = null; - } - private MyTableModel getTableModel() { return (MyTableModel)myTable.getModel(); } diff --git a/java/debugger/impl/src/com/intellij/debugger/settings/UserRenderersConfigurable.java b/java/debugger/impl/src/com/intellij/debugger/settings/UserRenderersConfigurable.java index a9197da82568..3967d2239455 100644 --- a/java/debugger/impl/src/com/intellij/debugger/settings/UserRenderersConfigurable.java +++ b/java/debugger/impl/src/com/intellij/debugger/settings/UserRenderersConfigurable.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2009 JetBrains s.r.o. + * Copyright 2000-2014 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -35,10 +35,6 @@ import java.awt.*; import java.util.ArrayList; import java.util.List; -/** - * @author Eugene Zhuravlev - * Date: Feb 19, 2005 - */ public class UserRenderersConfigurable implements ConfigurableUi { private static final Icon ADD_ICON = IconUtil.getAddIcon(); private static final Icon REMOVE_ICON = IconUtil.getRemoveIcon(); @@ -55,16 +51,9 @@ public class UserRenderersConfigurable implements ConfigurableUi Breakpoint javaBreakpoint = BreakpointManager.getJavaBreakpoint(breakpoint); if (javaBreakpoint != null) { PsiClass aClass = javaBreakpoint.getPsiClass(); - if (aClass != null && aClass.getContainingFile() != null) { + if (aClass != null && aClass.getContainingFile() != null && aClass.getTextOffset() >= 0) { return XDebuggerUtil.getInstance().createPositionByOffset(aClass.getContainingFile().getVirtualFile(), aClass.getTextOffset()); } } diff --git a/java/java-impl/src/com/intellij/psi/codeStyle/arrangement/JavaArrangementVisitor.java b/java/java-impl/src/com/intellij/psi/codeStyle/arrangement/JavaArrangementVisitor.java index 3651fa1afe3a..ff1170b64bef 100644 --- a/java/java-impl/src/com/intellij/psi/codeStyle/arrangement/JavaArrangementVisitor.java +++ b/java/java-impl/src/com/intellij/psi/codeStyle/arrangement/JavaArrangementVisitor.java @@ -280,6 +280,7 @@ public class JavaArrangementVisitor extends JavaRecursiveElementVisitor { if (ref instanceof PsiField && containingClassFields.contains(ref)) { referencedElements.add((PsiField)ref); } + super.visitReferenceExpression(expression); } }); diff --git a/java/java-impl/src/com/intellij/psi/impl/search/JspIndexPatternBuilder.java b/java/java-impl/src/com/intellij/psi/impl/search/JspIndexPatternBuilder.java index a8316f8dc0ee..eaa93875ba15 100644 --- a/java/java-impl/src/com/intellij/psi/impl/search/JspIndexPatternBuilder.java +++ b/java/java-impl/src/com/intellij/psi/impl/search/JspIndexPatternBuilder.java @@ -18,6 +18,7 @@ package com.intellij.psi.impl.search; import com.intellij.lang.LanguageParserDefinitions; import com.intellij.lang.ParserDefinition; import com.intellij.lexer.Lexer; +import com.intellij.openapi.editor.impl.EditorHighlighterCache; import com.intellij.psi.JspPsiUtil; import com.intellij.psi.PsiFile; import com.intellij.psi.impl.source.tree.StdTokenSets; @@ -34,7 +35,7 @@ public class JspIndexPatternBuilder implements IndexPatternBuilder { @Override public Lexer getIndexingLexer(@NotNull final PsiFile file) { if (JspPsiUtil.isInJspFile(file)) { - return LexerEditorHighlighterLexer.getLexerBasedOnLexerHighlighter(file.getText(), file.getVirtualFile(), file.getProject()); + return EditorHighlighterCache.getLexerBasedOnLexerHighlighter(file.getText(), file.getVirtualFile(), file.getProject()); } return null; diff --git a/java/java-psi-impl/src/com/intellij/psi/impl/PsiImplUtil.java b/java/java-psi-impl/src/com/intellij/psi/impl/PsiImplUtil.java index bcf0decb69b7..938b40f51bb8 100644 --- a/java/java-psi-impl/src/com/intellij/psi/impl/PsiImplUtil.java +++ b/java/java-psi-impl/src/com/intellij/psi/impl/PsiImplUtil.java @@ -687,4 +687,33 @@ public class PsiImplUtil { return element instanceof LeafElement && tokenSet.contains(((LeafElement)element).getElementType()); } + public static PsiType buildTypeFromTypeString(@NotNull final String typeName, @NotNull final PsiElement context, @NotNull final PsiFile psiFile) { + PsiType resultType; + final PsiManager psiManager = psiFile.getManager(); + + if (typeName.indexOf('<') != -1 || typeName.indexOf('[') != -1 || typeName.indexOf('.') == -1) { + try { + return JavaPsiFacade.getInstance(psiManager.getProject()).getElementFactory().createTypeFromText(typeName, context); + } catch(Exception ex) {} // invalid syntax will produce unresolved class type + } + + PsiClass aClass = JavaPsiFacade.getInstance(psiManager.getProject()).findClass(typeName, context.getResolveScope()); + + if (aClass == null) { + final LightClassReference ref = new LightClassReference( + psiManager, + PsiNameHelper.getShortClassName(typeName), + typeName, + PsiSubstitutor.EMPTY, + psiFile + ); + resultType = new PsiClassReferenceType(ref, null); + } else { + PsiElementFactory factory = JavaPsiFacade.getInstance(psiManager.getProject()).getElementFactory(); + PsiSubstitutor substitutor = factory.createRawSubstitutor(aClass); + resultType = factory.createType(aClass, substitutor); + } + + return resultType; + } } diff --git a/java/java-tests/testData/find/findInEditor/BasicFind.gold b/java/java-tests/testData/find/findInEditor/BasicFind.gold index f1ea3fa66d4c..46486765486d 100644 --- a/java/java-tests/testData/find/findInEditor/BasicFind.gold +++ b/java/java-tests/testData/find/findInEditor/BasicFind.gold @@ -3,8 +3,7 @@ myStringToFind =a myStringToReplace = isReplaceState =false isWholeWordsOnly =false -isInStringLiterals =false -isInComments =false +searchContext =ANY isFromCursor =true isForward =true isGlobal =true diff --git a/java/java-tests/testData/find/findInEditor/EmacsLikeFallback.gold b/java/java-tests/testData/find/findInEditor/EmacsLikeFallback.gold index c7676944b8da..bfda616a3ac1 100644 --- a/java/java-tests/testData/find/findInEditor/EmacsLikeFallback.gold +++ b/java/java-tests/testData/find/findInEditor/EmacsLikeFallback.gold @@ -3,8 +3,7 @@ myStringToFind =a myStringToReplace = isReplaceState =false isWholeWordsOnly =false -isInStringLiterals =false -isInComments =false +searchContext =ANY isFromCursor =true isForward =true isGlobal =true @@ -38,8 +37,7 @@ myStringToFind =ab myStringToReplace = isReplaceState =false isWholeWordsOnly =false -isInStringLiterals =false -isInComments =false +searchContext =ANY isFromCursor =true isForward =true isGlobal =true @@ -72,8 +70,7 @@ myStringToFind =a myStringToReplace = isReplaceState =false isWholeWordsOnly =false -isInStringLiterals =false -isInComments =false +searchContext =ANY isFromCursor =true isForward =true isGlobal =true diff --git a/java/java-tests/testData/find/findInEditor/ReplacementWithEmptyString.gold b/java/java-tests/testData/find/findInEditor/ReplacementWithEmptyString.gold index eb2cdb2c1240..a4d44cbfa445 100644 --- a/java/java-tests/testData/find/findInEditor/ReplacementWithEmptyString.gold +++ b/java/java-tests/testData/find/findInEditor/ReplacementWithEmptyString.gold @@ -3,8 +3,7 @@ myStringToFind = myStringToReplace = isReplaceState =false isWholeWordsOnly =false -isInStringLiterals =false -isInComments =false +searchContext =ANY isFromCursor =true isForward =true isGlobal =true @@ -34,8 +33,7 @@ myStringToFind =a myStringToReplace = isReplaceState =false isWholeWordsOnly =false -isInStringLiterals =false -isInComments =false +searchContext =ANY isFromCursor =true isForward =true isGlobal =true @@ -67,8 +65,7 @@ myStringToFind =a myStringToReplace = isReplaceState =true isWholeWordsOnly =false -isInStringLiterals =false -isInComments =false +searchContext =ANY isFromCursor =true isForward =true isGlobal =true @@ -102,8 +99,7 @@ myStringToFind =a myStringToReplace = isReplaceState =true isWholeWordsOnly =false -isInStringLiterals =false -isInComments =false +searchContext =ANY isFromCursor =true isForward =true isGlobal =true diff --git a/java/java-tests/testSrc/com/intellij/find/FindManagerTest.java b/java/java-tests/testSrc/com/intellij/find/FindManagerTest.java index 33f43ef93fa7..f83d836bb55a 100644 --- a/java/java-tests/testSrc/com/intellij/find/FindManagerTest.java +++ b/java/java-tests/testSrc/com/intellij/find/FindManagerTest.java @@ -615,7 +615,7 @@ public class FindManagerTest extends DaemonAnalyzerTestCase { FindModel findModel = FindManagerTestUtils.configureFindModel("^done$"); findModel.setRegularExpressions(true); - findModel.setSearchContext(FindModel.SearchContext.IN_STRINGS); + findModel.setSearchContext(FindModel.SearchContext.IN_STRING_LITERALS); String text = "\"done\"; 'done'; 'done' \"done2\""; FindManagerTestUtils.runFindForwardAndBackward(myFindManager, findModel, text, "java"); @@ -650,4 +650,42 @@ public class FindManagerTest extends DaemonAnalyzerTestCase { findModel.setWholeWordsOnly(true); assertSize(1, findUsages(findModel)); } + + public void testFindExceptComments() { + FindModel findModel = FindManagerTestUtils.configureFindModel("done"); + + String prefix = "/*"; + String text = prefix + "done*/done"; + + findModel.setSearchContext(FindModel.SearchContext.EXCEPT_COMMENTS); + LightVirtualFile file = new LightVirtualFile("A.java", text); + + FindResult findResult = myFindManager.findString(text, prefix.length(), findModel, file); + assertTrue(findResult.isStringFound()); + assertTrue(findResult.getStartOffset() > prefix.length()); + + findModel.setRegularExpressions(true); + findResult = myFindManager.findString(text, prefix.length(), findModel, file); + assertTrue(findResult.isStringFound()); + assertTrue(findResult.getStartOffset() > prefix.length()); + } + + public void testFindExceptLiterals() { + FindModel findModel = FindManagerTestUtils.configureFindModel("done"); + + String prefix = "\""; + String text = prefix + "done\"done"; + + findModel.setSearchContext(FindModel.SearchContext.EXCEPT_STRING_LITERALS); + LightVirtualFile file = new LightVirtualFile("A.java", text); + + FindResult findResult = myFindManager.findString(text, prefix.length(), findModel, file); + assertTrue(findResult.isStringFound()); + assertTrue(findResult.getStartOffset() > prefix.length()); + + findModel.setRegularExpressions(true); + findResult = myFindManager.findString(text, prefix.length(), findModel, file); + assertTrue(findResult.isStringFound()); + assertTrue(findResult.getStartOffset() > prefix.length()); + } } diff --git a/java/java-tests/testSrc/com/intellij/psi/codeStyle/arrangement/JavaRearrangerFieldReferenceTest.groovy b/java/java-tests/testSrc/com/intellij/psi/codeStyle/arrangement/JavaRearrangerFieldReferenceTest.groovy index d11a34f6e376..7b1861b9daec 100644 --- a/java/java-tests/testSrc/com/intellij/psi/codeStyle/arrangement/JavaRearrangerFieldReferenceTest.groovy +++ b/java/java-tests/testSrc/com/intellij/psi/codeStyle/arrangement/JavaRearrangerFieldReferenceTest.groovy @@ -385,4 +385,28 @@ public class test { ] ) } + + void "test IDEA-128071"() { + doTest( + initial: ''' +public class FormatTest { + public int a = 3; + private static final String FACEBOOK_CLIENT_ID = ""; + public static final String FACEBOOK_OAUTH_URL = "".concat(FACEBOOK_CLIENT_ID).concat(""); +} +''', + expected: ''' +public class FormatTest { + private static final String FACEBOOK_CLIENT_ID = ""; + public static final String FACEBOOK_OAUTH_URL = "".concat(FACEBOOK_CLIENT_ID).concat(""); + public int a = 3; +} +''', + rules: [ + rule(PUBLIC, STATIC, FINAL), + rule(PRIVATE, STATIC, FINAL), + rule(PUBLIC) + ] + ) + } } diff --git a/java/testFramework/src/com/intellij/find/FindManagerTestUtils.java b/java/testFramework/src/com/intellij/find/FindManagerTestUtils.java index ea08e96427c0..b65b58b8638c 100644 --- a/java/testFramework/src/com/intellij/find/FindManagerTestUtils.java +++ b/java/testFramework/src/com/intellij/find/FindManagerTestUtils.java @@ -24,7 +24,7 @@ public class FindManagerTestUtils { } public static void runFindInCommentsAndLiterals(FindManager findManager, FindModel findModel, String text, String ext) { - findModel.setSearchContext(FindModel.SearchContext.IN_STRINGS); + findModel.setSearchContext(FindModel.SearchContext.IN_STRING_LITERALS); runFindForwardAndBackward(findManager, findModel, text, ext); findModel.setSearchContext(FindModel.SearchContext.IN_COMMENTS); diff --git a/jps/model-serialization/src/org/jetbrains/jps/model/serialization/PathMacroUtil.java b/jps/model-serialization/src/org/jetbrains/jps/model/serialization/PathMacroUtil.java index 6214412c4a52..cf87eb5744b3 100644 --- a/jps/model-serialization/src/org/jetbrains/jps/model/serialization/PathMacroUtil.java +++ b/jps/model-serialization/src/org/jetbrains/jps/model/serialization/PathMacroUtil.java @@ -16,16 +16,19 @@ package org.jetbrains.jps.model.serialization; import com.intellij.openapi.application.PathManager; -import com.intellij.openapi.util.io.FileUtil; import com.intellij.openapi.util.text.StringUtil; +import com.intellij.util.ObjectUtils; import com.intellij.util.SystemProperties; -import com.intellij.util.containers.HashMap; +import com.intellij.util.containers.ContainerUtil; import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.io.File; import java.util.Map; +import static com.intellij.openapi.util.io.FileUtil.toSystemIndependentName; + /** * @author nik */ @@ -34,9 +37,16 @@ public class PathMacroUtil { @NonNls public static final String MODULE_DIR_MACRO_NAME = "MODULE_DIR"; @NonNls public static final String DIRECTORY_STORE_NAME = ".idea"; @NonNls public static final String APPLICATION_HOME_DIR = "APPLICATION_HOME_DIR"; + @NonNls public static final String APPLICATION_CONFIG_DIR = "APPLICATION_CONFIG_DIR"; @NonNls public static final String APPLICATION_PLUGINS_DIR = "APPLICATION_PLUGINS_DIR"; @NonNls public static final String USER_HOME_NAME = "USER_HOME"; + private static final Map ourGlobalMacros = ContainerUtil.immutableMapBuilder() + .put(APPLICATION_HOME_DIR, toSystemIndependentName(PathManager.getHomePath())) + .put(APPLICATION_CONFIG_DIR, toSystemIndependentName(PathManager.getConfigPath())) + .put(APPLICATION_PLUGINS_DIR, toSystemIndependentName(PathManager.getPluginsPath())) + .put(USER_HOME_NAME, StringUtil.trimEnd(toSystemIndependentName(SystemProperties.getUserHome()), "/")).build(); + @Nullable public static String getModuleDir(String moduleFilePath) { File moduleDirFile = new File(moduleFilePath).getParentFile(); @@ -57,31 +67,18 @@ public class PathMacroUtil { return moduleDir; } + @NotNull public static String getUserHomePath() { - return StringUtil.trimEnd(FileUtil.toSystemIndependentName(SystemProperties.getUserHome()), "/"); + return ObjectUtils.assertNotNull(getGlobalSystemMacroValue(USER_HOME_NAME)); } + @NotNull public static Map getGlobalSystemMacros() { - final Map map = new HashMap(); - map.put(APPLICATION_HOME_DIR, getApplicationHomeDirPath()); - map.put(APPLICATION_PLUGINS_DIR, getApplicationPluginsDirPath()); - map.put(USER_HOME_NAME, getUserHomePath()); - return map; - } - - private static String getApplicationHomeDirPath() { - return FileUtil.toSystemIndependentName(PathManager.getHomePath()); - } - - private static String getApplicationPluginsDirPath() { - return FileUtil.toSystemIndependentName(PathManager.getPluginsPath()); + return ourGlobalMacros; } @Nullable public static String getGlobalSystemMacroValue(String name) { - if (APPLICATION_HOME_DIR.equals(name)) return getApplicationHomeDirPath(); - if (APPLICATION_PLUGINS_DIR.equals(name)) return getApplicationPluginsDirPath(); - if (USER_HOME_NAME.equals(name)) return getUserHomePath(); - return null; + return ourGlobalMacros.get(name); } } diff --git a/platform/dvcs/src/com/intellij/dvcs/ui/VcsLogAction.java b/platform/dvcs/src/com/intellij/dvcs/ui/VcsLogAction.java new file mode 100644 index 000000000000..d8f4012f1b0e --- /dev/null +++ b/platform/dvcs/src/com/intellij/dvcs/ui/VcsLogAction.java @@ -0,0 +1,130 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.intellij.dvcs.ui; + +import com.intellij.dvcs.repo.Repository; +import com.intellij.openapi.actionSystem.AnActionEvent; +import com.intellij.openapi.actionSystem.CommonDataKeys; +import com.intellij.openapi.project.DumbAwareAction; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Condition; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.util.containers.ContainerUtil; +import com.intellij.util.containers.MultiMap; +import com.intellij.vcs.log.VcsFullCommitDetails; +import com.intellij.vcs.log.VcsLog; +import com.intellij.vcs.log.VcsLogDataKeys; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Collection; +import java.util.List; +import java.util.Map; + +public abstract class VcsLogAction extends DumbAwareAction { + + protected enum Mode { + SINGLE_COMMIT, + SINGLE_PER_REPO + } + + @Override + public void actionPerformed(AnActionEvent e) { + Project project = e.getRequiredData(CommonDataKeys.PROJECT); + VcsLog log = e.getRequiredData(VcsLogDataKeys.VSC_LOG); + List details = log.getSelectedDetails(); + MultiMap grouped = groupByRoot(project, details); + assert grouped != null; + Map singleElementMap = convertToSingleElementMap(grouped); + assert singleElementMap != null; + actionPerformed(project, singleElementMap); + } + + @Override + public void update(AnActionEvent e) { + Project project = e.getProject(); + VcsLog log = e.getData(VcsLogDataKeys.VSC_LOG); + if (project == null || log == null) { + e.getPresentation().setEnabledAndVisible(false); + return; + } + + List details = log.getSelectedDetails(); + MultiMap grouped = groupByRoot(project, details); + if (grouped == null) { + e.getPresentation().setEnabledAndVisible(false); + } + else { + e.getPresentation().setVisible(true); + e.getPresentation().setEnabled(isEnabled(grouped)); + } + } + + private boolean isEnabled(@NotNull MultiMap grouped) { + Mode mode = getMode(); + if (mode == Mode.SINGLE_COMMIT) { + return grouped.size() == 1; + } + if (mode == Mode.SINGLE_PER_REPO) { + return allValuesAreSingletons(grouped); + } + return false; + } + + @Nullable + protected abstract Repo getRepositoryForRoot(@NotNull Project project, @NotNull VirtualFile root); + + protected abstract void actionPerformed(@NotNull Project project, @NotNull Map commits); + + @NotNull + protected abstract Mode getMode(); + + @Nullable + private MultiMap groupByRoot(@NotNull Project project, @NotNull List commits) { + MultiMap map = MultiMap.create(); + for (VcsFullCommitDetails commit : commits) { + Repo root = getRepositoryForRoot(project, commit.getRoot()); + if (root == null) { // commit from some other VCS + return null; + } + map.putValue(root, commit); + } + return map; + } + + private boolean allValuesAreSingletons(@NotNull MultiMap grouped) { + return !ContainerUtil.exists(grouped.entrySet(), new Condition>>() { + @Override + public boolean value(Map.Entry> entry) { + return entry.getValue().size() != 1; + } + }); + } + + @Nullable + private Map convertToSingleElementMap(@NotNull MultiMap groupedCommits) { + Map map = ContainerUtil.newHashMap(); + for (Map.Entry> entry : groupedCommits.entrySet()) { + Collection commits = entry.getValue(); + if (commits.size() != 1) { + return null; + } + map.put(entry.getKey(), commits.iterator().next()); + } + return map; + } + +} diff --git a/platform/dvcs/src/com/intellij/dvcs/ui/VcsLogSingleCommitAction.java b/platform/dvcs/src/com/intellij/dvcs/ui/VcsLogSingleCommitAction.java deleted file mode 100644 index 1e20f2ceac11..000000000000 --- a/platform/dvcs/src/com/intellij/dvcs/ui/VcsLogSingleCommitAction.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright 2000-2014 JetBrains s.r.o. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.intellij.dvcs.ui; - -import com.intellij.dvcs.repo.Repository; -import com.intellij.openapi.actionSystem.AnActionEvent; -import com.intellij.openapi.actionSystem.CommonDataKeys; -import com.intellij.openapi.project.DumbAwareAction; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.util.Condition; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.util.containers.ContainerUtil; -import com.intellij.vcs.log.VcsFullCommitDetails; -import com.intellij.vcs.log.VcsLog; -import com.intellij.vcs.log.VcsLogDataKeys; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.List; - -public abstract class VcsLogSingleCommitAction extends DumbAwareAction { - - @Override - public void actionPerformed(AnActionEvent e) { - Project project = e.getRequiredData(CommonDataKeys.PROJECT); - VcsLog log = e.getRequiredData(VcsLogDataKeys.VSC_LOG); - List details = log.getSelectedDetails(); - assert details.size() == 1; - VcsFullCommitDetails commit = details.get(0); - Repo repository = getRepositoryForRoot(project, commit.getRoot()); - assert repository != null; - - actionPerformed(repository, commit); - } - - @Override - public void update(AnActionEvent e) { - Project project = e.getProject(); - VcsLog log = e.getData(VcsLogDataKeys.VSC_LOG); - if (project == null || log == null) { - e.getPresentation().setEnabledAndVisible(false); - return; - } - - List details = log.getSelectedDetails(); - if (containsCommitFromOtherVcs(project, details)) { - e.getPresentation().setEnabledAndVisible(false); - } - else { - e.getPresentation().setVisible(true); - e.getPresentation().setEnabled(details.size() == 1); - } - } - - @Nullable - protected abstract Repo getRepositoryForRoot(@NotNull Project project, @NotNull VirtualFile root); - - protected abstract void actionPerformed(@NotNull Repo repository, @NotNull VcsFullCommitDetails commit); - - private boolean containsCommitFromOtherVcs(@NotNull final Project project, @NotNull List details) { - return ContainerUtil.exists(details, new Condition() { - @Override - public boolean value(VcsFullCommitDetails details) { - return getRepositoryForRoot(project, details.getRoot()) == null; - } - }); - } - -} diff --git a/platform/lang-impl/src/com/intellij/psi/impl/search/LexerEditorHighlighterLexer.java b/platform/editor-ui-ex/src/com/intellij/psi/impl/search/LexerEditorHighlighterLexer.java similarity index 61% rename from platform/lang-impl/src/com/intellij/psi/impl/search/LexerEditorHighlighterLexer.java rename to platform/editor-ui-ex/src/com/intellij/psi/impl/search/LexerEditorHighlighterLexer.java index 2add3ed1b3c5..a966d831b71a 100644 --- a/platform/lang-impl/src/com/intellij/psi/impl/search/LexerEditorHighlighterLexer.java +++ b/platform/editor-ui-ex/src/com/intellij/psi/impl/search/LexerEditorHighlighterLexer.java @@ -15,23 +15,12 @@ */ package com.intellij.psi.impl.search; -import com.intellij.lexer.Lexer; import com.intellij.lexer.LexerBase; -import com.intellij.openapi.editor.Document; import com.intellij.openapi.editor.highlighter.EditorHighlighter; -import com.intellij.openapi.editor.highlighter.EditorHighlighterFactory; import com.intellij.openapi.editor.highlighter.HighlighterIterator; -import com.intellij.openapi.editor.impl.EditorHighlighterCache; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.psi.PsiDocumentManager; -import com.intellij.psi.PsiFile; -import com.intellij.psi.PsiManager; -import com.intellij.psi.impl.cache.impl.id.PlatformIdTableBuilding; import com.intellij.psi.tree.IElementType; import com.intellij.util.text.CharSequenceSubSequence; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; /** * @author Sergey Evdokimov @@ -49,31 +38,6 @@ public class LexerEditorHighlighterLexer extends LexerBase { myAlreadyInitializedHighlighter = alreadyInitializedHighlighter; } - @Nullable - public static Lexer getLexerBasedOnLexerHighlighter(CharSequence text, VirtualFile virtualFile, Project project) { - EditorHighlighter highlighter = null; - - PsiFile psiFile = virtualFile != null ? PsiManager.getInstance(project).findFile(virtualFile) : null; - final Document document = psiFile != null ? PsiDocumentManager.getInstance(project).getDocument(psiFile) : null; - final EditorHighlighter cachedEditorHighlighter; - boolean alreadyInitializedHighlighter = false; - - if (document != null && - (cachedEditorHighlighter = EditorHighlighterCache.getEditorHighlighterForCachesBuilding(document)) != null && - PlatformIdTableBuilding.checkCanUseCachedEditorHighlighter(text, cachedEditorHighlighter)) { - highlighter = cachedEditorHighlighter; - alreadyInitializedHighlighter = true; - } - else if (virtualFile != null) { - highlighter = EditorHighlighterFactory.getInstance().createEditorHighlighter(project, virtualFile); - } - - if (highlighter != null) { - return new LexerEditorHighlighterLexer(highlighter, alreadyInitializedHighlighter); - } - return null; - } - @Override public void start(@NotNull CharSequence buffer, int startOffset, int endOffset, int state) { if (myAlreadyInitializedHighlighter) { diff --git a/platform/external-system-api/src/com/intellij/openapi/externalSystem/model/DataNode.java b/platform/external-system-api/src/com/intellij/openapi/externalSystem/model/DataNode.java index 4efac184c2ee..10fc11acbf5f 100644 --- a/platform/external-system-api/src/com/intellij/openapi/externalSystem/model/DataNode.java +++ b/platform/external-system-api/src/com/intellij/openapi/externalSystem/model/DataNode.java @@ -68,6 +68,18 @@ public class DataNode implements Serializable { return result; } + @NotNull + public DataNode createOrReplaceChild(@NotNull Key key, @NotNull T data) { + for (Iterator> iterator = myChildren.iterator(); iterator.hasNext(); ) { + DataNode child = iterator.next(); + if (child.getKey().equals(key)) { + iterator.remove(); + break; + } + } + return createChild(key, data); + } + @NotNull public Key getKey() { return myKey; diff --git a/platform/external-system-api/src/com/intellij/openapi/externalSystem/model/DefaultExternalFilter.java b/platform/external-system-api/src/com/intellij/openapi/externalSystem/model/DefaultExternalFilter.java new file mode 100644 index 000000000000..6bc9cf9aed94 --- /dev/null +++ b/platform/external-system-api/src/com/intellij/openapi/externalSystem/model/DefaultExternalFilter.java @@ -0,0 +1,62 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.intellij.openapi.externalSystem.model; + +import org.jetbrains.annotations.NotNull; + +/** + * @author Vladislav.Soroka + * @since 7/22/2014 + */ +public class DefaultExternalFilter implements ExternalFilter { + private static final long serialVersionUID = 1L; + + @NotNull + private String myFilterType; + @NotNull + private String myPropertiesAsJsonMap; + + public DefaultExternalFilter() { + myPropertiesAsJsonMap = ""; + myFilterType = ""; + } + + + public DefaultExternalFilter(ExternalFilter filter) { + myPropertiesAsJsonMap = filter.getPropertiesAsJsonMap(); + myFilterType = filter.getFilterType(); + } + + @NotNull + @Override + public String getFilterType() { + return myFilterType; + } + + public void setFilterType(@NotNull String filterType) { + myFilterType = filterType; + } + + @Override + @NotNull + public String getPropertiesAsJsonMap() { + return myPropertiesAsJsonMap; + } + + public void setPropertiesAsJsonMap(@NotNull String propertiesAsJsonMap) { + myPropertiesAsJsonMap = propertiesAsJsonMap; + } +} diff --git a/platform/external-system-api/src/com/intellij/openapi/externalSystem/model/DefaultExternalPlugin.java b/platform/external-system-api/src/com/intellij/openapi/externalSystem/model/DefaultExternalPlugin.java new file mode 100644 index 000000000000..8b7991b1d88c --- /dev/null +++ b/platform/external-system-api/src/com/intellij/openapi/externalSystem/model/DefaultExternalPlugin.java @@ -0,0 +1,46 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.intellij.openapi.externalSystem.model; + +import org.jetbrains.annotations.NotNull; + +/** + * @author Vladislav.Soroka + * @since 7/16/2014 + */ +public class DefaultExternalPlugin implements ExternalPlugin { + private static final long serialVersionUID = 1L; + + @NotNull + private String myId; + + public DefaultExternalPlugin() { + } + + public DefaultExternalPlugin(ExternalPlugin plugin) { + myId = plugin.getId(); + } + + @NotNull + @Override + public String getId() { + return myId; + } + + public void setId(@NotNull String id) { + myId = id; + } +} diff --git a/platform/external-system-api/src/com/intellij/openapi/externalSystem/model/DefaultExternalProject.java b/platform/external-system-api/src/com/intellij/openapi/externalSystem/model/DefaultExternalProject.java new file mode 100644 index 000000000000..cecb9ad33c12 --- /dev/null +++ b/platform/external-system-api/src/com/intellij/openapi/externalSystem/model/DefaultExternalProject.java @@ -0,0 +1,243 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.intellij.openapi.externalSystem.model; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.io.File; +import java.util.HashMap; +import java.util.Map; + +/** + * @author Vladislav.Soroka + * @since 7/14/2014 + */ +public class DefaultExternalProject implements ExternalProject { + + private static final long serialVersionUID = 1L; + + @NotNull + private String myName; + @NotNull + private String myQName; + @Nullable + private String myDescription; + @NotNull + private String myGroup; + @NotNull + private String myVersion; + @NotNull + private Map myChildProjects; + @NotNull + private File myProjectDir; + @NotNull + private File myBuildDir; + @Nullable + private File myBuildFile; + @NotNull + private Map myTasks; + @NotNull + private Map myProperties; + @NotNull + private Map mySourceSets; + @NotNull + private String myExternalSystemId; + @NotNull + private Map myPlugins; + + public DefaultExternalProject() { + myChildProjects = new HashMap(); + myTasks = new HashMap(); + myProperties = new HashMap(); + mySourceSets = new HashMap(); + myPlugins = new HashMap(); + } + + public DefaultExternalProject(@NotNull ExternalProject externalProject) { + this(); + myName = externalProject.getName(); + myQName = externalProject.getQName(); + myVersion = externalProject.getVersion(); + myGroup = externalProject.getGroup(); + myDescription = externalProject.getDescription(); + myProjectDir = externalProject.getProjectDir(); + myBuildDir = externalProject.getBuildDir(); + myBuildFile = externalProject.getBuildFile(); + myExternalSystemId = externalProject.getExternalSystemId(); + + for (Map.Entry entry : externalProject.getChildProjects().entrySet()) { + myChildProjects.put(entry.getKey(), new DefaultExternalProject(entry.getValue())); + } + + for (Map.Entry entry : externalProject.getTasks().entrySet()) { + myTasks.put(entry.getKey(), new DefaultExternalTask(entry.getValue())); + } + for (Map.Entry entry : externalProject.getSourceSets().entrySet()) { + mySourceSets.put(entry.getKey(), new DefaultExternalSourceSet(entry.getValue())); + } + for (Map.Entry entry : externalProject.getPlugins().entrySet()) { + myPlugins.put(entry.getKey(), new DefaultExternalPlugin(entry.getValue())); + } + } + + + @NotNull + @Override + public String getExternalSystemId() { + return myExternalSystemId; + } + + public void setExternalSystemId(@NotNull String externalSystemId) { + myExternalSystemId = externalSystemId; + } + + @NotNull + @Override + public String getName() { + return myName; + } + + public void setName(@NotNull String name) { + myName = name; + } + + @NotNull + @Override + public String getQName() { + return myQName; + } + + public void setQName(@NotNull String QName) { + myQName = QName; + } + + @Nullable + @Override + public String getDescription() { + return myDescription; + } + + public void setDescription(@Nullable String description) { + myDescription = description; + } + + @NotNull + @Override + public String getGroup() { + return myGroup; + } + + public void setGroup(@NotNull String group) { + myGroup = group; + } + + @NotNull + @Override + public String getVersion() { + return myVersion; + } + + public void setVersion(@NotNull String version) { + myVersion = version; + } + + @NotNull + @Override + public Map getChildProjects() { + return myChildProjects; + } + + public void setChildProjects(@NotNull Map childProjects) { + myChildProjects = childProjects; + } + + @NotNull + @Override + public File getProjectDir() { + return myProjectDir; + } + + public void setProjectDir(@NotNull File projectDir) { + myProjectDir = projectDir; + } + + @NotNull + @Override + public File getBuildDir() { + return myBuildDir; + } + + public void setBuildDir(@NotNull File buildDir) { + myBuildDir = buildDir; + } + + @Nullable + @Override + public File getBuildFile() { + return myBuildFile; + } + + public void setBuildFile(@Nullable File buildFile) { + myBuildFile = buildFile; + } + + @NotNull + @Override + public Map getTasks() { + return myTasks; + } + + public void setTasks(@NotNull Map tasks) { + myTasks = tasks; + } + + @NotNull + @Override + public Map getPlugins() { + return myPlugins; + } + + public void setPlugins(@NotNull Map plugins) { + myPlugins = plugins; + } + + @NotNull + @Override + public Map getProperties() { + return myProperties; + } + + public void setProperties(@NotNull Map properties) { + myProperties = properties; + } + + @Nullable + @Override + public Object getProperty(String name) { + return myProperties.get(name); + } + + @NotNull + @Override + public Map getSourceSets() { + return mySourceSets; + } + + public void setSourceSets(@NotNull Map sourceSets) { + mySourceSets = sourceSets; + } +} diff --git a/platform/external-system-api/src/com/intellij/openapi/externalSystem/model/DefaultExternalSourceDirectorySet.java b/platform/external-system-api/src/com/intellij/openapi/externalSystem/model/DefaultExternalSourceDirectorySet.java new file mode 100644 index 000000000000..d6a8d7aa6291 --- /dev/null +++ b/platform/external-system-api/src/com/intellij/openapi/externalSystem/model/DefaultExternalSourceDirectorySet.java @@ -0,0 +1,124 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.intellij.openapi.externalSystem.model; + +import org.jetbrains.annotations.NotNull; + +import java.io.File; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +/** + * @author Vladislav.Soroka + * @since 7/14/2014 + */ +public class DefaultExternalSourceDirectorySet implements ExternalSourceDirectorySet { + private static final long serialVersionUID = 1L; + + @NotNull + private String myName; + @NotNull + private Set mySrcDirs; + @NotNull + private File myOutputDir; + @NotNull + private Set myExcludes; + @NotNull + private Set myIncludes; + @NotNull + private List myFilters; + + public DefaultExternalSourceDirectorySet() { + mySrcDirs = new HashSet(); + myExcludes = new HashSet(); + myIncludes = new HashSet(); + myFilters = new ArrayList(); + } + + public DefaultExternalSourceDirectorySet(ExternalSourceDirectorySet sourceDirectorySet) { + this(); + myName = sourceDirectorySet.getName(); + mySrcDirs = new HashSet(sourceDirectorySet.getSrcDirs()); + myOutputDir = sourceDirectorySet.getOutputDir(); + myExcludes = new HashSet(sourceDirectorySet.getExcludes()); + myIncludes = new HashSet(sourceDirectorySet.getIncludes()); + for (ExternalFilter filter : sourceDirectorySet.getFilters()) { + myFilters.add(new DefaultExternalFilter(filter)); + } + } + + @NotNull + @Override + public String getName() { + return myName; + } + + public void setName(@NotNull String name) { + myName = name; + } + + @NotNull + @Override + public Set getSrcDirs() { + return mySrcDirs; + } + + public void setSrcDirs(@NotNull Set srcDirs) { + mySrcDirs = srcDirs; + } + + @NotNull + @Override + public File getOutputDir() { + return myOutputDir; + } + + @NotNull + @Override + public Set getIncludes() { + return myIncludes; + } + + public void setIncludes(@NotNull Set includes) { + myIncludes = includes; + } + + @NotNull + @Override + public Set getExcludes() { + return myExcludes; + } + + public void setExcludes(@NotNull Set excludes) { + myExcludes = excludes; + } + + @NotNull + @Override + public List getFilters() { + return myFilters; + } + + public void setFilters(@NotNull List filters) { + myFilters = filters; + } + + public void setOutputDir(@NotNull File outputDir) { + myOutputDir = outputDir; + } +} diff --git a/platform/external-system-api/src/com/intellij/openapi/externalSystem/model/DefaultExternalSourceSet.java b/platform/external-system-api/src/com/intellij/openapi/externalSystem/model/DefaultExternalSourceSet.java new file mode 100644 index 000000000000..19d692dd1985 --- /dev/null +++ b/platform/external-system-api/src/com/intellij/openapi/externalSystem/model/DefaultExternalSourceSet.java @@ -0,0 +1,66 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.intellij.openapi.externalSystem.model; + +import com.intellij.openapi.externalSystem.model.project.ExternalSystemSourceType; +import com.intellij.openapi.externalSystem.model.project.IExternalSystemSourceType; +import org.jetbrains.annotations.NotNull; + +import java.util.HashMap; +import java.util.Map; + +/** + * @author Vladislav.Soroka + * @since 7/14/2014 + */ +public class DefaultExternalSourceSet implements ExternalSourceSet { + private static final long serialVersionUID = 1L; + + private String myName; + private Map mySources; + + public DefaultExternalSourceSet() { + mySources = new HashMap(); + } + + public DefaultExternalSourceSet(ExternalSourceSet sourceSet) { + this(); + myName = sourceSet.getName(); + for (Map.Entry entry : sourceSet.getSources().entrySet()) { + mySources.put(ExternalSystemSourceType.from(entry.getKey()), new DefaultExternalSourceDirectorySet(entry.getValue())); + } + } + + @NotNull + @Override + public String getName() { + return myName; + } + + public void setName(String name) { + myName = name; + } + + @NotNull + @Override + public Map getSources() { + return mySources; + } + + public void setSources(Map sources) { + mySources = sources; + } +} diff --git a/platform/external-system-api/src/com/intellij/openapi/externalSystem/model/DefaultExternalTask.java b/platform/external-system-api/src/com/intellij/openapi/externalSystem/model/DefaultExternalTask.java new file mode 100644 index 000000000000..e9392eb52e84 --- /dev/null +++ b/platform/external-system-api/src/com/intellij/openapi/externalSystem/model/DefaultExternalTask.java @@ -0,0 +1,86 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.intellij.openapi.externalSystem.model; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * @author Vladislav.Soroka + * @since 7/14/2014 + */ +public class DefaultExternalTask implements ExternalTask { + private static final long serialVersionUID = 1L; + + @NotNull + private String myName; + @NotNull + private String myQName; + @Nullable + private String myDescription; + @Nullable + private String myGroup; + + public DefaultExternalTask() { + } + + public DefaultExternalTask(ExternalTask externalTask) { + myName = externalTask.getName(); + myQName = externalTask.getQName(); + myDescription = externalTask.getDescription(); + myGroup = externalTask.getGroup(); + } + + @NotNull + @Override + public String getName() { + return myName; + } + + public void setName(@NotNull String name) { + myName = name; + } + + @NotNull + @Override + public String getQName() { + return myQName; + } + + public void setQName(@NotNull String QName) { + myQName = QName; + } + + @Nullable + @Override + public String getDescription() { + return myDescription; + } + + public void setDescription(@Nullable String description) { + myDescription = description; + } + + @Nullable + @Override + public String getGroup() { + return myGroup; + } + + public void setGroup(@Nullable String group) { + myGroup = group; + } +} diff --git a/platform/external-system-api/src/com/intellij/openapi/externalSystem/model/ExternalFilter.java b/platform/external-system-api/src/com/intellij/openapi/externalSystem/model/ExternalFilter.java new file mode 100644 index 000000000000..98fc003cb645 --- /dev/null +++ b/platform/external-system-api/src/com/intellij/openapi/externalSystem/model/ExternalFilter.java @@ -0,0 +1,34 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.intellij.openapi.externalSystem.model; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.io.Serializable; +import java.util.Map; +import java.util.Set; + +/** + * @author Vladislav.Soroka + * @since 7/14/2014 + */ +public interface ExternalFilter extends Serializable { + @NotNull + String getFilterType(); + @NotNull + String getPropertiesAsJsonMap(); +} diff --git a/platform/external-system-api/src/com/intellij/openapi/externalSystem/model/ExternalPlugin.java b/platform/external-system-api/src/com/intellij/openapi/externalSystem/model/ExternalPlugin.java new file mode 100644 index 000000000000..edd3db771b3f --- /dev/null +++ b/platform/external-system-api/src/com/intellij/openapi/externalSystem/model/ExternalPlugin.java @@ -0,0 +1,30 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.intellij.openapi.externalSystem.model; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.io.Serializable; + +/** + * @author Vladislav.Soroka + * @since 7/14/2014 + */ +public interface ExternalPlugin extends Serializable { + @NotNull + String getId(); +} diff --git a/platform/external-system-api/src/com/intellij/openapi/externalSystem/model/ExternalProject.java b/platform/external-system-api/src/com/intellij/openapi/externalSystem/model/ExternalProject.java new file mode 100644 index 000000000000..6df2d413d0fd --- /dev/null +++ b/platform/external-system-api/src/com/intellij/openapi/externalSystem/model/ExternalProject.java @@ -0,0 +1,87 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.intellij.openapi.externalSystem.model; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.io.File; +import java.io.Serializable; +import java.util.Map; + +/** + * @author Vladislav.Soroka + * @since 7/14/2014 + */ +public interface ExternalProject extends Serializable { + + @NotNull + String getExternalSystemId(); + + @NotNull + String getName(); + + @NotNull + String getQName(); + + @Nullable + String getDescription(); + + @NotNull + String getGroup(); + + @NotNull + String getVersion(); + + @NotNull + Map getChildProjects(); + + @NotNull + File getProjectDir(); + + @NotNull + File getBuildDir(); + + @Nullable + File getBuildFile(); + + @NotNull + Map getTasks(); + + //@NotNull + //Map getConfigurations(); + + //@NotNull + //List getRepositories(); + + //@NotNull + //List getDependencies(); + + @NotNull + Map getPlugins(); + + //@NotNull + //ExternalProjectBuild getBuild(); + + @NotNull + Map getProperties(); + + @Nullable + Object getProperty(String name); + + @NotNull + Map getSourceSets(); +} diff --git a/platform/external-system-api/src/com/intellij/openapi/externalSystem/model/ExternalSourceDirectorySet.java b/platform/external-system-api/src/com/intellij/openapi/externalSystem/model/ExternalSourceDirectorySet.java new file mode 100644 index 000000000000..70f0bed3d177 --- /dev/null +++ b/platform/external-system-api/src/com/intellij/openapi/externalSystem/model/ExternalSourceDirectorySet.java @@ -0,0 +1,46 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.intellij.openapi.externalSystem.model; + +import org.jetbrains.annotations.NotNull; + +import java.io.File; +import java.io.Serializable; +import java.util.List; +import java.util.Set; + +/** + * @author Vladislav.Soroka + * @since 7/14/2014 + */ +public interface ExternalSourceDirectorySet extends Serializable { + @NotNull + String getName(); + + @NotNull + Set getSrcDirs(); + + @NotNull + File getOutputDir(); + + @NotNull + Set getExcludes(); + @NotNull + Set getIncludes(); + + @NotNull + List getFilters(); +} diff --git a/platform/external-system-api/src/com/intellij/openapi/externalSystem/model/ExternalSourceSet.java b/platform/external-system-api/src/com/intellij/openapi/externalSystem/model/ExternalSourceSet.java new file mode 100644 index 000000000000..b23c07bbafa6 --- /dev/null +++ b/platform/external-system-api/src/com/intellij/openapi/externalSystem/model/ExternalSourceSet.java @@ -0,0 +1,39 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.intellij.openapi.externalSystem.model; + +import com.intellij.openapi.externalSystem.model.project.IExternalSystemSourceType; +import org.jetbrains.annotations.NotNull; + +import java.io.Serializable; +import java.util.Map; + +/** + * @author Vladislav.Soroka + * @since 7/14/2014 + */ +public interface ExternalSourceSet extends Serializable { + @NotNull + String getName(); + //@NotNull + //ClasspathContainer getCompileClasspath(); + + //@NotNull + //ClasspathContainer getRuntimeClasspath(); + + @NotNull + Map getSources(); +} diff --git a/platform/external-system-api/src/com/intellij/openapi/externalSystem/model/ExternalTask.java b/platform/external-system-api/src/com/intellij/openapi/externalSystem/model/ExternalTask.java new file mode 100644 index 000000000000..b4c61c662acc --- /dev/null +++ b/platform/external-system-api/src/com/intellij/openapi/externalSystem/model/ExternalTask.java @@ -0,0 +1,36 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.intellij.openapi.externalSystem.model; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.io.Serializable; + +/** + * @author Vladislav.Soroka + * @since 7/14/2014 + */ +public interface ExternalTask extends Serializable { + @NotNull + String getName(); + @NotNull + String getQName(); + @Nullable + String getDescription(); + @Nullable + String getGroup(); +} diff --git a/platform/external-system-api/src/com/intellij/openapi/externalSystem/model/project/ExternalSystemSourceType.java b/platform/external-system-api/src/com/intellij/openapi/externalSystem/model/project/ExternalSystemSourceType.java index ffe8a52d4a06..0e112b1c2b14 100644 --- a/platform/external-system-api/src/com/intellij/openapi/externalSystem/model/project/ExternalSystemSourceType.java +++ b/platform/external-system-api/src/com/intellij/openapi/externalSystem/model/project/ExternalSystemSourceType.java @@ -1,52 +1,62 @@ package com.intellij.openapi.externalSystem.model.project; -import org.jetbrains.annotations.NotNull; - -import java.io.Serializable; - /** * Enumerates module source types. * * @author Denis Zhdanov * @since 8/10/11 5:21 PM */ -public class ExternalSystemSourceType implements Serializable { +public enum ExternalSystemSourceType implements IExternalSystemSourceType { - @NotNull public static final ExternalSystemSourceType SOURCE = new ExternalSystemSourceType("SOURCE"); - @NotNull public static final ExternalSystemSourceType TEST = new ExternalSystemSourceType("TEST"); - @NotNull public static final ExternalSystemSourceType EXCLUDED = new ExternalSystemSourceType("EXCLUDED"); - @NotNull public static final ExternalSystemSourceType SOURCE_GENERATED = new ExternalSystemSourceType("SOURCE_GENERATED"); - @NotNull public static final ExternalSystemSourceType TEST_GENERATED = new ExternalSystemSourceType("TEST_GENERATED"); - @NotNull public static final ExternalSystemSourceType RESOURCE = new ExternalSystemSourceType("RESOURCE"); - @NotNull public static final ExternalSystemSourceType TEST_RESOURCE = new ExternalSystemSourceType("TEST_RESOURCE"); - - private static final long serialVersionUID = 1L; + SOURCE(false, false, false, false), + TEST(true, false, false, false), + EXCLUDED(false, false, false, true), + SOURCE_GENERATED(true, true, false, false), + TEST_GENERATED(true, true, false, false), + RESOURCE(false, false, true, false), + TEST_RESOURCE(true, false, true, false); - @NotNull private final String myId; + private final boolean isTest; + private final boolean isGenerated; + private final boolean isResource; + private final boolean isExcluded; - public ExternalSystemSourceType(@NotNull String id) { - myId = id; + ExternalSystemSourceType(boolean test, boolean generated, boolean resource, boolean excluded) { + isTest = test; + isGenerated = generated; + isResource = resource; + isExcluded = excluded; } @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - ExternalSystemSourceType type = (ExternalSystemSourceType)o; - - if (!myId.equals(type.myId)) return false; - - return true; + public boolean isTest() { + return isTest; } @Override - public int hashCode() { - return myId.hashCode(); + public boolean isGenerated() { + return isGenerated; } @Override - public String toString() { - return myId; + public boolean isResource() { + return isResource; + } + + @Override + public boolean isExcluded() { + return isExcluded; + } + + public static ExternalSystemSourceType from(IExternalSystemSourceType sourceType) { + for (ExternalSystemSourceType systemSourceType : ExternalSystemSourceType.values()) { + if (systemSourceType.isGenerated == sourceType.isGenerated() && + systemSourceType.isResource == sourceType.isResource() && + systemSourceType.isTest == sourceType.isTest() && + systemSourceType.isExcluded == sourceType.isExcluded()) { + return systemSourceType; + } + } + throw new IllegalArgumentException("Invalid source type: " + sourceType); } } diff --git a/plugins/properties/resources/icons/PropertiesIcons.java b/platform/external-system-api/src/com/intellij/openapi/externalSystem/model/project/IExternalSystemSourceType.java similarity index 56% rename from plugins/properties/resources/icons/PropertiesIcons.java rename to platform/external-system-api/src/com/intellij/openapi/externalSystem/model/project/IExternalSystemSourceType.java index 156d14467707..2ad8821b06cd 100644 --- a/plugins/properties/resources/icons/PropertiesIcons.java +++ b/platform/external-system-api/src/com/intellij/openapi/externalSystem/model/project/IExternalSystemSourceType.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2013 JetBrains s.r.o. + * Copyright 2000-2014 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,20 +13,20 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package icons; +package com.intellij.openapi.externalSystem.model.project; -import com.intellij.openapi.util.IconLoader; - -import javax.swing.*; +import java.io.Serializable; /** - * NOTE THIS FILE IS AUTO-GENERATED - * DO NOT EDIT IT BY HAND, run build/scripts/icons.gant instead + * @author Vladislav.Soroka + * @since 7/17/2014 */ -public class PropertiesIcons { - private static Icon load(String path) { - return IconLoader.getIcon(path, PropertiesIcons.class); - } +public interface IExternalSystemSourceType extends Serializable { + boolean isTest(); - public static final Icon XmlProperties = load("/icons/xmlProperties.png"); // 16x16 + boolean isGenerated(); + + boolean isResource(); + + boolean isExcluded(); } diff --git a/platform/external-system-api/src/com/intellij/openapi/externalSystem/util/ExternalSystemApiUtil.java b/platform/external-system-api/src/com/intellij/openapi/externalSystem/util/ExternalSystemApiUtil.java index a6cfc65eb759..fd3493447ced 100644 --- a/platform/external-system-api/src/com/intellij/openapi/externalSystem/util/ExternalSystemApiUtil.java +++ b/platform/external-system-api/src/com/intellij/openapi/externalSystem/util/ExternalSystemApiUtil.java @@ -681,4 +681,9 @@ public class ExternalSystemApiUtil { public static String getExternalProjectPath(@Nullable Module module) { return module != null ? module.getOptionValue(ExternalSystemConstants.LINKED_PROJECT_PATH_KEY) : null; } + + @Nullable + public static String getExternalProjectId(@Nullable Module module) { + return module != null ? module.getOptionValue(ExternalSystemConstants.LINKED_PROJECT_ID_KEY) : null; + } } diff --git a/platform/external-system-api/src/com/intellij/openapi/externalSystem/util/ExternalSystemConstants.java b/platform/external-system-api/src/com/intellij/openapi/externalSystem/util/ExternalSystemConstants.java index 283067810bb0..160c95938378 100644 --- a/platform/external-system-api/src/com/intellij/openapi/externalSystem/util/ExternalSystemConstants.java +++ b/platform/external-system-api/src/com/intellij/openapi/externalSystem/util/ExternalSystemConstants.java @@ -27,6 +27,7 @@ public class ExternalSystemConstants { @NonNls @NotNull public static final String EXTERNAL_SYSTEM_ID_KEY = "external.system.id"; @NonNls @NotNull public static final String LINKED_PROJECT_PATH_KEY = "external.linked.project.path"; @NonNls @NotNull public static final String ROOT_PROJECT_PATH_KEY = "external.root.project.path"; + @NonNls @NotNull public static final String LINKED_PROJECT_ID_KEY = "external.linked.project.id"; @NonNls @NotNull public static final String EXTERNAL_SYSTEM_MODULE_GROUP_KEY = "external.system.module.group"; @NonNls @NotNull public static final String EXTERNAL_SYSTEM_MODULE_VERSION_KEY = "external.system.module.version"; diff --git a/platform/external-system-impl/external-system-impl.iml b/platform/external-system-impl/external-system-impl.iml index aa68fdc4d7b6..1cbf68f907f9 100644 --- a/platform/external-system-impl/external-system-impl.iml +++ b/platform/external-system-impl/external-system-impl.iml @@ -24,6 +24,7 @@ + diff --git a/platform/external-system-impl/src/com/intellij/openapi/externalSystem/service/project/ProjectStructureServices.java b/platform/external-system-impl/src/com/intellij/openapi/externalSystem/service/project/ProjectStructureServices.java deleted file mode 100644 index 1e6dadd0d3b8..000000000000 --- a/platform/external-system-impl/src/com/intellij/openapi/externalSystem/service/project/ProjectStructureServices.java +++ /dev/null @@ -1,42 +0,0 @@ -package com.intellij.openapi.externalSystem.service.project; - -import org.jetbrains.annotations.NotNull; - -/** - * Facades all services necessary for the 'sync project changes' processing. - *

- * Thread-safe. - * - * @author Denis Zhdanov - * @since 2/14/12 1:26 PM - */ -public class ProjectStructureServices { - - @NotNull private final ProjectStructureHelper myProjectStructureHelper; - @NotNull private final PlatformFacade myPlatformFacade; - @NotNull private final ExternalLibraryPathTypeMapper myLibraryPathTypeMapper; - - public ProjectStructureServices(@NotNull ProjectStructureHelper projectStructureHelper, - @NotNull PlatformFacade platformFacade, - @NotNull ExternalLibraryPathTypeMapper mapper) - { - myProjectStructureHelper = projectStructureHelper; - myPlatformFacade = platformFacade; - myLibraryPathTypeMapper = mapper; - } - - @NotNull - public ProjectStructureHelper getProjectStructureHelper() { - return myProjectStructureHelper; - } - - @NotNull - public PlatformFacade getPlatformFacade() { - return myPlatformFacade; - } - - @NotNull - public ExternalLibraryPathTypeMapper getLibraryPathTypeMapper() { - return myLibraryPathTypeMapper; - } -} diff --git a/platform/external-system-impl/src/com/intellij/openapi/externalSystem/service/project/manage/ModuleDataService.java b/platform/external-system-impl/src/com/intellij/openapi/externalSystem/service/project/manage/ModuleDataService.java index 4b1e3b13a22f..32919794eca4 100644 --- a/platform/external-system-impl/src/com/intellij/openapi/externalSystem/service/project/manage/ModuleDataService.java +++ b/platform/external-system-impl/src/com/intellij/openapi/externalSystem/service/project/manage/ModuleDataService.java @@ -274,6 +274,7 @@ public class ModuleDataService implements ProjectDataService module.putUserData(MODULE_DATA_KEY, moduleData); module.setOption(ExternalSystemConstants.EXTERNAL_SYSTEM_ID_KEY, moduleData.getOwner().toString()); + module.setOption(ExternalSystemConstants.LINKED_PROJECT_ID_KEY, moduleData.getId()); module.setOption(ExternalSystemConstants.LINKED_PROJECT_PATH_KEY, moduleData.getLinkedExternalProjectPath()); final ProjectData projectData = moduleDataNode.getData(ProjectKeys.PROJECT); module.setOption(ExternalSystemConstants.ROOT_PROJECT_PATH_KEY, projectData != null ? projectData.getLinkedExternalProjectPath() : ""); diff --git a/platform/external-system-impl/src/com/intellij/openapi/externalSystem/service/project/manage/ProjectDataManager.java b/platform/external-system-impl/src/com/intellij/openapi/externalSystem/service/project/manage/ProjectDataManager.java index 6575ffb107f0..c2dcfe57b010 100644 --- a/platform/external-system-impl/src/com/intellij/openapi/externalSystem/service/project/manage/ProjectDataManager.java +++ b/platform/external-system-impl/src/com/intellij/openapi/externalSystem/service/project/manage/ProjectDataManager.java @@ -25,6 +25,7 @@ import com.intellij.util.containers.ContainerUtil; import com.intellij.util.containers.ContainerUtilRt; import com.intellij.util.containers.Stack; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import java.util.Collection; import java.util.List; @@ -40,8 +41,10 @@ public class ProjectDataManager { private static final Logger LOG = Logger.getInstance("#" + ProjectDataManager.class.getName()); - @NotNull private final NotNullLazyValue, List>>> myServices = - new NotNullLazyValue, List>>>() { + @NotNull private final NotNullLazyValue, List>>> myServices; + + public ProjectDataManager() { + myServices = new NotNullLazyValue, List>>>() { @NotNull @Override protected Map, List>> compute() { @@ -57,10 +60,22 @@ public class ProjectDataManager { for (List> services : result.values()) { ExternalSystemApiUtil.orderAwareSort(services); } - return result; } }; + } + + @Nullable + public List> getDataServices(Key key) { + return myServices.getValue().get(key); + } + + @Nullable + public ProjectDataService getDataService(Key key) { + final List> dataServices = myServices.getValue().get(key); + assert dataServices == null || dataServices.isEmpty() || dataServices.size() == 1; + return ContainerUtil.getFirstItem(dataServices); + } @SuppressWarnings("unchecked") public void importData(@NotNull Collection> nodes, @NotNull Project project, boolean synchronous) { diff --git a/platform/external-system-impl/src/com/intellij/openapi/externalSystem/service/settings/AbstractImportFromExternalSystemControl.java b/platform/external-system-impl/src/com/intellij/openapi/externalSystem/service/settings/AbstractImportFromExternalSystemControl.java index 5dcb5ef71e3b..deb9a9044ea6 100644 --- a/platform/external-system-impl/src/com/intellij/openapi/externalSystem/service/settings/AbstractImportFromExternalSystemControl.java +++ b/platform/external-system-impl/src/com/intellij/openapi/externalSystem/service/settings/AbstractImportFromExternalSystemControl.java @@ -193,6 +193,11 @@ public abstract class AbstractImportFromExternalSystemControl< return myProjectSettingsControl; } + @Nullable + public ExternalSystemSettingsControl getSystemSettingsControl() { + return mySystemSettingsControl; + } + public void setLinkedProjectPath(@NotNull String path) { myProjectSettings.setExternalProjectPath(path); myLinkedProjectPathField.setText(path); diff --git a/platform/external-system-impl/testSrc/com/intellij/openapi/externalSystem/test/ExternalSystemTestCase.java b/platform/external-system-impl/testSrc/com/intellij/openapi/externalSystem/test/ExternalSystemTestCase.java index 22ec927eb30d..770578ae44f5 100644 --- a/platform/external-system-impl/testSrc/com/intellij/openapi/externalSystem/test/ExternalSystemTestCase.java +++ b/platform/external-system-impl/testSrc/com/intellij/openapi/externalSystem/test/ExternalSystemTestCase.java @@ -15,24 +15,41 @@ */ package com.intellij.openapi.externalSystem.test; +import com.intellij.compiler.CompilerTestUtil; +import com.intellij.compiler.CompilerWorkspaceConfiguration; +import com.intellij.compiler.artifacts.ArtifactsTestUtil; +import com.intellij.compiler.impl.ModuleCompileScope; +import com.intellij.openapi.application.AccessToken; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.application.Result; import com.intellij.openapi.application.WriteAction; import com.intellij.openapi.command.WriteCommandAction; +import com.intellij.openapi.compiler.CompileContext; +import com.intellij.openapi.compiler.CompileScope; +import com.intellij.openapi.compiler.CompileStatusNotification; +import com.intellij.openapi.compiler.CompilerManager; import com.intellij.openapi.module.Module; import com.intellij.openapi.module.ModuleManager; import com.intellij.openapi.module.ModuleType; import com.intellij.openapi.module.StdModuleTypes; import com.intellij.openapi.project.Project; +import com.intellij.openapi.projectRoots.Sdk; +import com.intellij.openapi.projectRoots.impl.JavaAwareProjectJdkTableImpl; +import com.intellij.openapi.roots.ModuleRootModificationUtil; import com.intellij.openapi.util.SystemInfo; import com.intellij.openapi.util.io.FileUtil; import com.intellij.openapi.vfs.LocalFileSystem; import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.packaging.artifacts.Artifact; +import com.intellij.packaging.impl.compiler.ArtifactCompileScope; import com.intellij.testFramework.IdeaTestCase; +import com.intellij.testFramework.IdeaTestUtil; import com.intellij.testFramework.PsiTestUtil; import com.intellij.testFramework.UsefulTestCase; import com.intellij.testFramework.fixtures.IdeaProjectTestFixture; import com.intellij.testFramework.fixtures.IdeaTestFixtureFactory; +import com.intellij.util.concurrency.Semaphore; +import com.intellij.util.io.TestFileSystemItem; import com.intellij.util.ui.UIUtil; import gnu.trove.THashSet; import org.jetbrains.annotations.NonNls; @@ -40,6 +57,7 @@ import org.jetbrains.annotations.NotNull; import org.junit.After; import org.junit.Before; +import javax.swing.*; import java.awt.*; import java.io.File; import java.io.IOException; @@ -129,11 +147,11 @@ public abstract class ExternalSystemTestCase extends UsefulTestCase { @Override public void tearDown() throws Exception { try { - myProject = null; - UIUtil.invokeAndWaitIfNeeded(new Runnable() { + edt(new Runnable() { @Override public void run() { try { + CompilerTestUtil.disableExternalCompiler(myProject); tearDownFixtures(); } catch (Exception e) { @@ -141,6 +159,7 @@ public abstract class ExternalSystemTestCase extends UsefulTestCase { } } }); + myProject = null; if (!FileUtil.delete(myTestDir) && myTestDir.exists()) { System.err.println("Cannot delete " + myTestDir); //printDirectoryContent(myDir); @@ -319,7 +338,11 @@ public abstract class ExternalSystemTestCase extends UsefulTestCase { protected VirtualFile createProjectSubFile(String relativePath) throws IOException { File f = new File(getProjectPath(), relativePath); FileUtil.ensureExists(f.getParentFile()); - f.createNewFile(); + FileUtil.ensureCanCreateFile(f); + final boolean created = f.createNewFile(); + if(!created) { + throw new AssertionError("Unable to create the project sub file: " + f.getAbsolutePath()); + } return LocalFileSystem.getInstance().refreshAndFindFileByIoFile(f); } @@ -329,6 +352,114 @@ public abstract class ExternalSystemTestCase extends UsefulTestCase { return file; } + + protected void compileModules(final String... moduleNames) { + compile(createModulesCompileScope(moduleNames)); + } + + protected void buildArtifacts(String... artifactNames) { + compile(createArtifactsScope(artifactNames)); + } + + private void compile(final CompileScope scope) { + edt(new Runnable() { + @Override + public void run() { + for (Module module : scope.getAffectedModules()) { + setupJdkForModule(module.getName()); + } + } + }); + + CompilerWorkspaceConfiguration.getInstance(myProject).CLEAR_OUTPUT_DIRECTORY = true; + + final Semaphore semaphore = new Semaphore(); + semaphore.down(); + edt(new Runnable() { + @Override + public void run() { + CompilerTestUtil.enableExternalCompiler(); + CompilerManager.getInstance(myProject).make(scope, new CompileStatusNotification() { + @Override + public void finished(boolean aborted, int errors, int warnings, CompileContext compileContext) { + //assertFalse(aborted); + //assertEquals(collectMessages(compileContext, CompilerMessageCategory.ERROR), 0, errors); + //assertEquals(collectMessages(compileContext, CompilerMessageCategory.WARNING), 0, warnings); + semaphore.up(); + } + }); + } + }); + while (!semaphore.waitFor(100)) { + if (SwingUtilities.isEventDispatchThread()) { + UIUtil.dispatchAllInvocationEvents(); + } + } + if (SwingUtilities.isEventDispatchThread()) { + UIUtil.dispatchAllInvocationEvents(); + } + } + + private CompileScope createModulesCompileScope(final String[] moduleNames) { + final List modules = new ArrayList(); + for (String name : moduleNames) { + modules.add(getModule(name)); + } + return new ModuleCompileScope(myProject, modules.toArray(new Module[modules.size()]), false); + } + + private CompileScope createArtifactsScope(String[] artifactNames) { + List artifacts = new ArrayList(); + for (String name : artifactNames) { + artifacts.add(ArtifactsTestUtil.findArtifact(myProject, name)); + } + return ArtifactCompileScope.createArtifactsScope(myProject, artifacts); + } + + protected Sdk setupJdkForModule(final String moduleName) { + final Sdk sdk = true ? JavaAwareProjectJdkTableImpl.getInstanceEx().getInternalJdk() : createJdk("Java 1.5"); + ModuleRootModificationUtil.setModuleSdk(getModule(moduleName), sdk); + return sdk; + } + + protected static Sdk createJdk(String versionName) { + return IdeaTestUtil.getMockJdk17(versionName); + } + + protected Module getModule(final String name) { + AccessToken accessToken = ApplicationManager.getApplication().acquireReadActionLock(); + try { + Module m = ModuleManager.getInstance(myProject).findModuleByName(name); + assertNotNull("Module " + name + " not found", m); + return m; + } + finally { + accessToken.finish(); + } + } + + protected void assertExplodedLayout(String artifactName, String expected) { + assertJarLayout(artifactName + " exploded", expected); + } + + protected void assertJarLayout(String artifactName, String expected) { + ArtifactsTestUtil.assertLayout(myProject, artifactName, expected); + } + + protected void assertArtifactOutputPath(final String artifactName, final String expected) { + ArtifactsTestUtil.assertOutputPath(myProject, artifactName, expected); + } + + protected void assertArtifactOutputFileName(final String artifactName, final String expected) { + ArtifactsTestUtil.assertOutputFileName(myProject, artifactName, expected); + } + + protected void assertArtifactOutput(String artifactName, TestFileSystemItem fs) { + final String outputPath = ArtifactsTestUtil.findArtifact(myProject, artifactName).getOutputPath(); + assert outputPath != null; + fs.assertDirectoryEqual(new File(outputPath)); + } + private static void setFileContent(final VirtualFile file, final String content, final boolean advanceStamps) throws IOException { new WriteAction() { @Override diff --git a/platform/lang-api/src/com/intellij/find/FindModel.java b/platform/lang-api/src/com/intellij/find/FindModel.java index 7275bde8d3fe..b8caf760446d 100644 --- a/platform/lang-api/src/com/intellij/find/FindModel.java +++ b/platform/lang-api/src/com/intellij/find/FindModel.java @@ -19,6 +19,7 @@ import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.util.UserDataHolderBase; import com.intellij.openapi.util.text.StringUtil; import com.intellij.psi.search.SearchScope; +import com.intellij.util.PatternUtil; import com.intellij.util.containers.ContainerUtil; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; @@ -274,7 +275,7 @@ public class FindModel extends UserDataHolderBase implements Cloneable { public void setStringToFind(@NotNull String s) { boolean changed = !StringUtil.equals(s, myStringToFind); myStringToFind = s; - myPattern = NO_PATTERN; + myPattern = PatternUtil.NOTHING; if (changed) { notifyObservers(); } @@ -409,7 +410,7 @@ public class FindModel extends UserDataHolderBase implements Cloneable { boolean changed = val != isCaseSensitive; isCaseSensitive = val; if (changed) { - myPattern = NO_PATTERN; + myPattern = PatternUtil.NOTHING; notifyObservers(); } } @@ -850,11 +851,11 @@ public class FindModel extends UserDataHolderBase implements Cloneable { } public enum SearchContext { - ANY, IN_STRINGS, IN_COMMENTS, EXCEPT_STRINGS, EXCEPT_COMMENTS, EXCEPT_STRINGS_AND_COMMENTS + ANY, IN_STRING_LITERALS, IN_COMMENTS, EXCEPT_STRING_LITERALS, EXCEPT_COMMENTS, EXCEPT_COMMENTS_AND_STRING_LITERALS } public boolean isInStringLiteralsOnly() { - return searchContext == SearchContext.IN_STRINGS; + return searchContext == SearchContext.IN_STRING_LITERALS; } public boolean isExceptComments() { @@ -862,7 +863,7 @@ public class FindModel extends UserDataHolderBase implements Cloneable { } public boolean isExceptStringLiterals() { - return searchContext == SearchContext.EXCEPT_STRINGS; + return searchContext == SearchContext.EXCEPT_STRING_LITERALS; } public boolean isInCommentsOnly() { @@ -870,7 +871,32 @@ public class FindModel extends UserDataHolderBase implements Cloneable { } public boolean isExceptCommentsAndStringLiterals() { - return searchContext == SearchContext.EXCEPT_STRINGS_AND_COMMENTS; + return searchContext == SearchContext.EXCEPT_COMMENTS_AND_STRING_LITERALS; + } + + @Deprecated + public void setInCommentsOnly(boolean inCommentsOnly) { + doApplyContextChange(inCommentsOnly, SearchContext.IN_COMMENTS); + } + + @Deprecated + public void setInStringLiteralsOnly(boolean inStringLiteralsOnly) { + doApplyContextChange(inStringLiteralsOnly, SearchContext.IN_STRING_LITERALS); + } + + public void doApplyContextChange(boolean inCommentsOnly, SearchContext option) { + boolean changed = false; + if (inCommentsOnly) { + changed = searchContext == option; + searchContext = option; + } else if (searchContext == option) { // do not reset unrelated value + changed = true; + searchContext = SearchContext.ANY; + } + + if (changed) { + notifyObservers(); + } } public @NotNull SearchContext getSearchContext() { @@ -901,21 +927,18 @@ public class FindModel extends UserDataHolderBase implements Cloneable { } } - private static final Pattern NO_PATTERN = Pattern.compile(""); - private Pattern myPattern = NO_PATTERN; + private Pattern myPattern = PatternUtil.NOTHING; public Pattern compileRegExp() { String toFind = getStringToFind(); Pattern pattern = myPattern; - if (pattern == NO_PATTERN) { + if (pattern == PatternUtil.NOTHING) { try { myPattern = pattern = Pattern.compile(toFind, isCaseSensitive() ? Pattern.MULTILINE : Pattern.MULTILINE | Pattern.CASE_INSENSITIVE); } catch (PatternSyntaxException e) { - LOG.error("Regexp:'" + toFind + "'", e); - myPattern = null; - return null; + myPattern = pattern = null; } } diff --git a/platform/lang-impl/src/com/intellij/codeInsight/editorActions/IndentingBackspaceHandler.java b/platform/lang-impl/src/com/intellij/codeInsight/editorActions/IndentingBackspaceHandler.java index 501ea66fd767..cd49d111950b 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/editorActions/IndentingBackspaceHandler.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/editorActions/IndentingBackspaceHandler.java @@ -27,11 +27,8 @@ import com.intellij.openapi.editor.LogicalPosition; import com.intellij.openapi.fileEditor.FileDocumentManager; import com.intellij.openapi.fileTypes.FileType; import com.intellij.openapi.project.Project; -import com.intellij.openapi.util.Couple; -import com.intellij.openapi.util.TextRange; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.psi.PsiDocumentManager; import com.intellij.psi.PsiFile; import com.intellij.psi.codeStyle.CodeStyleSettings; import com.intellij.psi.codeStyle.CodeStyleSettingsManager; @@ -46,12 +43,10 @@ public class IndentingBackspaceHandler extends BackspaceHandlerDelegate { private static final Logger LOG = Logger.getInstance(IndentingBackspaceHandler.class); private boolean caretWasAtLineStart; - private int initialCaretOffset; @Override public void beforeCharDeleted(char c, PsiFile file, Editor editor) { caretWasAtLineStart = editor.getCaretModel().getLogicalPosition().column == 0; - initialCaretOffset = editor.getCaretModel().getOffset(); } @Override @@ -67,11 +62,10 @@ public class IndentingBackspaceHandler extends BackspaceHandlerDelegate { Project project = file.getProject(); Document document = editor.getDocument(); CaretModel caretModel = editor.getCaretModel(); - boolean isUncommitted = PsiDocumentManager.getInstance(project).isUncommited(document); int caretOffset = caretModel.getOffset(); int offset = CharArrayUtil.shiftForward(document.getCharsSequence(), caretOffset, " \t"); - int offsetInPsi = offset + (isUncommitted ? 1 : 0); + int offsetInPsi = getOffsetInPsi(document, file, offset); int beforeWhitespaceOffset = CharArrayUtil.shiftBackward(document.getCharsSequence(), offset - 1, " \t") + 1; LogicalPosition logicalPosition = caretOffset < offset ? editor.offsetToLogicalPosition(offset) : caretModel.getLogicalPosition(); int lineStartOffset = document.getLineStartOffset(logicalPosition.line); @@ -89,7 +83,7 @@ public class IndentingBackspaceHandler extends BackspaceHandlerDelegate { CodeStyleFacade codeStyleFacade = CodeStyleFacade.getInstance(project); // We should calculate indent at line containing the text after caret, cause that text might affect the result (e.g. closing brace in Java) - String indent = codeStyleFacade.getLineIndent(document, caretWasAtLineStart && isUncommitted ? initialCaretOffset : lineStartOffset); + String indent = codeStyleFacade.getLineIndent(document, offsetInPsi); if (indent == null) { return false; } @@ -131,6 +125,11 @@ public class IndentingBackspaceHandler extends BackspaceHandlerDelegate { return true; } + private static int getOffsetInPsi(Document document, PsiFile file, int offset) { + int psiLength = file.getTextLength(); + return Math.max(0, Math.min(psiLength, offset + psiLength - document.getTextLength())); + } + private static int getTabSize(@NotNull CodeStyleFacade codeStyleFacade, @NotNull Document document) { VirtualFile file = FileDocumentManager.getInstance().getFile(document); FileType fileType = file == null ? null : file.getFileType(); diff --git a/platform/lang-impl/src/com/intellij/codeInsight/template/impl/TemplateState.java b/platform/lang-impl/src/com/intellij/codeInsight/template/impl/TemplateState.java index 516ed9118c8c..0c838cd05ec5 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/template/impl/TemplateState.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/template/impl/TemplateState.java @@ -212,8 +212,11 @@ public class TemplateState implements Disposable { if (variableName.equals(TemplateImpl.END)) { return new TextResult(""); } - if (myPredefinedVariableValues != null && myPredefinedVariableValues.containsKey(variableName)) { - return new TextResult(myPredefinedVariableValues.get(variableName)); + if (myPredefinedVariableValues != null) { + String text = myPredefinedVariableValues.get(variableName); + if (text != null) { + return new TextResult(text); + } } CharSequence text = myDocument.getCharsSequence(); int segmentNumber = myTemplate.getVariableSegmentNumber(variableName); diff --git a/platform/lang-impl/src/com/intellij/execution/console/ConsoleHistoryController.java b/platform/lang-impl/src/com/intellij/execution/console/ConsoleHistoryController.java index 6c0bfffd450e..9aab415d5e70 100644 --- a/platform/lang-impl/src/com/intellij/execution/console/ConsoleHistoryController.java +++ b/platform/lang-impl/src/com/intellij/execution/console/ConsoleHistoryController.java @@ -45,7 +45,9 @@ import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.PsiFile; import com.intellij.psi.PsiFileFactory; import com.intellij.testFramework.LightVirtualFile; +import com.intellij.util.ExceptionUtil; import com.intellij.util.ObjectUtils; +import com.intellij.util.containers.ContainerUtil; import com.intellij.util.io.SafeFileOutputStream; import com.thoughtworks.xstream.io.HierarchicalStreamReader; import com.thoughtworks.xstream.io.xml.XppReader; @@ -56,7 +58,6 @@ import org.xmlpull.v1.XmlSerializer; import java.awt.event.KeyEvent; import java.io.*; -import java.util.ArrayList; import java.util.List; import java.util.ListIterator; @@ -65,6 +66,8 @@ import java.util.ListIterator; */ public class ConsoleHistoryController { + private static final int VERSION = 1; + private static final Logger LOG = Logger.getInstance("com.intellij.execution.console.ConsoleHistoryController"); private final LanguageConsoleImpl myConsole; @@ -394,7 +397,15 @@ public class ConsoleHistoryController { } } catch (Exception ex) { - LOG.error(ex); + //noinspection ThrowableResultOfMethodCallIgnored + Throwable cause = ExceptionUtil.getRootCause(ex); + if (cause instanceof EOFException) { + LOG.warn("Failed to load " + myType + " console history from: " + file.getPath(), ex); + return false; + } + else { + LOG.error(ex); + } } finally { if (xmlReader != null) { @@ -423,6 +434,7 @@ public class ConsoleHistoryController { } serializer.setOutput(os = new SafeFileOutputStream(file), CharsetToolkit.UTF8); saveHistory(serializer); + serializer.flush(); } catch (Exception ex) { LOG.error(ex); @@ -439,47 +451,58 @@ public class ConsoleHistoryController { } @Nullable - private String loadHistory(final HierarchicalStreamReader in, final String expectedId) { + private String loadHistory(HierarchicalStreamReader in, String expectedId) { if (!in.getNodeName().equals("console-history")) return null; - final String id = in.getAttribute("id"); + int version = StringUtil.parseInt(in.getAttribute("version"), 0); + String id = in.getAttribute("id"); if (!expectedId.equals(id)) return null; - final ArrayList entries = new ArrayList(); + List entries = ContainerUtil.newArrayList(); String consoleContent = null; while (in.hasMoreChildren()) { in.moveDown(); if ("history-entry".equals(in.getNodeName())) { - entries.add(in.getValue()); + entries.add(StringUtil.notNullize(in.getValue())); } else if ("console-content".equals(in.getNodeName())) { - consoleContent = in.getValue(); + consoleContent = StringUtil.notNullize(in.getValue()); } in.moveUp(); } for (ListIterator iterator = entries.listIterator(entries.size()); iterator.hasPrevious(); ) { - final String entry = iterator.previous(); + String entry = iterator.previous(); getModel().addToHistory(entry); } return consoleContent; } - private void saveHistory(final XmlSerializer out) throws IOException { + private void saveHistory(XmlSerializer out) throws IOException { out.startDocument(CharsetToolkit.UTF8, null); out.startTag(null, "console-history"); + out.attribute(null, "version", String.valueOf(VERSION)); out.attribute(null, "id", myId); - for (String s : getModel().getHistory()) { - out.startTag(null, "history-entry"); - out.text(s); - out.endTag(null, "history-entry"); + try { + for (String s : getModel().getHistory()) { + textTag(out, "history-entry", s); + } + String current = myContent; + if (StringUtil.isNotEmpty(current)) { + textTag(out, "console-content", current); + } } - String current = myContent; - if (StringUtil.isNotEmpty(current)) { - out.startTag(null, "console-content"); - out.text(current); - out.endTag(null, "console-content"); + finally { + out.endTag(null, "console-history"); + out.endDocument(); } - out.endTag(null, "console-history"); - out.endDocument(); } } + private static void textTag(@NotNull XmlSerializer out, @NotNull String tag, @NotNull String text) throws IOException { + out.startTag(null, tag); + try { + out.cdsect(text); + } + finally { + out.endTag(null, tag); + } + } } diff --git a/platform/lang-impl/src/com/intellij/find/editorHeaderActions/ToggleExceptCommentsAndLiteralsAction.java b/platform/lang-impl/src/com/intellij/find/editorHeaderActions/ToggleExceptCommentsAndLiteralsAction.java index ba7cc0e7be3e..d3ff1015e610 100644 --- a/platform/lang-impl/src/com/intellij/find/editorHeaderActions/ToggleExceptCommentsAndLiteralsAction.java +++ b/platform/lang-impl/src/com/intellij/find/editorHeaderActions/ToggleExceptCommentsAndLiteralsAction.java @@ -33,6 +33,6 @@ public class ToggleExceptCommentsAndLiteralsAction extends EditorHeaderToggleAct @Override public void setSelected(AnActionEvent e, boolean state) { - getEditorSearchComponent().getFindModel().setSearchContext(state ? FindModel.SearchContext.EXCEPT_STRINGS_AND_COMMENTS : FindModel.SearchContext.ANY); + getEditorSearchComponent().getFindModel().setSearchContext(state ? FindModel.SearchContext.EXCEPT_COMMENTS_AND_STRING_LITERALS : FindModel.SearchContext.ANY); } } diff --git a/platform/lang-impl/src/com/intellij/find/editorHeaderActions/ToggleExceptLiteralsAction.java b/platform/lang-impl/src/com/intellij/find/editorHeaderActions/ToggleExceptLiteralsAction.java index c6c0c7901e82..4b7f09c2b211 100644 --- a/platform/lang-impl/src/com/intellij/find/editorHeaderActions/ToggleExceptLiteralsAction.java +++ b/platform/lang-impl/src/com/intellij/find/editorHeaderActions/ToggleExceptLiteralsAction.java @@ -33,6 +33,6 @@ public class ToggleExceptLiteralsAction extends EditorHeaderToggleAction impleme @Override public void setSelected(AnActionEvent e, boolean state) { - getEditorSearchComponent().getFindModel().setSearchContext(state ? FindModel.SearchContext.EXCEPT_STRINGS : FindModel.SearchContext.ANY); + getEditorSearchComponent().getFindModel().setSearchContext(state ? FindModel.SearchContext.EXCEPT_STRING_LITERALS : FindModel.SearchContext.ANY); } } diff --git a/platform/lang-impl/src/com/intellij/find/editorHeaderActions/ToggleInLiteralsOnlyAction.java b/platform/lang-impl/src/com/intellij/find/editorHeaderActions/ToggleInLiteralsOnlyAction.java index 9046c92412ac..d8fc5d530095 100644 --- a/platform/lang-impl/src/com/intellij/find/editorHeaderActions/ToggleInLiteralsOnlyAction.java +++ b/platform/lang-impl/src/com/intellij/find/editorHeaderActions/ToggleInLiteralsOnlyAction.java @@ -18,6 +18,6 @@ public class ToggleInLiteralsOnlyAction extends EditorHeaderToggleAction implem @Override public void setSelected(AnActionEvent e, boolean state) { - getEditorSearchComponent().getFindModel().setSearchContext(state ? FindModel.SearchContext.IN_STRINGS : FindModel.SearchContext.ANY); + getEditorSearchComponent().getFindModel().setSearchContext(state ? FindModel.SearchContext.IN_STRING_LITERALS : FindModel.SearchContext.ANY); } } diff --git a/platform/lang-impl/src/com/intellij/find/findInProject/FindInProjectManager.java b/platform/lang-impl/src/com/intellij/find/findInProject/FindInProjectManager.java index 7bc0e0c595f8..d7f7665a9299 100644 --- a/platform/lang-impl/src/com/intellij/find/findInProject/FindInProjectManager.java +++ b/platform/lang-impl/src/com/intellij/find/findInProject/FindInProjectManager.java @@ -25,6 +25,7 @@ import com.intellij.find.impl.FindManagerImpl; import com.intellij.find.replaceInProject.ReplaceInProjectManager; import com.intellij.openapi.actionSystem.CommonDataKeys; import com.intellij.openapi.actionSystem.DataContext; +import com.intellij.openapi.actionSystem.PlatformDataKeys; import com.intellij.openapi.components.ServiceManager; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.project.Project; @@ -71,8 +72,14 @@ public class FindInProjectManager { findModel.setOpenInNewTab(toOpenInNewTab[0]); FindInProjectUtil.setDirectoryName(findModel, dataContext); - Editor editor = CommonDataKeys.EDITOR.getData(dataContext); - FindUtil.initStringToFindWithSelection(findModel, editor); + String text = PlatformDataKeys.PREDEFINED_TEXT.getData(dataContext); + if (text != null) { + FindModel.initStringToFindNoMultiline(findModel, text); + } + else { + Editor editor = CommonDataKeys.EDITOR.getData(dataContext); + FindUtil.initStringToFindWithSelection(findModel, editor); + } findManager.showFindDialog(findModel, new Runnable() { @Override diff --git a/platform/lang-impl/src/com/intellij/find/impl/FindDialog.java b/platform/lang-impl/src/com/intellij/find/impl/FindDialog.java index 1182e133f62d..4c21ddbefeb4 100644 --- a/platform/lang-impl/src/com/intellij/find/impl/FindDialog.java +++ b/platform/lang-impl/src/com/intellij/find/impl/FindDialog.java @@ -1000,7 +1000,7 @@ public class FindDialog extends DialogWrapper { String selectedSearchContextInUi = (String)mySearchContext.getSelectedItem(); FindModel.SearchContext searchContext = FindModel.SearchContext.ANY; if (FindBundle.message("find.context.in.literals.scope.label").equals(selectedSearchContextInUi)) { - searchContext = FindModel.SearchContext.IN_STRINGS; + searchContext = FindModel.SearchContext.IN_STRING_LITERALS; } else if (FindBundle.message("find.context.in.comments.scope.label").equals(selectedSearchContextInUi)) { searchContext = FindModel.SearchContext.IN_COMMENTS; @@ -1009,9 +1009,9 @@ public class FindDialog extends DialogWrapper { searchContext = FindModel.SearchContext.EXCEPT_COMMENTS; } else if (FindBundle.message("find.context.except.literals.scope.label").equals(selectedSearchContextInUi)) { - searchContext = FindModel.SearchContext.EXCEPT_STRINGS; + searchContext = FindModel.SearchContext.EXCEPT_STRING_LITERALS; } else if (FindBundle.message("find.context.except.comments.and.literals.scope.label").equals(selectedSearchContextInUi)) { - searchContext = FindModel.SearchContext.EXCEPT_STRINGS_AND_COMMENTS; + searchContext = FindModel.SearchContext.EXCEPT_COMMENTS_AND_STRING_LITERALS; } model.setSearchContext(searchContext); diff --git a/platform/lang-impl/src/com/intellij/find/impl/FindManagerImpl.java b/platform/lang-impl/src/com/intellij/find/impl/FindManagerImpl.java index bc99477ac08f..27e2b7215f25 100644 --- a/platform/lang-impl/src/com/intellij/find/impl/FindManagerImpl.java +++ b/platform/lang-impl/src/com/intellij/find/impl/FindManagerImpl.java @@ -27,7 +27,6 @@ import com.intellij.find.impl.livePreview.SearchResults; import com.intellij.lang.Language; import com.intellij.lang.LanguageParserDefinitions; import com.intellij.lang.ParserDefinition; -import com.intellij.lexer.LayeredLexer; import com.intellij.lexer.Lexer; import com.intellij.navigation.NavigationItem; import com.intellij.openapi.actionSystem.ActionManager; @@ -42,11 +41,6 @@ import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.editor.*; import com.intellij.openapi.editor.colors.TextAttributesKey; import com.intellij.openapi.editor.ex.FoldingModelEx; -import com.intellij.openapi.editor.ex.util.LayeredHighlighterIterator; -import com.intellij.openapi.editor.ex.util.LayeredLexerEditorHighlighter; -import com.intellij.openapi.editor.highlighter.EditorHighlighter; -import com.intellij.openapi.editor.highlighter.EditorHighlighterFactory; -import com.intellij.openapi.editor.highlighter.HighlighterIterator; import com.intellij.openapi.editor.markup.RangeHighlighter; import com.intellij.openapi.fileEditor.FileEditor; import com.intellij.openapi.fileEditor.TextEditor; @@ -58,7 +52,6 @@ import com.intellij.openapi.util.*; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.*; -import com.intellij.psi.impl.search.LexerEditorHighlighterLexer; import com.intellij.psi.search.SearchScope; import com.intellij.psi.tree.IElementType; import com.intellij.psi.tree.TokenSet; @@ -66,8 +59,10 @@ import com.intellij.ui.LightweightHint; import com.intellij.ui.ReplacePromptDialog; import com.intellij.usages.ChunkExtractor; import com.intellij.usages.UsageViewManager; +import com.intellij.usages.impl.SyntaxHighlighterOverEditorHighlighter; import com.intellij.util.Consumer; import com.intellij.util.containers.ContainerUtil; +import com.intellij.util.containers.Predicate; import com.intellij.util.messages.MessageBus; import com.intellij.util.text.CharArrayUtil; import com.intellij.util.text.StringSearcher; @@ -318,19 +313,23 @@ public class FindManagerImpl extends FindManager implements PersistentStateCompo LOG.debug(model.toString()); } - final char[] textArray = CharArrayUtil.fromSequenceWithoutCopying(text); + return findStringLoop(text, offset, model, file, getFindContextPredicate(model, file, text)); + } + private FindResult findStringLoop(CharSequence text, int offset, FindModel model, VirtualFile file, @Nullable Predicate filter) { + final char[] textArray = CharArrayUtil.fromSequenceWithoutCopying(text); while(true) { FindResult result = doFindString(text, textArray, offset, model, file); - - if (!model.isWholeWordsOnly()) { - return result; - } - if (!result.isStringFound()){ - return result; - } - if (isWholeWord(text, result.getStartOffset(), result.getEndOffset())){ - return result; + if (filter == null || filter.apply(result)) { + if (!model.isWholeWordsOnly()) { + return result; + } + if (!result.isStringFound()) { + return result; + } + if (isWholeWord(text, result.getStartOffset(), result.getEndOffset())) { + return result; + } } offset = model.isForward() ? result.getStartOffset() + 1 : result.getEndOffset() - 1; @@ -338,6 +337,80 @@ public class FindManagerImpl extends FindManager implements PersistentStateCompo } } + private class FindExceptCommentsOrLiteralsData implements Predicate { + private final VirtualFile myFile; + private final FindModel myFindModel; + private final TreeMap mySkipRangesSet; + + private FindExceptCommentsOrLiteralsData(VirtualFile file, FindModel model, CharSequence text) { + myFile = file; + myFindModel = model.clone(); + + TreeMap result = new TreeMap(); + + if (model.isExceptComments() || model.isExceptCommentsAndStringLiterals()) { + addRanges(file, model, text, result, FindModel.SearchContext.IN_COMMENTS); + } + + if (model.isExceptStringLiterals() || model.isExceptCommentsAndStringLiterals()) { + addRanges(file, model, text, result, FindModel.SearchContext.IN_STRING_LITERALS); + } + + mySkipRangesSet = result; + } + + private void addRanges(VirtualFile file, + FindModel model, + CharSequence text, + TreeMap result, + FindModel.SearchContext searchContext) { + FindModel clonedModel = model.clone(); + clonedModel.setSearchContext(searchContext); + clonedModel.setForward(true); + int offset = 0; + + while(true) { + FindResult customResult = findStringLoop(text, offset, clonedModel, file, null); + if (!customResult.isStringFound()) break; + result.put(customResult.getStartOffset(), customResult.getEndOffset()); + offset = Math.max(customResult.getEndOffset(), offset + 1); // avoid loop for zero size reg exps matches + if (offset >= text.length()) break; + } + } + + boolean isAcceptableFor(FindModel model, VirtualFile file) { + return Comparing.equal(myFile, file) && myFindModel.equals(model); + } + + @Override + public boolean apply(@Nullable FindResult input) { + if (input == null || !input.isStringFound()) return true; + NavigableMap map = mySkipRangesSet.headMap(input.getStartOffset(), true); + for(Map.Entry e:map.descendingMap().entrySet()) { + if (e.getKey() <= input.getStartOffset() && e.getValue() >= input.getEndOffset()) return false; + if (e.getValue() <= input.getStartOffset()) break; + } + return true; + } + } + private static Key ourExceptCommentsOrLiteralsDataKey = Key.create("except.comments.literals.search.data"); + + private Predicate getFindContextPredicate(@NotNull FindModel model, VirtualFile file, CharSequence text) { + if (file == null) return null; + FindModel.SearchContext context = model.getSearchContext(); + if( context == FindModel.SearchContext.ANY || context == FindModel.SearchContext.IN_COMMENTS || + context == FindModel.SearchContext.IN_STRING_LITERALS) { + return null; + } + + FindExceptCommentsOrLiteralsData data = model.getUserData(ourExceptCommentsOrLiteralsDataKey); + if (data == null || !data.isAcceptableFor(model, file)) { + model.putUserData(ourExceptCommentsOrLiteralsDataKey, data = new FindExceptCommentsOrLiteralsData(file, model, text)); + } + + return data; + } + @Override public int showMalformedReplacementPrompt(@NotNull FindModel model, String title, MalformedReplacementStringException exception) { return showPromptDialogImpl(model, title, exception); @@ -421,11 +494,15 @@ public class FindManagerImpl extends FindManager implements PersistentStateCompo return new StringSearcher(model.getStringToFind(), model.isCaseSensitive(), model.isForward()); } + public static void clearPreviousFindData(FindModel model) { + model.putUserData(ourCommentsLiteralsSearchDataKey, null); + model.putUserData(ourExceptCommentsOrLiteralsDataKey, null); + } + private static class CommentsLiteralsSearchData { final VirtualFile lastFile; int startOffset = 0; - final SyntaxHighlighter highlighter; - final Lexer highlightingLexer; + final SyntaxHighlighterOverEditorHighlighter highlighter; TokenSet tokensOfInterest; final StringSearcher searcher; @@ -433,8 +510,8 @@ public class FindManagerImpl extends FindManager implements PersistentStateCompo final Set relevantLanguages; final FindModel model; - public CommentsLiteralsSearchData(VirtualFile lastFile, Set relevantLanguages, SyntaxHighlighter highlighter, - Lexer lexer, TokenSet tokensOfInterest, + public CommentsLiteralsSearchData(VirtualFile lastFile, Set relevantLanguages, + SyntaxHighlighterOverEditorHighlighter highlighter, TokenSet tokensOfInterest, StringSearcher searcher, Matcher matcher, FindModel model) { this.lastFile = lastFile; this.highlighter = highlighter; @@ -442,12 +519,11 @@ public class FindManagerImpl extends FindManager implements PersistentStateCompo this.searcher = searcher; this.matcher = matcher; this.relevantLanguages = relevantLanguages; - highlightingLexer = lexer; this.model = model; } } - public static final Key ourCommentsLiteralsSearchDataKey = Key.create("comments.literals.search.data"); + private static final Key ourCommentsLiteralsSearchDataKey = Key.create("comments.literals.search.data"); @NotNull private FindResult findInCommentsAndLiterals(@NotNull CharSequence text, @@ -525,38 +601,15 @@ public class FindManagerImpl extends FindManager implements PersistentStateCompo Matcher matcher = model.isRegularExpressions() ? compileRegExp(model, ""):null; StringSearcher searcher = matcher != null ? null: new StringSearcher(model.getStringToFind(), model.isCaseSensitive(), true); - LayeredLexer.ourDisableLayersFlag.set(Boolean.TRUE); - EditorHighlighter editorHighlighter = EditorHighlighterFactory.getInstance().createEditorHighlighter(myProject, file); - Lexer lexer; - - try { - if (editorHighlighter instanceof LayeredLexerEditorHighlighter) { - lexer = new LexerEditorHighlighterLexer(editorHighlighter, false); - } else { - lexer = highlighter.getHighlightingLexer(); - } - } - finally { - LayeredLexer.ourDisableLayersFlag.set(null); - } - - data = new CommentsLiteralsSearchData(file, relevantLanguages, highlighter, lexer, tokensOfInterest, searcher, matcher, (FindModel)model.clone()); - lexer.start(text, 0, text.length(), 0); + SyntaxHighlighterOverEditorHighlighter highlighterAdapter = new SyntaxHighlighterOverEditorHighlighter(highlighter, file, myProject); + data = new CommentsLiteralsSearchData(file, relevantLanguages, highlighterAdapter, tokensOfInterest, searcher, matcher, model.clone()); + data.highlighter.restart(text); model.putUserData(ourCommentsLiteralsSearchDataKey, data); } int initialStartOffset = model.isForward() && data.startOffset < offset ? data.startOffset : 0; - final Lexer lexer = data.highlightingLexer; - LayeredHighlighterIterator layeredHighlighterIterator = null; - if (lexer instanceof LexerEditorHighlighterLexer) { - ((LexerEditorHighlighterLexer)lexer).resetPosition(initialStartOffset); - HighlighterIterator iterator = ((LexerEditorHighlighterLexer)lexer).getHighlighterIterator(); - if (iterator instanceof LayeredHighlighterIterator) { - layeredHighlighterIterator = (LayeredHighlighterIterator)iterator; - } - } else { - lexer.start(text, initialStartOffset, text.length(), 0); - } + data.highlighter.resetPosition(initialStartOffset); + final Lexer lexer = data.highlighter.getHighlightingLexer(); IElementType tokenType; TokenSet tokens = data.tokensOfInterest; @@ -568,9 +621,7 @@ public class FindManagerImpl extends FindManager implements PersistentStateCompo while((tokenType = lexer.getTokenType()) != null) { if (lexer.getState() == 0) lastGoodOffset = lexer.getTokenStart(); - final SyntaxHighlighter activeSyntaxHighlighter = - layeredHighlighterIterator != null ? layeredHighlighterIterator.getActiveSyntaxHighlighter() : data.highlighter; - final TextAttributesKey[] keys = activeSyntaxHighlighter.getTokenHighlights(tokenType); + final TextAttributesKey[] keys = data.highlighter.getTokenHighlights(tokenType); if (tokens.contains(tokenType) || (model.isInStringLiteralsOnly() && ChunkExtractor.isHighlightedAsString(keys)) || diff --git a/platform/lang-impl/src/com/intellij/find/impl/FindResultUsageInfo.java b/platform/lang-impl/src/com/intellij/find/impl/FindResultUsageInfo.java index 6baf8cc4fca8..ee8d98422250 100644 --- a/platform/lang-impl/src/com/intellij/find/impl/FindResultUsageInfo.java +++ b/platform/lang-impl/src/com/intellij/find/impl/FindResultUsageInfo.java @@ -80,7 +80,7 @@ public class FindResultUsageInfo extends UsageInfo { Long data = myFindModel.getUserData(ourDocumentTimestampKey); if (data == null || data != myTimestamp) { data = myTimestamp; - myFindModel.putUserData(FindManagerImpl.ourCommentsLiteralsSearchDataKey, null); + FindManagerImpl.clearPreviousFindData(myFindModel); } myFindModel.putUserData(ourDocumentTimestampKey, data); FindResult result; diff --git a/platform/lang-impl/src/com/intellij/find/impl/FindSettingsImpl.java b/platform/lang-impl/src/com/intellij/find/impl/FindSettingsImpl.java index b96d14b08c59..c9c8e3492834 100644 --- a/platform/lang-impl/src/com/intellij/find/impl/FindSettingsImpl.java +++ b/platform/lang-impl/src/com/intellij/find/impl/FindSettingsImpl.java @@ -104,8 +104,8 @@ public class FindSettingsImpl extends FindSettings implements PersistentStateCom @SuppressWarnings({"WeakerAccess"}) public boolean COMMENTS_ONLY = false; @SuppressWarnings({"WeakerAccess"}) public boolean STRING_LITERALS_ONLY = false; @SuppressWarnings({"WeakerAccess"}) public boolean EXCEPT_COMMENTS = false; - @SuppressWarnings({"WeakerAccess"}) public boolean EXCEPT_COMMENTS_AND_LITERALS = false; - @SuppressWarnings({"WeakerAccess"}) public boolean EXCEPT_LITERALS = false; + @SuppressWarnings({"WeakerAccess"}) public boolean EXCEPT_COMMENTS_AND_STRING_LITERALS = false; + @SuppressWarnings({"WeakerAccess"}) public boolean EXCEPT_STRING_LITERALS = false; @SuppressWarnings({"WeakerAccess"}) public boolean LOCAL_WHOLE_WORDS_ONLY = false; @SuppressWarnings({"WeakerAccess"}) public boolean REGULAR_EXPRESSIONS = false; @SuppressWarnings({"WeakerAccess"}) public boolean LOCAL_REGULAR_EXPRESSIONS = false; @@ -282,13 +282,13 @@ public class FindSettingsImpl extends FindSettings implements PersistentStateCom FindModel.SearchContext searchContext = isInCommentsOnly() ? FindModel.SearchContext.IN_COMMENTS : isInStringLiteralsOnly() ? - FindModel.SearchContext.IN_STRINGS : + FindModel.SearchContext.IN_STRING_LITERALS : isExceptComments() ? FindModel.SearchContext.EXCEPT_COMMENTS : isExceptStringLiterals() ? - FindModel.SearchContext.EXCEPT_STRINGS : + FindModel.SearchContext.EXCEPT_STRING_LITERALS : isExceptCommentsAndLiterals() ? - FindModel.SearchContext.EXCEPT_STRINGS_AND_COMMENTS : + FindModel.SearchContext.EXCEPT_COMMENTS_AND_STRING_LITERALS : FindModel.SearchContext.ANY; model.setSearchContext(searchContext); model.setWithSubdirectories(isWithSubdirectories()); @@ -405,12 +405,12 @@ public class FindSettingsImpl extends FindSettings implements PersistentStateCom @Override public void setExceptCommentsAndLiterals(boolean selected) { - EXCEPT_COMMENTS_AND_LITERALS = selected; + EXCEPT_COMMENTS_AND_STRING_LITERALS = selected; } @Override public boolean isExceptCommentsAndLiterals() { - return EXCEPT_COMMENTS_AND_LITERALS; + return EXCEPT_COMMENTS_AND_STRING_LITERALS; } @Override @@ -420,11 +420,11 @@ public class FindSettingsImpl extends FindSettings implements PersistentStateCom @Override public boolean isExceptStringLiterals() { - return EXCEPT_LITERALS; + return EXCEPT_STRING_LITERALS; } @Override public void setExceptStringLiterals(boolean selected) { - EXCEPT_LITERALS = selected; + EXCEPT_STRING_LITERALS = selected; } } diff --git a/platform/lang-impl/src/com/intellij/formatting/FormatterImpl.java b/platform/lang-impl/src/com/intellij/formatting/FormatterImpl.java index d28a68570e4d..7b40b786ff7a 100644 --- a/platform/lang-impl/src/com/intellij/formatting/FormatterImpl.java +++ b/platform/lang-impl/src/com/intellij/formatting/FormatterImpl.java @@ -128,7 +128,7 @@ public class FormatterImpl extends FormatterEx if (spacing == null) { return 0; } - return spacing.getMinSpaces(); + return Math.max(spacing.getMinSpaces(), 0); } private static Couple getBlockAtOffset(Block parent, Block block, int offset) { diff --git a/platform/lang-impl/src/com/intellij/psi/impl/source/codeStyle/CodeStyleManagerRunnable.java b/platform/lang-impl/src/com/intellij/psi/impl/source/codeStyle/CodeStyleManagerRunnable.java index 1b17faf21905..42dcb46c7ac0 100644 --- a/platform/lang-impl/src/com/intellij/psi/impl/source/codeStyle/CodeStyleManagerRunnable.java +++ b/platform/lang-impl/src/com/intellij/psi/impl/source/codeStyle/CodeStyleManagerRunnable.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2010 JetBrains s.r.o. + * Copyright 2000-2014 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -149,7 +149,7 @@ abstract class CodeStyleManagerRunnable { SourceTreeToPsiMap.psiElementToTree(CodeStyleManagerImpl.findElementInTreeWithFormatterEnabled(file, offset)); if (elementAtOffset == null) { int significantRangeStart = CharArrayUtil.shiftBackward(file.getText(), offset - 1, "\r\t "); - return new TextRange(significantRangeStart, offset); + return new TextRange(Math.max(significantRangeStart, 0), offset); } final FormattingModelBuilder builder = LanguageFormatting.INSTANCE.forContext(file); diff --git a/platform/lang-impl/src/com/intellij/webcore/packaging/PackagesNotificationPanel.java b/platform/lang-impl/src/com/intellij/webcore/packaging/PackagesNotificationPanel.java index d33bf51e9002..1513bb451aa2 100644 --- a/platform/lang-impl/src/com/intellij/webcore/packaging/PackagesNotificationPanel.java +++ b/platform/lang-impl/src/com/intellij/webcore/packaging/PackagesNotificationPanel.java @@ -8,7 +8,7 @@ import com.intellij.openapi.util.text.StringUtil; import com.intellij.ui.HyperlinkAdapter; import com.intellij.ui.ScrollPaneFactory; import com.intellij.ui.components.JBLabel; -import com.intellij.util.ObjectUtils; +import com.intellij.util.ui.SwingHelper; import com.intellij.util.ui.UIUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -23,19 +23,17 @@ import java.util.Map; * @author yole */ public class PackagesNotificationPanel { - private final JEditorPane myEditorPane = new MyNotificationPane(); private final Project myProject; + private final JEditorPane myHtmlViewer; private final Map myLinkHandlers = new HashMap(); private String myErrorTitle; private String myErrorDescription; - public PackagesNotificationPanel(Project project) { + public PackagesNotificationPanel(@NotNull Project project) { myProject = project; - myEditorPane.setBackground(UIManager.getColor("ArrowButton.background")); - myEditorPane.setContentType("text/html"); - myEditorPane.setEditable(false); - myEditorPane.setVisible(false); - myEditorPane.addHyperlinkListener(new HyperlinkAdapter() { + myHtmlViewer = SwingHelper.createHtmlViewer(true, null, null, null); + myHtmlViewer.setVisible(false); + myHtmlViewer.addHyperlinkListener(new HyperlinkAdapter() { @Override protected void hyperlinkActivated(HyperlinkEvent e) { final Runnable handler = myLinkHandlers.get(e.getDescription()); @@ -78,11 +76,18 @@ public class PackagesNotificationPanel { public void showResult(String packageName, @Nullable String errorDescription) { if (StringUtil.isEmpty(errorDescription)) { - showSuccess("Package successfully installed."); + String message = "Package installed successfully"; + if (packageName != null) { + message = "Package '" + packageName + "' installed successfully"; + } + showSuccess(message); } else { - String title = "Install packages failed"; - final String firstLine = title + ": Error occurred when installing package " + packageName + ". "; + String title = "Failed to install packages"; + if (packageName != null) { + title = "Failed to install package '" + packageName + "'"; + } + String firstLine = "Error occurred when installing package '" + packageName + "'. "; showError(firstLine + "Details...", title, firstLine + errorDescription); @@ -98,17 +103,17 @@ public class PackagesNotificationPanel { } public JComponent getComponent() { - return myEditorPane; + return myHtmlViewer; } public void showSuccess(String text) { showContent(text, MessageType.INFO.getPopupBackground()); } - private void showContent(String text, final Color background) { + private void showContent(@NotNull String text, @NotNull Color background) { String htmlText = text.startsWith("") ? text : UIUtil.toHtml(text); - myEditorPane.setText(htmlText); - myEditorPane.setBackground(background); + myHtmlViewer.setText(htmlText); + myHtmlViewer.setBackground(background); setVisibleEditorPane(true); myErrorTitle = null; myErrorDescription = null; @@ -129,15 +134,11 @@ public class PackagesNotificationPanel { } private void setVisibleEditorPane(boolean visible) { - boolean oldVisible = myEditorPane.isVisible(); - myEditorPane.setVisible(visible); + boolean oldVisible = myHtmlViewer.isVisible(); + myHtmlViewer.setVisible(visible); if (oldVisible != visible) { - JComponent comp = ObjectUtils.tryCast(myEditorPane.getParent(), JComponent.class); - if (comp == null) { - comp = myEditorPane; - } - comp.revalidate(); - comp.repaint(); + myHtmlViewer.revalidate(); + myHtmlViewer.repaint(); } } @@ -145,17 +146,6 @@ public class PackagesNotificationPanel { return myLinkHandlers.containsKey(key); } - private static class MyNotificationPane extends JEditorPane { - @Override - public Dimension getPreferredSize() { - // This trick makes text component to carry text over to the next line - // iff the text line width exceeds parent's width - Dimension dimension = super.getPreferredSize(); - dimension.width = 0; - return dimension; - } - } - public void removeLinkHandler(String key) { myLinkHandlers.remove(key); } diff --git a/platform/lang-impl/testSources/com/intellij/codeInsight/editorActions/IndentingBackspaceHandlerUncommittedDocumentTest.java b/platform/lang-impl/testSources/com/intellij/codeInsight/editorActions/IndentingBackspaceHandlerUncommittedDocumentTest.java new file mode 100644 index 000000000000..c4913f888f1b --- /dev/null +++ b/platform/lang-impl/testSources/com/intellij/codeInsight/editorActions/IndentingBackspaceHandlerUncommittedDocumentTest.java @@ -0,0 +1,34 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.intellij.codeInsight.editorActions; + +import com.intellij.testFramework.LightPlatformCodeInsightTestCase; + +import java.io.IOException; + +public class IndentingBackspaceHandlerUncommittedDocumentTest extends LightPlatformCodeInsightTestCase { + public void testSequentialBackspaceInvocation() throws IOException { + configureFromFileText(getTestName(false) + ".java", + "class Foo {\n" + + "\n" + + "\n" + + "}"); + backspace(); + backspace(); + checkResultByText("class Foo {\n" + + "}"); + } +} \ No newline at end of file diff --git a/platform/platform-api/src/com/intellij/openapi/actionSystem/IdeActions.java b/platform/platform-api/src/com/intellij/openapi/actionSystem/IdeActions.java index 6ade9b77afc0..049bde52f582 100644 --- a/platform/platform-api/src/com/intellij/openapi/actionSystem/IdeActions.java +++ b/platform/platform-api/src/com/intellij/openapi/actionSystem/IdeActions.java @@ -271,7 +271,7 @@ public interface IdeActions { String ACTION_UNDO = "$Undo"; String ACTION_REDO = "$Redo"; String GROUP_REFACTOR = "RefactoringMenu"; - String SELECTED_CHANGES_ROLLBACK = "RollbackLineStatusChanges"; + String SELECTED_CHANGES_ROLLBACK = "Vcs.RollbackChangedLines"; String CHANGES_VIEW_ROLLBACK = "ChangesView.Rollback"; String CONSOLE_CLEAR_ALL = "ConsoleView.ClearAll"; diff --git a/platform/platform-impl/src/com/intellij/ide/actions/SplitterActionBase.java b/platform/platform-impl/src/com/intellij/ide/actions/SplitterActionBase.java index 14216a6a29bd..dde5a5c898dd 100644 --- a/platform/platform-impl/src/com/intellij/ide/actions/SplitterActionBase.java +++ b/platform/platform-impl/src/com/intellij/ide/actions/SplitterActionBase.java @@ -27,9 +27,9 @@ public abstract class SplitterActionBase extends AnAction implements DumbAware { public void update(final AnActionEvent event) { final Project project = CommonDataKeys.PROJECT.getData(event.getDataContext()); final Presentation presentation = event.getPresentation(); - boolean context = ActionPlaces.isPopupPlace(event.getPlace()); - boolean enabled = project != null && isActionEnabled(project, context); - if (context) { + boolean inContextMenu = ActionPlaces.isPopupPlace(event.getPlace()); + boolean enabled = project != null && isActionEnabled(project, inContextMenu); + if (inContextMenu) { presentation.setVisible(enabled); } else { @@ -37,7 +37,17 @@ public abstract class SplitterActionBase extends AnAction implements DumbAware { } } - protected boolean isActionEnabled(Project project, boolean context) { + /** + * This method determines whether the action is enabled for {@code project} + * if {@code inContextMenu} is set to {@code false}. Otherwise, + * it determines whether the action is visible in the context menu. + * + * @param project the specified project + * @param inContextMenu whether the action is used in context menu + * @return {@code true} if the action is enabled, + * {@code false} otherwise + */ + protected boolean isActionEnabled(Project project, boolean inContextMenu) { final FileEditorManagerEx fileEditorManager = FileEditorManagerEx.getInstanceEx(project); return fileEditorManager.isInSplitter(); } diff --git a/platform/platform-impl/src/com/intellij/ide/actions/UnsplitAllAction.java b/platform/platform-impl/src/com/intellij/ide/actions/UnsplitAllAction.java index 32a7f05388c1..ec724884ef2d 100644 --- a/platform/platform-impl/src/com/intellij/ide/actions/UnsplitAllAction.java +++ b/platform/platform-impl/src/com/intellij/ide/actions/UnsplitAllAction.java @@ -32,8 +32,8 @@ public final class UnsplitAllAction extends SplitterActionBase { } @Override - protected boolean isActionEnabled(Project project, boolean context) { + protected boolean isActionEnabled(Project project, boolean inContextMenu) { final FileEditorManagerEx fileEditorManager = FileEditorManagerEx.getInstanceEx(project); - return context ? fileEditorManager.getWindowSplitCount() > 2 : fileEditorManager.isInSplitter(); + return inContextMenu ? fileEditorManager.getWindowSplitCount() > 2 : fileEditorManager.isInSplitter(); } } diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/impl/EditorHighlighterCache.java b/platform/platform-impl/src/com/intellij/openapi/editor/impl/EditorHighlighterCache.java index c1499a71a71b..8be2284e8360 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/impl/EditorHighlighterCache.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/impl/EditorHighlighterCache.java @@ -15,10 +15,19 @@ */ package com.intellij.openapi.editor.impl; +import com.intellij.lexer.Lexer; import com.intellij.openapi.editor.Document; import com.intellij.openapi.editor.ex.util.LexerEditorHighlighter; import com.intellij.openapi.editor.highlighter.EditorHighlighter; +import com.intellij.openapi.editor.highlighter.EditorHighlighterFactory; +import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Key; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.psi.PsiDocumentManager; +import com.intellij.psi.PsiFile; +import com.intellij.psi.PsiManager; +import com.intellij.psi.impl.cache.impl.id.PlatformIdTableBuilding; +import com.intellij.psi.impl.search.LexerEditorHighlighterLexer; import com.intellij.reference.SoftReference; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -55,4 +64,28 @@ public class EditorHighlighterCache { return null; } + @Nullable + public static Lexer getLexerBasedOnLexerHighlighter(CharSequence text, VirtualFile virtualFile, Project project) { + EditorHighlighter highlighter = null; + + PsiFile psiFile = virtualFile != null ? PsiManager.getInstance(project).findFile(virtualFile) : null; + final Document document = psiFile != null ? PsiDocumentManager.getInstance(project).getDocument(psiFile) : null; + final EditorHighlighter cachedEditorHighlighter; + boolean alreadyInitializedHighlighter = false; + + if (document != null && + (cachedEditorHighlighter = getEditorHighlighterForCachesBuilding(document)) != null && + PlatformIdTableBuilding.checkCanUseCachedEditorHighlighter(text, cachedEditorHighlighter)) { + highlighter = cachedEditorHighlighter; + alreadyInitializedHighlighter = true; + } + else if (virtualFile != null) { + highlighter = EditorHighlighterFactory.getInstance().createEditorHighlighter(project, virtualFile); + } + + if (highlighter != null) { + return new LexerEditorHighlighterLexer(highlighter, alreadyInitializedHighlighter); + } + return null; + } } diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/impl/EditorMarkupModelImpl.java b/platform/platform-impl/src/com/intellij/openapi/editor/impl/EditorMarkupModelImpl.java index f89d92ffd026..e276fe2cd4da 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/impl/EditorMarkupModelImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/impl/EditorMarkupModelImpl.java @@ -46,7 +46,6 @@ import com.intellij.openapi.ui.MessageType; import com.intellij.openapi.ui.popup.Balloon; import com.intellij.openapi.util.Disposer; import com.intellij.openapi.util.ProperTextRange; -import com.intellij.openapi.util.SystemInfo; import com.intellij.openapi.util.registry.Registry; import com.intellij.openapi.wm.ToolWindowAnchor; import com.intellij.openapi.wm.ex.ToolWindowManagerEx; @@ -430,7 +429,7 @@ public class EditorMarkupModelImpl extends MarkupModelImpl implements EditorMark } @Override - public void paint(Graphics g) { + public void paint(@NotNull Graphics g) { ((ApplicationImpl)ApplicationManager.getApplication()).editorPaintStart(); final Rectangle bounds = getBounds(); @@ -439,7 +438,7 @@ public class EditorMarkupModelImpl extends MarkupModelImpl implements EditorMark errorIconBounds.y = bounds.height / 2 - errorIconBounds.height / 2; try { - if (UISettings.getInstance().PRESENTATION_MODE || SystemInfo.isMac) { + if (UISettings.getInstance().PRESENTATION_MODE || ButtonlessScrollBarUI.isMacOverlayScrollbarSupported()) { g.setColor(getEditor().getColorsScheme().getDefaultBackground()); g.fillRect(0, 0, bounds.width, bounds.height); @@ -495,7 +494,7 @@ public class EditorMarkupModelImpl extends MarkupModelImpl implements EditorMark } @Override - public void uninstallUI(JComponent c) { + public void uninstallUI(@NotNull JComponent c) { super.uninstallUI(c); myCachedTrack = null; } @@ -530,7 +529,7 @@ public class EditorMarkupModelImpl extends MarkupModelImpl implements EditorMark @Override protected void paintThumb(Graphics g, JComponent c, Rectangle thumbBounds) { - if (UISettings.getInstance().PRESENTATION_MODE || SystemInfo.isMac) { + if (UISettings.getInstance().PRESENTATION_MODE || ButtonlessScrollBarUI.isMacOverlayScrollbarSupported()) { super.paintThumb(g, c, thumbBounds); return; } @@ -567,13 +566,13 @@ public class EditorMarkupModelImpl extends MarkupModelImpl implements EditorMark @Override protected int getThickness() { - if (UISettings.getInstance().PRESENTATION_MODE || SystemInfo.isMac) return super.getThickness(); + if (UISettings.getInstance().PRESENTATION_MODE || ButtonlessScrollBarUI.isMacOverlayScrollbarSupported()) return super.getThickness(); return super.getThickness() + (isMacOverlayScrollbar() ? 2 : 7); } @Override protected void doPaintTrack(Graphics g, JComponent c, Rectangle bounds) { - if (UISettings.getInstance().PRESENTATION_MODE || SystemInfo.isMac) { + if (UISettings.getInstance().PRESENTATION_MODE || ButtonlessScrollBarUI.isMacOverlayScrollbarSupported()) { g.setColor(getEditor().getColorsScheme().getDefaultBackground()); g.fillRect(bounds.x, bounds.y, bounds.width, bounds.height); //return; @@ -611,7 +610,7 @@ public class EditorMarkupModelImpl extends MarkupModelImpl implements EditorMark } private void paintTrackBasement(Graphics g, Rectangle bounds) { - if (UISettings.getInstance().PRESENTATION_MODE || SystemInfo.isMac) { + if (UISettings.getInstance().PRESENTATION_MODE || ButtonlessScrollBarUI.isMacOverlayScrollbarSupported()) { g.setColor(EditorColorsManager.getInstance().getGlobalScheme().getDefaultBackground()); g.fillRect(bounds.x, bounds.y, bounds.width, bounds.height); return; @@ -651,13 +650,13 @@ public class EditorMarkupModelImpl extends MarkupModelImpl implements EditorMark private void drawMarkup(final Graphics g, final int width, int startOffset, int endOffset, MarkupModelEx markup) { final Queue thinEnds = new PriorityQueue(5, new Comparator() { @Override - public int compare(PositionedStripe o1, PositionedStripe o2) { + public int compare(@NotNull PositionedStripe o1, @NotNull PositionedStripe o2) { return o1.yEnd - o2.yEnd; } }); final Queue wideEnds = new PriorityQueue(5, new Comparator() { @Override - public int compare(PositionedStripe o1, PositionedStripe o2) { + public int compare(@NotNull PositionedStripe o1, @NotNull PositionedStripe o2) { return o1.yEnd - o2.yEnd; } }); @@ -799,7 +798,7 @@ public class EditorMarkupModelImpl extends MarkupModelImpl implements EditorMark // mouse events @Override - public void mouseClicked(final MouseEvent e) { + public void mouseClicked(@NotNull final MouseEvent e) { CommandProcessor.getInstance().executeCommand(myEditor.getProject(), new Runnable() { @Override public void run() { @@ -813,11 +812,11 @@ public class EditorMarkupModelImpl extends MarkupModelImpl implements EditorMark } @Override - public void mousePressed(MouseEvent e) { + public void mousePressed(@NotNull MouseEvent e) { } @Override - public void mouseReleased(MouseEvent e) { + public void mouseReleased(@NotNull MouseEvent e) { } private int getWidth() { @@ -836,7 +835,7 @@ public class EditorMarkupModelImpl extends MarkupModelImpl implements EditorMark } @Override - public void mouseMoved(MouseEvent e) { + public void mouseMoved(@NotNull MouseEvent e) { EditorImpl.MyScrollBar scrollBar = myEditor.getVerticalScrollBar(); int buttonHeight = scrollBar.getDecScrollButtonHeight(); int lineCount = getDocument().getLineCount() + myEditor.getSettings().getAdditionalLinesCount(); @@ -909,16 +908,16 @@ public class EditorMarkupModelImpl extends MarkupModelImpl implements EditorMark } @Override - public void mouseEntered(MouseEvent e) { + public void mouseEntered(@NotNull MouseEvent e) { } @Override - public void mouseExited(MouseEvent e) { + public void mouseExited(@NotNull MouseEvent e) { cancelMyToolTips(e, true); } @Override - public void mouseDragged(MouseEvent e) { + public void mouseDragged(@NotNull MouseEvent e) { cancelMyToolTips(e, true); } @@ -1149,7 +1148,7 @@ public class EditorMarkupModelImpl extends MarkupModelImpl implements EditorMark myHighlighters.add(rangeHighlighter); } Collections.sort(myHighlighters, new Comparator() { - public int compare(RangeHighlighterEx ex1, RangeHighlighterEx ex2) { + public int compare(@NotNull RangeHighlighterEx ex1, @NotNull RangeHighlighterEx ex2) { LogicalPosition startPos1 = myEditor.offsetToLogicalPosition(ex1.getAffectedAreaStartOffset()); LogicalPosition startPos2 = myEditor.offsetToLogicalPosition(ex2.getAffectedAreaStartOffset()); if (startPos1.line != startPos2.line) return 0; @@ -1179,7 +1178,7 @@ public class EditorMarkupModelImpl extends MarkupModelImpl implements EditorMark } @Override - protected void paintComponent(Graphics g) { + protected void paintComponent(@NotNull Graphics g) { if (myVisualLine ==-1) return; Dimension size = getPreferredSize(); EditorGutterComponentEx gutterComponentEx = myEditor.getGutterComponentEx(); diff --git a/platform/platform-impl/src/com/intellij/openapi/wm/impl/commands/RequestFocusInEditorComponentCmd.java b/platform/platform-impl/src/com/intellij/openapi/wm/impl/commands/RequestFocusInEditorComponentCmd.java index c21b44f18825..2c681856a328 100644 --- a/platform/platform-impl/src/com/intellij/openapi/wm/impl/commands/RequestFocusInEditorComponentCmd.java +++ b/platform/platform-impl/src/com/intellij/openapi/wm/impl/commands/RequestFocusInEditorComponentCmd.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2012 JetBrains s.r.o. + * Copyright 2000-2014 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -102,7 +102,8 @@ public final class RequestFocusInEditorComponentCmd extends FinalizableCommand{ } if(myComponent != null){ - myFocusManager.requestFocus(myComponent, myForced).notifyWhenDone(myDoneCallback).doWhenDone(new Runnable() { + final boolean forced = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner() == null; + myFocusManager.requestFocus(myComponent, myForced || forced).notifyWhenDone(myDoneCallback).doWhenDone(new Runnable() { public void run() { // if owner is active window or it has active child window which isn't floating decorator then // don't bring owner window to font. If we will make toFront every time then it's possible diff --git a/platform/platform-impl/src/com/intellij/ui/AbstractExpandableItemsHandler.java b/platform/platform-impl/src/com/intellij/ui/AbstractExpandableItemsHandler.java index 4e65063e7939..d8b204cfd76c 100644 --- a/platform/platform-impl/src/com/intellij/ui/AbstractExpandableItemsHandler.java +++ b/platform/platform-impl/src/com/intellij/ui/AbstractExpandableItemsHandler.java @@ -223,6 +223,7 @@ public abstract class AbstractExpandableItemsHandler + + + @@ -530,8 +533,20 @@ + + + + + + + + + + + + diff --git a/platform/platform-resources/src/idea/Keymap_Default.xml b/platform/platform-resources/src/idea/Keymap_Default.xml index 58d6f3a76d27..3508b1d0112e 100644 --- a/platform/platform-resources/src/idea/Keymap_Default.xml +++ b/platform/platform-resources/src/idea/Keymap_Default.xml @@ -506,6 +506,9 @@ + + + diff --git a/platform/platform-resources/src/idea/VcsActions.xml b/platform/platform-resources/src/idea/VcsActions.xml index dc15f19282bf..5496ab6bf3e7 100644 --- a/platform/platform-resources/src/idea/VcsActions.xml +++ b/platform/platform-resources/src/idea/VcsActions.xml @@ -265,7 +265,7 @@ - + urls) { return false; } + + public boolean addCustomModuleRoots(@NotNull OrderRootType type, + @NotNull ModuleRootModel rootModel, + @NotNull Collection result, + boolean includeProduction, + boolean includeTests) { + return false; + } } diff --git a/platform/projectModel-impl/src/com/intellij/openapi/roots/impl/OrderEnumeratorBase.java b/platform/projectModel-impl/src/com/intellij/openapi/roots/impl/OrderEnumeratorBase.java index 61a6aee9315b..b13df0560292 100644 --- a/platform/projectModel-impl/src/com/intellij/openapi/roots/impl/OrderEnumeratorBase.java +++ b/platform/projectModel-impl/src/com/intellij/openapi/roots/impl/OrderEnumeratorBase.java @@ -356,6 +356,38 @@ abstract class OrderEnumeratorBase extends OrderEnumerator implements OrderEnume return false; } + boolean addCustomRootsForModule(OrderRootType type, + ModuleRootModel rootModel, + Collection result, + boolean includeProduction, + boolean includeTests) { + for (OrderEnumerationHandler handler : myCustomHandlers) { + final List urls = new ArrayList(); + final boolean added = handler.addCustomModuleRoots(type, rootModel, urls, includeProduction, includeTests); + for (String url : urls) { + ContainerUtil.addIfNotNull(VirtualFileManager.getInstance().findFileByUrl(url), result); + } + + if (added) return true; + } + return false; + } + + boolean addCustomRootUrlsForModule(OrderRootType type, + ModuleRootModel rootModel, + Collection result, + boolean includeProduction, + boolean includeTests) { + for (OrderEnumerationHandler handler : myCustomHandlers) { + final List urls = new ArrayList(); + final boolean added = handler.addCustomModuleRoots(type, rootModel, urls, includeProduction, includeTests); + result.addAll(urls); + + if (added) return true; + } + return false; + } + @Override public boolean isRuntimeOnly() { return myRuntimeOnly; diff --git a/platform/projectModel-impl/src/com/intellij/openapi/roots/impl/OrderRootsEnumeratorImpl.java b/platform/projectModel-impl/src/com/intellij/openapi/roots/impl/OrderRootsEnumeratorImpl.java index ea5742cce176..fb9a75176255 100644 --- a/platform/projectModel-impl/src/com/intellij/openapi/roots/impl/OrderRootsEnumeratorImpl.java +++ b/platform/projectModel-impl/src/com/intellij/openapi/roots/impl/OrderRootsEnumeratorImpl.java @@ -240,6 +240,7 @@ public class OrderRootsEnumeratorImpl implements OrderRootsEnumerator { } } } + myOrderEnumerator.addCustomRootsForModule(type, rootModel, result, includeProduction, includeTests); } private void collectModuleRootsUrls(OrderRootType type, diff --git a/platform/usageView/src/com/intellij/usages/ChunkExtractor.java b/platform/usageView/src/com/intellij/usages/ChunkExtractor.java index 71a1f50f1cd5..eea15c93605d 100644 --- a/platform/usageView/src/com/intellij/usages/ChunkExtractor.java +++ b/platform/usageView/src/com/intellij/usages/ChunkExtractor.java @@ -37,6 +37,7 @@ import com.intellij.psi.tree.IElementType; import com.intellij.reference.SoftReference; import com.intellij.usageView.UsageTreeColors; import com.intellij.usageView.UsageTreeColorsScheme; +import com.intellij.usages.impl.SyntaxHighlighterOverEditorHighlighter; import com.intellij.usages.impl.rules.UsageType; import com.intellij.util.Processor; import com.intellij.util.containers.FactoryMap; @@ -64,9 +65,7 @@ public class ChunkExtractor { private final Document myDocument; private long myDocumentStamp; - private final SyntaxHighlighter myHighlighter; - - private final Lexer myLexer; + private final SyntaxHighlighterOverEditorHighlighter myHighlighter; private abstract static class WeakFactory { private WeakReference myRef; @@ -119,11 +118,10 @@ public class ChunkExtractor { myDocument = PsiDocumentManager.getInstance(project).getDocument(file); LOG.assertTrue(myDocument != null); final FileType fileType = file.getFileType(); - final SyntaxHighlighter highlighter = SyntaxHighlighterFactory.getSyntaxHighlighter(fileType, project, file.getVirtualFile()); - myHighlighter = highlighter == null ? new PlainSyntaxHighlighter() : highlighter; - myLexer = myHighlighter.getHighlightingLexer(); - myLexer.start(myDocument.getCharsSequence()); - myDocumentStamp = myDocument.getModificationStamp(); + SyntaxHighlighter highlighter = SyntaxHighlighterFactory.getSyntaxHighlighter(fileType, project, file.getVirtualFile()); + highlighter = highlighter == null ? new PlainSyntaxHighlighter() : highlighter; + myHighlighter = new SyntaxHighlighterOverEditorHighlighter(highlighter, file.getVirtualFile(), project); + myDocumentStamp = -1; } public static int getStartOffset(final List rangeMarkers) { @@ -194,18 +192,21 @@ public class ChunkExtractor { int end, boolean selectUsageWithBold, @NotNull List result) { - final Lexer lexer = myLexer; - final SyntaxHighlighter highlighter = myHighlighter; + final Lexer lexer = myHighlighter.getHighlightingLexer(); + final SyntaxHighlighterOverEditorHighlighter highlighter = myHighlighter; LOG.assertTrue(start <= end); int i = StringUtil.indexOf(chars, '\n', start, end); if (i != -1) end = i; - if (lexer.getTokenStart() > start || myDocumentStamp != myDocument.getModificationStamp()) { - lexer.start(chars); + if (myDocumentStamp != myDocument.getModificationStamp()) { + highlighter.restart(chars); myDocumentStamp = myDocument.getModificationStamp(); + } else if(lexer.getTokenStart() > start) { + highlighter.resetPosition(0); } + boolean isBeginning = true; for(;lexer.getTokenType() != null; lexer.advance()) { @@ -218,8 +219,10 @@ public class ChunkExtractor { hiEnd = Math.min(hiEnd, end); if (hiStart >= hiEnd) { continue; } - String text = chars.subSequence(hiStart, hiEnd).toString(); - if (isBeginning && text.trim().isEmpty()) continue; + if (isBeginning) { + String text = chars.subSequence(hiStart, hiEnd).toString(); + if(text.trim().isEmpty()) continue; + } isBeginning = false; IElementType tokenType = lexer.getTokenType(); TextAttributesKey[] tokenHighlights = highlighter.getTokenHighlights(tokenType); diff --git a/platform/usageView/src/com/intellij/usages/impl/SyntaxHighlighterOverEditorHighlighter.java b/platform/usageView/src/com/intellij/usages/impl/SyntaxHighlighterOverEditorHighlighter.java new file mode 100644 index 000000000000..04690cc6fa20 --- /dev/null +++ b/platform/usageView/src/com/intellij/usages/impl/SyntaxHighlighterOverEditorHighlighter.java @@ -0,0 +1,108 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.intellij.usages.impl; + +import com.intellij.lexer.LayeredLexer; +import com.intellij.lexer.Lexer; +import com.intellij.openapi.editor.colors.TextAttributesKey; +import com.intellij.openapi.editor.ex.util.LayeredHighlighterIterator; +import com.intellij.openapi.editor.ex.util.LayeredLexerEditorHighlighter; +import com.intellij.openapi.editor.highlighter.EditorHighlighter; +import com.intellij.openapi.editor.highlighter.EditorHighlighterFactory; +import com.intellij.openapi.editor.highlighter.HighlighterIterator; +import com.intellij.openapi.fileTypes.PlainSyntaxHighlighter; +import com.intellij.openapi.fileTypes.PlainTextFileType; +import com.intellij.openapi.fileTypes.SyntaxHighlighter; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.psi.impl.search.LexerEditorHighlighterLexer; +import com.intellij.psi.tree.IElementType; +import org.jetbrains.annotations.NotNull; + +/** +* Created by Maxim.Mossienko on 7/31/2014. +*/ +public class SyntaxHighlighterOverEditorHighlighter implements SyntaxHighlighter { + private final Lexer lexer; + private LayeredHighlighterIterator layeredHighlighterIterator = null; + private final SyntaxHighlighter highlighter; + + public SyntaxHighlighterOverEditorHighlighter(SyntaxHighlighter _highlighter, VirtualFile file, Project project) { + if (file.getFileType() == PlainTextFileType.INSTANCE) { // optimization for large files, PlainTextSyntaxHighlighterFactory is slow + highlighter = new PlainSyntaxHighlighter(); + lexer = highlighter.getHighlightingLexer(); + } else { + highlighter = _highlighter; + LayeredLexer.ourDisableLayersFlag.set(Boolean.TRUE); + EditorHighlighter editorHighlighter = EditorHighlighterFactory.getInstance().createEditorHighlighter(project, file); + + try { + if (editorHighlighter instanceof LayeredLexerEditorHighlighter) { + lexer = new LexerEditorHighlighterLexer(editorHighlighter, false); + } + else { + lexer = highlighter.getHighlightingLexer(); + } + } + finally { + LayeredLexer.ourDisableLayersFlag.set(null); + } + } + } + + @NotNull + @Override + public Lexer getHighlightingLexer() { + return lexer; + } + + @NotNull + @Override + public TextAttributesKey[] getTokenHighlights(IElementType tokenType) { + final SyntaxHighlighter activeSyntaxHighlighter = + layeredHighlighterIterator != null ? layeredHighlighterIterator.getActiveSyntaxHighlighter() : highlighter; + return activeSyntaxHighlighter.getTokenHighlights(tokenType); + } + + public void restart(@NotNull CharSequence text) { + lexer.start(text); + + if (lexer instanceof LexerEditorHighlighterLexer) { + HighlighterIterator iterator = ((LexerEditorHighlighterLexer)lexer).getHighlighterIterator(); + if (iterator instanceof LayeredHighlighterIterator) { + layeredHighlighterIterator = (LayeredHighlighterIterator)iterator; + } else { + layeredHighlighterIterator = null; + } + } + } + + public void resetPosition(int startOffset) { + if (lexer instanceof LexerEditorHighlighterLexer) { + ((LexerEditorHighlighterLexer)lexer).resetPosition(startOffset); + + HighlighterIterator iterator = ((LexerEditorHighlighterLexer)lexer).getHighlighterIterator(); + if (iterator instanceof LayeredHighlighterIterator) { + layeredHighlighterIterator = (LayeredHighlighterIterator)iterator; + } else { + layeredHighlighterIterator = null; + } + } else { + CharSequence text = lexer.getBufferSequence(); + lexer.start(text, startOffset, text.length()); + } + } +} diff --git a/platform/usageView/usageView.iml b/platform/usageView/usageView.iml index e3943e442779..d69cf73e8a0c 100644 --- a/platform/usageView/usageView.iml +++ b/platform/usageView/usageView.iml @@ -9,6 +9,7 @@ + diff --git a/platform/util/resources/misc/registry.properties b/platform/util/resources/misc/registry.properties index 96dd1db9c255..99ff576c6ad6 100644 --- a/platform/util/resources/misc/registry.properties +++ b/platform/util/resources/misc/registry.properties @@ -316,6 +316,7 @@ diff.patience.alg=false svn.use.terminal=false svn.use.incoming.optimization=false +svn.executable.locale=C.UTF-8 completion.enable.relevant.method.chain.suggestions=false ide.mac.message.sheets.java.emulation=false @@ -398,4 +399,4 @@ fix.jdk7.alt.shortcuts=true fix.jdk7.alt.shortcuts.description=Allow to use alt for shortcuts on MacOSX with jdk7+ ide.new.markup.markers=true -ide.new.markup.markers.description=New error stripe markers \ No newline at end of file +ide.new.markup.markers.description=New error stripe markers diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/ex/RollbackLineStatusAction.java b/platform/vcs-impl/src/com/intellij/openapi/vcs/ex/RollbackLineStatusAction.java index 59697f1222dd..6e08d90a3b5d 100644 --- a/platform/vcs-impl/src/com/intellij/openapi/vcs/ex/RollbackLineStatusAction.java +++ b/platform/vcs-impl/src/com/intellij/openapi/vcs/ex/RollbackLineStatusAction.java @@ -13,6 +13,7 @@ package com.intellij.openapi.vcs.ex; import com.intellij.icons.AllIcons; +import com.intellij.idea.ActionsBundle; import com.intellij.openapi.actionSystem.AnActionEvent; import com.intellij.openapi.actionSystem.CommonDataKeys; import com.intellij.openapi.application.ApplicationManager; @@ -32,7 +33,9 @@ import java.util.List; public class RollbackLineStatusAction extends DumbAwareAction { public RollbackLineStatusAction() { - super("Rollback", "Rollback selected changes", AllIcons.Actions.Reset); + super(ActionsBundle.actionText("Vcs.RollbackChangedLines"), + ActionsBundle.actionDescription("Vcs.RollbackChangedLines"), + AllIcons.Actions.Reset); } @Override diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/history/BaseDiffFromHistoryHandler.java b/platform/vcs-impl/src/com/intellij/openapi/vcs/history/BaseDiffFromHistoryHandler.java new file mode 100644 index 000000000000..8a16ba6a481f --- /dev/null +++ b/platform/vcs-impl/src/com/intellij/openapi/vcs/history/BaseDiffFromHistoryHandler.java @@ -0,0 +1,197 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.intellij.openapi.vcs.history; + +import com.intellij.openapi.actionSystem.AnActionEvent; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.progress.ProgressIndicator; +import com.intellij.openapi.progress.Task; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.DialogBuilder; +import com.intellij.openapi.ui.MessageType; +import com.intellij.openapi.util.Couple; +import com.intellij.openapi.vcs.FilePath; +import com.intellij.openapi.vcs.VcsException; +import com.intellij.openapi.vcs.changes.Change; +import com.intellij.openapi.vcs.changes.ui.ChangesBrowser; +import com.intellij.openapi.vcs.ui.VcsBalloonProblemNotifier; +import com.intellij.util.containers.ContainerUtil; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.List; + +/** + * @author Konstantin Kolosovsky. + */ +public abstract class BaseDiffFromHistoryHandler implements DiffFromHistoryHandler { + + private static final Logger LOG = Logger.getInstance(BaseDiffFromHistoryHandler.class); + + @NotNull protected final Project myProject; + + protected BaseDiffFromHistoryHandler(@NotNull Project project) { + myProject = project; + } + + @Override + public void showDiffForOne(@NotNull AnActionEvent e, + @NotNull FilePath filePath, + @NotNull VcsFileRevision previousRevision, + @NotNull VcsFileRevision revision) { + doShowDiff(filePath, previousRevision, revision, false); + } + + @Override + public void showDiffForTwo(@NotNull FilePath filePath, @NotNull VcsFileRevision revision1, @NotNull VcsFileRevision revision2) { + doShowDiff(filePath, revision1, revision2, true); + } + + @SuppressWarnings("unchecked") + protected void doShowDiff(@NotNull FilePath filePath, + @NotNull VcsFileRevision revision1, + @NotNull VcsFileRevision revision2, + boolean autoSort) { + if (!filePath.isDirectory()) { + VcsHistoryUtil.showDifferencesInBackground(myProject, filePath, revision1, revision2, autoSort); + } + else if (revision1.equals(VcsFileRevision.NULL)) { + T right = (T)revision2; + showAffectedChanges(filePath, right); + } + else if (revision2 instanceof CurrentRevision) { + T left = (T)revision1; + showChangesBetweenRevisions(filePath, left, null); + } + else { + T left = (T)revision1; + T right = (T)revision2; + if (autoSort) { + Couple pair = VcsHistoryUtil.sortRevisions(revision1, revision2); + left = (T)pair.first; + right = (T)pair.second; + } + showChangesBetweenRevisions(filePath, left, right); + } + } + + protected void showChangesBetweenRevisions(@NotNull final FilePath path, @NotNull final T rev1, @Nullable final T rev2) { + new CollectChangesTask("Comparing revisions...") { + + @NotNull + @Override + public List getChanges() throws VcsException { + return getChangesBetweenRevisions(path, rev1, rev2); + } + + @NotNull + @Override + public String getDialogTitle() { + return getChangesBetweenRevisionsDialogTitle(path, rev1, rev2); + } + }.queue(); + } + + protected void showAffectedChanges(@NotNull final FilePath path, @NotNull final T rev) { + new CollectChangesTask("Collecting affected changes...") { + + @NotNull + @Override + public List getChanges() throws VcsException { + return getAffectedChanges(path, rev); + } + + @NotNull + @Override + public String getDialogTitle() { + return getAffectedChangesDialogTitle(path, rev); + } + }.queue(); + } + + // rev2 == null -> compare rev1 with local + // rev2 != null -> compare rev1 with rev2 + @NotNull + protected abstract List getChangesBetweenRevisions(@NotNull final FilePath path, @NotNull final T rev1, @Nullable final T rev2) + throws VcsException; + + @NotNull + protected abstract List getAffectedChanges(@NotNull final FilePath path, @NotNull final T rev) throws VcsException; + + @NotNull + protected abstract String getPresentableName(@NotNull T revision); + + protected void showChangesDialog(@NotNull String title, @NotNull List changes) { + DialogBuilder dialogBuilder = new DialogBuilder(myProject); + + dialogBuilder.setTitle(title); + dialogBuilder.setActionDescriptors(new DialogBuilder.ActionDescriptor[]{new DialogBuilder.CloseDialogAction()}); + final ChangesBrowser changesBrowser = + new ChangesBrowser(myProject, null, changes, null, false, true, null, ChangesBrowser.MyUseCase.COMMITTED_CHANGES, null); + changesBrowser.setChangesToDisplay(changes); + dialogBuilder.setCenterPanel(changesBrowser); + dialogBuilder.showNotModal(); + } + + protected void showError(@NotNull VcsException e, @NotNull String logMessage) { + LOG.info(logMessage, e); + VcsBalloonProblemNotifier.showOverVersionControlView(myProject, e.getMessage(), MessageType.ERROR); + } + + @NotNull + protected String getChangesBetweenRevisionsDialogTitle(@NotNull final FilePath path, @NotNull final T rev1, @Nullable final T rev2) { + String rev1Title = getPresentableName(rev1); + + return rev2 != null + ? String.format("Difference between %s and %s in %s", rev1Title, getPresentableName(rev2), path.getName()) + : String.format("Difference between %s and local version in %s", rev1Title, path.getName()); + } + + @NotNull + protected String getAffectedChangesDialogTitle(@NotNull final FilePath path, @NotNull final T rev) { + return String.format("Initial commit %s in %s", getPresentableName(rev), path.getName()); + } + + protected abstract class CollectChangesTask extends Task.Backgroundable { + + private List myChanges; + + public CollectChangesTask(@NotNull String title) { + super(BaseDiffFromHistoryHandler.this.myProject, title); + } + + @Override + public void run(@NotNull ProgressIndicator indicator) { + try { + myChanges = getChanges(); + } + catch (VcsException e) { + showError(e, "Error during task: " + getDialogTitle()); + } + } + + @NotNull + public abstract List getChanges() throws VcsException; + + @NotNull + public abstract String getDialogTitle(); + + @Override + public void onSuccess() { + showChangesDialog(getDialogTitle(), ContainerUtil.notNullize(myChanges)); + } + } +} diff --git a/platform/xdebugger-impl/src/com/intellij/xdebugger/impl/settings/MergedCompositeConfigurable.java b/platform/xdebugger-impl/src/com/intellij/xdebugger/impl/settings/MergedCompositeConfigurable.java index 459dda57d259..ccfc2708cb9e 100644 --- a/platform/xdebugger-impl/src/com/intellij/xdebugger/impl/settings/MergedCompositeConfigurable.java +++ b/platform/xdebugger-impl/src/com/intellij/xdebugger/impl/settings/MergedCompositeConfigurable.java @@ -58,6 +58,15 @@ class MergedCompositeConfigurable implements SearchableConfigurable { return children.length == 1 ? children[0].getHelpTopic() : null; } + /** + * false by default. + * + * If Ruby general settings will be without titled border in RubyMine, user could think that all other debugger categories also about Ruby. + */ + protected boolean isUseTargetedProductPolicyIfSeveralChildren() { + return false; + } + @Nullable @Override public JComponent createComponent() { @@ -82,7 +91,7 @@ class MergedCompositeConfigurable implements SearchableConfigurable { } else { boolean addBorder = true; - if (isFirstNamed) { + if (isUseTargetedProductPolicyIfSeveralChildren() && isFirstNamed) { isFirstNamed = false; if (isTargetedToProduct(configurable)) { addBorder = false; diff --git a/platform/xdebugger-impl/src/com/intellij/xdebugger/impl/ui/tree/XDebuggerTree.java b/platform/xdebugger-impl/src/com/intellij/xdebugger/impl/ui/tree/XDebuggerTree.java index d097b09ac3ac..fe32920a7ac6 100644 --- a/platform/xdebugger-impl/src/com/intellij/xdebugger/impl/ui/tree/XDebuggerTree.java +++ b/platform/xdebugger-impl/src/com/intellij/xdebugger/impl/ui/tree/XDebuggerTree.java @@ -35,10 +35,7 @@ import com.intellij.xdebugger.evaluation.XDebuggerEditorsProvider; import com.intellij.xdebugger.frame.XDebuggerTreeNodeHyperlink; import com.intellij.xdebugger.impl.actions.XDebuggerActions; import com.intellij.xdebugger.impl.frame.XValueMarkers; -import com.intellij.xdebugger.impl.ui.tree.nodes.MessageTreeNode; -import com.intellij.xdebugger.impl.ui.tree.nodes.RestorableStateNode; -import com.intellij.xdebugger.impl.ui.tree.nodes.XDebuggerTreeNode; -import com.intellij.xdebugger.impl.ui.tree.nodes.XValueContainerNode; +import com.intellij.xdebugger.impl.ui.tree.nodes.*; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -254,6 +251,12 @@ public class XDebuggerTree extends DnDAwareTree implements DataProvider, Disposa if (XDEBUGGER_TREE_KEY.is(dataId)) { return this; } + if (PlatformDataKeys.PREDEFINED_TEXT.is(dataId)) { + XValueNodeImpl[] selectedNodes = getSelectedNodes(XValueNodeImpl.class, null); + if (selectedNodes.length == 1 && selectedNodes[0].getFullValueEvaluator() == null) { + return selectedNodes[0].getRawValue(); + } + } return null; } diff --git a/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/InspectionGadgetsBundle.properties b/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/InspectionGadgetsBundle.properties index e08d3dae9744..715bf898c50a 100644 --- a/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/InspectionGadgetsBundle.properties +++ b/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/InspectionGadgetsBundle.properties @@ -1841,8 +1841,9 @@ double.literal.may.be.float.literal.problem.descriptor=#ref could b double.literal.may.be.float.literal.quickfix=Replace with ''{0}'' multiple.declaration.option=Ignore 'for' loop declarations simplifiable.annotation.display.name=Simplifiable annotation -simplifiable.annotation.problem.descriptor=Annotation #ref can be simplified #loc -simplifiable.annotation.quickfix=Replace with ''{0}'' +simplifiable.annotation.problem.descriptor=Unnecessary #ref in annotation #loc +simplifiable.annotation.whitespace.problem.descriptor=Unnecessary whitespace in annotation #loc +simplifiable.annotation.quickfix=Simplify annotation overloaded.methods.with.same.number.parameters.option=Ignore overloaded methods whose parameter types are definitely incompatible string.concatenation.in.format.call.display.name=String concatenation as argument to 'format()' call string.concatenation.in.format.call.problem.descriptor=#ref() call has a String concatenation argument #loc diff --git a/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/BaseInspectionVisitor.java b/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/BaseInspectionVisitor.java index 1ba34a6235d8..d792a1ae1101 100644 --- a/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/BaseInspectionVisitor.java +++ b/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/BaseInspectionVisitor.java @@ -1,5 +1,5 @@ /* - * Copyright 2003-2013 Dave Griffith, Bas Leijdekkers + * Copyright 2003-2014 Dave Griffith, Bas Leijdekkers * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -191,8 +191,13 @@ public abstract class BaseInspectionVisitor extends JavaElementVisitor { holder.registerProblem(location, description, highlightType, fixes); } - protected final void registerErrorAtOffset(@NotNull PsiElement location, - int offset, int length, Object... infos) { + protected final void registerErrorAtOffset(@NotNull PsiElement location, int offset, int length, Object... infos) { + registerErrorAtOffset(location, offset, length, ProblemHighlightType.GENERIC_ERROR_OR_WARNING, infos); + } + + protected final void registerErrorAtOffset(@NotNull PsiElement location, int offset, int length, + ProblemHighlightType highlightType, + Object... infos) { if (location.getTextLength() == 0 || length == 0) { return; } @@ -202,7 +207,7 @@ public abstract class BaseInspectionVisitor extends JavaElementVisitor { } final String description = inspection.buildErrorString(infos); final TextRange range = new TextRange(offset, offset + length); - holder.registerProblem(location, range, description, fixes); + holder.registerProblem(location, description, highlightType, range, fixes); } @NotNull diff --git a/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/style/SimplifiableAnnotationInspection.java b/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/style/SimplifiableAnnotationInspection.java index ed2aec66b42c..e45f06cac284 100644 --- a/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/style/SimplifiableAnnotationInspection.java +++ b/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/style/SimplifiableAnnotationInspection.java @@ -16,9 +16,10 @@ package com.siyeh.ig.style; import com.intellij.codeInspection.ProblemDescriptor; +import com.intellij.codeInspection.ProblemHighlightType; import com.intellij.openapi.project.Project; -import com.intellij.openapi.util.text.StringUtil; import com.intellij.psi.*; +import com.intellij.psi.util.PsiTreeUtil; import com.intellij.psi.util.PsiUtilCore; import com.intellij.psi.util.TypeConversionUtil; import com.intellij.util.IncorrectOperationException; @@ -46,30 +47,30 @@ public class SimplifiableAnnotationInspection extends BaseInspection { @NotNull @Override protected String buildErrorString(Object... infos) { - return InspectionGadgetsBundle.message("simplifiable.annotation.problem.descriptor"); + if (((Boolean)infos[0]).booleanValue()) { + return InspectionGadgetsBundle.message("simplifiable.annotation.whitespace.problem.descriptor"); + } + else { + return InspectionGadgetsBundle.message("simplifiable.annotation.problem.descriptor"); + } } @Override protected InspectionGadgetsFix buildFix(Object... infos) { - final PsiAnnotation annotation = (PsiAnnotation)infos[0]; - return new SimplifiableAnnotationFix(annotation); + return new SimplifiableAnnotationFix(); } private static class SimplifiableAnnotationFix extends InspectionGadgetsFix { - private final String replacement; - - public SimplifiableAnnotationFix(PsiAnnotation annotation) { - this.replacement = buildAnnotationText(annotation, new StringBuilder()).toString(); - } + public SimplifiableAnnotationFix() {} @Override @NotNull public String getName() { - return InspectionGadgetsBundle.message("simplifiable.annotation.quickfix", - StringUtil.shortenTextWithEllipsis(replacement, 50, 0, true)); + return InspectionGadgetsBundle.message("simplifiable.annotation.quickfix"); } - @Override + + @Override @NotNull public String getFamilyName() { return getName(); @@ -78,25 +79,25 @@ public class SimplifiableAnnotationInspection extends BaseInspection { @Override protected void doFix(Project project, ProblemDescriptor descriptor) throws IncorrectOperationException { final PsiElement element = descriptor.getPsiElement(); - if (!(element instanceof PsiAnnotation)) { + final PsiAnnotation annotation = PsiTreeUtil.getParentOfType(element, PsiAnnotation.class); + if (annotation == null) { return; } - final PsiAnnotation annotation = (PsiAnnotation)element; final PsiElementFactory factory = JavaPsiFacade.getElementFactory(project); - final String annotationText = buildAnnotationText(annotation, new StringBuilder()).toString(); + final String annotationText = buildAnnotationText(annotation); final PsiAnnotation newAnnotation = factory.createAnnotationFromText(annotationText, element); annotation.replace(newAnnotation); } - private static StringBuilder buildAnnotationText(PsiAnnotation annotation, StringBuilder out) { - out.append('@'); + private static String buildAnnotationText(PsiAnnotation annotation) { + final StringBuilder out = new StringBuilder("@"); final PsiJavaCodeReferenceElement nameReferenceElement = annotation.getNameReferenceElement(); assert nameReferenceElement != null; out.append(nameReferenceElement.getText()); final PsiAnnotationParameterList parameterList = annotation.getParameterList(); final PsiNameValuePair[] attributes = parameterList.getAttributes(); if (attributes.length == 0) { - return out; + return out.toString(); } out.append('('); if (attributes.length == 1) { @@ -118,7 +119,7 @@ public class SimplifiableAnnotationInspection extends BaseInspection { } } out.append(')'); - return out; + return out.toString(); } private static StringBuilder buildAttributeValueText(PsiAnnotationMemberValue value, StringBuilder out) { @@ -130,7 +131,7 @@ public class SimplifiableAnnotationInspection extends BaseInspection { } } else if (value instanceof PsiAnnotation) { - return buildAnnotationText((PsiAnnotation)value, out); + return out.append(buildAnnotationText((PsiAnnotation)value)); } return out.append(value.getText()); } @@ -153,36 +154,36 @@ public class SimplifiableAnnotationInspection extends BaseInspection { } final PsiNameValuePair[] attributes = parameterList.getAttributes(); final PsiElement[] annotationChildren = annotation.getChildren(); - if (annotationChildren.length >= 2 && annotationChildren[1] instanceof PsiWhiteSpace) { - if (!containsError(annotation)) { - registerError(annotation, annotation); - } + if (annotationChildren.length >= 2 && annotationChildren[1] instanceof PsiWhiteSpace && !containsError(annotation)) { + registerError(annotationChildren[1], Boolean.TRUE); } - else if (attributes.length == 0) { - final PsiElement[] children = parameterList.getChildren(); - if (children.length <= 0) { - return; - } - if (!containsError(annotation)) { - registerError(annotation, annotation); + if (attributes.length == 0) { + if (parameterList.getChildren().length > 0 && !containsError(annotation)) { + registerError(parameterList, ProblemHighlightType.LIKE_UNUSED_SYMBOL, Boolean.FALSE); } } else if (attributes.length == 1) { final PsiNameValuePair attribute = attributes[0]; - @NonNls final String name = attribute.getName(); + final PsiIdentifier identifier = attribute.getNameIdentifier(); final PsiAnnotationMemberValue attributeValue = attribute.getValue(); - if (!PsiAnnotation.DEFAULT_REFERENCED_METHOD_NAME.equals(name)) { - if (!(attributeValue instanceof PsiArrayInitializerMemberValue)) { - return; - } - final PsiArrayInitializerMemberValue arrayValue = (PsiArrayInitializerMemberValue)attributeValue; - final PsiAnnotationMemberValue[] initializers = arrayValue.getInitializers(); - if (initializers.length != 1) { - return; + if (identifier != null && attributeValue != null) { + @NonNls final String name = attribute.getName(); + if (PsiAnnotation.DEFAULT_REFERENCED_METHOD_NAME.equals(name) && !containsError(annotation)) { + registerErrorAtOffset(attribute, 0, attributeValue.getStartOffsetInParent(), ProblemHighlightType.LIKE_UNUSED_SYMBOL, + Boolean.FALSE); } } + if (!(attributeValue instanceof PsiArrayInitializerMemberValue)) { + return; + } + final PsiArrayInitializerMemberValue arrayValue = (PsiArrayInitializerMemberValue)attributeValue; + final PsiAnnotationMemberValue[] initializers = arrayValue.getInitializers(); + if (initializers.length != 1) { + return; + } if (!containsError(annotation)) { - registerError(annotation, annotation); + registerError(arrayValue.getFirstChild(), ProblemHighlightType.LIKE_UNUSED_SYMBOL, Boolean.FALSE); + registerError(arrayValue.getLastChild(), ProblemHighlightType.LIKE_UNUSED_SYMBOL, Boolean.FALSE); } } else if (attributes.length > 1) { @@ -191,15 +192,15 @@ public class SimplifiableAnnotationInspection extends BaseInspection { if (!(value instanceof PsiArrayInitializerMemberValue)) { continue; } - final PsiArrayInitializerMemberValue arrayInitializerMemberValue = (PsiArrayInitializerMemberValue)value; - final PsiAnnotationMemberValue[] initializers = arrayInitializerMemberValue.getInitializers(); + final PsiArrayInitializerMemberValue arrayValue = (PsiArrayInitializerMemberValue)value; + final PsiAnnotationMemberValue[] initializers = arrayValue.getInitializers(); if (initializers.length != 1) { continue; } if (!containsError(annotation)) { - registerError(annotation, annotation); + registerError(arrayValue.getFirstChild(), ProblemHighlightType.LIKE_UNUSED_SYMBOL, Boolean.FALSE); + registerError(arrayValue.getLastChild(), ProblemHighlightType.LIKE_UNUSED_SYMBOL, Boolean.FALSE); } - return; } } } diff --git a/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/style/UnnecessaryEnumModifierInspection.java b/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/style/UnnecessaryEnumModifierInspection.java index 2aa72fae7bbe..7d8d9558ef53 100644 --- a/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/style/UnnecessaryEnumModifierInspection.java +++ b/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/style/UnnecessaryEnumModifierInspection.java @@ -1,5 +1,5 @@ /* - * Copyright 2003-2012 Dave Griffith, Bas Leijdekkers + * Copyright 2003-2014 Dave Griffith, Bas Leijdekkers * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,6 +17,7 @@ package com.siyeh.ig.style; import com.intellij.codeInspection.CleanupLocalInspectionTool; import com.intellij.codeInspection.ProblemDescriptor; +import com.intellij.codeInspection.ProblemHighlightType; import com.intellij.openapi.project.Project; import com.intellij.psi.*; import com.intellij.util.IncorrectOperationException; @@ -112,7 +113,7 @@ public class UnnecessaryEnumModifierInspection extends BaseInspection implements for (final PsiElement child : children) { final String text = child.getText(); if (PsiModifier.STATIC.equals(text)) { - registerError(child, child, aClass); + registerError(child, ProblemHighlightType.LIKE_UNUSED_SYMBOL, child, aClass); } } } @@ -131,7 +132,7 @@ public class UnnecessaryEnumModifierInspection extends BaseInspection implements for (final PsiElement child : children) { final String text = child.getText(); if (PsiModifier.PRIVATE.equals(text)) { - registerError(child, child, method); + registerError(child, ProblemHighlightType.LIKE_UNUSED_SYMBOL, child, method); } } } diff --git a/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/style/UnnecessaryInterfaceModifierInspection.java b/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/style/UnnecessaryInterfaceModifierInspection.java index 310cc20c30b6..a0718c07fa8f 100644 --- a/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/style/UnnecessaryInterfaceModifierInspection.java +++ b/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/style/UnnecessaryInterfaceModifierInspection.java @@ -1,5 +1,5 @@ /* - * Copyright 2003-2012 Dave Griffith, Bas Leijdekkers + * Copyright 2003-2014 Dave Griffith, Bas Leijdekkers * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,6 +17,7 @@ package com.siyeh.ig.style; import com.intellij.codeInspection.CleanupLocalInspectionTool; import com.intellij.codeInspection.ProblemDescriptor; +import com.intellij.codeInspection.ProblemHighlightType; import com.intellij.openapi.project.Project; import com.intellij.psi.*; import com.intellij.psi.util.PsiUtil; @@ -214,7 +215,7 @@ public class UnnecessaryInterfaceModifierInspection extends BaseInspection imple } for (PsiElement child : children) { if (modifiers.contains(child.getText())) { - registerError(child, redundantModifiers.toString(), list); + registerError(child, ProblemHighlightType.LIKE_UNUSED_SYMBOL, redundantModifiers.toString(), list); } } } diff --git a/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/style/UnnecessarySemicolonInspection.java b/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/style/UnnecessarySemicolonInspection.java index c1a75887c6aa..36f5af9408df 100644 --- a/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/style/UnnecessarySemicolonInspection.java +++ b/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/style/UnnecessarySemicolonInspection.java @@ -1,5 +1,5 @@ /* - * Copyright 2003-2009 Dave Griffith, Bas Leijdekkers + * Copyright 2003-2014 Dave Griffith, Bas Leijdekkers * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,6 +17,7 @@ package com.siyeh.ig.style; import com.intellij.codeInspection.CleanupLocalInspectionTool; import com.intellij.codeInspection.ProblemDescriptor; +import com.intellij.codeInspection.ProblemHighlightType; import com.intellij.openapi.project.Project; import com.intellij.psi.*; import com.intellij.psi.tree.IElementType; @@ -111,7 +112,7 @@ public class UnnecessarySemicolonInspection extends BaseInspection implements Cl private void findTopLevelSemicolons(PsiElement element) { for (PsiElement sibling = element.getFirstChild(); sibling != null; sibling = skipForwardWhiteSpacesAndComments(sibling)) { if (PsiUtil.isJavaToken(sibling, JavaTokenType.SEMICOLON)) { - registerError(sibling); + registerError(sibling, ProblemHighlightType.LIKE_UNUSED_SYMBOL); } } } @@ -149,7 +150,7 @@ public class UnnecessarySemicolonInspection extends BaseInspection implements Cl if (next == null || !next.equals(aClass.getRBrace())) { return; } - registerError(element); + registerError(element, ProblemHighlightType.LIKE_UNUSED_SYMBOL); } private void findUnnecessarySemicolonsAfterEnumConstants( @@ -171,11 +172,11 @@ public class UnnecessarySemicolonInspection extends BaseInspection implements Cl if (!JavaTokenType.COMMA.equals(prevTokenType) && !JavaTokenType.LBRACE.equals( prevTokenType)) { - registerError(child); + registerError(child, ProblemHighlightType.LIKE_UNUSED_SYMBOL); } } else { - registerError(child); + registerError(child, ProblemHighlightType.LIKE_UNUSED_SYMBOL); } } } @@ -207,7 +208,7 @@ public class UnnecessarySemicolonInspection extends BaseInspection implements Cl if (semicolon == null) { return; } - registerError(semicolon); + registerError(semicolon, ProblemHighlightType.LIKE_UNUSED_SYMBOL); } } @@ -218,7 +219,7 @@ public class UnnecessarySemicolonInspection extends BaseInspection implements Cl if (last instanceof PsiJavaToken && ((PsiJavaToken)last).getTokenType() == JavaTokenType.RPARENTH) { final PsiElement prev = skipBackwardWhiteSpacesAndComments(last); if (prev instanceof PsiJavaToken && ((PsiJavaToken)prev).getTokenType() == JavaTokenType.SEMICOLON) { - registerError(prev); + registerError(prev, ProblemHighlightType.LIKE_UNUSED_SYMBOL); } } } diff --git a/plugins/InspectionGadgets/test/com/siyeh/igtest/style/simplifiable_annotation/SimplifiableAnnotation.java b/plugins/InspectionGadgets/test/com/siyeh/igtest/style/simplifiable_annotation/SimplifiableAnnotation.java index b40289507a08..1fe15e0f3eeb 100644 --- a/plugins/InspectionGadgets/test/com/siyeh/igtest/style/simplifiable_annotation/SimplifiableAnnotation.java +++ b/plugins/InspectionGadgets/test/com/siyeh/igtest/style/simplifiable_annotation/SimplifiableAnnotation.java @@ -2,8 +2,8 @@ package com.siyeh.igtest.style.simplifiable_annotation; public class SimplifiableAnnotation { - @ SuppressWarnings(value = "blabla") - @ Deprecated() + @ SuppressWarnings(value = "blabla") + @ Deprecated() Object foo() { return null; } @@ -14,8 +14,8 @@ public class SimplifiableAnnotation { @interface ArrayAnnotation { String[] array(); } -@ValueAnnotation({"the value"}) -@ArrayAnnotation(array = {"first"}) +@ValueAnnotation({"the value"}) +@ArrayAnnotation(array = {"first"}) class MyClass { @ ValueAnnotation @@ -23,7 +23,7 @@ class MyClass { return -1; } - @Two(i={1}, j = 2) + @Two(i={1}, j = 2) String s; } @interface Two { diff --git a/plugins/git4idea/src/META-INF/plugin.xml b/plugins/git4idea/src/META-INF/plugin.xml index d0a467a709c6..dc12d0e03ad0 100644 --- a/plugins/git4idea/src/META-INF/plugin.xml +++ b/plugins/git4idea/src/META-INF/plugin.xml @@ -92,12 +92,13 @@ - + - - + @@ -105,6 +106,8 @@ + + diff --git a/plugins/git4idea/src/git4idea/GitUtil.java b/plugins/git4idea/src/git4idea/GitUtil.java index ed3554eab9b3..4ff61fb4a68b 100644 --- a/plugins/git4idea/src/git4idea/GitUtil.java +++ b/plugins/git4idea/src/git4idea/GitUtil.java @@ -1025,4 +1025,14 @@ public class GitUtil { ApplicationManager.getApplication().getMessageBus().syncPublisher(BatchFileChangeListener.TOPIC).batchChangeCompleted(project); } + @NotNull + public static String cleanupErrorPrefixes(@NotNull String msg) { + final String[] PREFIXES = { "fatal:", "error:" }; + for (String prefix : PREFIXES) { + if (msg.startsWith(prefix)) { + return msg.substring(prefix.length()).trim(); + } + } + return msg; + } } diff --git a/plugins/git4idea/src/git4idea/actions/GitLogSingleCommitAction.java b/plugins/git4idea/src/git4idea/actions/GitLogSingleCommitAction.java index 83512de71d1f..2ddbed7e5478 100644 --- a/plugins/git4idea/src/git4idea/actions/GitLogSingleCommitAction.java +++ b/plugins/git4idea/src/git4idea/actions/GitLogSingleCommitAction.java @@ -15,16 +15,19 @@ */ package git4idea.actions; -import com.intellij.dvcs.ui.VcsLogSingleCommitAction; +import com.intellij.dvcs.ui.VcsLogAction; import com.intellij.openapi.components.ServiceManager; import com.intellij.openapi.project.Project; import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.vcs.log.VcsFullCommitDetails; import git4idea.repo.GitRepository; import git4idea.repo.GitRepositoryManager; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -public abstract class GitLogSingleCommitAction extends VcsLogSingleCommitAction { +import java.util.Map; + +public abstract class GitLogSingleCommitAction extends VcsLogAction { @Override @Nullable @@ -32,4 +35,21 @@ public abstract class GitLogSingleCommitAction extends VcsLogSingleCommitAction< return ServiceManager.getService(project, GitRepositoryManager.class).getRepositoryForRoot(root); } + @NotNull + @Override + protected Mode getMode() { + return Mode.SINGLE_COMMIT; + } + + @Override + protected void actionPerformed(@NotNull Project project, @NotNull Map commits) { + assert commits.size() == 1; + Map.Entry entry = commits.entrySet().iterator().next(); + GitRepository repository = entry.getKey(); + VcsFullCommitDetails commit = entry.getValue(); + actionPerformed(repository, commit); + } + + protected abstract void actionPerformed(@NotNull GitRepository repository, @NotNull VcsFullCommitDetails commit); + } diff --git a/plugins/git4idea/src/git4idea/branch/GitBranchUiHandler.java b/plugins/git4idea/src/git4idea/branch/GitBranchUiHandler.java index 3b694c455c7b..95546f0547e8 100644 --- a/plugins/git4idea/src/git4idea/branch/GitBranchUiHandler.java +++ b/plugins/git4idea/src/git4idea/branch/GitBranchUiHandler.java @@ -22,6 +22,7 @@ import com.intellij.openapi.vfs.VirtualFile; import git4idea.GitCommit; import git4idea.repo.GitRepository; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import java.util.Collection; import java.util.List; @@ -68,14 +69,15 @@ public interface GitBranchUiHandler { * Shows the dialog proposing to execute the operation (checkout or merge) smartly, i.e. stash-execute-unstash. * * @param project - * @param changes local changes that would be overwritten by checkout or merge. - * @param paths paths reported by Git (in most cases this is covered by {@code changes}. - * @param operation operation name: checkout or merge - * @param isForcePossible can the operation be executed force (force checkout is possible, force merge - not). + * @param changes local changes that would be overwritten by checkout or merge. + * @param paths paths reported by Git (in most cases this is covered by {@code changes}. + * @param operation operation name: checkout or merge + * @param forceButtonTitle if the operation can be executed force (force checkout is possible), + * specify the title of the force button; otherwise (force merge is not possible) pass null. * @return the code of the decision. */ int showSmartOperationDialog(@NotNull Project project, @NotNull List changes, @NotNull Collection paths, - @NotNull String operation, boolean isForcePossible); + @NotNull String operation, @Nullable String forceButtonTitle); boolean showBranchIsNotFullyMergedDialog(@NotNull Project project, @NotNull Map> history, @NotNull String unmergedBranch, @NotNull List mergedToBranches, diff --git a/plugins/git4idea/src/git4idea/branch/GitBranchUiHandlerImpl.java b/plugins/git4idea/src/git4idea/branch/GitBranchUiHandlerImpl.java index cea2b55596e0..20e207d14ba1 100644 --- a/plugins/git4idea/src/git4idea/branch/GitBranchUiHandlerImpl.java +++ b/plugins/git4idea/src/git4idea/branch/GitBranchUiHandlerImpl.java @@ -172,7 +172,7 @@ public class GitBranchUiHandlerImpl implements GitBranchUiHandler { @Override public int showSmartOperationDialog(@NotNull Project project, @NotNull List changes, @NotNull Collection paths, - @NotNull String operation, boolean isForcePossible) { + @NotNull String operation, @Nullable String forceButtonTitle) { JComponent fileBrowser; if (!changes.isEmpty()) { fileBrowser = new ChangesBrowserWithRollback(project, changes); @@ -180,7 +180,7 @@ public class GitBranchUiHandlerImpl implements GitBranchUiHandler { else { fileBrowser = new GitSimplePathsBrowser(project, paths); } - return GitSmartOperationDialog.showAndGetAnswer(myProject, fileBrowser, operation, isForcePossible); + return GitSmartOperationDialog.showAndGetAnswer(myProject, fileBrowser, operation, forceButtonTitle); } @Override diff --git a/plugins/git4idea/src/git4idea/branch/GitCheckoutOperation.java b/plugins/git4idea/src/git4idea/branch/GitCheckoutOperation.java index e69f5ecfe8fc..b772434959e8 100644 --- a/plugins/git4idea/src/git4idea/branch/GitCheckoutOperation.java +++ b/plugins/git4idea/src/git4idea/branch/GitCheckoutOperation.java @@ -121,7 +121,8 @@ class GitCheckoutOperation extends GitBranchOperation { List affectedChanges = conflictingRepositoriesAndAffectedChanges.getSecond(); Collection absolutePaths = GitUtil.toAbsolute(repository.getRoot(), localChangesOverwrittenByCheckout.getRelativeFilePaths()); - int smartCheckoutDecision = myUiHandler.showSmartOperationDialog(myProject, affectedChanges, absolutePaths, "checkout", true); + int smartCheckoutDecision = myUiHandler.showSmartOperationDialog(myProject, affectedChanges, absolutePaths, "checkout", + "&Force Checkout"); if (smartCheckoutDecision == GitSmartOperationDialog.SMART_EXIT_CODE) { boolean smartCheckedOutSuccessfully = smartCheckout(allConflictingRepositories, myStartPointReference, myNewBranch, getIndicator()); if (smartCheckedOutSuccessfully) { diff --git a/plugins/git4idea/src/git4idea/branch/GitMergeOperation.java b/plugins/git4idea/src/git4idea/branch/GitMergeOperation.java index bf14a6050cac..1b8f382d1dd8 100644 --- a/plugins/git4idea/src/git4idea/branch/GitMergeOperation.java +++ b/plugins/git4idea/src/git4idea/branch/GitMergeOperation.java @@ -34,6 +34,7 @@ import git4idea.commands.*; import git4idea.merge.GitMergeCommittingConflictResolver; import git4idea.merge.GitMerger; import git4idea.repo.GitRepository; +import git4idea.reset.GitResetMode; import git4idea.util.GitPreservingProcess; import org.jetbrains.annotations.NotNull; @@ -195,7 +196,7 @@ class GitMergeOperation extends GitBranchOperation { List affectedChanges = conflictingRepositoriesAndAffectedChanges.getSecond(); Collection absolutePaths = GitUtil.toAbsolute(repository.getRoot(), localChangesOverwrittenByMerge.getRelativeFilePaths()); - int smartCheckoutDecision = myUiHandler.showSmartOperationDialog(myProject, affectedChanges, absolutePaths, "merge", false); + int smartCheckoutDecision = myUiHandler.showSmartOperationDialog(myProject, affectedChanges, absolutePaths, "merge", null); if (smartCheckoutDecision == GitSmartOperationDialog.SMART_EXIT_CODE) { return doSmartMerge(allConflictingRepositories); } @@ -323,7 +324,7 @@ class GitMergeOperation extends GitBranchOperation { @NotNull private GitCommandResult rollback(@NotNull GitRepository repository) { - return myGit.resetHard(repository, myCurrentRevisionsBeforeMerge.get(repository)); + return myGit.reset(repository, GitResetMode.HARD, myCurrentRevisionsBeforeMerge.get(repository)); } @NotNull diff --git a/plugins/git4idea/src/git4idea/branch/GitSmartOperationDialog.java b/plugins/git4idea/src/git4idea/branch/GitSmartOperationDialog.java index 9c2ef4a8d107..4f5073027624 100644 --- a/plugins/git4idea/src/git4idea/branch/GitSmartOperationDialog.java +++ b/plugins/git4idea/src/git4idea/branch/GitSmartOperationDialog.java @@ -24,6 +24,7 @@ import com.intellij.ui.components.JBLabel; import com.intellij.util.ui.UIUtil; import git4idea.GitPlatformFacade; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import javax.swing.*; import java.awt.event.ActionEvent; @@ -44,18 +45,18 @@ public class GitSmartOperationDialog extends DialogWrapper { @NotNull private final JComponent myFileBrowser; @NotNull private final String myOperationTitle; - private final boolean myShowForceButton; + @Nullable private final String myForceButton; /** * Shows the dialog with the list of local changes preventing merge/checkout and returns the dialog exit code. */ static int showAndGetAnswer(@NotNull final Project project, @NotNull final JComponent fileBrowser, - @NotNull final String operationTitle, final boolean showForceButton) { + @NotNull final String operationTitle, @Nullable final String forceButtonTitle) { final AtomicInteger exitCode = new AtomicInteger(); UIUtil.invokeAndWaitIfNeeded(new Runnable() { @Override public void run() { - GitSmartOperationDialog dialog = new GitSmartOperationDialog(project, fileBrowser, operationTitle, showForceButton); + GitSmartOperationDialog dialog = new GitSmartOperationDialog(project, fileBrowser, operationTitle, forceButtonTitle); ServiceManager.getService(project, GitPlatformFacade.class).showDialog(dialog); exitCode.set(dialog.getExitCode()); } @@ -64,11 +65,11 @@ public class GitSmartOperationDialog extends DialogWrapper { } private GitSmartOperationDialog(@NotNull Project project, @NotNull JComponent fileBrowser, @NotNull String operationTitle, - boolean showForceButton) { + @Nullable String forceButton) { super(project); myFileBrowser = fileBrowser; myOperationTitle = operationTitle; - myShowForceButton = showForceButton; + myForceButton = forceButton; String capitalizedOperation = capitalize(myOperationTitle); setTitle("Git " + capitalizedOperation + " Problem"); @@ -82,8 +83,8 @@ public class GitSmartOperationDialog extends DialogWrapper { @NotNull @Override protected Action[] createLeftSideActions() { - if (myShowForceButton) { - return new Action[] {new ForceCheckoutAction(myOperationTitle) }; + if (myForceButton != null) { + return new Action[]{new ForceCheckoutAction(myForceButton, myOperationTitle)}; } return new Action[0]; } @@ -110,8 +111,8 @@ public class GitSmartOperationDialog extends DialogWrapper { private class ForceCheckoutAction extends AbstractAction { - ForceCheckoutAction(@NotNull String operationTitle) { - super("&Force " + capitalize(operationTitle)); + ForceCheckoutAction(@NotNull String buttonTitle, @NotNull String operationTitle) { + super(buttonTitle); putValue(Action.SHORT_DESCRIPTION, capitalize(operationTitle) + " and overwrite local changes"); } diff --git a/plugins/git4idea/src/git4idea/checkin/GitCheckinEnvironment.java b/plugins/git4idea/src/git4idea/checkin/GitCheckinEnvironment.java index 3bb008a2f95f..448dd25ef7ab 100644 --- a/plugins/git4idea/src/git4idea/checkin/GitCheckinEnvironment.java +++ b/plugins/git4idea/src/git4idea/checkin/GitCheckinEnvironment.java @@ -226,10 +226,7 @@ public class GitCheckinEnvironment implements CheckinEnvironment { @NotNull private static VcsException cleanupExceptionText(VcsException original) { String msg = original.getMessage(); - final String FATAL_PREFIX = "fatal:"; - if (msg.startsWith(FATAL_PREFIX)) { - msg = msg.substring(FATAL_PREFIX.length()); - } + msg = GitUtil.cleanupErrorPrefixes(msg); final String DURING_EXECUTING_SUFFIX = GitSimpleHandler.DURING_EXECUTING_ERROR_MESSAGE; int suffix = msg.indexOf(DURING_EXECUTING_SUFFIX); if (suffix > 0) { diff --git a/plugins/git4idea/src/git4idea/cherrypick/GitCherryPickAction.java b/plugins/git4idea/src/git4idea/cherrypick/GitCherryPickAction.java index aa3e1e440277..b4250a0d142b 100644 --- a/plugins/git4idea/src/git4idea/cherrypick/GitCherryPickAction.java +++ b/plugins/git4idea/src/git4idea/cherrypick/GitCherryPickAction.java @@ -49,6 +49,8 @@ import java.util.*; * @author Kirill Likhodedov */ public class GitCherryPickAction extends DumbAwareAction { + + private static final String NAME = "Cherry-Pick"; private static final Logger LOG = Logger.getInstance(GitCherryPickAction.class); @NotNull private final GitPlatformFacade myPlatformFacade; @@ -56,7 +58,7 @@ public class GitCherryPickAction extends DumbAwareAction { @NotNull private final Set myIdsInProgress; public GitCherryPickAction() { - super("Cherry-pick", "Cherry-pick", Git4ideaIcons.CherryPick); + super(NAME, null, Git4ideaIcons.CherryPick); myGit = ServiceManager.getService(Git.class); myPlatformFacade = ServiceManager.getService(GitPlatformFacade.class); myIdsInProgress = ContainerUtil.newHashSet(); @@ -81,9 +83,8 @@ public class GitCherryPickAction extends DumbAwareAction { new Task.Backgroundable(project, "Cherry-picking", false) { public void run(@NotNull ProgressIndicator indicator) { try { - boolean autoCommit = GitVcsSettings.getInstance(myProject).isAutoCommitOnCherryPick(); Map> commitsInRoots = sortCommits(groupCommitsByRoots(project, commits)); - new GitCherryPicker(myProject, myGit, myPlatformFacade, autoCommit).cherryPick(commitsInRoots); + new GitCherryPicker(project, myGit, myPlatformFacade, isAutoCommit(project)).cherryPick(commitsInRoots); } finally { ApplicationManager.getApplication().invokeLater(new Runnable() { @@ -130,15 +131,21 @@ public class GitCherryPickAction extends DumbAwareAction { return groupedCommits; } + private static boolean isAutoCommit(@NotNull Project project) { + return GitVcsSettings.getInstance(project).isAutoCommitOnCherryPick(); + } + @Override public void update(AnActionEvent e) { super.update(e); - final VcsLog log = getVcsLog(e); - if (log != null && !DvcsUtil.logHasRootForVcs(log, GitVcs.getKey())) { + VcsLog log = getVcsLog(e); + Project project = getEventProject(e); + if (project == null || log == null || !DvcsUtil.logHasRootForVcs(log, GitVcs.getKey())) { e.getPresentation().setEnabledAndVisible(false); } else { e.getPresentation().setEnabled(enabled(e)); + e.getPresentation().setText(isAutoCommit(project) ? NAME : NAME + "..."); } } diff --git a/plugins/git4idea/src/git4idea/commands/Git.java b/plugins/git4idea/src/git4idea/commands/Git.java index 284ccc1e20c5..830f73b4508f 100644 --- a/plugins/git4idea/src/git4idea/commands/Git.java +++ b/plugins/git4idea/src/git4idea/commands/Git.java @@ -22,6 +22,7 @@ import com.intellij.openapi.vfs.VirtualFile; import git4idea.GitCommit; import git4idea.push.GitPushSpec; import git4idea.repo.GitRepository; +import git4idea.reset.GitResetMode; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -93,7 +94,8 @@ public interface Git { GitCommandResult branchCreate(@NotNull GitRepository repository, @NotNull String branchName); @NotNull - GitCommandResult resetHard(@NotNull GitRepository repository, @NotNull String revision); + GitCommandResult reset(@NotNull GitRepository repository, @NotNull GitResetMode mode, @NotNull String target, + @NotNull GitLineHandlerListener... listeners); @NotNull GitCommandResult resetMerge(@NotNull GitRepository repository, @Nullable String revision); diff --git a/plugins/git4idea/src/git4idea/commands/GitCommandResult.java b/plugins/git4idea/src/git4idea/commands/GitCommandResult.java index f89a4890a76b..0683d738a9bf 100644 --- a/plugins/git4idea/src/git4idea/commands/GitCommandResult.java +++ b/plugins/git4idea/src/git4idea/commands/GitCommandResult.java @@ -16,10 +16,14 @@ package git4idea.commands; import com.intellij.openapi.util.text.StringUtil; +import com.intellij.util.Function; +import com.intellij.util.containers.ContainerUtil; +import git4idea.GitUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; import java.util.List; @@ -64,9 +68,10 @@ public class GitCommandResult { @NotNull public String getErrorOutputAsHtmlString() { - return StringUtil.join(myErrorOutput, "
"); + return StringUtil.join(cleanup(myErrorOutput), "
"); } - + + @NotNull public String getErrorOutputAsJoinedString() { return StringUtil.join(myErrorOutput, "\n"); } @@ -90,4 +95,14 @@ public class GitCommandResult { return false; // will be implemented later } + @NotNull + private static Collection cleanup(@NotNull Collection errorOutput) { + return ContainerUtil.map(errorOutput, new Function() { + @Override + public String fun(String errorMessage) { + return GitUtil.cleanupErrorPrefixes(errorMessage); + } + }); + } + } diff --git a/plugins/git4idea/src/git4idea/commands/GitImpl.java b/plugins/git4idea/src/git4idea/commands/GitImpl.java index c873e71ff94b..09e254f5392b 100644 --- a/plugins/git4idea/src/git4idea/commands/GitImpl.java +++ b/plugins/git4idea/src/git4idea/commands/GitImpl.java @@ -32,6 +32,7 @@ import git4idea.history.GitHistoryUtils; import git4idea.push.GitPushSpec; import git4idea.repo.GitRemote; import git4idea.repo.GitRepository; +import git4idea.reset.GitResetMode; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -338,20 +339,26 @@ public class GitImpl implements Git { @Override @NotNull - public GitCommandResult resetHard(@NotNull GitRepository repository, @NotNull String revision) { - final GitLineHandler handler = new GitLineHandler(repository.getProject(), repository.getRoot(), GitCommand.RESET); - handler.addParameters("--hard", revision); - return run(handler); + public GitCommandResult reset(@NotNull GitRepository repository, @NotNull GitResetMode mode, @NotNull String target, + @NotNull GitLineHandlerListener... listeners) { + return reset(repository, mode.getArgument(), target, listeners); } @Override @NotNull public GitCommandResult resetMerge(@NotNull GitRepository repository, @Nullable String revision) { + return reset(repository, "--merge", revision); + } + + @NotNull + private static GitCommandResult reset(@NotNull GitRepository repository, @NotNull String argument, @Nullable String target, + @NotNull GitLineHandlerListener... listeners) { final GitLineHandler handler = new GitLineHandler(repository.getProject(), repository.getRoot(), GitCommand.RESET); - handler.addParameters("--merge"); - if (revision != null) { - handler.addParameters(revision); + handler.addParameters(argument); + if (target != null) { + handler.addParameters(target); } + addListeners(handler, listeners); return run(handler); } diff --git a/plugins/git4idea/src/git4idea/commands/GitLocalChangesWouldBeOverwrittenDetector.java b/plugins/git4idea/src/git4idea/commands/GitLocalChangesWouldBeOverwrittenDetector.java index b62f12806761..e9d8df89614a 100644 --- a/plugins/git4idea/src/git4idea/commands/GitLocalChangesWouldBeOverwrittenDetector.java +++ b/plugins/git4idea/src/git4idea/commands/GitLocalChangesWouldBeOverwrittenDetector.java @@ -40,6 +40,13 @@ public class GitLocalChangesWouldBeOverwrittenDetector extends GitMessageWithFil ".*Your local changes to '(.*)' would be overwritten by merge.*" ); + private static final Pattern[] RESET_PATTERNS = new Pattern[]{Pattern.compile( + ".*Entry '(.*)' not uptodate. Cannot merge.*" + ), + Pattern.compile( + ".*Entry '(.*)' would be overwritten by merge.*" + )}; + // common for checkout and merge public static final Event NEW_PATTERN = new Event( "Your local changes to the following files would be overwritten by", @@ -49,17 +56,18 @@ public class GitLocalChangesWouldBeOverwrittenDetector extends GitMessageWithFil public enum Operation { CHECKOUT(OLD_CHECKOUT_PATTERN), - MERGE(OLD_MERGE_PATTERN); + MERGE(OLD_MERGE_PATTERN), + RESET(RESET_PATTERNS); - @NotNull private final Pattern myPattern; + @NotNull private final Pattern[] myPatterns; - Operation(@NotNull Pattern pattern) { - myPattern = pattern; + Operation(@NotNull Pattern... patterns) { + myPatterns = patterns; } @NotNull - public Pattern getPattern() { - return myPattern; + Pattern[] getPatterns() { + return myPatterns; } } @@ -71,10 +79,13 @@ public class GitLocalChangesWouldBeOverwrittenDetector extends GitMessageWithFil @Override public void onLineAvailable(@NotNull String line, @NotNull Key outputType) { super.onLineAvailable(line, outputType); - Matcher m = myOperation.getPattern().matcher(line); - if (m.matches()) { - myMessageDetected = true; - myAffectedFiles.add(m.group(1)); + for (Pattern pattern : myOperation.getPatterns()) { + Matcher m = pattern.matcher(line); + if (m.matches()) { + myMessageDetected = true; + myAffectedFiles.add(m.group(1)); + break; + } } } } diff --git a/plugins/git4idea/src/git4idea/config/GitVcsSettings.java b/plugins/git4idea/src/git4idea/config/GitVcsSettings.java index 4f74f7ca94f3..1b5860631e59 100644 --- a/plugins/git4idea/src/git4idea/config/GitVcsSettings.java +++ b/plugins/git4idea/src/git4idea/config/GitVcsSettings.java @@ -19,6 +19,7 @@ import com.intellij.lifecycle.PeriodicalTasksCloser; import com.intellij.openapi.components.*; import com.intellij.openapi.project.Project; import com.intellij.util.ArrayUtil; +import git4idea.reset.GitResetMode; import git4idea.ui.branch.GitBranchSyncSetting; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -61,6 +62,7 @@ public class GitVcsSettings implements PersistentStateComponent { private static final Logger LOG = Logger.getInstance(GitDiffFromHistoryHandler.class); - @NotNull private final Project myProject; @NotNull private final Git myGit; @NotNull private final GitRepositoryManager myRepositoryManager; public GitDiffFromHistoryHandler(@NotNull Project project) { - myProject = project; + super(project); myGit = ServiceManager.getService(project, Git.class); myRepositoryManager = GitUtil.getRepositoryManager(project); } @Override - public void showDiffForOne(@NotNull AnActionEvent e, @NotNull FilePath filePath, - @NotNull VcsFileRevision previousRevision, @NotNull VcsFileRevision revision) { + public void showDiffForOne(@NotNull AnActionEvent e, + @NotNull FilePath filePath, + @NotNull VcsFileRevision previousRevision, + @NotNull VcsFileRevision revision) { GitFileRevision rev = (GitFileRevision)revision; Collection parents = rev.getParents(); if (parents.size() < 2) { - doShowDiff(filePath, previousRevision, revision, false); + super.showDiffForOne(e, filePath, previousRevision, revision); } else { // merge showDiffForMergeCommit(e, filePath, rev, parents); } } + @NotNull @Override - public void showDiffForTwo(@NotNull FilePath filePath, @NotNull VcsFileRevision revision1, @NotNull VcsFileRevision revision2) { - doShowDiff(filePath, revision1, revision2, true); - } - - private void doShowDiff(@NotNull FilePath filePath, @NotNull VcsFileRevision revision1, @NotNull VcsFileRevision revision2, - boolean autoSort) { - if (!filePath.isDirectory()) { - VcsHistoryUtil.showDifferencesInBackground(myProject, filePath, revision1, revision2, autoSort); - } - else if (revision2 instanceof CurrentRevision) { - GitFileRevision left = (GitFileRevision)revision1; - showDiffForDirectory(filePath, left.getHash(), null); - } - else if (revision1.equals(VcsFileRevision.NULL)) { - GitFileRevision right = (GitFileRevision)revision2; - showDiffForDirectory(filePath, null, right.getHash()); - } - else { - GitFileRevision left = (GitFileRevision)revision1; - GitFileRevision right = (GitFileRevision)revision2; - if (autoSort) { - Couple pair = VcsHistoryUtil.sortRevisions(revision1, revision2); - left = (GitFileRevision)pair.first; - right = (GitFileRevision)pair.second; - } - showDiffForDirectory(filePath, left.getHash(), right.getHash()); - } - } - - private void showDiffForDirectory(@NotNull final FilePath path, @Nullable final String hash1, @Nullable final String hash2) { + protected List getChangesBetweenRevisions(@NotNull FilePath path, @NotNull GitFileRevision rev1, @Nullable GitFileRevision rev2) + throws VcsException { GitRepository repository = getRepository(path); - calculateDiffInBackground(repository, path, hash1, hash2, new Consumer>() { - @Override - public void consume(List changes) { - showDirDiffDialog(path, hash1, hash2, changes); - } - }); + String hash1 = rev1.getHash(); + String hash2 = rev2 != null ? rev2.getHash() : null; + + return ContainerUtil + .newArrayList(GitChangeUtils.getDiff(repository.getProject(), repository.getRoot(), hash1, hash2, Collections.singletonList(path))); + } + + @NotNull + @Override + protected List getAffectedChanges(@NotNull FilePath path, @NotNull GitFileRevision rev) throws VcsException { + GitRepository repository = getRepository(path); + + return ContainerUtil.newArrayList( + GitChangeUtils.getRevisionChanges(repository.getProject(), repository.getRoot(), rev.getHash(), false, true, true).getChanges()); + } + + @NotNull + @Override + protected String getPresentableName(@NotNull GitFileRevision revision) { + return GitUtil.getShortHash(revision.getHash()); } @NotNull @@ -141,63 +126,6 @@ public class GitDiffFromHistoryHandler implements DiffFromHistoryHandler { return repository; } - // hash1 == null => hash2 is the initial commit - // hash2 == null => comparing hash1 with local - private void calculateDiffInBackground(@NotNull final GitRepository repository, @NotNull final FilePath path, - @Nullable final String hash1, @Nullable final String hash2, - final Consumer> successHandler) { - new Task.Backgroundable(myProject, "Comparing revisions...") { - private List myChanges = Collections.emptyList(); - @Override - public void run(@NotNull ProgressIndicator indicator) { - try { - if (hash1 != null) { - // diff - myChanges = new ArrayList(GitChangeUtils.getDiff(repository.getProject(), repository.getRoot(), hash1, hash2, - Collections.singletonList(path))); - } - else { - // show the initial commit - myChanges = new ArrayList(GitChangeUtils.getRevisionChanges(repository.getProject(), repository.getRoot(), hash2, false, - true, true).getChanges()); - } - } - catch (VcsException e) { - showError(e, "Error during requesting diff for directory"); - } - } - - @Override - public void onSuccess() { - successHandler.consume(myChanges); - } - }.queue(); - } - - private void showDirDiffDialog(@NotNull FilePath path, @Nullable String hash1, @Nullable String hash2, @NotNull List diff) { - DialogBuilder dialogBuilder = new DialogBuilder(myProject); - String title; - if (hash2 != null) { - if (hash1 != null) { - title = String.format("Difference between %s and %s in %s", GitUtil.getShortHash(hash1), GitUtil.getShortHash(hash2), path.getName()); - } - else { - title = String.format("Initial commit %s in %s", GitUtil.getShortHash(hash2), path.getName()); - } - } - else { - LOG.assertTrue(hash1 != null, "hash1 and hash2 can't both be null. Path: " + path); - title = String.format("Difference between %s and local version in %s", GitUtil.getShortHash(hash1), path.getName()); - } - dialogBuilder.setTitle(title); - dialogBuilder.setActionDescriptors(new DialogBuilder.ActionDescriptor[] { new DialogBuilder.CloseDialogAction()}); - final ChangesBrowser changesBrowser = new ChangesBrowser(myProject, null, diff, null, false, true, - null, ChangesBrowser.MyUseCase.COMMITTED_CHANGES, null); - changesBrowser.setChangesToDisplay(diff); - dialogBuilder.setCenterPanel(changesBrowser); - dialogBuilder.showNotModal(); - } - private void showDiffForMergeCommit(@NotNull final AnActionEvent event, @NotNull final FilePath filePath, @NotNull final GitFileRevision rev, @NotNull final Collection parents) { @@ -299,12 +227,6 @@ public class GitDiffFromHistoryHandler implements DiffFromHistoryHandler { return makeRevisionFromHash(currentRevisionPath, parentHash); } - - private void showError(VcsException e, String logMessage) { - LOG.info(logMessage, e); - VcsBalloonProblemNotifier.showOverVersionControlView(this.myProject, e.getMessage(), MessageType.ERROR); - } - private void showPopup(@NotNull AnActionEvent event, @NotNull GitFileRevision rev, @NotNull FilePath filePath, @NotNull Collection parents) { ActionGroup parentActions = createActionGroup(rev, filePath, parents); diff --git a/plugins/git4idea/src/git4idea/merge/GitPullDialog.java b/plugins/git4idea/src/git4idea/merge/GitPullDialog.java index f9d8085cfab8..68b16164b335 100644 --- a/plugins/git4idea/src/git4idea/merge/GitPullDialog.java +++ b/plugins/git4idea/src/git4idea/merge/GitPullDialog.java @@ -44,66 +44,23 @@ import java.util.Collection; import java.util.Collections; import java.util.List; -/** - * Git pull dialog - */ public class GitPullDialog extends DialogWrapper { private static final Logger LOG = Logger.getInstance(GitPullDialog.class); - /** - * root panel - */ private JPanel myPanel; - /** - * The selected git root - */ private JComboBox myGitRoot; - /** - * Current branch label - */ private JLabel myCurrentBranch; - /** - * The merge strategy - */ private JComboBox myStrategy; - /** - * No commit option - */ private JCheckBox myNoCommitCheckBox; - /** - * Squash commit option - */ private JCheckBox mySquashCommitCheckBox; - /** - * No fast forward option - */ private JCheckBox myNoFastForwardCheckBox; - /** - * Add log info to commit option - */ private JCheckBox myAddLogInformationCheckBox; - /** - * Selected remote option - */ private JComboBox myRemote; - /** - * The branch chooser - */ private ElementsChooser myBranchChooser; - /** - * The context project - */ private final Project myProject; private final GitRepositoryManager myRepositoryManager; - /** - * A constructor - * - * @param project a project to select - * @param roots a git repository roots for the project - * @param defaultRoot a guessed default root - */ public GitPullDialog(Project project, List roots, VirtualFile defaultRoot) { super(project, true); setTitle(GitBundle.getString("pull.title")); @@ -138,9 +95,6 @@ public class GitPullDialog extends DialogWrapper { init(); } - /** - * Validate dialog and enable buttons - */ private void validateDialog() { String selectedRemote = getRemote(); if (StringUtil.isEmptyOrSpaces(selectedRemote)) { @@ -150,9 +104,6 @@ public class GitPullDialog extends DialogWrapper { setOKActionEnabled(myBranchChooser.getMarkedElements().size() != 0); } - /** - * @return a pull handler configured according to dialog options - */ public GitLineHandler makeHandler(@NotNull String url) { GitLineHandler h = new GitLineHandler(myProject, gitRoot(), GitCommand.PULL); // ignore merge failure for the pull @@ -232,9 +183,6 @@ public class GitPullDialog extends DialogWrapper { return branch.getName().startsWith(remote + "/"); } - /** - * Update remotes for the git root - */ private void updateRemotes() { GitRepository repository = getRepository(); if (repository == null) { @@ -331,39 +279,23 @@ public class GitPullDialog extends DialogWrapper { }; } - /** - * @return a currently selected git root - */ public VirtualFile gitRoot() { return (VirtualFile)myGitRoot.getSelectedItem(); } - - /** - * Create branch chooser - */ private void createUIComponents() { myBranchChooser = new ElementsChooser(true); } - /** - * {@inheritDoc} - */ protected JComponent createCenterPanel() { return myPanel; } - /** - * {@inheritDoc} - */ @Override protected String getDimensionServiceKey() { return getClass().getName(); } - /** - * {@inheritDoc} - */ @Override protected String getHelpId() { return "reference.VersionControl.Git.Pull"; diff --git a/plugins/git4idea/src/git4idea/reset/GitNewResetDialog.java b/plugins/git4idea/src/git4idea/reset/GitNewResetDialog.java new file mode 100644 index 000000000000..a053248dfdb9 --- /dev/null +++ b/plugins/git4idea/src/git4idea/reset/GitNewResetDialog.java @@ -0,0 +1,142 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package git4idea.reset; + +import com.intellij.openapi.components.ServiceManager; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.DialogWrapper; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.ui.components.JBLabel; +import com.intellij.ui.components.JBRadioButton; +import com.intellij.util.ui.GridBag; +import com.intellij.util.ui.RadioButtonEnumModel; +import com.intellij.util.ui.UIUtil; +import com.intellij.vcs.log.VcsFullCommitDetails; +import com.intellij.xml.util.XmlStringUtil; +import git4idea.GitUtil; +import git4idea.repo.GitRepository; +import git4idea.repo.GitRepositoryManager; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; +import java.awt.*; +import java.util.Map; + +import static com.intellij.dvcs.DvcsUtil.getShortRepositoryName; + +public class GitNewResetDialog extends DialogWrapper { + + private static final String DIALOG_ID = "git.new.reset.dialog"; + + @NotNull private final Project myProject; + @NotNull private final Map myCommits; + @NotNull private final GitResetMode myDefaultMode; + @NotNull private final ButtonGroup myButtonGroup; + + private RadioButtonEnumModel myEnumModel; + + protected GitNewResetDialog(@NotNull Project project, @NotNull Map commits, + @NotNull GitResetMode defaultMode) { + super(project); + myProject = project; + myCommits = commits; + myDefaultMode = defaultMode; + myButtonGroup = new ButtonGroup(); + + init(); + setTitle("Git Reset"); + setOKButtonText("Reset"); + setOKButtonMnemonic('R'); + setResizable(false); + } + + @Nullable + @Override + protected JComponent createCenterPanel() { + JPanel panel = new JPanel(new GridBagLayout()); + GridBag gb = new GridBag(). + setDefaultAnchor(GridBagConstraints.LINE_START). + setDefaultInsets(0, UIUtil.DEFAULT_HGAP, UIUtil.LARGE_VGAP, 0); + + String description = prepareDescription(myProject, myCommits); + panel.add(new JBLabel(XmlStringUtil.wrapInHtml(description)), gb.nextLine().next().coverLine()); + + String explanation = "This will reset the current branch head to the selected commit,
" + + "and update the working tree and the index according to the selected mode:"; + panel.add(new JBLabel(XmlStringUtil.wrapInHtml(explanation), UIUtil.ComponentStyle.SMALL), gb.nextLine().next().coverLine()); + + for (GitResetMode mode : GitResetMode.values()) { + JBRadioButton button = new JBRadioButton(mode.getName()); + button.setMnemonic(mode.getName().charAt(0)); + myButtonGroup.add(button); + panel.add(button, gb.nextLine().next()); + panel.add(new JBLabel(XmlStringUtil.wrapInHtml(mode.getDescription()), UIUtil.ComponentStyle.SMALL), gb.next()); + } + + myEnumModel = RadioButtonEnumModel.bindEnum(GitResetMode.class, myButtonGroup); + myEnumModel.setSelected(myDefaultMode); + return panel; + } + + @Nullable + @Override + protected String getHelpId() { + return DIALOG_ID; + } + + @NotNull + private static String prepareDescription(@NotNull Project project, @NotNull Map commits) { + if (commits.size() == 1 && !isMultiRepo(project)) { + Map.Entry entry = commits.entrySet().iterator().next(); + return "Reset " + getSourceText(entry.getKey()) + " to " + getTargetText(entry.getValue()); + } + + StringBuilder desc = new StringBuilder("Reset "); + for (Map.Entry entry : commits.entrySet()) { + GitRepository repository = entry.getKey(); + VcsFullCommitDetails commit = entry.getValue(); + desc.append(String.format("%s in %s to %s
", getSourceText(repository), + getShortRepositoryName(repository), getTargetText(commit))); + } + return desc.toString(); + } + + @NotNull + private static String getTargetText(@NotNull VcsFullCommitDetails commit) { + String commitMessage = StringUtil.shortenTextWithEllipsis(commit.getSubject(), 20, 0); + return "" + commit.getId().toShortString() + " \"" + commitMessage + "\" by " + commit.getAuthor().getName() + ""; + } + + @NotNull + private static String getSourceText(@NotNull GitRepository repository) { + String currentRevision = repository.getCurrentRevision(); + assert currentRevision != null; + return repository.getCurrentBranch() == null ? + "HEAD (" + GitUtil.getShortHash(currentRevision) + ")" : + repository.getCurrentBranch().getName(); + } + + private static boolean isMultiRepo(@NotNull Project project) { + return ServiceManager.getService(project, GitRepositoryManager.class).moreThanOneRoot(); + } + + @NotNull + public GitResetMode getResetMode() { + return myEnumModel.getSelected(); + } + +} diff --git a/plugins/git4idea/src/git4idea/reset/GitResetAction.java b/plugins/git4idea/src/git4idea/reset/GitResetAction.java new file mode 100644 index 000000000000..5870c2fc4993 --- /dev/null +++ b/plugins/git4idea/src/git4idea/reset/GitResetAction.java @@ -0,0 +1,71 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package git4idea.reset; + +import com.intellij.dvcs.ui.VcsLogAction; +import com.intellij.openapi.components.ServiceManager; +import com.intellij.openapi.progress.ProgressIndicator; +import com.intellij.openapi.progress.Task; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.util.ObjectUtils; +import com.intellij.vcs.log.VcsFullCommitDetails; +import git4idea.config.GitVcsSettings; +import git4idea.repo.GitRepository; +import git4idea.repo.GitRepositoryManager; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Map; + +public class GitResetAction extends VcsLogAction { + + @Nullable + @Override + protected GitRepository getRepositoryForRoot(@NotNull Project project, @NotNull VirtualFile root) { + return getRepoManager(project).getRepositoryForRoot(root); + } + + @NotNull + @Override + protected Mode getMode() { + return Mode.SINGLE_PER_REPO; + } + + @Override + protected void actionPerformed(@NotNull final Project project, @NotNull final Map commits) { + GitVcsSettings settings = GitVcsSettings.getInstance(project); + GitResetMode defaultMode = ObjectUtils.notNull(settings.getResetMode(), GitResetMode.getDefault()); + GitNewResetDialog dialog = new GitNewResetDialog(project, commits, defaultMode); + dialog.show(); + if (dialog.isOK()) { + final GitResetMode selectedMode = dialog.getResetMode(); + settings.setResetMode(selectedMode); + new Task.Backgroundable(project, "Git reset", false) { + @Override + public void run(@NotNull ProgressIndicator indicator) { + new GitResetOperation(project, commits, selectedMode, indicator).execute(); + } + }.queue(); + } + } + + @NotNull + private static GitRepositoryManager getRepoManager(@NotNull Project project) { + return ServiceManager.getService(project, GitRepositoryManager.class); + } + +} diff --git a/plugins/git4idea/src/git4idea/reset/GitResetMode.java b/plugins/git4idea/src/git4idea/reset/GitResetMode.java new file mode 100644 index 000000000000..c10d95707073 --- /dev/null +++ b/plugins/git4idea/src/git4idea/reset/GitResetMode.java @@ -0,0 +1,59 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package git4idea.reset; + +import org.jetbrains.annotations.NotNull; + +public enum GitResetMode { + + SOFT("Soft", "--soft", "Files won't change, differences will be staged for commit."), + MIXED("Mixed", "--mixed", "Files won't change, differences won't be staged."), + HARD("Hard", "--hard", "Files will be reverted to the state of the selected commit.
" + + "Warning: any local changes will be lost."), + KEEP("Keep", "--keep", "Files will be reverted to the state of the selected commit,
" + + "but local changes will be kept intact."); + + @NotNull private final String myName; + @NotNull private final String myArgument; + @NotNull private final String myDescription; + + GitResetMode(@NotNull String name, @NotNull String argument, @NotNull String description) { + myName = name; + myArgument = argument; + myDescription = description; + } + + @NotNull + public static GitResetMode getDefault() { + return MIXED; + } + + @NotNull + public String getName() { + return myName; + } + + @NotNull + public String getArgument() { + return myArgument; + } + + @NotNull + public String getDescription() { + return myDescription; + } + +} diff --git a/plugins/git4idea/src/git4idea/reset/GitResetOperation.java b/plugins/git4idea/src/git4idea/reset/GitResetOperation.java new file mode 100644 index 000000000000..9c66a5225a52 --- /dev/null +++ b/plugins/git4idea/src/git4idea/reset/GitResetOperation.java @@ -0,0 +1,191 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package git4idea.reset; + +import com.intellij.dvcs.repo.RepositoryUtil; +import com.intellij.openapi.components.ServiceManager; +import com.intellij.openapi.fileEditor.FileDocumentManager; +import com.intellij.openapi.progress.ProgressIndicator; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Ref; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.openapi.vcs.VcsNotifier; +import com.intellij.openapi.vcs.changes.Change; +import com.intellij.openapi.vfs.VfsUtil; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.util.Function; +import com.intellij.util.containers.ContainerUtil; +import com.intellij.util.containers.MultiMap; +import com.intellij.util.ui.UIUtil; +import com.intellij.vcs.log.VcsFullCommitDetails; +import git4idea.GitPlatformFacade; +import git4idea.GitUtil; +import git4idea.branch.GitBranchUiHandlerImpl; +import git4idea.branch.GitSmartOperationDialog; +import git4idea.commands.Git; +import git4idea.commands.GitCommandResult; +import git4idea.commands.GitLocalChangesWouldBeOverwrittenDetector; +import git4idea.repo.GitRepository; +import git4idea.util.GitPreservingProcess; +import org.jetbrains.annotations.NotNull; + +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import static git4idea.commands.GitLocalChangesWouldBeOverwrittenDetector.Operation.RESET; + +public class GitResetOperation { + + @NotNull private final Project myProject; + @NotNull private final Map myCommits; + @NotNull private final GitResetMode myMode; + @NotNull private final ProgressIndicator myIndicator; + @NotNull private final Git myGit; + @NotNull private final VcsNotifier myNotifier; + @NotNull private final GitPlatformFacade myFacade; + @NotNull private final GitBranchUiHandlerImpl myUiHandler; + + public GitResetOperation(@NotNull Project project, @NotNull Map targetCommits, + @NotNull GitResetMode mode, @NotNull ProgressIndicator indicator) { + myProject = project; + myCommits = targetCommits; + myMode = mode; + myIndicator = indicator; + myGit = ServiceManager.getService(Git.class); + myNotifier = VcsNotifier.getInstance(project); + myFacade = ServiceManager.getService(GitPlatformFacade.class); + myUiHandler = new GitBranchUiHandlerImpl(myProject, myFacade, myGit, indicator); + } + + public void execute() { + saveAllDocuments(); + GitUtil.workingTreeChangeStarted(myProject); + Map results = ContainerUtil.newHashMap(); + try { + for (Map.Entry entry : myCommits.entrySet()) { + GitRepository repository = entry.getKey(); + VirtualFile root = repository.getRoot(); + String target = entry.getValue().getId().asString(); + GitLocalChangesWouldBeOverwrittenDetector detector = new GitLocalChangesWouldBeOverwrittenDetector(root, RESET); + + GitCommandResult result = myGit.reset(repository, myMode, target, detector); + if (!result.success() && detector.wasMessageDetected()) { + GitCommandResult smartResult = proposeSmartReset(detector, repository, target); + if (smartResult != null) { + result = smartResult; + } + } + results.put(repository, result); + repository.update(); + VfsUtil.markDirtyAndRefresh(true, true, false, root); + } + } + finally { + GitUtil.workingTreeChangeFinished(myProject); + } + notifyResult(results); + } + + private GitCommandResult proposeSmartReset(@NotNull GitLocalChangesWouldBeOverwrittenDetector detector, + @NotNull final GitRepository repository, @NotNull final String target) { + Collection absolutePaths = GitUtil.toAbsolute(repository.getRoot(), detector.getRelativeFilePaths()); + List affectedChanges = GitUtil.findLocalChangesForPaths(myProject, repository.getRoot(), absolutePaths, false); + int choice = myUiHandler.showSmartOperationDialog(myProject, affectedChanges, absolutePaths, "reset", "&Hard Reset"); + if (choice == GitSmartOperationDialog.SMART_EXIT_CODE) { + final Ref result = Ref.create(); + new GitPreservingProcess(myProject, myFacade, myGit, Collections.singleton(repository), "reset", target, myIndicator, new Runnable() { + @Override + public void run() { + result.set(myGit.reset(repository, myMode, target)); + } + }).execute(); + return result.get(); + } + if (choice == GitSmartOperationDialog.FORCE_EXIT_CODE) { + return myGit.reset(repository, GitResetMode.HARD, target); + } + return null; + } + + private void notifyResult(@NotNull Map results) { + Map successes = ContainerUtil.newHashMap(); + Map errors = ContainerUtil.newHashMap(); + for (Map.Entry entry : results.entrySet()) { + GitCommandResult result = entry.getValue(); + GitRepository repository = entry.getKey(); + if (result.success()) { + successes.put(repository, result); + } + else { + errors.put(repository, result); + } + } + + if (errors.isEmpty()) { + myNotifier.notifySuccess("", "Reset successful"); + } + else if (!successes.isEmpty()) { + myNotifier.notifyImportantWarning("Reset partially failed", + "Reset was successful for " + joinRepos(successes.keySet()) + + "
but failed for " + joinRepos(errors.keySet()) + ":
" + formErrorReport(errors)); + } + else { + myNotifier.notifyError("Reset Failed", formErrorReport(errors)); + } + } + + @NotNull + private static String formErrorReport(@NotNull Map errorResults) { + MultiMap grouped = groupByResult(errorResults); + if (grouped.size() == 1) { + return "" + grouped.keySet().iterator().next() + ""; + } + return StringUtil.join(grouped.entrySet(), new Function>, String>() { + @NotNull + @Override + public String fun(@NotNull Map.Entry> entry) { + return joinRepos(entry.getValue()) + ":
" + entry.getKey() + ""; + } + }, "
"); + } + + // to avoid duplicate error reports if they are the same for different repositories + @NotNull + private static MultiMap groupByResult(@NotNull Map results) { + MultiMap grouped = MultiMap.create(); + for (Map.Entry entry : results.entrySet()) { + grouped.putValue(entry.getValue().getErrorOutputAsHtmlString(), entry.getKey()); + } + return grouped; + } + + @NotNull + private static String joinRepos(@NotNull Collection repositories) { + return StringUtil.join(RepositoryUtil.sortRepositories(repositories), ", "); + } + + private static void saveAllDocuments() { + UIUtil.invokeAndWaitIfNeeded(new Runnable() { + @Override + public void run() { + FileDocumentManager.getInstance().saveAllDocuments(); + } + }); + } + +} diff --git a/plugins/git4idea/tests/git4idea/branch/GitBranchWorkerTest.groovy b/plugins/git4idea/tests/git4idea/branch/GitBranchWorkerTest.groovy index d838310c41c5..9a2bad2ad663 100644 --- a/plugins/git4idea/tests/git4idea/branch/GitBranchWorkerTest.groovy +++ b/plugins/git4idea/tests/git4idea/branch/GitBranchWorkerTest.groovy @@ -14,6 +14,7 @@ * limitations under the License. */ package git4idea.branch + import com.intellij.openapi.progress.ProgressIndicator import com.intellij.openapi.progress.util.ProgressIndicatorBase import com.intellij.openapi.project.Project @@ -32,6 +33,7 @@ import git4idea.config.GitVersionSpecialty import git4idea.repo.GitRepository import git4idea.test.GitPlatformTest import org.jetbrains.annotations.NotNull +import org.jetbrains.annotations.Nullable import java.util.regex.Matcher @@ -243,7 +245,7 @@ class GitBranchWorkerTest extends GitPlatformTest { List changes = null; checkoutOrMerge(operation, "feature", [ - showSmartOperationDialog: { Project p, List cs, Collection paths, String op, boolean force -> + showSmartOperationDialog: { Project p, List cs, Collection paths, String op, String force -> changes = cs DialogWrapper.CANCEL_EXIT_CODE } @@ -329,7 +331,7 @@ class GitBranchWorkerTest extends GitPlatformTest { prepareLocalChangesOverwrittenBy(myUltimate) checkoutOrMerge(operation, "feature", [ - showSmartOperationDialog: { Project p, List cs, Collection paths, String op, boolean f + showSmartOperationDialog: { Project p, List cs, Collection paths, String op, String force -> GitSmartOperationDialog.CANCEL_EXIT_CODE }, ] as GitBranchUiHandler ) @@ -355,7 +357,7 @@ class GitBranchWorkerTest extends GitPlatformTest { def rollbackMsg = null checkoutOrMerge(operation, "feature", [ showSmartOperationDialog : { - Project p, List cs, Collection paths, String op, boolean f -> GitSmartOperationDialog.CANCEL_EXIT_CODE + Project p, List cs, Collection paths, String op, String f -> GitSmartOperationDialog.CANCEL_EXIT_CODE }, notifyErrorWithRollbackProposal: { String t, String m, String rp -> rollbackMsg = m; false } ] as GitBranchUiHandler ) @@ -368,7 +370,7 @@ class GitBranchWorkerTest extends GitPlatformTest { prepareLocalChangesOverwrittenBy(myUltimate) def uiHandler = [ - showSmartOperationDialog: { Project p, List cs, Collection paths, String op, boolean force -> + showSmartOperationDialog: { Project p, List cs, Collection paths, String op, String force -> GitSmartOperationDialog.FORCE_EXIT_CODE; }, ] as GitBranchUiHandler @@ -733,7 +735,7 @@ class GitBranchWorkerTest extends GitPlatformTest { @NotNull List changes, @NotNull Collection paths, @NotNull String operation, - boolean isForcePossible) { + @Nullable String forceButton) { GitSmartOperationDialog.SMART_EXIT_CODE } diff --git a/plugins/gradle/gradle.iml b/plugins/gradle/gradle.iml index 878ec399b87c..9e9fb73cdd72 100644 --- a/plugins/gradle/gradle.iml +++ b/plugins/gradle/gradle.iml @@ -14,6 +14,7 @@ + @@ -105,6 +106,9 @@ + + +
diff --git a/plugins/gradle/jps-plugin/gradle-jps-plugin.iml b/plugins/gradle/jps-plugin/gradle-jps-plugin.iml new file mode 100644 index 000000000000..0d1d5300e84b --- /dev/null +++ b/plugins/gradle/jps-plugin/gradle-jps-plugin.iml @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/plugins/gradle/jps-plugin/src/META-INF/services/org.jetbrains.jps.gradle.model.JpsGradleExtensionService b/plugins/gradle/jps-plugin/src/META-INF/services/org.jetbrains.jps.gradle.model.JpsGradleExtensionService new file mode 100644 index 000000000000..526afcc9d0a9 --- /dev/null +++ b/plugins/gradle/jps-plugin/src/META-INF/services/org.jetbrains.jps.gradle.model.JpsGradleExtensionService @@ -0,0 +1,16 @@ +# +# Copyright 2000-2014 JetBrains s.r.o. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +org.jetbrains.jps.gradle.model.impl.JpsGradleExtensionServiceImpl \ No newline at end of file diff --git a/plugins/gradle/jps-plugin/src/META-INF/services/org.jetbrains.jps.incremental.BuilderService b/plugins/gradle/jps-plugin/src/META-INF/services/org.jetbrains.jps.incremental.BuilderService new file mode 100644 index 000000000000..90e686daeaca --- /dev/null +++ b/plugins/gradle/jps-plugin/src/META-INF/services/org.jetbrains.jps.incremental.BuilderService @@ -0,0 +1,16 @@ +# +# Copyright 2000-2014 JetBrains s.r.o. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +org.jetbrains.jps.gradle.compiler.GradleBuilderService \ No newline at end of file diff --git a/plugins/gradle/jps-plugin/src/META-INF/services/org.jetbrains.jps.model.java.impl.JpsJavaDependenciesEnumerationHandler$Factory b/plugins/gradle/jps-plugin/src/META-INF/services/org.jetbrains.jps.model.java.impl.JpsJavaDependenciesEnumerationHandler$Factory new file mode 100644 index 000000000000..f1518a33d329 --- /dev/null +++ b/plugins/gradle/jps-plugin/src/META-INF/services/org.jetbrains.jps.model.java.impl.JpsJavaDependenciesEnumerationHandler$Factory @@ -0,0 +1,16 @@ +# +# Copyright 2000-2014 JetBrains s.r.o. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +org.jetbrains.jps.gradle.model.impl.JpsGradleDependenciesEnumerationHandler$GradleFactory \ No newline at end of file diff --git a/plugins/gradle/jps-plugin/src/META-INF/services/org.jetbrains.jps.model.serialization.JpsModelSerializerExtension b/plugins/gradle/jps-plugin/src/META-INF/services/org.jetbrains.jps.model.serialization.JpsModelSerializerExtension new file mode 100644 index 000000000000..a86ddcf7d451 --- /dev/null +++ b/plugins/gradle/jps-plugin/src/META-INF/services/org.jetbrains.jps.model.serialization.JpsModelSerializerExtension @@ -0,0 +1,16 @@ +# +# Copyright 2000-2014 JetBrains s.r.o. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +org.jetbrains.jps.gradle.model.impl.JpsGradleModelSerializationExtension \ No newline at end of file diff --git a/plugins/gradle/jps-plugin/src/org/jetbrains/jps/gradle/compiler/ChainingFilterTransformer.java b/plugins/gradle/jps-plugin/src/org/jetbrains/jps/gradle/compiler/ChainingFilterTransformer.java new file mode 100644 index 000000000000..c717fe16c792 --- /dev/null +++ b/plugins/gradle/jps-plugin/src/org/jetbrains/jps/gradle/compiler/ChainingFilterTransformer.java @@ -0,0 +1,111 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jetbrains.jps.gradle.compiler; + +import com.intellij.openapi.util.Ref; +import org.gradle.api.Transformer; +import org.gradle.util.ConfigureUtil; +import org.jetbrains.jps.gradle.model.impl.ResourceRootFilter; +import org.jetbrains.jps.incremental.CompileContext; +import org.jetbrains.jps.incremental.messages.BuildMessage; +import org.jetbrains.jps.incremental.messages.CompilerMessage; + +import java.io.File; +import java.io.FilterReader; +import java.io.Reader; +import java.lang.reflect.Constructor; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Map; +import java.util.regex.Matcher; + +/** + * @author Vladislav.Soroka + * @since 7/24/2014 + */ +public class ChainingFilterTransformer implements Transformer { + private final Collection myFilters = new ArrayList(); + private final CompileContext myContext; + private final Ref myOutputFileRef; + + + public ChainingFilterTransformer(CompileContext context, Collection filters, Ref outputFileRef) { + myContext = context; + myOutputFileRef = outputFileRef; + myFilters.addAll(filters); + } + + public ChainingFilterTransformer(CompileContext context, Collection filters) { + this(context, filters, null); + } + + public void addAll(Collection filters) { + myFilters.addAll(filters); + } + + public void add(ResourceRootFilter... filters) { + Collections.addAll(myFilters, filters); + } + + @Override + public Reader transform(Reader original) { + Reader value = original; + for (ResourceRootFilter filter : myFilters) { + value = doTransform(filter, value); + } + return value; + } + + private Reader doTransform(ResourceRootFilter filter, Reader original) { + if ("RenamingCopyFilter" .equals(filter.filterType)) { + final Matcher matcher = (Matcher)filter.getProperties().get("matcher"); + final String replacement = (String)filter.getProperties().get("replacement"); + if (matcher == null || replacement == null) return original; + + matcher.reset(myOutputFileRef.get().getName()); + if (matcher.find()) { + final String newFileName = matcher.replaceFirst(replacement); + myOutputFileRef.set(new File(myOutputFileRef.get().getParentFile(), newFileName)); + } + return original; + } + try { + Class clazz = Class.forName(filter.filterType); + if (!FilterReader.class.isAssignableFrom(clazz)) { + myContext.processMessage( + new CompilerMessage( + GradleResourcesBuilder.BUILDER_NAME, BuildMessage.Kind.WARNING, + String.format("Error - Invalid filter specification for %s. It should extend java.io.FilterReader.", filter.filterType), null) + ); + } + Constructor constructor = clazz.getConstructor(Reader.class); + FilterReader result = (FilterReader)constructor.newInstance(original); + final Map properties = filter.getProperties(); + if (!properties.isEmpty()) { + ConfigureUtil.configureByMap(properties, result); + } + return result; + } + catch (Throwable th) { + myContext.processMessage(new CompilerMessage( + GradleResourcesBuilder.BUILDER_NAME, BuildMessage.Kind.WARNING, + String.format("Error - Failed to apply filter(%s): %s", filter.filterType, th.getMessage()), null) + ); + } + return original; + } +} diff --git a/plugins/gradle/jps-plugin/src/org/jetbrains/jps/gradle/compiler/GradleBuilderService.java b/plugins/gradle/jps-plugin/src/org/jetbrains/jps/gradle/compiler/GradleBuilderService.java new file mode 100644 index 000000000000..5219a2ccad7b --- /dev/null +++ b/plugins/gradle/jps-plugin/src/org/jetbrains/jps/gradle/compiler/GradleBuilderService.java @@ -0,0 +1,44 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jetbrains.jps.gradle.compiler; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.jps.builders.BuildTargetType; +import org.jetbrains.jps.gradle.model.impl.GradleResourcesTargetType; +import org.jetbrains.jps.incremental.BuilderService; +import org.jetbrains.jps.incremental.TargetBuilder; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +/** + * @author Vladislav.Soroka + * @since 7/10/2014 + */ +public class GradleBuilderService extends BuilderService { + @NotNull + @Override + public List> getTargetTypes() { + return Arrays.asList(GradleResourcesTargetType.PRODUCTION, GradleResourcesTargetType.TEST); + } + + @NotNull + @Override + public List> createBuilders() { + return Collections.singletonList(new GradleResourcesBuilder()); + } +} diff --git a/plugins/gradle/jps-plugin/src/org/jetbrains/jps/gradle/compiler/GradleResourceFileProcessor.java b/plugins/gradle/jps-plugin/src/org/jetbrains/jps/gradle/compiler/GradleResourceFileProcessor.java new file mode 100644 index 000000000000..dadfa21906a0 --- /dev/null +++ b/plugins/gradle/jps-plugin/src/org/jetbrains/jps/gradle/compiler/GradleResourceFileProcessor.java @@ -0,0 +1,94 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jetbrains.jps.gradle.compiler; + +import com.intellij.openapi.util.Ref; +import com.intellij.openapi.util.io.FileUtil; +import com.intellij.openapi.util.io.StreamUtil; +import org.apache.tools.ant.util.ReaderInputStream; +import org.jetbrains.jps.gradle.model.impl.GradleModuleResourceConfiguration; +import org.jetbrains.jps.gradle.model.impl.GradleProjectConfiguration; +import org.jetbrains.jps.gradle.model.impl.ResourceRootConfiguration; +import org.jetbrains.jps.gradle.model.impl.ResourceRootFilter; +import org.jetbrains.jps.incremental.CompileContext; +import org.jetbrains.jps.incremental.messages.BuildMessage; +import org.jetbrains.jps.incremental.messages.CompilerMessage; +import org.jetbrains.jps.model.JpsEncodingConfigurationService; +import org.jetbrains.jps.model.JpsEncodingProjectConfiguration; +import org.jetbrains.jps.model.JpsProject; + +import java.io.*; +import java.util.List; + +/** + * @author Vladislav.Soroka + * @since 7/10/2014 + */ +public class GradleResourceFileProcessor { + private static final int FILTERING_SIZE_LIMIT = 10 * 1024 * 1024 /*10 mb*/; + protected final JpsEncodingProjectConfiguration myEncodingConfig; + protected final GradleProjectConfiguration myProjectConfig; + protected final GradleModuleResourceConfiguration myModuleConfiguration; + + public GradleResourceFileProcessor(GradleProjectConfiguration projectConfiguration, JpsProject project, + GradleModuleResourceConfiguration moduleConfiguration) { + myProjectConfig = projectConfiguration; + myEncodingConfig = JpsEncodingConfigurationService.getInstance().getEncodingConfiguration(project); + myModuleConfiguration = moduleConfiguration; + } + + public void copyFile(File file, Ref targetFileRef, ResourceRootConfiguration rootConfiguration, CompileContext context, + FileFilter filteringFilter) throws IOException { + boolean shouldFilter = rootConfiguration.isFiltered && !rootConfiguration.filters.isEmpty() && filteringFilter.accept(file); + if (shouldFilter && file.length() > FILTERING_SIZE_LIMIT) { + context.processMessage(new CompilerMessage( + GradleResourcesBuilder.BUILDER_NAME, BuildMessage.Kind.WARNING, + "File is too big to be filtered. Most likely it is a binary file and should be excluded from filtering", file.getPath()) + ); + shouldFilter = false; + } + if (shouldFilter) { + copyWithFiltering(file, targetFileRef, rootConfiguration.filters, context); + } + else { + FileUtil.copyContent(file, targetFileRef.get()); + } + } + + private static void copyWithFiltering(File file, Ref outputFileRef, List filters, CompileContext context) + throws IOException { + final FileInputStream originalInputStream = new FileInputStream(file); + try { + final InputStream inputStream = transform(filters, originalInputStream, outputFileRef, context); + FileUtil.createIfDoesntExist(outputFileRef.get()); + FileOutputStream outputStream = new FileOutputStream(outputFileRef.get()); + try { + FileUtil.copy(inputStream, outputStream); + } + finally { + StreamUtil.closeStream(inputStream); + StreamUtil.closeStream(outputStream); + } + } + finally { + StreamUtil.closeStream(originalInputStream); + } + } + + private static InputStream transform(List filters, FileInputStream original, Ref outputFileRef, CompileContext context) { + return new ReaderInputStream(new ChainingFilterTransformer(context, filters, outputFileRef).transform(new InputStreamReader(original))); + } +} \ No newline at end of file diff --git a/plugins/gradle/jps-plugin/src/org/jetbrains/jps/gradle/compiler/GradleResourcesBuilder.java b/plugins/gradle/jps-plugin/src/org/jetbrains/jps/gradle/compiler/GradleResourcesBuilder.java new file mode 100644 index 000000000000..295c8bab8133 --- /dev/null +++ b/plugins/gradle/jps-plugin/src/org/jetbrains/jps/gradle/compiler/GradleResourcesBuilder.java @@ -0,0 +1,128 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jetbrains.jps.gradle.compiler; + +import com.intellij.openapi.util.Ref; +import com.intellij.openapi.util.io.FileUtil; +import com.intellij.openapi.util.io.FileUtilRt; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.jps.builders.BuildOutputConsumer; +import org.jetbrains.jps.builders.DirtyFilesHolder; +import org.jetbrains.jps.builders.FileProcessor; +import org.jetbrains.jps.builders.storage.BuildDataPaths; +import org.jetbrains.jps.gradle.model.JpsGradleExtensionService; +import org.jetbrains.jps.gradle.model.impl.*; +import org.jetbrains.jps.incremental.CompileContext; +import org.jetbrains.jps.incremental.ProjectBuildException; +import org.jetbrains.jps.incremental.TargetBuilder; +import org.jetbrains.jps.incremental.messages.ProgressMessage; + +import java.io.File; +import java.io.IOException; +import java.util.*; + +/** + * @author Vladislav.Soroka + * @since 7/10/2014 + */ +public class GradleResourcesBuilder extends TargetBuilder { + public static final String BUILDER_NAME = "Gradle Resources Compiler"; + + public GradleResourcesBuilder() { + super(Arrays.asList(GradleResourcesTargetType.PRODUCTION, GradleResourcesTargetType.TEST)); + } + + @Override + public void build(@NotNull final GradleResourcesTarget target, + @NotNull final DirtyFilesHolder holder, + @NotNull final BuildOutputConsumer outputConsumer, + @NotNull final CompileContext context) throws ProjectBuildException, IOException { + final BuildDataPaths dataPaths = context.getProjectDescriptor().dataManager.getDataPaths(); + final GradleProjectConfiguration projectConfig = JpsGradleExtensionService.getInstance().getGradleProjectConfiguration(dataPaths); + final GradleModuleResourceConfiguration config = target.getModuleResourcesConfiguration(dataPaths); + if (config == null) return; + + final Map> files = new HashMap>(); + + holder.processDirtyFiles(new FileProcessor() { + + @Override + public boolean apply(GradleResourcesTarget t, File file, GradleResourceRootDescriptor rd) throws IOException { + assert target == t; + + List fileList = files.get(rd); + if (fileList == null) { + fileList = new ArrayList(); + files.put(rd, fileList); + } + + fileList.add(file); + return true; + } + }); + + GradleResourceRootDescriptor[] roots = files.keySet().toArray(new GradleResourceRootDescriptor[files.keySet().size()]); + Arrays.sort(roots, new Comparator() { + @Override + public int compare(GradleResourceRootDescriptor r1, GradleResourceRootDescriptor r2) { + int res = r1.getIndexInPom() - r2.getIndexInPom(); + if (r1.isOverwrite()) { + assert r2.isOverwrite(); + return res; + } + + if (r1.getConfiguration().isFiltered && !r2.getConfiguration().isFiltered) return 1; + if (!r1.getConfiguration().isFiltered && r2.getConfiguration().isFiltered) return -1; + + if (!r1.getConfiguration().isFiltered) { + res = -res; + } + + return res; + } + }); + + GradleResourceFileProcessor fileProcessor = new GradleResourceFileProcessor(projectConfig, target.getModule().getProject(), config); + + for (GradleResourceRootDescriptor rd : roots) { + for (File file : files.get(rd)) { + + String relPath = FileUtil.getRelativePath(rd.getRootFile(), file); + if (relPath == null) continue; + + final File outputDir = + GradleResourcesTarget.getOutputDir(target.getModuleOutputDir(), rd.getConfiguration(), config.outputDirectory); + if (outputDir == null) continue; + + context.processMessage(new ProgressMessage("Copying resources... [" + target.getModule().getName() + "]")); + + final Ref fileRef = Ref.create(new File(outputDir, relPath)); + fileProcessor.copyFile(file, fileRef, rd.getConfiguration(), context, FileUtilRt.ALL_FILES); + outputConsumer.registerOutputFile(fileRef.get(), Collections.singleton(file.getPath())); + + if (context.getCancelStatus().isCanceled()) return; + } + } + + context.checkCanceled(); + context.processMessage(new ProgressMessage("")); + } + + @NotNull + public String getPresentableName() { + return BUILDER_NAME; + } +} diff --git a/plugins/gradle/jps-plugin/src/org/jetbrains/jps/gradle/model/JpsGradleExtensionService.java b/plugins/gradle/jps-plugin/src/org/jetbrains/jps/gradle/model/JpsGradleExtensionService.java new file mode 100644 index 000000000000..126cf5758ac1 --- /dev/null +++ b/plugins/gradle/jps-plugin/src/org/jetbrains/jps/gradle/model/JpsGradleExtensionService.java @@ -0,0 +1,49 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jetbrains.jps.gradle.model; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.jps.builders.storage.BuildDataPaths; +import org.jetbrains.jps.gradle.model.impl.GradleProjectConfiguration; +import org.jetbrains.jps.model.module.JpsDependencyElement; +import org.jetbrains.jps.model.module.JpsModule; +import org.jetbrains.jps.service.JpsServiceManager; + +/** + * @author Vladislav.Soroka + * @since 7/10/2014 + */ +public abstract class JpsGradleExtensionService { + public static JpsGradleExtensionService getInstance() { + return JpsServiceManager.getInstance().getService(JpsGradleExtensionService.class); + } + + @Nullable + public abstract JpsGradleModuleExtension getExtension(@NotNull JpsModule module); + + @NotNull + public abstract JpsGradleModuleExtension getOrCreateExtension(@NotNull JpsModule module); + + public abstract void setProductionOnTestDependency(@NotNull JpsDependencyElement dependency, boolean value); + + public abstract boolean isProductionOnTestDependency(@NotNull JpsDependencyElement dependency); + + public abstract boolean hasGradleProjectConfiguration(@NotNull BuildDataPaths paths); + + @NotNull + public abstract GradleProjectConfiguration getGradleProjectConfiguration(BuildDataPaths paths); +} diff --git a/plugins/gradle/jps-plugin/src/org/jetbrains/jps/gradle/model/JpsGradleModuleExtension.java b/plugins/gradle/jps-plugin/src/org/jetbrains/jps/gradle/model/JpsGradleModuleExtension.java new file mode 100644 index 000000000000..c673e4c0d27c --- /dev/null +++ b/plugins/gradle/jps-plugin/src/org/jetbrains/jps/gradle/model/JpsGradleModuleExtension.java @@ -0,0 +1,25 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jetbrains.jps.gradle.model; + +import org.jetbrains.jps.model.JpsElement; + +/** + * @author Vladislav.Soroka + * @since 7/10/2014 + */ +public interface JpsGradleModuleExtension extends JpsElement { +} diff --git a/plugins/gradle/jps-plugin/src/org/jetbrains/jps/gradle/model/impl/FilePattern.java b/plugins/gradle/jps-plugin/src/org/jetbrains/jps/gradle/model/impl/FilePattern.java new file mode 100644 index 000000000000..98938f596d05 --- /dev/null +++ b/plugins/gradle/jps-plugin/src/org/jetbrains/jps/gradle/model/impl/FilePattern.java @@ -0,0 +1,38 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jetbrains.jps.gradle.model.impl; + +import com.intellij.util.xmlb.annotations.AbstractCollection; +import com.intellij.util.xmlb.annotations.Tag; + +import java.util.HashSet; +import java.util.Set; + +/** + * @author Vladislav.Soroka + * @since 7/10/2014 + */ +public class FilePattern { + + @Tag("includes") + @AbstractCollection(surroundWithTag = false, elementTag = "pattern") + public Set includes = new HashSet(); + + @Tag("excludes") + @AbstractCollection(surroundWithTag = false, elementTag = "pattern") + public Set excludes = new HashSet(); + +} diff --git a/plugins/gradle/jps-plugin/src/org/jetbrains/jps/gradle/model/impl/GradleModuleResourceConfiguration.java b/plugins/gradle/jps-plugin/src/org/jetbrains/jps/gradle/model/impl/GradleModuleResourceConfiguration.java new file mode 100644 index 000000000000..9b6732a6d5d5 --- /dev/null +++ b/plugins/gradle/jps-plugin/src/org/jetbrains/jps/gradle/model/impl/GradleModuleResourceConfiguration.java @@ -0,0 +1,81 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jetbrains.jps.gradle.model.impl; + +import com.intellij.util.xmlb.annotations.AbstractCollection; +import com.intellij.util.xmlb.annotations.OptionTag; +import com.intellij.util.xmlb.annotations.Tag; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.List; + + +/** + * @author Vladislav.Soroka + * @since 7/10/2014 + */ +public class GradleModuleResourceConfiguration { + @NotNull + @Tag("id") + public ModuleVersion id; + + @Nullable + @Tag("parentId") + public ModuleVersion parentId; + + @NotNull + @Tag("directory") + public String directory; + + @OptionTag + public boolean overwrite; + + @OptionTag + public String outputDirectory = null; + + @Tag("resources") + @AbstractCollection(surroundWithTag = false, elementTag = "resource") + public List resources = new ArrayList(); + + @Tag("test-resources") + @AbstractCollection(surroundWithTag = false, elementTag = "resource") + public List testResources = new ArrayList(); + + public int computeConfigurationHash(boolean forTestResources) { + int result = computeModuleConfigurationHash(); + + final List _resources = forTestResources ? testResources : resources; + result = 31 * result; + for (ResourceRootConfiguration resource : _resources) { + result += resource.computeConfigurationHash(); + } + return result; + } + + public int computeModuleConfigurationHash() { + int result = id.hashCode(); + result = 31 * result + (parentId != null ? parentId.hashCode() : 0); + result = 31 * result + directory.hashCode(); + result = 31 * result + (outputDirectory != null ? outputDirectory.hashCode() : 0); + result = 31 * result + (overwrite ? 1 : 0); + return result; + } +} + + + diff --git a/plugins/gradle/jps-plugin/src/org/jetbrains/jps/gradle/model/impl/GradleProjectConfiguration.java b/plugins/gradle/jps-plugin/src/org/jetbrains/jps/gradle/model/impl/GradleProjectConfiguration.java new file mode 100644 index 000000000000..083bba570d8a --- /dev/null +++ b/plugins/gradle/jps-plugin/src/org/jetbrains/jps/gradle/model/impl/GradleProjectConfiguration.java @@ -0,0 +1,35 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jetbrains.jps.gradle.model.impl; + +import com.intellij.util.xmlb.annotations.MapAnnotation; +import com.intellij.util.xmlb.annotations.Tag; +import gnu.trove.THashMap; + +import java.util.Map; + +/** + * @author Vladislav.Soroka + * @since 7/10/2014 + */ +public class GradleProjectConfiguration { + public static final String CONFIGURATION_FILE_RELATIVE_PATH = "gradle/configuration.xml"; + + @Tag("resource-processing") + @MapAnnotation(surroundWithTag = false, surroundKeyWithTag = false, surroundValueWithTag = false, entryTagName = "gradle-module", + keyAttributeName = "name") + public Map moduleConfigurations = new THashMap(); +} diff --git a/plugins/gradle/jps-plugin/src/org/jetbrains/jps/gradle/model/impl/GradleResourceFileFilter.java b/plugins/gradle/jps-plugin/src/org/jetbrains/jps/gradle/model/impl/GradleResourceFileFilter.java new file mode 100644 index 000000000000..60e7fd295daa --- /dev/null +++ b/plugins/gradle/jps-plugin/src/org/jetbrains/jps/gradle/model/impl/GradleResourceFileFilter.java @@ -0,0 +1,81 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jetbrains.jps.gradle.model.impl; + +import com.intellij.openapi.util.io.FileUtil; +import org.gradle.api.file.RelativePath; +import org.gradle.api.internal.file.pattern.PatternMatcherFactory; +import org.gradle.api.specs.Spec; +import org.gradle.api.specs.Specs; +import org.jetbrains.annotations.NotNull; + +import java.io.File; +import java.io.FileFilter; +import java.util.ArrayList; +import java.util.Collection; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.regex.Pattern; + +/** + * @author Vladislav.Soroka + * @since 7/10/2014 + */ +public class GradleResourceFileFilter implements FileFilter { + private FilePattern myFilePattern; + private File myRoot; + private final Spec myFileFilterSpec; + + public GradleResourceFileFilter(@NotNull File rootFile, @NotNull FilePattern filePattern) { + myFilePattern = filePattern; + myRoot = rootFile; + myFileFilterSpec = getAsSpec(); + } + + @Override + public boolean accept(@NotNull File file) { + final String relPath = FileUtil.getRelativePath(myRoot, file); + return relPath != null && isIncluded(relPath); + } + + private boolean isIncluded(@NotNull String relativePath) { + RelativePath path = new RelativePath(true, relativePath.split(Pattern.quote(File.separator))); + return myFileFilterSpec.isSatisfiedBy(path); + } + + private Spec getAsSpec() { + return Specs.and(getAsIncludeSpec(true), Specs.not(getAsExcludeSpec(true))); + } + + private Spec getAsExcludeSpec(boolean caseSensitive) { + Collection allExcludes = new LinkedHashSet(myFilePattern.excludes); + List> matchers = new ArrayList>(); + for (String exclude : allExcludes) { + Spec patternMatcher = PatternMatcherFactory.getPatternMatcher(false, caseSensitive, exclude); + matchers.add(patternMatcher); + } + return Specs.or(false, matchers); + } + + private Spec getAsIncludeSpec(boolean caseSensitive) { + List> matchers = new ArrayList>(); + for (String include : myFilePattern.includes) { + Spec patternMatcher = PatternMatcherFactory.getPatternMatcher(true, caseSensitive, include); + matchers.add(patternMatcher); + } + return Specs.or(true, matchers); + } +} \ No newline at end of file diff --git a/plugins/gradle/jps-plugin/src/org/jetbrains/jps/gradle/model/impl/GradleResourceRootDescriptor.java b/plugins/gradle/jps-plugin/src/org/jetbrains/jps/gradle/model/impl/GradleResourceRootDescriptor.java new file mode 100644 index 000000000000..e8ae0d701200 --- /dev/null +++ b/plugins/gradle/jps-plugin/src/org/jetbrains/jps/gradle/model/impl/GradleResourceRootDescriptor.java @@ -0,0 +1,88 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jetbrains.jps.gradle.model.impl; + +import com.intellij.openapi.util.io.FileUtil; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.jps.builders.BuildRootDescriptor; + +import java.io.File; +import java.io.FileFilter; + +/** + * @author Vladislav.Soroka + * @since 7/10/2014 + */ +public class GradleResourceRootDescriptor extends BuildRootDescriptor { + private final GradleResourcesTarget myTarget; + private final ResourceRootConfiguration myConfig; + private final File myFile; + private final String myId; + private final boolean myOverwrite; + + private final int myIndexInPom; + + public GradleResourceRootDescriptor(@NotNull GradleResourcesTarget target, + ResourceRootConfiguration config, + int indexInPom, + boolean overwrite) { + myTarget = target; + myConfig = config; + final String path = FileUtil.toCanonicalPath(config.directory); + myFile = new File(path); + myId = path; + myIndexInPom = indexInPom; + myOverwrite = overwrite; + } + + public ResourceRootConfiguration getConfiguration() { + return myConfig; + } + + @Override + public String getRootId() { + return myId; + } + + @Override + public File getRootFile() { + return myFile; + } + + @Override + public GradleResourcesTarget getTarget() { + return myTarget; + } + + @NotNull + @Override + public FileFilter createFileFilter() { + return new GradleResourceFileFilter(myFile, myConfig); + } + + @Override + public boolean canUseFileCache() { + return true; + } + + public int getIndexInPom() { + return myIndexInPom; + } + + public boolean isOverwrite() { + return myOverwrite; + } +} diff --git a/plugins/gradle/jps-plugin/src/org/jetbrains/jps/gradle/model/impl/GradleResourcesTarget.java b/plugins/gradle/jps-plugin/src/org/jetbrains/jps/gradle/model/impl/GradleResourcesTarget.java new file mode 100644 index 000000000000..344c83080186 --- /dev/null +++ b/plugins/gradle/jps-plugin/src/org/jetbrains/jps/gradle/model/impl/GradleResourcesTarget.java @@ -0,0 +1,161 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jetbrains.jps.gradle.model.impl; + +import com.intellij.openapi.util.io.FileUtil; +import com.intellij.openapi.util.text.StringUtil; +import gnu.trove.THashSet; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.jps.builders.*; +import org.jetbrains.jps.builders.storage.BuildDataPaths; +import org.jetbrains.jps.cmdline.ProjectDescriptor; +import org.jetbrains.jps.gradle.model.JpsGradleExtensionService; +import org.jetbrains.jps.incremental.CompileContext; +import org.jetbrains.jps.indices.IgnoredFileIndex; +import org.jetbrains.jps.indices.ModuleExcludeIndex; +import org.jetbrains.jps.model.JpsModel; +import org.jetbrains.jps.model.java.JpsJavaExtensionService; +import org.jetbrains.jps.model.module.JpsModule; +import org.jetbrains.jps.util.JpsPathUtil; + +import java.io.File; +import java.io.PrintWriter; +import java.util.*; + +/** + * @author Vladislav.Soroka + * @since 7/10/2014 + */ +public class GradleResourcesTarget extends ModuleBasedTarget { + + GradleResourcesTarget(final GradleResourcesTargetType type, @NotNull JpsModule module) { + super(type, module); + } + + @Override + public String getId() { + return myModule.getName(); + } + + @Override + public Collection> computeDependencies(BuildTargetRegistry targetRegistry, TargetOutputIndex outputIndex) { + return Collections.emptyList(); + } + + @Override + public boolean isCompiledBeforeModuleLevelBuilders() { + return true; + } + + @NotNull + @Override + public List computeRootDescriptors(JpsModel model, ModuleExcludeIndex index, IgnoredFileIndex ignoredFileIndex, BuildDataPaths dataPaths) { + final List result = new ArrayList(); + + GradleProjectConfiguration projectConfig = JpsGradleExtensionService.getInstance().getGradleProjectConfiguration(dataPaths); + GradleModuleResourceConfiguration moduleConfig = projectConfig.moduleConfigurations.get(myModule.getName()); + if (moduleConfig == null) return Collections.emptyList(); + + int i = 0; + + for (ResourceRootConfiguration resource : getRootConfigurations(moduleConfig)) { + result.add(new GradleResourceRootDescriptor(this, resource, i++, moduleConfig.overwrite)); + } + return result; + } + + private Collection getRootConfigurations(@Nullable GradleModuleResourceConfiguration moduleConfig) { + if (moduleConfig != null) { + return isTests() ? moduleConfig.testResources : moduleConfig.resources; + } + return Collections.emptyList(); + } + + public GradleModuleResourceConfiguration getModuleResourcesConfiguration(BuildDataPaths dataPaths) { + final GradleProjectConfiguration projectConfig = JpsGradleExtensionService.getInstance().getGradleProjectConfiguration(dataPaths); + return projectConfig.moduleConfigurations.get(myModule.getName()); + } + + public boolean isTests() { + return ((GradleResourcesTargetType)getTargetType()).isTests(); + } + + @Nullable + @Override + public GradleResourceRootDescriptor findRootDescriptor(String rootId, BuildRootIndex rootIndex) { + for (GradleResourceRootDescriptor descriptor : rootIndex.getTargetRoots(this, null)) { + if (descriptor.getRootId().equals(rootId)) { + return descriptor; + } + } + return null; + } + + @NotNull + @Override + public String getPresentableName() { + return getTargetType().getTypeId() + ":" + myModule.getName(); + } + + @NotNull + @Override + public Collection getOutputRoots(CompileContext context) { + GradleModuleResourceConfiguration configuration = + getModuleResourcesConfiguration(context.getProjectDescriptor().dataManager.getDataPaths()); + final Set result = new THashSet(FileUtil.FILE_HASHING_STRATEGY); + final File moduleOutput = getModuleOutputDir(); + for (ResourceRootConfiguration resConfig : getRootConfigurations(configuration)) { + final File output = getOutputDir(moduleOutput, resConfig, configuration.outputDirectory); + if (output != null) { + result.add(output); + } + } + return result; + } + + @Nullable + public File getModuleOutputDir() { + return JpsJavaExtensionService.getInstance().getOutputDirectory(myModule, isTests()); + } + + @Nullable + public static File getOutputDir(@Nullable File moduleOutput, ResourceRootConfiguration config, @Nullable String outputDirectory) { + if(outputDirectory != null) { + moduleOutput = JpsPathUtil.urlToFile(outputDirectory); + } + + if (moduleOutput == null) { + return null; + } + String targetPath = config.targetPath; + if (StringUtil.isEmptyOrSpaces(targetPath)) { + return moduleOutput; + } + final File targetPathFile = new File(targetPath); + final File outputFile = targetPathFile.isAbsolute() ? targetPathFile : new File(moduleOutput, targetPath); + return new File(FileUtil.toCanonicalPath(outputFile.getPath())); + } + + @Override + public void writeConfiguration(ProjectDescriptor pd, PrintWriter out) { + final BuildDataPaths dataPaths = pd.getTargetsState().getDataPaths(); + final GradleModuleResourceConfiguration configuration = getModuleResourcesConfiguration(dataPaths); + if (configuration != null) { + out.write(Integer.toHexString(configuration.computeConfigurationHash(isTests()))); + } + } +} diff --git a/plugins/gradle/jps-plugin/src/org/jetbrains/jps/gradle/model/impl/GradleResourcesTargetType.java b/plugins/gradle/jps-plugin/src/org/jetbrains/jps/gradle/model/impl/GradleResourcesTargetType.java new file mode 100644 index 000000000000..bd399a164d31 --- /dev/null +++ b/plugins/gradle/jps-plugin/src/org/jetbrains/jps/gradle/model/impl/GradleResourcesTargetType.java @@ -0,0 +1,78 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jetbrains.jps.gradle.model.impl; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.jps.builders.BuildTargetLoader; +import org.jetbrains.jps.builders.ModuleBasedBuildTargetType; +import org.jetbrains.jps.gradle.model.JpsGradleExtensionService; +import org.jetbrains.jps.model.JpsModel; +import org.jetbrains.jps.model.module.JpsModule; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * @author Vladislav.Soroka + * @since 7/10/2014 + */ +public class GradleResourcesTargetType extends ModuleBasedBuildTargetType { + public static final GradleResourcesTargetType PRODUCTION = new GradleResourcesTargetType("gradle-resources-production", false); + public static final GradleResourcesTargetType TEST = new GradleResourcesTargetType("gradle-resources-test", true); + + private final boolean myIsTests; + + private GradleResourcesTargetType(final String typeId, boolean isTests) { + super(typeId); + myIsTests = isTests; + } + + public boolean isTests() { + return myIsTests; + } + + @NotNull + @Override + public List computeAllTargets(@NotNull JpsModel model) { + final List targets = new ArrayList(); + for (JpsModule module : model.getProject().getModules()) { + if (JpsGradleExtensionService.getInstance().getExtension(module) != null) { + targets.add(new GradleResourcesTarget(this, module)); + } + } + return targets; + } + + @NotNull + @Override + public BuildTargetLoader createLoader(@NotNull JpsModel model) { + final Map modules = new HashMap(); + for (JpsModule module : model.getProject().getModules()) { + modules.put(module.getName(), module); + } + return new BuildTargetLoader() { + @Nullable + @Override + public GradleResourcesTarget createTarget(@NotNull String targetId) { + final JpsModule module = modules.get(targetId); + return module != null ? new GradleResourcesTarget(GradleResourcesTargetType.this, module) : null; + } + }; + } +} diff --git a/plugins/gradle/jps-plugin/src/org/jetbrains/jps/gradle/model/impl/JpsGradleDependenciesEnumerationHandler.java b/plugins/gradle/jps-plugin/src/org/jetbrains/jps/gradle/model/impl/JpsGradleDependenciesEnumerationHandler.java new file mode 100644 index 000000000000..af0fd08fb6cf --- /dev/null +++ b/plugins/gradle/jps-plugin/src/org/jetbrains/jps/gradle/model/impl/JpsGradleDependenciesEnumerationHandler.java @@ -0,0 +1,57 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jetbrains.jps.gradle.model.impl; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.jps.gradle.model.JpsGradleExtensionService; +import org.jetbrains.jps.model.java.impl.JpsJavaDependenciesEnumerationHandler; +import org.jetbrains.jps.model.module.JpsDependencyElement; +import org.jetbrains.jps.model.module.JpsModule; + +import java.util.Collection; + +/** + * @author Vladislav.Soroka + * @since 7/10/2014 + */ +public class JpsGradleDependenciesEnumerationHandler extends JpsJavaDependenciesEnumerationHandler { + private static final JpsGradleDependenciesEnumerationHandler INSTANCE = new JpsGradleDependenciesEnumerationHandler(); + + @Override + public boolean shouldAddRuntimeDependenciesToTestCompilationClasspath() { + return true; + } + + @Override + public boolean isProductionOnTestsDependency(JpsDependencyElement element) { + return JpsGradleExtensionService.getInstance().isProductionOnTestDependency(element); + } + + public static class GradleFactory extends Factory { + @Nullable + @Override + public JpsJavaDependenciesEnumerationHandler createHandler(@NotNull Collection modules) { + JpsGradleExtensionService service = JpsGradleExtensionService.getInstance(); + for (JpsModule module : modules) { + if (service.getExtension(module) != null) { + return INSTANCE; + } + } + return null; + } + } +} diff --git a/plugins/gradle/jps-plugin/src/org/jetbrains/jps/gradle/model/impl/JpsGradleExtensionServiceImpl.java b/plugins/gradle/jps-plugin/src/org/jetbrains/jps/gradle/model/impl/JpsGradleExtensionServiceImpl.java new file mode 100644 index 000000000000..81ed9076f13e --- /dev/null +++ b/plugins/gradle/jps-plugin/src/org/jetbrains/jps/gradle/model/impl/JpsGradleExtensionServiceImpl.java @@ -0,0 +1,136 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jetbrains.jps.gradle.model.impl; + +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.util.JDOMUtil; +import com.intellij.openapi.util.io.FileUtil; +import com.intellij.util.containers.ConcurrentFactoryMap; +import com.intellij.util.containers.FactoryMap; +import com.intellij.util.xmlb.XmlSerializer; +import gnu.trove.THashMap; +import org.jdom.Document; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.jps.builders.storage.BuildDataPaths; +import org.jetbrains.jps.gradle.model.JpsGradleExtensionService; +import org.jetbrains.jps.gradle.model.JpsGradleModuleExtension; +import org.jetbrains.jps.incremental.resources.ResourcesBuilder; +import org.jetbrains.jps.incremental.resources.StandardResourceBuilderEnabler; +import org.jetbrains.jps.model.JpsElementChildRole; +import org.jetbrains.jps.model.JpsElementFactory; +import org.jetbrains.jps.model.JpsSimpleElement; +import org.jetbrains.jps.model.ex.JpsElementChildRoleBase; +import org.jetbrains.jps.model.module.JpsDependencyElement; +import org.jetbrains.jps.model.module.JpsModule; + +import java.io.File; +import java.util.Map; + +/** + * @author Vladislav.Soroka + * @since 7/10/2014 + */ +public class JpsGradleExtensionServiceImpl extends JpsGradleExtensionService { + private static final Logger LOG = Logger.getInstance(JpsGradleExtensionServiceImpl.class); + private static final JpsElementChildRole> PRODUCTION_ON_TEST_ROLE = JpsElementChildRoleBase.create("production on test"); + private final Map myLoadedConfigs = + new THashMap(FileUtil.FILE_HASHING_STRATEGY); + private final FactoryMap myConfigFileExists = new ConcurrentFactoryMap() { + @Nullable + @Override + protected Boolean create(File key) { + return key.exists(); + } + }; + + public JpsGradleExtensionServiceImpl() { + ResourcesBuilder.registerEnabler(new StandardResourceBuilderEnabler() { + @Override + public boolean isResourceProcessingEnabled(JpsModule module) { + // enable standard resource processing only if this is not a gradle module + // for gradle modules use gradle-aware resource builder + return getExtension(module) == null; + } + }); + } + + @Nullable + @Override + public JpsGradleModuleExtension getExtension(@NotNull JpsModule module) { + return module.getContainer().getChild(JpsGradleModuleExtensionImpl.ROLE); + } + + @NotNull + @Override + public JpsGradleModuleExtension getOrCreateExtension(@NotNull JpsModule module) { + JpsGradleModuleExtension extension = module.getContainer().getChild(JpsGradleModuleExtensionImpl.ROLE); + if (extension == null) { + extension = new JpsGradleModuleExtensionImpl(); + module.getContainer().setChild(JpsGradleModuleExtensionImpl.ROLE, extension); + } + return extension; + } + + @Override + public void setProductionOnTestDependency(@NotNull JpsDependencyElement dependency, boolean value) { + if (value) { + dependency.getContainer().setChild(PRODUCTION_ON_TEST_ROLE, JpsElementFactory.getInstance().createSimpleElement(true)); + } + else { + dependency.getContainer().removeChild(PRODUCTION_ON_TEST_ROLE); + } + } + + @Override + public boolean isProductionOnTestDependency(@NotNull JpsDependencyElement dependency) { + JpsSimpleElement child = dependency.getContainer().getChild(PRODUCTION_ON_TEST_ROLE); + return child != null && child.getData(); + } + + @Override + public boolean hasGradleProjectConfiguration(@NotNull BuildDataPaths paths) { + return myConfigFileExists.get(new File(paths.getDataStorageRoot(), GradleProjectConfiguration.CONFIGURATION_FILE_RELATIVE_PATH)); + } + + @NotNull + @Override + public GradleProjectConfiguration getGradleProjectConfiguration(BuildDataPaths paths) { + final File dataStorageRoot = paths.getDataStorageRoot(); + return getGradleProjectConfiguration(dataStorageRoot); + } + + @NotNull + public GradleProjectConfiguration getGradleProjectConfiguration(@NotNull File dataStorageRoot) { + final File configFile = new File(dataStorageRoot, GradleProjectConfiguration.CONFIGURATION_FILE_RELATIVE_PATH); + GradleProjectConfiguration config; + synchronized (myLoadedConfigs) { + config = myLoadedConfigs.get(configFile); + if (config == null) { + config = new GradleProjectConfiguration(); + try { + final Document document = JDOMUtil.loadDocument(configFile); + XmlSerializer.deserializeInto(config, document.getRootElement()); + } + catch (Exception e) { + LOG.info(e); + } + myLoadedConfigs.put(configFile, config); + } + } + return config; + } +} diff --git a/plugins/gradle/jps-plugin/src/org/jetbrains/jps/gradle/model/impl/JpsGradleModelSerializationExtension.java b/plugins/gradle/jps-plugin/src/org/jetbrains/jps/gradle/model/impl/JpsGradleModelSerializationExtension.java new file mode 100644 index 000000000000..cebd00878b19 --- /dev/null +++ b/plugins/gradle/jps-plugin/src/org/jetbrains/jps/gradle/model/impl/JpsGradleModelSerializationExtension.java @@ -0,0 +1,59 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jetbrains.jps.gradle.model.impl; + +import org.jdom.Element; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.jps.gradle.model.JpsGradleExtensionService; +import org.jetbrains.jps.model.module.JpsDependencyElement; +import org.jetbrains.jps.model.module.JpsModule; +import org.jetbrains.jps.model.serialization.JpsModelSerializerExtension; + +/** + * @author Vladislav.Soroka + * @since 7/10/2014 + */ +public class JpsGradleModelSerializationExtension extends JpsModelSerializerExtension { + private static final String PRODUCTION_ON_TEST_ATTRIBUTE = "production-on-test"; + + @Override + public void loadModuleOptions(@NotNull JpsModule module, @NotNull Element rootElement) { + if ("GRADLE".equals(rootElement.getAttributeValue("external.system.id"))) { + JpsGradleExtensionService.getInstance().getOrCreateExtension(module); + } + } + + @Override + public void saveModuleOptions(@NotNull JpsModule module, @NotNull Element rootElement) { + if (JpsGradleExtensionService.getInstance().getExtension(module) != null) { + rootElement.setAttribute("external.system.id", "GRADLE"); + } + } + + @Override + public void loadModuleDependencyProperties(JpsDependencyElement dependency, Element orderEntry) { + if (orderEntry.getAttributeValue(PRODUCTION_ON_TEST_ATTRIBUTE) != null) { + JpsGradleExtensionService.getInstance().setProductionOnTestDependency(dependency, true); + } + } + + @Override + public void saveModuleDependencyProperties(JpsDependencyElement dependency, Element orderEntry) { + if (JpsGradleExtensionService.getInstance().isProductionOnTestDependency(dependency)) { + orderEntry.setAttribute(PRODUCTION_ON_TEST_ATTRIBUTE, ""); + } + } +} diff --git a/plugins/gradle/jps-plugin/src/org/jetbrains/jps/gradle/model/impl/JpsGradleModuleExtensionImpl.java b/plugins/gradle/jps-plugin/src/org/jetbrains/jps/gradle/model/impl/JpsGradleModuleExtensionImpl.java new file mode 100644 index 000000000000..ecdd04fce10e --- /dev/null +++ b/plugins/gradle/jps-plugin/src/org/jetbrains/jps/gradle/model/impl/JpsGradleModuleExtensionImpl.java @@ -0,0 +1,43 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jetbrains.jps.gradle.model.impl; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.jps.gradle.model.JpsGradleModuleExtension; +import org.jetbrains.jps.model.JpsElementChildRole; +import org.jetbrains.jps.model.ex.JpsElementBase; +import org.jetbrains.jps.model.ex.JpsElementChildRoleBase; + +/** + * @author Vladislav.Soroka + * @since 7/10/2014 + */ +public class JpsGradleModuleExtensionImpl extends JpsElementBase implements JpsGradleModuleExtension { + public static final JpsElementChildRole ROLE = JpsElementChildRoleBase.create("gradle"); + + public JpsGradleModuleExtensionImpl() { + } + + @NotNull + @Override + public JpsGradleModuleExtensionImpl createCopy() { + return new JpsGradleModuleExtensionImpl(); + } + + @Override + public void applyChanges(@NotNull JpsGradleModuleExtensionImpl modified) { + } +} diff --git a/plugins/gradle/jps-plugin/src/org/jetbrains/jps/gradle/model/impl/ModuleVersion.java b/plugins/gradle/jps-plugin/src/org/jetbrains/jps/gradle/model/impl/ModuleVersion.java new file mode 100644 index 000000000000..dbb696d8c33c --- /dev/null +++ b/plugins/gradle/jps-plugin/src/org/jetbrains/jps/gradle/model/impl/ModuleVersion.java @@ -0,0 +1,64 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jetbrains.jps.gradle.model.impl; + +import com.intellij.util.xmlb.annotations.Tag; + +/** + * @author Vladislav.Soroka + * @since 7/10/2014 + */ +public class ModuleVersion { + @Tag("groupId") + public String groupId; + + @Tag("artifactId") + public String artifactId; + + @Tag("version") + public String version; + + public ModuleVersion() { + } + + public ModuleVersion(String groupId, String artifactId, String version) { + this.groupId = groupId; + this.artifactId = artifactId; + this.version = version; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + ModuleVersion bean = (ModuleVersion)o; + + if (artifactId != null ? !artifactId.equals(bean.artifactId) : bean.artifactId != null) return false; + if (groupId != null ? !groupId.equals(bean.groupId) : bean.groupId != null) return false; + if (version != null ? !version.equals(bean.version) : bean.version != null) return false; + + return true; + } + + @Override + public int hashCode() { + int result = groupId != null ? groupId.hashCode() : 0; + result = 31 * result + (artifactId != null ? artifactId.hashCode() : 0); + result = 31 * result + (version != null ? version.hashCode() : 0); + return result; + } +} diff --git a/plugins/gradle/jps-plugin/src/org/jetbrains/jps/gradle/model/impl/ResourceRootConfiguration.java b/plugins/gradle/jps-plugin/src/org/jetbrains/jps/gradle/model/impl/ResourceRootConfiguration.java new file mode 100644 index 000000000000..efd419175dc3 --- /dev/null +++ b/plugins/gradle/jps-plugin/src/org/jetbrains/jps/gradle/model/impl/ResourceRootConfiguration.java @@ -0,0 +1,59 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jetbrains.jps.gradle.model.impl; + +import com.intellij.util.xmlb.annotations.AbstractCollection; +import com.intellij.util.xmlb.annotations.Attribute; +import com.intellij.util.xmlb.annotations.Tag; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.List; + +/** + * @author Vladislav.Soroka + * @since 7/10/2014 + */ +@Tag("resource") +public class ResourceRootConfiguration extends FilePattern { + @Tag("directory") + @NotNull + public String directory; + + @Tag("targetPath") + @Nullable + public String targetPath; + + @Attribute("filtered") + public boolean isFiltered; + + @Tag("filters") + @AbstractCollection(surroundWithTag = false, elementTag = "filter") + public List filters = new ArrayList(); + + public int computeConfigurationHash() { + int result = directory.hashCode(); + result = 31 * result + (targetPath != null ? targetPath.hashCode() : 0); + result = 31 * result + (isFiltered ? 1 : 0); + result = 31 * result + includes.hashCode(); + result = 31 * result + excludes.hashCode(); + for (ResourceRootFilter filter : filters) { + result = 31 * result + filter.computeConfigurationHash(); + } + return result; + } +} diff --git a/plugins/gradle/jps-plugin/src/org/jetbrains/jps/gradle/model/impl/ResourceRootFilter.java b/plugins/gradle/jps-plugin/src/org/jetbrains/jps/gradle/model/impl/ResourceRootFilter.java new file mode 100644 index 000000000000..e8c39e32417e --- /dev/null +++ b/plugins/gradle/jps-plugin/src/org/jetbrains/jps/gradle/model/impl/ResourceRootFilter.java @@ -0,0 +1,75 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jetbrains.jps.gradle.model.impl; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonParseException; +import com.google.gson.JsonSyntaxException; +import com.google.gson.reflect.TypeToken; +import com.intellij.util.xmlb.annotations.Tag; +import org.jetbrains.annotations.NotNull; + +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * @author Vladislav.Soroka + * @since 7/22/2014 + */ +@Tag("filter") +public class ResourceRootFilter { + @Tag("filterType") + @NotNull + public String filterType; + @Tag("properties") + @NotNull + public String properties; + + private transient Map propertiesMap; + + public int computeConfigurationHash() { + int result = filterType.hashCode(); + result = 31 * result + properties.hashCode(); + return result; + } + + @NotNull + public Map getProperties() { + if (propertiesMap == null) { + try { + Gson gson = new GsonBuilder().create(); + propertiesMap = gson.fromJson( + properties, + new TypeToken>() { + }.getType()); + + if("RenamingCopyFilter".equals(filterType)) { + final Object pattern = propertiesMap.get("pattern"); + final Matcher matcher = Pattern.compile(pattern instanceof String ? (String)pattern : "").matcher(""); + propertiesMap.put("matcher", matcher); + } + } + catch (JsonSyntaxException e) { + throw new RuntimeException("Unsupported filter: " + properties , e); + } catch (JsonParseException e) { + throw new RuntimeException("Unsupported filter: " + properties , e); + } + } + return propertiesMap; + } +} diff --git a/plugins/gradle/src/META-INF/gradle-maven-integration.xml b/plugins/gradle/src/META-INF/gradle-maven-integration.xml index 72f46b785ea7..b8fad2f5c3be 100644 --- a/plugins/gradle/src/META-INF/gradle-maven-integration.xml +++ b/plugins/gradle/src/META-INF/gradle-maven-integration.xml @@ -21,7 +21,6 @@ - diff --git a/plugins/gradle/src/META-INF/plugin.xml b/plugins/gradle/src/META-INF/plugin.xml index de6937c18c6c..f15f424a10b8 100644 --- a/plugins/gradle/src/META-INF/plugin.xml +++ b/plugins/gradle/src/META-INF/plugin.xml @@ -67,6 +67,11 @@ + + + + + @@ -95,6 +100,7 @@ + diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/compiler/GradleBuildProcessParametersProvider.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/compiler/GradleBuildProcessParametersProvider.java new file mode 100644 index 000000000000..4337686235f5 --- /dev/null +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/compiler/GradleBuildProcessParametersProvider.java @@ -0,0 +1,93 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jetbrains.plugins.gradle.compiler; + +import com.google.gson.Gson; +import com.intellij.compiler.server.BuildProcessParametersProvider; +import com.intellij.openapi.externalSystem.util.ExternalSystemApiUtil; +import com.intellij.openapi.module.Module; +import com.intellij.openapi.module.ModuleManager; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.io.FileUtil; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.util.PathUtil; +import com.intellij.util.containers.ContainerUtil; +import groovy.lang.GroovyObject; +import org.apache.tools.ant.taskdefs.Ant; +import org.gradle.tooling.ProjectConnection; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.plugins.gradle.util.GradleConstants; + +import java.io.File; +import java.util.List; + +/** + * Adds Gradle build dependencies to the project build process' classpath. + * + * @author Vladislav.Soroka + * @since 7/22/2014 + */ +public class GradleBuildProcessParametersProvider extends BuildProcessParametersProvider { + @NotNull private final Project myProject; + + private List myClasspath; + + public GradleBuildProcessParametersProvider(@NotNull Project project) { + myProject = project; + } + + @Override + @NotNull + public List getClassPath() { + if (myClasspath == null) { + myClasspath = ContainerUtil.newArrayList(); + addGradleClassPath(myClasspath); + final ModuleManager moduleManager = ModuleManager.getInstance(myProject); + for (Module module : moduleManager.getModules()) { + if (ExternalSystemApiUtil.isExternalSystemAwareModule(GradleConstants.SYSTEM_ID, module)) { + addOtherClassPath(myClasspath); + break; + } + } + } + return myClasspath; + } + + private static void addGradleClassPath(@NotNull final List classpath) { + String gradleLibDirPath = null; + String gradleToolingApiJarPath = PathUtil.getJarPathForClass(ProjectConnection.class); + if (!StringUtil.isEmpty(gradleToolingApiJarPath)) { + gradleLibDirPath = PathUtil.getParentPath(gradleToolingApiJarPath); + } + if (gradleLibDirPath == null || gradleLibDirPath.isEmpty()) return; + + File gradleLibDir = new File(gradleLibDirPath); + if (!gradleLibDir.isDirectory()) return; + + File[] children = FileUtil.notNullize(gradleLibDir.listFiles()); + for (File child : children) { + if (child.isFile() && child.getName().endsWith(".jar")) { + classpath.add(child.getAbsolutePath()); + } + } + } + + private static void addOtherClassPath(@NotNull final List classpath) { + classpath.add(PathUtil.getJarPathForClass(Ant.class)); + classpath.add(PathUtil.getJarPathForClass(GroovyObject.class)); + classpath.add(PathUtil.getJarPathForClass(Gson.class)); + } +} diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/config/GradleResourceCompilerConfigurationGenerator.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/config/GradleResourceCompilerConfigurationGenerator.java new file mode 100644 index 000000000000..01c5275f3828 --- /dev/null +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/config/GradleResourceCompilerConfigurationGenerator.java @@ -0,0 +1,186 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jetbrains.plugins.gradle.config; + +import com.intellij.compiler.server.BuildManager; +import com.intellij.openapi.compiler.CompileContext; +import com.intellij.openapi.compiler.CompilerMessageCategory; +import com.intellij.openapi.components.ServiceManager; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.externalSystem.model.ExternalFilter; +import com.intellij.openapi.externalSystem.model.ExternalProject; +import com.intellij.openapi.externalSystem.model.ExternalSourceDirectorySet; +import com.intellij.openapi.externalSystem.model.ExternalSourceSet; +import com.intellij.openapi.externalSystem.model.project.ExternalSystemSourceType; +import com.intellij.openapi.externalSystem.service.project.manage.ProjectDataManager; +import com.intellij.openapi.externalSystem.util.ExternalSystemApiUtil; +import com.intellij.openapi.externalSystem.util.ExternalSystemConstants; +import com.intellij.openapi.module.Module; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.roots.CompilerModuleExtension; +import com.intellij.openapi.util.JDOMUtil; +import com.intellij.openapi.util.io.FileUtil; +import com.intellij.openapi.vfs.VfsUtilCore; +import com.intellij.util.containers.FactoryMap; +import com.intellij.util.xmlb.XmlSerializer; +import org.jdom.Document; +import org.jdom.Element; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.jps.gradle.model.impl.*; +import org.jetbrains.plugins.gradle.service.project.data.ExternalProjectDataService; +import org.jetbrains.plugins.gradle.util.GradleConstants; + +import java.io.File; +import java.io.IOException; +import java.util.List; +import java.util.Map; + +/** + * @author Vladislav.Soroka + * @since 7/10/2014 + */ +public class GradleResourceCompilerConfigurationGenerator { + + private static Logger LOG = Logger.getInstance(GradleResourceCompilerConfigurationGenerator.class); + + @NotNull private final Project myProject; + @NotNull private final CompileContext myContext; + @NotNull private final Map myExternalProjectMap; + private final ExternalProjectDataService myExternalProjectDataService; + + public GradleResourceCompilerConfigurationGenerator(@NotNull final Project project, @NotNull final CompileContext context) { + myProject = project; + myContext = context; + myExternalProjectDataService = + (ExternalProjectDataService)ServiceManager.getService(ProjectDataManager.class).getDataService(ExternalProjectDataService.KEY); + assert myExternalProjectDataService != null; + + myExternalProjectMap = new FactoryMap() { + @Nullable + @Override + protected ExternalProject create(String gradleProjectPath) { + return myExternalProjectDataService.getOrImportRootExternalProject(project, GradleConstants.SYSTEM_ID, new File(gradleProjectPath)); + } + }; + } + + public void generateBuildConfiguration() { + if (!hasGradleModules()) return; + + final BuildManager buildManager = BuildManager.getInstance(); + final File projectSystemDir = buildManager.getProjectSystemDirectory(myProject); + if (projectSystemDir == null) return; + + final File gradleConfigFile = new File(projectSystemDir, GradleProjectConfiguration.CONFIGURATION_FILE_RELATIVE_PATH); + + GradleProjectConfiguration projectConfig = new GradleProjectConfiguration(); + for (Module module : myContext.getCompileScope().getAffectedModules()) { + if (!ExternalSystemApiUtil.isExternalSystemAwareModule(GradleConstants.SYSTEM_ID, module)) continue; + + final String gradleProjectPath = module.getOptionValue(ExternalSystemConstants.ROOT_PROJECT_PATH_KEY); + assert gradleProjectPath != null; + final ExternalProject externalRootProject = myExternalProjectMap.get(gradleProjectPath); + if (externalRootProject == null) { + myContext.addMessage(CompilerMessageCategory.ERROR, + String.format("Unable to make the module: %s, related gradle module configuration was not imported", + module.getName()), + VfsUtilCore.pathToUrl(gradleProjectPath), -1, -1); + continue; + } + + ExternalProject externalProject = myExternalProjectDataService.findExternalProject(externalRootProject, module); + if (externalProject == null) { + LOG.warn("Unable to find config for module: " + module.getName()); + continue; + } + + final CompilerModuleExtension compilerModuleExtension = CompilerModuleExtension.getInstance(module); + assert compilerModuleExtension != null; + + GradleModuleResourceConfiguration resourceConfig = new GradleModuleResourceConfiguration(); + resourceConfig.id = new ModuleVersion(externalProject.getGroup(), externalProject.getName(), externalProject.getVersion()); + resourceConfig.directory = FileUtil.toSystemIndependentName(externalProject.getProjectDir().getPath()); + + final ExternalSourceSet mainSourcesSet = externalProject.getSourceSets().get("main"); + addResources(resourceConfig.resources, mainSourcesSet, ExternalSystemSourceType.RESOURCE); + + final ExternalSourceSet testSourcesSet = externalProject.getSourceSets().get("test"); + addResources(resourceConfig.testResources, testSourcesSet, ExternalSystemSourceType.TEST_RESOURCE); + + projectConfig.moduleConfigurations.put(module.getName(), resourceConfig); + } + + final Document document = new Document(new Element("gradle-project-configuration")); + XmlSerializer.serializeInto(projectConfig, document.getRootElement()); + buildManager.runCommand(new Runnable() { + @Override + public void run() { + buildManager.clearState(myProject); + FileUtil.createIfDoesntExist(gradleConfigFile); + try { + JDOMUtil.writeDocument(document, gradleConfigFile, "\n"); + } + catch (IOException e) { + throw new RuntimeException(e); + } + } + }); + } + + private boolean hasGradleModules() { + for (Module module : myContext.getCompileScope().getAffectedModules()) { + if (ExternalSystemApiUtil.isExternalSystemAwareModule(GradleConstants.SYSTEM_ID, module)) return true; + } + return false; + } + + private static void addResources(@NotNull List container, + @Nullable ExternalSourceSet externalSourceSet, + @NotNull ExternalSystemSourceType sourceType) { + if (externalSourceSet == null) return; + final ExternalSourceDirectorySet directorySet = externalSourceSet.getSources().get(sourceType); + if (directorySet == null) return; + + for (File file : directorySet.getSrcDirs()) { + final String dir = file.getPath(); + final ResourceRootConfiguration rootConfiguration = new ResourceRootConfiguration(); + rootConfiguration.directory = FileUtil.toSystemIndependentName(dir); + final String target = directorySet.getOutputDir().getPath(); + rootConfiguration.targetPath = FileUtil.toSystemIndependentName(target); + + rootConfiguration.includes.clear(); + for (String include : directorySet.getIncludes()) { + rootConfiguration.includes.add(include.trim()); + } + rootConfiguration.excludes.clear(); + for (String exclude : directorySet.getExcludes()) { + rootConfiguration.excludes.add(exclude.trim()); + } + + rootConfiguration.isFiltered = !directorySet.getFilters().isEmpty(); + rootConfiguration.filters.clear(); + for (ExternalFilter filter : directorySet.getFilters()) { + final ResourceRootFilter resourceRootFilter = new ResourceRootFilter(); + resourceRootFilter.filterType = filter.getFilterType(); + resourceRootFilter.properties = filter.getPropertiesAsJsonMap(); + rootConfiguration.filters.add(resourceRootFilter); + } + + container.add(rootConfiguration); + } + } +} diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/execution/GradleOrderEnumeratorHandler.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/execution/GradleOrderEnumeratorHandler.java new file mode 100644 index 000000000000..0dc670dbefa8 --- /dev/null +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/execution/GradleOrderEnumeratorHandler.java @@ -0,0 +1,114 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jetbrains.plugins.gradle.execution; + +import com.intellij.openapi.components.ServiceManager; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.externalSystem.model.ExternalProject; +import com.intellij.openapi.externalSystem.model.ExternalSourceDirectorySet; +import com.intellij.openapi.externalSystem.model.ExternalSourceSet; +import com.intellij.openapi.externalSystem.model.project.ExternalSystemSourceType; +import com.intellij.openapi.externalSystem.service.project.manage.ProjectDataManager; +import com.intellij.openapi.externalSystem.util.ExternalSystemApiUtil; +import com.intellij.openapi.externalSystem.util.ExternalSystemConstants; +import com.intellij.openapi.module.Module; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.roots.ModuleRootModel; +import com.intellij.openapi.roots.OrderEnumerationHandler; +import com.intellij.openapi.roots.OrderRootType; +import com.intellij.openapi.vfs.VfsUtilCore; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.plugins.gradle.service.project.data.ExternalProjectDataService; +import org.jetbrains.plugins.gradle.util.GradleConstants; + +import java.io.File; +import java.util.Collection; + +public class GradleOrderEnumeratorHandler extends OrderEnumerationHandler { + private static final Logger LOG = Logger.getInstance(GradleOrderEnumeratorHandler.class); + + public static class FactoryImpl extends Factory { + @Override + public boolean isApplicable(@NotNull Project project) { + return true; + } + + @Override + public boolean isApplicable(@NotNull Module module) { + return ExternalSystemApiUtil.isExternalSystemAwareModule(GradleConstants.SYSTEM_ID, module); + } + + @Override + public OrderEnumerationHandler createHandler(@Nullable Module module) { + return INSTANCE; + } + } + + private static final GradleOrderEnumeratorHandler INSTANCE = new GradleOrderEnumeratorHandler(); + + @Override + public boolean addCustomModuleRoots(@NotNull OrderRootType type, + @NotNull ModuleRootModel rootModel, + @NotNull Collection result, + boolean includeProduction, + boolean includeTests) { + if (!type.equals(OrderRootType.CLASSES)) return false; + if (!ExternalSystemApiUtil.isExternalSystemAwareModule(GradleConstants.SYSTEM_ID, rootModel.getModule())) return false; + + final String gradleProjectPath = rootModel.getModule().getOptionValue(ExternalSystemConstants.ROOT_PROJECT_PATH_KEY); + if (gradleProjectPath == null) { + LOG.error("Root project path of the Gradle project not found for " + rootModel.getModule()); + return false; + } + + final ExternalProjectDataService externalProjectDataService = + (ExternalProjectDataService)ServiceManager.getService(ProjectDataManager.class).getDataService(ExternalProjectDataService.KEY); + + assert externalProjectDataService != null; + final ExternalProject externalRootProject = + externalProjectDataService.getRootExternalProject(GradleConstants.SYSTEM_ID, new File(gradleProjectPath)); + if (externalRootProject == null) { + LOG.debug("Root external project was not yep imported for the project path: " + gradleProjectPath); + return false; + } + + ExternalProject externalProject = externalProjectDataService.findExternalProject(externalRootProject, rootModel.getModule()); + if (externalProject == null) return false; + + if (includeProduction) { + addSourceRoots(externalProject.getSourceSets().get("main"), ExternalSystemSourceType.RESOURCE, result); + } + + if (includeTests) { + addSourceRoots(externalProject.getSourceSets().get("test"), ExternalSystemSourceType.TEST_RESOURCE, result); + } + + return true; + } + + private static void addSourceRoots(@Nullable ExternalSourceSet externalSourceSet, + @NotNull ExternalSystemSourceType sourceType, + @NotNull Collection result) { + if (externalSourceSet == null) return; + final ExternalSourceDirectorySet directorySet = externalSourceSet.getSources().get(sourceType); + if (directorySet == null) return; + + for (File file : directorySet.getSrcDirs()) { + result.add(VfsUtilCore.pathToUrl(file.getAbsolutePath())); + } + } +} diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/service/project/AbstractProjectResolverExtension.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/service/project/AbstractProjectResolverExtension.java index 1d8addd31369..9603470a0878 100644 --- a/plugins/gradle/src/org/jetbrains/plugins/gradle/service/project/AbstractProjectResolverExtension.java +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/service/project/AbstractProjectResolverExtension.java @@ -28,6 +28,7 @@ import com.intellij.openapi.externalSystem.util.Order; import com.intellij.openapi.util.KeyValue; import com.intellij.util.Consumer; import org.gradle.tooling.model.idea.IdeaModule; +import org.gradle.tooling.model.idea.IdeaProject; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -80,6 +81,11 @@ public abstract class AbstractProjectResolverExtension implements GradleProjectR return nextResolver.createJavaProjectData(); } + @Override + public void populateProjectExtraModels(@NotNull IdeaProject gradleProject, @NotNull DataNode ideProject) { + nextResolver.populateProjectExtraModels(gradleProject, ideProject); + } + @NotNull @Override public ModuleData createModule(@NotNull IdeaModule gradleModule, @NotNull ProjectData projectData) { diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/service/project/BaseGradleProjectResolverExtension.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/service/project/BaseGradleProjectResolverExtension.java index 6f43559f7a11..ad7ade6a3c58 100644 --- a/plugins/gradle/src/org/jetbrains/plugins/gradle/service/project/BaseGradleProjectResolverExtension.java +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/service/project/BaseGradleProjectResolverExtension.java @@ -15,14 +15,13 @@ */ package org.jetbrains.plugins.gradle.service.project; +import com.google.gson.GsonBuilder; import com.intellij.execution.ExecutionException; import com.intellij.execution.configurations.SimpleJavaParameters; import com.intellij.externalSystem.JavaProjectData; import com.intellij.openapi.application.PathManager; import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.externalSystem.model.DataNode; -import com.intellij.openapi.externalSystem.model.ExternalSystemException; -import com.intellij.openapi.externalSystem.model.ProjectKeys; +import com.intellij.openapi.externalSystem.model.*; import com.intellij.openapi.externalSystem.model.project.*; import com.intellij.openapi.externalSystem.model.task.TaskData; import com.intellij.openapi.externalSystem.util.ExternalSystemApiUtil; @@ -56,6 +55,7 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.plugins.gradle.model.*; import org.jetbrains.plugins.gradle.model.data.BuildScriptClasspathData; +import org.jetbrains.plugins.gradle.service.project.data.ExternalProjectDataService; import org.jetbrains.plugins.gradle.tooling.builder.ModelBuildScriptClasspathBuilderImpl; import org.jetbrains.plugins.gradle.tooling.internal.init.Init; import org.jetbrains.plugins.gradle.util.GradleBundle; @@ -79,6 +79,8 @@ public class BaseGradleProjectResolverExtension implements GradleProjectResolver private static final Logger LOG = Logger.getInstance("#" + BaseGradleProjectResolverExtension.class.getName()); @NotNull @NonNls private static final String UNRESOLVED_DEPENDENCY_PREFIX = "unresolved dependency - "; + private static final String MAIN_SOURCE_SET = "main"; + private static final String TEST_SOURCE_SET = "test"; @NotNull private ProjectResolverContext resolverCtx; @NotNull private final BaseProjectImportErrorHandler myErrorHandler = new BaseProjectImportErrorHandler(); @@ -120,6 +122,14 @@ public class BaseGradleProjectResolverExtension implements GradleProjectResolver return javaProjectData; } + @Override + public void populateProjectExtraModels(@NotNull IdeaProject gradleProject, @NotNull DataNode ideProject) { + final ExternalProject externalProject = resolverCtx.getExtraProject(ExternalProject.class); + if (externalProject != null) { + ideProject.createChild(ExternalProjectDataService.KEY, externalProject); + } + } + @NotNull @Override public ModuleData createModule(@NotNull IdeaModule gradleModule, @NotNull ProjectData projectData) { @@ -220,6 +230,8 @@ public class BaseGradleProjectResolverExtension implements GradleProjectResolver File sourceCompileOutputPath = null; File testCompileOutputPath = null; + File resourceCompileOutputPath; + File testResourceCompileOutputPath; boolean inheritOutputDirs = false; ModuleData moduleData = ideModule.getData(); @@ -229,30 +241,71 @@ public class BaseGradleProjectResolverExtension implements GradleProjectResolver inheritOutputDirs = moduleCompilerOutput.getInheritOutputDirs(); } + ExternalProject externalProject = resolverCtx.getExtraProject(gradleModule, ExternalProject.class); + if (externalProject != null) { + externalProject = new DefaultExternalProject(externalProject); + } + if (!inheritOutputDirs && (sourceCompileOutputPath == null || testCompileOutputPath == null)) { - ModuleExtendedModel moduleExtendedModel = resolverCtx.getExtraProject(gradleModule, ModuleExtendedModel.class); - if (moduleExtendedModel != null) { - ExtIdeaCompilerOutput output = moduleExtendedModel.getCompilerOutput(); - if (output != null) { - if (sourceCompileOutputPath == null) { - sourceCompileOutputPath = output.getMainClassesDir(); - } - if (testCompileOutputPath == null) { - testCompileOutputPath = output.getTestClassesDir(); + sourceCompileOutputPath = getCompileOutputPath(externalProject, MAIN_SOURCE_SET, ExternalSystemSourceType.SOURCE); + resourceCompileOutputPath = getCompileOutputPath(externalProject, MAIN_SOURCE_SET, ExternalSystemSourceType.RESOURCE); + testCompileOutputPath = getCompileOutputPath(externalProject, TEST_SOURCE_SET, ExternalSystemSourceType.TEST); + testResourceCompileOutputPath = getCompileOutputPath(externalProject, TEST_SOURCE_SET, ExternalSystemSourceType.TEST_RESOURCE); + } + else { + resourceCompileOutputPath = sourceCompileOutputPath; + testResourceCompileOutputPath = testCompileOutputPath; + + if (externalProject != null) { + final ExternalSourceSet mainSourceSet = externalProject.getSourceSets().get(MAIN_SOURCE_SET); + if (mainSourceSet != null) { + final ExternalSourceDirectorySet resourceDirectories = mainSourceSet.getSources().get(ExternalSystemSourceType.RESOURCE); + if (resourceDirectories instanceof DefaultExternalSourceDirectorySet) { + ((DefaultExternalSourceDirectorySet)resourceDirectories).setOutputDir(sourceCompileOutputPath); } } + final ExternalSourceSet testSourceSet = externalProject.getSourceSets().get(TEST_SOURCE_SET); + if (testSourceSet != null) { + final ExternalSourceDirectorySet testResourceDirectories = testSourceSet.getSources().get(ExternalSystemSourceType.TEST_RESOURCE); + if (testResourceDirectories instanceof DefaultExternalSourceDirectorySet) { + ((DefaultExternalSourceDirectorySet)testResourceDirectories).setOutputDir(testCompileOutputPath); + } + } + + final DataNode projectDataNode = ExternalSystemApiUtil.findParent(ideModule, ProjectKeys.PROJECT); + assert projectDataNode != null; + projectDataNode.createOrReplaceChild(ExternalProjectDataService.KEY, externalProject); } } if (sourceCompileOutputPath != null) { moduleData.setCompileOutputPath(ExternalSystemSourceType.SOURCE, sourceCompileOutputPath.getAbsolutePath()); } + if (resourceCompileOutputPath != null) { + moduleData.setCompileOutputPath(ExternalSystemSourceType.RESOURCE, resourceCompileOutputPath.getAbsolutePath()); + } if (testCompileOutputPath != null) { moduleData.setCompileOutputPath(ExternalSystemSourceType.TEST, testCompileOutputPath.getAbsolutePath()); } + if (testResourceCompileOutputPath != null) { + moduleData.setCompileOutputPath(ExternalSystemSourceType.TEST_RESOURCE, testResourceCompileOutputPath.getAbsolutePath()); + } + moduleData.setInheritProjectCompileOutputPath(inheritOutputDirs || sourceCompileOutputPath == null); } + @Nullable + private static File getCompileOutputPath(@Nullable ExternalProject externalProject, + @NotNull String sourceSetName, + @NotNull ExternalSystemSourceType sourceType) { + if (externalProject == null) return null; + final ExternalSourceSet sourceSet = externalProject.getSourceSets().get(sourceSetName); + if(sourceSet == null) return null; + + final ExternalSourceDirectorySet directorySet = sourceSet.getSources().get(sourceType); + return directorySet != null ? directorySet.getOutputDir() : null; + } + @Override public void populateModuleDependencies(@NotNull IdeaModule gradleModule, @NotNull DataNode ideModule, @@ -319,17 +372,19 @@ public class BaseGradleProjectResolverExtension implements GradleProjectResolver @Override public Set getExtraProjectModelClasses() { return ContainerUtil.set( - GradleBuild.class, ModuleExtendedModel.class, BuildScriptClasspathModel.class); + GradleBuild.class, ExternalProject.class, ModuleExtendedModel.class, BuildScriptClasspathModel.class); } @NotNull @Override public Set getToolingExtensionsClasses() { return ContainerUtil.set( + ExternalProject.class, // gradle-tooling-extension-api jar ProjectImportAction.class, // gradle-tooling-extension-impl jar - ModelBuildScriptClasspathBuilderImpl.class + ModelBuildScriptClasspathBuilderImpl.class, + GsonBuilder.class ); } @@ -416,6 +471,8 @@ public class BaseGradleProjectResolverExtension implements GradleProjectResolver List additionalEntries = ContainerUtilRt.newArrayList(); ContainerUtilRt.addIfNotNull(additionalEntries, PathUtil.getJarPathForClass(GroovyObject.class)); + ContainerUtilRt.addIfNotNull(additionalEntries, PathUtil.getJarPathForClass(GsonBuilder.class)); + ContainerUtilRt.addIfNotNull(additionalEntries, PathUtil.getJarPathForClass(ExternalProject.class)); ContainerUtilRt.addIfNotNull(additionalEntries, PathUtil.getJarPathForClass(JavaProjectData.class)); ContainerUtilRt.addIfNotNull(additionalEntries, PathUtil.getJarPathForClass(LanguageLevel.class)); ContainerUtilRt.addIfNotNull(additionalEntries, PathUtil.getJarPathForClass(StdModuleTypes.class)); diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/service/project/GradleProjectResolver.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/service/project/GradleProjectResolver.java index 8a2ffaae36c9..24c8e126ed3f 100644 --- a/plugins/gradle/src/org/jetbrains/plugins/gradle/service/project/GradleProjectResolver.java +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/service/project/GradleProjectResolver.java @@ -199,6 +199,9 @@ public class GradleProjectResolver implements ExternalSystemProjectResolver gradleModules = ideaProject.getModules(); if (gradleModules == null || gradleModules.isEmpty()) { throw new IllegalStateException("No modules found for the target project: " + ideaProject); diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/service/project/GradleProjectResolverExtension.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/service/project/GradleProjectResolverExtension.java index b727005db845..6dd201b839fb 100644 --- a/plugins/gradle/src/org/jetbrains/plugins/gradle/service/project/GradleProjectResolverExtension.java +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/service/project/GradleProjectResolverExtension.java @@ -27,6 +27,7 @@ import com.intellij.openapi.externalSystem.service.ParametersEnhancer; import com.intellij.openapi.util.KeyValue; import com.intellij.util.Consumer; import org.gradle.tooling.model.idea.IdeaModule; +import org.gradle.tooling.model.idea.IdeaProject; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.plugins.gradle.GradleManager; @@ -62,6 +63,8 @@ public interface GradleProjectResolverExtension extends ParametersEnhancer { @NotNull JavaProjectData createJavaProjectData(); + void populateProjectExtraModels(@NotNull IdeaProject gradleProject, @NotNull DataNode ideProject); + @NotNull ModuleData createModule(@NotNull IdeaModule gradleModule, @NotNull ProjectData projectData); diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/service/project/GradleStartupActivity.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/service/project/GradleStartupActivity.java index 81ceaa3177e2..0e5fcc12f3dc 100644 --- a/plugins/gradle/src/org/jetbrains/plugins/gradle/service/project/GradleStartupActivity.java +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/service/project/GradleStartupActivity.java @@ -21,6 +21,11 @@ import com.intellij.ide.util.newProjectWizard.AddModuleWizard; import com.intellij.notification.Notification; import com.intellij.notification.NotificationListener; import com.intellij.notification.NotificationType; +import com.intellij.openapi.application.AccessToken; +import com.intellij.openapi.application.ReadAction; +import com.intellij.openapi.compiler.CompileContext; +import com.intellij.openapi.compiler.CompileTask; +import com.intellij.openapi.compiler.CompilerManager; import com.intellij.openapi.components.ServiceManager; import com.intellij.openapi.externalSystem.model.ExternalSystemDataKeys; import com.intellij.openapi.externalSystem.service.project.manage.ProjectDataManager; @@ -30,6 +35,8 @@ import com.intellij.openapi.util.io.FileUtil; import com.intellij.openapi.vfs.VfsUtilCore; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; +import org.jetbrains.idea.maven.project.MavenResourceCompilerConfigurationGenerator; +import org.jetbrains.plugins.gradle.config.GradleResourceCompilerConfigurationGenerator; import org.jetbrains.plugins.gradle.service.GradleBuildClasspathManager; import org.jetbrains.plugins.gradle.service.project.wizard.GradleProjectImportBuilder; import org.jetbrains.plugins.gradle.service.project.wizard.GradleProjectImportProvider; @@ -52,9 +59,22 @@ public class GradleStartupActivity implements StartupActivity { private static final String DO_NOT_SHOW_EVENT_DESCRIPTION = "do.not.show"; @Override - public void runActivity(@NotNull Project project) { + public void runActivity(@NotNull final Project project) { configureBuildClasspath(project); showNotificationForUnlinkedGradleProject(project); + CompilerManager.getInstance(project).addBeforeTask(new CompileTask() { + @Override + public boolean execute(CompileContext context) { + AccessToken token = ReadAction.start(); + try { + new GradleResourceCompilerConfigurationGenerator(project, context).generateBuildConfiguration(); + } + finally { + token.finish(); + } + return true; + } + }); } private static void configureBuildClasspath(@NotNull final Project project) { diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/service/project/ProjectResolverContext.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/service/project/ProjectResolverContext.java index b7f94dd57122..3aa0027185bf 100644 --- a/plugins/gradle/src/org/jetbrains/plugins/gradle/service/project/ProjectResolverContext.java +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/service/project/ProjectResolverContext.java @@ -19,6 +19,7 @@ import com.intellij.openapi.externalSystem.model.task.ExternalSystemTaskId; import com.intellij.openapi.externalSystem.model.task.ExternalSystemTaskNotificationListener; import org.gradle.tooling.ProjectConnection; import org.gradle.tooling.model.idea.IdeaModule; +import org.gradle.tooling.model.idea.IdeaProject; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.plugins.gradle.model.ProjectImportAction; @@ -93,7 +94,12 @@ public class ProjectResolverContext { } @Nullable - public T getExtraProject(@NotNull IdeaModule module, Class modelClazz) { + public T getExtraProject(Class modelClazz) { + return myModels.getExtraProject(null, modelClazz); + } + + @Nullable + public T getExtraProject(@Nullable IdeaModule module, Class modelClazz) { return myModels.getExtraProject(module, modelClazz); } diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/service/project/data/ExternalProjectDataService.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/service/project/data/ExternalProjectDataService.java new file mode 100644 index 000000000000..fae9ee0d88c1 --- /dev/null +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/service/project/data/ExternalProjectDataService.java @@ -0,0 +1,233 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jetbrains.plugins.gradle.service.project.data; + +import com.intellij.CommonBundle; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.externalSystem.model.*; +import com.intellij.openapi.externalSystem.model.project.ProjectData; +import com.intellij.openapi.externalSystem.model.task.ExternalSystemTaskNotificationListener; +import com.intellij.openapi.externalSystem.service.internal.ExternalSystemResolveProjectTask; +import com.intellij.openapi.externalSystem.service.notification.ExternalSystemNotificationManager; +import com.intellij.openapi.externalSystem.service.notification.NotificationSource; +import com.intellij.openapi.externalSystem.service.project.manage.ProjectDataManager; +import com.intellij.openapi.externalSystem.service.project.manage.ProjectDataService; +import com.intellij.openapi.externalSystem.settings.ExternalProjectSettings; +import com.intellij.openapi.externalSystem.util.*; +import com.intellij.openapi.module.Module; +import com.intellij.openapi.progress.ProgressIndicator; +import com.intellij.openapi.progress.Task; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.roots.ex.ProjectRootManagerEx; +import com.intellij.openapi.ui.Messages; +import com.intellij.openapi.util.Computable; +import com.intellij.openapi.util.Pair; +import com.intellij.openapi.util.Ref; +import com.intellij.openapi.util.io.FileUtil; +import com.intellij.util.Function; +import com.intellij.util.containers.ConcurrentFactoryMap; +import com.intellij.util.containers.ContainerUtil; +import com.intellij.util.ui.UIUtil; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.io.File; +import java.util.Collection; +import java.util.Map; + +/** + * @author Vladislav.Soroka + * @since 7/17/2014 + */ +@Order(ExternalSystemConstants.BUILTIN_SERVICE_ORDER) +public class ExternalProjectDataService implements ProjectDataService { + private static final Logger LOG = Logger.getInstance(ExternalProjectDataService.class); + + @NotNull public static final Key KEY = Key.create(ExternalProject.class, ProjectKeys.TASK.getProcessingWeight() + 1); + + @NotNull private final Map, ExternalProject> myExternalRootProjects; + + @NotNull private ProjectDataManager myProjectDataManager; + + public ExternalProjectDataService(@NotNull ProjectDataManager projectDataManager) { + myProjectDataManager = projectDataManager; + myExternalRootProjects = new ConcurrentFactoryMap, ExternalProject>() { + @Nullable + @Override + protected ExternalProject create(Pair key) { + return new ExternalProjectSerializer().load(key.first, key.second); + } + + @Override + public ExternalProject put(Pair key, ExternalProject value) { + new ExternalProjectSerializer().save(value); + return super.put(key, value); + } + }; + } + + @NotNull + @Override + public Key getTargetDataKey() { + return KEY; + } + + public void importData(@NotNull final Collection> toImport, + @NotNull final Project project, + final boolean synchronous) { + if (toImport.size() != 1) { + throw new IllegalArgumentException( + String.format("Expected to get a single external project but got %d: %s", toImport.size(), toImport)); + } + saveExternalProject(toImport.iterator().next().getData()); + } + + @Override + public void removeData(@NotNull final Collection modules, @NotNull Project project, boolean synchronous) { + } + + @Nullable + public ExternalProject getOrImportRootExternalProject(@NotNull Project project, + @NotNull ProjectSystemId systemId, + @NotNull File projectRootDir) { + final ExternalProject externalProject = getRootExternalProject(systemId, projectRootDir); + return externalProject != null ? externalProject : importExternalProject(project, systemId, projectRootDir); + } + + @Nullable + private ExternalProject importExternalProject(@NotNull final Project project, + @NotNull final ProjectSystemId projectSystemId, + @NotNull final File projectRootDir) { + final Boolean result = UIUtil.invokeAndWaitIfNeeded(new Computable() { + @Override + public Boolean compute() { + final Ref result = new Ref(false); + if (project.isDisposed()) return false; + + final String linkedProjectPath = FileUtil.toCanonicalPath(projectRootDir.getPath()); + final ExternalProjectSettings projectSettings = + ExternalSystemApiUtil.getSettings(project, projectSystemId).getLinkedProjectSettings(linkedProjectPath); + if (projectSettings == null) { + LOG.warn("Unable to get project settings for project path: " + linkedProjectPath); + if (LOG.isDebugEnabled()) { + LOG.debug("Available projects paths: " + ContainerUtil.map( + ExternalSystemApiUtil.getSettings(project, projectSystemId).getLinkedProjectsSettings(), + new Function() { + @Override + public String fun(ExternalProjectSettings settings) { + return settings.getExternalProjectPath(); + } + })); + } + return false; + } + + final File projectFile = new File(linkedProjectPath); + final String projectName; + if (projectFile.isFile()) { + projectName = projectFile.getParentFile().getName(); + } + else { + projectName = projectFile.getName(); + } + + // ask a user for the project import if auto-import is disabled + if (!projectSettings.isUseAutoImport()) { + String message = String.format("Project '%s' require synchronization with %s configuration. \nImport the project?", + projectName, projectSystemId.getReadableName()); + int returnValue = Messages.showOkCancelDialog( + message, "Import Project", CommonBundle.getOkButtonText(), CommonBundle.getCancelButtonText(), Messages.getQuestionIcon() + ); + if (returnValue != Messages.OK) return false; + } + + final String title = ExternalSystemBundle.message("progress.import.text", linkedProjectPath, projectSystemId.getReadableName()); + new Task.Modal(project, title, false) { + @Override + public void run(@NotNull ProgressIndicator indicator) { + if (project.isDisposed()) return; + + ExternalSystemNotificationManager.getInstance(project) + .clearNotifications(null, NotificationSource.PROJECT_SYNC, projectSystemId); + ExternalSystemResolveProjectTask task = + new ExternalSystemResolveProjectTask(projectSystemId, project, linkedProjectPath, false); + task.execute(indicator, ExternalSystemTaskNotificationListener.EP_NAME.getExtensions()); + if (project.isDisposed()) return; + + final Throwable error = task.getError(); + if (error != null) { + ExternalSystemNotificationManager.getInstance(project) + .processExternalProjectRefreshError(error, projectName, projectSystemId); + return; + } + final DataNode projectDataDataNode = task.getExternalProject(); + if (projectDataDataNode == null) return; + + final Collection> nodes = ExternalSystemApiUtil.findAll(projectDataDataNode, KEY); + if (nodes.size() != 1) { + throw new IllegalArgumentException( + String.format("Expected to get a single external project but got %d: %s", nodes.size(), nodes)); + } + + ProjectRootManagerEx.getInstanceEx(myProject).mergeRootsChangesDuring(new Runnable() { + @Override + public void run() { + myProjectDataManager.importData(KEY, nodes, project, true); + } + }); + + result.set(true); + } + }.queue(); + + return result.get(); + } + }); + + return result ? getRootExternalProject(projectSystemId, projectRootDir) : null; + } + + @Nullable + public ExternalProject getRootExternalProject(@NotNull ProjectSystemId systemId, @NotNull File projectRootDir) { + return myExternalRootProjects.get(Pair.create(systemId, projectRootDir)); + } + + public void saveExternalProject(@NotNull ExternalProject externalProject) { + myExternalRootProjects.put( + Pair.create(new ProjectSystemId(externalProject.getExternalSystemId()), externalProject.getProjectDir()), + new DefaultExternalProject(externalProject) + ); + } + + @Nullable + public ExternalProject findExternalProject(@NotNull ExternalProject parentProject, @NotNull Module module) { + String externalProjectId = ExternalSystemApiUtil.getExternalProjectId(module); + return externalProjectId != null ? findExternalProject(parentProject, externalProjectId) : null; + } + + @Nullable + private static ExternalProject findExternalProject(@NotNull ExternalProject parentProject, @NotNull String externalProjectId) { + if (parentProject.getQName().equals(externalProjectId)) return parentProject; + if (parentProject.getChildProjects().containsKey(externalProjectId)) { + return parentProject.getChildProjects().get(externalProjectId); + } + for (ExternalProject externalProject : parentProject.getChildProjects().values()) { + final ExternalProject project = findExternalProject(externalProject, externalProjectId); + if (project != null) return project; + } + return null; + } +} diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/service/project/data/ExternalProjectSerializer.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/service/project/data/ExternalProjectSerializer.java new file mode 100644 index 000000000000..8828b9ef5880 --- /dev/null +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/service/project/data/ExternalProjectSerializer.java @@ -0,0 +1,262 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jetbrains.plugins.gradle.service.project.data; + +import com.esotericsoftware.kryo.Kryo; +import com.esotericsoftware.kryo.Serializer; +import com.esotericsoftware.kryo.io.Input; +import com.esotericsoftware.kryo.io.Output; +import com.esotericsoftware.kryo.serializers.CollectionSerializer; +import com.esotericsoftware.kryo.serializers.DefaultSerializers; +import com.esotericsoftware.kryo.serializers.FieldSerializer; +import com.esotericsoftware.kryo.serializers.MapSerializer; +import com.esotericsoftware.minlog.Log; +import com.intellij.openapi.application.PathManager; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.externalSystem.model.*; +import com.intellij.openapi.externalSystem.model.project.ExternalSystemSourceType; +import com.intellij.openapi.util.io.FileUtil; +import com.intellij.openapi.util.io.StreamUtil; +import gnu.trove.THashMap; +import gnu.trove.THashSet; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.objenesis.strategy.StdInstantiatorStrategy; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.util.*; + +/** + * @author Vladislav.Soroka + * @since 7/15/2014 + */ +public class ExternalProjectSerializer { + private static final Logger LOG = Logger.getInstance(ExternalProjectSerializer.class); + + private final Kryo myKryo; + + public ExternalProjectSerializer() { + myKryo = new Kryo() { + @Override + public T newInstance(Class type) { + LOG.error("Serializing default type: " + type); + return super.newInstance(type); + } + }; + configureKryo(); + } + + private void configureKryo() { + myKryo.setAutoReset(true); + + myKryo.setRegistrationRequired(true); + Log.set(Log.LEVEL_WARN); + + myKryo.register(ArrayList.class, new CollectionSerializer() { + @Override + protected Collection create(Kryo kryo, Input input, Class type) { + return new ArrayList(); + } + }); + myKryo.register(HashMap.class, new MapSerializer() { + @Override + protected Map create(Kryo kryo, Input input, Class type) { + return new HashMap(); + } + }); + myKryo.register(HashSet.class, new CollectionSerializer() { + @Override + protected Collection create(Kryo kryo, Input input, Class type) { + return new HashSet(); + } + }); + + myKryo.register(File.class, new FileSerializer()); + myKryo.register(DefaultExternalProject.class, new FieldSerializer(myKryo, DefaultExternalProject.class) { + @Override + protected DefaultExternalProject create(Kryo kryo, Input input, Class type) { + return new DefaultExternalProject(); + } + }); + + myKryo.register(DefaultExternalTask.class, new FieldSerializer(myKryo, DefaultExternalTask.class) { + @Override + protected DefaultExternalTask create(Kryo kryo, Input input, Class type) { + return new DefaultExternalTask(); + } + }); + + myKryo.register(DefaultExternalPlugin.class, new FieldSerializer(myKryo, DefaultExternalPlugin.class) { + @Override + protected DefaultExternalPlugin create(Kryo kryo, Input input, Class type) { + return new DefaultExternalPlugin(); + } + }); + + myKryo.register(DefaultExternalSourceSet.class, new FieldSerializer(myKryo, DefaultExternalSourceSet.class) { + @Override + protected DefaultExternalSourceSet create(Kryo kryo, Input input, Class type) { + return new DefaultExternalSourceSet(); + } + }); + + myKryo.register( + DefaultExternalSourceDirectorySet.class, + new FieldSerializer(myKryo, DefaultExternalSourceDirectorySet.class) { + @Override + protected DefaultExternalSourceDirectorySet create(Kryo kryo, Input input, Class type) { + return new DefaultExternalSourceDirectorySet(); + } + } + ); + + myKryo.register(DefaultExternalFilter.class, new FieldSerializer(myKryo, DefaultExternalFilter.class) { + @Override + protected DefaultExternalFilter create(Kryo kryo, Input input, Class type) { + return new DefaultExternalFilter(); + } + }); + + myKryo.register(ExternalSystemSourceType.class, new DefaultSerializers.EnumSerializer(ExternalSystemSourceType.class)); + + myKryo.register(LinkedHashSet.class, new CollectionSerializer() { + @Override + protected Collection create(Kryo kryo, Input input, Class type) { + return new LinkedHashSet(); + } + }); + myKryo.register(HashSet.class, new CollectionSerializer() { + @Override + protected Collection create(Kryo kryo, Input input, Class type) { + return new HashSet(); + } + }); + myKryo.register(THashSet.class, new CollectionSerializer() { + @Override + protected Collection create(Kryo kryo, Input input, Class type) { + return new THashSet(); + } + }); + myKryo.register(Set.class, new CollectionSerializer() { + @Override + protected Collection create(Kryo kryo, Input input, Class type) { + return new HashSet(); + } + }); + myKryo.register(THashMap.class, new MapSerializer() { + @Override + protected Map create(Kryo kryo, Input input, Class type) { + return new THashMap(); + } + }); + } + + + public void save(@NotNull ExternalProject externalProject) { + Output output = null; + try { + final String externalProjectPath = externalProject.getProjectDir().getPath(); + final File configurationFile = + getProjectConfigurationFile(new ProjectSystemId(externalProject.getExternalSystemId()), externalProjectPath); + if (!FileUtil.createParentDirs(configurationFile)) return; + + output = new Output(new FileOutputStream(configurationFile)); + myKryo.writeObject(output, externalProject); + } + catch (FileNotFoundException e) { + LOG.error(e); + } + finally { + StreamUtil.closeStream(output); + } + } + + @Nullable + public ExternalProject load(@NotNull ProjectSystemId externalSystemId, File externalProjectPath) { + Input input = null; + try { + final File configurationFile = getProjectConfigurationFile(externalSystemId, externalProjectPath.getPath()); + if (!configurationFile.isFile()) return null; + + input = new Input(new FileInputStream(configurationFile)); + return myKryo.readObject(input, DefaultExternalProject.class); + } + catch (Exception e) { + LOG.error(e); + } + finally { + StreamUtil.closeStream(input); + } + + return null; + } + + private static File getProjectConfigurationFile(ProjectSystemId externalSystemId, String externalProjectPath) { + return new File(getProjectConfigurationDir(externalSystemId), Integer.toHexString(externalProjectPath.hashCode()) + "/project.dat"); + } + + private static File getProjectConfigurationDir(ProjectSystemId externalSystemId) { + return getPluginSystemDir(externalSystemId, "Projects"); + } + + private static File getPluginSystemDir(ProjectSystemId externalSystemId, String folder) { + return new File(PathManager.getSystemPath(), externalSystemId.getId().toLowerCase() + "/" + folder).getAbsoluteFile(); + } + + private static class FileSerializer extends Serializer { + private final Kryo myStdKryo; + + public FileSerializer() { + myStdKryo = new Kryo(); + myStdKryo.register(File.class); + myStdKryo.setInstantiatorStrategy(new StdInstantiatorStrategy()); + } + + @Override + public void write(Kryo kryo, Output output, File object) { + myStdKryo.writeObject(output, object); + } + + @Override + public File read(Kryo kryo, Input input, Class type) { + File file = myStdKryo.readObject(input, File.class); + return new File(file.getPath()); + } + } + + private static class StdSerializer extends Serializer { + private final Kryo myStdKryo; + + public StdSerializer(Class clazz) { + myStdKryo = new Kryo(); + myStdKryo.register(clazz); + myStdKryo.setInstantiatorStrategy(new StdInstantiatorStrategy()); + } + + @Override + public void write(Kryo kryo, Output output, T object) { + myStdKryo.writeObject(output, object); + } + + @Override + public T read(Kryo kryo, Input input, Class type) { + return myStdKryo.readObject(input, type); + } + } +} diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/service/project/wizard/GradleProjectOpenProcessor.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/service/project/wizard/GradleProjectOpenProcessor.java index 213ba84271ef..10f766cc5e34 100644 --- a/plugins/gradle/src/org/jetbrains/plugins/gradle/service/project/wizard/GradleProjectOpenProcessor.java +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/service/project/wizard/GradleProjectOpenProcessor.java @@ -16,17 +16,26 @@ package org.jetbrains.plugins.gradle.service.project.wizard; import com.intellij.ide.util.newProjectWizard.AddModuleWizard; -import com.intellij.ide.util.projectWizard.ProjectBuilder; +import com.intellij.ide.util.projectWizard.ModuleWizardStep; import com.intellij.ide.util.projectWizard.WizardContext; import com.intellij.ide.wizard.Step; +import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.externalSystem.service.project.wizard.SelectExternalProjectStep; -import com.intellij.openapi.util.text.StringUtil; +import com.intellij.openapi.options.ConfigurationException; +import com.intellij.openapi.ui.Messages; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.projectImport.ProjectOpenProcessorBase; import com.intellij.util.Function; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import org.jetbrains.plugins.gradle.service.settings.GradleProjectSettingsControl; +import org.jetbrains.plugins.gradle.service.settings.GradleSystemSettingsControl; +import org.jetbrains.plugins.gradle.service.settings.ImportFromGradleControl; +import org.jetbrains.plugins.gradle.settings.DistributionType; +import org.jetbrains.plugins.gradle.settings.GradleProjectSettings; +import org.jetbrains.plugins.gradle.settings.GradleSettings; import org.jetbrains.plugins.gradle.util.GradleConstants; +import static org.jetbrains.plugins.gradle.util.GradleEnvironment.Headless.*; /** * @author Denis Zhdanov @@ -61,24 +70,90 @@ public class GradleProjectOpenProcessor extends ProjectOpenProcessorBase() { - @Override - public Boolean fun(Step step) { - return step instanceof SelectExternalProjectStep; - } - }); - boolean result = dialog.showAndGet(); + final boolean result; + if (ApplicationManager.getApplication().isHeadlessEnvironment()) { + result = setupGradleProjectSettingsInHeadlessMode(file, projectImportProvider, wizardContext); + } + else { + AddModuleWizard dialog = new AddModuleWizard(null, file.getPath(), projectImportProvider); + dialog.getWizardContext().setProjectBuilder(getBuilder()); + dialog.navigateToStep(new Function() { + @Override + public Boolean fun(Step step) { + return step instanceof SelectExternalProjectStep; + } + }); + result = dialog.showAndGet(); + } if (result && getBuilder().getExternalProjectNode() != null) { wizardContext.setProjectName(getBuilder().getExternalProjectNode().getData().getInternalName()); } return result; } + private boolean setupGradleProjectSettingsInHeadlessMode(VirtualFile file, + GradleProjectImportProvider projectImportProvider, + WizardContext wizardContext) { + final ModuleWizardStep[] wizardSteps = projectImportProvider.createSteps(wizardContext); + if (wizardSteps.length > 0 && wizardSteps[0] instanceof SelectExternalProjectStep) { + SelectExternalProjectStep selectExternalProjectStep = (SelectExternalProjectStep)wizardSteps[0]; + wizardContext.setProjectBuilder(getBuilder()); + try { + selectExternalProjectStep.updateStep(); + final ImportFromGradleControl importFromGradleControl = getBuilder().getControl(wizardContext.getProject()); + + GradleProjectSettingsControl gradleProjectSettingsControl = + (GradleProjectSettingsControl)importFromGradleControl.getProjectSettingsControl(); + + final GradleProjectSettings projectSettings = gradleProjectSettingsControl.getInitialSettings(); + + if (GRADLE_DISTRIBUTION_TYPE != null) { + for (DistributionType type : DistributionType.values()) { + if (type.name().equals(GRADLE_DISTRIBUTION_TYPE)) { + projectSettings.setDistributionType(type); + break; + } + } + } + if (GRADLE_HOME != null) { + projectSettings.setGradleHome(GRADLE_HOME); + } + gradleProjectSettingsControl.reset(); + + final GradleSystemSettingsControl systemSettingsControl = + (GradleSystemSettingsControl)importFromGradleControl.getSystemSettingsControl(); + assert systemSettingsControl != null; + final GradleSettings gradleSettings = systemSettingsControl.getInitialSettings(); + if (GRADLE_VM_OPTIONS != null) { + gradleSettings.setGradleVmOptions(GRADLE_VM_OPTIONS); + } + if (GRADLE_OFFLINE != null) { + gradleSettings.setOfflineWork(Boolean.parseBoolean(GRADLE_OFFLINE)); + } + String serviceDirectory = GRADLE_SERVICE_DIRECTORY; + if (GRADLE_SERVICE_DIRECTORY != null) { + gradleSettings.setServiceDirectoryPath(serviceDirectory); + } + systemSettingsControl.reset(); + + if (!selectExternalProjectStep.validate()) { + return false; + } + } + catch (ConfigurationException e) { + Messages.showErrorDialog(wizardContext.getProject(), e.getMessage(), e.getTitle()); + return false; + } + selectExternalProjectStep.updateDataModel(); + } + return true; + } + @Override public boolean lookForProjectsInDirectory() { return false; diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/service/settings/GradleSystemSettingsControl.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/service/settings/GradleSystemSettingsControl.java index ffdf37c74b87..ab6454c46c99 100644 --- a/plugins/gradle/src/org/jetbrains/plugins/gradle/service/settings/GradleSystemSettingsControl.java +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/service/settings/GradleSystemSettingsControl.java @@ -173,4 +173,9 @@ public class GradleSystemSettingsControl implements ExternalSystemSettingsContro public void disposeUIResources() { ExternalSystemUiUtil.disposeUi(this); } + + @NotNull + public GradleSettings getInitialSettings() { + return myInitialSettings; + } } diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/util/GradleEnvironment.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/util/GradleEnvironment.java index 2c61ac6c10eb..eb780ced4d5a 100644 --- a/plugins/gradle/src/org/jetbrains/plugins/gradle/util/GradleEnvironment.java +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/util/GradleEnvironment.java @@ -13,6 +13,18 @@ public class GradleEnvironment { @NonNls public static final boolean DISABLE_ENHANCED_TOOLING_API = Boolean.getBoolean("gradle.disable.enhanced.tooling.api"); @NonNls public static final boolean ADJUST_USER_DIR = Boolean.getBoolean("gradle.adjust.userdir"); + public static class Headless { + @NonNls public static final String GRADLE_DISTRIBUTION_TYPE = System.getProperty("idea.gradle.distributionType"); + @NonNls public static final String GRADLE_HOME = System.getProperty("idea.gradle.home"); + @NonNls public static final String GRADLE_VM_OPTIONS = System.getProperty("idea.gradle.vmOptions"); + @NonNls public static final String GRADLE_OFFLINE = System.getProperty("idea.gradle.offline"); + @NonNls public static final String GRADLE_SERVICE_DIRECTORY = System.getProperty("idea.gradle.serviceDirectory"); + + private Headless() { + } + } + + private GradleEnvironment() { } } diff --git a/plugins/gradle/testSources/org/jetbrains/plugins/gradle/compiler/GradleCompilingTestCase.java b/plugins/gradle/testSources/org/jetbrains/plugins/gradle/compiler/GradleCompilingTestCase.java new file mode 100644 index 000000000000..3b508c8cf289 --- /dev/null +++ b/plugins/gradle/testSources/org/jetbrains/plugins/gradle/compiler/GradleCompilingTestCase.java @@ -0,0 +1,89 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jetbrains.plugins.gradle.compiler; + +import com.intellij.compiler.artifacts.ArtifactsTestUtil; +import com.intellij.openapi.application.AccessToken; +import com.intellij.openapi.application.ReadAction; +import com.intellij.openapi.compiler.CompileContext; +import com.intellij.openapi.compiler.CompileTask; +import com.intellij.openapi.compiler.CompilerManager; +import com.intellij.openapi.util.io.FileUtil; +import com.intellij.packaging.impl.artifacts.ArtifactUtil; +import com.intellij.util.io.TestFileSystemItem; +import org.jetbrains.plugins.gradle.config.GradleResourceCompilerConfigurationGenerator; +import org.jetbrains.plugins.gradle.importing.GradleImportingTestCase; + +import java.io.File; + +/** + * @author Vladislav.Soroka + * @since 7/21/2014 + */ +public abstract class GradleCompilingTestCase extends GradleImportingTestCase { + @Override + protected void setUpInWriteAction() throws Exception { + super.setUpInWriteAction(); + CompilerManager.getInstance(myProject).addBeforeTask(new CompileTask() { + @Override + public boolean execute(CompileContext context) { + AccessToken token = ReadAction.start(); + try { + new GradleResourceCompilerConfigurationGenerator(myProject, context).generateBuildConfiguration(); + } + finally { + token.finish(); + } + return true; + } + }); + } + + protected void assertCopied(String path) { + assertTrue(new File(myProjectConfig.getParent().getPath(), path).exists()); + } + + protected void assertCopied(String path, String content) { + assertCopied(path); + assertSameLinesWithFile(new File(myProjectConfig.getParent().getPath(), path).getPath(), content); + } + + protected void assertNotCopied(String path) { + assertFalse(new File(myProjectConfig.getParent().getPath(), path).exists()); + } + + @Override + protected void assertArtifactOutputPath(String artifactName, String expected) { + final String defaultArtifactOutputPath = ArtifactUtil.getDefaultArtifactOutputPath(artifactName, myProject); + assert defaultArtifactOutputPath != null; + final String basePath = FileUtil.toSystemIndependentName(new File(defaultArtifactOutputPath).getParent()); + super.assertArtifactOutputPath(artifactName, basePath + expected); + } + + protected void assertArtifactOutputFile(String artifactName, String path, String content) { + final String defaultArtifactOutputPath = ArtifactUtil.getDefaultArtifactOutputPath(artifactName, myProject); + assert defaultArtifactOutputPath != null; + final String basePath = FileUtil.toSystemIndependentName(new File(defaultArtifactOutputPath).getParent()); + assertSameLinesWithFile(basePath + path, content); + } + + protected void assertArtifactOutputFile(String artifactName, String path) { + final String defaultArtifactOutputPath = ArtifactUtil.getDefaultArtifactOutputPath(artifactName, myProject); + assert defaultArtifactOutputPath != null; + final String basePath = FileUtil.toSystemIndependentName(new File(defaultArtifactOutputPath).getParent()); + assertExists(new File(basePath + path)); + } +} diff --git a/plugins/gradle/testSources/org/jetbrains/plugins/gradle/compiler/GradleResourceFilteringTest.java b/plugins/gradle/testSources/org/jetbrains/plugins/gradle/compiler/GradleResourceFilteringTest.java new file mode 100644 index 000000000000..df9c6a879ac7 --- /dev/null +++ b/plugins/gradle/testSources/org/jetbrains/plugins/gradle/compiler/GradleResourceFilteringTest.java @@ -0,0 +1,132 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jetbrains.plugins.gradle.compiler; + +import org.junit.Test; + +/** + * @author Vladislav.Soroka + * @since 7/21/2014 + */ +@SuppressWarnings("JUnit4AnnotatedMethodInJUnit3TestCase") +public class GradleResourceFilteringTest extends GradleCompilingTestCase { + + @Test + public void testHeadFilter() throws Exception { + createProjectSubFile( + "src/main/resources/dir/file.txt", "1 Header\n" + + "2\n" + + "3 another text\n" + + "4\n" + + "5 another text \n" + + "6 another text @token@ another text\n" + + "7\n" + + "8 Footer"); + importProject( + "apply plugin: 'java'\n" + + "\n" + + "import org.apache.tools.ant.filters.*\n" + + "processResources {\n" + + " filter(HeadFilter, lines:3, skip:2)\n" + + "}" + ); + assertModules("project"); + compileModules("project"); + + assertCopied("build/resources/main/dir/file.txt", "3 another text\n" + + "4\n" + + "5 another text \n"); + } + + @Test + public void testReplaceTokensFilter() throws Exception { + createProjectSubFile( + "src/main/resources/dir/file.txt", "1 Header\n" + + "2\n" + + "3 #token1#another text\n" + + "4\n" + + "5 another text \n" + + "6 another text #token2# another text\n" + + "7\n" + + "8 Footer"); + importProject( + "apply plugin: 'java'\n" + + "\n" + + "import org.apache.tools.ant.filters.*\n" + + "processResources {\n" + + " filter(ReplaceTokens, tokens:[token1:'<11111>', token2:'<2222>'], beginToken: '#', endToken: '#')\n" + + "}" + ); + assertModules("project"); + compileModules("project"); + + assertCopied("build/resources/main/dir/file.txt", "1 Header\n" + + "2\n" + + "3 <11111>another text\n" + + "4\n" + + "5 another text \n" + + "6 another text <2222> another text\n" + + "7\n" + + "8 Footer"); + } + + @Test + public void testRenameFilter() throws Exception { + createProjectSubFile("src/main/resources/dir/file.txt"); + importProject( + "apply plugin: 'java'\n" + + "\n" + + "import org.apache.tools.ant.filters.*\n" + + "processResources {\n" + + " rename 'file.txt', 'file001.txt'\n" + + "}" + ); + assertModules("project"); + compileModules("project"); + + assertCopied("build/resources/main/dir/file001.txt"); + } + + @Test + public void testFiltersChain() throws Exception { + createProjectSubFile( + "src/main/resources/dir/file.txt", "1 Header\n" + + "2\n" + + "3 another text@token1@\n" + + "4\n" + + "5 another text \n" + + "6 another text @token2@ another text\n" + + "7\n" + + "8 Footer"); + importProject( + "apply plugin: 'java'\n" + + "\n" + + "import org.apache.tools.ant.filters.*\n" + + "processResources {\n" + + " filter(HeadFilter, lines:4, skip:2)\n" + + " filter(ReplaceTokens, tokens:[token1:'<11111>', token2:'<2222>'])\n" + + " rename 'file.txt', 'file001.txt'\n" + + "}" + ); + assertModules("project"); + compileModules("project"); + + assertCopied("build/resources/main/dir/file001.txt", "3 another text<11111>\n" + + "4\n" + + "5 another text \n" + + "6 another text <2222> another text"); + } +} \ No newline at end of file diff --git a/plugins/gradle/testSources/org/jetbrains/plugins/gradle/compiler/GradleResourceProcessingTest.java b/plugins/gradle/testSources/org/jetbrains/plugins/gradle/compiler/GradleResourceProcessingTest.java new file mode 100644 index 000000000000..873234425e30 --- /dev/null +++ b/plugins/gradle/testSources/org/jetbrains/plugins/gradle/compiler/GradleResourceProcessingTest.java @@ -0,0 +1,176 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jetbrains.plugins.gradle.compiler; + +import org.junit.Test; + +import java.io.IOException; + +/** + * @author Vladislav.Soroka + * @since 7/21/2014 + */ +@SuppressWarnings("JUnit4AnnotatedMethodInJUnit3TestCase") +public class GradleResourceProcessingTest extends GradleCompilingTestCase { + + @Test + public void testBasicResourceCopying() throws Exception { + createProjectSubFile("src/main/resources/dir/file.properties"); + createProjectSubFile("src/test/resources/dir/file-test.properties"); + importProject( + "apply plugin: 'java'" + ); + assertModules("project"); + compileModules("project"); + + assertCopied("build/resources/main/dir/file.properties"); + assertCopied("build/resources/test/dir/file-test.properties"); + } + + @Test + public void testResourceProcessingWithIdeaGradlePluginCustomization() throws Exception { + createProjectSubFile("src/main/resources/dir/file.properties"); + createProjectSubFile("src/test/resources/dir/file-test.properties"); + importProject( + "apply plugin: 'java'\n" + + "apply plugin: 'idea'\n" + + "idea {\n" + + " module {\n" + + " inheritOutputDirs = false\n" + + " outputDir = file('muchBetterOutputDir')\n" + + " testOutputDir = file('muchBetterTestOutputDir')\n" + + " }\n" + + "}" + ); + assertModules("project"); + compileModules("project"); + + assertCopied("muchBetterOutputDir/dir/file.properties"); + assertCopied("muchBetterTestOutputDir/dir/file-test.properties"); + } + + @Test + public void testIncludesAndExcludesInSourceSets() throws Exception { + createFilesForIncludesAndExcludesTest(); + + importProject( + "apply plugin: 'java'\n" + + "\n" + + "sourceSets {\n" + + " main {\n" + + " resources {\n" + + " include '**/*.yyy'\n" + + " include '**/*.xxx'\n" + + " exclude 'dir/*.yyy'\n" + + " exclude '*.xxx'\n" + + " }\n" + + " }\n" + + " test {\n" + + " resources {\n" + + " include '**/*.yyy'\n" + + " include '**/*.xxx'\n" + + " exclude 'dir/*.yyy'\n" + + " exclude '*.xxx'\n" + + " }\n" + + " }\n" + + "}" + ); + assertModules("project"); + compileModules("project"); + + assertCopiedResources(); + } + + @Test + public void testIncludesAndExcludesInAllSourceSets() throws Exception { + createFilesForIncludesAndExcludesTest(); + + importProject( + "apply plugin: 'java'\n" + + "\n" + + "sourceSets.all {\n" + + " resources {\n" + + " include '**/*.yyy'\n" + + " include '**/*.xxx'\n" + + " exclude 'dir/*.yyy'\n" + + " exclude '*.xxx'\n" + + " }\n" + + "}" + ); + assertModules("project"); + compileModules("project"); + + assertCopiedResources(); + } + + + @Test + public void testIncludesAndExcludesInResourcesTask() throws Exception { + createFilesForIncludesAndExcludesTest(); + + importProject( + "apply plugin: 'java'\n" + + "\n" + + "processResources {\n" + + " include '**/*.yyy'\n" + + " include '**/*.xxx'\n" + + " exclude 'dir/*.yyy'\n" + + " exclude '*.xxx'\n" + + "}\n" + + "\n" + + "processTestResources {\n" + + " include '**/*.yyy'\n" + + " include '**/*.xxx'\n" + + " exclude 'dir/*.yyy'\n" + + " exclude '*.xxx'\n" + + "}\n" + ); + assertModules("project"); + compileModules("project"); + + assertCopiedResources(); + } + + private void createFilesForIncludesAndExcludesTest() throws IOException { + createProjectSubFile("src/main/resources/dir/file.xxx"); + createProjectSubFile("src/main/resources/dir/file.yyy"); + createProjectSubFile("src/main/resources/file.xxx"); + createProjectSubFile("src/main/resources/file.yyy"); + createProjectSubFile("src/main/resources/file.zzz"); + + createProjectSubFile("src/test/resources/dir/file.xxx"); + createProjectSubFile("src/test/resources/dir/file.yyy"); + createProjectSubFile("src/test/resources/file.xxx"); + createProjectSubFile("src/test/resources/file.yyy"); + createProjectSubFile("src/test/resources/file.zzz"); + } + + private void assertCopiedResources() { + // assert production resources + assertCopied("build/resources/main/dir/file.xxx"); + assertCopied("build/resources/main/file.yyy"); + assertNotCopied("build/resources/main/dir/file.yyy"); + assertNotCopied("build/resources/main/file.xxx"); + assertNotCopied("build/resources/main/file.zzz"); + + // assert test resources + assertCopied("build/resources/test/dir/file.xxx"); + assertCopied("build/resources/test/file.yyy"); + assertNotCopied("build/resources/test/dir/file.yyy"); + assertNotCopied("build/resources/test/file.xxx"); + assertNotCopied("build/resources/test/file.zzz"); + } +} diff --git a/plugins/gradle/tooling-extension-api/src/org/jetbrains/plugins/gradle/model/ModuleExtendedModel.java b/plugins/gradle/tooling-extension-api/src/org/jetbrains/plugins/gradle/model/ModuleExtendedModel.java index 97dfd2547b73..91889d85fa9f 100644 --- a/plugins/gradle/tooling-extension-api/src/org/jetbrains/plugins/gradle/model/ModuleExtendedModel.java +++ b/plugins/gradle/tooling-extension-api/src/org/jetbrains/plugins/gradle/model/ModuleExtendedModel.java @@ -22,9 +22,12 @@ import java.io.Serializable; import java.util.List; /** + * @deprecated Use {@link com.intellij.openapi.externalSystem.model.ExternalProject} model instead. + * * @author Vladislav.Soroka * @since 11/5/13 */ +@Deprecated() public interface ModuleExtendedModel extends Serializable { /** * The group of the module. diff --git a/plugins/gradle/tooling-extension-api/src/org/jetbrains/plugins/gradle/model/ProjectImportAction.java b/plugins/gradle/tooling-extension-api/src/org/jetbrains/plugins/gradle/model/ProjectImportAction.java index af6f4fed60f4..c27a9a216c36 100644 --- a/plugins/gradle/tooling-extension-api/src/org/jetbrains/plugins/gradle/model/ProjectImportAction.java +++ b/plugins/gradle/tooling-extension-api/src/org/jetbrains/plugins/gradle/model/ProjectImportAction.java @@ -18,6 +18,7 @@ package org.jetbrains.plugins.gradle.model; import com.intellij.openapi.externalSystem.model.ExternalSystemException; import org.gradle.tooling.BuildAction; import org.gradle.tooling.BuildController; +import org.gradle.tooling.model.Model; import org.gradle.tooling.model.build.BuildEnvironment; import org.gradle.tooling.model.idea.BasicIdeaProject; import org.gradle.tooling.model.idea.IdeaModule; @@ -48,8 +49,7 @@ public class ProjectImportAction implements BuildAction aClass1 = myIsPreviewMode ? BasicIdeaProject.class : IdeaProject.class; - final IdeaProject ideaProject = controller.getModel(aClass1); + final IdeaProject ideaProject = controller.getModel(myIsPreviewMode ? BasicIdeaProject.class : IdeaProject.class); if (ideaProject == null || ideaProject.getModules().isEmpty()) { return null; } @@ -59,25 +59,30 @@ public class ProjectImportAction implements BuildAction projectsByPath = new HashMap(); @NotNull private final IdeaProject myIdeaProject; @@ -101,9 +106,13 @@ public class ProjectImportAction implements BuildAction T getExtraProject(Class modelClazz) { + return getExtraProject(null, modelClazz); + } @Nullable - public T getExtraProject(@NotNull IdeaModule module, Class modelClazz) { + public T getExtraProject(@Nullable IdeaModule module, Class modelClazz) { Object extraProject = projectsByPath.get(extractMapKey(modelClazz, module)); if (modelClazz.isInstance(extraProject)) { //noinspection unchecked @@ -129,13 +138,17 @@ public class ProjectImportAction implements BuildAction" + "" + myProject + ((myDescription != null) ? ": " + myDescription : "") + "" + - "\nDetails: " + getErrorMessage(myException) + + (myException != null ? "\nDetails: " + getErrorMessage(myException) : "") + "" ).replaceAll("\r\n|\n\r|\n|\r", EOL_TAG) ); diff --git a/plugins/gradle/tooling-extension-impl/gradle-tooling-extension-impl.iml b/plugins/gradle/tooling-extension-impl/gradle-tooling-extension-impl.iml index 1bcde5e35b79..ea579287b3e8 100644 --- a/plugins/gradle/tooling-extension-impl/gradle-tooling-extension-impl.iml +++ b/plugins/gradle/tooling-extension-impl/gradle-tooling-extension-impl.iml @@ -67,6 +67,7 @@ + diff --git a/plugins/gradle/tooling-extension-impl/src/META-INF/services/org.jetbrains.plugins.gradle.tooling.ModelBuilderService b/plugins/gradle/tooling-extension-impl/src/META-INF/services/org.jetbrains.plugins.gradle.tooling.ModelBuilderService index 244e58d2128e..816ef05c1e4d 100644 --- a/plugins/gradle/tooling-extension-impl/src/META-INF/services/org.jetbrains.plugins.gradle.tooling.ModelBuilderService +++ b/plugins/gradle/tooling-extension-impl/src/META-INF/services/org.jetbrains.plugins.gradle.tooling.ModelBuilderService @@ -17,3 +17,4 @@ org.jetbrains.plugins.gradle.tooling.builder.WarModelBuilderImpl org.jetbrains.plugins.gradle.tooling.builder.ModuleExtendedModelBuilderImpl org.jetbrains.plugins.gradle.tooling.builder.ModelBuildScriptClasspathBuilderImpl org.jetbrains.plugins.gradle.tooling.builder.ScalaModelBuilderImpl +org.jetbrains.plugins.gradle.tooling.builder.ExternalProjectBuilderImpl diff --git a/plugins/gradle/tooling-extension-impl/src/org/jetbrains/plugins/gradle/tooling/builder/ExternalProjectBuilderImpl.groovy b/plugins/gradle/tooling-extension-impl/src/org/jetbrains/plugins/gradle/tooling/builder/ExternalProjectBuilderImpl.groovy new file mode 100644 index 000000000000..3bb6b471b755 --- /dev/null +++ b/plugins/gradle/tooling-extension-impl/src/org/jetbrains/plugins/gradle/tooling/builder/ExternalProjectBuilderImpl.groovy @@ -0,0 +1,231 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License") + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jetbrains.plugins.gradle.tooling.builder + +import com.google.gson.GsonBuilder +import com.intellij.openapi.externalSystem.model.* +import com.intellij.openapi.externalSystem.model.project.ExternalSystemSourceType +import org.gradle.api.Action +import org.gradle.api.Project +import org.gradle.api.Task +import org.gradle.api.file.ContentFilterable +import org.gradle.api.file.FileCopyDetails +import org.gradle.api.tasks.SourceSet +import org.gradle.api.tasks.SourceSetContainer +import org.gradle.api.tasks.util.PatternFilterable +import org.jetbrains.annotations.NotNull +import org.jetbrains.annotations.Nullable +import org.jetbrains.plugins.gradle.tooling.ErrorMessageBuilder +import org.jetbrains.plugins.gradle.tooling.ModelBuilderService + +import java.util.concurrent.ConcurrentHashMap + +/** + * @author Vladislav.Soroka + * @since 12/20/13 + */ +class ExternalProjectBuilderImpl implements ModelBuilderService { + + private final cache = new ConcurrentHashMap() + + @Override + public boolean canBuild(String modelName) { + return ExternalProject.name.equals(modelName) + } + + @Nullable + @Override + public Object buildAll(final String modelName, final Project project) { + ExternalProject externalProject = cache[project.path] + if (externalProject != null) return externalProject + + DefaultExternalProject defaultExternalProject = new DefaultExternalProject() + defaultExternalProject.externalSystemId = "GRADLE" + defaultExternalProject.name = project.name + defaultExternalProject.QName = ":".equals(project.path) ? project.name : project.path + defaultExternalProject.version = wrap(project.version) + defaultExternalProject.description = project.description + defaultExternalProject.buildDir = project.buildDir + defaultExternalProject.buildFile = project.buildFile + defaultExternalProject.group = wrap(project.group) + defaultExternalProject.projectDir = project.projectDir + defaultExternalProject.sourceSets = getSourceSets(project) + defaultExternalProject.tasks = getTasks(project) + + defaultExternalProject.plugins = getPlugins(project) + //defaultExternalProject.setProperties(project.getProperties()) + + + final Map childProjects = new HashMap(project.getChildProjects().size()) + for (Map.Entry projectEntry : project.getChildProjects().entrySet()) { + final Object externalProjectChild = buildAll(modelName, projectEntry.getValue()) + if (externalProjectChild instanceof ExternalProject) { + childProjects.put(projectEntry.getKey(), (ExternalProject)externalProjectChild) + } + } + defaultExternalProject.setChildProjects(childProjects) + cache.put(project.getPath(), defaultExternalProject) + + defaultExternalProject + } + + static Map getPlugins(Project project) { + def result = [:] as Map + project.convention.plugins.each { key, value -> + ExternalPlugin externalPlugin = new DefaultExternalPlugin() + externalPlugin.id = key + result.put(key, externalPlugin) + } + + result + } + + static Map getTasks(Project project) { + def result = [:] as Map + + project.tasks.all { Task task -> + ExternalTask externalTask = new DefaultExternalTask() + externalTask.name = task.name + externalTask.description = task.description + externalTask.group = task.group + externalTask.QName = task.path + result.put(externalTask.QName, externalTask) + } + + result + } + + static Map getSourceSets(Project project) { + def result = [:] as Map + if (!project.hasProperty("sourceSets") || !(project.sourceSets instanceof SourceSetContainer)) { + return result + } + def sourceSets = project.sourceSets as SourceSetContainer + + def (resourcesIncludes, resourcesExcludes, filterReaders) = getFilters(project, 'processResources') + def (testResourcesIncludes, testResourcesExcludes, testFilterReaders) = getFilters(project, 'processTestResources') + //def (javaIncludes,javaExcludes) = getFilters(project,'compileJava') + + sourceSets.all { SourceSet sourceSet -> + ExternalSourceSet externalSourceSet = new DefaultExternalSourceSet() + externalSourceSet.name = sourceSet.name + + def sources = [:] as Map + ExternalSourceDirectorySet resourcesDirectorySet = new DefaultExternalSourceDirectorySet() + resourcesDirectorySet.name = sourceSet.resources.name + resourcesDirectorySet.srcDirs = sourceSet.resources.srcDirs + resourcesDirectorySet.outputDir = sourceSet.output.resourcesDir + + ExternalSourceDirectorySet javaDirectorySet = new DefaultExternalSourceDirectorySet() + javaDirectorySet.name = sourceSet.allJava.name + javaDirectorySet.srcDirs = sourceSet.allJava.srcDirs + javaDirectorySet.outputDir = sourceSet.output.classesDir +// javaDirectorySet.excludes = javaExcludes + sourceSet.java.excludes; +// javaDirectorySet.includes = javaIncludes + sourceSet.java.includes; + + if (SourceSet.TEST_SOURCE_SET_NAME.equals(sourceSet.name)) { + resourcesDirectorySet.excludes = testResourcesExcludes + sourceSet.resources.excludes; + resourcesDirectorySet.includes = testResourcesIncludes + sourceSet.resources.includes; + resourcesDirectorySet.filters = testFilterReaders + sources.put(ExternalSystemSourceType.TEST, javaDirectorySet) + sources.put(ExternalSystemSourceType.TEST_RESOURCE, resourcesDirectorySet) + } + else { + resourcesDirectorySet.excludes = resourcesExcludes + sourceSet.resources.excludes; + resourcesDirectorySet.includes = resourcesIncludes + sourceSet.resources.includes; + resourcesDirectorySet.filters = filterReaders + sources.put(ExternalSystemSourceType.SOURCE, javaDirectorySet) + sources.put(ExternalSystemSourceType.RESOURCE, resourcesDirectorySet) + } + + externalSourceSet.sources = sources + result[sourceSet.name] = externalSourceSet + } + result + } + + static getFilters(Project project, String taskName) { + def includes = [] + def excludes = [] + def filterReaders = [] as List + def filterableTask = project.tasks.findByName(taskName) + if (filterableTask instanceof PatternFilterable) { + includes += filterableTask.includes + excludes += filterableTask.excludes + } + + if(System.getProperty('idea.disable.gradle.resource.filtering', 'false').toBoolean()) { + return [includes, excludes, filterReaders] + } + + try { + if (filterableTask instanceof ContentFilterable && filterableTask.metaClass.respondsTo(filterableTask, "getMainSpec")) { + def properties = filterableTask.getMainSpec().properties + def copyActions = properties?.allCopyActions ?: properties?.copyActions + + if(copyActions) { + copyActions.each { Action action -> + if (action.hasProperty('val$filterType') && action.hasProperty('val$properties')) { + def filterType = (action?.val$filterType as Class).name + def filter = [filterType: filterType] as DefaultExternalFilter + def props = action?.val$properties + if (props) { + filter.propertiesAsJsonMap = new GsonBuilder().create().toJson(props); + } + filterReaders << filter + } + else if (action.class.simpleName.equals('RenamingCopyAction') && action.hasProperty('transformer')) { + if (action.transformer.hasProperty('matcher') && action?.transformer.hasProperty('replacement')) { + String pattern = action?.transformer?.matcher.pattern().pattern + String replacement = action?.transformer?.replacement + def filter = [filterType: 'RenamingCopyFilter'] as DefaultExternalFilter + if(pattern && replacement){ + filter.propertiesAsJsonMap = new GsonBuilder().create().toJson([pattern: pattern, replacement: replacement]); + filterReaders << filter + } + } + } +// else { +// project.logger.error( +// ErrorMessageBuilder.create(project, "Resource configuration errors") +// .withDescription("Unsupported copy action found: " + action.class.name).build()) +// } + } + } + } + } + catch (Exception ignore) { +// project.logger.error( +// ErrorMessageBuilder.create(project, e, "Resource configuration errors") +// .withDescription("Unable to resolve resources filtering configuration").build()) + } + + return [includes, excludes, filterReaders] + } + + + private static String wrap(Object o) { + return o instanceof CharSequence ? o.toString() : "" + } + + @NotNull + @Override + public ErrorMessageBuilder getErrorMessageBuilder(@NotNull Project project, @NotNull Exception e) { + return ErrorMessageBuilder.create( + project, e, "Project resolve errors" + ).withDescription("Unable to resolve additional project configuration.") + } +} diff --git a/plugins/gradle/tooling-extension-impl/testSources/org/jetbrains/plugins/gradle/tooling/builder/AbstractModelBuilderTest.java b/plugins/gradle/tooling-extension-impl/testSources/org/jetbrains/plugins/gradle/tooling/builder/AbstractModelBuilderTest.java index d09a9b7c5e3c..faf3a98bc3a4 100644 --- a/plugins/gradle/tooling-extension-impl/testSources/org/jetbrains/plugins/gradle/tooling/builder/AbstractModelBuilderTest.java +++ b/plugins/gradle/tooling-extension-impl/testSources/org/jetbrains/plugins/gradle/tooling/builder/AbstractModelBuilderTest.java @@ -15,6 +15,7 @@ */ package org.jetbrains.plugins.gradle.tooling.builder; +import com.intellij.openapi.externalSystem.model.ExternalProject; import com.intellij.openapi.util.Pair; import com.intellij.openapi.util.io.FileUtil; import com.intellij.testFramework.UsefulTestCase; @@ -127,7 +128,7 @@ public abstract class AbstractModelBuilderTest { BuildActionExecuter buildActionExecutor = connection.action(projectImportAction); File initScript = GradleExecutionHelper.generateInitScript(false, getToolingExtensionClasses()); assertNotNull(initScript); - buildActionExecutor.withArguments(GradleConstants.INIT_SCRIPT_CMD_OPTION, initScript.getAbsolutePath()); + buildActionExecutor.withArguments("--recompile-scripts", GradleConstants.INIT_SCRIPT_CMD_OPTION, initScript.getAbsolutePath()); allModels = buildActionExecutor.run(); assertNotNull(allModels); } @@ -135,6 +136,7 @@ public abstract class AbstractModelBuilderTest { @NotNull private Set getToolingExtensionClasses() { final Set classes = ContainerUtil.set( + ExternalProject.class, // gradle-tooling-extension-api jar ProjectImportAction.class, // gradle-tooling-extension-impl jar diff --git a/plugins/groovy/test/org/jetbrains/plugins/groovy/GrFindManagerTest.java b/plugins/groovy/test/org/jetbrains/plugins/groovy/GrFindManagerTest.java index c2a62a7b2a9c..6112a2151a7e 100644 --- a/plugins/groovy/test/org/jetbrains/plugins/groovy/GrFindManagerTest.java +++ b/plugins/groovy/test/org/jetbrains/plugins/groovy/GrFindManagerTest.java @@ -44,7 +44,8 @@ public class GrFindManagerTest extends DaemonAnalyzerTestCase { public void testFindInLiteralToSkipQuotes() { FindModel findModel = FindManagerTestUtils.configureFindModel("^done$"); findModel.setRegularExpressions(true); - findModel.setSearchContext(FindModel.SearchContext.IN_STRINGS); + findModel.setInStringLiteralsOnly(true); + findModel.setInCommentsOnly(false); String text = "def n = \"\"\"done\"\"\"\n def n = /done/\n def n = \"done\"\n def n = \"done2\""; FindManagerTestUtils.runFindForwardAndBackward(myFindManager, findModel, text, "groovy"); } @@ -52,7 +53,7 @@ public class GrFindManagerTest extends DaemonAnalyzerTestCase { public void testFindInShellCommentsOfGroovy() { FindModel findModel = FindManagerTestUtils.configureFindModel("done"); findModel.setWholeWordsOnly(true); - findModel.setSearchContext(FindModel.SearchContext.IN_COMMENTS); + findModel.setInCommentsOnly(true); String text = "#! done done done\n"; FindManagerTestUtils.runFindForwardAndBackward(myFindManager, findModel, text, "groovy"); } diff --git a/plugins/hg4idea/src/org/zmlx/hg4idea/action/HgLogSingleCommitAction.java b/plugins/hg4idea/src/org/zmlx/hg4idea/action/HgLogSingleCommitAction.java index 415e79be5a3b..e0bde5df924a 100644 --- a/plugins/hg4idea/src/org/zmlx/hg4idea/action/HgLogSingleCommitAction.java +++ b/plugins/hg4idea/src/org/zmlx/hg4idea/action/HgLogSingleCommitAction.java @@ -16,20 +16,41 @@ package org.zmlx.hg4idea.action; -import com.intellij.dvcs.ui.VcsLogSingleCommitAction; +import com.intellij.dvcs.ui.VcsLogAction; import com.intellij.openapi.components.ServiceManager; import com.intellij.openapi.project.Project; import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.vcs.log.VcsFullCommitDetails; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.zmlx.hg4idea.repo.HgRepository; import org.zmlx.hg4idea.repo.HgRepositoryManager; -public abstract class HgLogSingleCommitAction extends VcsLogSingleCommitAction { +import java.util.Map; + +public abstract class HgLogSingleCommitAction extends VcsLogAction { @Nullable @Override protected HgRepository getRepositoryForRoot(@NotNull Project project, @NotNull VirtualFile root) { return ServiceManager.getService(project, HgRepositoryManager.class).getRepositoryForRoot(root); } + + @NotNull + @Override + protected Mode getMode() { + return Mode.SINGLE_COMMIT; + } + + @Override + protected void actionPerformed(@NotNull Project project, @NotNull Map commits) { + assert commits.size() == 1; + Map.Entry entry = commits.entrySet().iterator().next(); + HgRepository repository = entry.getKey(); + VcsFullCommitDetails commit = entry.getValue(); + actionPerformed(repository, commit); + } + + protected abstract void actionPerformed(@NotNull HgRepository repository, @NotNull VcsFullCommitDetails commit); + } diff --git a/plugins/hg4idea/src/org/zmlx/hg4idea/provider/HgDiffFromHistoryHandler.java b/plugins/hg4idea/src/org/zmlx/hg4idea/provider/HgDiffFromHistoryHandler.java index 61d08bc94e8c..1d556a8b19cb 100644 --- a/plugins/hg4idea/src/org/zmlx/hg4idea/provider/HgDiffFromHistoryHandler.java +++ b/plugins/hg4idea/src/org/zmlx/hg4idea/provider/HgDiffFromHistoryHandler.java @@ -15,22 +15,13 @@ */ package org.zmlx.hg4idea.provider; -import com.intellij.openapi.actionSystem.AnActionEvent; import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.progress.ProgressIndicator; -import com.intellij.openapi.progress.Task; import com.intellij.openapi.project.Project; -import com.intellij.openapi.ui.DialogBuilder; -import com.intellij.openapi.util.Couple; import com.intellij.openapi.vcs.FilePath; +import com.intellij.openapi.vcs.VcsException; import com.intellij.openapi.vcs.changes.Change; -import com.intellij.openapi.vcs.changes.ui.ChangesBrowser; -import com.intellij.openapi.vcs.history.CurrentRevision; -import com.intellij.openapi.vcs.history.DiffFromHistoryHandler; -import com.intellij.openapi.vcs.history.VcsFileRevision; -import com.intellij.openapi.vcs.history.VcsHistoryUtil; +import com.intellij.openapi.vcs.history.BaseDiffFromHistoryHandler; import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.util.Consumer; import com.intellij.vcsUtil.VcsUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -44,111 +35,37 @@ import java.util.List; * * @author Nadya Zabrodina */ -public class HgDiffFromHistoryHandler implements DiffFromHistoryHandler { +public class HgDiffFromHistoryHandler extends BaseDiffFromHistoryHandler { private static final Logger LOG = Logger.getInstance(HgDiffFromHistoryHandler.class); - @NotNull private final Project myProject; - public HgDiffFromHistoryHandler(@NotNull Project project) { - myProject = project; + super(project); } + @NotNull @Override - public void showDiffForOne(@NotNull AnActionEvent e, @NotNull FilePath filePath, - @NotNull VcsFileRevision previousRevision, @NotNull VcsFileRevision revision) { - doShowDiff(filePath, previousRevision, revision, false); + protected List getChangesBetweenRevisions(@NotNull FilePath path, @NotNull HgFileRevision rev1, @Nullable HgFileRevision rev2) { + return executeDiff(path, rev1, rev2); } - + @NotNull @Override - public void showDiffForTwo(@NotNull FilePath filePath, @NotNull VcsFileRevision revision1, @NotNull VcsFileRevision revision2) { - doShowDiff(filePath, revision1, revision2, true); + protected List getAffectedChanges(@NotNull FilePath path, @NotNull HgFileRevision rev) throws VcsException { + return executeDiff(path, null, rev); } - private void doShowDiff(@NotNull FilePath filePath, @NotNull VcsFileRevision revision1, @NotNull VcsFileRevision revision2, - boolean autoSort) { - if (!filePath.isDirectory()) { - VcsHistoryUtil.showDifferencesInBackground(myProject, filePath, revision1, revision2, autoSort); - } - else if (revision2 instanceof CurrentRevision) { - HgFileRevision left = (HgFileRevision)revision1; - showDiffForDirectory(filePath, left, null); - } - else if (revision1.equals(VcsFileRevision.NULL)) { - HgFileRevision right = (HgFileRevision)revision2; - showDiffForDirectory(filePath, null, right); - } - else { - HgFileRevision left = (HgFileRevision)revision1; - HgFileRevision right = (HgFileRevision)revision2; - if (autoSort) { - Couple pair = VcsHistoryUtil.sortRevisions(revision1, revision2); - left = (HgFileRevision)pair.first; - right = (HgFileRevision)pair.second; - } - showDiffForDirectory(filePath, left, right); - } + @NotNull + @Override + protected String getPresentableName(@NotNull HgFileRevision revision) { + return revision.getRevisionNumber().getChangeset(); } - private void showDiffForDirectory(@NotNull final FilePath path, - @Nullable final HgFileRevision rev1, - @Nullable final HgFileRevision rev2) { + @NotNull + private List executeDiff(@NotNull FilePath path, @Nullable HgFileRevision rev1, @Nullable HgFileRevision rev2) { VirtualFile root = VcsUtil.getVcsRootFor(myProject, path); LOG.assertTrue(root != null, "Repository is null for " + path); - calculateDiffInBackground(root, path, rev1, rev2, new Consumer>() { - @Override - public void consume(List changes) { - showDirDiffDialog(path, rev1 != null ? rev1.getRevisionNumber().getChangeset() : null, - rev2 != null ? rev2.getRevisionNumber().getChangeset() : null, changes); - } - }); + + return HgUtil.getDiff(myProject, root, path, rev1, rev2); } - - // rev1 == null => rev2 is the initial commit - // rev2 == null => comparing rev1 with local - private void calculateDiffInBackground(@NotNull final VirtualFile root, @NotNull final FilePath path, - @Nullable final HgFileRevision rev1, @Nullable final HgFileRevision rev2, - final Consumer> successHandler) { - new Task.Backgroundable(myProject, "Comparing revisions...") { - private List myChanges; - - @Override - public void run(@NotNull ProgressIndicator indicator) { - if (myProject != null) { - myChanges = HgUtil.getDiff(HgDiffFromHistoryHandler.this.myProject, root, path, rev1, rev2); - } - } - - @Override - public void onSuccess() { - successHandler.consume(myChanges); - } - }.queue(); - } - - private void showDirDiffDialog(@NotNull FilePath path, @Nullable String hash1, @Nullable String hash2, @NotNull List diff) { - DialogBuilder dialogBuilder = new DialogBuilder(myProject); - String title; - if (hash2 != null) { - if (hash1 != null) { - title = String.format("Difference between %s and %s in %s", hash1, hash2, path.getName()); - } - else { - title = String.format("Initial commit %s in %s", hash2, path.getName()); - } - } - else { - LOG.assertTrue(hash1 != null, "hash1 and hash2 can't both be null. Path: " + path); - title = String.format("Difference between %s and local version in %s", hash1, path.getName()); - } - dialogBuilder.setTitle(title); - dialogBuilder.setActionDescriptors(new DialogBuilder.ActionDescriptor[]{new DialogBuilder.CloseDialogAction()}); - final ChangesBrowser changesBrowser = new ChangesBrowser(myProject, null, diff, null, false, true, - null, ChangesBrowser.MyUseCase.COMMITTED_CHANGES, null); - changesBrowser.setChangesToDisplay(diff); - dialogBuilder.setCenterPanel(changesBrowser); - dialogBuilder.showNotModal(); - } -} - +} \ No newline at end of file diff --git a/plugins/properties/resources/icons/xmlProperties.png b/plugins/properties/properties-psi-api/resources/icons/xmlProperties.png similarity index 100% rename from plugins/properties/resources/icons/xmlProperties.png rename to plugins/properties/properties-psi-api/resources/icons/xmlProperties.png diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/NestedCopiesBuilder.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/NestedCopiesBuilder.java index d0eafb77bc4b..062666fbd402 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/NestedCopiesBuilder.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/NestedCopiesBuilder.java @@ -86,6 +86,10 @@ public class NestedCopiesBuilder implements StatusReceiver { } } + @Override + public void finish() { + } + public Set getCopies() { return mySet; } diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/StatusReceiver.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/StatusReceiver.java index ac165c48f5c3..f6515de0ebf1 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/StatusReceiver.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/StatusReceiver.java @@ -29,4 +29,5 @@ public interface StatusReceiver extends EventListener { void processUnversioned(final VirtualFile vFile); void processCopyRoot(VirtualFile file, SVNURL url, WorkingCopyFormat format, SVNURL rootURL); void bewareRoot(VirtualFile vf, SVNURL url); + void finish(); } diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnChangeProvider.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnChangeProvider.java index 073826f6b035..41461ed623c6 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnChangeProvider.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnChangeProvider.java @@ -95,6 +95,8 @@ public class SvnChangeProvider implements ChangeProvider { walker.go(item.getDir(), Depth.IMMEDIATES); } + statusReceiver.getMulticaster().finish(); + processCopiedAndDeleted(context, dirtyScope); processUnsaved(dirtyScope, addGate, context); diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnChangeProviderContext.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnChangeProviderContext.java index 716abbe1b20b..9ae90c722cb3 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnChangeProviderContext.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnChangeProviderContext.java @@ -23,6 +23,7 @@ import com.intellij.openapi.vcs.*; import com.intellij.openapi.vcs.changes.*; import com.intellij.openapi.vfs.LocalFileSystem; import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.util.containers.ContainerUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.idea.svn.actions.AbstractShowPropertiesDiffAction; @@ -48,6 +49,7 @@ class SvnChangeProviderContext implements StatusReceiver { private Map myCopyFromURLs = null; private final SvnVcs myVcs; private final SvnBranchConfigurationManager myBranchConfigurationManager; + private final List filesToRefresh = ContainerUtil.newArrayList(); private final ProgressIndicator myProgress; @@ -79,6 +81,11 @@ class SvnChangeProviderContext implements StatusReceiver { public void bewareRoot(VirtualFile vf, SVNURL url) { } + @Override + public void finish() { + LocalFileSystem.getInstance().refreshIoFiles(filesToRefresh, true, false, null); + } + public ChangelistBuilder getBuilder() { return myChangelistBuilder; } @@ -302,7 +309,7 @@ class SvnChangeProviderContext implements StatusReceiver { * * @param filePath the path of a changed file. */ - private static void loadEntriesFile(final FilePath filePath) { + private void loadEntriesFile(final FilePath filePath) { final FilePath parentPath = filePath.getParentPath(); if (parentPath == null) { return; @@ -313,9 +320,11 @@ class SvnChangeProviderContext implements StatusReceiver { } } - private static void refreshDotSvnAndEntries(FilePath filePath) { + private void refreshDotSvnAndEntries(FilePath filePath) { final File svn = new File(filePath.getPath(), SvnUtil.SVN_ADMIN_DIR_NAME); - LocalFileSystem.getInstance().refreshIoFiles(Arrays.asList(svn, new File(svn, SvnUtil.ENTRIES_FILE_NAME)), true, false, null); + + filesToRefresh.add(svn); + filesToRefresh.add(new File(svn, SvnUtil.ENTRIES_FILE_NAME)); } // seems here we can only have a tree conflict; which can be marked on either path (?) diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnDiffProvider.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnDiffProvider.java index 49ea87d44f6e..5f5edd12eea7 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnDiffProvider.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnDiffProvider.java @@ -34,14 +34,14 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.idea.svn.commandLine.SvnBindException; import org.jetbrains.idea.svn.history.LatestExistentSearcher; -import org.jetbrains.idea.svn.info.InfoConsumer; import org.jetbrains.idea.svn.info.Info; +import org.jetbrains.idea.svn.info.InfoConsumer; +import org.jetbrains.idea.svn.properties.PropertyValue; import org.jetbrains.idea.svn.status.Status; import org.jetbrains.idea.svn.status.StatusType; import org.tmatesoft.svn.core.SVNException; -import org.tmatesoft.svn.core.SVNPropertyValue; import org.tmatesoft.svn.core.SVNURL; -import org.tmatesoft.svn.core.wc.*; +import org.tmatesoft.svn.core.wc.SVNRevision; import org.tmatesoft.svn.core.wc2.SvnTarget; import java.io.File; @@ -162,11 +162,12 @@ public class SvnDiffProvider extends DiffProviderEx implements DiffProvider, Dif } } + @Nullable private String getCommitMessage(File path) throws VcsException { - SVNPropertyData property = + PropertyValue property = myVcs.getFactory(path).createPropertyClient().getProperty(SvnTarget.fromFile(path), COMMIT_MESSAGE, true, SVNRevision.BASE); - return property != null ? SVNPropertyValue.getPropertyAsString(property.getValue()) : null; + return PropertyValue.toString(property); } private static ItemLatestState defaultResult() { diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnEditFileProvider.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnEditFileProvider.java index 64248892614a..c8b47aa0928e 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnEditFileProvider.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnEditFileProvider.java @@ -19,7 +19,7 @@ import com.intellij.openapi.vcs.EditFileProvider; import com.intellij.openapi.vcs.VcsException; import com.intellij.openapi.vfs.VirtualFile; import org.jetbrains.idea.svn.properties.PropertyClient; -import org.tmatesoft.svn.core.wc.SVNPropertyData; +import org.jetbrains.idea.svn.properties.PropertyValue; import org.tmatesoft.svn.core.wc.SVNRevision; import org.tmatesoft.svn.core.wc2.SvnTarget; @@ -42,10 +42,10 @@ public class SvnEditFileProvider implements EditFileProvider { ioFiles[i] = new File(files[i].getPath()); PropertyClient client = myVCS.getFactory(ioFiles[i]).createPropertyClient(); - SVNPropertyData property = client.getProperty(SvnTarget.fromFile(ioFiles[i], SVNRevision.WORKING), SvnPropertyKeys.SVN_NEEDS_LOCK, + PropertyValue property = client.getProperty(SvnTarget.fromFile(ioFiles[i], SVNRevision.WORKING), SvnPropertyKeys.SVN_NEEDS_LOCK, false, SVNRevision.WORKING); - if (property == null || property.getValue() == null) { + if (property == null) { throw new VcsException(SvnBundle.message("exception.text.file.miss.svn", ioFiles[i].getName())); } } diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnFormatSelector.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnFormatSelector.java index 1719f781a34a..977c6add0c1f 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnFormatSelector.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnFormatSelector.java @@ -15,122 +15,15 @@ */ package org.jetbrains.idea.svn; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.diagnostic.Logger; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import org.tmatesoft.svn.core.SVNErrorCode; -import org.tmatesoft.svn.core.SVNErrorMessage; import org.tmatesoft.svn.core.SVNException; -import org.tmatesoft.svn.core.internal.wc.admin.ISVNAdminAreaFactorySelector; import org.tmatesoft.svn.core.internal.wc.admin.SVNAdminAreaFactory; import org.tmatesoft.svn.core.internal.wc2.SvnWcGeneration; import org.tmatesoft.svn.core.wc2.SvnOperationFactory; import java.io.File; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.Iterator; -public class SvnFormatSelector implements ISVNAdminAreaFactorySelector { - - private static final Logger LOG = Logger.getInstance("#org.jetbrains.idea.svn.SvnFormatSelector"); - - public Collection getEnabledFactories(File path, Collection factories, boolean writeAccess) throws SVNException { - if (ApplicationManager.getApplication().isUnitTestMode()) { - return factories; - } - - if (! writeAccess) { - return factories; - } - - Collection result = null; - final WorkingCopyFormat presetFormat = SvnWorkingCopyFormatHolder.getPresetFormat(); - if (presetFormat != null) { - result = format2Factories(presetFormat, factories); - } - - if (result == null) { - final WorkingCopyFormat format = getWorkingCopyFormat(path); - result = format2Factories(format, factories); - } - - if (result == null) { - throw new SVNException(SVNErrorMessage.create(SVNErrorCode.WC_NOT_DIRECTORY)); - } - return result; - } - - @Nullable - static Collection format2Factories(final WorkingCopyFormat format, final Collection factories) { - if (WorkingCopyFormat.ONE_DOT_SEVEN.equals(format)) { - return factories; - } else if (WorkingCopyFormat.ONE_DOT_SIX.equals(format)) { - return factoriesFor16(factories); - } else if (WorkingCopyFormat.ONE_DOT_FIVE.equals(format)) { - return factoriesFor15(factories); - } else if (WorkingCopyFormat.ONE_DOT_FOUR.equals(format)) { - return factoriesFor14(factories); - } else if (WorkingCopyFormat.ONE_DOT_THREE.equals(format)) { - return factoriesFor13(factories); - } - return null; - } - - private static Collection factoriesFor13(final Collection factories) { - for (Iterator iterator = factories.iterator(); iterator.hasNext();) { - final SVNAdminAreaFactory factory = (SVNAdminAreaFactory) iterator.next(); - final int supportedVersion = factory.getSupportedVersion(); - if (WorkingCopyFormat.ONE_DOT_THREE.getFormat() == supportedVersion) { - return Collections.singletonList(factory); - } - } - return Collections.emptyList(); - } - - private static Collection factoriesFor14(final Collection factories) { - final Collection result = new ArrayList(2); - for (Iterator iterator = factories.iterator(); iterator.hasNext();) { - final SVNAdminAreaFactory factory = (SVNAdminAreaFactory) iterator.next(); - final int supportedVersion = factory.getSupportedVersion(); - if ((WorkingCopyFormat.ONE_DOT_FOUR.getFormat() == supportedVersion) || - (WorkingCopyFormat.ONE_DOT_THREE.getFormat() == supportedVersion)) { - result.add(factory); - } - } - return result; - } - - private static Collection factoriesFor15(final Collection factories) { - final Collection result = new ArrayList(2); - for (Iterator iterator = factories.iterator(); iterator.hasNext();) { - final SVNAdminAreaFactory factory = (SVNAdminAreaFactory) iterator.next(); - final int supportedVersion = factory.getSupportedVersion(); - if ((WorkingCopyFormat.ONE_DOT_FOUR.getFormat() == supportedVersion) || - (WorkingCopyFormat.ONE_DOT_THREE.getFormat() == supportedVersion) || - (WorkingCopyFormat.ONE_DOT_FIVE.getFormat() == supportedVersion)) { - result.add(factory); - } - } - return result; - } - - private static Collection factoriesFor16(final Collection factories) { - final Collection result = new ArrayList(2); - for (Iterator iterator = factories.iterator(); iterator.hasNext();) { - final SVNAdminAreaFactory factory = (SVNAdminAreaFactory) iterator.next(); - final int supportedVersion = factory.getSupportedVersion(); - if ((WorkingCopyFormat.ONE_DOT_FOUR.getFormat() == supportedVersion) || - (WorkingCopyFormat.ONE_DOT_THREE.getFormat() == supportedVersion) || - (WorkingCopyFormat.ONE_DOT_FIVE.getFormat() == supportedVersion) || - (WorkingCopyFormat.ONE_DOT_SIX.getFormat() == supportedVersion)) { - result.add(factory); - } - } - return result; - } +public class SvnFormatSelector { @NotNull public static WorkingCopyFormat findRootAndGetFormat(final File path) { diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnRecursiveStatusWalker.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnRecursiveStatusWalker.java index 8929e0cd97b0..13c15b23efa8 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnRecursiveStatusWalker.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnRecursiveStatusWalker.java @@ -184,15 +184,20 @@ public class SvnRecursiveStatusWalker { } }; if (Depth.EMPTY.equals(newDepth)) { - directoryFilter = Processor.TRUE; + // just process immediate children - so only root directory itself should satisfy filter + directoryFilter = new Processor() { + @Override + public boolean process(File file) { + return FileUtil.filesEqual(ioFile, file); + } + }; processor = new Processor() { @Override public boolean process(File file) { - // here we deal only with immediate children - so ignored on IDEA level for children is not important - we nevertheless do not go into - // other levels - if (! FileUtil.filesEqual(ioFile, file)) return true; - if (! FileUtil.filesEqual(ioFile, file.getParentFile())) return false; - return checkDirProcessor.process(file); + // TODO: check if we should still call checkDirProcessor() here - or we really could not check ignore settings but just call + // TODO: myReceiver.processUnversioned() for all immediate children + // here we deal only with immediate children - so ignored on IDEA level for children is not important + return FileUtil.filesEqual(ioFile, file) || checkDirProcessor.process(file); } }; } else { diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnVcs.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnVcs.java index 5010ac580750..b44fd16f979f 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnVcs.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnVcs.java @@ -81,6 +81,8 @@ import org.jetbrains.idea.svn.history.SvnHistoryProvider; import org.jetbrains.idea.svn.info.Info; import org.jetbrains.idea.svn.info.InfoConsumer; import org.jetbrains.idea.svn.properties.PropertyClient; +import org.jetbrains.idea.svn.properties.PropertyData; +import org.jetbrains.idea.svn.properties.PropertyValue; import org.jetbrains.idea.svn.rollback.SvnRollbackEnvironment; import org.jetbrains.idea.svn.status.Status; import org.jetbrains.idea.svn.status.StatusType; @@ -89,7 +91,6 @@ import org.jetbrains.idea.svn.update.SvnIntegrateEnvironment; import org.jetbrains.idea.svn.update.SvnUpdateEnvironment; import org.tmatesoft.svn.core.*; import org.tmatesoft.svn.core.internal.wc.SVNAdminUtil; -import org.tmatesoft.svn.core.wc.SVNPropertyData; import org.tmatesoft.svn.core.wc.SVNRevision; import org.tmatesoft.svn.core.wc2.SvnTarget; @@ -108,8 +109,8 @@ public class SvnVcs extends AbstractVcs { private static final VcsKey ourKey = createKey(VCS_NAME); public static final Topic WC_CONVERTED = new Topic("WC_CONVERTED", Runnable.class); - private final Map>>> myPropertyCache = - new SoftHashMap>>>(); + private final Map>>> myPropertyCache = + new SoftHashMap>>>(); private final SvnConfiguration myConfiguration; private final SvnEntriesFileListener myEntriesFileListener; @@ -567,9 +568,9 @@ public class SvnVcs extends AbstractVcs { } @Nullable - public SVNPropertyValue getPropertyWithCaching(final VirtualFile file, final String propName) throws VcsException { - Map>> cachedMap = myPropertyCache.get(keyForVf(file)); - final Pair> cachedValue = cachedMap == null ? null : cachedMap.get(propName); + public PropertyValue getPropertyWithCaching(final VirtualFile file, final String propName) throws VcsException { + Map>> cachedMap = myPropertyCache.get(keyForVf(file)); + final Pair> cachedValue = cachedMap == null ? null : cachedMap.get(propName); final File ioFile = new File(file.getPath()); final Trinity tsTrinity = getTimestampForPropertiesChange(ioFile, file.isDirectory()); @@ -582,17 +583,16 @@ public class SvnVcs extends AbstractVcs { } PropertyClient client = getFactory(ioFile).createPropertyClient(); - final SVNPropertyData value = client.getProperty(SvnTarget.fromFile(ioFile, SVNRevision.WORKING), propName, false, SVNRevision.WORKING); - final SVNPropertyValue propValue = value == null ? null : value.getValue(); + final PropertyValue value = client.getProperty(SvnTarget.fromFile(ioFile, SVNRevision.WORKING), propName, false, SVNRevision.WORKING); if (cachedMap == null) { - cachedMap = new HashMap>>(); + cachedMap = new HashMap>>(); myPropertyCache.put(keyForVf(file), cachedMap); } - cachedMap.put(propName, Pair.create(propValue, tsTrinity)); + cachedMap.put(propName, Pair.create(value, tsTrinity)); - return propValue; + return value; } @Override diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/actions/AbstractShowPropertiesDiffAction.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/actions/AbstractShowPropertiesDiffAction.java index 97e6a618ba7c..395dc36b7371 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/actions/AbstractShowPropertiesDiffAction.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/actions/AbstractShowPropertiesDiffAction.java @@ -19,12 +19,15 @@ import com.intellij.openapi.actionSystem.*; import com.intellij.openapi.diff.DiffManager; import com.intellij.openapi.diff.SimpleContent; import com.intellij.openapi.diff.SimpleDiffRequest; +import com.intellij.openapi.progress.PerformInBackgroundOption; import com.intellij.openapi.progress.ProgressIndicator; import com.intellij.openapi.progress.ProgressManager; import com.intellij.openapi.progress.Task; import com.intellij.openapi.project.DumbAware; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.Messages; +import com.intellij.openapi.util.Condition; +import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vcs.AbstractVcs; import com.intellij.openapi.vcs.FilePath; import com.intellij.openapi.vcs.VcsDataKeys; @@ -34,17 +37,20 @@ import com.intellij.openapi.vcs.changes.ChangesUtil; import com.intellij.openapi.vcs.changes.ContentRevision; import com.intellij.openapi.vcs.changes.MarkerVcsContentRevision; import com.intellij.openapi.vfs.VirtualFile; -import org.jetbrains.annotations.NonNls; +import com.intellij.util.containers.ContainerUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.idea.svn.SvnBundle; -import org.jetbrains.idea.svn.SvnRevisionNumber; import org.jetbrains.idea.svn.SvnVcs; import org.jetbrains.idea.svn.api.Depth; import org.jetbrains.idea.svn.history.SvnRepositoryContentRevision; -import org.tmatesoft.svn.core.*; -import org.tmatesoft.svn.core.wc.ISVNPropertyHandler; -import org.tmatesoft.svn.core.wc.SVNPropertyData; +import org.jetbrains.idea.svn.properties.PropertyConsumer; +import org.jetbrains.idea.svn.properties.PropertyData; +import org.jetbrains.idea.svn.properties.PropertyValue; +import org.tmatesoft.svn.core.SVNErrorCode; +import org.tmatesoft.svn.core.SVNErrorMessage; +import org.tmatesoft.svn.core.SVNException; +import org.tmatesoft.svn.core.SVNURL; import org.tmatesoft.svn.core.wc.SVNRevision; import org.tmatesoft.svn.core.wc2.SvnTarget; @@ -69,8 +75,6 @@ public abstract class AbstractShowPropertiesDiffAction extends AnAction implemen @Override public void update(final AnActionEvent e) { final DataContext dataContext = e.getDataContext(); - final Project project = CommonDataKeys.PROJECT.getData(dataContext); - final Presentation presentation = e.getPresentation(); final Change[] data = VcsDataKeys.CHANGES.getData(dataContext); boolean showAction = checkThatChangesAreUnderSvn(data); @@ -78,38 +82,23 @@ public abstract class AbstractShowPropertiesDiffAction extends AnAction implemen presentation.setEnabled(showAction); } - private boolean checkThatChangesAreUnderSvn(Change[] data) { - boolean showAction = false; - if (data != null) { - for (Change change : data) { - final ContentRevision before = change.getBeforeRevision(); - if (before != null) { - showAction = showAction || before instanceof MarkerVcsContentRevision && SvnVcs.getKey().equals(((MarkerVcsContentRevision)before).getVcsKey()); + private static boolean checkThatChangesAreUnderSvn(@Nullable Change[] changes) { + boolean result = false; + + if (changes != null) { + result = ContainerUtil.or(changes, new Condition() { + @Override + public boolean value(Change change) { + return isUnderSvn(change.getBeforeRevision()) || isUnderSvn(change.getAfterRevision()); } - final ContentRevision after = change.getAfterRevision(); - if (after != null) { - showAction = showAction || after instanceof MarkerVcsContentRevision && SvnVcs.getKey().equals(((MarkerVcsContentRevision)after).getVcsKey()); - } - if (showAction) break; - } + }); } - return showAction; + + return result; } - private boolean enabled(final Project project, final Change[] changes) { - final boolean noChange = (project == null) || (changes == null) || (changes.length != 1); - if (noChange) { - return false; - } else { - final Change change = changes[0]; - - final ContentRevision revision = (change.getBeforeRevision() != null) ? change.getBeforeRevision() : change.getAfterRevision(); - if ((revision == null) || (! (revision.getRevisionNumber() instanceof SvnRevisionNumber))) { - return false; - } - - return checkVcs(project, change); - } + private static boolean isUnderSvn(@Nullable ContentRevision revision) { + return revision instanceof MarkerVcsContentRevision && SvnVcs.getKey().equals(((MarkerVcsContentRevision)revision).getVcsKey()); } protected boolean checkVcs(final Project project, final Change change) { @@ -146,7 +135,7 @@ public abstract class AbstractShowPropertiesDiffAction extends AnAction implemen private final String myErrorTitle; private CalculateAndShow(@Nullable final Project project, final Change change, final String errorTitle) { - super(project, SvnBundle.message("fetching.properties.contents.progress.title"), true, Backgroundable.DEAF); + super(project, SvnBundle.message("fetching.properties.contents.progress.title"), true, PerformInBackgroundOption.DEAF); myChange = change; myErrorTitle = errorTitle; } @@ -179,7 +168,7 @@ public abstract class AbstractShowPropertiesDiffAction extends AnAction implemen } if (myBeforeContent != null && myAfterContent != null && myBeforeRevisionValue != null && myAfterRevision != null) { final SimpleDiffRequest diffRequest = new SimpleDiffRequest(myProject, getDiffWindowTitle(myChange)); - if (compareRevisions(myBeforeRevisionValue, myAfterRevision) >= 0) { + if (compareRevisions(myBeforeRevisionValue, myAfterRevision) > 0) { // before ahead diffRequest.setContents(new SimpleContent(myAfterContent), new SimpleContent(myBeforeContent)); diffRequest.setContentTitles(revisionToString(myAfterRevision), revisionToString(myBeforeRevisionValue)); @@ -194,7 +183,8 @@ public abstract class AbstractShowPropertiesDiffAction extends AnAction implemen } } - private String getDiffWindowTitle(final Change change) { + @NotNull + private static String getDiffWindowTitle(@NotNull Change change) { if (change.isMoved() || change.isRenamed()) { final FilePath beforeFilePath = ChangesUtil.getBeforePath(change); final FilePath afterFilePath = ChangesUtil.getAfterPath(change); @@ -207,7 +197,7 @@ public abstract class AbstractShowPropertiesDiffAction extends AnAction implemen } } - private int compareRevisions(@NonNls final SVNRevision revision1, @NonNls final SVNRevision revision2) { + private static int compareRevisions(@NotNull SVNRevision revision1, @NotNull SVNRevision revision2) { if (revision1.equals(revision2)) { return 0; } @@ -227,11 +217,9 @@ public abstract class AbstractShowPropertiesDiffAction extends AnAction implemen return revision1.getNumber() > revision2.getNumber() ? 1 : -1; } - private String revisionToString(final SVNRevision revision) { - if (revision == null) { - return "not exists"; - } - return revision.toString(); + @NotNull + private static String revisionToString(@Nullable SVNRevision revision) { + return revision == null ? "not exists" : revision.toString(); } private final static String ourPropertiesDelimiter = "\n"; @@ -273,35 +261,35 @@ public abstract class AbstractShowPropertiesDiffAction extends AnAction implemen private static String getPropertyList(@NotNull SvnVcs vcs, @NotNull SvnTarget target, @Nullable SVNRevision revision) throws VcsException { - final List lines = new ArrayList(); - final ISVNPropertyHandler propertyHandler = createHandler(revision, lines); + final List lines = new ArrayList(); + final PropertyConsumer propertyHandler = createHandler(revision, lines); vcs.getFactory(target).createPropertyClient().list(target, revision, Depth.EMPTY, propertyHandler); return toSortedStringPresentation(lines); } - private static ISVNPropertyHandler createHandler(SVNRevision revision, final List lines) { + private static PropertyConsumer createHandler(SVNRevision revision, final List lines) { final ProgressIndicator indicator = ProgressManager.getInstance().getProgressIndicator(); if (indicator != null) { indicator.checkCanceled(); indicator.setText(SvnBundle.message("show.properties.diff.progress.text.revision.information", revision.toString())); } - return new ISVNPropertyHandler() { - public void handleProperty(final File path, final SVNPropertyData property) throws SVNException { + return new PropertyConsumer() { + public void handleProperty(final File path, final PropertyData property) throws SVNException { registerProperty(property); } - public void handleProperty(final SVNURL url, final SVNPropertyData property) throws SVNException { + public void handleProperty(final SVNURL url, final PropertyData property) throws SVNException { registerProperty(property); } - public void handleProperty(final long revision, final SVNPropertyData property) throws SVNException { + public void handleProperty(final long revision, final PropertyData property) throws SVNException { // revision properties here } - private void registerProperty(@NotNull SVNPropertyData property) { + private void registerProperty(@NotNull PropertyData property) { if (indicator != null) { indicator.checkCanceled(); indicator.setText2(SvnBundle.message("show.properties.diff.progress.text2.property.information", property.getName())); @@ -311,26 +299,26 @@ public abstract class AbstractShowPropertiesDiffAction extends AnAction implemen }; } - private static String toSortedStringPresentation(List lines) { + private static String toSortedStringPresentation(List lines) { StringBuilder sb = new StringBuilder(); - Collections.sort(lines, new Comparator() { - public int compare(final SVNPropertyData o1, final SVNPropertyData o2) { + Collections.sort(lines, new Comparator() { + public int compare(final PropertyData o1, final PropertyData o2) { return o1.getName().compareTo(o2.getName()); } }); - for (SVNPropertyData line : lines) { + for (PropertyData line : lines) { addPropertyPresentation(line, sb); } return sb.toString(); } - private static void addPropertyPresentation(final SVNPropertyData property, final StringBuilder sb) { + private static void addPropertyPresentation(final PropertyData property, final StringBuilder sb) { if (sb.length() != 0) { sb.append(ourPropertiesDelimiter); } - sb.append(property.getName()).append("=").append((property.getValue() == null) ? "" : SVNPropertyValue.getPropertyAsString(property.getValue())); + sb.append(property.getName()).append("=").append(StringUtil.notNullize(PropertyValue.toString(property.getValue()))); } } diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/actions/CreateExternalAction.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/actions/CreateExternalAction.java index 31206eec045c..d6d9c3152dde 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/actions/CreateExternalAction.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/actions/CreateExternalAction.java @@ -24,7 +24,6 @@ import com.intellij.openapi.progress.Task; import com.intellij.openapi.project.DumbAwareAction; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.DialogWrapper; -import com.intellij.openapi.util.Comparing; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vcs.*; import com.intellij.openapi.vcs.changes.VcsDirtyScopeManager; @@ -42,15 +41,16 @@ import org.jetbrains.idea.svn.api.ProgressEvent; import org.jetbrains.idea.svn.api.ProgressTracker; import org.jetbrains.idea.svn.commandLine.CommandUtil; import org.jetbrains.idea.svn.dialogs.SelectCreateExternalTargetDialog; +import org.jetbrains.idea.svn.properties.ExternalsDefinitionParser; +import org.jetbrains.idea.svn.properties.PropertyValue; import org.jetbrains.idea.svn.update.UpdateClient; import org.tmatesoft.svn.core.SVNCancelException; import org.tmatesoft.svn.core.SVNException; -import org.tmatesoft.svn.core.SVNPropertyValue; -import org.tmatesoft.svn.core.internal.wc.SVNExternal; -import org.tmatesoft.svn.core.wc.*; +import org.tmatesoft.svn.core.wc.SVNRevision; import org.tmatesoft.svn.core.wc2.SvnTarget; import java.io.File; +import java.util.Map; /** * Created with IntelliJ IDEA. @@ -132,25 +132,24 @@ public class CreateExternalAction extends DumbAwareAction { public static boolean addToExternalProperty(@NotNull SvnVcs vcs, @NotNull File ioFile, String target, String url) throws SVNException, VcsException { ClientFactory factory = vcs.getFactory(ioFile); - SVNPropertyData propertyData = factory.createPropertyClient().getProperty(SvnTarget.fromFile(ioFile), SvnPropertyKeys.SVN_EXTERNALS, + PropertyValue propertyValue = factory.createPropertyClient().getProperty(SvnTarget.fromFile(ioFile), SvnPropertyKeys.SVN_EXTERNALS, false, SVNRevision.UNDEFINED); String newValue; - if (propertyData != null && propertyData.getValue() != null && ! StringUtil.isEmptyOrSpaces(propertyData.getValue().getString())) { - final SVNExternal[] externals = SVNExternal.parseExternals("Create External", propertyData.getValue().getString()); - for (SVNExternal external : externals) { - if (Comparing.equal(external.getPath(), target)) { - AbstractVcsHelper - .getInstance(vcs.getProject()).showError(new VcsException("Selected destination conflicts with existing: " + external.toString()), "Create External"); - return true; - } + if (propertyValue != null && !StringUtil.isEmptyOrSpaces(propertyValue.toString())) { + Map externalsMap = ExternalsDefinitionParser.parseExternalsProperty(propertyValue.toString()); + String externalsForTarget = externalsMap.get(target); + + if (externalsForTarget != null) { + AbstractVcsHelper.getInstance(vcs.getProject()).showError( + new VcsException("Selected destination conflicts with existing: " + externalsForTarget), "Create External"); + return true; } final String string = createExternalDefinitionString(url, target); - newValue = propertyData.getValue().getString().trim() + "\n" + string; + newValue = propertyValue.toString().trim() + "\n" + string; } else { newValue = createExternalDefinitionString(url, target); } - factory.createPropertyClient().setProperty(ioFile, SvnPropertyKeys.SVN_EXTERNALS, SVNPropertyValue.create(newValue), Depth.EMPTY, - false); + factory.createPropertyClient().setProperty(ioFile, SvnPropertyKeys.SVN_EXTERNALS, PropertyValue.create(newValue), Depth.EMPTY, false); return false; } diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/actions/SetPropertyAction.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/actions/SetPropertyAction.java index cdc738cf2d40..5e53cc956e92 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/actions/SetPropertyAction.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/actions/SetPropertyAction.java @@ -30,7 +30,7 @@ import org.jetbrains.idea.svn.SvnVcs; import org.jetbrains.idea.svn.api.Depth; import org.jetbrains.idea.svn.dialogs.SetPropertyDialog; import org.jetbrains.idea.svn.properties.PropertyClient; -import org.tmatesoft.svn.core.SVNPropertyValue; +import org.jetbrains.idea.svn.properties.PropertyValue; import java.io.File; @@ -79,7 +79,7 @@ public class SetPropertyAction extends BasicAction { // TODO: most likely SVNDepth.getInfinityOrEmptyDepth should be used instead of SVNDepth.fromRecursive - to have either "infinity" // TODO: or "empty" depth, and not "infinity" or "files" depth. But previous logic used SVNDepth.fromRecursive implicitly - client.setProperty(ioFile, name, SVNPropertyValue.create(value), Depth.allOrFiles(recursive), false); + client.setProperty(ioFile, name, PropertyValue.create(value), Depth.allOrFiles(recursive), false); } for(int i = 0; i < file.length; i++) { if (recursive && file[i].isDirectory()) { diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/actions/SvnMergeProvider.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/actions/SvnMergeProvider.java index 912bd970f7b0..ddd9bd572f0e 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/actions/SvnMergeProvider.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/actions/SvnMergeProvider.java @@ -32,9 +32,9 @@ import org.jetbrains.idea.svn.SvnVcs; import org.jetbrains.idea.svn.api.Depth; import org.jetbrains.idea.svn.info.Info; import org.jetbrains.idea.svn.properties.PropertyClient; +import org.jetbrains.idea.svn.properties.PropertyValue; import org.tmatesoft.svn.core.SVNProperty; -import org.tmatesoft.svn.core.SVNPropertyValue; -import org.tmatesoft.svn.core.wc.*; +import org.tmatesoft.svn.core.wc.SVNRevision; import org.tmatesoft.svn.core.wc2.SvnTarget; import java.io.ByteArrayOutputStream; @@ -162,8 +162,8 @@ public class SvnMergeProvider implements MergeProvider { File ioFile = new File(file.getPath()); PropertyClient client = vcs.getFactory(ioFile).createPropertyClient(); - SVNPropertyData svnPropertyData = client.getProperty(SvnTarget.fromFile(ioFile), SVNProperty.MIME_TYPE, false, SVNRevision.WORKING); - if (svnPropertyData != null && SVNProperty.isBinaryMimeType(SVNPropertyValue.getPropertyAsString(svnPropertyData.getValue()))) { + PropertyValue value = client.getProperty(SvnTarget.fromFile(ioFile), SVNProperty.MIME_TYPE, false, SVNRevision.WORKING); + if (value != null && SVNProperty.isBinaryMimeType(value.toString())) { return true; } } diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/CommandExecutor.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/CommandExecutor.java index 33e06739ad0b..0b484e41d1c5 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/CommandExecutor.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/CommandExecutor.java @@ -23,6 +23,7 @@ import com.intellij.openapi.application.PathManager; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.util.Key; import com.intellij.openapi.util.io.FileUtil; +import com.intellij.openapi.util.registry.Registry; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vfs.CharsetToolkit; import com.intellij.util.EventDispatcher; @@ -36,6 +37,7 @@ import java.io.File; import java.io.IOException; import java.io.OutputStreamWriter; import java.util.List; +import java.util.Map; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; @@ -152,9 +154,20 @@ public class CommandExecutor { protected void beforeCreateProcess() throws SvnBindException { EncodingEnvironmentUtil.fixDefaultEncodingIfMac(myCommandLine, null); + setupLocale(); ensureMessageFile(); } + private void setupLocale() { + String locale = Registry.stringValue("svn.executable.locale"); + Map environment = myCommandLine.getEnvironment(); + + // TODO: check if we need to set LC_ALL to configured locale or just clear it + environment.put("LC_ALL", ""); + environment.put("LC_MESSAGES", locale); + environment.put("LANG", locale); + } + private void ensureMessageFile() throws SvnBindException { if (myMessage != null) { myMessageFile = createTempFile("commit-message", ".txt"); 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 eee798737846..eb2e6cf976cb 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/CommandUtil.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/CommandUtil.java @@ -58,8 +58,7 @@ public class CommandUtil { boolean hasPegRevision = pegRevision != null && !SVNRevision.UNDEFINED.equals(pegRevision) && !SVNRevision.WORKING.equals(pegRevision) && - pegRevision.isValid() && - pegRevision.getNumber() != 0; + pegRevision.isValid(); if (hasPegRevision || hasAtSymbol) { // add '@' to correctly handle paths that contain '@' symbol diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/config/SvnConfigureProxiesDialog.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/config/SvnConfigureProxiesDialog.java index a70a4f28d0fd..40e3c80d1fc6 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/config/SvnConfigureProxiesDialog.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/config/SvnConfigureProxiesDialog.java @@ -63,16 +63,10 @@ public class SvnConfigureProxiesDialog extends DialogWrapper implements Validati public void onError(final String text, final JComponent component, final boolean forbidSave) { myTabbedPane.setSelectedComponent(component); - String prefixString = ""; - for (int i = 0; i < myTabbedPane.getComponentCount(); i++) { - final Component currentComponent = myTabbedPane.getComponentAt(i); - // compare referencies - same objects - if (currentComponent == component) { - prefixString = myTabbedPane.getTitleAt(i) + ": "; - } - } + String errorPrefix = myTabbedPane.getTitleAt(myTabbedPane.indexOfComponent(component)) + ": "; + setOKActionEnabled(! forbidSave); - setInvalid(prefixString + text); + setInvalid(errorPrefix + text); } public void onSuccess() { diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/BranchConfigurationDialog.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/BranchConfigurationDialog.java index 6bd08c1089cb..50ddd9615d41 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/BranchConfigurationDialog.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/BranchConfigurationDialog.java @@ -22,10 +22,12 @@ import com.intellij.openapi.ui.DialogWrapper; import com.intellij.openapi.ui.Messages; import com.intellij.openapi.ui.MultiLineLabelUI; import com.intellij.openapi.ui.TextFieldWithBrowseButton; +import com.intellij.openapi.util.Pair; import com.intellij.openapi.vcs.VcsException; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.ui.*; import com.intellij.ui.components.JBList; +import com.intellij.util.ObjectUtils; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -100,16 +102,23 @@ public class BranchConfigurationDialog extends DialogWrapper { myListPanel.add( ToolbarDecorator.createDecorator(myLocationList) .setAddAction(new AnActionButtonRunnable() { + + @Nullable private SVNURL usedRootUrl; + @Override public void run(AnActionButton button) { - final String selectedUrl = SelectLocationDialog.selectLocation(project, rootUrl.toDecodedString()); - if (selectedUrl != null) { - if (!configuration.getBranchUrls().contains(selectedUrl)) { - configuration - .addBranches(selectedUrl, new InfoStorage>(new ArrayList(), InfoReliability.empty)); - mySvnBranchConfigManager.reloadBranches(myRoot, selectedUrl, null); - listModel.fireItemAdded(); - myLocationList.setSelectedIndex(listModel.getSize() - 1); + Pair result = SelectLocationDialog.selectLocation(project, ObjectUtils.notNull(usedRootUrl, rootUrl)); + if (result != null) { + String selectedUrl = result.getFirst(); + usedRootUrl = result.getSecond(); + if (selectedUrl != null) { + if (!configuration.getBranchUrls().contains(selectedUrl)) { + configuration + .addBranches(selectedUrl, new InfoStorage>(new ArrayList(), InfoReliability.empty)); + mySvnBranchConfigManager.reloadBranches(myRoot, selectedUrl, null); + listModel.fireItemAdded(); + myLocationList.setSelectedIndex(listModel.getSize() - 1); + } } } } diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/PropertiesComponent.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/PropertiesComponent.java index 860c524ed591..cd504be56077 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/PropertiesComponent.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/PropertiesComponent.java @@ -37,9 +37,12 @@ import org.jetbrains.annotations.Nullable; import org.jetbrains.idea.svn.SvnPropertyKeys; import org.jetbrains.idea.svn.SvnVcs; import org.jetbrains.idea.svn.api.Depth; -import org.tmatesoft.svn.core.*; -import org.tmatesoft.svn.core.wc.ISVNPropertyHandler; -import org.tmatesoft.svn.core.wc.SVNPropertyData; +import org.jetbrains.idea.svn.properties.PropertyConsumer; +import org.jetbrains.idea.svn.properties.PropertyData; +import org.jetbrains.idea.svn.properties.PropertyValue; +import org.tmatesoft.svn.core.SVNException; +import org.tmatesoft.svn.core.SVNProperty; +import org.tmatesoft.svn.core.SVNURL; import org.tmatesoft.svn.core.wc.SVNRevision; import org.tmatesoft.svn.core.wc2.SvnTarget; @@ -151,20 +154,20 @@ public class PropertiesComponent extends JPanel { } } - private void collectProperties(@NotNull SvnVcs vcs, @NotNull File file, @NotNull final Map props) { + private static void collectProperties(@NotNull SvnVcs vcs, @NotNull File file, @NotNull final Map props) { try { - ISVNPropertyHandler handler = new ISVNPropertyHandler() { - public void handleProperty(File path, SVNPropertyData property) throws SVNException { - final SVNPropertyValue value = property.getValue(); + PropertyConsumer handler = new PropertyConsumer() { + public void handleProperty(File path, PropertyData property) throws SVNException { + final PropertyValue value = property.getValue(); if (value != null) { - props.put(property.getName(), SVNPropertyValue.getPropertyAsString(property.getValue())); + props.put(property.getName(), PropertyValue.toString(property.getValue())); } } - public void handleProperty(SVNURL url, SVNPropertyData property) throws SVNException { + public void handleProperty(SVNURL url, PropertyData property) throws SVNException { } - public void handleProperty(long revision, SVNPropertyData property) throws SVNException { + public void handleProperty(long revision, PropertyData property) throws SVNException { } }; vcs.getFactory(file).createPropertyClient().list(SvnTarget.fromFile(file, SVNRevision.UNDEFINED), SVNRevision.WORKING, Depth.EMPTY, @@ -229,10 +232,12 @@ public class PropertiesComponent extends JPanel { String url = "file://" + myFile.getPath().replace(File.separatorChar, '/'); VirtualFile file = VirtualFileManager.getInstance().findFileByUrl(url); if (file != null) { + VcsDirtyScopeManager dirtyScopeManager = VcsDirtyScopeManager.getInstance(myVcs.getProject()); + if (recursive && file.isDirectory()) { - VcsDirtyScopeManager.getInstance(myVcs.getProject()).dirDirtyRecursively(file, true); + dirtyScopeManager.dirDirtyRecursively(file); } else { - VcsDirtyScopeManager.getInstance(myVcs.getProject()).fileDirty(file); + dirtyScopeManager.fileDirty(file); } } } @@ -272,8 +277,7 @@ public class PropertiesComponent extends JPanel { if (!StringUtil.isEmpty(property)) { try { myVcs.getFactory(myFile).createPropertyClient() - .setProperty(myFile, property, value != null ? SVNPropertyValue.create(value) : null, - Depth.allOrEmpty(recursive), force); + .setProperty(myFile, property, PropertyValue.create(value), Depth.allOrEmpty(recursive), force); } catch (VcsException error) { VcsBalloonProblemNotifier @@ -302,7 +306,7 @@ public class PropertiesComponent extends JPanel { public void actionPerformed(AnActionEvent e) { Project project = CommonDataKeys.PROJECT.getData(e.getDataContext()); - SVNPropertyData propValue = null; + PropertyValue propValue = null; try { propValue = myVcs.getFactory(myFile).createPropertyClient() .getProperty(SvnTarget.fromFile(myFile), SVNProperty.KEYWORDS, false, SVNRevision.WORKING); @@ -311,8 +315,7 @@ public class PropertiesComponent extends JPanel { // show erorr message } - SetKeywordsDialog dialog = new SetKeywordsDialog(project, - propValue != null ? SVNPropertyValue.getPropertyAsString(propValue.getValue()) : null); + SetKeywordsDialog dialog = new SetKeywordsDialog(project, PropertyValue.toString(propValue)); dialog.show(); if (dialog.isOK()) { setProperty(SvnPropertyKeys.SVN_KEYWORDS, dialog.getKeywords(), false, false); diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/RepositoryBrowserComponent.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/RepositoryBrowserComponent.java index 0fd1f06bf7cb..163be4922224 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/RepositoryBrowserComponent.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/RepositoryBrowserComponent.java @@ -76,10 +76,26 @@ public class RepositoryBrowserComponent extends JPanel implements Disposable, Da } public void setRepositoryURLs(SVNURL[] urls, final boolean showFiles) { + setRepositoryURLs(urls, showFiles, null, false); + } + + public void setRepositoryURLs(SVNURL[] urls, + final boolean showFiles, + @Nullable NotNullFunction defaultExpanderFactory, + boolean expandFirst) { RepositoryTreeModel model = new RepositoryTreeModel(myVCS, showFiles, this); + + if (defaultExpanderFactory != null) { + model.setDefaultExpanderFactory(defaultExpanderFactory); + } + model.setRoots(urls); Disposer.register(this, model); myRepositoryTree.setModel(model); + + if (expandFirst) { + myRepositoryTree.expandRow(0); + } } public void setRepositoryURL(SVNURL url, boolean showFiles, final NotNullFunction defaultExpanderFactory) { diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/RepositoryBrowserDialog.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/RepositoryBrowserDialog.java index 191acf387b86..83d895b93ffc 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/RepositoryBrowserDialog.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/RepositoryBrowserDialog.java @@ -93,7 +93,7 @@ public class RepositoryBrowserDialog extends DialogWrapper { private final boolean myShowFiles; - @NonNls private static final String PLACE_TOOLBAR = "RepositoryBrowser.Toolbar"; + @NonNls public static final String PLACE_TOOLBAR = "RepositoryBrowser.Toolbar"; @NonNls private static final String PLACE_MENU = "RepositoryBrowser.Menu"; private final String myRepositoriesLabelText; protected JLabel myRepositoriesLabel; @@ -138,7 +138,7 @@ public class RepositoryBrowserDialog extends DialogWrapper { DefaultActionGroup group = new DefaultActionGroup(); final RepositoryBrowserComponent browser = getRepositoryBrowser(); group.add(new AddLocationAction(browser)); - group.add(new EditLocationAction()); + group.add(new EditLocationAction(browser)); group.add(new DiscardLocationAction(browser)); group.add(new DetailsAction()); group.addSeparator(); @@ -216,7 +216,7 @@ public class RepositoryBrowserDialog extends DialogWrapper { group.add(copyUrlAction); group.addSeparator(); group.add(new RefreshAction(browser)); - group.add(new EditLocationAction()); + group.add(new EditLocationAction(browser)); group.add(new DiscardLocationAction(browser)); ActionPopupMenu menu = ActionManager.getInstance().createActionPopupMenu(PLACE_MENU, group); return menu.getComponent(); @@ -413,13 +413,17 @@ public class RepositoryBrowserDialog extends DialogWrapper { } } - protected class EditLocationAction extends AnAction { - public EditLocationAction() { + protected static class EditLocationAction extends AnAction { + + @NotNull private final RepositoryBrowserComponent myBrowserComponent; + + public EditLocationAction(@NotNull RepositoryBrowserComponent browserComponent) { super(SvnBundle.message("repository.browser.edit.location.menu.item")); + myBrowserComponent = browserComponent; } public void update(AnActionEvent e) { - RepositoryTreeNode node = getRepositoryBrowser().getSelectedNode(); + RepositoryTreeNode node = myBrowserComponent.getSelectedNode(); if (e.getPlace().equals(PLACE_TOOLBAR)) { e.getPresentation().setDescription(SvnBundle.message("repository.browser.edit.location.menu.item")); e.getPresentation().setText(SvnBundle.message("repository.browser.edit.location.menu.item")); @@ -429,13 +433,14 @@ public class RepositoryBrowserDialog extends DialogWrapper { } public void actionPerformed(AnActionEvent e) { - RepositoryTreeNode node = getRepositoryBrowser().getSelectedNode(); + RepositoryTreeNode node = myBrowserComponent.getSelectedNode(); if (node == null || (! (node.getParent() instanceof RepositoryTreeRootNode))) { return; } final String oldUrl = node.getURL().toString(); final SvnApplicationSettings settings = SvnApplicationSettings.getInstance(); - final AddRepositoryLocationDialog dialog = new AddRepositoryLocationDialog(myProject, settings.getTypedUrlsListCopy()) { + final AddRepositoryLocationDialog dialog = + new AddRepositoryLocationDialog(myBrowserComponent.getProject(), settings.getTypedUrlsListCopy()) { @Override protected String initText() { return oldUrl; @@ -453,9 +458,9 @@ public class RepositoryBrowserDialog extends DialogWrapper { settings.addTypedUrl(url); settings.removeCheckoutURL(oldUrl); settings.addCheckoutURL(url); - final RepositoryBrowserComponent browser = getRepositoryBrowser(); - browser.removeURL(oldUrl); - browser.addURL(url); + + myBrowserComponent.removeURL(oldUrl); + myBrowserComponent.addURL(url); } } } diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/SelectLocationDialog.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/SelectLocationDialog.java index a6ca6ad54f11..bcbbe79c1b45 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/SelectLocationDialog.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/SelectLocationDialog.java @@ -15,12 +15,15 @@ */ package org.jetbrains.idea.svn.dialogs; +import com.intellij.openapi.actionSystem.ActionManager; +import com.intellij.openapi.actionSystem.DefaultActionGroup; import com.intellij.openapi.help.HelpManager; import com.intellij.openapi.progress.ProgressManager; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.DialogWrapper; import com.intellij.openapi.ui.Messages; import com.intellij.openapi.util.Disposer; +import com.intellij.openapi.util.Pair; import com.intellij.openapi.util.Ref; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; @@ -52,21 +55,30 @@ public class SelectLocationDialog extends DialogWrapper { private final String myDstLabel; private JTextField myDstText; private final boolean myIsShowFiles; + private final boolean myAllowActions; @NonNls private static final String HELP_ID = "vcs.subversion.common"; // todo check that works when authenticated @Nullable public static String selectLocation(Project project, String url) { - SelectLocationDialog dialog = openDialog(project, url, null, null, true, null); + SelectLocationDialog dialog = openDialog(project, url, null, null, true, false, null); return dialog == null || !dialog.isOK() ? null : dialog.getSelectedURL(); } + @Nullable + public static Pair selectLocation(Project project, @NotNull SVNURL url) { + SelectLocationDialog dialog = new SelectLocationDialog(project, url, null, null, true, true); + dialog.show(); + + return dialog.isOK() ? Pair.create(dialog.getSelectedURL(), dialog.getRootUrl()) : null; + } + @Nullable public static String selectCopyDestination(Project project, String url, String dstLabel, String dstName, boolean showFiles) { SelectLocationDialog dialog = - openDialog(project, url, dstLabel, dstName, showFiles, SvnBundle.message("select.location.invalid.url.message", url)); + openDialog(project, url, dstLabel, dstName, showFiles, false, SvnBundle.message("select.location.invalid.url.message", url)); return dialog == null || !dialog.isOK() ? null : SVNPathUtil.append(dialog.getSelectedURL(), dialog.getDestinationName()); } @@ -77,6 +89,7 @@ public class SelectLocationDialog extends DialogWrapper { String dstLabel, String dstName, boolean showFiles, + boolean allowActions, String errorMessage) { try { SVNURL svnUrl = SvnUtil.createUrl(url); @@ -86,8 +99,7 @@ public class SelectLocationDialog extends DialogWrapper { SvnBundle.message("dialog.title.select.repository.location")); return null; } - - SelectLocationDialog dialog = new SelectLocationDialog(project, repositoryUrl, dstLabel, dstName, showFiles); + SelectLocationDialog dialog = new SelectLocationDialog(project, repositoryUrl, dstLabel, dstName, showFiles, allowActions); dialog.show(); return dialog; } @@ -98,13 +110,14 @@ public class SelectLocationDialog extends DialogWrapper { } } - private SelectLocationDialog(Project project, SVNURL url, String dstLabel, String dstName, boolean showFiles) { + private SelectLocationDialog(Project project, SVNURL url, String dstLabel, String dstName, boolean showFiles, boolean allowActions) { super(project, true); myProject = project; myDstLabel = dstLabel; myDstName = dstName; myURL = url; myIsShowFiles = showFiles; + myAllowActions = allowActions; setTitle(SvnBundle.message("dialog.title.select.repository.location")); getHelpAction().setEnabled(true); init(); @@ -146,7 +159,13 @@ public class SelectLocationDialog extends DialogWrapper { protected void init() { super.init(); final String urlString = myURL.toString(); - myRepositoryBrowser.setRepositoryURL(myURL, myIsShowFiles, new UrlOpeningExpander.Factory(urlString, urlString)); + if (myAllowActions) { + // initialize repo browser this way - to make actions work correctly + myRepositoryBrowser.setRepositoryURLs(new SVNURL[]{myURL}, myIsShowFiles, new UrlOpeningExpander.Factory(urlString, urlString), true); + } + else { + myRepositoryBrowser.setRepositoryURL(myURL, myIsShowFiles, new UrlOpeningExpander.Factory(urlString, urlString)); + } myRepositoryBrowser.addChangeListener(new TreeSelectionListener() { public void valueChanged(TreeSelectionEvent e) { getOKAction().setEnabled(isOKActionEnabled()); @@ -162,7 +181,10 @@ public class SelectLocationDialog extends DialogWrapper { protected JComponent createCenterPanel() { JPanel panel = new JPanel(); - panel.setLayout(new GridBagLayout()); + panel.setLayout(new BorderLayout()); + + JPanel browserPanel = new JPanel(); + browserPanel.setLayout(new GridBagLayout()); GridBagConstraints gc = new GridBagConstraints(); gc.insets = new Insets(2, 2, 2, 2); @@ -177,7 +199,7 @@ public class SelectLocationDialog extends DialogWrapper { myRepositoryBrowser = new RepositoryBrowserComponent(SvnVcs.getInstance(myProject)); - panel.add(myRepositoryBrowser, gc); + browserPanel.add(myRepositoryBrowser, gc); if (myDstName != null) { gc.gridy += 1; gc.gridwidth = 1; @@ -187,7 +209,7 @@ public class SelectLocationDialog extends DialogWrapper { gc.weighty = 0; JLabel dstLabel = new JLabel(myDstLabel); - panel.add(dstLabel, gc); + browserPanel.add(dstLabel, gc); gc.gridx += 1; gc.weightx = 1; @@ -196,7 +218,7 @@ public class SelectLocationDialog extends DialogWrapper { myDstText = new JTextField(); myDstText.setText(myDstName); myDstText.selectAll(); - panel.add(myDstText, gc); + browserPanel.add(myDstText, gc); myDstText.getDocument().addDocumentListener(new DocumentListener() { public void insertUpdate(DocumentEvent e) { @@ -217,12 +239,25 @@ public class SelectLocationDialog extends DialogWrapper { gc.gridy += 1; gc.gridwidth = 2; - panel.add(new JSeparator(), gc); + browserPanel.add(new JSeparator(), gc); } + if (myAllowActions) { + panel.add(createToolbar(), BorderLayout.NORTH); + } + panel.add(browserPanel, BorderLayout.CENTER); + return panel; } + @NotNull + private JComponent createToolbar() { + DefaultActionGroup group = new DefaultActionGroup(); + group.add(new RepositoryBrowserDialog.EditLocationAction(myRepositoryBrowser)); + + return ActionManager.getInstance().createActionToolbar(RepositoryBrowserDialog.PLACE_TOOLBAR, group, true).getComponent(); + } + public JComponent getPreferredFocusedComponent() { return (JComponent)myRepositoryBrowser.getPreferredFocusedComponent(); } @@ -246,4 +281,16 @@ public class SelectLocationDialog extends DialogWrapper { public String getSelectedURL() { return myRepositoryBrowser.getSelectedURL(); } + + @Nullable + public SVNURL getRootUrl() { + RepositoryTreeNode node = myRepositoryBrowser.getSelectedNode(); + + // find the most top parent of type RepositoryTreeNode + while (node != null && node.getParent() instanceof RepositoryTreeNode) { + node = (RepositoryTreeNode)node.getParent(); + } + + return node != null ? node.getURL() : null; + } } diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/SetPropertyDialog.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/SetPropertyDialog.java index 28375d28b7ba..f2a58a0bd2da 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/SetPropertyDialog.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/SetPropertyDialog.java @@ -24,15 +24,16 @@ import com.intellij.openapi.vcs.VcsException; import com.intellij.ui.DocumentAdapter; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import org.jetbrains.idea.svn.SvnBundle; import org.jetbrains.idea.svn.SvnPropertyKeys; import org.jetbrains.idea.svn.SvnVcs; import org.jetbrains.idea.svn.api.Depth; import org.jetbrains.idea.svn.properties.PropertyClient; -import org.tmatesoft.svn.core.SVNPropertyValue; +import org.jetbrains.idea.svn.properties.PropertyConsumer; +import org.jetbrains.idea.svn.properties.PropertyData; +import org.jetbrains.idea.svn.properties.PropertyValue; import org.tmatesoft.svn.core.SVNURL; -import org.tmatesoft.svn.core.wc.ISVNPropertyHandler; -import org.tmatesoft.svn.core.wc.SVNPropertyData; import org.tmatesoft.svn.core.wc.SVNRevision; import org.tmatesoft.svn.core.wc2.SvnTarget; @@ -154,10 +155,10 @@ public class SetPropertyDialog extends DialogWrapper { return; } File file = myFiles[0]; - SVNPropertyData property = !StringUtil.isEmpty(name) ? getProperty(file, name) : null; + PropertyValue property = !StringUtil.isEmpty(name) ? getProperty(file, name) : null; if (property != null) { - myValueText.setText(SVNPropertyValue.getPropertyAsString(property.getValue())); + myValueText.setText(property.toString()); myValueText.selectAll(); } else { @@ -165,19 +166,20 @@ public class SetPropertyDialog extends DialogWrapper { } } - private SVNPropertyData getProperty(@NotNull File file, @NotNull String name) { - SVNPropertyData property; + @Nullable + private PropertyValue getProperty(@NotNull File file, @NotNull String name) { + PropertyValue result; try { PropertyClient client = myVCS.getFactory(file).createPropertyClient(); - property = client.getProperty(SvnTarget.fromFile(file, SVNRevision.WORKING), name, false, SVNRevision.WORKING); + result = client.getProperty(SvnTarget.fromFile(file, SVNRevision.WORKING), name, false, SVNRevision.WORKING); } catch (VcsException e) { LOG.info(e); - property = null; + result = null; } - return property; + return result; } protected JComponent createCenterPanel() { @@ -205,18 +207,18 @@ public class SetPropertyDialog extends DialogWrapper { if (files.length == 1) { File file = files[0]; try { - ISVNPropertyHandler handler = new ISVNPropertyHandler() { - public void handleProperty(File path, SVNPropertyData property) { + PropertyConsumer handler = new PropertyConsumer() { + public void handleProperty(File path, PropertyData property) { String name = property.getName(); if (name != null) { names.add(name); } } - public void handleProperty(SVNURL url, SVNPropertyData property) { + public void handleProperty(SVNURL url, PropertyData property) { } - public void handleProperty(long revision, SVNPropertyData property) { + public void handleProperty(long revision, PropertyData property) { } }; diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/diff/CmdDiffClient.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/diff/CmdDiffClient.java index 497134727ee4..be084b0986d0 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/diff/CmdDiffClient.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/diff/CmdDiffClient.java @@ -52,13 +52,14 @@ import java.util.List; */ public class CmdDiffClient extends BaseSvnClient implements DiffClient { + @NotNull @Override public List compare(@NotNull SvnTarget target1, @NotNull SvnTarget target2) throws VcsException { - assertUrl(target2); - if (target1.isFile()) { + assertUrl(target1); + if (target2.isFile()) { // Such combination (file and url) with "--summarize" option is supported only in svn 1.8. // For svn 1.7 "--summarize" is only supported when both targets are repository urls. - assertDirectory(target1); + assertDirectory(target2); WorkingCopyFormat format = WorkingCopyFormat.from(myFactory.createVersionClient().getVersion()); if (format.less(WorkingCopyFormat.ONE_DOT_EIGHT)) { @@ -95,6 +96,7 @@ public class CmdDiffClient extends BaseSvnClient implements DiffClient { } } + @NotNull private List parseOutput(@NotNull SvnTarget target1, @NotNull SvnTarget target2, @NotNull CommandExecutor executor) throws SvnBindException { try { @@ -114,16 +116,30 @@ public class CmdDiffClient extends BaseSvnClient implements DiffClient { } } - private ContentRevision createRemoteRevision(@NotNull FilePath remotePath, @NotNull FilePath localPath, @NotNull FileStatus status) { + @NotNull + private ContentRevision createRevision(@NotNull FilePath path, + @NotNull FilePath localPath, + @NotNull SVNRevision revision, + @NotNull FileStatus status) { + ContentRevision result; + + if (path.isNonLocal()) { // explicitly use local path for deleted items - so these items will be correctly displayed as deleted under local working copy node // and not as deleted under remote branch node (in ChangesBrowser) // NOTE, that content is still retrieved using remotePath. - return SvnRepositoryContentRevision - .create(myVcs, remotePath, status == FileStatus.DELETED ? localPath : null, SVNRevision.HEAD.getNumber()); + result = SvnRepositoryContentRevision.create(myVcs, path, status == FileStatus.DELETED ? localPath : null, revision.getNumber()); + } + else { + result = CurrentContentRevision.create(path); } - private static ContentRevision createLocalRevision(@NotNull FilePath path) { - return CurrentContentRevision.create(path); + return result; + } + + private static FilePath createFilePath(@NotNull SvnTarget target, boolean isDirectory) { + return target.isFile() + ? VcsUtil.getFilePath(target.getFile(), isDirectory) + : VcsUtil.getFilePathOnNonLocal(SvnUtil.toDecodedString(target), isDirectory); } @NotNull @@ -134,9 +150,7 @@ public class CmdDiffClient extends BaseSvnClient implements DiffClient { // TODO: 3) Properties change is currently not added as part of result change like in SvnChangeProviderContext.patchWithPropertyChange SvnTarget subTarget1 = SvnUtil.append(target1, diffPath.path, true); - String relativePath = target1.isFile() - ? FileUtil.getRelativePath(target1.getFile(), subTarget1.getFile()) - : SvnUtil.getRelativeUrl(SvnUtil.toDecodedString(target1), SvnUtil.toDecodedString(subTarget1)); + String relativePath = SvnUtil.getRelativeUrl(SvnUtil.toDecodedString(target1), SvnUtil.toDecodedString(subTarget1)); if (relativePath == null) { throw new SvnBindException("Could not get relative path for " + target1 + " and " + subTarget1); @@ -144,27 +158,19 @@ public class CmdDiffClient extends BaseSvnClient implements DiffClient { SvnTarget subTarget2 = SvnUtil.append(target2, FileUtil.toSystemIndependentName(relativePath)); - FilePath target1Path = target1.isFile() - ? VcsUtil.getFilePath(subTarget1.getFile(), diffPath.isDirectory()) - : VcsUtil.getFilePathOnNonLocal(SvnUtil.toDecodedString(subTarget1), diffPath.isDirectory()); - FilePath target2Path = VcsUtil.getFilePathOnNonLocal(SvnUtil.toDecodedString(subTarget2), diffPath.isDirectory()); + FilePath target1Path = createFilePath(subTarget1, diffPath.isDirectory()); + FilePath target2Path = createFilePath(subTarget2, diffPath.isDirectory()); FileStatus status = SvnStatusConvertor .convertStatus(SvnStatusHandler.getStatus(diffPath.itemStatus), SvnStatusHandler.getStatus(diffPath.propertiesStatus)); - // for "file + url" pair - statuses determine changes needs to be done to "url" to get "file" state - // for "url1 + url2" pair - statuses determine changes needs to be done to "url1" to get "url2" state + // statuses determine changes needs to be done to "target1" to get "target2" state ContentRevision beforeRevision = status == FileStatus.ADDED ? null - : target1.isFile() - ? createRemoteRevision(target2Path, target1Path, status) - : createRemoteRevision(target1Path, target2Path, status); + : createRevision(target1Path, target2Path, target1.getPegRevision(), status); ContentRevision afterRevision = status == FileStatus.DELETED ? null - : target1.isFile() - ? createLocalRevision(target1Path) - : createRemoteRevision(target2Path, target1Path, status); - + : createRevision(target2Path, target1Path, target2.getPegRevision(), status); return createChange(status, beforeRevision, afterRevision); } diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/diff/DiffClient.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/diff/DiffClient.java index 35c35e9313f1..dfa43ddcb4bd 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/diff/DiffClient.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/diff/DiffClient.java @@ -29,6 +29,11 @@ import java.util.List; */ public interface DiffClient extends SvnClient { + /** + * @param target1 Should always be url. + * @param target2 Could be either url or file. And should be directory if file. + */ + @NotNull List compare(@NotNull SvnTarget target1, @NotNull SvnTarget target2) throws VcsException; void unifiedDiff(@NotNull SvnTarget target1, @NotNull SvnTarget target2, @NotNull OutputStream output) throws VcsException; diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/diff/DirectoryWithBranchComparer.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/diff/DirectoryWithBranchComparer.java index 0f0e6a714037..784548d9e661 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/diff/DirectoryWithBranchComparer.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/diff/DirectoryWithBranchComparer.java @@ -24,6 +24,7 @@ import com.intellij.openapi.vfs.VfsUtilCore; import com.intellij.openapi.vfs.VirtualFile; import org.jetbrains.annotations.NotNull; import org.jetbrains.idea.svn.SvnBundle; +import org.jetbrains.idea.svn.SvnVcs; import org.jetbrains.idea.svn.WorkingCopyFormat; import org.jetbrains.idea.svn.api.ClientFactory; import org.tmatesoft.svn.core.SVNException; @@ -53,19 +54,24 @@ public class DirectoryWithBranchComparer extends ElementWithBranchComparer { titleBuilder.append(SvnBundle.message("repository.browser.compare.title", myElementUrl, FileUtil.toSystemDependentName(myVirtualFile.getPresentableUrl()))); - SvnTarget target1 = SvnTarget.fromFile(new File(myVirtualFile.getPath())); - SvnTarget target2 = SvnTarget.fromURL(myElementUrl); + SvnTarget target1 = SvnTarget.fromURL(myElementUrl); + SvnTarget target2 = SvnTarget.fromFile(new File(myVirtualFile.getPath())); changes.addAll(getClientFactory().createDiffClient().compare(target1, target2)); } @NotNull private ClientFactory getClientFactory() { - WorkingCopyFormat format = myVcs.getWorkingCopyFormat(VfsUtilCore.virtualToIoFile(myVirtualFile)); + return getClientFactory(myVcs, VfsUtilCore.virtualToIoFile(myVirtualFile)); + } + + @NotNull + public static ClientFactory getClientFactory(@NotNull SvnVcs vcs, @NotNull File file) { + WorkingCopyFormat format = vcs.getWorkingCopyFormat(file); // svn 1.7 command line "--summarize" option for "diff" command does not support comparing working copy directories with repository // directories - that is why command line is only used explicitly for svn 1.8 - return format.isOrGreater(WorkingCopyFormat.ONE_DOT_EIGHT) ? myVcs.getCommandLineFactory() : myVcs.getSvnKitFactory(); + return format.isOrGreater(WorkingCopyFormat.ONE_DOT_EIGHT) ? vcs.getCommandLineFactory() : vcs.getSvnKitFactory(); } @Override diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/diff/SvnKitDiffClient.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/diff/SvnKitDiffClient.java index 0a1b14837b7e..ec4c51288cc2 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/diff/SvnKitDiffClient.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/diff/SvnKitDiffClient.java @@ -54,6 +54,7 @@ import java.util.List; */ public class SvnKitDiffClient extends BaseSvnClient implements DiffClient { + @NotNull @Override public List compare(@NotNull SvnTarget target1, @NotNull SvnTarget target2) throws VcsException { DiffExecutor executor = new DiffExecutor(target1, target2); @@ -99,12 +100,12 @@ public class SvnKitDiffClient extends BaseSvnClient implements DiffClient { } public void run() throws SVNException { - assertUrl(myTarget2); + assertUrl(myTarget1); - if (myTarget1.isFile()) { - assertDirectory(myTarget1); + if (myTarget2.isFile()) { + assertDirectory(myTarget2); - WorkingCopyFormat format = myVcs.getWorkingCopyFormat(myTarget1.getFile()); + WorkingCopyFormat format = myVcs.getWorkingCopyFormat(myTarget2.getFile()); myChanges.addAll(WorkingCopyFormat.ONE_DOT_SIX.equals(format) ? run16Diff() : run17Diff()); } else { @@ -142,20 +143,20 @@ public class SvnKitDiffClient extends BaseSvnClient implements DiffClient { } private Collection run17Diff() throws SVNException { - final Info info1 = myVcs.getInfo(myTarget1.getFile(), SVNRevision.HEAD); + final Info info1 = myVcs.getInfo(myTarget2.getFile(), SVNRevision.HEAD); if (info1 == null) { SVNErrorMessage err = - SVNErrorMessage.create(SVNErrorCode.ENTRY_NOT_FOUND, "''{0}'' is not under version control", myTarget1); + SVNErrorMessage.create(SVNErrorCode.ENTRY_NOT_FOUND, "''{0}'' is not under version control", myTarget2); SVNErrorManager.error(err, SVNLogType.WC); } else if (info1.getURL() == null) { - SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.ENTRY_MISSING_URL, "''{0}'' has no URL", myTarget1); + SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.ENTRY_MISSING_URL, "''{0}'' has no URL", myTarget2); SVNErrorManager.error(err, SVNLogType.WC); } final SVNReporter17 reporter17 = - new SVNReporter17(myTarget1.getFile(), new SVNWCContext(myVcs.getSvnKitManager().getSvnOptions(), new ISVNEventHandler() { + new SVNReporter17(myTarget2.getFile(), new SVNWCContext(myVcs.getSvnKitManager().getSvnOptions(), new ISVNEventHandler() { @Override public void handleEvent(SVNEvent event, double progress) throws SVNException { } @@ -169,9 +170,9 @@ public class SvnKitDiffClient extends BaseSvnClient implements DiffClient { try { repository = myVcs.getSvnKitManager().createRepository(info1.getURL()); long rev = repository.getLatestRevision(); - repository2 = myVcs.getSvnKitManager().createRepository(myTarget2.getURL()); - SvnDiffEditor diffEditor = new SvnDiffEditor(myTarget1.getFile(), repository2, rev, true); - repository.diff(myTarget2.getURL(), rev, rev, null, true, SVNDepth.INFINITY, false, reporter17, + repository2 = myVcs.getSvnKitManager().createRepository(myTarget1.getURL()); + SvnDiffEditor diffEditor = new SvnDiffEditor(myTarget2.getFile(), repository2, rev, true); + repository.diff(myTarget1.getURL(), rev, rev, null, true, SVNDepth.INFINITY, false, reporter17, SVNCancellableEditor.newInstance(diffEditor, new SvnKitProgressCanceller(), null)); return diffEditor.getChangesMap().values(); @@ -193,7 +194,7 @@ public class SvnKitDiffClient extends BaseSvnClient implements DiffClient { SVNRepository repository = null; SVNRepository repository2 = null; try { - SVNAdminAreaInfo info = wcAccess.openAnchor(myTarget1.getFile(), false, SVNWCAccess.INFINITE_DEPTH); + SVNAdminAreaInfo info = wcAccess.openAnchor(myTarget2.getFile(), false, SVNWCAccess.INFINITE_DEPTH); File anchorPath = info.getAnchor().getRoot(); String target = "".equals(info.getTargetName()) ? null : info.getTargetName(); @@ -215,10 +216,10 @@ public class SvnKitDiffClient extends BaseSvnClient implements DiffClient { repository = myVcs.getSvnKitManager().createRepository(anchorURL.toString()); long rev = repository.getLatestRevision(); repository2 = - myVcs.getSvnKitManager().createRepository((target == null) ? myTarget2.getURL() : myTarget2.getURL().removePathTail()); + myVcs.getSvnKitManager().createRepository((target == null) ? myTarget1.getURL() : myTarget1.getURL().removePathTail()); SvnDiffEditor diffEditor = - new SvnDiffEditor(target == null ? myTarget1.getFile() : myTarget1.getFile().getParentFile(), repository2, rev, true); - repository.diff(myTarget2.getURL(), rev, rev, target, true, true, false, reporter, + new SvnDiffEditor(target == null ? myTarget2.getFile() : myTarget2.getFile().getParentFile(), repository2, rev, true); + repository.diff(myTarget1.getURL(), rev, rev, target, true, true, false, reporter, SVNCancellableEditor.newInstance(diffEditor, new SvnKitProgressCanceller(), null)); return diffEditor.getChangesMap().values(); diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/history/SvnDiffFromHistoryHandler.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/history/SvnDiffFromHistoryHandler.java new file mode 100644 index 000000000000..be112127ecc9 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/history/SvnDiffFromHistoryHandler.java @@ -0,0 +1,80 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jetbrains.idea.svn.history; + +import com.intellij.openapi.vcs.FilePath; +import com.intellij.openapi.vcs.VcsException; +import com.intellij.openapi.vcs.changes.Change; +import com.intellij.openapi.vcs.history.BaseDiffFromHistoryHandler; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.idea.svn.SvnUtil; +import org.jetbrains.idea.svn.SvnVcs; +import org.jetbrains.idea.svn.api.ClientFactory; +import org.jetbrains.idea.svn.diff.DirectoryWithBranchComparer; +import org.tmatesoft.svn.core.wc.SVNRevision; +import org.tmatesoft.svn.core.wc2.SvnTarget; + +import java.io.File; +import java.util.List; + +/** + * @author Konstantin Kolosovsky. + */ +public class SvnDiffFromHistoryHandler extends BaseDiffFromHistoryHandler { + + @NotNull private final SvnVcs myVcs; + + public SvnDiffFromHistoryHandler(@NotNull SvnVcs vcs) { + super(vcs.getProject()); + myVcs = vcs; + } + + @NotNull + @Override + protected List getChangesBetweenRevisions(@NotNull FilePath path, @NotNull SvnFileRevision rev1, @Nullable SvnFileRevision rev2) + throws VcsException { + File file = path.getIOFile(); + SvnTarget target1 = SvnTarget.fromURL(SvnUtil.createUrl(rev1.getURL()), rev1.getRevision()); + SvnTarget target2 = rev2 != null ? SvnTarget.fromURL(SvnUtil.createUrl(rev2.getURL()), rev2.getRevision()) : SvnTarget.fromFile(file); + + return executeDiff(path, target1, target2); + } + + @NotNull + @Override + protected List getAffectedChanges(@NotNull FilePath path, @NotNull SvnFileRevision rev) throws VcsException { + // Diff with zero revision is used here to get just affected changes under the path, and not all affected changes of the revision. + SvnTarget target1 = SvnTarget.fromURL(SvnUtil.createUrl(rev.getURL()), SVNRevision.create(0)); + SvnTarget target2 = SvnTarget.fromURL(SvnUtil.createUrl(rev.getURL()), rev.getRevision()); + + return executeDiff(path, target1, target2); + } + + @NotNull + @Override + protected String getPresentableName(@NotNull SvnFileRevision revision) { + return revision.getRevisionNumber().asString(); + } + + @NotNull + private List executeDiff(@NotNull FilePath path, @NotNull SvnTarget target1, @NotNull SvnTarget target2) throws VcsException { + File file = path.getIOFile(); + ClientFactory factory = target2.isURL() ? myVcs.getFactory(file) : DirectoryWithBranchComparer.getClientFactory(myVcs, file); + + return factory.createDiffClient().compare(target1, target2); + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/history/SvnEditCommitMessageAction.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/history/SvnEditCommitMessageAction.java index d2b820607ff4..8d1830763fc6 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/history/SvnEditCommitMessageAction.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/history/SvnEditCommitMessageAction.java @@ -40,8 +40,8 @@ import org.jetbrains.annotations.Nullable; import org.jetbrains.idea.svn.SvnPropertyKeys; import org.jetbrains.idea.svn.SvnUtil; import org.jetbrains.idea.svn.SvnVcs; +import org.jetbrains.idea.svn.properties.PropertyValue; import org.tmatesoft.svn.core.SVNException; -import org.tmatesoft.svn.core.SVNPropertyValue; import org.tmatesoft.svn.core.SVNURL; import org.tmatesoft.svn.core.wc.SVNRevision; import org.tmatesoft.svn.core.wc2.SvnTarget; @@ -145,7 +145,7 @@ public class SvnEditCommitMessageAction extends AnAction { } SvnTarget target = SvnTarget.fromURL(root); myVcs.getFactory(target).createPropertyClient() - .setRevisionProperty(target, SvnPropertyKeys.LOG, SVNRevision.create(myNumber), SVNPropertyValue.create(myNewMessage), false); + .setRevisionProperty(target, SvnPropertyKeys.LOG, SVNRevision.create(myNumber), PropertyValue.create(myNewMessage), false); } catch (SVNException e) { myException = new VcsException(e); diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/history/SvnHistoryProvider.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/history/SvnHistoryProvider.java index 9e9dd7e1befc..b23b57772535 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/history/SvnHistoryProvider.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/history/SvnHistoryProvider.java @@ -73,7 +73,7 @@ public class SvnHistoryProvider @Override public DiffFromHistoryHandler getHistoryDiffHandler() { - return null; + return new SvnDiffFromHistoryHandler(myVcs); } @Override diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/history/SvnHistorySession.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/history/SvnHistorySession.java index f2f91bceec14..8ae2da7cf8b3 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/history/SvnHistorySession.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/history/SvnHistorySession.java @@ -77,11 +77,6 @@ public class SvnHistorySession extends VcsAbstractHistorySession { return myCommittedPath; } - @Override - public boolean isContentAvailable(final VcsFileRevision revision) { - return !myCommittedPath.isDirectory(); - } - public boolean isHaveMergeSources() { return myHaveMergeSources; } diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/ignore/SvnPropertyService.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/ignore/SvnPropertyService.java index 8a9dbf760922..e0bdc15f78b1 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/ignore/SvnPropertyService.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/ignore/SvnPropertyService.java @@ -24,8 +24,7 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.idea.svn.SvnPropertyKeys; import org.jetbrains.idea.svn.SvnVcs; import org.jetbrains.idea.svn.api.Depth; -import org.tmatesoft.svn.core.SVNPropertyValue; -import org.tmatesoft.svn.core.wc.SVNPropertyData; +import org.jetbrains.idea.svn.properties.PropertyValue; import org.tmatesoft.svn.core.wc.SVNRevision; import org.tmatesoft.svn.core.wc2.SvnTarget; @@ -68,7 +67,7 @@ public class SvnPropertyService { protected final boolean myCanUseCachedProperty; protected abstract void processFolder(final VirtualFile folder, final File folderDir, final Set data, - final SVNPropertyValue propertyValue) throws VcsException; + final PropertyValue propertyValue) throws VcsException; protected abstract void onAfterProcessing(final VirtualFile[] file) throws VcsException; @@ -92,14 +91,12 @@ public class SvnPropertyService { } final File dir = new File(entry.getKey().getPath()); try { - final SVNPropertyValue value; + final PropertyValue value; if (myCanUseCachedProperty) { value = myVcs.getPropertyWithCaching(entry.getKey(), SvnPropertyKeys.SVN_IGNORE); } else { - final SVNPropertyData data = - myVcs.getFactory(dir).createPropertyClient() - .getProperty(SvnTarget.fromFile(dir), SvnPropertyKeys.SVN_IGNORE, false, SVNRevision.WORKING); - value = data == null ? null : data.getValue(); + value = myVcs.getFactory(dir).createPropertyClient() + .getProperty(SvnTarget.fromFile(dir), SvnPropertyKeys.SVN_IGNORE, false, SVNRevision.WORKING); } processFolder(entry.getKey(), dir, entry.getValue(), value); } @@ -127,7 +124,7 @@ public class SvnPropertyService { return (! myFilesOk) && (! myExtensionOk); } - protected void processFolder(final VirtualFile folder, final File folderDir, final Set data, final SVNPropertyValue propertyValue) + protected void processFolder(final VirtualFile folder, final File folderDir, final Set data, final PropertyValue propertyValue) throws VcsException { if (propertyValue == null) { myFilesOk = false; @@ -135,7 +132,7 @@ public class SvnPropertyService { return; } final Set ignorePatterns = new HashSet(); - final StringTokenizer st = new StringTokenizer(SVNPropertyValue.getPropertyAsString(propertyValue), "\r\n "); + final StringTokenizer st = new StringTokenizer(PropertyValue.toString(propertyValue), "\r\n "); while (st.hasMoreElements()) { final String ignorePattern = (String)st.nextElement(); ignorePatterns.add(ignorePattern); @@ -180,14 +177,14 @@ public class SvnPropertyService { return false; } - protected abstract String getNewPropertyValue(final Set data, final SVNPropertyValue propertyValue); + protected abstract String getNewPropertyValue(final Set data, final PropertyValue propertyValue); - protected void processFolder(final VirtualFile folder, final File folderDir, final Set data, final SVNPropertyValue propertyValue) + protected void processFolder(final VirtualFile folder, final File folderDir, final Set data, final PropertyValue propertyValue) throws VcsException { String newValue = getNewPropertyValue(data, propertyValue); newValue = (newValue.trim().isEmpty()) ? null : newValue; myVcs.getFactory(folderDir).createPropertyClient() - .setProperty(folderDir, SvnPropertyKeys.SVN_IGNORE, SVNPropertyValue.create(newValue), Depth.EMPTY, false); + .setProperty(folderDir, SvnPropertyKeys.SVN_IGNORE, PropertyValue.create(newValue), Depth.EMPTY, false); if (myUseCommonExtension) { dirtyScopeManager.dirDirtyRecursively(folder); @@ -216,9 +213,9 @@ public class SvnPropertyService { super(activeVcs, project, useCommonExtension); } - protected String getNewPropertyValue(final Set data, final SVNPropertyValue propertyValue) { + protected String getNewPropertyValue(final Set data, final PropertyValue propertyValue) { if (propertyValue != null) { - return getNewPropertyValueForRemove(data, SVNPropertyValue.getPropertyAsString(propertyValue)); + return getNewPropertyValueForRemove(data, PropertyValue.toString(propertyValue)); } return ""; } @@ -241,7 +238,7 @@ public class SvnPropertyService { super(activeVcs, project, useCommonExtension); } - protected String getNewPropertyValue(final Set data, final SVNPropertyValue propertyValue) { + protected String getNewPropertyValue(final Set data, final PropertyValue propertyValue) { final String ignoreString; if (data.size() == 1) { ignoreString = data.iterator().next(); @@ -252,7 +249,7 @@ public class SvnPropertyService { } ignoreString = sb.toString(); } - return (propertyValue == null) ? ignoreString : (SVNPropertyValue.getPropertyAsString(propertyValue) + '\n' + ignoreString); + return (propertyValue == null) ? ignoreString : (PropertyValue.toString(propertyValue) + '\n' + ignoreString); } } } diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/integrate/GatheringChangelistBuilder.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/integrate/GatheringChangelistBuilder.java index b6228fca921d..1e031e20d408 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/integrate/GatheringChangelistBuilder.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/integrate/GatheringChangelistBuilder.java @@ -20,7 +20,10 @@ import com.intellij.openapi.util.Comparing; import com.intellij.openapi.vcs.FilePath; import com.intellij.openapi.vcs.VcsException; import com.intellij.openapi.vcs.VcsKey; -import com.intellij.openapi.vcs.changes.*; +import com.intellij.openapi.vcs.changes.Change; +import com.intellij.openapi.vcs.changes.ChangeList; +import com.intellij.openapi.vcs.changes.ChangesUtil; +import com.intellij.openapi.vcs.changes.EmptyChangelistBuilder; import com.intellij.openapi.vcs.update.UpdatedFilesReverseSide; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.util.containers.ContainerUtil; @@ -28,12 +31,14 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.idea.svn.SvnPropertyKeys; import org.jetbrains.idea.svn.SvnVcs; -import org.tmatesoft.svn.core.wc.SVNPropertyData; +import org.jetbrains.idea.svn.properties.PropertyValue; import org.tmatesoft.svn.core.wc.SVNRevision; import org.tmatesoft.svn.core.wc2.SvnTarget; import java.io.File; -import java.util.*; +import java.util.Iterator; +import java.util.List; +import java.util.Set; public class GatheringChangelistBuilder extends EmptyChangelistBuilder { @@ -92,13 +97,13 @@ public class GatheringChangelistBuilder extends EmptyChangelistBuilder { SvnTarget target = SvnTarget.fromFile(file); try { - SVNPropertyData current = + PropertyValue current = myVcs.getFactory(target).createPropertyClient().getProperty(target, SvnPropertyKeys.MERGE_INFO, false, SVNRevision.WORKING); - SVNPropertyData base = + PropertyValue base = myVcs.getFactory(target).createPropertyClient().getProperty(target, SvnPropertyKeys.MERGE_INFO, false, SVNRevision.BASE); if (current != null) { - return base == null || !Comparing.equal(current.getValue(), base.getValue()); + return base == null || !Comparing.equal(current, base); } } catch (VcsException e) { diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/mergeinfo/BranchInfo.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/mergeinfo/BranchInfo.java index 62638291b485..701bb2575001 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/mergeinfo/BranchInfo.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/mergeinfo/BranchInfo.java @@ -22,10 +22,10 @@ import com.intellij.util.containers.MultiMap; import org.jetbrains.idea.svn.SvnVcs; import org.jetbrains.idea.svn.history.SvnChangeList; import org.jetbrains.idea.svn.info.Info; +import org.jetbrains.idea.svn.properties.PropertyValue; import org.tmatesoft.svn.core.*; import org.tmatesoft.svn.core.internal.util.SVNMergeInfoUtil; import org.tmatesoft.svn.core.internal.util.SVNPathUtil; -import org.tmatesoft.svn.core.wc.SVNPropertyData; import org.tmatesoft.svn.core.wc.SVNRevision; import org.tmatesoft.svn.core.wc2.SvnTarget; @@ -205,7 +205,7 @@ public class BranchInfo { return SvnMergeInfoCache.MergeCheckResult.getInstance(mergeInfo.contains(revisionAsked)); } - final SVNPropertyData mergeinfoProperty; + final PropertyValue mergeinfoProperty; SvnTarget target = SvnTarget.fromURL(branchUrl); try { @@ -235,7 +235,7 @@ public class BranchInfo { return goUpInRepo(revisionAsked, targetRevision, newBranchUrl, newTrunkUrl); } // process - return processMergeinfoProperty(keyString, revisionAsked, mergeinfoProperty.getValue(), trunkUrl, false); + return processMergeinfoProperty(keyString, revisionAsked, mergeinfoProperty, trunkUrl, false); } private Info getInfo(final File pathFile) { @@ -277,7 +277,7 @@ public class BranchInfo { return SvnMergeInfoCache.MergeCheckResult.getInstance(merged); } - final SVNPropertyData mergeinfoProperty; + final PropertyValue mergeinfoProperty; try { if (actualRevision == targetRevisionCorrected) { // look in WC @@ -302,11 +302,11 @@ public class BranchInfo { return goUp(revisionAsked, targetRevisionCorrected, branchRootPath, path, trunkUrl); } // process - return processMergeinfoProperty(keyString, revisionAsked, mergeinfoProperty.getValue(), trunkUrl, self); + return processMergeinfoProperty(keyString, revisionAsked, mergeinfoProperty, trunkUrl, self); } private SvnMergeInfoCache.MergeCheckResult processMergeinfoProperty(final String pathWithRevisionNumber, final long revisionAsked, - final SVNPropertyValue value, final String trunkRelativeUrl, + final PropertyValue value, final String trunkRelativeUrl, final boolean self) { final String valueAsString = value.toString().trim(); @@ -318,7 +318,7 @@ public class BranchInfo { final Map map; try { - map = SVNMergeInfoUtil.parseMergeInfo(new StringBuffer(replaceSeparators(value.getString())), null); + map = SVNMergeInfoUtil.parseMergeInfo(new StringBuffer(replaceSeparators(value.toString())), null); } catch (SVNException e) { LOG.info(e); diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/mergeinfo/OneRecursiveShotMergeInfoWorker.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/mergeinfo/OneRecursiveShotMergeInfoWorker.java index 0ea95c99c4d4..225bc6ad0396 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/mergeinfo/OneRecursiveShotMergeInfoWorker.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/mergeinfo/OneRecursiveShotMergeInfoWorker.java @@ -23,11 +23,12 @@ import com.intellij.util.PairProcessor; import org.jetbrains.annotations.NotNull; import org.jetbrains.idea.svn.api.Depth; import org.jetbrains.idea.svn.dialogs.MergeContext; +import org.jetbrains.idea.svn.properties.PropertyConsumer; +import org.jetbrains.idea.svn.properties.PropertyData; +import org.jetbrains.idea.svn.properties.PropertyValue; import org.tmatesoft.svn.core.*; import org.tmatesoft.svn.core.internal.util.SVNMergeInfoUtil; import org.tmatesoft.svn.core.internal.util.SVNPathUtil; -import org.tmatesoft.svn.core.wc.ISVNPropertyHandler; -import org.tmatesoft.svn.core.wc.SVNPropertyData; import org.tmatesoft.svn.core.wc.SVNRevision; import org.tmatesoft.svn.core.wc2.SvnTarget; @@ -63,19 +64,19 @@ public class OneRecursiveShotMergeInfoWorker implements MergeInfoWorker { public void prepare() throws VcsException { final Depth depth = Depth.allOrEmpty(myMergeContext.getVcs().getSvnConfiguration().isCheckNestedForQuickMerge()); - ISVNPropertyHandler handler = new ISVNPropertyHandler() { - public void handleProperty(File path, SVNPropertyData property) throws SVNException { + PropertyConsumer handler = new PropertyConsumer() { + public void handleProperty(File path, PropertyData property) throws SVNException { final String key = keyFromFile(path); synchronized (myLock) { myDataMap.put(key, SVNMergeInfoUtil - .parseMergeInfo(new StringBuffer(replaceSeparators(SVNPropertyValue.getPropertyAsString(property.getValue()))), null)); + .parseMergeInfo(new StringBuffer(replaceSeparators(PropertyValue.toString(property.getValue()))), null)); } } - public void handleProperty(SVNURL url, SVNPropertyData property) throws SVNException { + public void handleProperty(SVNURL url, PropertyData property) throws SVNException { } - public void handleProperty(long revision, SVNPropertyData property) throws SVNException { + public void handleProperty(long revision, PropertyData property) throws SVNException { } }; diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/properties/CmdPropertyClient.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/properties/CmdPropertyClient.java index e855abe1ec2e..e053776d14ce 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/properties/CmdPropertyClient.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/properties/CmdPropertyClient.java @@ -10,9 +10,8 @@ import org.jetbrains.idea.svn.commandLine.CommandExecutor; import org.jetbrains.idea.svn.commandLine.CommandUtil; import org.jetbrains.idea.svn.commandLine.SvnCommandName; import org.jetbrains.idea.svn.info.Info; -import org.tmatesoft.svn.core.*; -import org.tmatesoft.svn.core.wc.ISVNPropertyHandler; -import org.tmatesoft.svn.core.wc.SVNPropertyData; +import org.tmatesoft.svn.core.SVNException; +import org.tmatesoft.svn.core.SVNURL; import org.tmatesoft.svn.core.wc.SVNRevision; import org.tmatesoft.svn.core.wc2.SvnTarget; @@ -24,6 +23,7 @@ import javax.xml.bind.annotation.XmlValue; import java.io.File; import java.util.ArrayList; import java.util.List; +import java.util.Map; /** * @author Konstantin Kolosovsky. @@ -32,10 +32,10 @@ public class CmdPropertyClient extends BaseSvnClient implements PropertyClient { @Nullable @Override - public SVNPropertyData getProperty(@NotNull SvnTarget target, - @NotNull String property, - boolean revisionProperty, - @Nullable SVNRevision revision) + public PropertyValue getProperty(@NotNull SvnTarget target, + @NotNull String property, + boolean revisionProperty, + @Nullable SVNRevision revision) throws VcsException { List parameters = new ArrayList(); @@ -57,7 +57,9 @@ public class CmdPropertyClient extends BaseSvnClient implements PropertyClient { parameters.add("--xml"); CommandExecutor command = execute(myVcs, target, SvnCommandName.propget, parameters, null); - return parseSingleProperty(target, command.getOutput()); + PropertyData data = parseSingleProperty(target, command.getOutput()); + + return data != null ? data.getValue() : null; } @Override @@ -65,7 +67,7 @@ public class CmdPropertyClient extends BaseSvnClient implements PropertyClient { @NotNull String property, @Nullable SVNRevision revision, @Nullable Depth depth, - @Nullable ISVNPropertyHandler handler) throws VcsException { + @Nullable PropertyConsumer handler) throws VcsException { List parameters = new ArrayList(); parameters.add(property); @@ -79,7 +81,7 @@ public class CmdPropertyClient extends BaseSvnClient implements PropertyClient { public void list(@NotNull SvnTarget target, @Nullable SVNRevision revision, @Nullable Depth depth, - @Nullable ISVNPropertyHandler handler) throws VcsException { + @Nullable PropertyConsumer handler) throws VcsException { List parameters = new ArrayList(); fillListParameters(target, revision, depth, parameters, true); @@ -90,39 +92,39 @@ public class CmdPropertyClient extends BaseSvnClient implements PropertyClient { @Override public void setProperty(@NotNull File file, @NotNull String property, - @Nullable SVNPropertyValue value, + @Nullable PropertyValue value, @Nullable Depth depth, boolean force) throws VcsException { runSetProperty(SvnTarget.fromFile(file), property, null, depth, value, force); } @Override - public void setProperties(@NotNull File file, @NotNull SVNProperties properties) throws VcsException { - SVNProperties currentProperties = collectPropertiesToDelete(file); + public void setProperties(@NotNull File file, @NotNull PropertiesMap properties) throws VcsException { + PropertiesMap currentProperties = collectPropertiesToDelete(file); currentProperties.putAll(properties); - for (String propertyName : currentProperties.nameSet()) { - setProperty(file, propertyName, currentProperties.getSVNPropertyValue(propertyName), Depth.EMPTY, true); + for (Map.Entry entry : currentProperties.entrySet()) { + setProperty(file, entry.getKey(), entry.getValue(), Depth.EMPTY, true); } } @NotNull - private SVNProperties collectPropertiesToDelete(@NotNull File file) throws VcsException { - final SVNProperties result = new SVNProperties(); + private PropertiesMap collectPropertiesToDelete(@NotNull File file) throws VcsException { + final PropertiesMap result = new PropertiesMap(); - list(SvnTarget.fromFile(file), null, Depth.EMPTY, new ISVNPropertyHandler() { + list(SvnTarget.fromFile(file), null, Depth.EMPTY, new PropertyConsumer() { @Override - public void handleProperty(File path, SVNPropertyData property) throws SVNException { + public void handleProperty(File path, PropertyData property) throws SVNException { // null indicates property will be deleted - result.put(property.getName(), (SVNPropertyValue)null); + result.put(property.getName(), null); } @Override - public void handleProperty(SVNURL url, SVNPropertyData property) throws SVNException { + public void handleProperty(SVNURL url, PropertyData property) throws SVNException { } @Override - public void handleProperty(long revision, SVNPropertyData property) throws SVNException { + public void handleProperty(long revision, PropertyData property) throws SVNException { } }); @@ -133,7 +135,7 @@ public class CmdPropertyClient extends BaseSvnClient implements PropertyClient { public void setRevisionProperty(@NotNull SvnTarget target, @NotNull String property, @NotNull SVNRevision revision, - @Nullable SVNPropertyValue value, + @Nullable PropertyValue value, boolean force) throws VcsException { runSetProperty(target, property, revision, null, value, force); } @@ -142,7 +144,7 @@ public class CmdPropertyClient extends BaseSvnClient implements PropertyClient { @NotNull String property, @Nullable SVNRevision revision, @Nullable Depth depth, - @Nullable SVNPropertyValue value, + @Nullable PropertyValue value, boolean force) throws VcsException { List parameters = new ArrayList(); boolean isDelete = value == null; @@ -153,7 +155,7 @@ public class CmdPropertyClient extends BaseSvnClient implements PropertyClient { CommandUtil.put(parameters, revision); } if (!isDelete) { - parameters.add(SVNPropertyValue.getPropertyAsString(value)); + parameters.add(PropertyValue.toString(value)); // --force could only be used in "propset" command, but not in "propdel" command CommandUtil.put(parameters, force, "--force"); } @@ -180,21 +182,22 @@ public class CmdPropertyClient extends BaseSvnClient implements PropertyClient { CommandUtil.put(parameters, verbose, "--verbose"); } - private SVNPropertyData parseSingleProperty(SvnTarget target, String output) throws VcsException { - final SVNPropertyData[] data = new SVNPropertyData[1]; - ISVNPropertyHandler handler = new ISVNPropertyHandler() { + @Nullable + private PropertyData parseSingleProperty(SvnTarget target, String output) throws VcsException { + final PropertyData[] data = new PropertyData[1]; + PropertyConsumer handler = new PropertyConsumer() { @Override - public void handleProperty(File path, SVNPropertyData property) throws SVNException { + public void handleProperty(File path, PropertyData property) throws SVNException { data[0] = property; } @Override - public void handleProperty(SVNURL url, SVNPropertyData property) throws SVNException { + public void handleProperty(SVNURL url, PropertyData property) throws SVNException { data[0] = property; } @Override - public void handleProperty(long revision, SVNPropertyData property) throws SVNException { + public void handleProperty(long revision, PropertyData property) throws SVNException { data[0] = property; } }; @@ -204,7 +207,7 @@ public class CmdPropertyClient extends BaseSvnClient implements PropertyClient { return data[0]; } - private static void parseOutput(SvnTarget target, String output, ISVNPropertyHandler handler) throws VcsException { + private static void parseOutput(SvnTarget target, String output, PropertyConsumer handler) throws VcsException { try { Properties properties = CommandUtil.parse(output, Properties.class); @@ -231,7 +234,7 @@ public class CmdPropertyClient extends BaseSvnClient implements PropertyClient { } } - private static void invokeHandler(@NotNull SvnTarget target, @Nullable SVNPropertyData data, @Nullable ISVNPropertyHandler handler) + private static void invokeHandler(@NotNull SvnTarget target, @Nullable PropertyData data, @Nullable PropertyConsumer handler) throws SVNException { if (handler != null && data != null) { if (target.isFile()) { @@ -242,7 +245,7 @@ public class CmdPropertyClient extends BaseSvnClient implements PropertyClient { } } - private static void invokeHandler(long revision, @Nullable SVNPropertyData data, @Nullable ISVNPropertyHandler handler) + private static void invokeHandler(long revision, @Nullable PropertyData data, @Nullable PropertyConsumer handler) throws SVNException { if (handler != null && data != null) { handler.handleProperty(revision, data); @@ -250,13 +253,13 @@ public class CmdPropertyClient extends BaseSvnClient implements PropertyClient { } @Nullable - private static SVNPropertyData create(@NotNull String property, @Nullable String value) { - SVNPropertyData result = null; + private static PropertyData create(@NotNull String property, @Nullable String value) { + PropertyData result = null; // such behavior is required to compatibility with SVNKit as some logic in merge depends on // whether null property data or property data with empty string value is returned if (value != null) { - result = new SVNPropertyData(property, SVNPropertyValue.create(value.trim()), LF_SEPARATOR_OPTIONS); + result = new PropertyData(property, PropertyValue.create(value.trim())); } return result; diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/properties/ExternalsDefinitionParser.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/properties/ExternalsDefinitionParser.java new file mode 100644 index 000000000000..b3ec74e397cb --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/properties/ExternalsDefinitionParser.java @@ -0,0 +1,106 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jetbrains.idea.svn.properties; + +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.util.containers.ContainerUtil; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.idea.svn.commandLine.SvnBindException; + +import java.util.HashMap; +import java.util.Map; + +/** + * @author Konstantin Kolosovsky. + */ +public class ExternalsDefinitionParser { + + /** + * Parses "svn:externals" property in format starting from svn 1.5. + * + * @return map of externals definitions: key - relative directory, value - corresponding complete externals definition. + */ + @NotNull + public static Map parseExternalsProperty(@NotNull String externals) throws SvnBindException { + HashMap map = ContainerUtil.newHashMap(); + + for (String external : StringUtil.splitByLines(externals, true)) { + map.put(parseRelativeDirectory(external), external); + } + + return map; + } + + /** + * Parses relative directory from externals definition (in format starting from svn 1.5). Restrictions for relative directory: + * - is at the end of externals definition separated from other parameters by ' ' char + * - could be quoted with '"' char + * - certain chars could be escaped with '\' char + */ + @NotNull + public static String parseRelativeDirectory(@NotNull String s) throws SvnBindException { + s = s.trim(); + + int length = s.length(); + String result; + + if (isUnescapedQuote(s, length - 1)) { + int index = lastUnescapedIndexOf(s, length - 1, '"'); + assertIndex(s, index, "Could not find start quote"); + result = s.substring(index + 1, length - 1); + } + else { + int index = lastUnescapedIndexOf(s, length, ' '); + assertIndex(s, index, "Could not find separating space"); + result = s.substring(index + 1); + } + + return unescape(result); + } + + private static void assertIndex(@NotNull String s, int index, @NotNull String message) throws SvnBindException { + if (index < 0) { + throw new SvnBindException(message + " - " + s); + } + } + + @NotNull + private static String unescape(@NotNull String s) { + return s.replace("\\", ""); + } + + /** + * "from" index is excluded. + */ + private static int lastUnescapedIndexOf(@NotNull String s, int from, char c) { + int result = from; + + do { + result = s.lastIndexOf(c, result - 1); + } + while (result != -1 && !isUnescaped(s, result, c)); + + return result; + } + + private static boolean isUnescapedQuote(@NotNull String s, int index) { + return isUnescaped(s, index, '"'); + } + + private static boolean isUnescaped(@NotNull String s, int index, char c) { + return StringUtil.isChar(s, index, c) && !StringUtil.isChar(s, index - 1, '\\'); + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/properties/PropertiesMap.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/properties/PropertiesMap.java new file mode 100644 index 000000000000..c5857aa4b345 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/properties/PropertiesMap.java @@ -0,0 +1,24 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jetbrains.idea.svn.properties; + +import com.intellij.util.containers.HashMap; + +/** + * @author Konstantin Kolosovsky. + */ +public class PropertiesMap extends HashMap { +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/properties/PropertyClient.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/properties/PropertyClient.java index 1b61d59c0e20..42acfc8109d7 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/properties/PropertyClient.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/properties/PropertyClient.java @@ -1,18 +1,10 @@ package org.jetbrains.idea.svn.properties; import com.intellij.openapi.vcs.VcsException; -import com.intellij.openapi.vfs.CharsetToolkit; -import com.intellij.util.LineSeparator; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.idea.svn.api.Depth; import org.jetbrains.idea.svn.api.SvnClient; -import org.tmatesoft.svn.core.SVNProperties; -import org.tmatesoft.svn.core.SVNPropertyValue; -import org.tmatesoft.svn.core.internal.wc.DefaultSVNOptions; -import org.tmatesoft.svn.core.wc.ISVNOptions; -import org.tmatesoft.svn.core.wc.ISVNPropertyHandler; -import org.tmatesoft.svn.core.wc.SVNPropertyData; import org.tmatesoft.svn.core.wc.SVNRevision; import org.tmatesoft.svn.core.wc2.SvnTarget; @@ -23,40 +15,33 @@ import java.io.File; */ public interface PropertyClient extends SvnClient { - ISVNOptions LF_SEPARATOR_OPTIONS = new DefaultSVNOptions() { - @Override - public byte[] getNativeEOL() { - return CharsetToolkit.getUtf8Bytes(LineSeparator.LF.getSeparatorString()); - } - }; - @Nullable - SVNPropertyData getProperty(@NotNull final SvnTarget target, - @NotNull final String property, - boolean revisionProperty, - @Nullable SVNRevision revision) throws VcsException; + PropertyValue getProperty(@NotNull final SvnTarget target, + @NotNull final String property, + boolean revisionProperty, + @Nullable SVNRevision revision) throws VcsException; void getProperty(@NotNull SvnTarget target, @NotNull String property, @Nullable SVNRevision revision, @Nullable Depth depth, - @Nullable ISVNPropertyHandler handler) throws VcsException; + @Nullable PropertyConsumer handler) throws VcsException; void list(@NotNull SvnTarget target, @Nullable SVNRevision revision, @Nullable Depth depth, - @Nullable ISVNPropertyHandler handler) throws VcsException; + @Nullable PropertyConsumer handler) throws VcsException; void setProperty(@NotNull File file, @NotNull String property, - @Nullable SVNPropertyValue value, + @Nullable PropertyValue value, @Nullable Depth depth, boolean force) throws VcsException; - void setProperties(@NotNull File file, @NotNull SVNProperties properties) throws VcsException; + void setProperties(@NotNull File file, @NotNull PropertiesMap properties) throws VcsException; void setRevisionProperty(@NotNull SvnTarget target, @NotNull String property, @NotNull SVNRevision revision, - @Nullable SVNPropertyValue value, + @Nullable PropertyValue value, boolean force) throws VcsException; } diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/properties/PropertyConsumer.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/properties/PropertyConsumer.java new file mode 100644 index 000000000000..f45e09df39d5 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/properties/PropertyConsumer.java @@ -0,0 +1,33 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jetbrains.idea.svn.properties; + +import org.tmatesoft.svn.core.SVNException; +import org.tmatesoft.svn.core.SVNURL; + +import java.io.File; + +/** + * @author Konstantin Kolosovsky. + */ +public interface PropertyConsumer { + + void handleProperty(File path, PropertyData property) throws SVNException; + + void handleProperty(SVNURL url, PropertyData property) throws SVNException; + + void handleProperty(long revision, PropertyData property) throws SVNException; +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/properties/PropertyData.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/properties/PropertyData.java new file mode 100644 index 000000000000..3953636d870c --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/properties/PropertyData.java @@ -0,0 +1,53 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jetbrains.idea.svn.properties; + +import org.jetbrains.annotations.Nullable; +import org.tmatesoft.svn.core.wc.SVNPropertyData; + +/** + * @author Konstantin Kolosovsky. + */ +public class PropertyData { + + private final PropertyValue myValue; + + private final String myName; + + public PropertyData(String name, PropertyValue value) { + myName = name; + myValue = value; + } + + public String getName() { + return myName; + } + + public PropertyValue getValue() { + return myValue; + } + + @Nullable + public static PropertyData create(@Nullable SVNPropertyData data) { + PropertyData result = null; + + if (data != null) { + result = new PropertyData(data.getName(), PropertyValue.create(data.getValue())); + } + + return result; + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/properties/PropertyValue.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/properties/PropertyValue.java new file mode 100644 index 000000000000..62cb7cbd0340 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/properties/PropertyValue.java @@ -0,0 +1,53 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jetbrains.idea.svn.properties; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.tmatesoft.svn.core.SVNPropertyValue; + +/** + * TODO: Add correct support of binary properties - support in api, diff, etc. + * + * @author Konstantin Kolosovsky. + */ +public class PropertyValue { + + @NotNull private final String myValue; + + @Nullable + public static PropertyValue create(@Nullable SVNPropertyValue value) { + return create(SVNPropertyValue.getPropertyAsString(value)); + } + + private PropertyValue(@NotNull String propertyValue) { + myValue = propertyValue; + } + + @Nullable + public static PropertyValue create(@Nullable String propertyValue) { + return propertyValue == null ? null : new PropertyValue(propertyValue); + } + + @Nullable + public static String toString(@Nullable PropertyValue value) { + return value == null ? null : value.myValue; + } + + public String toString() { + return myValue; + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/properties/SvnKitPropertyClient.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/properties/SvnKitPropertyClient.java index 4ad3f10afccc..23fedd3c7ac8 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/properties/SvnKitPropertyClient.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/properties/SvnKitPropertyClient.java @@ -1,42 +1,57 @@ package org.jetbrains.idea.svn.properties; import com.intellij.openapi.vcs.VcsException; +import com.intellij.openapi.vfs.CharsetToolkit; +import com.intellij.util.LineSeparator; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.idea.svn.api.BaseSvnClient; import org.jetbrains.idea.svn.api.Depth; import org.jetbrains.idea.svn.commandLine.SvnBindException; import org.tmatesoft.svn.core.*; +import org.tmatesoft.svn.core.internal.wc.DefaultSVNOptions; import org.tmatesoft.svn.core.wc.*; import org.tmatesoft.svn.core.wc2.SvnTarget; import java.io.File; +import java.util.Map; /** * @author Konstantin Kolosovsky. */ public class SvnKitPropertyClient extends BaseSvnClient implements PropertyClient { + public static final ISVNOptions LF_SEPARATOR_OPTIONS = new DefaultSVNOptions() { + @Override + public byte[] getNativeEOL() { + return CharsetToolkit.getUtf8Bytes(LineSeparator.LF.getSeparatorString()); + } + }; + @Nullable @Override - public SVNPropertyData getProperty(@NotNull SvnTarget target, - @NotNull String property, - boolean revisionProperty, - @Nullable SVNRevision revision) throws VcsException { + public PropertyValue getProperty(@NotNull SvnTarget target, + @NotNull String property, + boolean revisionProperty, + @Nullable SVNRevision revision) throws VcsException { + PropertyData resultData; + try { if (!revisionProperty) { if (target.isFile()) { - return createClient().doGetProperty(target.getFile(), property, target.getPegRevision(), revision); + resultData = PropertyData.create(createClient().doGetProperty(target.getFile(), property, target.getPegRevision(), revision)); } else { - return createClient().doGetProperty(target.getURL(), property, target.getPegRevision(), revision); + resultData = PropertyData.create(createClient().doGetProperty(target.getURL(), property, target.getPegRevision(), revision)); } } else { - return getRevisionProperty(target, property, revision); + resultData = getRevisionProperty(target, property, revision); } } catch (SVNException e) { throw new VcsException(e); } + + return resultData != null ? resultData.getValue() : null; } @NotNull @@ -52,7 +67,7 @@ public class SvnKitPropertyClient extends BaseSvnClient implements PropertyClien @NotNull String property, @Nullable SVNRevision revision, @Nullable Depth depth, - @Nullable ISVNPropertyHandler handler) throws VcsException { + @Nullable PropertyConsumer handler) throws VcsException { runGetProperty(target, property, revision, depth, handler); } @@ -60,18 +75,18 @@ public class SvnKitPropertyClient extends BaseSvnClient implements PropertyClien public void list(@NotNull SvnTarget target, @Nullable SVNRevision revision, @Nullable Depth depth, - @Nullable ISVNPropertyHandler handler) throws VcsException { + @Nullable PropertyConsumer handler) throws VcsException { runGetProperty(target, null, revision, depth, handler); } @Override public void setProperty(@NotNull File file, @NotNull String property, - @Nullable SVNPropertyValue value, + @Nullable PropertyValue value, @Nullable Depth depth, boolean force) throws VcsException { try { - createClient().doSetProperty(file, property, value, force, toDepth(depth), null, null); + createClient().doSetProperty(file, property, toPropertyValue(value), force, toDepth(depth), null, null); } catch (SVNException e) { throw new SvnBindException(e); @@ -79,12 +94,13 @@ public class SvnKitPropertyClient extends BaseSvnClient implements PropertyClien } @Override - public void setProperties(@NotNull File file, @NotNull SVNProperties properties) throws VcsException { + public void setProperties(@NotNull File file, @NotNull PropertiesMap properties) throws VcsException { + final SVNProperties propertiesToSet = toSvnProperties(properties); try { createClient().doSetProperty(file, new ISVNPropertyValueProvider() { @Override public SVNProperties providePropertyValues(File path, SVNProperties properties) throws SVNException { - return properties; + return propertiesToSet; } }, true, SVNDepth.EMPTY, null, null); } @@ -97,14 +113,14 @@ public class SvnKitPropertyClient extends BaseSvnClient implements PropertyClien public void setRevisionProperty(@NotNull SvnTarget target, @NotNull String property, @NotNull SVNRevision revision, - @Nullable SVNPropertyValue value, + @Nullable PropertyValue value, boolean force) throws VcsException { try { if (target.isFile()) { - createClient().doSetRevisionProperty(target.getFile(), revision, property, value, force, null); + createClient().doSetRevisionProperty(target.getFile(), revision, property, toPropertyValue(value), force, null); } else { - createClient().doSetRevisionProperty(target.getURL(), revision, property, value, force, null); + createClient().doSetRevisionProperty(target.getURL(), revision, property, toPropertyValue(value), force, null); } } catch (SVNException e) { @@ -112,27 +128,39 @@ public class SvnKitPropertyClient extends BaseSvnClient implements PropertyClien } } + @NotNull + private static SVNProperties toSvnProperties(@NotNull PropertiesMap properties) { + SVNProperties result = new SVNProperties(); + + for (Map.Entry entry : properties.entrySet()) { + result.put(entry.getKey(), toPropertyValue(entry.getValue())); + } + + return result; + } + private void runGetProperty(@NotNull SvnTarget target, @Nullable String property, @Nullable SVNRevision revision, @Nullable Depth depth, - @Nullable ISVNPropertyHandler handler) throws VcsException { + @Nullable PropertyConsumer handler) throws VcsException { SVNWCClient client = createClient(); try { if (target.isURL()) { - client.doGetProperty(target.getURL(), property, target.getPegRevision(), revision, toDepth(depth), handler); + client.doGetProperty(target.getURL(), property, target.getPegRevision(), revision, toDepth(depth), toHandler(handler)); } else { - client.doGetProperty(target.getFile(), property, target.getPegRevision(), revision, toDepth(depth), handler, null); + client.doGetProperty(target.getFile(), property, target.getPegRevision(), revision, toDepth(depth), toHandler(handler), null); } } catch (SVNException e) { throw new VcsException(e); } } - private SVNPropertyData getRevisionProperty(@NotNull SvnTarget target, @NotNull final String property, @Nullable SVNRevision revision) throws SVNException{ + private PropertyData getRevisionProperty(@NotNull SvnTarget target, @NotNull final String property, @Nullable SVNRevision revision) + throws SVNException { final SVNWCClient client = createClient(); - final SVNPropertyData[] result = new SVNPropertyData[1]; + final PropertyData[] result = new PropertyData[1]; ISVNPropertyHandler handler = new ISVNPropertyHandler() { @Override public void handleProperty(File path, SVNPropertyData property) throws SVNException { @@ -151,7 +179,7 @@ public class SvnKitPropertyClient extends BaseSvnClient implements PropertyClien private void handle(@NotNull SVNPropertyData data) { if (property.equals(data.getName())) { - result[0] = data; + result[0] = PropertyData.create(data); } } }; @@ -164,4 +192,41 @@ public class SvnKitPropertyClient extends BaseSvnClient implements PropertyClien return result[0]; } + + @Nullable + private static ISVNPropertyHandler toHandler(@Nullable final PropertyConsumer consumer) { + ISVNPropertyHandler result = null; + + if (consumer != null) { + result = new ISVNPropertyHandler() { + @Override + public void handleProperty(File path, SVNPropertyData property) throws SVNException { + consumer.handleProperty(path, PropertyData.create(property)); + } + + @Override + public void handleProperty(SVNURL url, SVNPropertyData property) throws SVNException { + consumer.handleProperty(url, PropertyData.create(property)); + } + + @Override + public void handleProperty(long revision, SVNPropertyData property) throws SVNException { + consumer.handleProperty(revision, PropertyData.create(property)); + } + }; + } + + return result; + } + + @Nullable + private static SVNPropertyValue toPropertyValue(@Nullable PropertyValue value) { + SVNPropertyValue result = null; + + if (value != null) { + result = SVNPropertyValue.create(value.toString()); + } + + return result; + } } diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/rollback/SvnRollbackEnvironment.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/rollback/SvnRollbackEnvironment.java index 237d05bd5f7a..7b09461b3766 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/rollback/SvnRollbackEnvironment.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/rollback/SvnRollbackEnvironment.java @@ -34,6 +34,9 @@ import org.jetbrains.idea.svn.api.ProgressEvent; import org.jetbrains.idea.svn.api.ProgressTracker; import org.jetbrains.idea.svn.commandLine.SvnBindException; import org.jetbrains.idea.svn.info.Info; +import org.jetbrains.idea.svn.properties.PropertiesMap; +import org.jetbrains.idea.svn.properties.PropertyConsumer; +import org.jetbrains.idea.svn.properties.PropertyData; import org.tmatesoft.svn.core.*; import org.tmatesoft.svn.core.wc.*; import org.tmatesoft.svn.core.wc2.SvnTarget; @@ -100,7 +103,7 @@ public class SvnRollbackEnvironment extends DefaultRollbackEnvironment { }; final List fromToModified = new ArrayList(); - final Map properties = ContainerUtil.newHashMap(); + final Map properties = ContainerUtil.newHashMap(); moveRenamesToTmp(exceptions, fromToModified, properties, collector); // adds (deletes) // deletes (adds) @@ -123,29 +126,29 @@ public class SvnRollbackEnvironment extends DefaultRollbackEnvironment { private void moveRenamesToTmp(List exceptions, List fromToModified, - final Map properties, + final Map properties, final UnversionedAndNotTouchedFilesGroupCollector collector) { final Map fromTo = collector.getFromTo(); try { final File tmp = FileUtil.createTempDirectory("forRename", ""); - final ISVNPropertyHandler handler = new ISVNPropertyHandler() { + final PropertyConsumer handler = new PropertyConsumer() { @Override - public void handleProperty(File path, SVNPropertyData property) throws SVNException { + public void handleProperty(File path, PropertyData property) throws SVNException { final ThroughRenameInfo info = collector.findToFile(new FilePathImpl(path, path.isDirectory()), null); if (info != null) { if (!properties.containsKey(info.getTo())) { - properties.put(info.getTo(), new SVNProperties()); + properties.put(info.getTo(), new PropertiesMap()); } properties.get(info.getTo()).put(property.getName(), property.getValue()); } } @Override - public void handleProperty(SVNURL url, SVNPropertyData property) throws SVNException { + public void handleProperty(SVNURL url, PropertyData property) throws SVNException { } @Override - public void handleProperty(long revision, SVNPropertyData property) throws SVNException { + public void handleProperty(long revision, PropertyData property) throws SVNException { } }; @@ -181,7 +184,7 @@ public class SvnRollbackEnvironment extends DefaultRollbackEnvironment { private void moveGroup(final List exceptions, List fromTo, - Map properties) { + Map properties) { Collections.sort(fromTo, new Comparator() { @Override public int compare(CopiedAsideInfo o1, CopiedAsideInfo o2) { @@ -240,8 +243,8 @@ public class SvnRollbackEnvironment extends DefaultRollbackEnvironment { applyProperties(properties, exceptions); } - private void applyProperties(Map propertiesMap, final List exceptions) { - for (Map.Entry entry : propertiesMap.entrySet()) { + private void applyProperties(Map propertiesMap, final List exceptions) { + for (Map.Entry entry : propertiesMap.entrySet()) { File file = entry.getKey(); try { mySvnVcs.getFactory(file).createPropertyClient().setProperties(file, entry.getValue()); diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/svnkit/SvnKitAdminAreaFactorySelector.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/svnkit/SvnKitAdminAreaFactorySelector.java new file mode 100644 index 000000000000..0d1a4c0106e0 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/svnkit/SvnKitAdminAreaFactorySelector.java @@ -0,0 +1,137 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jetbrains.idea.svn.svnkit; + +import com.intellij.openapi.application.ApplicationManager; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.idea.svn.SvnFormatSelector; +import org.jetbrains.idea.svn.SvnWorkingCopyFormatHolder; +import org.jetbrains.idea.svn.WorkingCopyFormat; +import org.tmatesoft.svn.core.SVNErrorCode; +import org.tmatesoft.svn.core.SVNErrorMessage; +import org.tmatesoft.svn.core.SVNException; +import org.tmatesoft.svn.core.internal.wc.admin.ISVNAdminAreaFactorySelector; +import org.tmatesoft.svn.core.internal.wc.admin.SVNAdminAreaFactory; + +import java.io.File; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; + +/** + * @author Konstantin Kolosovsky. + */ +public class SvnKitAdminAreaFactorySelector implements ISVNAdminAreaFactorySelector { + + public Collection getEnabledFactories(File path, Collection factories, boolean writeAccess) throws SVNException { + if (ApplicationManager.getApplication().isUnitTestMode()) { + return factories; + } + + if (!writeAccess) { + return factories; + } + + Collection result = null; + final WorkingCopyFormat presetFormat = SvnWorkingCopyFormatHolder.getPresetFormat(); + if (presetFormat != null) { + result = format2Factories(presetFormat, factories); + } + + if (result == null) { + final WorkingCopyFormat format = SvnFormatSelector.getWorkingCopyFormat(path); + result = format2Factories(format, factories); + } + + if (result == null) { + throw new SVNException(SVNErrorMessage.create(SVNErrorCode.WC_NOT_DIRECTORY)); + } + return result; + } + + @Nullable + static Collection format2Factories(final WorkingCopyFormat format, final Collection factories) { + if (WorkingCopyFormat.ONE_DOT_SEVEN.equals(format)) { + return factories; + } + else if (WorkingCopyFormat.ONE_DOT_SIX.equals(format)) { + return factoriesFor16(factories); + } + else if (WorkingCopyFormat.ONE_DOT_FIVE.equals(format)) { + return factoriesFor15(factories); + } + else if (WorkingCopyFormat.ONE_DOT_FOUR.equals(format)) { + return factoriesFor14(factories); + } + else if (WorkingCopyFormat.ONE_DOT_THREE.equals(format)) { + return factoriesFor13(factories); + } + return null; + } + + private static Collection factoriesFor13(final Collection factories) { + for (Object item : factories) { + final SVNAdminAreaFactory factory = (SVNAdminAreaFactory)item; + final int supportedVersion = factory.getSupportedVersion(); + if (WorkingCopyFormat.ONE_DOT_THREE.getFormat() == supportedVersion) { + return Collections.singletonList(factory); + } + } + return Collections.emptyList(); + } + + private static Collection factoriesFor14(final Collection factories) { + final Collection result = new ArrayList(2); + for (Object item : factories) { + final SVNAdminAreaFactory factory = (SVNAdminAreaFactory)item; + final int supportedVersion = factory.getSupportedVersion(); + if ((WorkingCopyFormat.ONE_DOT_FOUR.getFormat() == supportedVersion) || + (WorkingCopyFormat.ONE_DOT_THREE.getFormat() == supportedVersion)) { + result.add(factory); + } + } + return result; + } + + private static Collection factoriesFor15(final Collection factories) { + final Collection result = new ArrayList(2); + for (Object item : factories) { + final SVNAdminAreaFactory factory = (SVNAdminAreaFactory)item; + final int supportedVersion = factory.getSupportedVersion(); + if ((WorkingCopyFormat.ONE_DOT_FOUR.getFormat() == supportedVersion) || + (WorkingCopyFormat.ONE_DOT_THREE.getFormat() == supportedVersion) || + (WorkingCopyFormat.ONE_DOT_FIVE.getFormat() == supportedVersion)) { + result.add(factory); + } + } + return result; + } + + private static Collection factoriesFor16(final Collection factories) { + final Collection result = new ArrayList(2); + for (Object item : factories) { + final SVNAdminAreaFactory factory = (SVNAdminAreaFactory)item; + final int supportedVersion = factory.getSupportedVersion(); + if ((WorkingCopyFormat.ONE_DOT_FOUR.getFormat() == supportedVersion) || + (WorkingCopyFormat.ONE_DOT_THREE.getFormat() == supportedVersion) || + (WorkingCopyFormat.ONE_DOT_FIVE.getFormat() == supportedVersion) || + (WorkingCopyFormat.ONE_DOT_SIX.getFormat() == supportedVersion)) { + result.add(factory); + } + } + return result; + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/svnkit/SvnKitManager.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/svnkit/SvnKitManager.java index a446b58bb1cb..757100026797 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/svnkit/SvnKitManager.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/svnkit/SvnKitManager.java @@ -26,7 +26,6 @@ import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.idea.svn.SvnConfiguration; -import org.jetbrains.idea.svn.SvnFormatSelector; import org.jetbrains.idea.svn.SvnHttpAuthMethodsDefaultChecker; import org.jetbrains.idea.svn.SvnVcs; import org.jetbrains.idea.svn.auth.SvnAuthenticationManager; @@ -72,7 +71,7 @@ public class SvnKitManager { SVNJNAUtil.setJNAEnabled(true); SvnHttpAuthMethodsDefaultChecker.check(); - SVNAdminAreaFactory.setSelector(new SvnFormatSelector()); + SVNAdminAreaFactory.setSelector(new SvnKitAdminAreaFactorySelector()); DAVRepositoryFactory.setup(); SVNRepositoryFactoryImpl.setup(); diff --git a/plugins/svn4idea/testSource/org/jetbrains/idea/svn/ExternalsDefinitionParserTest.java b/plugins/svn4idea/testSource/org/jetbrains/idea/svn/ExternalsDefinitionParserTest.java new file mode 100644 index 000000000000..ff24c79a6b4c --- /dev/null +++ b/plugins/svn4idea/testSource/org/jetbrains/idea/svn/ExternalsDefinitionParserTest.java @@ -0,0 +1,62 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jetbrains.idea.svn; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.idea.svn.properties.ExternalsDefinitionParser; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +/** + * @author Konstantin Kolosovsky. + */ +public class ExternalsDefinitionParserTest { + + @Test + public void test_no_space_directory_with_revision() throws Exception { + assertRelativeDirectory("third-party/skins", "-r148 http://svn.example.com/skinproj third-party/skins"); + } + + @Test + public void test_no_space_directory_with_peg_revision() throws Exception { + assertRelativeDirectory("third-party/skins", "http://svn.example.com/skinproj@148 third-party/skins"); + } + + @Test + public void test_no_space_directory_without_revision() throws Exception { + assertRelativeDirectory("third-party/sounds", " http://svn.example.com/repos/sounds third-party/sounds"); + } + + @Test + public void test_quoted_no_space_directory() throws Exception { + assertRelativeDirectory("third-party/skins", "-r148 http://svn.example.com/skinproj \"third-party/skins\""); + } + + @Test + public void test_quoted_with_space_directory() throws Exception { + assertRelativeDirectory("My Project", "http://svn.thirdparty.com/repos/My%20Project \"My Project\""); + } + + @Test + public void test_escaped_with_space_and_quotes_directory() throws Exception { + assertRelativeDirectory("\"Quotes Too\"", "http://svn.thirdparty.com/repos/%22Quotes%20Too%22 \\\"Quotes\\ Too\\\""); + } + + private static void assertRelativeDirectory(@NotNull String expected, @NotNull String external) throws Exception { + assertEquals(expected, ExternalsDefinitionParser.parseRelativeDirectory(external)); + } +} diff --git a/python/IntelliLang-python/IntelliLang-python.iml b/python/IntelliLang-python/IntelliLang-python.iml index a877c73afd91..49ab883c711d 100644 --- a/python/IntelliLang-python/IntelliLang-python.iml +++ b/python/IntelliLang-python/IntelliLang-python.iml @@ -4,6 +4,7 @@ + diff --git a/python/IntelliLang-python/src/META-INF/intellilang-python-support.xml b/python/IntelliLang-python/resources/META-INF/intellilang-python-support.xml similarity index 91% rename from python/IntelliLang-python/src/META-INF/intellilang-python-support.xml rename to python/IntelliLang-python/resources/META-INF/intellilang-python-support.xml index 44bab2c91b05..c96f0b7f2b3a 100644 --- a/python/IntelliLang-python/src/META-INF/intellilang-python-support.xml +++ b/python/IntelliLang-python/resources/META-INF/intellilang-python-support.xml @@ -3,7 +3,7 @@ - + diff --git a/python/IntelliLang-python/src/META-INF/plugin.xml b/python/IntelliLang-python/resources/META-INF/plugin.xml similarity index 92% rename from python/IntelliLang-python/src/META-INF/plugin.xml rename to python/IntelliLang-python/resources/META-INF/plugin.xml index 3009f34c2b9c..6b2717a6e7b1 100644 --- a/python/IntelliLang-python/src/META-INF/plugin.xml +++ b/python/IntelliLang-python/resources/META-INF/plugin.xml @@ -5,6 +5,7 @@ This plugin enables language injections JetBrains + com.intellij.modules.python org.intellij.intelliLang diff --git a/python/IntelliLang-python/src/pyInjections.xml b/python/IntelliLang-python/src/resources/pyInjections.xml similarity index 100% rename from python/IntelliLang-python/src/pyInjections.xml rename to python/IntelliLang-python/src/resources/pyInjections.xml diff --git a/python/helpers/pycharm/_bdd_utils.py b/python/helpers/pycharm/_bdd_utils.py index 26bd107c5288..0c92532b516c 100644 --- a/python/helpers/pycharm/_bdd_utils.py +++ b/python/helpers/pycharm/_bdd_utils.py @@ -19,12 +19,16 @@ def get_path_by_args(arguments): """ :type arguments list :param arguments: arguments (sys.argv) - :return: Folder (current or first argument from argv) checking it exists - :rtype str + :return: tuple (base_dir, what_to_run) where dir is current or first argument from argv, checking it exists + :rtype tuple of str """ - path = arguments[1] if len(arguments) > 1 else "." - assert os.path.exists(path), "{} does not exist".format(path) - return path + what_to_run = arguments[1] if len(arguments) > 1 else "." + base_dir = what_to_run + assert os.path.exists(what_to_run), "{} does not exist".format(what_to_run) + + if os.path.isfile(what_to_run): + base_dir = os.path.dirname(what_to_run) # User may point to the file directly + return base_dir, what_to_run class BddRunner(object): @@ -68,7 +72,8 @@ class BddRunner(object): :param location object with "file" (relative to base_dir) and "line" fields. :return: location in format file:line (as supported in tcmessages) """ - return "file:///{}/{}:{}".format(self.__base_dir, location.file, location.line) + my_file = str(location.file).lstrip("/\\") + return "file:///{}:{}".format(os.path.normpath(os.path.join(self.__base_dir, my_file)), location.line) def _test_undefined(self, test_name, location): """ diff --git a/python/helpers/pycharm/behave_runner.py b/python/helpers/pycharm/behave_runner.py index b11abfe7cc6b..e66805cfafb2 100644 --- a/python/helpers/pycharm/behave_runner.py +++ b/python/helpers/pycharm/behave_runner.py @@ -220,21 +220,19 @@ if __name__ == "__main__": """ pass - tags = [] - if len(sys.argv) > 1: - for arg in filter(None, sys.argv[2::]): - if str(arg).startswith("--tags"): - tags.append(str(arg)) - else: - raise Exception("Not a tag expression (should be --tags=..):{}".format(str(arg))) - my_config = configuration.Configuration(command_args=tags) + command_args = list(filter(None, sys.argv[1:])) + my_config = configuration.Configuration(command_args=command_args) formatters.register_as(_Null, "com.intellij.python.null") my_config.format = ["com.intellij.python.null"] # To prevent output to stdout my_config.reporters = [] # To prevent summary to stdout my_config.stdout_capture = False # For test output my_config.stderr_capture = False # For test output - base_dir = _bdd_utils.get_path_by_args(sys.argv) - my_config.paths = _get_dirs_to_run(base_dir) + (base_dir, what_to_run) = _bdd_utils.get_path_by_args(sys.argv) + if not my_config.paths: # No path provided, trying to load dit manually + if os.path.isfile(what_to_run): # File is provided, load it + my_config.paths = [what_to_run] + else: # Dir is provided, find subdirs ro run + my_config.paths = _get_dirs_to_run(base_dir) _BehaveRunner(my_config, base_dir).run() diff --git a/python/helpers/pycharm/lettuce_runner.py b/python/helpers/pycharm/lettuce_runner.py index 6a3558bc8f44..3cd112540e5f 100644 --- a/python/helpers/pycharm/lettuce_runner.py +++ b/python/helpers/pycharm/lettuce_runner.py @@ -18,17 +18,23 @@ class _LettuceRunner(_bdd_utils.BddRunner): Lettuce runner (BddRunner for lettuce) """ - def __init__(self, base_dir): + def __init__(self, base_dir, what_to_run): """ - :param base_dir base directory to run tests in - :type base_dir: str + :param base_dir base directory to run tests in + :type base_dir: str + :param what_to_run folder or file to run + :type what_to_run str """ super(_LettuceRunner, self).__init__(base_dir) - self.__runner = lettuce.Runner(base_dir) + self.__runner = lettuce.Runner(what_to_run) def _get_features_to_run(self): super(_LettuceRunner, self)._get_features_to_run() + if self.__runner.single_feature: # We need to run one and only one feature + return [core.Feature.from_file(self.__runner.single_feature)] + + # Find all features in dir features = [] for feature_file in self.__runner.loader.find_feature_files(): feature = core.Feature.from_file(feature_file) @@ -59,6 +65,8 @@ class _LettuceRunner(_bdd_utils.BddRunner): reason = step.why assert isinstance(reason, ReasonToFail), reason self._test_failed(test_name, message=reason.exception, details=reason.traceback) + elif step.has_definition: + self._test_skipped(test_name, "In lettuce, we do know the reason", step.described_at) else: self._test_undefined(test_name, step.described_at) @@ -100,4 +108,5 @@ class _LettuceRunner(_bdd_utils.BddRunner): if __name__ == "__main__": - _LettuceRunner(_bdd_utils.get_path_by_args(sys.argv)).run() \ No newline at end of file + (base_dir, what_to_run) = _bdd_utils.get_path_by_args(sys.argv) + _LettuceRunner(base_dir, what_to_run).run() \ No newline at end of file diff --git a/python/psi-api/src/com/jetbrains/python/psi/PyElementGenerator.java b/python/psi-api/src/com/jetbrains/python/psi/PyElementGenerator.java index 197f7536e121..daa8b3063014 100644 --- a/python/psi-api/src/com/jetbrains/python/psi/PyElementGenerator.java +++ b/python/psi-api/src/com/jetbrains/python/psi/PyElementGenerator.java @@ -37,15 +37,17 @@ public abstract class PyElementGenerator { public abstract PyStringLiteralExpression createStringLiteralAlreadyEscaped(String str); - - /** * Creates a string literal, adding appropriate quotes, properly escaping characters inside. + * * @param destination where the literal is destined to; used to determine the encoding. * @param unescaped the string - * @return a newly created literal + * @param preferUTF8 try to use UTF8 (would use ascii if false) + * @return a newly created literal */ - public abstract PyStringLiteralExpression createStringLiteralFromString(@Nullable PsiFile destination, String unescaped); + public abstract PyStringLiteralExpression createStringLiteralFromString(@Nullable PsiFile destination, String unescaped, + boolean preferUTF8); + public abstract PyStringLiteralExpression createStringLiteralFromString(@NotNull String unescaped); public abstract PyStringLiteralExpression createStringLiteral(@NotNull PyStringLiteralExpression oldElement, @NotNull String unescaped); @@ -59,11 +61,11 @@ public abstract class PyElementGenerator { public abstract PyBinaryExpression createBinaryExpression(String s, PyExpression expr, PyExpression listLiteral); /** - * @deprecated use the overload with language level specified * @param text the text to create an expression from * @return the expression + * @deprecated use the overload with language level specified */ - public abstract PyExpression createExpressionFromText(String text); + public abstract PyExpression createExpressionFromText(String text); public abstract PyExpression createExpressionFromText(final LanguageLevel languageLevel, String text); @@ -71,9 +73,9 @@ public abstract class PyElementGenerator { * Adds elements to list inserting required commas. * Method is like {@link #insertItemIntoList(PyElement, PyExpression, PyExpression)} but does not add unneeded commas. * - * @param list where to add + * @param list where to add * @param afterThis after which element it should be added (null for add to the head) - * @param toInsert what to insert + * @param toInsert what to insert * @return newly inserted element */ @NotNull @@ -92,7 +94,10 @@ public abstract class PyElementGenerator { public abstract PyImportElement createImportElement(final LanguageLevel languageLevel, String name); - public abstract PyFunction createProperty(final LanguageLevel languageLevel, String propertyName, String fieldName, AccessDirection accessDirection); + public abstract PyFunction createProperty(final LanguageLevel languageLevel, + String propertyName, + String fieldName, + AccessDirection accessDirection); @NotNull public abstract T createFromText(LanguageLevel langLevel, Class aClass, final String text); @@ -103,6 +108,7 @@ public abstract class PyElementGenerator { /** * Creates an arbitrary PSI element from text, by creating a bigger construction and then cutting the proper subelement. * Will produce all kinds of exceptions if the path or class would not match the PSI tree. + * * @param langLevel the language level to use for parsing the text * @param aClass class of the PSI element; may be an interface not descending from PsiElement, as long as target node can be cast to it * @param text text to parse @@ -122,6 +128,7 @@ public abstract class PyElementGenerator { public abstract PsiFile createDummyFile(LanguageLevel langLevel, String contents); public abstract PyExpressionStatement createDocstring(String content); + public abstract PyPassStatement createPassStatement(); @NotNull diff --git a/python/python-rest/src/com/jetbrains/rest/run/RestCommandLineState.java b/python/python-rest/src/com/jetbrains/rest/run/RestCommandLineState.java index e421df82d4f7..e5b52b5dc3a7 100644 --- a/python/python-rest/src/com/jetbrains/rest/run/RestCommandLineState.java +++ b/python/python-rest/src/com/jetbrains/rest/run/RestCommandLineState.java @@ -19,7 +19,6 @@ import com.intellij.execution.ExecutionException; import com.intellij.execution.configurations.GeneralCommandLine; import com.intellij.execution.configurations.ParametersList; import com.intellij.execution.configurations.ParamsGroup; -import com.intellij.execution.filters.Filter; import com.intellij.execution.process.ProcessAdapter; import com.intellij.execution.process.ProcessEvent; import com.intellij.execution.process.ProcessHandler; @@ -33,7 +32,6 @@ import com.jetbrains.python.run.PythonProcessRunner; import org.jetbrains.annotations.Nullable; import javax.swing.*; -import java.util.Collections; /** * User : catherine @@ -43,7 +41,7 @@ public abstract class RestCommandLineState extends PythonCommandLineState { public RestCommandLineState(RestRunConfiguration configuration, ExecutionEnvironment env) { - super(configuration, env, Collections.emptyList()); + super(configuration, env); myConfiguration = configuration; } diff --git a/python/src/com/jetbrains/python/psi/impl/PyElementGeneratorImpl.java b/python/src/com/jetbrains/python/psi/impl/PyElementGeneratorImpl.java index 0fa3a4dff6b9..bb0e29f800a7 100644 --- a/python/src/com/jetbrains/python/psi/impl/PyElementGeneratorImpl.java +++ b/python/src/com/jetbrains/python/psi/impl/PyElementGeneratorImpl.java @@ -87,8 +87,9 @@ public class PyElementGeneratorImpl extends PyElementGenerator { } + @Override public PyStringLiteralExpression createStringLiteralFromString(@NotNull String unescaped) { - return createStringLiteralFromString(null, unescaped); + return createStringLiteralFromString(null, unescaped, true); } public PyStringLiteralExpression createStringLiteral(@NotNull PyStringLiteralExpression oldElement, @NotNull String unescaped) { @@ -101,7 +102,11 @@ public class PyElementGeneratorImpl extends PyElementGenerator { } } - public PyStringLiteralExpression createStringLiteralFromString(@Nullable PsiFile destination, @NotNull String unescaped) { + + @Override + public PyStringLiteralExpression createStringLiteralFromString(@Nullable PsiFile destination, + @NotNull String unescaped, + final boolean preferUTF8) { boolean useDouble = !unescaped.contains("\""); boolean useMulti = unescaped.matches(".*(\r|\n).*"); String quotes; @@ -116,7 +121,7 @@ public class PyElementGeneratorImpl extends PyElementGenerator { VirtualFile vfile = destination == null ? null : destination.getVirtualFile(); Charset charset; if (vfile == null) { - charset = Charset.forName("US-ASCII"); + charset = (preferUTF8 ? Charset.forName("UTF-8") : Charset.forName("US-ASCII")); } else { charset = vfile.getCharset(); diff --git a/python/src/com/jetbrains/python/run/PyRemoteTracebackFilter.java b/python/src/com/jetbrains/python/run/PyRemoteTracebackFilter.java new file mode 100644 index 000000000000..2af1a641de83 --- /dev/null +++ b/python/src/com/jetbrains/python/run/PyRemoteTracebackFilter.java @@ -0,0 +1,57 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.jetbrains.python.run; + +import com.intellij.openapi.project.Project; +import com.intellij.openapi.vfs.LocalFileSystem; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.remote.RemoteProcessHandlerBase; +import com.intellij.util.PathMappingSettings; +import org.jetbrains.annotations.Nullable; + +/** + * @author traff + */ +public class PyRemoteTracebackFilter extends PythonTracebackFilter { + private final RemoteProcessHandlerBase myHandler; + + public PyRemoteTracebackFilter(Project project, String workingDirectory, RemoteProcessHandlerBase remoteProcessHandler) { + super(project, workingDirectory); + + myHandler = remoteProcessHandler; + } + + @Override + @Nullable + protected VirtualFile findFileByName(String fileName) { + VirtualFile vFile = super.findFileByName(fileName); + if (vFile != null) { + return vFile; + } + for (PathMappingSettings.PathMapping m : myHandler.getMappingSettings().getPathMappings()) { + if (m.canReplaceRemote(fileName)) { + VirtualFile file = LocalFileSystem.getInstance().findFileByPath(m.mapToLocal(fileName)); + if (file != null && file.exists()) { + return file; + } + } + } + + + + return null; + } +} diff --git a/python/src/com/jetbrains/python/run/PythonCommandLineState.java b/python/src/com/jetbrains/python/run/PythonCommandLineState.java index 216e68c58e1d..17fda94437d0 100644 --- a/python/src/com/jetbrains/python/run/PythonCommandLineState.java +++ b/python/src/com/jetbrains/python/run/PythonCommandLineState.java @@ -47,6 +47,7 @@ import com.intellij.openapi.util.io.FileUtil; import com.intellij.openapi.util.registry.Registry; import com.intellij.openapi.vfs.JarFileSystem; import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.remote.RemoteProcessHandlerBase; import com.intellij.util.PlatformUtils; import com.intellij.util.containers.HashMap; import com.jetbrains.python.PythonHelpersLocator; @@ -80,7 +81,9 @@ public abstract class PythonCommandLineState extends CommandLineState { public static final String GROUP_DEBUGGER = "Debugger"; public static final String GROUP_SCRIPT = "Script"; private final AbstractPythonRunConfiguration myConfig; - private final List myFilters; + + private final List myFilters = Lists.newArrayList(new UrlFilter()); + private Boolean myMultiprocessDebug = null; public boolean isDebug() { @@ -99,15 +102,9 @@ public abstract class PythonCommandLineState extends CommandLineState { return serverSocket; } - public PythonCommandLineState(AbstractPythonRunConfiguration runConfiguration, ExecutionEnvironment env, List filters) { + public PythonCommandLineState(AbstractPythonRunConfiguration runConfiguration, ExecutionEnvironment env) { super(env); myConfig = runConfiguration; - myFilters = Lists.newArrayList(filters); - addDefaultFilters(); - } - - protected void addDefaultFilters() { - myFilters.add(new UrlFilter()); } @Nullable @@ -134,10 +131,23 @@ public abstract class PythonCommandLineState extends CommandLineState { protected ConsoleView createAndAttachConsole(Project project, ProcessHandler processHandler, Executor executor) throws ExecutionException { final ConsoleView consoleView = createConsoleBuilder(project).filters(myFilters).getConsole(); + + addTracebackFilter(project, consoleView, processHandler); + consoleView.attachToProcess(processHandler); return consoleView; } + protected void addTracebackFilter(Project project, ConsoleView consoleView, ProcessHandler processHandler) { + if (PySdkUtil.isRemote(myConfig.getSdk())) { + assert processHandler instanceof RemoteProcessHandlerBase; + consoleView.addMessageFilter(new PyRemoteTracebackFilter(project, null, (RemoteProcessHandlerBase) processHandler)); + } + else { + consoleView.addMessageFilter(new PythonTracebackFilter(project, myConfig.getWorkingDirectory())); + } + } + private TextConsoleBuilder createConsoleBuilder(Project project) { if (isDebug()) { return new PyDebugConsoleBuilder(project, PythonSdkType.findSdkByPath(myConfig.getInterpreterPath())); diff --git a/python/src/com/jetbrains/python/run/PythonRunConfiguration.java b/python/src/com/jetbrains/python/run/PythonRunConfiguration.java index bbba44441227..118c27beb548 100644 --- a/python/src/com/jetbrains/python/run/PythonRunConfiguration.java +++ b/python/src/com/jetbrains/python/run/PythonRunConfiguration.java @@ -15,11 +15,9 @@ */ package com.jetbrains.python.run; -import com.google.common.collect.Lists; import com.intellij.execution.ExecutionException; import com.intellij.execution.Executor; import com.intellij.execution.configurations.*; -import com.intellij.execution.filters.Filter; import com.intellij.execution.runners.ExecutionEnvironment; import com.intellij.openapi.components.PathMacroManager; import com.intellij.openapi.options.SettingsEditor; @@ -40,7 +38,6 @@ import org.jdom.Element; import org.jetbrains.annotations.NotNull; import java.io.File; -import java.util.List; /** * @author yole @@ -64,10 +61,7 @@ public class PythonRunConfiguration extends AbstractPythonRunConfiguration } public RunProfileState getState(@NotNull final Executor executor, @NotNull final ExecutionEnvironment env) throws ExecutionException { - List filters = Lists.newArrayList(); - filters.add(new PythonTracebackFilter(getProject(), getWorkingDirectory())); - - return new PythonScriptCommandLineState(this, env, filters); + return new PythonScriptCommandLineState(this, env); } public void checkConfiguration() throws RuntimeConfigurationException { diff --git a/python/src/com/jetbrains/python/run/PythonScriptCommandLineState.java b/python/src/com/jetbrains/python/run/PythonScriptCommandLineState.java index 53ef554838ae..81d2daaee3d0 100644 --- a/python/src/com/jetbrains/python/run/PythonScriptCommandLineState.java +++ b/python/src/com/jetbrains/python/run/PythonScriptCommandLineState.java @@ -18,20 +18,17 @@ package com.jetbrains.python.run; import com.intellij.execution.configurations.GeneralCommandLine; import com.intellij.execution.configurations.ParametersList; import com.intellij.execution.configurations.ParamsGroup; -import com.intellij.execution.filters.Filter; import com.intellij.execution.runners.ExecutionEnvironment; import com.intellij.openapi.util.text.StringUtil; -import java.util.List; - /** * @author yole */ public class PythonScriptCommandLineState extends PythonCommandLineState { private final PythonRunConfiguration myConfig; - public PythonScriptCommandLineState(PythonRunConfiguration runConfiguration, ExecutionEnvironment env, List filters) { - super(runConfiguration, env, filters); + public PythonScriptCommandLineState(PythonRunConfiguration runConfiguration, ExecutionEnvironment env) { + super(runConfiguration, env); myConfig = runConfiguration; } diff --git a/python/src/com/jetbrains/python/run/PythonTracebackFilter.java b/python/src/com/jetbrains/python/run/PythonTracebackFilter.java index e3bd879cfe81..73a3abd222b1 100644 --- a/python/src/com/jetbrains/python/run/PythonTracebackFilter.java +++ b/python/src/com/jetbrains/python/run/PythonTracebackFilter.java @@ -51,10 +51,7 @@ public class PythonTracebackFilter implements Filter { if (matcher.find()) { String fileName = matcher.group(1).replace('\\', '/'); int lineNumber = Integer.parseInt(matcher.group(2)); - VirtualFile vFile = LocalFileSystem.getInstance().findFileByPath(fileName); - if (vFile == null && !StringUtil.isEmptyOrSpaces(myWorkingDirectory)) { - vFile = LocalFileSystem.getInstance().findFileByIoFile(new File(myWorkingDirectory, fileName)); - } + VirtualFile vFile = findFileByName(fileName); if (vFile != null) { OpenFileHyperlinkInfo hyperlink = new OpenFileHyperlinkInfo(myProject, vFile, lineNumber - 1); @@ -66,4 +63,13 @@ public class PythonTracebackFilter implements Filter { } return null; } + + @Nullable + protected VirtualFile findFileByName(String fileName) { + VirtualFile vFile = LocalFileSystem.getInstance().findFileByPath(fileName); + if (vFile == null && !StringUtil.isEmptyOrSpaces(myWorkingDirectory)) { + vFile = LocalFileSystem.getInstance().findFileByIoFile(new File(myWorkingDirectory, fileName)); + } + return vFile; + } } diff --git a/python/src/com/jetbrains/python/testing/PythonTestCommandLineStateBase.java b/python/src/com/jetbrains/python/testing/PythonTestCommandLineStateBase.java index 39e4e96b836b..1a5aafe59051 100644 --- a/python/src/com/jetbrains/python/testing/PythonTestCommandLineStateBase.java +++ b/python/src/com/jetbrains/python/testing/PythonTestCommandLineStateBase.java @@ -22,7 +22,6 @@ import com.intellij.execution.ExecutionResult; import com.intellij.execution.Executor; import com.intellij.execution.configurations.GeneralCommandLine; import com.intellij.execution.configurations.ParamsGroup; -import com.intellij.execution.filters.Filter; import com.intellij.execution.process.ProcessHandler; import com.intellij.execution.runners.ExecutionEnvironment; import com.intellij.execution.testframework.TestFrameworkRunningModel; @@ -42,12 +41,10 @@ import com.jetbrains.python.console.PythonDebugLanguageConsoleView; import com.jetbrains.python.run.AbstractPythonRunConfiguration; import com.jetbrains.python.run.CommandLinePatcher; import com.jetbrains.python.run.PythonCommandLineState; -import com.jetbrains.python.run.PythonTracebackFilter; import com.jetbrains.python.sdk.PythonSdkType; import org.jetbrains.annotations.NotNull; import java.io.File; -import java.util.Collections; import java.util.List; import java.util.Map; @@ -62,7 +59,7 @@ public abstract class PythonTestCommandLineStateBase extends PythonCommandLineSt } public PythonTestCommandLineStateBase(AbstractPythonRunConfiguration configuration, ExecutionEnvironment env) { - super(configuration, env, Collections.emptyList()); + super(configuration, env); myConfiguration = configuration; } @@ -77,7 +74,6 @@ public abstract class PythonTestCommandLineStateBase extends PythonCommandLineSt consoleProperties, getEnvironment()); final ConsoleView consoleView = new PythonDebugLanguageConsoleView(project, PythonSdkType.findSdkByPath(myConfiguration.getInterpreterPath()), testsOutputConsoleView); - consoleView.addMessageFilter(new PythonTracebackFilter(project, myConfiguration.getWorkingDirectory())); consoleView.attachToProcess(processHandler); return consoleView; } @@ -85,7 +81,7 @@ public abstract class PythonTestCommandLineStateBase extends PythonCommandLineSt processHandler, consoleProperties, getEnvironment()); - consoleView.addMessageFilter(new PythonTracebackFilter(project, myConfiguration.getWorkingDirectory())); + addTracebackFilter(project, consoleView, processHandler); return consoleView; } diff --git a/python/src/com/jetbrains/python/testing/pytest/PyTestCommandLineState.java b/python/src/com/jetbrains/python/testing/pytest/PyTestCommandLineState.java index 3637f37ca91c..581e688de1d5 100644 --- a/python/src/com/jetbrains/python/testing/pytest/PyTestCommandLineState.java +++ b/python/src/com/jetbrains/python/testing/pytest/PyTestCommandLineState.java @@ -81,7 +81,7 @@ public class PyTestCommandLineState extends PythonTestCommandLineStateBase { protected ConsoleView createAndAttachConsole(Project project, ProcessHandler processHandler, Executor executor) throws ExecutionException { final ConsoleView consoleView = super.createAndAttachConsole(project, processHandler, executor); - consoleView.addMessageFilter(new PyTestTracebackFilter(project, myConfiguration.getWorkingDirectory())); + addTracebackFilter(project, consoleView, processHandler); return consoleView; } } diff --git a/resources/src/idea/RichPlatformPlugin.xml b/resources/src/idea/RichPlatformPlugin.xml index 92ae7046006e..56c14fb3f9de 100644 --- a/resources/src/idea/RichPlatformPlugin.xml +++ b/resources/src/idea/RichPlatformPlugin.xml @@ -372,6 +372,8 @@ + + diff --git a/xml/impl/src/org/jetbrains/builtInWebServer/BuiltInServerConfigurableUi.form b/xml/impl/src/org/jetbrains/builtInWebServer/BuiltInServerConfigurableUi.form new file mode 100644 index 000000000000..3aa78b3be81c --- /dev/null +++ b/xml/impl/src/org/jetbrains/builtInWebServer/BuiltInServerConfigurableUi.form @@ -0,0 +1,40 @@ + +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/xml/impl/src/org/jetbrains/builtInWebServer/BuiltInServerConfigurableUi.java b/xml/impl/src/org/jetbrains/builtInWebServer/BuiltInServerConfigurableUi.java new file mode 100644 index 000000000000..edbef65543af --- /dev/null +++ b/xml/impl/src/org/jetbrains/builtInWebServer/BuiltInServerConfigurableUi.java @@ -0,0 +1,47 @@ +package org.jetbrains.builtInWebServer; + +import com.intellij.openapi.options.ConfigurableUi; +import com.intellij.ui.PortField; +import org.jetbrains.annotations.NotNull; + +import javax.swing.*; + +class BuiltInServerConfigurableUi implements ConfigurableUi { + private JPanel mainPanel; + + private PortField builtInServerPort; + private JCheckBox builtInServerAvailableExternallyCheckBox; + + public BuiltInServerConfigurableUi() { + builtInServerPort.setMin(1024); + } + + @Override + @NotNull + public JComponent getComponent() { + return mainPanel; + } + + @Override + public boolean isModified(@NotNull BuiltInServerOptions settings) { + return builtInServerPort.getNumber() != settings.builtInServerPort || + builtInServerAvailableExternallyCheckBox.isSelected() != settings.builtInServerAvailableExternally; + } + + @Override + public void apply(@NotNull BuiltInServerOptions settings) { + boolean builtInServerPortChanged = settings.builtInServerPort != builtInServerPort.getNumber() || settings.builtInServerAvailableExternally != builtInServerAvailableExternallyCheckBox.isSelected(); + if (builtInServerPortChanged) { + settings.builtInServerPort = builtInServerPort.getNumber(); + settings.builtInServerAvailableExternally = builtInServerAvailableExternallyCheckBox.isSelected(); + + BuiltInServerOptions.onBuiltInServerPortChanged(); + } + } + + @Override + public void reset(@NotNull BuiltInServerOptions settings) { + builtInServerPort.setNumber(settings.builtInServerPort); + builtInServerAvailableExternallyCheckBox.setSelected(settings.builtInServerAvailableExternally); + } +} diff --git a/xml/impl/src/org/jetbrains/builtInWebServer/BuiltInServerOptions.java b/xml/impl/src/org/jetbrains/builtInWebServer/BuiltInServerOptions.java new file mode 100644 index 000000000000..5eb65c1f9cbc --- /dev/null +++ b/xml/impl/src/org/jetbrains/builtInWebServer/BuiltInServerOptions.java @@ -0,0 +1,116 @@ +package org.jetbrains.builtInWebServer; + +import com.intellij.notification.Notification; +import com.intellij.notification.NotificationDisplayType; +import com.intellij.notification.NotificationType; +import com.intellij.notification.Notifications; +import com.intellij.openapi.application.ApplicationNamesInfo; +import com.intellij.openapi.application.PathManager; +import com.intellij.openapi.components.*; +import com.intellij.openapi.options.Configurable; +import com.intellij.openapi.options.SimpleConfigurable; +import com.intellij.openapi.util.Getter; +import com.intellij.util.xmlb.XmlSerializerUtil; +import com.intellij.util.xmlb.annotations.Attribute; +import com.intellij.xdebugger.settings.DebuggerConfigurableProvider; +import com.intellij.xdebugger.settings.DebuggerSettingsCategory; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.ide.BuiltInServerManager; +import org.jetbrains.ide.CustomPortServerManager; +import org.jetbrains.io.CustomPortServerManagerBase; + +import java.io.File; +import java.util.Collection; +import java.util.Collections; + +@State( + name = "BuiltInServerOptions", + storages = { + @Storage( + file = StoragePathMacros.APP_CONFIG + "/other.xml" + )} +) +public class BuiltInServerOptions implements PersistentStateComponent, ExportableComponent, Getter { + @Attribute + public int builtInServerPort = 63342; + @Attribute + public boolean builtInServerAvailableExternally = false; + + public static BuiltInServerOptions getInstance() { + return ServiceManager.getService(BuiltInServerOptions.class); + } + + @Override + public BuiltInServerOptions get() { + return this; + } + + static final class BuiltInServerDebuggerConfigurableProvider extends DebuggerConfigurableProvider { + @NotNull + @Override + public Collection getConfigurables(@NotNull DebuggerSettingsCategory category) { + if (category == DebuggerSettingsCategory.GENERAL) { + return Collections.singletonList(SimpleConfigurable.create("builtInServer", "", BuiltInServerConfigurableUi.class, getInstance())); + } + return Collections.emptyList(); + } + } + + @NotNull + @Override + public File[] getExportFiles() { + return new File[]{PathManager.getOptionsFile("other")}; + } + + @NotNull + @Override + public String getPresentableName() { + return "Built-in server"; + } + + @Nullable + @Override + public BuiltInServerOptions getState() { + return this; + } + + @Override + public void loadState(BuiltInServerOptions state) { + XmlSerializerUtil.copyBean(state, this); + } + + public int getEffectiveBuiltInServerPort() { + MyCustomPortServerManager portServerManager = CustomPortServerManager.EP_NAME.findExtension(MyCustomPortServerManager.class); + if (!portServerManager.isBound()) { + return BuiltInServerManager.getInstance().getPort(); + } + return builtInServerPort; + } + + public static final class MyCustomPortServerManager extends CustomPortServerManagerBase { + @Override + public void cannotBind(Exception e, int port) { + String groupDisplayId = "Built-in Web Server"; + Notifications.Bus.register(groupDisplayId, NotificationDisplayType.STICKY_BALLOON); + new Notification(groupDisplayId, "Built-in HTTP server on custom port " + port + " disabled", + "Cannot start built-in HTTP server on custom port " + port + ". " + + "Please ensure that port is free (or check your firewall settings) and restart " + ApplicationNamesInfo.getInstance().getFullProductName(), + NotificationType.ERROR).notify(null); + } + + @Override + public int getPort() { + return getInstance().builtInServerPort; + } + + @Override + public boolean isAvailableExternally() { + return getInstance().builtInServerAvailableExternally; + } + } + + public static void onBuiltInServerPortChanged() { + CustomPortServerManager.EP_NAME.findExtension(MyCustomPortServerManager.class).portChanged(); + } +} \ No newline at end of file diff --git a/xml/impl/src/org/jetbrains/builtInWebServer/BuiltInWebBrowserUrlProvider.java b/xml/impl/src/org/jetbrains/builtInWebServer/BuiltInWebBrowserUrlProvider.java new file mode 100644 index 000000000000..bb170d38b96c --- /dev/null +++ b/xml/impl/src/org/jetbrains/builtInWebServer/BuiltInWebBrowserUrlProvider.java @@ -0,0 +1,78 @@ +package org.jetbrains.builtInWebServer; + +import com.intellij.ide.browsers.OpenInBrowserRequest; +import com.intellij.ide.browsers.WebBrowserUrlProvider; +import com.intellij.openapi.project.DumbAware; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.psi.PsiFile; +import com.intellij.testFramework.LightVirtualFile; +import com.intellij.util.Url; +import com.intellij.util.Urls; +import com.intellij.util.containers.ContainerUtil; +import com.intellij.xml.util.HtmlUtil; +import org.jetbrains.ide.BuiltInServerManager; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +public class BuiltInWebBrowserUrlProvider extends WebBrowserUrlProvider implements DumbAware { + @NotNull + public static List getUrls(@NotNull VirtualFile file, @NotNull Project project, @Nullable String currentAuthority) { + if (currentAuthority != null && !compareAuthority(currentAuthority)) { + return Collections.emptyList(); + } + + String path = PathToFileManager.getInstance(project).getPath(file); + if (path == null) { + return Collections.emptyList(); + } + + int effectiveBuiltInServerPort = BuiltInServerOptions.getInstance().getEffectiveBuiltInServerPort(); + Url url = Urls.newHttpUrl(currentAuthority == null ? "localhost:" + effectiveBuiltInServerPort : currentAuthority, '/' + project.getName() + '/' + path); + int defaultPort = BuiltInServerManager.getInstance().getPort(); + if (currentAuthority != null || defaultPort == effectiveBuiltInServerPort) { + return Collections.singletonList(url); + } + return Arrays.asList(url, Urls.newHttpUrl("localhost:" + defaultPort, '/' + project.getName() + '/' + path)); + } + + public static boolean compareAuthority(@Nullable String currentAuthority) { + if (currentAuthority == null) { + return false; + } + + int portIndex = currentAuthority.indexOf(':'); + if (portIndex < 0) { + return false; + } + + String host = currentAuthority.substring(0, portIndex); + if (!BuiltInWebServer.isOwnHostName(host)) { + return false; + } + + int port = StringUtil.parseInt(currentAuthority.substring(portIndex + 1), -1); + return port == BuiltInServerOptions.getInstance().getEffectiveBuiltInServerPort() || + port == BuiltInServerManager.getInstance().getPort(); + } + + @Override + public boolean canHandleElement(@NotNull OpenInBrowserRequest request) { + return request.getFile().getViewProvider().isPhysical() && !(request.getVirtualFile() instanceof LightVirtualFile) && isMyLanguage(request.getFile()); + } + + protected boolean isMyLanguage(PsiFile psiFile) { + return HtmlUtil.isHtmlFile(psiFile); + } + + @Nullable + @Override + protected Url getUrl(@NotNull OpenInBrowserRequest request, @NotNull VirtualFile virtualFile) throws BrowserException { + return ContainerUtil.getFirstItem(getUrls(virtualFile, request.getProject(), null)); + } +} diff --git a/xml/impl/src/org/jetbrains/builtInWebServer/BuiltInWebServer.java b/xml/impl/src/org/jetbrains/builtInWebServer/BuiltInWebServer.java new file mode 100644 index 000000000000..c87c3096dca3 --- /dev/null +++ b/xml/impl/src/org/jetbrains/builtInWebServer/BuiltInWebServer.java @@ -0,0 +1,285 @@ +package org.jetbrains.builtInWebServer; + +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.project.ProjectManager; +import com.intellij.openapi.util.SystemInfoRt; +import com.intellij.openapi.util.io.FileUtil; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.openapi.vfs.VfsUtil; +import com.intellij.openapi.vfs.VfsUtilCore; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.util.UriUtil; +import com.intellij.util.io.URLUtil; +import com.intellij.util.net.NetUtils; +import io.netty.channel.Channel; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.http.*; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.ide.HttpRequestHandler; +import org.jetbrains.io.FileResponses; +import org.jetbrains.io.Responses; + +import java.io.File; +import java.io.IOException; +import java.net.InetAddress; +import java.net.URI; +import java.net.UnknownHostException; + +import static org.jetbrains.io.Responses.sendOptionsResponse; +import static org.jetbrains.io.Responses.sendStatus; + +public class BuiltInWebServer extends HttpRequestHandler { + private static final Logger LOG = Logger.getInstance(BuiltInWebServer.class); + + @Nullable + public static VirtualFile findIndexFile(@NotNull VirtualFile basedir) { + VirtualFile[] children = basedir.getChildren(); + if (children == null || children.length == 0) { + return null; + } + + for (String indexNamePrefix : new String[]{"index.", "default."}) { + VirtualFile index = null; + String preferredName = indexNamePrefix + "html"; + for (VirtualFile child : children) { + if (!child.isDirectory()) { + String name = child.getName(); + if (name.equals(preferredName)) { + return child; + } + else if (index == null && name.startsWith(indexNamePrefix)) { + index = child; + } + } + } + if (index != null) { + return index; + } + } + return null; + } + + @Override + public boolean isSupported(@NotNull FullHttpRequest request) { + return super.isSupported(request) || request.method() == HttpMethod.POST || request.method() == HttpMethod.OPTIONS; + } + + @Override + public boolean process(@NotNull QueryStringDecoder urlDecoder, @NotNull FullHttpRequest request, @NotNull ChannelHandlerContext context) throws IOException { + if (request.method() == HttpMethod.OPTIONS) { + sendOptionsResponse("GET, POST, HEAD, OPTIONS", request, context); + return true; + } + + String host = HttpHeaders.getHost(request); + if (StringUtil.isEmpty(host)) { + return false; + } + + int portIndex = host.indexOf(':'); + if (portIndex > 0) { + host = host.substring(0, portIndex); + } + + String projectName; + boolean isIpv6 = host.charAt(0) == '[' && host.length() > 2 && host.charAt(host.length() - 1) == ']'; + if (isIpv6) { + host = host.substring(1, host.length() - 1); + } + + if (isIpv6 || Character.digit(host.charAt(0), 10) != -1 || host.charAt(0) == ':' || isOwnHostName(host)) { + if (urlDecoder.path().length() < 2) { + return false; + } + projectName = null; + } + else { + projectName = host; + } + return doProcess(request, context.channel(), projectName); + } + + public static boolean isOwnHostName(String host) { + if (NetUtils.isLocalhost(host)) { + return true; + } + + try { + InetAddress address = InetAddress.getByName(host); + if (host.equals(address.getHostAddress()) || host.equalsIgnoreCase(address.getCanonicalHostName())) { + return true; + } + + String localHostName = InetAddress.getLocalHost().getHostName(); + // WEB-8889 + // develar.local is own host name: develar. equals to "develar.labs.intellij.net" (canonical host name) + return localHostName.equalsIgnoreCase(host) || + (host.endsWith(".local") && localHostName.regionMatches(true, 0, host, 0, host.length() - ".local".length())); + } + catch (UnknownHostException ignored) { + return false; + } + } + + private static boolean doProcess(FullHttpRequest request, Channel channel, @Nullable String projectName) { + final String decodedPath = URLUtil.unescapePercentSequences(UriUtil.trimParameters(request.uri())); + int offset; + boolean emptyPath; + boolean isCustomHost = projectName != null; + if (isCustomHost) { + // host mapped to us + offset = 0; + emptyPath = decodedPath.isEmpty(); + } + else { + offset = decodedPath.indexOf('/', 1); + projectName = decodedPath.substring(1, offset == -1 ? decodedPath.length() : offset); + emptyPath = offset == -1; + } + + Project project = findProject(projectName, isCustomHost); + if (project == null) { + return false; + } + + if (emptyPath) { + if (!SystemInfoRt.isFileSystemCaseSensitive) { + // may be passed path is not correct + projectName = project.getName(); + } + + // we must redirect "jsdebug" to "jsdebug/" as nginx does, otherwise browser will treat it as file instead of directory, so, relative path will not work + redirectToDirectory(request, channel, projectName); + return true; + } + + + final String path = FileUtil.toCanonicalPath(decodedPath.substring(offset + 1), '/'); + LOG.assertTrue(path != null); + PathToFileManager pathToFileManager = PathToFileManager.getInstance(project); + VirtualFile result = pathToFileManager.pathToFileCache.getIfPresent(path); + boolean indexUsed = false; + if (result == null || !result.isValid()) { + result = pathToFileManager.findByRelativePath(project, path); + if (result == null) { + if (path.isEmpty()) { + sendStatus(HttpResponseStatus.NOT_FOUND, channel, "Index file doesn't exist.", request); + return true; + } + else { + return false; + } + } + else if (result.isDirectory()) { + if (!endsWithSlash(decodedPath)) { + redirectToDirectory(request, channel, isCustomHost ? path : (projectName + '/' + path)); + return true; + } + + result = findIndexFile(result); + if (result == null) { + sendStatus(HttpResponseStatus.NOT_FOUND, channel, "Index file doesn't exist.", request); + return true; + } + indexUsed = true; + } + + pathToFileManager.pathToFileCache.put(path, result); + } + else if (!path.endsWith(result.getName())) { + if (endsWithSlash(decodedPath)) { + indexUsed = true; + } + else { + // FallbackResource feature in action, /login requested, /index.php retrieved, we must not redirect /login to /login/ + if (path.endsWith(result.getParent().getName())) { + redirectToDirectory(request, channel, isCustomHost ? path : (projectName + '/' + path)); + return true; + } + } + } + + StringBuilder canonicalRequestPath = new StringBuilder(); + canonicalRequestPath.append('/'); + if (!isCustomHost) { + canonicalRequestPath.append(projectName).append('/'); + } + canonicalRequestPath.append(path); + if (indexUsed) { + canonicalRequestPath.append('/').append(result.getName()); + } + + for (FileHandler fileHandler : FileHandler.EP_NAME.getExtensions()) { + try { + if (fileHandler.process(result, canonicalRequestPath.toString(), project, request, channel)) { + return true; + } + } + catch (Throwable e) { + LOG.error(e); + } + } + + return false; + } + + private static boolean endsWithSlash(String path) { + return path.charAt(path.length() - 1) == '/'; + } + + static final class StaticFileHandler extends FileHandler { + @Override + public boolean process(@NotNull VirtualFile file, + @NotNull String canonicalRequestPath, + @NotNull Project project, + @NotNull FullHttpRequest request, + @NotNull Channel channel) throws IOException { + File ioFile = VfsUtilCore.virtualToIoFile(file); + if (hasAccess(ioFile)) { + FileResponses.sendFile(request, channel, ioFile); + } + else { + sendStatus(HttpResponseStatus.FORBIDDEN, channel, request); + } + return true; + } + + private static boolean hasAccess(File result) { + // deny access to .htaccess files + return !result.isDirectory() && result.canRead() && !(result.isHidden() || result.getName().startsWith(".ht")); + } + } + + private static void redirectToDirectory(HttpRequest request, Channel channel, String path) { + FullHttpResponse response = Responses.response(HttpResponseStatus.MOVED_PERMANENTLY); + URI url = VfsUtil.toUri("http://" + HttpHeaders.getHost(request) + "/" + path + "/"); + LOG.assertTrue(url != null); + response.headers().add(HttpHeaders.Names.LOCATION, url.toASCIIString()); + Responses.send(response, channel, request); + } + + @Nullable + private static Project findProject(String projectName, boolean isCustomHost) { + // user can rename project directory, so, we should support this case - find project by base directory name + Project candidateByDirectoryName = null; + for (Project project : ProjectManager.getInstance().getOpenProjects()) { + String name = project.getName(); + // domain name is case-insensitive + if (!project.isDisposed() && ((isCustomHost || !SystemInfoRt.isFileSystemCaseSensitive) ? projectName.equalsIgnoreCase(name) : projectName.equals(name))) { + return project; + } + + if (candidateByDirectoryName == null && compareNameAndProjectBasePath(projectName, project)) { + candidateByDirectoryName = project; + } + } + return candidateByDirectoryName; + } + + public static boolean compareNameAndProjectBasePath(String projectName, Project project) { + String basePath = project.getBasePath(); + return basePath != null && basePath.length() > projectName.length() && basePath.endsWith(projectName) && basePath.charAt(basePath.length() - projectName.length() - 1) == '/'; + } +} \ No newline at end of file diff --git a/xml/impl/src/org/jetbrains/builtInWebServer/FileHandler.java b/xml/impl/src/org/jetbrains/builtInWebServer/FileHandler.java new file mode 100644 index 000000000000..a122bf35719a --- /dev/null +++ b/xml/impl/src/org/jetbrains/builtInWebServer/FileHandler.java @@ -0,0 +1,20 @@ +package org.jetbrains.builtInWebServer; + +import com.intellij.openapi.extensions.ExtensionPointName; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.vfs.VirtualFile; +import io.netty.channel.Channel; +import io.netty.handler.codec.http.FullHttpRequest; +import org.jetbrains.annotations.NotNull; + +import java.io.IOException; + +public abstract class FileHandler { + static final ExtensionPointName EP_NAME = ExtensionPointName.create("org.jetbrains.webServerFileHandler"); + + public abstract boolean process(@NotNull VirtualFile file, + @NotNull String canonicalRequestPath, + @NotNull Project project, + @NotNull FullHttpRequest request, + @NotNull Channel channel) throws IOException; +} \ No newline at end of file diff --git a/xml/impl/src/org/jetbrains/builtInWebServer/PathToFileManager.java b/xml/impl/src/org/jetbrains/builtInWebServer/PathToFileManager.java new file mode 100644 index 000000000000..4cf1d3d6d2d2 --- /dev/null +++ b/xml/impl/src/org/jetbrains/builtInWebServer/PathToFileManager.java @@ -0,0 +1,146 @@ +package org.jetbrains.builtInWebServer; + +import com.google.common.cache.Cache; +import com.google.common.cache.CacheBuilder; +import com.intellij.ProjectTopics; +import com.intellij.openapi.application.Application; +import com.intellij.openapi.components.ServiceManager; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.roots.ModuleRootAdapter; +import com.intellij.openapi.roots.ModuleRootEvent; +import com.intellij.openapi.util.Pair; +import com.intellij.openapi.vfs.VfsUtilCore; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.openapi.vfs.VirtualFileManager; +import com.intellij.openapi.vfs.newvfs.BulkFileListener; +import com.intellij.openapi.vfs.newvfs.events.VFileContentChangeEvent; +import com.intellij.openapi.vfs.newvfs.events.VFileEvent; +import com.intellij.util.PairFunction; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.List; +import java.util.concurrent.TimeUnit; + +public class PathToFileManager { + private static final PairFunction RELATIVE_PATH_RESOLVER = new PairFunction() { + @Nullable + @Override + public VirtualFile fun(String path, VirtualFile parent) { + return parent.findFileByRelativePath(path); + } + }; + + private static final PairFunction EMPTY_PATH_RESOLVER = new PairFunction() { + @Nullable + @Override + public VirtualFile fun(String path, VirtualFile parent) { + return BuiltInWebServer.findIndexFile(parent); + } + }; + + private final Project project; + + final Cache pathToFileCache = CacheBuilder.newBuilder().maximumSize(512).expireAfterAccess(10, TimeUnit.MINUTES).build(); + // time to expire should be greater than pathToFileCache + private final Cache> fileToRoot = CacheBuilder.newBuilder().maximumSize(512).expireAfterAccess(11, TimeUnit.MINUTES).build(); + + public static PathToFileManager getInstance(@NotNull Project project) { + return ServiceManager.getService(project, PathToFileManager.class); + } + + public PathToFileManager(@NotNull Application application, @NotNull Project project) { + this.project = project; + application.getMessageBus().connect(project).subscribe(VirtualFileManager.VFS_CHANGES, new BulkFileListener.Adapter() { + @Override + public void after(@NotNull List events) { + for (VFileEvent event : events) { + if (event instanceof VFileContentChangeEvent) { + VirtualFile file = ((VFileContentChangeEvent)event).getFile(); + for (ProjectRootsProvider rootsProvider : ProjectRootsProvider.EP_NAME.getExtensions()) { + if (rootsProvider.isClearCacheOnFileContentChanged(file)) { + clearCache(); + break; + } + } + } + else { + clearCache(); + break; + } + } + } + }); + project.getMessageBus().connect().subscribe(ProjectTopics.PROJECT_ROOTS, new ModuleRootAdapter() { + @Override + public void rootsChanged(ModuleRootEvent event) { + clearCache(); + } + }); + } + + private void clearCache() { + pathToFileCache.invalidateAll(); + fileToRoot.invalidateAll(); + } + + @Nullable + public VirtualFile get(@NotNull String path) { + return get(path, true); + } + + @Nullable + public VirtualFile get(@NotNull String path, boolean cacheResult) { + VirtualFile result = pathToFileCache.getIfPresent(path); + if (result == null || !result.isValid()) { + result = findByRelativePath(project, path); + if (cacheResult && result != null && result.isValid()) { + pathToFileCache.put(path, result); + } + } + return result; + } + + @Nullable + public String getPath(@NotNull VirtualFile file) { + Pair root = getRoot(file); + if (root == null) { + return null; + } + else { + return (root.second == null ? "" : root.second + '/') + VfsUtilCore.getRelativePath(file, root.first, '/'); + } + } + + @Nullable + public Pair getRoot(@NotNull VirtualFile child) { + Pair result = fileToRoot.getIfPresent(child); + if (result == null) { + for (ProjectRootsProvider rootsProvider : ProjectRootsProvider.EP_NAME.getExtensions()) { + result = rootsProvider.getRoot(child, project); + if (result != null) { + fileToRoot.put(child, result); + break; + } + } + } + return result; + } + + @Nullable + VirtualFile findByRelativePath(@NotNull Project project, @NotNull String path) { + for (ProjectRootsProvider rootsProvider : ProjectRootsProvider.EP_NAME.getExtensions()) { + Pair> result = rootsProvider.resolve(path, project); + if (result != null) { + fileToRoot.put(result.first, result.second); + return result.first; + } + } + return null; + } + + @NotNull + public PairFunction getResolver(@NotNull String path) { + return path.isEmpty() ? EMPTY_PATH_RESOLVER : RELATIVE_PATH_RESOLVER; + } +} \ No newline at end of file diff --git a/xml/impl/src/org/jetbrains/builtInWebServer/ProjectRootsProvider.java b/xml/impl/src/org/jetbrains/builtInWebServer/ProjectRootsProvider.java new file mode 100644 index 000000000000..ae69616b4174 --- /dev/null +++ b/xml/impl/src/org/jetbrains/builtInWebServer/ProjectRootsProvider.java @@ -0,0 +1,37 @@ +package org.jetbrains.builtInWebServer; + +import com.intellij.openapi.extensions.ExtensionPointName; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Pair; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.util.PairFunction; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public abstract class ProjectRootsProvider { + static final ExtensionPointName EP_NAME = ExtensionPointName.create("org.jetbrains.webServerProjectRootsProvider"); + + @Nullable + /** + * @return pair of child and root or null if cannot resolve + */ + public abstract Pair> resolve(@NotNull String path, @NotNull Project project); + + @Nullable + public abstract Pair getRoot(@NotNull VirtualFile file, @NotNull Project project); + + public boolean isClearCacheOnFileContentChanged(@NotNull VirtualFile file) { + return false; + } + + public abstract static class PrefixlessProjectRootsProvider extends ProjectRootsProvider { + @Nullable + @Override + public final Pair> resolve(@NotNull String path, @NotNull Project project) { + return resolve(path, project, PathToFileManager.getInstance(project).getResolver(path)); + } + + @Nullable + public abstract Pair> resolve(@NotNull String path, @NotNull Project project, @NotNull PairFunction resolver); + } +} \ No newline at end of file diff --git a/xml/impl/src/org/jetbrains/builtInWebServer/ProjectRootsProviderImpl.java b/xml/impl/src/org/jetbrains/builtInWebServer/ProjectRootsProviderImpl.java new file mode 100644 index 000000000000..811aaf1b85a4 --- /dev/null +++ b/xml/impl/src/org/jetbrains/builtInWebServer/ProjectRootsProviderImpl.java @@ -0,0 +1,135 @@ +package org.jetbrains.builtInWebServer; + +import com.intellij.openapi.application.AccessToken; +import com.intellij.openapi.application.ReadAction; +import com.intellij.openapi.application.Result; +import com.intellij.openapi.module.Module; +import com.intellij.openapi.module.ModuleManager; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.roots.ModuleRootManager; +import com.intellij.openapi.roots.ProjectFileIndex; +import com.intellij.openapi.roots.ProjectRootManager; +import com.intellij.openapi.util.Couple; +import com.intellij.openapi.util.Pair; +import com.intellij.openapi.util.SystemInfo; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.util.PairFunction; +import com.intellij.util.PlatformUtils; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +final class ProjectRootsProviderImpl extends ProjectRootsProvider { + @Nullable + @Override + public Pair> resolve(@NotNull String path, @NotNull Project project) { + PairFunction resolver; + if (PlatformUtils.isIntelliJ()) { + int index = path.indexOf('/'); + if (index > 0 && !path.regionMatches(!SystemInfo.isFileSystemCaseSensitive, 0, project.getName(), 0, index)) { + String moduleName = path.substring(0, index); + AccessToken token = ReadAction.start(); + Module module; + try { + module = ModuleManager.getInstance(project).findModuleByName(moduleName); + } + finally { + token.finish(); + } + + if (module != null && !module.isDisposed()) { + path = path.substring(index + 1); + resolver = PathToFileManager.getInstance(project).getResolver(path); + + ModuleRootManager moduleRootManager = ModuleRootManager.getInstance(module); + Couple result = resolve(path, moduleRootManager.getSourceRoots(), resolver); + if (result == null) { + result = resolve(path, moduleRootManager.getContentRoots(), resolver); + } + if (result != null) { + return Pair.create(result.first, Pair.create(result.second, module.getName())); + } + } + } + } + + Module[] modules; + AccessToken token = ReadAction.start(); + try { + modules = ModuleManager.getInstance(project).getModules(); + } + finally { + token.finish(); + } + + resolver = PathToFileManager.getInstance(project).getResolver(path); + Pair> result = findByRelativePath(project, path, modules, true, resolver); + if (result == null) { + // let's find in content roots + return findByRelativePath(project, path, modules, false, resolver); + } + else { + return result; + } + } + + @Nullable + @Override + public Pair getRoot(@NotNull final VirtualFile file, @NotNull final Project project) { + return new ReadAction>() { + protected void run(@NotNull final Result> result) { + VirtualFile root = null; + ProjectFileIndex fileIndex = ProjectRootManager.getInstance(project).getFileIndex(); + if (fileIndex.isInSourceContent(file)) { + root = fileIndex.getSourceRootForFile(file); + } + else if (fileIndex.isInContent(file)) { + root = fileIndex.getContentRootForFile(file); + } + else if (fileIndex.isInLibraryClasses(file)) { + root = fileIndex.getClassRootForFile(file); + } + assert root != null : file.getPresentableUrl(); + result.setResult(Pair.create(root, getModuleNameQualifier(project, fileIndex.getModuleForFile(file)))); + } + }.execute().getResultObject(); + } + + @Nullable + private static String getModuleNameQualifier(@NotNull Project project, @Nullable Module module) { + if (module != null && + PlatformUtils.isIntelliJ() && + !(module.getName().equalsIgnoreCase(project.getName()) || BuiltInWebServer.compareNameAndProjectBasePath(module.getName(), project))) { + return module.getName(); + } + return null; + } + + @Nullable + private static Couple resolve(@NotNull String path, @NotNull VirtualFile[] roots, @NotNull PairFunction resolver) { + for (VirtualFile root : roots) { + VirtualFile file = resolver.fun(path, root); + if (file != null) { + return Couple.of(file, root); + } + } + return null; + } + + @Nullable + private static Pair> findByRelativePath(@NotNull Project project, + @NotNull String path, + @NotNull Module[] modules, + boolean inSourceRoot, + @NotNull PairFunction resolver) { + for (Module module : modules) { + if (!module.isDisposed()) { + ModuleRootManager moduleRootManager = ModuleRootManager.getInstance(module); + Couple result = resolve(path, inSourceRoot ? moduleRootManager.getSourceRoots() : moduleRootManager.getContentRoots(), resolver); + if (result != null) { + return Pair.create(result.first, Pair.create(result.second, getModuleNameQualifier(project, module))); + } + } + } + return null; + } +} \ No newline at end of file diff --git a/xml/impl/src/org/jetbrains/io/fastCgi/FastCgiChannelHandler.java b/xml/impl/src/org/jetbrains/io/fastCgi/FastCgiChannelHandler.java new file mode 100644 index 000000000000..d24078221b30 --- /dev/null +++ b/xml/impl/src/org/jetbrains/io/fastCgi/FastCgiChannelHandler.java @@ -0,0 +1,108 @@ +package org.jetbrains.io.fastCgi; + +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.openapi.util.text.StringUtilRt; +import com.intellij.util.containers.ConcurrentIntObjectMap; +import io.netty.buffer.ByteBuf; +import io.netty.channel.Channel; +import io.netty.channel.ChannelHandler; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.http.*; +import org.jetbrains.io.Responses; +import org.jetbrains.io.SimpleChannelInboundHandlerAdapter; + +import static org.jetbrains.io.fastCgi.FastCgiService.LOG; + +@ChannelHandler.Sharable +public class FastCgiChannelHandler extends SimpleChannelInboundHandlerAdapter { + private final ConcurrentIntObjectMap requestToChannel; + + public FastCgiChannelHandler(ConcurrentIntObjectMap channel) { + requestToChannel = channel; + } + + @Override + protected void messageReceived(ChannelHandlerContext context, FastCgiResponse response) throws Exception { + ByteBuf buffer = response.getData(); + Channel channel = requestToChannel.remove(response.getId()); + if (channel == null || !channel.isActive()) { + if (buffer != null) { + buffer.release(); + } + return; + } + + if (buffer == null) { + Responses.sendStatus(HttpResponseStatus.BAD_GATEWAY, channel); + return; + } + + HttpResponse httpResponse = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK, buffer); + try { + parseHeaders(httpResponse, buffer); + Responses.addServer(httpResponse); + if (!HttpHeaders.isContentLengthSet(httpResponse)) { + HttpHeaders.setContentLength(httpResponse, buffer.readableBytes()); + } + } + catch (Throwable e) { + buffer.release(); + Responses.sendStatus(HttpResponseStatus.INTERNAL_SERVER_ERROR, channel); + LOG.error(e); + } + channel.writeAndFlush(httpResponse); + } + + private static void parseHeaders(HttpResponse response, ByteBuf buffer) { + StringBuilder builder = new StringBuilder(); + while (buffer.isReadable()) { + builder.setLength(0); + + String key = null; + boolean valueExpected = true; + while (true) { + int b = buffer.readByte(); + if (b < 0 || b == '\n') { + break; + } + + if (b != '\r') { + if (valueExpected && b == ':') { + valueExpected = false; + + key = builder.toString(); + builder.setLength(0); + skipWhitespace(buffer); + } + else { + builder.append((char)b); + } + } + } + + if (builder.length() == 0) { + // end of headers + return; + } + + // skip standard headers + if (StringUtil.isEmpty(key) || StringUtilRt.startsWithIgnoreCase(key, "http") || StringUtilRt.startsWithIgnoreCase(key, "X-Accel-")) { + continue; + } + + String value = builder.toString(); + if (key.equalsIgnoreCase("status")) { + response.setStatus(HttpResponseStatus.valueOf(Integer.parseInt(value.substring(0, value.indexOf(' '))))); + } + else if (!(key.startsWith("http") || key.startsWith("HTTP"))) { + response.headers().add(key, value); + } + } + } + + private static void skipWhitespace(ByteBuf buffer) { + while (buffer.isReadable() && buffer.getByte(buffer.readerIndex()) == ' ') { + buffer.skipBytes(1); + } + } +} \ No newline at end of file diff --git a/xml/impl/src/org/jetbrains/io/fastCgi/FastCgiConstants.java b/xml/impl/src/org/jetbrains/io/fastCgi/FastCgiConstants.java new file mode 100644 index 000000000000..23a85dead1f9 --- /dev/null +++ b/xml/impl/src/org/jetbrains/io/fastCgi/FastCgiConstants.java @@ -0,0 +1,5 @@ +package org.jetbrains.io.fastCgi; + +public final class FastCgiConstants { + public static final int HEADER_LENGTH = 8; +} \ No newline at end of file diff --git a/xml/impl/src/org/jetbrains/io/fastCgi/FastCgiDecoder.java b/xml/impl/src/org/jetbrains/io/fastCgi/FastCgiDecoder.java new file mode 100644 index 000000000000..1cd7adbef5d4 --- /dev/null +++ b/xml/impl/src/org/jetbrains/io/fastCgi/FastCgiDecoder.java @@ -0,0 +1,149 @@ +package org.jetbrains.io.fastCgi; + +import com.intellij.util.Consumer; +import gnu.trove.TIntObjectHashMap; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.CompositeByteBuf; +import io.netty.buffer.Unpooled; +import io.netty.channel.ChannelHandlerContext; +import io.netty.util.CharsetUtil; +import org.jetbrains.io.Decoder; + +import static org.jetbrains.io.fastCgi.FastCgiService.LOG; + +public class FastCgiDecoder extends Decoder { + private enum State { + HEADER, CONTENT + } + + private State state = State.HEADER; + + private enum ProtocolStatus { + REQUEST_COMPLETE, CANT_MPX_CONN, OVERLOADED, UNKNOWN_ROLE + } + + public static final class RecordType { + public static final int END_REQUEST = 3; + public static final int STDOUT = 6; + public static final int STDERR = 7; + } + + private int type; + private int id; + private int contentLength; + private int paddingLength; + + private final TIntObjectHashMap dataBuffers = new TIntObjectHashMap(); + + private final Consumer errorOutputConsumer; + + public FastCgiDecoder(Consumer errorOutputConsumer) { + this.errorOutputConsumer = errorOutputConsumer; + } + + @Override + protected void messageReceived(ChannelHandlerContext context, ByteBuf input) throws Exception { + while (true) { + switch (state) { + case HEADER: { + if (paddingLength > 0) { + if (input.readableBytes() >= paddingLength) { + input.skipBytes(paddingLength); + paddingLength = 0; + } + else { + paddingLength -= input.readableBytes(); + input.skipBytes(input.readableBytes()); + input.release(); + return; + } + } + + ByteBuf buffer = getBufferIfSufficient(input, FastCgiConstants.HEADER_LENGTH, context); + if (buffer == null) { + input.release(); + return; + } + + decodeHeader(buffer); + state = State.CONTENT; + } + + case CONTENT: { + if (contentLength > 0) { + ByteBuf buffer = getBufferIfSufficient(input, contentLength, context); + if (buffer == null) { + input.release(); + return; + } + + FastCgiResponse response = readContent(buffer); + if (response != null) { + context.fireChannelRead(response); + } + } + state = State.HEADER; + } + } + } + } + + private void decodeHeader(ByteBuf buffer) { + buffer.skipBytes(1); + type = buffer.readUnsignedByte(); + id = buffer.readUnsignedShort(); + contentLength = buffer.readUnsignedShort(); + paddingLength = buffer.readUnsignedByte(); + buffer.skipBytes(1); + } + + private FastCgiResponse readContent(ByteBuf buffer) { + switch (type) { + case RecordType.END_REQUEST: + int appStatus = buffer.readInt(); + int protocolStatus = buffer.readUnsignedByte(); + buffer.skipBytes(3); + if (appStatus != 0 || protocolStatus != ProtocolStatus.REQUEST_COMPLETE.ordinal()) { + LOG.warn("Protocol status " + protocolStatus); + dataBuffers.remove(id); + return new FastCgiResponse(id, null); + } + else if (protocolStatus == ProtocolStatus.REQUEST_COMPLETE.ordinal()) { + return new FastCgiResponse(id, dataBuffers.remove(id)); + } + break; + + case RecordType.STDOUT: + ByteBuf data = dataBuffers.get(id); + ByteBuf sliced = buffer.slice(buffer.readerIndex(), contentLength); + if (data == null) { + dataBuffers.put(id, sliced); + } + else if (data instanceof CompositeByteBuf) { + ((CompositeByteBuf)data).addComponent(sliced); + data.writerIndex(data.writerIndex() + sliced.readableBytes()); + } + else { + dataBuffers.put(id, Unpooled.wrappedBuffer(data, sliced)); + } + sliced.retain(); + buffer.skipBytes(contentLength); + break; + + case RecordType.STDERR: + try { + errorOutputConsumer.consume(buffer.toString(buffer.readerIndex(), contentLength, CharsetUtil.UTF_8)); + } + catch (Throwable e) { + LOG.error(e); + } + buffer.skipBytes(contentLength); + break; + + default: + LOG.error("Unknown type " + type); + break; + } + return null; + } +} \ No newline at end of file diff --git a/xml/impl/src/org/jetbrains/io/fastCgi/FastCgiRequest.java b/xml/impl/src/org/jetbrains/io/fastCgi/FastCgiRequest.java new file mode 100644 index 000000000000..73dbcbf12b3a --- /dev/null +++ b/xml/impl/src/org/jetbrains/io/fastCgi/FastCgiRequest.java @@ -0,0 +1,147 @@ +package org.jetbrains.io.fastCgi; + +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Pair; +import com.intellij.openapi.vfs.VirtualFile; +import org.jetbrains.builtInWebServer.PathToFileManager; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufAllocator; +import io.netty.channel.Channel; +import io.netty.handler.codec.http.FullHttpRequest; +import io.netty.handler.codec.http.HttpHeaders; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.io.Responses; + +import java.net.InetSocketAddress; +import java.util.Locale; +import java.util.Map; + +public class FastCgiRequest { + private static final int PARAMS = 4; + private static final int BEGIN_REQUEST = 1; + private static final int RESPONDER = 1; + private static final int FCGI_KEEP_CONNECTION = 1; + private static final int STDIN = 5; + private static final int VERSION = 1; + + private final ByteBuf buffer; + final int requestId; + + public FastCgiRequest(int requestId, ByteBufAllocator allocator) { + this.requestId = requestId; + + buffer = allocator.buffer(); + writeHeader(buffer, BEGIN_REQUEST, FastCgiConstants.HEADER_LENGTH); + buffer.writeShort(RESPONDER); + buffer.writeByte(FCGI_KEEP_CONNECTION); + buffer.writeZero(5); + } + + public void writeFileHeaders(VirtualFile file, Project project, String canonicalRequestPath) { + Pair root = PathToFileManager.getInstance(project).getRoot(file); + FastCgiService.LOG.assertTrue(root != null); + addHeader("DOCUMENT_ROOT", root.first.getPath()); + addHeader("SCRIPT_FILENAME", file.getPath()); + addHeader("SCRIPT_NAME", canonicalRequestPath); + } + + public final void addHeader(@NotNull String key, @Nullable String value) { + if (value == null) { + return; + } + + int keyLength = key.length(); + int valLength = value.length(); + writeHeader(buffer, PARAMS, keyLength + valLength + (keyLength < 0x80 ? 1 : 4) + (valLength < 0x80 ? 1 : 4)); + + if (keyLength < 0x80) { + buffer.writeByte(keyLength); + } + else { + buffer.writeByte(0x80 | (keyLength >> 24)); + buffer.writeByte(keyLength >> 16); + buffer.writeByte(keyLength >> 8); + buffer.writeByte(keyLength); + } + + if (valLength < 0x80) { + buffer.writeByte(valLength); + } + else { + buffer.writeByte(0x80 | (valLength >> 24)); + buffer.writeByte(valLength >> 16); + buffer.writeByte(valLength >> 8); + buffer.writeByte(valLength); + } + + buffer.writeBytes(key.getBytes()); + buffer.writeBytes(value.getBytes()); + } + + public void writeHeaders(FullHttpRequest request, Channel clientChannel) { + addHeader("REQUEST_URI", request.uri()); + addHeader("REQUEST_METHOD", request.method().name()); + + InetSocketAddress remote = (InetSocketAddress)clientChannel.remoteAddress(); + addHeader("REMOTE_ADDR", remote.getAddress().getHostAddress()); + addHeader("REMOTE_PORT", Integer.toString(remote.getPort())); + + InetSocketAddress local = (InetSocketAddress)clientChannel.localAddress(); + addHeader("SERVER_SOFTWARE", Responses.getServerHeaderValue()); + addHeader("SERVER_NAME", Responses.getServerHeaderValue()); + + addHeader("SERVER_ADDR", local.getAddress().getHostAddress()); + addHeader("SERVER_PORT", Integer.toString(local.getPort())); + + addHeader("GATEWAY_INTERFACE", "CGI/1.1"); + addHeader("SERVER_PROTOCOL", request.protocolVersion().text()); + addHeader("CONTENT_TYPE", request.headers().get(HttpHeaders.Names.CONTENT_TYPE)); + + // PHP only, required if PHP was built with --enable-force-cgi-redirect + addHeader("REDIRECT_STATUS", "200"); + + String queryString = ""; + int queryIndex = request.uri().indexOf('?'); + if (queryIndex != -1) { + queryString = request.uri().substring(queryIndex + 1); + } + addHeader("QUERY_STRING", queryString); + + addHeader("CONTENT_LENGTH", String.valueOf(request.content().readableBytes())); + + for (Map.Entry entry : request.headers()) { + addHeader("HTTP_" + entry.getKey().replace('-', '_').toUpperCase(Locale.ENGLISH), entry.getValue()); + } + } + + final void writeToServerChannel(ByteBuf content, Channel fastCgiChannel) { + writeHeader(buffer, PARAMS, 0); + fastCgiChannel.write(buffer); + + if (content.isReadable()) { + ByteBuf headerBuffer = fastCgiChannel.alloc().buffer(FastCgiConstants.HEADER_LENGTH, FastCgiConstants.HEADER_LENGTH); + writeHeader(headerBuffer, STDIN, content.readableBytes()); + fastCgiChannel.write(headerBuffer); + + fastCgiChannel.write(content); + + headerBuffer = fastCgiChannel.alloc().buffer(FastCgiConstants.HEADER_LENGTH, FastCgiConstants.HEADER_LENGTH); + writeHeader(headerBuffer, STDIN, 0); + fastCgiChannel.write(headerBuffer); + } + else { + content.release(); + } + + fastCgiChannel.flush(); + } + + private void writeHeader(ByteBuf buffer, int type, int length) { + buffer.writeByte(VERSION); + buffer.writeByte(type); + buffer.writeShort(requestId); + buffer.writeShort(length); + buffer.writeZero(2); + } +} \ No newline at end of file diff --git a/xml/impl/src/org/jetbrains/io/fastCgi/FastCgiResponse.java b/xml/impl/src/org/jetbrains/io/fastCgi/FastCgiResponse.java new file mode 100644 index 000000000000..e249f7152c7c --- /dev/null +++ b/xml/impl/src/org/jetbrains/io/fastCgi/FastCgiResponse.java @@ -0,0 +1,21 @@ +package org.jetbrains.io.fastCgi; + +import io.netty.buffer.ByteBuf; + +public class FastCgiResponse { + private final int id; + private final ByteBuf data; + + public FastCgiResponse(int id, ByteBuf data) { + this.id = id; + this.data = data; + } + + public ByteBuf getData() { + return data; + } + + public int getId() { + return id; + } +} \ No newline at end of file diff --git a/xml/impl/src/org/jetbrains/io/fastCgi/FastCgiService.java b/xml/impl/src/org/jetbrains/io/fastCgi/FastCgiService.java new file mode 100644 index 000000000000..54f13c6c8ea1 --- /dev/null +++ b/xml/impl/src/org/jetbrains/io/fastCgi/FastCgiService.java @@ -0,0 +1,249 @@ +package org.jetbrains.io.fastCgi; + +import com.intellij.concurrency.JobScheduler; +import com.intellij.execution.filters.TextConsoleBuilder; +import com.intellij.execution.filters.TextConsoleBuilderFactory; +import com.intellij.execution.process.OSProcessHandler; +import com.intellij.execution.process.ProcessAdapter; +import com.intellij.execution.process.ProcessEvent; +import com.intellij.execution.ui.ConsoleView; +import com.intellij.execution.ui.ConsoleViewContentType; +import com.intellij.openapi.Disposable; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.AsyncResult; +import com.intellij.openapi.util.AsyncValueLoader; +import com.intellij.openapi.util.Key; +import com.intellij.openapi.wm.ToolWindow; +import com.intellij.openapi.wm.ToolWindowAnchor; +import com.intellij.openapi.wm.ToolWindowManager; +import com.intellij.ui.content.ContentFactory; +import com.intellij.util.Consumer; +import com.intellij.util.containers.ContainerUtil; +import com.intellij.util.containers.StripedLockIntObjectConcurrentHashMap; +import com.intellij.util.net.NetUtils; +import io.netty.bootstrap.Bootstrap; +import io.netty.buffer.ByteBuf; +import io.netty.channel.Channel; +import io.netty.channel.ChannelInitializer; +import io.netty.handler.codec.http.HttpResponseStatus; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.io.ChannelExceptionHandler; +import org.jetbrains.io.NettyUtil; +import org.jetbrains.io.Responses; + +import javax.swing.*; +import java.io.IOException; +import java.net.InetSocketAddress; +import java.util.List; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +// todo send FCGI_ABORT_REQUEST if client channel disconnected +public abstract class FastCgiService implements Disposable { + static final Logger LOG = Logger.getInstance(FastCgiService.class); + + protected final Project project; + + private final AtomicInteger requestIdCounter = new AtomicInteger(); + private final StripedLockIntObjectConcurrentHashMap requests = new StripedLockIntObjectConcurrentHashMap(); + + private volatile Channel fastCgiChannel; + + protected final AsyncValueLoader processHandler = new AsyncValueLoader() { + @Override + protected boolean isCancelOnReject() { + return true; + } + + @Override + protected void load(@NotNull final AsyncResult result) throws IOException { + final int port = NetUtils.findAvailableSocketPort(); + final OSProcessHandler processHandler = createProcessHandler(project, port); + if (processHandler == null) { + result.setRejected(); + return; + } + + result.doWhenRejected(new Runnable() { + @Override + public void run() { + processHandler.destroyProcess(); + } + }); + + final MyProcessAdapter processListener = new MyProcessAdapter(); + processHandler.addProcessListener(processListener); + processHandler.startNotify(); + + if (result.isRejected()) { + return; + } + + JobScheduler.getScheduler().schedule(new Runnable() { + @Override + public void run() { + if (result.isRejected()) { + return; + } + + ApplicationManager.getApplication().executeOnPooledThread(new Runnable() { + @Override + public void run() { + if (!result.isRejected()) { + try { + connectToProcess(result, port, processHandler, processListener); + } + catch (Throwable e) { + result.setRejected(); + LOG.error(e); + } + } + } + }); + } + }, NettyUtil.MIN_START_TIME, TimeUnit.MILLISECONDS); + } + + @Override + protected void disposeResult(@NotNull OSProcessHandler processHandler) { + try { + Channel currentFastCgiChannel = fastCgiChannel; + if (currentFastCgiChannel != null) { + fastCgiChannel = null; + NettyUtil.closeAndReleaseFactory(currentFastCgiChannel); + } + processHandler.destroyProcess(); + } + finally { + requestIdCounter.set(0); + if (!requests.isEmpty()) { + List waitingClients = ContainerUtil.toList(requests.elements()); + requests.clear(); + for (Channel channel : waitingClients) { + try { + if (channel.isActive()) { + Responses.sendStatus(HttpResponseStatus.BAD_GATEWAY, channel); + } + } + catch (Throwable e) { + NettyUtil.log(e, LOG); + } + } + } + } + } + }; + + private ConsoleView console; + + protected FastCgiService(Project project) { + this.project = project; + } + + protected abstract OSProcessHandler createProcessHandler(Project project, int port); + + private void connectToProcess(final AsyncResult asyncResult, final int port, final OSProcessHandler processHandler, final Consumer errorOutputConsumer) { + Bootstrap bootstrap = NettyUtil.oioClientBootstrap(); + final FastCgiChannelHandler fastCgiChannelHandler = new FastCgiChannelHandler(requests); + bootstrap.handler(new ChannelInitializer() { + @Override + protected void initChannel(Channel channel) throws Exception { + channel.pipeline().addLast(new FastCgiDecoder(errorOutputConsumer), fastCgiChannelHandler, ChannelExceptionHandler.getInstance()); + } + }); + fastCgiChannel = NettyUtil.connectClient(bootstrap, new InetSocketAddress(NetUtils.getLoopbackAddress(), port), asyncResult); + if (fastCgiChannel != null) { + asyncResult.setDone(processHandler); + } + } + + public void send(final FastCgiRequest fastCgiRequest, final ByteBuf content) { + content.retain(); + + if (processHandler.has()) { + fastCgiRequest.writeToServerChannel(content, fastCgiChannel); + } + else { + processHandler.get().doWhenDone(new Runnable() { + @Override + public void run() { + fastCgiRequest.writeToServerChannel(content, fastCgiChannel); + } + }).doWhenRejected(new Runnable() { + @Override + public void run() { + content.release(); + Channel channel = requests.get(fastCgiRequest.requestId); + if (channel != null && channel.isActive()) { + Responses.sendStatus(HttpResponseStatus.BAD_GATEWAY, channel); + } + } + }); + } + } + + public int allocateRequestId(Channel channel) { + int requestId = requestIdCounter.getAndIncrement(); + if (requestId >= Short.MAX_VALUE) { + requestIdCounter.set(0); + requestId = requestIdCounter.getAndDecrement(); + } + requests.put(requestId, channel); + return requestId; + } + + @Override + public void dispose() { + processHandler.reset(); + } + + protected abstract void buildConsole(@NotNull TextConsoleBuilder consoleBuilder); + + @NotNull + protected abstract String getConsoleToolWindowId(); + + @NotNull + protected abstract Icon getConsoleToolWindowIcon(); + + private final class MyProcessAdapter extends ProcessAdapter implements Consumer { + private void createConsole() { + TextConsoleBuilder consoleBuilder = TextConsoleBuilderFactory.getInstance().createBuilder(project); + buildConsole(consoleBuilder); + console = consoleBuilder.getConsole(); + + ApplicationManager.getApplication().invokeLater(new Runnable() { + @Override + public void run() { + ToolWindow toolWindow = ToolWindowManager.getInstance(project).registerToolWindow(getConsoleToolWindowId(), false, ToolWindowAnchor.BOTTOM, project, true); + toolWindow.setIcon(getConsoleToolWindowIcon()); + toolWindow.getContentManager().addContent(ContentFactory.SERVICE.getInstance().createContent(console.getComponent(), "", false)); + } + }, project.getDisposed()); + } + + @Override + public void onTextAvailable(ProcessEvent event, Key outputType) { + print(event.getText(), ConsoleViewContentType.getConsoleViewType(outputType)); + } + + private void print(String text, ConsoleViewContentType contentType) { + if (console == null) { + createConsole(); + } + console.print(text, contentType); + } + + @Override + public void processTerminated(ProcessEvent event) { + processHandler.reset(); + print(getConsoleToolWindowId() + " terminated\n", ConsoleViewContentType.SYSTEM_OUTPUT); + } + + @Override + public void consume(String message) { + print(message, ConsoleViewContentType.ERROR_OUTPUT); + } + } +} \ No newline at end of file diff --git a/xml/impl/src/org/jetbrains/notification/SingletonNotificationManager.java b/xml/impl/src/org/jetbrains/notification/SingletonNotificationManager.java new file mode 100644 index 000000000000..4ea4a10dcf85 --- /dev/null +++ b/xml/impl/src/org/jetbrains/notification/SingletonNotificationManager.java @@ -0,0 +1,86 @@ +package org.jetbrains.notification; + +import com.intellij.notification.*; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.wm.ToolWindowManager; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.concurrent.atomic.AtomicReference; + +public final class SingletonNotificationManager { + private final AtomicReference notification = new AtomicReference(); + + private final NotificationGroup group; + private final NotificationType type; + @Nullable + private final NotificationListener listener; + + private Runnable expiredListener; + + public SingletonNotificationManager(@NotNull String groupId, @NotNull NotificationType type, @Nullable NotificationListener listener) { + this(new NotificationGroup(groupId, NotificationDisplayType.STICKY_BALLOON, true), type, listener); + } + + public SingletonNotificationManager(@NotNull NotificationGroup group, @NotNull NotificationType type, @Nullable NotificationListener listener) { + this.group = group; + this.type = type; + this.listener = listener; + } + + public boolean notify(@NotNull String title, @NotNull String content) { + return notify(title, content, null); + } + + public boolean notify(@NotNull String title, @NotNull String content, @Nullable Project project) { + return notify(title, content, listener, project); + } + + public boolean notify(@NotNull String content, @Nullable Project project) { + return notify("", content, listener, project); + } + + public boolean notify(@NotNull String title, + @NotNull String content, + @Nullable NotificationListener listener, + @Nullable Project project) { + Notification oldNotification = notification.get(); + // !oldNotification.isExpired() is not enough - notification could be closed, but not expired + if (oldNotification != null) { + if (!oldNotification.isExpired() && (oldNotification.getBalloon() != null || + (project != null && + group.getDisplayType() == NotificationDisplayType.TOOL_WINDOW && + ToolWindowManager.getInstance(project).getToolWindowBalloon(group.getToolWindowId()) != null))) { + return false; + } + oldNotification.whenExpired(null); + oldNotification.expire(); + } + + if (expiredListener == null) { + expiredListener = new Runnable() { + @Override + public void run() { + Notification currentNotification = notification.get(); + if (currentNotification != null && currentNotification.isExpired()) { + notification.compareAndSet(currentNotification, null); + } + } + }; + } + + Notification newNotification = group.createNotification(title, content, type, listener); + newNotification.whenExpired(expiredListener); + notification.set(newNotification); + newNotification.notify(project); + return true; + } + + public void clear() { + Notification oldNotification = notification.getAndSet(null); + if (oldNotification != null) { + oldNotification.whenExpired(null); + oldNotification.expire(); + } + } +} \ No newline at end of file diff --git a/xml/impl/xml.iml b/xml/impl/xml.iml index 60e210829091..2fb3ee7ea262 100644 --- a/xml/impl/xml.iml +++ b/xml/impl/xml.iml @@ -21,6 +21,8 @@ + + diff --git a/xml/xml-psi-impl/src/com/intellij/javaee/ExternalResourceManagerExImpl.java b/xml/xml-psi-impl/src/com/intellij/javaee/ExternalResourceManagerExImpl.java index 2eee2882613e..d030ccf9f189 100644 --- a/xml/xml-psi-impl/src/com/intellij/javaee/ExternalResourceManagerExImpl.java +++ b/xml/xml-psi-impl/src/com/intellij/javaee/ExternalResourceManagerExImpl.java @@ -47,7 +47,7 @@ import java.util.*; @State(name = "ExternalResourceManagerImpl", storages = {@Storage( file = StoragePathMacros.APP_CONFIG + "/other.xml")}) -public class ExternalResourceManagerExImpl extends ExternalResourceManagerEx { +public class ExternalResourceManagerExImpl extends ExternalResourceManagerEx implements JDOMExternalizable { static final Logger LOG = Logger.getInstance("#com.intellij.j2ee.openapi.impl.ExternalResourceManagerImpl"); @NonNls public static final String J2EE_1_3 = "http://java.sun.com/dtd/"; @@ -374,6 +374,7 @@ public class ExternalResourceManagerExImpl extends ExternalResourceManagerEx { return getProjectResources(project).getModificationCount(); } + @Override public void readExternal(Element element) { final ExpandMacroToPathMap macroExpands = new ExpandMacroToPathMap(); myPathMacros.addMacroExpands(macroExpands); @@ -404,6 +405,7 @@ public class ExternalResourceManagerExImpl extends ExternalResourceManagerEx { } } + @Override public void writeExternal(Element element) { final String[] urls = getAvailableUrls(); for (String url : urls) {