From 8cfd2283f2a92f1d089d3ff0169d1966d6165091 Mon Sep 17 00:00:00 2001 From: "Denis.Zhdanov" Date: Wed, 28 Sep 2011 17:53:51 +0400 Subject: [PATCH] IDEA-53476 Gradle integration (Maven's level - dependencies, modules, repositories) 1. Corrected gradle entities cloning; 2. Added null-check to the class which hashCode()/equals() are called during deserialization when the state is not initialised yet; 3. Improve automatic library name generation algorithm; --- .../model/AbstractGradleDependency.java | 3 +- .../importing/model/GradleContentRoot.java | 3 +- .../importing/model/GradleDependency.java | 3 +- .../gradle/importing/model/GradleEntity.java | 5 +- .../model/GradleEntityCloneContext.java | 44 ++++++++++++++++ .../gradle/importing/model/GradleLibrary.java | 15 ++++-- .../model/GradleLibraryDependency.java | 5 +- .../gradle/importing/model/GradleModule.java | 32 ++++++++---- .../model/GradleModuleDependency.java | 7 +-- .../gradle/importing/model/GradleProject.java | 7 +-- .../GradleAdjustImportSettingsStep.java | 5 +- .../impl/GradleProjectResolverImpl.java | 50 +++++++++++++++++-- 12 files changed, 147 insertions(+), 32 deletions(-) create mode 100644 plugins/gradle/src/org/jetbrains/plugins/gradle/importing/model/GradleEntityCloneContext.java 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 b8bf9767825f..1a4538dc187f 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 @@ -57,7 +57,8 @@ public abstract class AbstractGradleDependency extends AbstractGradleEntity impl public String toString() { return "scope: " + getScope() + ", exported: " + isExported(); } - + + @NotNull @Override public GradleDependency clone() { try { diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/importing/model/GradleContentRoot.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/importing/model/GradleContentRoot.java index d22170d9b4c8..c4b6b78b88d7 100644 --- a/plugins/gradle/src/org/jetbrains/plugins/gradle/importing/model/GradleContentRoot.java +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/importing/model/GradleContentRoot.java @@ -99,8 +99,9 @@ public class GradleContentRoot extends AbstractGradleEntity { return buffer.toString(); } + @NotNull @Override - public GradleContentRoot clone() { + public GradleContentRoot clone(@NotNull GradleEntityCloneContext context) { GradleContentRoot result = new GradleContentRoot(getRootPath()); for (Map.Entry> entry : myData.entrySet()) { for (String path : entry.getValue()) { diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/importing/model/GradleDependency.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/importing/model/GradleDependency.java index 70d60d924bff..86da85d2303a 100644 --- a/plugins/gradle/src/org/jetbrains/plugins/gradle/importing/model/GradleDependency.java +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/importing/model/GradleDependency.java @@ -16,5 +16,6 @@ public interface GradleDependency extends GradleEntity { @NotNull DependencyScope getScope(); - GradleDependency clone(); + @NotNull + GradleDependency clone(@NotNull GradleEntityCloneContext context); } diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/importing/model/GradleEntity.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/importing/model/GradleEntity.java index fec73b86486d..cf843a1f27a8 100644 --- a/plugins/gradle/src/org/jetbrains/plugins/gradle/importing/model/GradleEntity.java +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/importing/model/GradleEntity.java @@ -15,7 +15,7 @@ import java.io.Serializable; * @author Denis Zhdanov * @since 8/12/11 12:50 PM */ -public interface GradleEntity extends Cloneable, Serializable { +public interface GradleEntity extends Serializable { /** * Follows contract of {@link PropertyChangeSupport#addPropertyChangeListener(PropertyChangeListener)} @@ -25,4 +25,7 @@ public interface GradleEntity extends Cloneable, Serializable { void addPropertyChangeListener(@NotNull PropertyChangeListener listener); void invite(@NotNull GradleEntityVisitor visitor); + + @NotNull + GradleEntity clone(@NotNull GradleEntityCloneContext context); } diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/importing/model/GradleEntityCloneContext.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/importing/model/GradleEntityCloneContext.java new file mode 100644 index 000000000000..dd86d7c22a85 --- /dev/null +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/importing/model/GradleEntityCloneContext.java @@ -0,0 +1,44 @@ +package org.jetbrains.plugins.gradle.importing.model; + +import com.intellij.util.containers.hash.HashMap; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Map; + +/** + * We need to be able to perform cloning of gradle entities. However, there is a possible case that particular entity + * encapsulates graph of other entities. For example, {@link GradleModule} has a number of + * {@link GradleModule#getDependencies() dependencies} where those dependencies can reference other modules that, in turn, also + * have dependencies. + *

+ * The problem is that we need to ensure that particular entity is shared within a single entities graph (e.g. there should + * be a single shared instance of {@link GradleModule gradle module} after cloning). That's why we need some place to serve + * as a cache during cloning. This class serves that purpose. + * + * @author Denis Zhdanov + * @since 9/28/11 12:36 PM + */ +public class GradleEntityCloneContext { + + private final Map myLibraries = new HashMap(); + private final Map myModules = new HashMap(); + + @Nullable + public GradleLibrary getLibrary(@NotNull GradleLibrary library) { + return myLibraries.get(library); + } + + public void store(@NotNull GradleLibrary key, @NotNull GradleLibrary value) { + myLibraries.put(key, value); + } + + @Nullable + public GradleModule getModule(@NotNull GradleModule module) { + return myModules.get(module); + } + + public void store(@NotNull GradleModule key, @NotNull GradleModule value) { + myModules.put(key, value); + } +} diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/importing/model/GradleLibrary.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/importing/model/GradleLibrary.java index 2c19ab72332c..e2c37d95baba 100644 --- a/plugins/gradle/src/org/jetbrains/plugins/gradle/importing/model/GradleLibrary.java +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/importing/model/GradleLibrary.java @@ -56,12 +56,17 @@ public class GradleLibrary extends AbstractNamedGradleEntity implements Named { return "library: " + getName(); } + @NotNull @Override - protected GradleLibrary clone() { - GradleLibrary result = new GradleLibrary(getName()); - for (Map.Entry entry : myPaths.entrySet()) { - result.addPath(entry.getKey(), entry.getValue()); - } + public GradleLibrary clone(@NotNull GradleEntityCloneContext context) { + GradleLibrary result = context.getLibrary(this); + if (result == null) { + result = new GradleLibrary(getName()); + context.store(this, result); + for (Map.Entry entry : myPaths.entrySet()) { + result.addPath(entry.getKey(), entry.getValue()); + } + } return result; } } diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/importing/model/GradleLibraryDependency.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/importing/model/GradleLibraryDependency.java index 003146dabe34..2586f460c53a 100644 --- a/plugins/gradle/src/org/jetbrains/plugins/gradle/importing/model/GradleLibraryDependency.java +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/importing/model/GradleLibraryDependency.java @@ -46,9 +46,10 @@ public class GradleLibraryDependency extends AbstractGradleDependency { return super.toString() + ", dependency library: " + getLibrary(); } + @NotNull @Override - public GradleLibraryDependency clone() { - GradleLibraryDependency result = new GradleLibraryDependency(getLibrary().clone()); + public GradleLibraryDependency clone(@NotNull GradleEntityCloneContext context) { + GradleLibraryDependency result = new GradleLibraryDependency(getLibrary().clone(context)); copyTo(result); return result; } diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/importing/model/GradleModule.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/importing/model/GradleModule.java index 172dc2bce629..14a708ffd002 100644 --- a/plugins/gradle/src/org/jetbrains/plugins/gradle/importing/model/GradleModule.java +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/importing/model/GradleModule.java @@ -97,10 +97,13 @@ public class GradleModule extends AbstractNamedGradleEntity implements Named { @Override public int hashCode() { int result = super.hashCode(); - result = 31 * result + myModuleFilePath.hashCode(); + + // Sometimes instance fields are not initialised here during deserialization. Seems to be standard library bug. That's why we + // do null-check here. + result = myModuleFilePath == null ? result : (31 * result + myModuleFilePath.hashCode()); result = 31 * result + (myInheritProjectCompileOutputPath ? 1 : 0); - result = 31 * result + myCompileOutputPaths.hashCode(); - result = 31 * result + myContentRoots.hashCode(); + result = myCompileOutputPaths == null ? result : (31 * result + myCompileOutputPaths.hashCode()); + result = myContentRoots == null ? result : (31 * result + myContentRoots.hashCode()); // We intentionally don't use dependencies here in order to allow module mappings before and after external libraries // resolving (downloading) @@ -116,10 +119,13 @@ public class GradleModule extends AbstractNamedGradleEntity implements Named { GradleModule that = (GradleModule)o; if (!super.equals(that)) return false; - if (!myModuleFilePath.equals(that.myModuleFilePath)) return false; + // Sometimes instance fields are not initialised here during deserialization. Seems to be standard library bug. That's why we + // do null-check here. + if (myModuleFilePath == null ? that.myModuleFilePath != null : !myModuleFilePath.equals(that.myModuleFilePath)) return false; if (myInheritProjectCompileOutputPath != that.myInheritProjectCompileOutputPath) return false; - if (!myCompileOutputPaths.equals(that.myCompileOutputPaths)) return false; - if (!myContentRoots.equals(that.myContentRoots)) return false; + if (myCompileOutputPaths == null + ? that.myCompileOutputPaths != null : !myCompileOutputPaths.equals(that.myCompileOutputPaths)) return false; + if (myContentRoots == null ? that.myContentRoots != null : !myContentRoots.equals(that.myContentRoots)) return false; // We intentionally don't use dependencies here in order to allow module mappings before and after external libraries // resolving (downloading) @@ -135,18 +141,24 @@ public class GradleModule extends AbstractNamedGradleEntity implements Named { ); } + @NotNull @Override - public GradleModule clone() { - GradleModule result = new GradleModule(getName(), new File(getModuleFilePath()).getParent()); + public GradleModule clone(@NotNull GradleEntityCloneContext context) { + GradleModule result = context.getModule(this); + if (result != null) { + return result; + } + result = new GradleModule(getName(), new File(getModuleFilePath()).getParent()); + context.store(this, result); result.setInheritProjectCompileOutputPath(isInheritProjectCompileOutputPath()); for (GradleContentRoot contentRoot : getContentRoots()) { - result.addContentRoot(contentRoot.clone()); + result.addContentRoot(contentRoot.clone(context)); } for (Map.Entry entry : myCompileOutputPaths.entrySet()) { result.setCompileOutputPath(entry.getKey(), entry.getValue()); } for (GradleDependency dependency : getDependencies()) { - result.addDependency(dependency.clone()); + result.addDependency(dependency.clone(context)); } return result; } diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/importing/model/GradleModuleDependency.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/importing/model/GradleModuleDependency.java index e42b02a7e7de..da9e2e86045d 100644 --- a/plugins/gradle/src/org/jetbrains/plugins/gradle/importing/model/GradleModuleDependency.java +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/importing/model/GradleModuleDependency.java @@ -54,10 +54,11 @@ public class GradleModuleDependency extends AbstractGradleDependency { return super.toString() + ", dependency module: " + getModule(); } + @NotNull @Override - public GradleModuleDependency clone() { - GradleModuleDependency result = new GradleModuleDependency(getModule().clone()); - copyTo(result); + public GradleModuleDependency clone(@NotNull GradleEntityCloneContext context) { + GradleModuleDependency result = new GradleModuleDependency(getModule().clone(context)); + copyTo(result); return result; } } diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/importing/model/GradleProject.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/importing/model/GradleProject.java index 1b6f20019c5f..a163e1a4dc00 100644 --- a/plugins/gradle/src/org/jetbrains/plugins/gradle/importing/model/GradleProject.java +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/importing/model/GradleProject.java @@ -194,17 +194,18 @@ public class GradleProject extends AbstractNamedGradleEntity { getName(), getJdkVersion(), getLanguageLevel(), getModules()); } + @NotNull @Override - public GradleProject clone() { + public GradleProject clone(@NotNull GradleEntityCloneContext context) { GradleProject result = new GradleProject(getProjectFileDirectoryPath(), getCompileOutputPath()); result.setName(getName()); result.setJdkVersion(getJdkVersion()); result.setLanguageLevel(getLanguageLevel()); for (GradleModule module : getModules()) { - result.addModule(module.clone()); + result.addModule(module.clone(context)); } for (GradleLibrary library : getLibraries()) { - result.addLibrary(library.clone()); + result.addLibrary(library.clone(context)); } return result; } diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/importing/wizard/adjust/GradleAdjustImportSettingsStep.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/importing/wizard/adjust/GradleAdjustImportSettingsStep.java index 5af5a7c50657..61e9b7854413 100644 --- a/plugins/gradle/src/org/jetbrains/plugins/gradle/importing/wizard/adjust/GradleAdjustImportSettingsStep.java +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/importing/wizard/adjust/GradleAdjustImportSettingsStep.java @@ -174,9 +174,10 @@ public class GradleAdjustImportSettingsStep extends AbstractImportFromGradleWiza Collections.sort(modules, Named.COMPARATOR); List moduleNodes = new ArrayList(); Map moduleMappings = new HashMap(); - + + GradleEntityCloneContext cloneContext = new GradleEntityCloneContext(); for (GradleModule module : modules) { - GradleModule moduleCopy = module.clone(); + GradleModule moduleCopy = module.clone(cloneContext); moduleMappings.put(module, moduleCopy); DefaultMutableTreeNode moduleNode = buildNode(module, entity2nodes, counter++); moduleNodes.add(moduleNode); diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/remote/impl/GradleProjectResolverImpl.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/remote/impl/GradleProjectResolverImpl.java index e5faf477373c..17dad9759b35 100644 --- a/plugins/gradle/src/org/jetbrains/plugins/gradle/remote/impl/GradleProjectResolverImpl.java +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/remote/impl/GradleProjectResolverImpl.java @@ -13,8 +13,6 @@ import org.gradle.tooling.model.idea.*; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.plugins.gradle.importing.model.*; -import org.jetbrains.plugins.gradle.importing.model.GradleLibraryDependency; -import org.jetbrains.plugins.gradle.importing.model.GradleProject; import org.jetbrains.plugins.gradle.remote.GradleProjectResolver; import org.jetbrains.plugins.gradle.remote.RemoteGradleProcessSettings; import org.jetbrains.plugins.gradle.remote.RemoteGradleService; @@ -24,6 +22,7 @@ import org.jetbrains.plugins.gradle.util.GradleUtil; import java.io.File; import java.rmi.RemoteException; +import java.util.Arrays; import java.util.HashSet; import java.util.Map; import java.util.Set; @@ -37,7 +36,11 @@ import java.util.concurrent.atomic.AtomicReference; * @since 8/8/11 11:09 AM */ public class GradleProjectResolverImpl extends RemoteObject implements GradleProjectResolver, RemoteGradleService { - + + private static final Set NON_UNIQUE_PATH_ENTRIES = new HashSet(Arrays.asList( + "src", "main", "java", "test", "resources" + )); + private final BlockingQueue myConnections = new LinkedBlockingQueue(); private final AtomicReference mySettings = new AtomicReference(); @@ -55,6 +58,7 @@ public class GradleProjectResolverImpl extends RemoteObject implements GradlePro // populating dependent module object. Map> modules = createModules(project, result); populateModules(modules.values(), result); + diversifyLibraryNamesIfNecessary(result); return result; } @@ -310,6 +314,46 @@ public class GradleProjectResolverImpl extends RemoteObject implements GradlePro } return null; } + + /** + * Gradle API doesn't provide library names at the moment, so, we deduce them from the path. However, it's possible to have identical + * names then. + *

+ * This method solves that by diversifying duplicate library names. + * + * @param project target project which library names should be diversified if necessary + */ + private static void diversifyLibraryNamesIfNecessary(@NotNull GradleProject project) { + Map libraries = new HashMap(); + for (GradleLibrary library : project.getLibraries()) { + GradleLibrary previous = libraries.remove(library.getName()); + if (previous == null) { + libraries.put(library.getName(), library); + continue; + } + previous.setName(generateName(previous)); + libraries.put(previous.getName(), previous); + library.setName(generateName(library)); + libraries.put(library.getName(), library); + } + } + + @NotNull + private static String generateName(@NotNull GradleLibrary library) { + for (LibraryPathType pathType : LibraryPathType.values()) { + String path = library.getPath(pathType); + if (path == null) { + continue; + } + File file = new File(path).getParentFile(); + for (; file != null; file = file.getParentFile()) { + if (!NON_UNIQUE_PATH_ENTRIES.contains(file.getName())) { + return file.getName() + "-" + library.getName(); + } + } + } + return library.getName(); + } /** * Allows to retrieve gradle api connection to use for the given project.