/*
- * Copyright 2000-2009 JetBrains s.r.o.
+ * Copyright 2000-2012 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
public void clear() {
map.clear();
}
+
+ @Override
+ public String toString() {
+ return map.keySet().toString();
+ }
}
<applicationService serviceImplementation="org.jetbrains.plugins.gradle.remote.GradleApiFacadeManager"/>
<applicationService serviceImplementation="org.jetbrains.plugins.gradle.util.GradleLibraryManager"/>
- <applicationService serviceImplementation="org.jetbrains.plugins.gradle.diff.GradleProjectStructureChangesCalculator"/>
+ <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.GradleLibraryDependencyStructureChangesCalculator"/>
+ <applicationService serviceInterface="org.jetbrains.plugins.gradle.diff.GradleProjectStructureHelper"
+ serviceImplementation="org.jetbrains.plugins.gradle.diff.GradleProjectStructureHelperImpl"/>
<applicationService serviceInterface="org.jetbrains.plugins.gradle.notification.GradleProgressNotificationManager"
serviceImplementation="org.jetbrains.plugins.gradle.notification.GradleProgressNotificationManagerImpl"/>
* Example: particular module has been added at the gradle side. We want to mark that module, its content root(s), dependencies etc
* as gradle-local changes.
*
- * @param entity target gradle-local entity
- * @return collection of gradle-local changes for the given entity and its interested sub-entities
+ * @param entity target gradle-local entity
+ * @param currentChanges holder for the changes built during the current call
*/
- public static Set<GradleProjectStructureChange> buildLocalChanges(@NotNull GradleEntity entity) {
- final Set<GradleProjectStructureChange> result = new HashSet<GradleProjectStructureChange>();
+ public static void buildLocalChanges(@NotNull GradleEntity entity, @NotNull final Set<GradleProjectStructureChange> currentChanges) {
entity.invite(new GradleEntityVisitor() {
@Override
public void visit(@NotNull GradleProject project) {
@Override
public void visit(@NotNull GradleModule module) {
- result.add(new GradleModulePresenceChange(module, null));
+ currentChanges.add(new GradleModulePresenceChange(module, null));
for (GradleDependency dependency : module.getDependencies()) {
dependency.invite(this);
}
@Override
public void visit(@NotNull GradleLibraryDependency dependency) {
- result.add(new GradleLibraryDependencyPresenceChange(dependency, null));
+ currentChanges.add(new GradleLibraryDependencyPresenceChange(dependency, null));
}
});
- return result;
}
/**
* Analogues to {@link #buildLocalChanges} but targets intellij entity.
*
- * @param module target intellij-local module that doesn't present at the gradle side
- * @return collection of intellij-local changes for the given entity and its interested sub-entities
+ * @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 Set<? extends GradleProjectStructureChange> buildLocalChanges(@NotNull Module module) {
- Set<GradleProjectStructureChange> result = new HashSet<GradleProjectStructureChange>();
- result.add(new GradleModulePresenceChange(null, module));
+ public static void buildLocalChanges(@NotNull Module module, @NotNull Set<GradleProjectStructureChange> currentChanges) {
+ currentChanges.add(new GradleModulePresenceChange(null, module));
// TODO den process module sub-entities here (content roots and dependencies).
- return result;
}
/**
* Analogues to {@link #buildLocalChanges} but targets intellij entity.
*
* @param libraryDependency target intellij-local library dependency that doesn't present at the gradle side
- * @return collection of intellij-local changes for the given entity and its interested sub-entities
+ * @param currentChanges holder for the changes built during the current call
*/
- public static Set<? extends GradleProjectStructureChange> buildLocalChanges(@NotNull LibraryOrderEntry libraryDependency) {
- return Collections.singleton(new GradleLibraryDependencyPresenceChange(null, libraryDependency));
+ public static void buildLocalChanges(@NotNull LibraryOrderEntry libraryDependency,
+ @NotNull Set<GradleProjectStructureChange> currentChanges)
+ {
+ currentChanges.add(new GradleLibraryDependencyPresenceChange(null, libraryDependency));
}
/**
* Performs argument type-based dispatch and delegates to one of strongly typed <code>'buildLocalChanges()'</code> methods.
*
- * @param entity target intellij-local entity that doesn't present at the gradle side
- * @return collection of intellij-local changes for the given entity and its interested sub-entities
+ * @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
*/
- @NotNull
- public static Set<? extends GradleProjectStructureChange> buildLocalChanges(@NotNull Object entity) {
+ public static void buildLocalChanges(@NotNull Object entity,
+ @NotNull Set<GradleProjectStructureChange> currentChanges)
+ {
if (entity instanceof GradleEntity) {
- return buildLocalChanges((GradleEntity)entity);
+ buildLocalChanges((GradleEntity)entity, currentChanges);
}
else if (entity instanceof Module) {
- return buildLocalChanges((Module)entity);
+ buildLocalChanges((Module)entity, currentChanges);
}
else if (entity instanceof LibraryOrderEntry) {
- return buildLocalChanges((LibraryOrderEntry)entity);
- }
- else {
- return Collections.emptySet();
- }
- }
-
- /**
- * Concatenates given entities into the single collection and returns it.
- * <p/>
- * The main idea behind this method is that most of the time we don't expect changes at all, hence, corresponding changes calculators
- * can use {@link Collections#emptySet()}. However, if some sub-nodes do have changes, attempt
- * to {@link Collection#addAll(Collection) merge} them within the empty set mentioned above would cause an exception.
- * <p/>
- * That's why we provide dedicated method for creating new collection as a merge result.
- *
- * @param collections collections to merge
- * @return merge result
- */
- @NotNull
- public static Set<GradleProjectStructureChange> concatenate(Collection<? extends GradleProjectStructureChange>... collections) {
- Set<GradleProjectStructureChange> result = null;
- for (Collection<? extends GradleProjectStructureChange> collection : collections) {
- if (result == null) {
- result = new HashSet<GradleProjectStructureChange>();
- }
- result.addAll(collection);
+ buildLocalChanges((LibraryOrderEntry)entity, currentChanges);
}
- return result == null ? Collections.<GradleProjectStructureChange>emptySet() : result;
}
/**
* @param gradleEntities entities available at the gradle side
* @param intellijEntities entities available at the intellij side
* @param knownChanges collection that contains known changes about the entities
+ * @param currentChanges holder for the changes discovered during the current call
* @param <I> target intellij entity type
* @param <G> target gradle entity type
- * @return set of changes between the given entity collections
*/
- @NotNull
- public static <I, G extends GradleEntity> Set<GradleProjectStructureChange> calculate(
+ public static <I, G extends GradleEntity> void calculate(
@NotNull GradleStructureChangesCalculator<G, I> calculator,
@NotNull Iterable<? extends G> gradleEntities,
@NotNull Iterable<? extends I> intellijEntities,
- @NotNull Set<GradleProjectStructureChange> knownChanges)
+ @NotNull Set<GradleProjectStructureChange> knownChanges,
+ @NotNull Set<GradleProjectStructureChange> currentChanges)
{
- Set<GradleProjectStructureChange> result = Collections.emptySet();
Map<Object, I> intellijEntitiesByKeys = new HashMap<Object, I>();
for (I entity : intellijEntities) {
- final I previous = intellijEntitiesByKeys.put(calculator.getIntellijKey(entity, knownChanges), entity);
+ final I previous = intellijEntitiesByKeys.put(calculator.getIntellijKey(entity), entity);
assert previous == null;
}
for (G gradleEntity: gradleEntities) {
I intellijEntity = intellijEntitiesByKeys.remove(calculator.getGradleKey(gradleEntity, knownChanges));
- Set<GradleProjectStructureChange> changesToMerge;
if (intellijEntity == null) {
- changesToMerge = buildLocalChanges(gradleEntity);
+ buildLocalChanges(gradleEntity, currentChanges);
}
else {
- changesToMerge = calculator.calculate(gradleEntity, intellijEntity, knownChanges);
+ calculator.calculate(gradleEntity, intellijEntity, knownChanges, currentChanges);
}
- result = concatenate(result, changesToMerge);
}
for (I entity : intellijEntitiesByKeys.values()) {
- result = concatenate(result, buildLocalChanges(entity));
+ buildLocalChanges(entity, currentChanges);
}
-
- return result;
}
}
import org.jetbrains.annotations.NotNull;
import org.jetbrains.plugins.gradle.model.GradleLibraryDependency;
-import java.util.Collections;
import java.util.Set;
/**
public class GradleLibraryDependencyStructureChangesCalculator
implements GradleStructureChangesCalculator<GradleLibraryDependency, LibraryOrderEntry>
{
- @NotNull
+
@Override
- public Set<GradleProjectStructureChange> calculate(@NotNull GradleLibraryDependency gradleEntity,
- @NotNull LibraryOrderEntry intellijEntity,
- @NotNull Set<GradleProjectStructureChange> knownChanges)
+ public void calculate(@NotNull GradleLibraryDependency gradleEntity,
+ @NotNull LibraryOrderEntry intellijEntity,
+ @NotNull Set<GradleProjectStructureChange> knownChanges,
+ @NotNull Set<GradleProjectStructureChange> currentChanges)
{
// TODO den implement
- return Collections.emptySet();
}
@NotNull
@Override
- public Object getIntellijKey(@NotNull LibraryOrderEntry entity, @NotNull Set<GradleProjectStructureChange> knownChanges) {
- // TODO den consider the known changes
+ public Object getIntellijKey(@NotNull LibraryOrderEntry entity) {
final String result = entity.getLibraryName();
return result == null ? "" : result;
}
*/
public class GradleModuleStructureChangesCalculator implements GradleStructureChangesCalculator<GradleModule, Module> {
- private final GradleLibraryDependencyStructureChangesCalculator myLibraryDependencyCalculator
- = new GradleLibraryDependencyStructureChangesCalculator();
-
- @NotNull
+ private final GradleLibraryDependencyStructureChangesCalculator myLibraryDependencyCalculator;
+ private final GradleProjectStructureHelper myStructureHelper;
+
+ public GradleModuleStructureChangesCalculator(@NotNull GradleLibraryDependencyStructureChangesCalculator libraryDependencyCalculator,
+ @NotNull GradleProjectStructureHelper structureHelper)
+ {
+ myLibraryDependencyCalculator = libraryDependencyCalculator;
+ myStructureHelper = structureHelper;
+ }
+
@Override
- public Set<GradleProjectStructureChange> calculate(@NotNull GradleModule gradleEntity,
- @NotNull Module intellijEntity,
- @NotNull Set<GradleProjectStructureChange> knownChanges)
+ public void calculate(@NotNull GradleModule gradleEntity,
+ @NotNull Module intellijEntity,
+ @NotNull Set<GradleProjectStructureChange> knownChanges,
+ @NotNull Set<GradleProjectStructureChange> currentChanges)
{
//TODO den process module-local settings
- //TODO den process content roots
- return checkDependencies(gradleEntity, intellijEntity, knownChanges);
+ //TODO den process content roots
+ checkDependencies(gradleEntity, intellijEntity, knownChanges, currentChanges);
}
@NotNull
@Override
- public Object getIntellijKey(@NotNull Module entity, @NotNull Set<GradleProjectStructureChange> knownChanges) {
- // TODO den consider the known changes
+ public Object getIntellijKey(@NotNull Module entity) {
return entity.getName();
}
return entity.getName();
}
- private Set<GradleProjectStructureChange> checkDependencies(@NotNull GradleModule gradleModule,
- @NotNull Module intellijModule,
- @NotNull Set<GradleProjectStructureChange> knownChanges)
+ private void checkDependencies(@NotNull GradleModule gradleModule,
+ @NotNull Module intellijModule,
+ @NotNull Set<GradleProjectStructureChange> knownChanges,
+ @NotNull Set<GradleProjectStructureChange> currentChanges)
{
// Prepare intellij part.
final List<ModuleOrderEntry> intellijModuleDependencies = new ArrayList<ModuleOrderEntry>();
return libraryOrderEntry;
}
};
- final ModuleRootManager moduleRootManager = ModuleRootManager.getInstance(intellijModule);
- for (OrderEntry orderEntry : moduleRootManager.getOrderEntries()) {
+ for (OrderEntry orderEntry : myStructureHelper.getOrderEntries(intellijModule)) {
orderEntry.accept(policy, null);
}
-
+
// Prepare gradle part.
final List<GradleModuleDependency> gradleModuleDependencies = new ArrayList<GradleModuleDependency>();
final List<GradleLibraryDependency> gradleLibraryDependencies = new ArrayList<GradleLibraryDependency>();
for (GradleDependency dependency : gradleModule.getDependencies()) {
dependency.invite(visitor);
}
-
+
// Calculate changes.
// TODO den process module dependencies here as well.
- final Set<GradleProjectStructureChange> libraryChanges
- = GradleDiffUtil.calculate(myLibraryDependencyCalculator, gradleLibraryDependencies, intellijLibraryDependencies, knownChanges);
- return libraryChanges;
-
+ GradleDiffUtil.calculate(myLibraryDependencyCalculator, gradleLibraryDependencies, intellijLibraryDependencies,
+ knownChanges, currentChanges);
}
}
package org.jetbrains.plugins.gradle.diff;
import com.intellij.openapi.module.Module;
-import com.intellij.openapi.module.ModuleManager;
import com.intellij.openapi.project.Project;
-import com.intellij.openapi.roots.LanguageLevelProjectExtension;
import com.intellij.pom.java.LanguageLevel;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.plugins.gradle.model.GradleModule;
import org.jetbrains.plugins.gradle.model.GradleProject;
-import java.util.Collections;
-import java.util.List;
+import java.util.Collection;
import java.util.Set;
-import static java.util.Arrays.asList;
-import static org.jetbrains.plugins.gradle.diff.GradleDiffUtil.concatenate;
-
/**
* Encapsulates functionality of calculating changes between Gradle and IntelliJ IDEA project hierarchies.
* <p/>
*/
public class GradleProjectStructureChangesCalculator implements GradleStructureChangesCalculator<GradleProject, Project> {
- private final GradleModuleStructureChangesCalculator myModuleChangesCalculator = new GradleModuleStructureChangesCalculator();
+ private final GradleModuleStructureChangesCalculator myModuleChangesCalculator;
+ private final GradleProjectStructureHelper myStructureHelper;
+
+ public GradleProjectStructureChangesCalculator(@NotNull GradleModuleStructureChangesCalculator moduleCalculator,
+ @NotNull GradleProjectStructureHelper structureHelper) {
+ myModuleChangesCalculator = moduleCalculator;
+ myStructureHelper = structureHelper;
+ }
- @NotNull
@Override
- public Set<GradleProjectStructureChange> calculate(@NotNull GradleProject gradleEntity,
- @NotNull Project intellijEntity,
- @NotNull Set<GradleProjectStructureChange> knownChanges)
+ public void calculate(@NotNull GradleProject gradleEntity,
+ @NotNull Project intellijEntity,
+ @NotNull Set<GradleProjectStructureChange> knownChanges,
+ @NotNull Set<GradleProjectStructureChange> currentChanges)
{
- final Set<GradleProjectStructureChange> result = calculateProjectChanges(gradleEntity, intellijEntity, knownChanges);
+ calculateProjectChanges(gradleEntity, intellijEntity, currentChanges);
final Set<? extends GradleModule> gradleSubEntities = gradleEntity.getModules();
- final List<Module> intellijSubEntities = asList(ModuleManager.getInstance(intellijEntity).getModules());
- return concatenate(result, GradleDiffUtil.calculate(myModuleChangesCalculator, gradleSubEntities, intellijSubEntities, knownChanges));
+ final Collection<Module> intellijSubEntities = myStructureHelper.getModules(intellijEntity);
+ GradleDiffUtil.calculate(myModuleChangesCalculator, gradleSubEntities, intellijSubEntities, knownChanges, currentChanges);
}
@NotNull
@Override
- public Object getIntellijKey(@NotNull Project entity, @NotNull Set<GradleProjectStructureChange> knownChanges) {
- // TODO den consider the known changes
+ public Object getIntellijKey(@NotNull Project entity) {
return entity.getName();
}
return entity.getName();
}
- @NotNull
- private static Set<GradleProjectStructureChange> calculateProjectChanges(@NotNull GradleProject gradleProject,
- @NotNull Project intellijProject,
- @NotNull Set<GradleProjectStructureChange> knownChanges)
+ private void calculateProjectChanges(@NotNull GradleProject gradleProject,
+ @NotNull Project intellijProject,
+ @NotNull Set<GradleProjectStructureChange> currentChanges)
{
- final Set<GradleProjectStructureChange> nameChanges = checkName(gradleProject, intellijProject, knownChanges);
- final Set<GradleProjectStructureChange> levelChanges = checkLanguageLevel(gradleProject, intellijProject, knownChanges);
- return concatenate(nameChanges, levelChanges);
+ checkName(gradleProject, intellijProject, currentChanges);
+ checkLanguageLevel(gradleProject, intellijProject, currentChanges);
}
- @NotNull
- private static Set<GradleProjectStructureChange> checkName(@NotNull GradleProject gradleProject,
- @NotNull Project intellijProject,
- @NotNull Set<GradleProjectStructureChange> knownChanges)
+ private static void checkName(@NotNull GradleProject gradleProject,
+ @NotNull Project intellijProject,
+ @NotNull Set<GradleProjectStructureChange> currentChanges)
{
String gradleName = gradleProject.getName();
String intellijName = intellijProject.getName();
- if (gradleName.equals(intellijName)) {
- return Collections.emptySet();
+ if (!gradleName.equals(intellijName)) {
+ currentChanges.add(new GradleRenameChange(GradleRenameChange.Entity.PROJECT, gradleName, intellijName));
}
- final GradleRenameChange change = new GradleRenameChange(GradleRenameChange.Entity.PROJECT, gradleName, intellijName);
- return knownChanges.contains(change) ? Collections.<GradleProjectStructureChange>emptySet()
- : Collections.<GradleProjectStructureChange>singleton(change);
}
- @NotNull
- private static Set<GradleProjectStructureChange> checkLanguageLevel(@NotNull GradleProject gradleProject,
- @NotNull Project intellijProject,
- @NotNull Set<GradleProjectStructureChange> knownChanges)
+ private void checkLanguageLevel(@NotNull GradleProject gradleProject,
+ @NotNull Project intellijProject,
+ @NotNull Set<GradleProjectStructureChange> currentChanges)
{
LanguageLevel gradleLevel = gradleProject.getLanguageLevel();
- LanguageLevel intellijLevel = LanguageLevelProjectExtension.getInstance(intellijProject).getLanguageLevel();
- if (gradleLevel == intellijLevel) {
- return Collections.emptySet();
+ LanguageLevel intellijLevel = myStructureHelper.getLanguageLevel(intellijProject);
+ if (gradleLevel != intellijLevel) {
+ currentChanges.add(new GradleLanguageLevelChange(gradleLevel, intellijLevel));
}
- final GradleLanguageLevelChange change = new GradleLanguageLevelChange(gradleLevel, intellijLevel);
- return knownChanges.contains(change) ? Collections.<GradleProjectStructureChange>emptySet()
- : Collections.<GradleProjectStructureChange>singleton(change);
}
}
--- /dev/null
+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.pom.java.LanguageLevel;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.Collection;
+
+/**
+ * IntelliJ code provides a lot of statical bindings to the interested pieces of data. For example we need to execute code
+ * like below to get list of modules for the target project:
+ * <pre>
+ * ModuleManager.getInstance(project).getModules()
+ * </pre>
+ * That means that it's not possible to test target classes in isolation if corresponding infrastructure is not set up.
+ * However, we don't want to set it up if we execute a simple standalone test.
+ * <p/>
+ * This interface is intended to encapsulate access to the underlying project infrastructure.
+ * <p/>
+ * Implementations of this interface are expected to be thread-safe.
+ *
+ * @author Denis Zhdanov
+ * @since 1/26/12 11:32 AM
+ */
+public interface GradleProjectStructureHelper {
+
+ @NotNull
+ LanguageLevel getLanguageLevel(@NotNull Project project);
+
+ @NotNull
+ Collection<Module> getModules(@NotNull Project project);
+
+ @NotNull
+ Collection<OrderEntry> getOrderEntries(@NotNull Module module);
+}
--- /dev/null
+package org.jetbrains.plugins.gradle.diff;
+
+import com.intellij.openapi.module.Module;
+import com.intellij.openapi.module.ModuleManager;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.roots.LanguageLevelProjectExtension;
+import com.intellij.openapi.roots.ModuleRootManager;
+import com.intellij.openapi.roots.OrderEntry;
+import com.intellij.pom.java.LanguageLevel;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.Arrays;
+import java.util.Collection;
+
+/**
+ * @author Denis Zhdanov
+ * @since 1/26/12 11:54 AM
+ */
+public class GradleProjectStructureHelperImpl implements GradleProjectStructureHelper {
+
+ @NotNull
+ @Override
+ public LanguageLevel getLanguageLevel(@NotNull Project project) {
+ return LanguageLevelProjectExtension.getInstance(project).getLanguageLevel();
+ }
+
+ @NotNull
+ @Override
+ public Collection<Module> getModules(@NotNull Project project) {
+ return Arrays.asList(ModuleManager.getInstance(project).getModules());
+ }
+
+ @NotNull
+ @Override
+ public Collection<OrderEntry> getOrderEntries(@NotNull Module module) {
+ return Arrays.asList(ModuleRootManager.getInstance(module).getOrderEntries());
+ }
+}
*/
public interface GradleStructureChangesCalculator<G extends GradleEntity, I> {
- // TODO den add doc
- @NotNull
- Set<GradleProjectStructureChange> calculate(@NotNull G gradleEntity,
- @NotNull I intellijEntity,
- @NotNull Set<GradleProjectStructureChange> knownChanges);
+ /**
+ * Calculates changes between the given entities.
+ *
+ * @param gradleEntity target gradle entity
+ * @param intellijEntity target intellij entity
+ * @param knownChanges changes between the gradle and intellij project structure that has been known up until now
+ * @param currentChanges holder for the changes between the given entities discovered by the current call. Note that
+ * it must contain the change objects that have been known (contained at the <code>'knownChanges'</code>)
+ * but are still in place
+ */
+ void calculate(@NotNull G gradleEntity,
+ @NotNull I intellijEntity,
+ @NotNull Set<GradleProjectStructureChange> knownChanges,
+ @NotNull Set<GradleProjectStructureChange> currentChanges);
- // TODO den add doc
+ /**
+ * There are three possible situations when we compare a set of gradle entities with a set of intellij entities:
+ * <pre>
+ * <ul>
+ * <li>particular entity presents only at the gradle side;</li>
+ * <li>particular entity presents only at the intellij side;</li>
+ * <li>particular gradle entity is matched to particular intellij entity (they may have difference in their settings though);</li>
+ * </ul>
+ * </pre>
+ * <p/>
+ * The general idea is to map evey item at the given sets of gradle and intellij entities to particular key (both gradle and
+ * intellij keys are expected to belong to the same class) and then compare them. Matched keys shows that corresponding
+ * entities should be {@link #calculate(GradleEntity, Object, Set) compared to each other}; non-matched indicate that corresponding
+ * entities are gradle- or intellij-local.
+ * <p/>
+ * This method allows to match intellij entity to the target key.
+ *
+ * @param entity intellij entity to match
+ * @return key for the given entity
+ * @see #getGradleKey(GradleEntity, Set)
+ */
@NotNull
- Object getIntellijKey(@NotNull I entity, @NotNull Set<GradleProjectStructureChange> knownChanges);
-
- // TODO den add doc
+ Object getIntellijKey(@NotNull I entity);
+
+ /**
+ * Serves the same purpose as {@link #getIntellijKey(Object)} but targets gradle entities.
+ * <p/>
+ * There is a possible case that two corresponding gradle and intellij entities differ from each other by the setting that
+ * affects the resulting key (e.g. we may use module name as a key for 'module' entities and intellij module name differs from
+ * the name of the corresponding gradle module). We need to match only in one direction then (e.g. consider a situation when
+ * particular module is named differently at gradle and intellij. We shouldn't consider that change during both
+ * {@code intellij-entity -> key} and {@code gradle-entity -> key} mappings because that would produce two different keys).
+ * So, we take into consideration the known changes only during {@code gradle-entity -> key} processing.
+ *
+ * @param entity target gradle entity that should be mapped to a key
+ * @param knownChanges known changes between the gradle and intellij structures
+ * @return key for the given entity
+ * @see #getIntellijKey(Object)
+ */
@NotNull
Object getGradleKey(@NotNull G entity, @NotNull Set<GradleProjectStructureChange> knownChanges);
}
/**
* Notifies current listener on the newly discovered changes between the gradle and intellij project models.
*
- * @param changes newly discovered changes between the gradle and intellij project models.
+ * @param oldChanges changes between the gradle and intellij project models that had been known prior to the current update
+ * @param currentChanges the most up-to-date changes between the gradle and intellij project models
*/
- void onChanges(@NotNull Collection<GradleProjectStructureChange> changes);
+ void onChanges(@NotNull Collection<GradleProjectStructureChange> oldChanges,
+ @NotNull Collection<GradleProjectStructureChange> currentChanges);
}
import com.intellij.openapi.components.AbstractProjectComponent;
import com.intellij.openapi.project.Project;
-import com.intellij.util.containers.ConcurrentHashSet;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.plugins.gradle.diff.GradleProjectStructureChange;
-import org.jetbrains.plugins.gradle.diff.GradleProjectStructureChangesCalculator;
+import org.jetbrains.plugins.gradle.diff.GradleStructureChangesCalculator;
import org.jetbrains.plugins.gradle.model.GradleProject;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;
+import java.util.concurrent.atomic.AtomicReference;
/**
* // TODO den add doc
public class GradleProjectStructureChangesModel extends AbstractProjectComponent {
private final Set<GradleProjectStructureChangeListener> myListeners = new CopyOnWriteArraySet<GradleProjectStructureChangeListener>();
- private final Set<GradleProjectStructureChange> myChanges = new ConcurrentHashSet<GradleProjectStructureChange>();
+ private final AtomicReference<Set<GradleProjectStructureChange>> myChanges
+ = new AtomicReference<Set<GradleProjectStructureChange>>(new HashSet<GradleProjectStructureChange>());
- private final GradleProjectStructureChangesCalculator myChangesCalculator;
+ private final GradleStructureChangesCalculator<GradleProject, Project> myChangesCalculator;
- public GradleProjectStructureChangesModel(@NotNull Project project, @NotNull GradleProjectStructureChangesCalculator changesCalculator) {
+ public GradleProjectStructureChangesModel(@NotNull Project project,
+ @NotNull GradleStructureChangesCalculator<GradleProject, Project> changesCalculator)
+ {
super(project);
myChangesCalculator = changesCalculator;
}
* </li>
* <li>{@link #addListener(GradleProjectStructureChangeListener) Registered listeners} are notified if any new change is detected;</li>
* </ol>
+ * <p/>
+ * <b>Note:</b> it's very important that the listeners are notified <b>after</b> the actual state change, i.e. {@link #getChanges()}
+ * during the update returns up-to-date data.
*
* @param gradleProject gradle project to sync with
*/
public void update(@NotNull GradleProject gradleProject) {
- Set<GradleProjectStructureChange> knownChanges = new HashSet<GradleProjectStructureChange>(myChanges);
- final Set<GradleProjectStructureChange> newChanges = myChangesCalculator.calculate(gradleProject, myProject, knownChanges);
- myChanges.addAll(newChanges);
+ Set<GradleProjectStructureChange> knownChanges = new HashSet<GradleProjectStructureChange>(myChanges.get());
+ Set<GradleProjectStructureChange> currentChanges = new HashSet<GradleProjectStructureChange>();
+ myChangesCalculator.calculate(gradleProject, myProject, knownChanges, currentChanges);
+ if (currentChanges.equals(knownChanges)) {
+ return;
+ }
+ myChanges.set(currentChanges);
for (GradleProjectStructureChangeListener listener : myListeners) {
- listener.onChanges(newChanges);
+ listener.onChanges(knownChanges, currentChanges);
}
}
*/
@NotNull
public Set<GradleProjectStructureChange> getChanges() {
- return myChanges;
+ return myChanges.get();
}
}
import com.intellij.openapi.roots.OrderEntry;
import com.intellij.openapi.roots.RootPolicy;
import com.intellij.ui.treeStructure.Tree;
+import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.hash.HashMap;
import com.intellij.util.ui.UIUtil;
import org.jetbrains.annotations.NotNull;
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.awt.*;
import java.util.ArrayList;
import java.util.Collection;
*/
private final Map<String, DefaultMutableTreeNode> myModuleDependencies = new HashMap<String, DefaultMutableTreeNode>();
private final Map<String, DefaultMutableTreeNode> myModules = new HashMap<String, DefaultMutableTreeNode>();
+
+ private final TreeNode[] myNodeHolder = new TreeNode[1];
+ private final int[] myIndexHolder = new int[1];
private final GradleProjectStructureChangesModel myChangesModel;
myChangesModel = model;
myChangesModel.addListener(new GradleProjectStructureChangeListener() {
@Override
- public void onChanges(@NotNull final Collection<GradleProjectStructureChange> changes) {
+ public void onChanges(@NotNull final Collection<GradleProjectStructureChange> oldChanges,
+ @NotNull final Collection<GradleProjectStructureChange> currentChanges)
+ {
UIUtil.invokeLaterIfNeeded(new Runnable() {
@Override
public void run() {
- updateTree(changes);
+ updateTree(currentChanges);
+ processObsoleteChanges(ContainerUtil.subtract(oldChanges, currentChanges));
+
}
});
}
constraints.fill = GridBagConstraints.BOTH;
constraints.weightx = constraints.weighty = 1;
myContent.add(tree, constraints);
+ myContent.setBackground(tree.getBackground());
return treeModel;
}
});
}
}
+
+ /**
+ * Updates the tree state considering that the given changes are obsolete.
+ * <p/>
+ * Example:
+ * <pre>
+ * <ol>
+ * <li>There is a particular intellij-local library (change from the gradle project structure);</li>
+ * <li>Corresponding node is shown at the current UI;</li>
+ * <li>The library is removed, i.e. corresponding change has become obsolete;</li>
+ * <li>This method is notified within the obsolete change and is expected to remove the corresponding node;</li>
+ * </ol>
+ * </pre>
+ */
+ private void processObsoleteChanges(Collection<GradleProjectStructureChange> changes) {
+ for (GradleProjectStructureChange change : changes) {
+ change.invite(new GradleProjectStructureChangeVisitor() {
+ @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
+ }
+
+ @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;
+ }
+ else {
+ moduleName = gradleEntity.getOwnerModule().getName();
+ library = gradleEntity;
+ }
+ final DefaultMutableTreeNode holder = myModuleDependencies.get(moduleName);
+ 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;
+ }
+ }
+ }
+ });
+ }
+ }
+
+ private void removeNode(@NotNull TreeNode node) {
+ final MutableTreeNode parent = (MutableTreeNode)node.getParent();
+ if (parent == null) {
+ return;
+ }
+ int i = parent.getIndex(node);
+ if (i <= 0) {
+ assert false : node;
+ return;
+ }
+ parent.remove(i);
+ myIndexHolder[0] = i;
+ myNodeHolder[0] = node;
+ myTreeModel.nodesWereRemoved(parent, myIndexHolder, myNodeHolder);
+ }
}
return;
}
final GradleProjectStructureChangesModel model = myIntellijProject.getComponent(GradleProjectStructureChangesModel.class);
- model.update(project);
+ if (model != null) {
+ // This task may be called during the 'import from gradle' processing, hence, no project-level IoC is up.
+ // Model update is necessary for the correct tool window project structure diff showing but we don't have
+ // gradle tool window on this stage.
+ model.update(project);
+ }
}
@Nullable
--- /dev/null
+package org.jetbrains.plugins.gradle.sync;
+
+
+import com.intellij.openapi.project.Project
+import org.jetbrains.plugins.gradle.testutil.ChangeBuilder
+import org.jetbrains.plugins.gradle.testutil.GradleProjectBuilder
+import org.jetbrains.plugins.gradle.testutil.IntellijProjectBuilder
+import org.junit.Before
+import org.junit.Test
+import org.picocontainer.defaults.DefaultPicoContainer
+import org.jetbrains.plugins.gradle.diff.*
+import static org.junit.Assert.assertEquals
+
+/**
+ * @author Denis Zhdanov
+ * @since 01/25/2012
+ */
+public class GradleProjectStructureChangesModelTest {
+
+ private GradleProjectStructureChangesModel myModel;
+ def gradle;
+ def intellij;
+ def changes;
+
+ @Before
+ public void setUp() {
+ gradle = new GradleProjectBuilder()
+ intellij = new IntellijProjectBuilder()
+ changes = new ChangeBuilder()
+ def container = new DefaultPicoContainer()
+ container.registerComponentInstance(Project, intellij.project)
+ container.registerComponentInstance(GradleProjectStructureHelper, intellij.projectStructureHelper as GradleProjectStructureHelper)
+ container.registerComponentImplementation(GradleProjectStructureChangesModel)
+ container.registerComponentImplementation(GradleStructureChangesCalculator, GradleProjectStructureChangesCalculator)
+ container.registerComponentImplementation(GradleModuleStructureChangesCalculator)
+ container.registerComponentImplementation(GradleLibraryDependencyStructureChangesCalculator)
+
+ myModel = container.getComponentInstance(GradleProjectStructureChangesModel.class) as GradleProjectStructureChangesModel
+ }
+
+ @Test
+ public void mergeGradleLocalToIntellij() {
+ gradle {
+ module {
+ dependencies {
+ lib(name: "lib1")
+ lib(name: "lib2")
+ } } }
+
+ intellij {
+ module {
+ dependencies {
+ lib(name: "lib1")
+ } } }
+
+ myModel.update(gradle.project)
+ checkChanges {
+ presence {
+ lib(gradle: gradle.modules.dependencies.flatten().findAll { it.name == "lib2" })
+ }
+ }
+
+ gradle {
+ module {
+ dependencies {
+ lib(name: "lib1")
+ } } }
+ myModel.update(gradle.project)
+ assertEquals([].toSet(), myModel.changes)
+ }
+
+ private def checkChanges(c) {
+ c.delegate = changes
+ assertEquals(c(), myModel.changes)
+ }
+}
--- /dev/null
+package org.jetbrains.plugins.gradle.testutil
+
+import com.intellij.pom.java.LanguageLevel;
+
+/**
+ * @author Denis Zhdanov
+ * @since 1/25/12 4:06 PM
+ */
+public abstract class AbstractProjectBuilder extends BuilderSupport {
+
+ private static final def SAME_TOKEN = "same"
+ private static int COUNTER
+
+ def project
+ def modules = []
+ def libraries = [:].withDefault { createLibrary(it.name?: same, it.paths?: [:]) }
+ def dependencies = [:].withDefault {[]}
+
+ @Override
+ protected void setParent(Object parent, Object child) {
+ }
+
+ @Override
+ protected Object createNode(Object name) {
+ switch (name) {
+ case "call": return createNode("project", [name: same])
+ case "module": return createNode(name, [name: same])
+ case "dependencies": return getCurrent() // Assuming that 'current' is a module object
+ }
+ }
+
+ @Override
+ protected Object createNode(Object name, Object value) {
+ return null
+ }
+
+ @Override
+ protected Object createNode(Object name, Map attributes) {
+ switch (name) {
+ case "project":
+ reset()
+ return project = createProject(attributes.name?: same, attributes.langLevel?: LanguageLevel.JDK_1_6)
+ case "module": def module = createModule(attributes.name?: same); modules << module; return module
+ case "lib":
+ def module = getCurrent()
+ def dep = createLibraryDependency(module, libraries[attributes])
+ dependencies[module] << dep
+ return dep
+ }
+ }
+
+ @Override
+ protected Object createNode(Object name, Map attributes, Object value) {
+ return null
+ }
+
+ 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 createLibraryDependency(module, library)
+
+ protected String getUnique() { "./${COUNTER++}" }
+ protected String getSame() { SAME_TOKEN }
+
+ private def reset() {
+ [modules, dependencies, libraries]*.clear()
+ }
+}
--- /dev/null
+package org.jetbrains.plugins.gradle.testutil;
+
+
+import org.jetbrains.plugins.gradle.diff.GradleLibraryDependencyPresenceChange
+
+/**
+ * @author Denis Zhdanov
+ * @since 1/26/12 3:25 PM
+ */
+public class ChangeBuilder extends BuilderSupport {
+
+ def changes = []
+
+ @Override
+ protected void setParent(Object parent, Object child) {
+ }
+
+ @Override
+ protected Object createNode(Object name) {
+ if (current == null) {
+ changes = []
+ }
+ changes
+ }
+
+ @Override
+ protected Object createNode(Object name, Object value) { changes }
+
+ @Override
+ protected Object createNode(Object name, Map attributes) {
+ 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
+ }
+ }
+
+ @Override
+ protected Object createNode(Object name, Map attributes, Object value) { changes }
+
+ @Override
+ protected Object postNodeCompletion(Object parent, Object node) {
+ parent == null ? changes.toSet() : node
+ }
+}
--- /dev/null
+package org.jetbrains.plugins.gradle.testutil
+
+import org.jetbrains.plugins.gradle.model.GradleProject
+import org.jetbrains.plugins.gradle.model.GradleModule
+import org.jetbrains.plugins.gradle.model.GradleLibraryDependency
+import org.jetbrains.plugins.gradle.model.GradleLibrary
+import org.jetbrains.plugins.gradle.model.LibraryPathType
+import com.intellij.pom.java.LanguageLevel
+
+/**
+ * @author Denis Zhdanov
+ * @since 1/25/12 1:29 PM
+ */
+class GradleProjectBuilder extends AbstractProjectBuilder {
+
+ @Override
+ protected createProject(String name, LanguageLevel languageLevel) {
+ def result = new GradleProject(same, same)
+ result.name = name
+ result.languageLevel = languageLevel
+ result
+ }
+
+ @Override
+ protected createModule(String name) {
+ def result = new GradleModule(name, unique)
+ project.addModule(result)
+ result
+ }
+
+ @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) }
+ result
+ }
+
+ @Override
+ protected createLibraryDependency(module, library) {
+ def result = new GradleLibraryDependency(module, library)
+ module.addDependency(result)
+ result
+ }
+}
--- /dev/null
+package org.jetbrains.plugins.gradle.testutil
+
+import com.intellij.pom.java.LanguageLevel
+import com.intellij.openapi.module.Module
+import com.intellij.openapi.roots.RootPolicy
+import com.intellij.openapi.roots.libraries.Library
+import com.intellij.openapi.roots.LibraryOrderEntry
+import groovy.mock.interceptor.StubFor
+import com.intellij.openapi.project.Project
+
+/**
+ * @author Denis Zhdanov
+ * @since 1/25/12 3:09 PM
+ */
+class IntellijProjectBuilder extends AbstractProjectBuilder {
+
+ def projectStub = [:]
+ def project = projectStub as Project
+ def projectStructureHelper = [
+ getModules: { modules },
+ getOrderEntries: { dependencies[it] }
+ ]
+
+ @Override
+ protected createProject(String name, LanguageLevel languageLevel) {
+ projectStub.getName = { name }
+ projectStructureHelper.getLanguageLevel = { languageLevel }
+ project
+ }
+
+ @Override
+ protected createModule(String name) {
+ [ getName: { name } ] as Module
+ }
+
+ @Override
+ protected createLibrary(String name, Map paths) {
+ [ getName: { name } ] as Library
+ }
+
+ @Override
+ protected createLibraryDependency(module, library) {
+ def stub = [:]
+ def result = stub as LibraryOrderEntry
+ stub.accept = { policy, defaultValue -> policy.visitLibraryOrderEntry(result, defaultValue) }
+ stub.getLibraryName = { library.name }
+ result
+ }
+}