IDEA-76142: Gradle support - cannot update IDEA projects once one of build.gradle files changes

1. Changes infrastructure is re-written in order to not hold references to the changed entities (Module, LibraryOrderEntry etc). Reason: their hash code may be changed on state change (e.g. new binary file registered for the library changes its hash code);
2. Added support for showing conflict nodes at the gradle project structure tree;
3. Corresponding tests have been added;
This commit is contained in:
Denis.Zhdanov
2012-02-07 10:31:42 +04:00
parent bc66b65e21
commit f9fa4ebd4c
36 changed files with 931 additions and 268 deletions
@@ -54,6 +54,11 @@ gradle.sync.change.type.intellij={0} local setting
gradle.sync.change.type.conflict=Settings conflict
gradle.sync.change.type.unchanged=Unchanged setting
gradle.sync.change.type.confirmed=Confirmed settings diff
gradle.sync.change.entity.type.module=module
gradle.sync.change.entity.type.library.dependency=library dependency
gradle.sync.change.library.path=library ''{0}'' binary path
gradle.sync.change.project.name.text=project name
gradle.sync.change.project.language.level.text=project language level change
gradle.library.resolve.progress.text=Resolving gradle libraries
+5 -1
View File
@@ -39,9 +39,10 @@
<applicationService serviceInterface="org.jetbrains.plugins.gradle.diff.GradleStructureChangesCalculator"
serviceImplementation="org.jetbrains.plugins.gradle.diff.GradleProjectStructureChangesCalculator"/>
<applicationService serviceImplementation="org.jetbrains.plugins.gradle.diff.GradleModuleStructureChangesCalculator"/>
<applicationService serviceImplementation="org.jetbrains.plugins.gradle.diff.GradleLibraryStructureChangesCalculator"/>
<applicationService serviceImplementation="org.jetbrains.plugins.gradle.diff.GradleLibraryDependencyStructureChangesCalculator"/>
<applicationService serviceInterface="org.jetbrains.plugins.gradle.diff.PlatformFacade"
serviceImplementation="org.jetbrains.plugins.gradle.diff.GradleProjectStructureHelperImpl"/>
serviceImplementation="org.jetbrains.plugins.gradle.diff.PlatformFacadeImpl"/>
<applicationService serviceInterface="org.jetbrains.plugins.gradle.notification.GradleProgressNotificationManager"
serviceImplementation="org.jetbrains.plugins.gradle.notification.GradleProgressNotificationManagerImpl"/>
@@ -62,6 +63,9 @@
<component>
<implementation-class>org.jetbrains.plugins.gradle.sync.GradleProjectStructureChangesModel</implementation-class>
</component>
<component>
<implementation-class>org.jetbrains.plugins.gradle.sync.GradleProjectStructureHelper</implementation-class>
</component>
</project-components>
<actions>
@@ -12,6 +12,7 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.plugins.gradle.diff.PlatformFacade;
import org.jetbrains.plugins.gradle.sync.GradleProjectStructureChangesModel;
import org.jetbrains.plugins.gradle.sync.GradleProjectStructureChangesPanel;
import org.jetbrains.plugins.gradle.sync.GradleProjectStructureHelper;
import org.jetbrains.plugins.gradle.ui.GradleIcons;
import org.jetbrains.plugins.gradle.util.GradleBundle;
@@ -24,15 +25,18 @@ import org.jetbrains.plugins.gradle.util.GradleBundle;
public class GradleBootstrap extends AbstractProjectComponent {
private static final String GRADLE_TOOL_WINDOW_ID = GradleBundle.message("gradle.name");
private final GradleProjectStructureChangesModel myChangesModel;
private final PlatformFacade myProjectStructureHelper;
private final PlatformFacade myPlatformFacade;
private final GradleProjectStructureHelper myProjectStructureHelper;
public GradleBootstrap(@NotNull Project project,
@NotNull GradleProjectStructureChangesModel changesModel,
@NotNull PlatformFacade projectStructureHelper) {
@NotNull PlatformFacade platformFacade,
@NotNull GradleProjectStructureHelper projectStructureHelper) {
super(project);
myChangesModel = changesModel;
myPlatformFacade = platformFacade;
myProjectStructureHelper = projectStructureHelper;
}
@@ -52,7 +56,7 @@ public class GradleBootstrap extends AbstractProjectComponent {
toolWindow.setIcon(GradleIcons.GRADLE_ICON);
String syncTitle = GradleBundle.message("gradle.sync.title.tab");
final GradleProjectStructureChangesPanel projectStructureChanges
= new GradleProjectStructureChangesPanel(myProject, myChangesModel, myProjectStructureHelper);
= new GradleProjectStructureChangesPanel(myProject, myChangesModel, myPlatformFacade, myProjectStructureHelper);
toolWindow.getContentManager().addContent(new ContentImpl(projectStructureChanges, syncTitle, true));
}
}
@@ -10,6 +10,7 @@ import com.intellij.util.messages.MessageBusConnection;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.plugins.gradle.diff.PlatformFacade;
import org.jetbrains.plugins.gradle.sync.GradleProjectStructureHelper;
import org.jetbrains.plugins.gradle.util.GradleBundle;
import org.jetbrains.plugins.gradle.ui.RichTextControlBuilder;
@@ -43,14 +44,17 @@ public abstract class GradleToolWindowPanel extends SimpleToolWindowPanel {
private final JPanel myContent = new JPanel(myLayout);
private final Project myProject;
private final PlatformFacade myProjectStructureHelper;
private final PlatformFacade myProjectFacade;
private final GradleProjectStructureHelper myProjectStructureHelper;
protected GradleToolWindowPanel(@NotNull Project project,
@Nullable PlatformFacade projectStructureHelper,
@Nullable PlatformFacade projectFacade,
@NotNull GradleProjectStructureHelper projectStructureHelper,
@NotNull String place)
{
super(true);
myProject = project;
myProjectFacade = projectFacade;
myProjectStructureHelper = projectStructureHelper;
final ActionManager actionManager = ActionManager.getInstance();
final ActionGroup actionGroup = (ActionGroup)actionManager.getAction(TOOL_WINDOW_TOOLBAR_ID);
@@ -98,7 +102,12 @@ public abstract class GradleToolWindowPanel extends SimpleToolWindowPanel {
}
@NotNull
public PlatformFacade getProjectStructureHelper() {
public PlatformFacade getProjectFacade() {
return myProjectFacade;
}
@NotNull
public GradleProjectStructureHelper getProjectStructureHelper() {
return myProjectStructureHelper;
}
@@ -19,19 +19,25 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* Defines contract for change that points to particular property value change.
* Defines general contract for a change that encapsulates information about conflicting property value of the matched gradle
* and intellij entities.
* <p/>
* For example we may match particular gradle library to an intellij library but they may have different set of attached binaries.
* <p/>
* Thread-safe.
*
* @author Denis Zhdanov
* @since 11/15/11 7:57 PM
* @param <T> target property value type
*/
public abstract class GradleAbstractPropertyValueChange<T> extends GradleAbstractProjectStructureChange {
public abstract class GradleAbstractConflictingPropertyChange<T> extends GradleAbstractProjectStructureChange {
private final String myPropertyDescription;
private final T myGradleValue;
private final T myIntellijValue;
private final String myProperyName;
private final T myGradleValue;
private final T myIntellijValue;
public GradleAbstractPropertyValueChange(@NotNull String propertyName, @Nullable T gradleValue, @Nullable T intellijValue) {
myProperyName = propertyName;
public GradleAbstractConflictingPropertyChange(@NotNull String propertyDescription, @Nullable T gradleValue, @Nullable T intellijValue) {
myPropertyDescription = propertyDescription;
myGradleValue = gradleValue;
myIntellijValue = intellijValue;
}
@@ -56,7 +62,7 @@ public abstract class GradleAbstractPropertyValueChange<T> extends GradleAbstrac
public int hashCode() {
int result = super.hashCode();
result = 31 * result + (myGradleValue != null ? myGradleValue.hashCode() : 0);
return 31 * result + (myIntellijValue != null ? myIntellijValue.hashCode() : 0);
return 31 * result + (myIntellijValue != null ? myIntellijValue.hashCode() : 0);
}
@Override
@@ -65,16 +71,15 @@ public abstract class GradleAbstractPropertyValueChange<T> extends GradleAbstrac
if (o == null || getClass() != o.getClass()) return false;
if (!super.equals(o)) return false;
GradleAbstractPropertyValueChange that = (GradleAbstractPropertyValueChange)o;
GradleAbstractConflictingPropertyChange that = (GradleAbstractConflictingPropertyChange)o;
if (myGradleValue != null ? !myGradleValue.equals(that.myGradleValue) : that.myGradleValue != null) return false;
if (myIntellijValue != null ? !myIntellijValue.equals(that.myIntellijValue) : that.myIntellijValue != null) return false;
return true;
}
@Override
public String toString() {
return String.format("%s change: gradle='%s', intellij='%s'", myProperyName, myGradleValue, myIntellijValue);
return String.format("%s change: gradle='%s', intellij='%s'", myPropertyDescription, myGradleValue, myIntellijValue);
}
}
@@ -37,7 +37,7 @@ public class GradleDiffUtil {
@Override
public void visit(@NotNull GradleModule module) {
currentChanges.add(new GradleModulePresenceChange(module, null));
currentChanges.add(new GradleModulePresenceChange(module.getName(), null));
for (GradleDependency dependency : module.getDependencies()) {
dependency.invite(this);
}
@@ -68,11 +68,14 @@ public class GradleDiffUtil {
/**
* Analogues to {@link #buildLocalChanges} but targets intellij entity.
*
* @param gradleProject gradle project which structure is being compared to the intellij one
* @param module target intellij-local module that doesn't present at the gradle side
* @param currentChanges holder for the changes built during the current call
*/
public static void buildLocalChanges(@NotNull Module module, @NotNull Set<GradleProjectStructureChange> currentChanges) {
currentChanges.add(new GradleModulePresenceChange(null, module));
public static void buildLocalChanges(@NotNull Module module,
@NotNull Set<GradleProjectStructureChange> currentChanges)
{
currentChanges.add(new GradleModulePresenceChange(null, module.getName()));
// TODO den process module sub-entities here (content roots and dependencies).
}
@@ -85,18 +88,20 @@ public class GradleDiffUtil {
public static void buildLocalChanges(@NotNull LibraryOrderEntry libraryDependency,
@NotNull Set<GradleProjectStructureChange> currentChanges)
{
currentChanges.add(new GradleLibraryDependencyPresenceChange(null, libraryDependency));
final String libraryName = libraryDependency.getLibraryName();
if (libraryName != null) {
currentChanges.add(new GradleLibraryDependencyPresenceChange(null, libraryDependency));
}
}
/**
* Performs argument type-based dispatch and delegates to one of strongly typed <code>'buildLocalChanges()'</code> methods.
*
* @param gradleProject gradle project which structure is being compared to the intellij one
* @param entity target intellij-local entity that doesn't present at the gradle side
* @param currentChanges holder for the changes built during the current call
*/
public static void buildLocalChanges(@NotNull Object entity,
@NotNull Set<GradleProjectStructureChange> currentChanges)
{
public static void buildLocalChanges(@NotNull Object entity, @NotNull Set<GradleProjectStructureChange> currentChanges) {
if (entity instanceof GradleEntity) {
buildLocalChanges((GradleEntity)entity, currentChanges);
}
@@ -15,6 +15,7 @@
*/
package org.jetbrains.plugins.gradle.diff;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
@@ -22,13 +23,13 @@ import org.jetbrains.annotations.Nullable;
*
* @author Denis Zhdanov
* @since 11/17/11 12:43 PM
* @param <G> entity type at Gradle side
* @param <I> entity type at IntelliJ IDEA side
* @param <T> entity type
*/
public abstract class GradleEntityPresenceChange<G, I> extends GradleAbstractProjectStructureChange {
public abstract class GradleEntityPresenceChange<T> extends GradleAbstractProjectStructureChange {
private final G myGradleEntity;
private final I myIntellijEntity;
private final T myGradleEntity;
private final T myIntellijEntity;
private final String myEntityName;
/**
* Creates new <code>GradleEntityPresenceChange</code> project.
@@ -40,7 +41,11 @@ public abstract class GradleEntityPresenceChange<G, I> extends GradleAbstractPro
* @throws IllegalArgumentException if both of the given entities are defined or undefined. Expecting this constructor to be
* called with one <code>null</code> argument and one non-<code>null</code> argument
*/
public GradleEntityPresenceChange(@Nullable G gradleEntity, @Nullable I intellijEntity) throws IllegalArgumentException {
public GradleEntityPresenceChange(@NotNull String entityName,
@Nullable T gradleEntity,
@Nullable T intellijEntity)
throws IllegalArgumentException
{
if (!(gradleEntity == null ^ intellijEntity == null)) {
throw new IllegalArgumentException(String.format(
"Can't construct %s object. Reason: expected that only gradle or intellij entity is null, actual: gradle='%s'; intellij='%s'",
@@ -49,15 +54,16 @@ public abstract class GradleEntityPresenceChange<G, I> extends GradleAbstractPro
}
myGradleEntity = gradleEntity;
myIntellijEntity = intellijEntity;
myEntityName = entityName;
}
@Nullable
public G getGradleEntity() {
public T getGradleEntity() {
return myGradleEntity;
}
@Nullable
public I getIntellijEntity() {
public T getIntellijEntity() {
return myIntellijEntity;
}
@@ -83,6 +89,6 @@ public abstract class GradleEntityPresenceChange<G, I> extends GradleAbstractPro
@Override
public String toString() {
return String.format("entity presence change: gradle='%s', intellij='%s'", myGradleEntity, myIntellijEntity);
return String.format("%s presence change: gradle='%s', intellij='%s'", myEntityName, myGradleEntity, myIntellijEntity);
}
}
@@ -18,15 +18,16 @@ package org.jetbrains.plugins.gradle.diff;
import com.intellij.pom.java.LanguageLevel;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.plugins.gradle.util.GradleBundle;
/**
* @author Denis Zhdanov
* @since 11/15/11 8:05 PM
*/
public class GradleLanguageLevelChange extends GradleAbstractPropertyValueChange<LanguageLevel> {
public class GradleLanguageLevelChange extends GradleAbstractConflictingPropertyChange<LanguageLevel> {
public GradleLanguageLevelChange(@Nullable LanguageLevel gradleValue, @Nullable LanguageLevel intellijValue) {
super("project language level", gradleValue, intellijValue);
super(GradleBundle.message("gradle.sync.change.project.language.level.text"), gradleValue, intellijValue);
}
@Override
@@ -4,17 +4,21 @@ import com.intellij.openapi.roots.LibraryOrderEntry;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.plugins.gradle.model.GradleLibraryDependency;
import org.jetbrains.plugins.gradle.model.GradleLibraryDependencyId;
import org.jetbrains.plugins.gradle.util.GradleBundle;
import static org.jetbrains.plugins.gradle.model.GradleLibraryDependencyId.of;
/**
* @author Denis Zhdanov
* @since 1/24/12 9:48 AM
*/
public class GradleLibraryDependencyPresenceChange extends GradleEntityPresenceChange<GradleLibraryDependency, LibraryOrderEntry> {
public class GradleLibraryDependencyPresenceChange extends GradleEntityPresenceChange<GradleLibraryDependencyId> {
public GradleLibraryDependencyPresenceChange(@Nullable GradleLibraryDependency gradleEntity, @Nullable LibraryOrderEntry intellijEntity)
throws IllegalArgumentException
public GradleLibraryDependencyPresenceChange(@Nullable GradleLibraryDependency gradleDependency,
@Nullable LibraryOrderEntry intellijDependency) throws IllegalArgumentException
{
super(gradleEntity, intellijEntity);
super(GradleBundle.message("gradle.sync.change.entity.type.library.dependency"), of(gradleDependency), of(intellijDependency));
}
@Override
@@ -1,6 +1,7 @@
package org.jetbrains.plugins.gradle.diff;
import com.intellij.openapi.roots.LibraryOrderEntry;
import com.intellij.openapi.roots.libraries.Library;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.plugins.gradle.model.GradleLibraryDependency;
@@ -13,6 +14,12 @@ import java.util.Set;
public class GradleLibraryDependencyStructureChangesCalculator
implements GradleStructureChangesCalculator<GradleLibraryDependency, LibraryOrderEntry>
{
private final GradleLibraryStructureChangesCalculator myLibraryCalculator;
public GradleLibraryDependencyStructureChangesCalculator(@NotNull GradleLibraryStructureChangesCalculator libraryCalculator) {
myLibraryCalculator = libraryCalculator;
}
@Override
public void calculate(@NotNull GradleLibraryDependency gradleEntity,
@@ -20,7 +27,11 @@ public class GradleLibraryDependencyStructureChangesCalculator
@NotNull Set<GradleProjectStructureChange> knownChanges,
@NotNull Set<GradleProjectStructureChange> currentChanges)
{
// TODO den implement
final Library library = intellijEntity.getLibrary();
if (library == null) {
return;
}
myLibraryCalculator.calculate(gradleEntity.getTarget(), library, knownChanges, currentChanges);
}
@NotNull
@@ -33,7 +44,6 @@ public class GradleLibraryDependencyStructureChangesCalculator
@NotNull
@Override
public Object getGradleKey(@NotNull GradleLibraryDependency entity, @NotNull Set<GradleProjectStructureChange> knownChanges) {
// TODO den consider the known changes
return entity.getName();
return myLibraryCalculator.getGradleKey(entity.getTarget(), knownChanges);
}
}
@@ -0,0 +1,56 @@
package org.jetbrains.plugins.gradle.diff;
import com.intellij.openapi.roots.OrderRootType;
import com.intellij.openapi.roots.libraries.Library;
import com.intellij.openapi.vfs.VirtualFile;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.plugins.gradle.model.GradleLibrary;
import org.jetbrains.plugins.gradle.model.LibraryPathType;
import java.util.HashSet;
import java.util.Set;
/**
* @author Denis Zhdanov
* @since 2/3/12 11:17 AM
*/
public class GradleLibraryStructureChangesCalculator implements GradleStructureChangesCalculator<GradleLibrary, Library> {
private final PlatformFacade myPlatformFacade;
public GradleLibraryStructureChangesCalculator(@NotNull PlatformFacade platformFacade) {
myPlatformFacade = platformFacade;
}
@Override
public void calculate(@NotNull GradleLibrary gradleEntity,
@NotNull Library intellijEntity,
@NotNull Set<GradleProjectStructureChange> knownChanges,
@NotNull Set<GradleProjectStructureChange> currentChanges)
{
final Set<String> gradleBinaryPaths = new HashSet<String>(gradleEntity.getPaths(LibraryPathType.BINARY));
for (VirtualFile file : intellijEntity.getFiles(OrderRootType.CLASSES)) {
final String path = myPlatformFacade.getLocalFileSystemPath(file);
if (!gradleBinaryPaths.remove(path)) {
currentChanges.add(new GradleMismatchedLibraryPathChange(intellijEntity, null, path));
}
}
for (String binaryPath : gradleBinaryPaths) {
currentChanges.add(new GradleMismatchedLibraryPathChange(intellijEntity, binaryPath, null));
}
}
@NotNull
@Override
public Object getIntellijKey(@NotNull Library entity) {
return entity.getName();
}
@NotNull
@Override
public Object getGradleKey(@NotNull GradleLibrary entity, @NotNull Set<GradleProjectStructureChange> knownChanges) {
// TODO den consider the known changes
return entity.getName();
}
}
@@ -0,0 +1,52 @@
package org.jetbrains.plugins.gradle.diff;
import com.intellij.openapi.roots.libraries.Library;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.plugins.gradle.util.GradleBundle;
/**
* @author Denis Zhdanov
* @since 2/2/12 1:32 PM
*/
public class GradleMismatchedLibraryPathChange extends GradleAbstractConflictingPropertyChange<String> {
private final String myLibraryName;
public GradleMismatchedLibraryPathChange(@NotNull Library entity,
@Nullable String gradleValue,
@Nullable String intellijValue)
throws IllegalArgumentException
{
super(GradleBundle.message("gradle.sync.change.library.path", entity.getName()), gradleValue, intellijValue);
myLibraryName = entity.getName();
if (myLibraryName == null) {
throw new IllegalArgumentException(String.format("Can't create %s instance. Reason: given library has no name (%s)",
getClass().getName(), entity));
}
}
@NotNull
public String getLibraryName() {
return myLibraryName;
}
@Override
public void invite(@NotNull GradleProjectStructureChangeVisitor visitor) {
visitor.visit(this);
}
@Override
public int hashCode() {
return 31 * super.hashCode() + myLibraryName.hashCode();
}
@Override
public boolean equals(Object o) {
if (!super.equals(o)) {
return false;
}
GradleMismatchedLibraryPathChange that = (GradleMismatchedLibraryPathChange)o;
return myLibraryName.equals(that.myLibraryName);
}
}
@@ -15,19 +15,20 @@
*/
package org.jetbrains.plugins.gradle.diff;
import com.intellij.openapi.module.Module;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.plugins.gradle.model.GradleModule;
import org.jetbrains.plugins.gradle.util.GradleBundle;
/**
* @author Denis Zhdanov
* @since 11/17/11 12:50 PM
*/
public class GradleModulePresenceChange extends GradleEntityPresenceChange<GradleModule, Module> {
public class GradleModulePresenceChange extends GradleEntityPresenceChange<String> {
public GradleModulePresenceChange(@Nullable GradleModule gradleEntity, @Nullable Module intellijEntity) throws IllegalArgumentException {
super(gradleEntity, intellijEntity);
public GradleModulePresenceChange(@Nullable String gradleModuleName, @Nullable String intellijModuleName)
throws IllegalArgumentException
{
super(GradleBundle.message("gradle.sync.change.entity.type.module"), gradleModuleName, intellijModuleName);
}
@Override
@@ -0,0 +1,22 @@
package org.jetbrains.plugins.gradle.diff;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.plugins.gradle.util.GradleBundle;
/**
* Describes project name change.
*
* @author Denis Zhdanov
* @since 11/3/11 3:54 PM
*/
public class GradleProjectRenameChange extends GradleAbstractConflictingPropertyChange<String> {
public GradleProjectRenameChange(@NotNull String gradleName, @NotNull String intellijName) {
super(GradleBundle.message("gradle.sync.change.project.name.text"), gradleName, intellijName);
}
@Override
public void invite(@NotNull GradleProjectStructureChangeVisitor visitor) {
visitor.visit(this);
}
}
@@ -24,8 +24,9 @@ import org.jetbrains.annotations.NotNull;
* @since 11/16/11 8:48 PM
*/
public interface GradleProjectStructureChangeVisitor {
void visit(@NotNull GradleRenameChange change);
void visit(@NotNull GradleProjectStructureChange change);
void visit(@NotNull GradleProjectRenameChange change);
void visit(@NotNull GradleLanguageLevelChange change);
void visit(@NotNull GradleModulePresenceChange change);
void visit(@NotNull GradleLibraryDependencyPresenceChange change);
void visit(@NotNull GradleMismatchedLibraryPathChange change);
}
@@ -24,11 +24,11 @@ import org.jetbrains.annotations.NotNull;
public class GradleProjectStructureChangeVisitorAdapter implements GradleProjectStructureChangeVisitor {
@Override
public void visit(@NotNull GradleRenameChange change) {
public void visit(@NotNull GradleProjectRenameChange change) {
}
@Override
public void visit(@NotNull GradleProjectStructureChange change) {
public void visit(@NotNull GradleLanguageLevelChange change) {
}
@Override
@@ -38,4 +38,8 @@ public class GradleProjectStructureChangeVisitorAdapter implements GradleProject
@Override
public void visit(@NotNull GradleLibraryDependencyPresenceChange change) {
}
@Override
public void visit(@NotNull GradleMismatchedLibraryPathChange change) {
}
}
@@ -70,13 +70,13 @@ public class GradleProjectStructureChangesCalculator implements GradleStructureC
String gradleName = gradleProject.getName();
String intellijName = intellijProject.getName();
if (!gradleName.equals(intellijName)) {
currentChanges.add(new GradleRenameChange(GradleRenameChange.Entity.PROJECT, gradleName, intellijName));
currentChanges.add(new GradleProjectRenameChange(gradleName, intellijName));
}
}
private void checkLanguageLevel(@NotNull GradleProject gradleProject,
@NotNull Project intellijProject,
@NotNull Set<GradleProjectStructureChange> currentChanges)
@NotNull Project intellijProject,
@NotNull Set<GradleProjectStructureChange> currentChanges)
{
LanguageLevel gradleLevel = gradleProject.getLanguageLevel();
LanguageLevel intellijLevel = myStructureHelper.getLanguageLevel(intellijProject);
@@ -1,48 +0,0 @@
package org.jetbrains.plugins.gradle.diff;
import org.jetbrains.annotations.NotNull;
/**
* Describes particular entity name change.
*
* @author Denis Zhdanov
* @since 11/3/11 3:54 PM
*/
public class GradleRenameChange extends GradleAbstractPropertyValueChange<String> {
public enum Entity { PROJECT, MODULE, LIBRARY }
private final Entity myEntity;
public GradleRenameChange(@NotNull Entity entity, @NotNull String gradleName, @NotNull String intellijName) {
super(entity.toString().toLowerCase(), gradleName, intellijName);
myEntity = entity;
}
@NotNull
public Entity getEntity() {
return myEntity;
}
@Override
public int hashCode() {
return 31 * super.hashCode() + myEntity.hashCode();
}
@Override
public boolean equals(Object o) {
if (!super.equals(o)) {
return false;
}
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
GradleRenameChange that = (GradleRenameChange)o;
return myEntity.equals(that.myEntity);
}
@Override
public void invite(@NotNull GradleProjectStructureChangeVisitor visitor) {
visitor.visit(this);
}
}
@@ -3,6 +3,7 @@ package org.jetbrains.plugins.gradle.diff;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.roots.OrderEntry;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.pom.java.LanguageLevel;
import org.jetbrains.annotations.NotNull;
@@ -41,4 +42,16 @@ public interface PlatformFacade {
*/
@NotNull
Icon getProjectIcon();
/**
* Allows to derive from the given VFS file path that may be compared to the path used by the gradle api.
* <p/>
* Generally, this method is necessary for processing binary library paths - they point to jar files and VFS uses
* <code>'!'</code> marks in their paths internally.
*
* @param file target file
* @return given file's path that may be compared to the one used by the gradle api
*/
@NotNull
String getLocalFileSystemPath(@NotNull VirtualFile file);
}
@@ -1,6 +1,7 @@
package org.jetbrains.plugins.gradle.diff;
import com.intellij.openapi.application.ex.ApplicationInfoEx;
import com.intellij.openapi.fileTypes.FileTypes;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.module.ModuleManager;
import com.intellij.openapi.project.Project;
@@ -8,6 +9,8 @@ import com.intellij.openapi.roots.LanguageLevelProjectExtension;
import com.intellij.openapi.roots.ModuleRootManager;
import com.intellij.openapi.roots.OrderEntry;
import com.intellij.openapi.util.IconLoader;
import com.intellij.openapi.vfs.JarFileSystem;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.pom.java.LanguageLevel;
import org.jetbrains.annotations.NotNull;
@@ -19,7 +22,7 @@ import java.util.Collection;
* @author Denis Zhdanov
* @since 1/26/12 11:54 AM
*/
public class GradleProjectStructureHelperImpl implements PlatformFacade {
public class PlatformFacadeImpl implements PlatformFacade {
@NotNull
@Override
@@ -44,4 +47,16 @@ public class GradleProjectStructureHelperImpl implements PlatformFacade {
public Icon getProjectIcon() {
return IconLoader.getIcon(ApplicationInfoEx.getInstanceEx().getSmallIconUrl());
}
@NotNull
@Override
public String getLocalFileSystemPath(@NotNull VirtualFile file) {
if (file.getFileType() == FileTypes.ARCHIVE) {
final VirtualFile jar = JarFileSystem.getInstance().getVirtualFileForJar(file);
if (jar != null) {
return jar.getPath();
}
}
return file.getPath();
}
}
@@ -260,7 +260,7 @@ public class GradleAdjustImportSettingsStep extends AbstractImportFromGradleWiza
// We build tree node, its settings control and map them altogether. The only trick here is that nodes can reuse the same
// settings control (e.g. more than one node may have the same library as a dependency, so, library dependency node for
// every control will use the same settings control).
GradleProjectStructureNode result = new GradleProjectStructureNode(myFactory.buildDescriptor(entity));
GradleProjectStructureNode<T> result = new GradleProjectStructureNode<T>(myFactory.buildDescriptor(entity));
Pair<String, Collection<GradleProjectStructureNode>> pair = processed.get(entity);
if (pair == null) {
String cardName = String.valueOf(counter);
@@ -30,9 +30,9 @@ public class GradleProjectStructureFactory {
/** Shared instance of the current (stateless) class. */
public static final GradleProjectStructureFactory INSTANCE = new GradleProjectStructureFactory();
@SuppressWarnings("MethodMayBeStatic")
@SuppressWarnings({"MethodMayBeStatic", "unchecked"})
@NotNull
public <T extends GradleEntity> GradleProjectStructureNodeDescriptor buildDescriptor(@NotNull T entity) {
public <T extends GradleEntity> GradleProjectStructureNodeDescriptor<T> buildDescriptor(@NotNull T entity) {
final Ref<GradleProjectStructureNodeDescriptor<GradleEntity>> result = new Ref<GradleProjectStructureNodeDescriptor<GradleEntity>>();
entity.invite(new GradleEntityVisitor() {
@Override
@@ -70,7 +70,7 @@ public class GradleProjectStructureFactory {
visit(dependency.getTarget());
}
});
return result.get();
return (GradleProjectStructureNodeDescriptor<T>)result.get();
}
@SuppressWarnings("MethodMayBeStatic")
@@ -138,7 +138,7 @@ public class GradleProjectStructureFactory {
return;
}
for (GradleProjectStructureNode node : treeNodes) {
node.getDescriptor().setName(evt.getNewValue().toString());
node.getDescriptor().setText(evt.getNewValue().toString());
model.nodeChanged(node);
}
}
@@ -39,6 +39,10 @@ public class GradleLibrary extends AbstractNamedGradleEntity implements Named {
paths.add(GradleUtil.toCanonicalPath(path));
}
public void forgetAllPaths() {
myPaths.clear();
}
@Override
public void invite(@NotNull GradleEntityVisitor visitor) {
visitor.visit(this);
@@ -0,0 +1,78 @@
package org.jetbrains.plugins.gradle.model;
import com.intellij.openapi.roots.LibraryOrderEntry;
import com.intellij.openapi.util.text.StringUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* This class serves as an id for any library dependency. We can't use library object itself because its hashCode()/equals() may
* be changed over time (e.g. source root is added).
* <p/>
* Thread-safe.
*
* @author Denis Zhdanov
* @since 2/6/12 12:09 PM
*/
public class GradleLibraryDependencyId {
private final String myLibraryName;
private final String myModuleName;
public GradleLibraryDependencyId(@NotNull String libraryName, @NotNull String moduleName) {
myLibraryName = libraryName;
myModuleName = moduleName;
}
@Nullable
public static GradleLibraryDependencyId of(@Nullable GradleLibraryDependency dependency) {
return dependency == null ? null : new GradleLibraryDependencyId(dependency.getName(), dependency.getOwnerModule().getName());
}
@Nullable
public static GradleLibraryDependencyId of(@Nullable LibraryOrderEntry dependency) {
if (dependency == null) {
return null;
}
final String libraryName = dependency.getLibraryName();
if (libraryName == null || StringUtil.isEmpty(libraryName)) {
return null;
}
return new GradleLibraryDependencyId(libraryName, dependency.getOwnerModule().getName());
}
@NotNull
public String getLibraryName() {
return myLibraryName;
}
@NotNull
public String getModuleName() {
return myModuleName;
}
@Override
public int hashCode() {
int result = myLibraryName.hashCode();
result = 31 * result + myModuleName.hashCode();
return result;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
GradleLibraryDependencyId id = (GradleLibraryDependencyId)o;
if (!myLibraryName.equals(id.myLibraryName)) return false;
if (!myModuleName.equals(id.myModuleName)) return false;
return true;
}
@Override
public String toString() {
return String.format("library dependency: library='%s', module='%s'", myLibraryName, myModuleName);
}
}
@@ -3,6 +3,7 @@ package org.jetbrains.plugins.gradle.sync;
import com.intellij.openapi.components.AbstractProjectComponent;
import com.intellij.openapi.project.Project;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.plugins.gradle.diff.GradleProjectStructureChange;
import org.jetbrains.plugins.gradle.diff.GradleStructureChangesCalculator;
import org.jetbrains.plugins.gradle.model.GradleProject;
@@ -13,7 +14,7 @@ import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.atomic.AtomicReference;
/**
* // TODO den add doc
* Manages information about the changes between the gradle and intellij project structures.
* <p/>
* Thread-safe.
*
@@ -25,7 +26,9 @@ public class GradleProjectStructureChangesModel extends AbstractProjectComponent
private final Set<GradleProjectStructureChangeListener> myListeners = new CopyOnWriteArraySet<GradleProjectStructureChangeListener>();
private final AtomicReference<Set<GradleProjectStructureChange>> myChanges
= new AtomicReference<Set<GradleProjectStructureChange>>(new HashSet<GradleProjectStructureChange>());
private final AtomicReference<GradleProject> myGradleProject = new AtomicReference<GradleProject>();
private final GradleStructureChangesCalculator<GradleProject, Project> myChangesCalculator;
public GradleProjectStructureChangesModel(@NotNull Project project,
@@ -54,6 +57,7 @@ public class GradleProjectStructureChangesModel extends AbstractProjectComponent
* @param gradleProject gradle project to sync with
*/
public void update(@NotNull GradleProject gradleProject) {
myGradleProject.set(gradleProject);
Set<GradleProjectStructureChange> knownChanges = new HashSet<GradleProjectStructureChange>(myChanges.get());
Set<GradleProjectStructureChange> currentChanges = new HashSet<GradleProjectStructureChange>();
myChangesCalculator.calculate(gradleProject, myProject, knownChanges, currentChanges);
@@ -66,6 +70,14 @@ public class GradleProjectStructureChangesModel extends AbstractProjectComponent
}
}
/**
* @return last known gradle project state
*/
@Nullable
public GradleProject getGradleProject() {
return myGradleProject.get();
}
/**
* Registers given listener within the current model.
*
@@ -29,9 +29,10 @@ public class GradleProjectStructureChangesPanel extends GradleToolWindowPanel {
public GradleProjectStructureChangesPanel(@NotNull Project project,
@NotNull GradleProjectStructureChangesModel model,
@NotNull PlatformFacade projectStructureHelper)
@NotNull PlatformFacade platformFacade,
@NotNull GradleProjectStructureHelper projectStructureHelper)
{
super(project, projectStructureHelper, GradleConstants.TOOL_WINDOW_TOOLBAR_PLACE);
super(project, platformFacade, projectStructureHelper, GradleConstants.TOOL_WINDOW_TOOLBAR_PLACE);
model.addListener(new GradleProjectStructureChangeListener() {
@Override
public void onChanges(@NotNull final Collection<GradleProjectStructureChange> oldChanges,
@@ -41,7 +42,7 @@ public class GradleProjectStructureChangesPanel extends GradleToolWindowPanel {
@Override
public void run() {
myTreeModel.update(currentChanges);
myTreeModel.pruneObsoleteNodes(ContainerUtil.subtract(oldChanges, currentChanges));
myTreeModel.processObsoleteChanges(ContainerUtil.subtract(oldChanges, currentChanges));
}
});
}
@@ -50,7 +51,7 @@ public class GradleProjectStructureChangesPanel extends GradleToolWindowPanel {
private void init() {
myContent = new JPanel(new GridBagLayout());
myTreeModel = new GradleProjectStructureTreeModel(getProject(), getProjectStructureHelper());
myTreeModel = new GradleProjectStructureTreeModel(getProject(), getProjectFacade(), getProjectStructureHelper());
Tree tree = new Tree(myTreeModel);
applyInitialAppearance(tree, (DefaultMutableTreeNode)myTreeModel.getRoot());
@@ -0,0 +1,92 @@
package org.jetbrains.plugins.gradle.sync;
import com.intellij.openapi.components.AbstractProjectComponent;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.roots.LibraryOrderEntry;
import com.intellij.openapi.roots.OrderEntry;
import com.intellij.openapi.roots.RootPolicy;
import com.intellij.openapi.util.Ref;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.plugins.gradle.diff.PlatformFacade;
import org.jetbrains.plugins.gradle.model.*;
/**
* @author Denis Zhdanov
* @since 2/6/12 3:28 PM
*/
public class GradleProjectStructureHelper extends AbstractProjectComponent {
private final GradleProjectStructureChangesModel myModel;
private final PlatformFacade myFacade;
public GradleProjectStructureHelper(@NotNull Project project,
@NotNull GradleProjectStructureChangesModel model,
@NotNull PlatformFacade facade)
{
super(project);
myModel = model;
myFacade = facade;
}
/**
* Allows to answer if target library dependency is still available at the target project.
*
* @param id target library id
* @return <code>true</code> if target library dependency is still available at the target project;
* <code>false</code> otherwise
*/
public boolean isIntellijLibraryDependencyExist(@NotNull final GradleLibraryDependencyId id) {
for (Module module : myFacade.getModules(myProject)) {
if (!id.getModuleName().equals(module.getName())) {
continue;
}
RootPolicy<Boolean> visitor = new RootPolicy<Boolean>() {
@Override
public Boolean visitLibraryOrderEntry(LibraryOrderEntry libraryOrderEntry, Boolean value) {
return id.getLibraryName().equals(libraryOrderEntry.getLibraryName());
}
};
for (OrderEntry entry : myFacade.getOrderEntries(module)) {
if (entry.accept(visitor, false)) {
return true;
}
}
}
return false;
}
/**
* Allows to answer if target library dependency is still available at the target project.
*
* @param id target library id
* @return <code>true</code> if target library dependency is still available at the target project;
* <code>false</code> otherwise
*/
public boolean isGradleLibraryDependencyExist(@NotNull final GradleLibraryDependencyId id) {
final GradleProject project = myModel.getGradleProject();
if (project == null) {
return false;
}
final Ref<Boolean> matched = new Ref<Boolean>();
GradleEntityVisitor visitor = new GradleEntityVisitorAdapter() {
@Override
public void visit(@NotNull GradleLibraryDependency dependency) {
matched.set(id.getLibraryName().equals(dependency.getName()));
}
};
for (GradleModule module : project.getModules()) {
if (!id.getModuleName().equals(module.getName())) {
continue;
}
for (GradleDependency dependency : module.getDependencies()) {
dependency.invite(visitor);
if (matched.get()) {
return true;
}
}
return false;
}
return false;
}
}
@@ -1,26 +1,23 @@
package org.jetbrains.plugins.gradle.sync;
import com.intellij.openapi.editor.colors.TextAttributesKey;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.roots.LibraryOrderEntry;
import com.intellij.openapi.roots.OrderEntry;
import com.intellij.openapi.roots.RootPolicy;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.util.containers.hash.HashMap;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.plugins.gradle.config.GradleTextAttributes;
import org.jetbrains.plugins.gradle.diff.*;
import org.jetbrains.plugins.gradle.model.GradleLibraryDependency;
import org.jetbrains.plugins.gradle.model.Named;
import org.jetbrains.plugins.gradle.model.GradleLibraryDependencyId;
import org.jetbrains.plugins.gradle.ui.GradleIcons;
import org.jetbrains.plugins.gradle.ui.GradleProjectStructureNode;
import org.jetbrains.plugins.gradle.ui.GradleProjectStructureNodeDescriptor;
import org.jetbrains.plugins.gradle.util.GradleBundle;
import javax.swing.*;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.MutableTreeNode;
import javax.swing.tree.TreeNode;
import java.util.ArrayList;
import java.util.Collection;
@@ -37,7 +34,9 @@ import java.util.Map;
*/
public class GradleProjectStructureTreeModel extends DefaultTreeModel {
public static final GradleProjectStructureNodeDescriptor<Object> DEPENDENCIES_NODE_DESCRIPTOR = buildDescriptor(
public static final GradleProjectStructureNodeDescriptor<String> DEPENDENCIES_NODE_DESCRIPTOR
= new GradleProjectStructureNodeDescriptor<String>(
GradleBundle.message("gradle.project.structure.tree.node.dependencies"),
GradleBundle.message("gradle.project.structure.tree.node.dependencies"),
null
);
@@ -52,27 +51,33 @@ public class GradleProjectStructureTreeModel extends DefaultTreeModel {
* |_dependency2
* </pre>
*/
private final Map<String, DefaultMutableTreeNode> myModuleDependencies = new HashMap<String, DefaultMutableTreeNode>();
private final Map<String, DefaultMutableTreeNode> myModules = new HashMap<String, DefaultMutableTreeNode>();
private final Map<String, GradleProjectStructureNode<String>> myModuleDependencies
= new HashMap<String, GradleProjectStructureNode<String>>();
private final Map<String, GradleProjectStructureNode<String>> myModules
= new HashMap<String, GradleProjectStructureNode<String>>();
private final TreeNode[] myNodeHolder = new TreeNode[1];
private final int[] myIndexHolder = new int[1];
private final Project myProject;
private final PlatformFacade myPlatformFacade;
private final Project myProject;
private final PlatformFacade myPlatformFacade;
private final GradleProjectStructureHelper myProjectStructureHelper;
public GradleProjectStructureTreeModel(@NotNull Project project, @NotNull PlatformFacade platformFacade) {
public GradleProjectStructureTreeModel(@NotNull Project project,
@NotNull PlatformFacade platformFacade,
@NotNull GradleProjectStructureHelper projectStructureHelper) {
super(null);
myProject = project;
myPlatformFacade = platformFacade;
myProjectStructureHelper = projectStructureHelper;
rebuild();
}
public void rebuild() {
myModuleDependencies.clear();
myModules.clear();
DefaultMutableTreeNode root = new DefaultMutableTreeNode(buildDescriptor(getProject()));
GradleProjectStructureNode<Project> root = buildNode(getProject(), getProject().getName(), myPlatformFacade.getProjectIcon());
final Collection<Module> modules = myPlatformFacade.getModules(getProject());
RootPolicy<LibraryOrderEntry> policy = new RootPolicy<LibraryOrderEntry>() {
@Override
@@ -81,19 +86,21 @@ public class GradleProjectStructureTreeModel extends DefaultTreeModel {
}
};
for (Module module : modules) {
final DefaultMutableTreeNode moduleNode = new DefaultMutableTreeNode(buildDescriptor(module));
final GradleProjectStructureNode<String> moduleNode = buildNode(module.getName(), GradleIcons.MODULE_ICON);
myModules.put(module.getName(), moduleNode); // Assuming that module names are unique.
List<LibraryOrderEntry> libraryDependencies = new ArrayList<LibraryOrderEntry>();
for (OrderEntry orderEntry : myPlatformFacade.getOrderEntries(module)) {
final LibraryOrderEntry libraryDependency = orderEntry.accept(policy, null);
if (libraryDependency != null && !StringUtil.isEmpty(libraryDependency.getLibraryName())) {
libraryDependencies.add(libraryDependency);
}
libraryDependencies.add(libraryDependency);
}
if (!libraryDependencies.isEmpty()) {
DefaultMutableTreeNode dependenciesNode = getDependenciesNode(module.getName());
GradleProjectStructureNode<String> dependenciesNode = getDependenciesNode(module.getName());
for (LibraryOrderEntry dependency : libraryDependencies) {
dependenciesNode.add(new DefaultMutableTreeNode(buildDescriptor(dependency)));
GradleLibraryDependencyId id = GradleLibraryDependencyId.of(dependency);
if (id == null) {
continue;
}
dependenciesNode.add(buildNode(id, id.getLibraryName(), GradleIcons.LIB_ICON));
}
moduleNode.add(dependenciesNode);
}
@@ -108,40 +115,30 @@ public class GradleProjectStructureTreeModel extends DefaultTreeModel {
return myProject;
}
private static GradleProjectStructureNodeDescriptor<Object> buildDescriptor(@NotNull String name, @Nullable Icon icon) {
return new GradleProjectStructureNodeDescriptor<Object>(new Object(), name, icon);
private static <T> GradleProjectStructureNodeDescriptor<T> buildDescriptor(@NotNull T entity, @NotNull String name, @NotNull Icon icon) {
return new GradleProjectStructureNodeDescriptor<T>(entity, name, icon);
}
private static GradleProjectStructureNodeDescriptor<Named> buildDescriptor(@NotNull Named entity, @NotNull Icon icon) {
return new GradleProjectStructureNodeDescriptor<Named>(entity, entity.getName(), icon);
private static GradleProjectStructureNode<String> buildNode(@NotNull String name, @NotNull Icon icon) {
return buildNode(name, name, icon);
}
private GradleProjectStructureNodeDescriptor<Project> buildDescriptor(@NotNull Project project) {
return new GradleProjectStructureNodeDescriptor<Project>(project, project.getName(), myPlatformFacade.getProjectIcon());
private static <T> GradleProjectStructureNode<T> buildNode(@NotNull T entity, @NotNull String name, @NotNull Icon icon) {
return new GradleProjectStructureNode<T>(buildDescriptor(entity, name, icon));
}
private static GradleProjectStructureNodeDescriptor<Module> buildDescriptor(@NotNull Module module) {
return new GradleProjectStructureNodeDescriptor<Module>(module, module.getName(), GradleIcons.MODULE_ICON);
}
private static GradleProjectStructureNodeDescriptor<LibraryOrderEntry> buildDescriptor(@NotNull LibraryOrderEntry library) {
final String name = library.getLibraryName();
assert name != null;
return new GradleProjectStructureNodeDescriptor<LibraryOrderEntry>(library, name, GradleIcons.LIB_ICON);
}
private DefaultMutableTreeNode getDependenciesNode(@NotNull String moduleName) {
final DefaultMutableTreeNode cached = myModuleDependencies.get(moduleName);
private GradleProjectStructureNode<String> getDependenciesNode(@NotNull String moduleName) {
final GradleProjectStructureNode<String> cached = myModuleDependencies.get(moduleName);
if (cached != null) {
return cached;
}
DefaultMutableTreeNode moduleNode = myModules.get(moduleName);
GradleProjectStructureNode<String> moduleNode = myModules.get(moduleName);
if (moduleNode == null) {
moduleNode = new DefaultMutableTreeNode(buildDescriptor(moduleName, GradleIcons.MODULE_ICON));
moduleNode = buildNode(moduleName, GradleIcons.MODULE_ICON);
myModules.put(moduleName, moduleNode);
}
DefaultMutableTreeNode result = new DefaultMutableTreeNode(DEPENDENCIES_NODE_DESCRIPTOR);
GradleProjectStructureNode<String> result = new GradleProjectStructureNode<String>(DEPENDENCIES_NODE_DESCRIPTOR);
moduleNode.add(result);
myModuleDependencies.put(moduleName, result);
@@ -155,62 +152,40 @@ public class GradleProjectStructureTreeModel extends DefaultTreeModel {
*/
public void update(@NotNull Collection<GradleProjectStructureChange> changes) {
for (GradleProjectStructureChange change : changes) {
change.invite(new GradleProjectStructureChangeVisitor() {
change.invite(new GradleProjectStructureChangeVisitorAdapter() {
@Override
public void visit(@NotNull GradleRenameChange change) {
// TODO den implement
}
@Override
public void visit(@NotNull GradleProjectStructureChange change) {
// TODO den implement
}
@Override
public void visit(@NotNull GradleModulePresenceChange change) {
// TODO den implement
public void visit(@NotNull GradleMismatchedLibraryPathChange change) {
for (GradleProjectStructureNode<String> holder : myModuleDependencies.values()) {
for (GradleProjectStructureNode<GradleLibraryDependencyId> dependencyNode : holder.getChildren(GradleLibraryDependencyId.class)) {
final GradleLibraryDependencyId id = dependencyNode.getDescriptor().getElement();
if (change.getLibraryName().equals(id.getLibraryName())) {
dependencyNode.addConflictChange(change);
break;
}
}
}
}
@Override
public void visit(@NotNull GradleLibraryDependencyPresenceChange change) {
String moduleName;
GradleProjectStructureNodeDescriptor<?> descriptor;
final GradleLibraryDependency gradleEntity = change.getGradleEntity();
final LibraryOrderEntry intellijEntity = change.getIntellijEntity();
final Object missingEntity;
if (gradleEntity == null && intellijEntity == null) {
// Never expect to be here.
assert false;
GradleLibraryDependencyId id = change.getGradleEntity();
TextAttributesKey attributes = GradleTextAttributes.GRADLE_LOCAL_CHANGE;
if (id == null) {
id = change.getIntellijEntity();
attributes = GradleTextAttributes.INTELLIJ_LOCAL_CHANGE;
}
if (gradleEntity == null) {
// Particular library dependency is added at the intellij side.
moduleName = intellijEntity.getOwnerModule().getName();
if (intellijEntity.getLibraryName() == null) {
return;
}
descriptor = buildDescriptor(intellijEntity);
descriptor.setAttributes(GradleTextAttributes.INTELLIJ_LOCAL_CHANGE);
missingEntity = intellijEntity;
}
else {
// Particular library dependency is added at the gradle side.
moduleName = gradleEntity.getOwnerModule().getName();
descriptor = buildDescriptor(gradleEntity, GradleIcons.LIB_ICON);
descriptor.setAttributes(GradleTextAttributes.GRADLE_LOCAL_CHANGE);
missingEntity = gradleEntity;
}
final DefaultMutableTreeNode dependenciesNode = getDependenciesNode(moduleName);
for (int i = 0, max = dependenciesNode.getChildCount(); i < max; i++) {
final DefaultMutableTreeNode child = (DefaultMutableTreeNode)dependenciesNode.getChildAt(i);
GradleProjectStructureNodeDescriptor<?> d = (GradleProjectStructureNodeDescriptor<?>)child.getUserObject();
if (missingEntity.equals(d.getElement())) {
d.setAttributes(descriptor.getAttributes());
nodeStructureChanged(child);
assert id != null;
final GradleProjectStructureNode<String> dependenciesNode = getDependenciesNode(id.getModuleName());
for (GradleProjectStructureNode<GradleLibraryDependencyId> node : dependenciesNode.getChildren(GradleLibraryDependencyId.class)) {
GradleProjectStructureNodeDescriptor<GradleLibraryDependencyId> d = node.getDescriptor();
if (id.equals(d.getElement())) {
d.setAttributes(attributes);
nodeStructureChanged(node);
return;
}
}
DefaultMutableTreeNode newNode = new DefaultMutableTreeNode(descriptor);
GradleProjectStructureNode<GradleLibraryDependencyId> newNode = buildNode(id, id.getLibraryName(), GradleIcons.LIB_ICON);
newNode.getDescriptor().setAttributes(attributes);
dependenciesNode.add(newNode);
nodeStructureChanged(dependenciesNode);
}
@@ -219,7 +194,7 @@ public class GradleProjectStructureTreeModel extends DefaultTreeModel {
}
/**
* Asks current model to remove all obsolete nodes for the considering that the given changes are obsolete.
* Asks current model to process given changes assuming that they are obsolete.
* <p/>
* Example:
* <pre>
@@ -231,63 +206,72 @@ public class GradleProjectStructureTreeModel extends DefaultTreeModel {
* </ol>
* </pre>
*/
public void pruneObsoleteNodes(Collection<GradleProjectStructureChange> changes) {
public void processObsoleteChanges(Collection<GradleProjectStructureChange> changes) {
for (GradleProjectStructureChange change : changes) {
change.invite(new GradleProjectStructureChangeVisitor() {
change.invite(new GradleProjectStructureChangeVisitorAdapter() {
@Override
public void visit(@NotNull GradleRenameChange change) {
// TODO den implement
}
@Override
public void visit(@NotNull GradleProjectStructureChange change) {
// TODO den implement
}
@Override
public void visit(@NotNull GradleModulePresenceChange change) {
// TODO den implement
public void visit(@NotNull GradleMismatchedLibraryPathChange change) {
for (GradleProjectStructureNode<String> holder : myModuleDependencies.values()) {
for (GradleProjectStructureNode<GradleLibraryDependencyId> node : holder.getChildren(GradleLibraryDependencyId.class)) {
final GradleLibraryDependencyId id = node.getDescriptor().getElement();
if (id.getLibraryName().equals(change.getLibraryName())) {
node.removeConflictChange(change);
nodeChanged(node);
break;
}
}
}
}
@Override
public void visit(@NotNull GradleLibraryDependencyPresenceChange change) {
// We need to remove the corresponding node then.
String moduleName;
Object library;
final GradleLibraryDependency gradleEntity = change.getGradleEntity();
final LibraryOrderEntry intellijEntity = change.getIntellijEntity();
assert gradleEntity != null || intellijEntity != null;
if (gradleEntity == null) {
moduleName = intellijEntity.getOwnerModule().getName();
library = intellijEntity;
GradleLibraryDependencyId id = change.getGradleEntity();
boolean removeNode;
if (id == null) {
id = change.getIntellijEntity();
assert id != null;
removeNode = !myProjectStructureHelper.isIntellijLibraryDependencyExist(id);
}
else {
moduleName = gradleEntity.getOwnerModule().getName();
library = gradleEntity;
removeNode = !myProjectStructureHelper.isGradleLibraryDependencyExist(id);
}
final DefaultMutableTreeNode holder = myModuleDependencies.get(moduleName);
final GradleProjectStructureNode<String> holder = myModuleDependencies.get(id.getModuleName());
if (holder == null) {
return;
}
for (DefaultMutableTreeNode node = holder.getFirstLeaf(); node != null; node = node.getNextSibling()) {
GradleProjectStructureNodeDescriptor<?> descriptor = (GradleProjectStructureNodeDescriptor<?>)node.getUserObject();
if (descriptor.getElement().equals(library)) {
removeNode(node);
return;
// There are two possible cases why 'local library dependency' change is obsolete:
// 1. Corresponding dependency has been added at the counterparty;
// 2. The 'local dependency' has been removed;
// We should distinguish between those situations because we need to mark the node as 'synced' at one case and
// completely removed at another one.
for (GradleProjectStructureNode<GradleLibraryDependencyId> node : holder.getChildren(GradleLibraryDependencyId.class)) {
GradleProjectStructureNodeDescriptor<GradleLibraryDependencyId> descriptor = node.getDescriptor();
if (!id.equals(descriptor.getElement())) {
continue;
}
if (removeNode) {
removeNode(node);
}
else {
descriptor.setAttributes(GradleTextAttributes.GRADLE_NO_CHANGE);
}
return;
}
}
});
}
}
private void removeNode(@NotNull TreeNode node) {
final MutableTreeNode parent = (MutableTreeNode)node.getParent();
private void removeNode(@NotNull GradleProjectStructureNode node) {
final GradleProjectStructureNode parent = node.getParent();
if (parent == null) {
return;
}
int i = parent.getIndex(node);
if (i <= 0) {
if (i < 0) {
assert false : node;
return;
}
@@ -1,24 +1,140 @@
package org.jetbrains.plugins.gradle.ui;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.plugins.gradle.config.GradleTextAttributes;
import org.jetbrains.plugins.gradle.diff.GradleProjectStructureChange;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.MutableTreeNode;
import java.util.*;
/**
* @author Denis Zhdanov
* @since 8/23/11 3:50 PM
* @param <T> type of the target entity {@link GradleProjectStructureNodeDescriptor#getElement() associated} with the current node
*/
public class GradleProjectStructureNode extends DefaultMutableTreeNode {
private final GradleProjectStructureNodeDescriptor myDescriptor;
public class GradleProjectStructureNode<T> extends DefaultMutableTreeNode implements Iterable<GradleProjectStructureNode<?>> {
public GradleProjectStructureNode(@NotNull GradleProjectStructureNodeDescriptor descriptor) {
private final Set<GradleProjectStructureChange> myConflictChanges = new HashSet<GradleProjectStructureChange>();
private final GradleProjectStructureNodeDescriptor<T> myDescriptor;
public GradleProjectStructureNode(@NotNull GradleProjectStructureNodeDescriptor<T> descriptor) {
super(descriptor);
myDescriptor = descriptor;
}
@NotNull
public GradleProjectStructureNodeDescriptor getDescriptor() {
public GradleProjectStructureNodeDescriptor<T> getDescriptor() {
return myDescriptor;
}
@Override
public GradleProjectStructureNode<?> getChildAt(int index) {
return (GradleProjectStructureNode)super.getChildAt(index);
}
@Override
public GradleProjectStructureNode<?> getParent() {
return (GradleProjectStructureNode)super.getParent();
}
@Override
public void add(MutableTreeNode newChild) {
GradleProjectStructureNode<?> child = (GradleProjectStructureNode)newChild;
final String newText = child.getDescriptor().getText();
for (int i = 0; i < getChildCount(); i++) {
GradleProjectStructureNode<?> node = getChildAt(i);
if (newText.compareTo(node.getDescriptor().getText()) < 0) {
insert(newChild, i);
return;
}
}
super.add(newChild);
}
/**
* Registers given change within the given node assuming that it is
* {@link GradleTextAttributes#GRADLE_CHANGE_CONFLICT 'conflict change'}. We need to track number of such changes per-node because
* of the following possible situation:
* <pre>
* <ol>
* <li>There are two 'conflict changes' for particular node;</li>
* <li>
* One of those changes is resolved but the node still should be marked as 'conflict' because there is still one conflict change;
* </li>
* <li>The second conflict change is removed. The node should be marked as 'no change' now;</li>
* </ol>
* </pre>
*
* @param change conflict change to register for the current node
*/
public void addConflictChange(@NotNull GradleProjectStructureChange change) {
myConflictChanges.add(change);
myDescriptor.setAttributes(GradleTextAttributes.GRADLE_CHANGE_CONFLICT);
}
/**
* Performs reverse operation to {@link #addConflictChange(GradleProjectStructureChange)}.
*
* @param change conflict change to de-register from the current node
*/
public void removeConflictChange(@NotNull GradleProjectStructureChange change) {
myConflictChanges.remove(change);
if (myConflictChanges.isEmpty()) {
myDescriptor.setAttributes(GradleTextAttributes.GRADLE_NO_CHANGE);
}
}
/**
* Allows to query current node for all children that are associated with the entity of the given type.
*
* @param clazz target entity type
* @param <C> target entity type
* @return all children nodes that are associated with the entity of the given type if any;
* empty collection otherwise
*/
@SuppressWarnings("unchecked")
@NotNull
public <C> Collection<GradleProjectStructureNode<C>> getChildren(@NotNull Class<C> clazz) {
List<GradleProjectStructureNode<C>> result = null;
for (int i = 0; i < getChildCount(); i++) {
final GradleProjectStructureNode<?> child = getChildAt(i);
final Object element = child.getDescriptor().getElement();
if (!clazz.isInstance(element)) {
continue;
}
if (result == null) {
result = new ArrayList<GradleProjectStructureNode<C>>();
}
result.add((GradleProjectStructureNode<C>)child);
}
if (result == null) {
result = Collections.emptyList();
}
return result;
}
@NotNull
@Override
public Iterator<GradleProjectStructureNode<?>> iterator() {
return new Iterator<GradleProjectStructureNode<?>>() {
private int i;
@Override
public boolean hasNext() {
return i < getChildCount();
}
@Override
public GradleProjectStructureNode<?> next() {
return getChildAt(i++);
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
};
}
}
@@ -40,12 +40,18 @@ public class GradleProjectStructureNodeDescriptor<T> extends PresentableNodeDesc
presentation.setClosedIcon(myClosedIcon);
}
@NotNull
@Override
public T getElement() {
return myData;
}
public void setName(@NotNull String name) {
@NotNull
public String getText() {
return myName;
}
public void setText(@NotNull String name) {
myName = name;
}
@@ -42,7 +42,9 @@ public class GradleProjectStructureChangesModelTest {
container.registerComponentImplementation(GradleStructureChangesCalculator, GradleProjectStructureChangesCalculator)
container.registerComponentImplementation(GradleModuleStructureChangesCalculator)
container.registerComponentImplementation(GradleLibraryDependencyStructureChangesCalculator)
container.registerComponentImplementation(GradleLibraryStructureChangesCalculator)
container.registerComponentImplementation(GradleProjectStructureTreeModel)
container.registerComponentImplementation(GradleProjectStructureHelper)
changesModel = container.getComponentInstance(GradleProjectStructureChangesModel) as GradleProjectStructureChangesModel
}
@@ -56,6 +58,7 @@ public class GradleProjectStructureChangesModelTest {
dependencies {
lib(name: "lib1")
lib(name: "lib2")
lib(name: "lib3")
} } },
intellij {
@@ -69,46 +72,157 @@ public class GradleProjectStructureChangesModelTest {
checkChanges {
presence {
lib(gradle: gradle.modules.dependencies.flatten().findAll { it.name == "lib2" })
lib(gradle: gradle.modules.dependencies.flatten().findAll { it.name == "lib3" })
} }
checkTree {
project {
module("xxx") {
module() {
dependencies {
lib1()
lib2('gradle')
lib3('gradle')
} } } }
// Define changed project state.
// Add the same library at intellij side. Expecting to have the only change now.
intellij {
module {
dependencies {
lib(name: "lib1")
lib(name: "lib2")
} } }
changesModel.update(gradle.project)
checkChanges {
presence {
lib(gradle: gradle.modules.dependencies.flatten().findAll { it.name == "lib3" })
} }
checkTree {
project {
module() {
dependencies {
lib1()
lib2()
lib3('gradle')
} } } }
// Remove the 'gradle local' dependency.
gradle {
module {
dependencies {
lib(name: "lib1")
lib(name: "lib2")
} } }
// Apply the changed project state and check if it's correctly parsed.
changesModel.update(gradle.project)
checkChanges { } // no changes.
assertEquals([].toSet(), changesModel.changes)
checkTree {
project {
module {
dependencies {
lib1()
lib2()
} } } }
}
@Test
public void libraryDependenciesWithDifferentPaths() {
// Let the model has two differences in a library setup initially.
init(
gradle {
module {
dependencies {
lib(name: "lib1")
lib(name: "lib2", bin: ['1', '2'])
} } },
intellij {
module {
dependencies {
lib(name: "lib1")
lib(name: "lib2", bin: ['2', '3'])
}}}
)
checkChanges {
libraryConflict(entity: intellij.libraries['lib2']) {
binaryPath(gradle: '1', intellij: null)
binaryPath(gradle: null, intellij: '3')
} }
checkTree {
project {
module {
dependencies {
lib1()
lib2('conflict')
} } } }
// Remove one difference from the library setup and check that the corresponding node is still marked as conflicted
gradle {
module {
dependencies {
lib(name: "lib1")
lib(name: "lib2", bin: ['2'])
} } }
intellij {
module {
dependencies {
lib(name: "lib1")
lib(name: "lib2", bin: ['2', '3'])
}}}
changesModel.update(gradle.project)
checkChanges {
libraryConflict(entity: intellij.dependencies.values().flatten().find {it.library.name == 'lib2' }.library) {
binaryPath(gradle: null, intellij: '3')
} }
checkTree {
project {
module {
dependencies {
lib1()
lib2('conflict')
} } } }
// Match the remaining change and check that the corresponding node is not marked as conflicted anymore.
gradle {
module {
dependencies {
lib(name: "lib1")
lib(name: "lib2", bin: ['2', '3'])
} } }
intellij {
module {
dependencies {
lib(name: "lib1")
lib(name: "lib2", bin: ['2', '3'])
}}}
changesModel.update(gradle.project)
checkChanges { } // No changes
checkTree {
project {
module {
dependencies {
lib1()
lib2()
} } } }
}
@SuppressWarnings("GroovyAssignabilityCheck")
private def init(gradleProjectInit, intellijProjectInit) {
treeModel = container.getComponentInstance(GradleProjectStructureTreeModel) as GradleProjectStructureTreeModel
changesModel.addListener({ old, current ->
treeModel.update(current)
treeModel.pruneObsoleteNodes(ContainerUtil.subtract(old, current));
treeModel.processObsoleteChanges(ContainerUtil.subtract(old, current));
} as GradleProjectStructureChangeListener)
changesModel.update(gradle.project)
}
private def checkChanges(c) {
c.delegate = changes
assertEquals(c(), changesModel.changes)
def expected = c()
if (!expected) {
expected = [].toSet()
}
assertEquals(expected, changesModel.changes)
}
private def checkTree(c) {
@@ -1,19 +1,26 @@
package org.jetbrains.plugins.gradle.testutil
import com.intellij.pom.java.LanguageLevel;
import com.intellij.pom.java.LanguageLevel
/**
* @author Denis Zhdanov
* @since 1/25/12 4:06 PM
*/
public abstract class AbstractProjectBuilder extends BuilderSupport {
public abstract class AbstractProjectBuilder extends BuilderSupport {
private static final def SAME_TOKEN = "same"
public static final def SAME_TOKEN = "same"
private static int COUNTER
def project
def modules = []
def libraries = [:].withDefault { createLibrary(it.name?: same, it.paths?: [:]) }
/** Holds (library name; library) pairs for the active configuration. */
def libraries = [:]
/**
* Holds (library name; library) pairs for the whole test. I.e. there is a possible case that we define particular configuration
* initially and the adjust it. We need to use the same library instance then in order to pass hashCode()/equals() checks then.
* This map works as a test-wide storage.
*/
def librariesCache = [:]
def dependencies = [:].withDefault {[]}
@Override
@@ -43,7 +50,7 @@ public abstract class AbstractProjectBuilder extends BuilderSupport {
case "module": def module = createModule(attributes.name?: same); modules << module; return module
case "lib":
def module = getCurrent()
def dep = createLibraryDependency(module, libraries[attributes])
def dep = createLibraryDependency(module, getLibrary(attributes))
dependencies[module] << dep
return dep
}
@@ -57,10 +64,27 @@ public abstract class AbstractProjectBuilder extends BuilderSupport {
protected abstract def createProject(String name, LanguageLevel languageLevel)
protected abstract def createModule(String name)
protected abstract def createLibrary(String name, Map paths)
protected abstract def applyLibraryPaths(library, Map paths)
protected abstract def createLibraryDependency(module, library)
protected String getUnique() { "./${COUNTER++}" }
protected String getSame() { SAME_TOKEN }
private def getLibrary(Map attributes) {
def name = attributes.name?: same
def result = libraries[name]
if (result) return result
result = librariesCache[name]
if (result) {
applyLibraryPaths(result, attributes)
}
else {
result = createLibrary(name, attributes.withDefault { /* empty paths*/ [] })
librariesCache[name] = result
}
libraries[name] = result
result
}
private def reset() {
[modules, dependencies, libraries]*.clear()
@@ -2,6 +2,9 @@ package org.jetbrains.plugins.gradle.testutil;
import org.jetbrains.plugins.gradle.diff.GradleLibraryDependencyPresenceChange
import org.jetbrains.plugins.gradle.diff.GradleMismatchedLibraryPathChange
import com.intellij.openapi.roots.libraries.Library
import org.jetbrains.plugins.gradle.util.GradleUtil
/**
* @author Denis Zhdanov
@@ -17,10 +20,7 @@ public class ChangeBuilder extends BuilderSupport {
@Override
protected Object createNode(Object name) {
if (current == null) {
changes = []
}
changes
createNode(name, [:])
}
@Override
@@ -28,13 +28,31 @@ public class ChangeBuilder extends BuilderSupport {
@Override
protected Object createNode(Object name, Map attributes) {
if (current == null) {
changes = []
}
switch (name) {
case "presence": return changes
case "lib":
changes.addAll attributes.gradle.collect { new GradleLibraryDependencyPresenceChange(it, null)}
changes.addAll attributes.intellij.collect { new GradleLibraryDependencyPresenceChange(null, it)}
return
return changes
case "libraryConflict":
def library = attributes.entity
if (!library) {
throw new IllegalArgumentException("No entity is defined for the library conflict change. Known attributes: $attributes")
}
if (attributes.gradle) {
return register(new GradleMismatchedLibraryPathChange(library, attributes.gradle, attributes.intellij))
}
return library
case "binaryPath":
// Assuming that we're processing library binary path conflict here
register(new GradleMismatchedLibraryPathChange(
current as Library, toCanonicalPath(attributes.gradle), toCanonicalPath(attributes.intellij)
))
}
changes
}
@Override
@@ -44,4 +62,13 @@ public class ChangeBuilder extends BuilderSupport {
protected Object postNodeCompletion(Object parent, Object node) {
parent == null ? changes.toSet() : node
}
protected def register(change) {
changes << change
change
}
private def toCanonicalPath(String path) {
path ? GradleUtil.toCanonicalPath(path) : path
}
}
@@ -31,9 +31,7 @@ class GradleProjectBuilder extends AbstractProjectBuilder {
@Override
protected createLibrary(String name, Map paths) {
def result = new GradleLibrary(name)
(paths.bin?: [same]).each { result.addPath(LibraryPathType.BINARY, it) }
(paths.src?: [same]).each { result.addPath(LibraryPathType.SOURCE, it) }
(paths.doc?: [same]).each { result.addPath(LibraryPathType.DOC, it) }
applyLibraryPaths(result, paths)
result
}
@@ -43,4 +41,12 @@ class GradleProjectBuilder extends AbstractProjectBuilder {
module.addDependency(result)
result
}
@Override
protected applyLibraryPaths(library, Map paths) {
library.forgetAllPaths()
['bin': LibraryPathType.BINARY, 'src': LibraryPathType.SOURCE, 'doc': LibraryPathType.DOC].each {
key, type -> paths[key]?.each { library.addPath(type, it) }
}
}
}
@@ -9,19 +9,29 @@ import com.intellij.openapi.roots.LibraryOrderEntry
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.IconLoader
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.openapi.roots.OrderRootType
import org.jetbrains.plugins.gradle.util.GradleUtil
/**
* @author Denis Zhdanov
* @since 1/25/12 3:09 PM
*/
class IntellijProjectBuilder extends AbstractProjectBuilder {
static def LIBRARY_ENTRY_TYPES = [(OrderRootType.CLASSES) : "bin"]
static VirtualFile[] DUMMY_VIRTUAL_FILE_ARRAY = new VirtualFile[0]
def projectStub = [:]
def project = projectStub as Project
def platformFacade = [
getModules: { modules },
getOrderEntries: { dependencies[it] },
getProjectIcon: { IconLoader.getIcon("/nodes/ideaProject.png") }
getProjectIcon: { IconLoader.getIcon("/nodes/ideaProject.png") },
getLocalFileSystemPath: { it.path }
]
/** (library name - (library root type - paths)). */
def libraryPaths = [:].withDefault { [:] }
@Override
protected createProject(String name, LanguageLevel languageLevel) {
@@ -37,7 +47,17 @@ class IntellijProjectBuilder extends AbstractProjectBuilder {
@Override
protected createLibrary(String name, Map paths) {
[ getName: { name }, getPresentableName: { name } ] as Library
libraryPaths[name] = paths
[
getName: { name },
getPresentableName: { name },
getFiles: {
type -> (libraryPaths[name])[LIBRARY_ENTRY_TYPES[type]].findAll { it }.collect {
String path = it
[getPath: { GradleUtil.toCanonicalPath(path) }] as VirtualFile
}.toArray(DUMMY_VIRTUAL_FILE_ARRAY)
}
] as Library
}
@Override
@@ -46,6 +66,13 @@ class IntellijProjectBuilder extends AbstractProjectBuilder {
def result = stub as LibraryOrderEntry
stub.accept = { policy, defaultValue -> policy.visitLibraryOrderEntry(result, defaultValue) }
stub.getLibraryName = { library.name }
stub.getLibrary = { library }
stub.getOwnerModule = { module }
result
}
@Override
protected applyLibraryPaths(library, Map paths) {
libraryPaths[library.name] = paths
}
}
@@ -35,6 +35,9 @@ class ProjectStructureChecker {
check it as Node, actual.getChildAt(childIndex++) as DefaultMutableTreeNode
}
for (it in expected.children().findAll { it instanceof Node}) {
if (childIndex >= actual.childCount) {
fail "Expected node is not matched: $expected"
}
check it as Node, actual.getChildAt(childIndex++) as DefaultMutableTreeNode
}
if (childIndex < actual.childCount) {
@@ -43,7 +46,7 @@ class ProjectStructureChecker {
}
private void checkName(Node expected, GradleProjectStructureNodeDescriptor actual) {
if (expected.name() == actual.toString()) {
if (AbstractProjectBuilder.SAME_TOKEN == actual.toString() || expected.name() == actual.toString()) {
return
}
def clazz = BUILT_IN[expected.name()]