diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/importing/model/AbstractGradleDependency.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/importing/model/AbstractGradleDependency.java index 1a4538dc187f..5e26f0accf18 100644 --- a/plugins/gradle/src/org/jetbrains/plugins/gradle/importing/model/AbstractGradleDependency.java +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/importing/model/AbstractGradleDependency.java @@ -35,22 +35,12 @@ public abstract class AbstractGradleDependency extends AbstractGradleEntity impl @Override public int hashCode() { - int result = myScope.hashCode(); - result = 31 * result + (myExported ? 1 : 0); - return result; + return 31; } @Override public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - AbstractGradleDependency that = (AbstractGradleDependency)o; - - if (myExported != that.myExported) return false; - if (myScope != that.myScope) return false; - - return true; + return this == o || (o != null && getClass() == o.getClass()); } @Override diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/remote/impl/GradleLibraryNamesMixer.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/remote/impl/GradleLibraryNamesMixer.java new file mode 100644 index 000000000000..45413cdf04e1 --- /dev/null +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/remote/impl/GradleLibraryNamesMixer.java @@ -0,0 +1,222 @@ +package org.jetbrains.plugins.gradle.remote.impl; + +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.util.containers.HashMap; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.plugins.gradle.importing.model.GradleLibrary; +import org.jetbrains.plugins.gradle.importing.model.LibraryPathType; + +import java.io.File; +import java.util.*; + +/** + * Encapsulates logic of checking if particular collection of gradle libraries contains libraries with the same names and + * tries to diversify them in the case of the positive answer. + *

+ * Thread-safe. + * + * @author Denis Zhdanov + * @since 10/19/11 2:04 PM + */ +public class GradleLibraryNamesMixer { + + /** + * Holds mappings like ('file name'; boolean) where 'file name' defines 'too common' file/dir + * name that should not be used during library name generation. Boolean flag indicates if 'common file name' may be used + * if 'non-common' files are the same. + *

+ * Example: consider the following file system tree: + *

+   *   module
+   *     |_src
+   *        |_main
+   *        |  |_resources
+   *        |
+   *        |_test
+   *           |_resources
+   * 
+ * Let's say we have two libraries where one of them points to 'src/main/resources' and another one + * to 'src/test/resources'. We want to generate names 'module-resources' and + * 'module-test-resources' respectively because 'test' entry at the current collection is + * stored with 'true' flag. + */ + private static final Map NON_UNIQUE_PATH_ENTRIES = new HashMap(); + static { + NON_UNIQUE_PATH_ENTRIES.put("src", false); + NON_UNIQUE_PATH_ENTRIES.put("main", false); + NON_UNIQUE_PATH_ENTRIES.put("test", true); + NON_UNIQUE_PATH_ENTRIES.put("resources", false); + NON_UNIQUE_PATH_ENTRIES.put("java", false); + NON_UNIQUE_PATH_ENTRIES.put("groovy", false); + } + private static final char NAME_SEPARATOR = '-'; + + /** + * Tries to ensure that given libraries have distinct names, i.e. traverses all of them and tries to generate + * unique name for those with equal names. + * + * @param libraries libraries to process + */ + @SuppressWarnings("MethodMayBeStatic") + public void mixNames(@NotNull Iterable libraries) { + Map names = new HashMap(); + List data = new ArrayList(); + for (GradleLibrary library : libraries) { + Wrapped wrapped = new Wrapped(library); + data.add(wrapped); + } + boolean mixed = false; + while (!mixed) { + mixed = doMixNames(data, names); + } + } + + /** + * Does the same as {@link #mixNames(Iterable)} but uses given ('library name; wrapped library'} mappings cache. + * + * @param libraries libraries to process + * @param cache cache to use + * @return true if all of the given libraries have distinct names now; false otherwise + */ + private static boolean doMixNames(@NotNull Collection libraries, @NotNull Map cache) { + cache.clear(); + for (Wrapped current : libraries) { + Wrapped previous = cache.remove(current.library.getName()); + if (previous == null) { + cache.put(current.library.getName(), current); + } + else { + mixNames(current, previous); + return current.library.getName().equals(previous.library.getName()); // Stop processing if it's not possible to generate + } + } + return true; + } + + /** + * Tries to generate distinct names for the given wrapped libraries (assuming that they have equal names at the moment). + * + * @param wrapped1 one of the libraries with equal names + * @param wrapped2 another library which name is equal to the name of the given one + */ + @SuppressWarnings("AssignmentToForLoopParameter") + private static void mixNames(@NotNull Wrapped wrapped1, @NotNull Wrapped wrapped2) { + if (!wrapped1.prepare() || !wrapped2.prepare()) { + return; + } + String wrapped1AltText = null; + String wrapped2AltText = null; + + for (File file1 = wrapped1.currentFile, file2 = wrapped2.currentFile; + file1 != null && file2 != null; + file1 = file1.getParentFile(), file2 = file2.getParentFile()) + { + while (file1 != null && !StringUtil.isEmpty(file1.getName()) && NON_UNIQUE_PATH_ENTRIES.containsKey(file1.getName())) { + if (NON_UNIQUE_PATH_ENTRIES.get(file1.getName())) { + if (StringUtil.isEmpty(wrapped1AltText)) { + wrapped1AltText = file1.getName(); + } + else { + wrapped1AltText += NAME_SEPARATOR + file1.getName(); + } + } + file1 = file1.getParentFile(); + } + while (file2 != null && !StringUtil.isEmpty(file2.getName()) && NON_UNIQUE_PATH_ENTRIES.containsKey(file2.getName())) { + if (NON_UNIQUE_PATH_ENTRIES.get(file2.getName())) { + if (StringUtil.isEmpty(wrapped2AltText)) { + wrapped2AltText = file2.getName(); + } + else { + wrapped2AltText += NAME_SEPARATOR + file2.getName(); + } + } + file2 = file2.getParentFile(); + } + + if (file1 == null) { + wrapped1.nextFile(); + } + if (file2 == null) { + wrapped2.nextFile(); + } + + if (file1 != null && file2 != null && !file1.getName().equals(file2.getName())) { + wrapped1.library.setName(file1.getName() + NAME_SEPARATOR + wrapped1.library.getName()); + wrapped2.library.setName(file2.getName() + NAME_SEPARATOR + wrapped2.library.getName()); + } + + if (wrapped1.library.getName().equals(wrapped2.library.getName())) { + if (wrapped1AltText != null) { + diversifyName(wrapped1AltText, wrapped1, file1); + return; + } + else if (wrapped2AltText != null) { + diversifyName(wrapped2AltText, wrapped2, file1); + return; + } + } + else { + return; + } + + if (file1 == null || file2 == null) { + return; + } + } + } + + @SuppressWarnings("ConstantConditions") + private static void diversifyName(@NotNull String changeText, @NotNull Wrapped wrapped, @Nullable File file) { + String name = wrapped.library.getName(); + int i = file == null ? - 1 : name.indexOf(file.getName()); + final String newName; + if (i >= 0) { + newName = name.substring(0, i + file.getName().length()) + NAME_SEPARATOR + changeText + name.substring(i + file.getName().length()); + } + else { + newName = changeText + NAME_SEPARATOR + name; + } + wrapped.library.setName(newName); + } + + /** + * Wraps target library and hold auxiliary information required for the processing. + */ + private static class Wrapped { + /** Holds list of files that may be used for name generation. */ + public final Set files = new HashSet(); + /** File that was used for the current name generation. */ + public File currentFile; + /** Target library. */ + public GradleLibrary library; + + Wrapped(@NotNull GradleLibrary library) { + this.library = library; + for (LibraryPathType pathType : LibraryPathType.values()) { + String path = library.getPath(pathType); + if (path != null) { + files.add(new File(path)); + } + } + } + + public boolean prepare() { + if (currentFile != null) { + return true; + } + return nextFile(); + } + + public boolean nextFile() { + if (files.isEmpty()) { + return false; + } + Iterator iterator = files.iterator(); + currentFile = iterator.next(); + iterator.remove(); + return true; + } + } +} diff --git a/plugins/gradle/testSources/org/jetbrains/plugins/gradle/remote/impl/GradleLibraryNamesMixerTest.java b/plugins/gradle/testSources/org/jetbrains/plugins/gradle/remote/impl/GradleLibraryNamesMixerTest.java new file mode 100644 index 000000000000..dc5dc5077934 --- /dev/null +++ b/plugins/gradle/testSources/org/jetbrains/plugins/gradle/remote/impl/GradleLibraryNamesMixerTest.java @@ -0,0 +1,71 @@ +package org.jetbrains.plugins.gradle.remote.impl; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.plugins.gradle.importing.model.GradleLibrary; +import org.jetbrains.plugins.gradle.importing.model.LibraryPathType; +import org.junit.Before; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.IdentityHashMap; +import java.util.List; +import java.util.Map; + +import static org.junit.Assert.assertEquals; + +/** + * @author Denis Zhdanov + * @since 10/19/11 5:25 PM + */ +public class GradleLibraryNamesMixerTest { + + private GradleLibraryNamesMixer myMixer; + + @Before + public void setUp() { + myMixer = new GradleLibraryNamesMixer(); + } + + @Test + public void sourceVsTest() { + doTest( + t("resources", "my-module-resources", "dir1/dir2/my-module/src/main/resources"), + t("resources", "my-module-test-resources", "dir1/dir2/my-module/src/test/resources"), + t("resources", "my-another-module-resources", "dir1/dir2/my-another-module/src/main/resources"), + t("resources", "my-another-module-test-resources", "dir1/dir2/my-another-module/src/test/resources") + ); + } + + private void doTest(TestDataEntry... entries) { + Map expected = new IdentityHashMap(); + List libraries = new ArrayList(); + for (TestDataEntry entry : entries) { + GradleLibrary library = new GradleLibrary(entry.initialName); + library.addPath(LibraryPathType.BINARY, entry.path); + libraries.add(library); + expected.put(library, entry.expectedName); + } + + myMixer.mixNames(libraries); + for (GradleLibrary library : libraries) { + assertEquals(expected.get(library), library.getName()); + } + } + + private static class TestDataEntry { + + public String initialName; + public String expectedName; + public String path; + + TestDataEntry(@NotNull String initialName, @NotNull String expectedName, @NotNull String path) { + this.initialName = initialName; + this.expectedName = expectedName; + this.path = path; + } + } + + public static TestDataEntry t(@NotNull String initialName, @NotNull String expectedName, @NotNull String path) { + return new TestDataEntry(initialName, expectedName, path); + } +}