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;
This commit is contained in:
Denis.Zhdanov
2011-09-28 17:55:38 +04:00
parent 78c02bf4ea
commit 8cfd2283f2
12 changed files with 147 additions and 32 deletions
@@ -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 {
@@ -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<SourceType, Collection<String>> entry : myData.entrySet()) {
for (String path : entry.getValue()) {
@@ -16,5 +16,6 @@ public interface GradleDependency extends GradleEntity {
@NotNull
DependencyScope getScope();
GradleDependency clone();
@NotNull
GradleDependency clone(@NotNull GradleEntityCloneContext context);
}
@@ -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);
}
@@ -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.
* <p/>
* 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<GradleLibrary, GradleLibrary> myLibraries = new HashMap<GradleLibrary, GradleLibrary>();
private final Map<GradleModule, GradleModule> myModules = new HashMap<GradleModule, GradleModule>();
@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);
}
}
@@ -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<LibraryPathType, String> 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<LibraryPathType, String> entry : myPaths.entrySet()) {
result.addPath(entry.getKey(), entry.getValue());
}
}
return result;
}
}
@@ -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;
}
@@ -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<SourceType, String> entry : myCompileOutputPaths.entrySet()) {
result.setCompileOutputPath(entry.getKey(), entry.getValue());
}
for (GradleDependency dependency : getDependencies()) {
result.addDependency(dependency.clone());
result.addDependency(dependency.clone(context));
}
return result;
}
@@ -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;
}
}
@@ -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;
}
@@ -174,9 +174,10 @@ public class GradleAdjustImportSettingsStep extends AbstractImportFromGradleWiza
Collections.sort(modules, Named.COMPARATOR);
List<MutableTreeNode> moduleNodes = new ArrayList<MutableTreeNode>();
Map<GradleModule, GradleModule> moduleMappings = new HashMap<GradleModule, GradleModule>();
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);
@@ -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<String> NON_UNIQUE_PATH_ENTRIES = new HashSet<String>(Arrays.asList(
"src", "main", "java", "test", "resources"
));
private final BlockingQueue<ProjectConnection> myConnections = new LinkedBlockingQueue<ProjectConnection>();
private final AtomicReference<RemoteGradleProcessSettings> mySettings = new AtomicReference<RemoteGradleProcessSettings>();
@@ -55,6 +58,7 @@ public class GradleProjectResolverImpl extends RemoteObject implements GradlePro
// populating dependent module object.
Map<String, Pair<GradleModule, IdeaModule>> 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.
* <p/>
* 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<String, GradleLibrary> libraries = new HashMap<String, GradleLibrary>();
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.