[Gradle] Project dependencies serialization service IDEA-218166
authorVladislav.Soroka <Vladislav.Soroka@jetbrains.com>
Mon, 20 Jan 2020 13:01:19 +0000 (16:01 +0300)
committerintellij-monorepo-bot <intellij-monorepo-bot-no-reply@jetbrains.com>
Wed, 22 Jan 2020 18:07:02 +0000 (18:07 +0000)
GitOrigin-RevId: 2d0676cf6e8d1573ff328bcba052cf7080d1a95e

platform/external-system-rt/src/com/intellij/openapi/externalSystem/model/project/dependencies/AbstractDependencyNode.java
platform/external-system-rt/src/com/intellij/openapi/externalSystem/model/project/dependencies/DependencyNode.java
platform/external-system-rt/src/com/intellij/openapi/externalSystem/model/project/dependencies/ReferenceNode.java
platform/external-system-rt/src/com/intellij/openapi/externalSystem/util/BiFunction.java [new file with mode: 0644]
platform/external-system-rt/src/com/intellij/openapi/externalSystem/util/BooleanBiFunction.java [new file with mode: 0644]
platform/external-system-rt/src/com/intellij/openapi/externalSystem/util/IteratorUtils.java [new file with mode: 0644]
plugins/gradle/tooling-extension-api/src/org/jetbrains/plugins/gradle/tooling/serialization/ToolingStreamApiUtils.java
plugins/gradle/tooling-extension-impl/intellij.gradle.toolingExtension.tests.iml
plugins/gradle/tooling-extension-impl/src/META-INF/services/org.jetbrains.plugins.gradle.tooling.serialization.SerializationService
plugins/gradle/tooling-extension-impl/src/org/jetbrains/plugins/gradle/tooling/serialization/ProjectDependenciesSerializationService.java [new file with mode: 0644]
plugins/gradle/tooling-extension-impl/testSources/org/jetbrains/plugins/gradle/tooling/serialization/ToolingSerializerTest.kt

index 66bd51f88f5a387d72acffc0be63c63c382099e9..6bc851a3ba0fe662004c749ebdb4f91e4796e0cc 100644 (file)
@@ -1,33 +1,45 @@
 // Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
 package com.intellij.openapi.externalSystem.model.project.dependencies;
 
+import com.intellij.openapi.externalSystem.util.BooleanBiFunction;
+import com.intellij.openapi.externalSystem.util.IteratorUtils;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
 import java.io.Serializable;
-import java.util.LinkedHashSet;
-import java.util.Set;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import static com.intellij.openapi.externalSystem.util.IteratorUtils.match;
 
 public abstract class AbstractDependencyNode implements DependencyNode, Serializable {
   private final long id;
-  private final Set<DependencyNode> dependencies = new LinkedHashSet<DependencyNode>();
+  private final List<DependencyNode> dependencies;
   private String resolutionState;
 
-  protected AbstractDependencyNode(long id) {this.id = id;}
+  protected AbstractDependencyNode(long id) {this.id = id;
+    dependencies = new ArrayList<DependencyNode>(0);
+  }
 
   @Override
   public long getId() {
     return id;
   }
 
+  @NotNull
   @Override
-  public Set<DependencyNode> getDependencies() {
+  public List<DependencyNode> getDependencies() {
     return dependencies;
   }
 
+  @Nullable
   @Override
   public String getResolutionState() {
     return resolutionState;
   }
 
-  public void setResolutionState(String resolutionState) {
+  public void setResolutionState(@Nullable String resolutionState) {
     this.resolutionState = resolutionState;
   }
 
@@ -38,16 +50,49 @@ public abstract class AbstractDependencyNode implements DependencyNode, Serializ
 
     AbstractDependencyNode node = (AbstractDependencyNode)o;
     if (id != node.id) return false;
-    if (!dependencies.equals(node.dependencies)) return false;
     if (resolutionState != null ? !resolutionState.equals(node.resolutionState) : node.resolutionState != null) return false;
+    if (!equal(dependencies, node.dependencies)) return false;
     return true;
   }
 
   @Override
   public int hashCode() {
     int result = (int)(id ^ (id >>> 32));
-    result = 31 * result + dependencies.hashCode();
+    result = 31 * result + (dependencies != null ? dependencies.size() : 0);
     result = 31 * result + (resolutionState != null ? resolutionState.hashCode() : 0);
     return result;
   }
+
+  private static boolean equal(@NotNull Collection<DependencyNode> dependencies1,
+                               @NotNull Collection<DependencyNode> dependencies2) {
+    return match(new DependenciesIterator(dependencies1), new DependenciesIterator(dependencies2),
+                 new BooleanBiFunction<DependencyNode, DependencyNode>() {
+                   @Override
+                   public Boolean fun(DependencyNode o1, DependencyNode o2) {
+                     if (o1 instanceof AbstractDependencyNode && o2 instanceof AbstractDependencyNode) {
+                       AbstractDependencyNode o11 = (AbstractDependencyNode)o1;
+                       AbstractDependencyNode o21 = (AbstractDependencyNode)o2;
+                       if (o11.id != o21.id) return false;
+                       if (o11.resolutionState != null ? !o11.resolutionState.equals(o21.resolutionState) : o21.resolutionState != null) {
+                         return false;
+                       }
+                       return true;
+                     }
+                     else {
+                       return o1 == null ? o2 == null : o1.equals(o2);
+                     }
+                   }
+                 });
+  }
+
+  private static class DependenciesIterator extends IteratorUtils.AbstractObjectGraphIterator<DependencyNode> {
+    private DependenciesIterator(Collection<DependencyNode> dependencies) {
+      super(dependencies);
+    }
+
+    @Override
+    public Collection<? extends DependencyNode> getChildren(DependencyNode node) {
+      return node.getDependencies();
+    }
+  }
 }
index f59766c0b7f31f4b69efe87cad523af73cf70287..f2a0b0b828138affdbceef3b54f7f0d82a03d47a 100644 (file)
@@ -4,7 +4,7 @@ package com.intellij.openapi.externalSystem.model.project.dependencies;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
-import java.util.Set;
+import java.util.List;
 
 public interface DependencyNode {
   long getId();
@@ -15,5 +15,6 @@ public interface DependencyNode {
   @Nullable
   String getResolutionState();
 
-  Set<DependencyNode> getDependencies();
+  @NotNull
+  List<DependencyNode> getDependencies();
 }
index b96439df20899546135d71b4a0b13026b948974e..24a1a451ac3e10a872c1160bd4d64414c183f9bf 100644 (file)
@@ -7,7 +7,7 @@ import org.jetbrains.annotations.Nullable;
 
 import java.io.Serializable;
 import java.util.Collections;
-import java.util.Set;
+import java.util.List;
 
 public class ReferenceNode implements DependencyNode, Serializable {
   private final long id;
@@ -32,9 +32,10 @@ public class ReferenceNode implements DependencyNode, Serializable {
     return null;
   }
 
+  @NotNull
   @Override
-  public Set<DependencyNode> getDependencies() {
-    return Collections.emptySet();
+  public List<DependencyNode> getDependencies() {
+    return Collections.emptyList();
   }
 
   @Override
diff --git a/platform/external-system-rt/src/com/intellij/openapi/externalSystem/util/BiFunction.java b/platform/external-system-rt/src/com/intellij/openapi/externalSystem/util/BiFunction.java
new file mode 100644 (file)
index 0000000..8a224dd
--- /dev/null
@@ -0,0 +1,9 @@
+// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
+package com.intellij.openapi.externalSystem.util;
+
+import org.jetbrains.annotations.ApiStatus;
+
+@ApiStatus.Experimental
+public interface BiFunction<Result, Param1, Param2> {
+  Result fun(Param1 var1, Param2 var2);
+}
diff --git a/platform/external-system-rt/src/com/intellij/openapi/externalSystem/util/BooleanBiFunction.java b/platform/external-system-rt/src/com/intellij/openapi/externalSystem/util/BooleanBiFunction.java
new file mode 100644 (file)
index 0000000..9478bc8
--- /dev/null
@@ -0,0 +1,8 @@
+// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
+package com.intellij.openapi.externalSystem.util;
+
+import org.jetbrains.annotations.ApiStatus;
+
+@ApiStatus.Experimental
+public interface BooleanBiFunction<Param1, Param2> extends BiFunction<Boolean, Param1, Param2> {
+}
diff --git a/platform/external-system-rt/src/com/intellij/openapi/externalSystem/util/IteratorUtils.java b/platform/external-system-rt/src/com/intellij/openapi/externalSystem/util/IteratorUtils.java
new file mode 100644 (file)
index 0000000..edfb0bf
--- /dev/null
@@ -0,0 +1,79 @@
+// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
+package com.intellij.openapi.externalSystem.util;
+
+import org.jetbrains.annotations.ApiStatus;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.*;
+
+@ApiStatus.Experimental
+public class IteratorUtils {
+  public static <T> boolean match(@NotNull Iterator<T> iterator1,
+                                  @NotNull Iterator<T> iterator2,
+                                  @NotNull BooleanBiFunction<? super T, ? super T> condition) {
+    while (iterator2.hasNext()) {
+      if (!iterator1.hasNext() || !condition.fun(iterator1.next(), iterator2.next())) {
+        return false;
+      }
+    }
+    return !iterator1.hasNext();
+  }
+
+  public static <T> boolean match(@NotNull AbstractObjectGraphIterator<T> iterator1,
+                                  @NotNull AbstractObjectGraphIterator<T> iterator2,
+                                  @NotNull BooleanBiFunction<? super T, ? super T> condition) {
+    while (iterator2.hasNext()) {
+      if (!iterator1.hasNext() ||
+          !iterator1.myProcessedStructure.equals(iterator2.myProcessedStructure) ||
+          !condition.fun(iterator1.next(), iterator2.next())) {
+        return false;
+      }
+    }
+    return !iterator1.hasNext();
+  }
+
+  @ApiStatus.Experimental
+  public abstract static class AbstractObjectGraphIterator<T> implements Iterator<T> {
+    private final Set<T> mySeenObjects;
+    private final LinkedList<T> myToProcess;
+    private final LinkedList<Integer> myProcessedStructure;
+
+    public AbstractObjectGraphIterator(@NotNull Collection<T> dependencies) {
+      mySeenObjects = Collections.newSetFromMap(new IdentityHashMap<T, Boolean>());
+      myToProcess = new LinkedList<T>(dependencies);
+      myProcessedStructure = new LinkedList<Integer>();
+    }
+
+    public abstract Collection<? extends T> getChildren(T t);
+
+    @Override
+    public boolean hasNext() {
+      T dependency = myToProcess.peekFirst();
+      if (dependency == null) return false;
+      if (mySeenObjects.contains(dependency)) {
+        myToProcess.removeFirst();
+        return hasNext();
+      }
+      return !myToProcess.isEmpty();
+    }
+
+    @Override
+    public T next() {
+      T dependency = myToProcess.removeFirst();
+      if (mySeenObjects.add(dependency)) {
+        Collection<? extends T> children = getChildren(dependency);
+        myToProcess.addAll(children);
+        myProcessedStructure.add(children.size());
+        return dependency;
+      }
+      else {
+        return next();
+      }
+    }
+
+    @Override
+    public void remove() {
+      throw new UnsupportedOperationException("remove");
+    }
+  }
+}
index 130582673ef5298ebf5c2f173f59f6adf3804e15..2adb27724430bcf164252f35f36c67e10b9638db 100644 (file)
@@ -32,6 +32,12 @@ public class ToolingStreamApiUtils {
     return reader.intValue();
   }
 
+  public static long readLong(@NotNull IonReader reader, @NotNull String fieldName) {
+    reader.next();
+    assertFieldName(reader, fieldName);
+    return reader.longValue();
+  }
+
   public static boolean readBoolean(@NotNull IonReader reader, @NotNull String fieldName) {
     reader.next();
     assertFieldName(reader, fieldName);
@@ -108,6 +114,11 @@ public class ToolingStreamApiUtils {
     writer.writeString(value);
   }
 
+  public static void writeLong(@NotNull IonWriter writer, @NotNull String fieldName, long value) throws IOException {
+    writer.setFieldName(fieldName);
+    writer.writeInt(value);
+  }
+
   public static void writeBoolean(@NotNull IonWriter writer, @NotNull String fieldName, boolean value) throws IOException {
     writer.setFieldName(fieldName);
     writer.writeBool(value);
index c5194fea5260bf7445eabb23b784d301e9db8627..8f1fbffd4c59b281754c1e27dc311e356c724f25 100644 (file)
     <orderEntry type="library" scope="TEST" name="assertJ" level="project" />
     <orderEntry type="module-library" scope="TEST">
       <library name="org.jeasy:easy-random-core:4.0.0" type="repository">
-        <properties maven-id="org.jeasy:easy-random-core:4.0.0">
+        <properties maven-id="org.jeasy:easy-random-core:4.1.0">
           <exclude>
             <dependency maven-id="com.fasterxml.jackson.core:jackson-databind" />
           </exclude>
         </properties>
         <CLASSES>
-          <root url="jar://$MAVEN_REPOSITORY$/org/jeasy/easy-random-core/4.0.0/easy-random-core-4.0.0.jar!/" />
-          <root url="jar://$MAVEN_REPOSITORY$/org/objenesis/objenesis/3.0.1/objenesis-3.0.1.jar!/" />
-          <root url="jar://$MAVEN_REPOSITORY$/io/github/classgraph/classgraph/4.8.29/classgraph-4.8.29.jar!/" />
+          <root url="jar://$MAVEN_REPOSITORY$/org/jeasy/easy-random-core/4.1.0/easy-random-core-4.1.0.jar!/" />
+          <root url="jar://$MAVEN_REPOSITORY$/org/objenesis/objenesis/3.1/objenesis-3.1.jar!/" />
+          <root url="jar://$MAVEN_REPOSITORY$/io/github/classgraph/classgraph/4.8.52/classgraph-4.8.52.jar!/" />
         </CLASSES>
         <JAVADOC />
         <SOURCES />
index 80c8938691ef9298a7ec9a1dd63af0aea2d061a5..b28e73a5bc97733699145c771a61c132ad5ed031 100644 (file)
@@ -19,4 +19,5 @@ org.jetbrains.plugins.gradle.tooling.serialization.BuildScriptClasspathModelSeri
 org.jetbrains.plugins.gradle.tooling.serialization.RepositoriesModelSerializationService
 org.jetbrains.plugins.gradle.tooling.serialization.GradleExtensionsSerializationService
 org.jetbrains.plugins.gradle.tooling.serialization.ExternalTestsSerializationService
-org.jetbrains.plugins.gradle.tooling.serialization.AnnotationProcessingModelSerializationService
\ No newline at end of file
+org.jetbrains.plugins.gradle.tooling.serialization.AnnotationProcessingModelSerializationService
+org.jetbrains.plugins.gradle.tooling.serialization.ProjectDependenciesSerializationService
\ No newline at end of file
diff --git a/plugins/gradle/tooling-extension-impl/src/org/jetbrains/plugins/gradle/tooling/serialization/ProjectDependenciesSerializationService.java b/plugins/gradle/tooling-extension-impl/src/org/jetbrains/plugins/gradle/tooling/serialization/ProjectDependenciesSerializationService.java
new file mode 100644 (file)
index 0000000..286e79e
--- /dev/null
@@ -0,0 +1,423 @@
+// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
+package org.jetbrains.plugins.gradle.tooling.serialization;
+
+import com.amazon.ion.IonReader;
+import com.amazon.ion.IonType;
+import com.amazon.ion.IonWriter;
+import com.amazon.ion.system.IonBinaryWriterBuilder;
+import com.amazon.ion.system.IonReaderBuilder;
+import com.intellij.openapi.externalSystem.model.project.dependencies.*;
+import org.jetbrains.annotations.Nullable;
+import org.jetbrains.plugins.gradle.tooling.util.IntObjectMap;
+import org.jetbrains.plugins.gradle.tooling.util.ObjectCollector;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import static org.jetbrains.plugins.gradle.tooling.serialization.ToolingStreamApiUtils.*;
+
+/**
+ * @author Vladislav.Soroka
+ */
+public class ProjectDependenciesSerializationService implements SerializationService<ProjectDependencies> {
+  private final WriteContext myWriteContext = new WriteContext();
+  private final ReadContext myReadContext = new ReadContext();
+
+  @Override
+  public byte[] write(ProjectDependencies dependencies, Class<? extends ProjectDependencies> modelClazz) throws IOException {
+    ByteArrayOutputStream out = new ByteArrayOutputStream();
+    IonWriter writer = IonBinaryWriterBuilder.standard().build(out);
+    try {
+      write(writer, myWriteContext, dependencies);
+    }
+    finally {
+      writer.close();
+    }
+    return out.toByteArray();
+  }
+
+  @Override
+  public ProjectDependencies read(byte[] object, Class<? extends ProjectDependencies> modelClazz) throws IOException {
+    IonReader reader = IonReaderBuilder.standard().build(object);
+    try {
+      return read(reader, myReadContext);
+    }
+    finally {
+      reader.close();
+    }
+  }
+
+  @Override
+  public Class<? extends ProjectDependencies> getModelClass() {
+    return ProjectDependencies.class;
+  }
+
+  private static void write(final IonWriter writer, final WriteContext context, final ProjectDependencies model) throws IOException {
+    context.objectCollector.add(model, new ObjectCollector.Processor<IOException>() {
+      @Override
+      public void process(boolean isAdded, int objectId) throws IOException {
+        writer.stepIn(IonType.STRUCT);
+        writer.setFieldName(OBJECT_ID_FIELD);
+        writer.writeInt(objectId);
+        if (isAdded) {
+          writeComponentsDependencies(writer, context, model.getComponentsDependencies());
+        }
+        writer.stepOut();
+      }
+    });
+  }
+
+  private static void writeComponentsDependencies(IonWriter writer,
+                                                  WriteContext context,
+                                                  Collection<ComponentDependencies> dependencies) throws IOException {
+    writer.setFieldName("componentsDependencies");
+    writer.stepIn(IonType.LIST);
+    for (ComponentDependencies componentDependencies : dependencies) {
+      writeComponentDependencies(writer, context, componentDependencies);
+    }
+    writer.stepOut();
+  }
+
+  private static void writeComponentDependencies(final IonWriter writer,
+                                                 final WriteContext context,
+                                                 final ComponentDependencies componentDependencies) throws IOException {
+    context.componentDependenciesCollector.add(componentDependencies, new ObjectCollector.Processor<IOException>() {
+      @Override
+      public void process(boolean isAdded, int objectId) throws IOException {
+        writer.stepIn(IonType.STRUCT);
+        writer.setFieldName(OBJECT_ID_FIELD);
+        writer.writeInt(objectId);
+        if (isAdded) {
+          writeString(writer, "name", componentDependencies.getComponentName());
+          writer.setFieldName("compile");
+          writeDependencyNode(writer, context, componentDependencies.getCompileDependenciesGraph());
+          writer.setFieldName("runtime");
+          writeDependencyNode(writer, context, componentDependencies.getRuntimeDependenciesGraph());
+        }
+        writer.stepOut();
+      }
+    });
+  }
+
+  private static void writeDependencyNode(final IonWriter writer,
+                                          final WriteContext context,
+                                          final DependencyNode node) throws IOException {
+    if (node instanceof ProjectDependencyNode) {
+      writeDependencyNode(writer, context, (ProjectDependencyNode)node);
+    }
+    else if (node instanceof ArtifactDependencyNode) {
+      writeDependencyNode(writer, context, (ArtifactDependencyNode)node);
+    }
+    else if (node instanceof FileCollectionDependencyNode) {
+      writeDependencyNode(writer, context, (FileCollectionDependencyNode)node);
+    }
+    else if (node instanceof ReferenceNode) {
+      writeDependencyNode(writer, context, (ReferenceNode)node);
+    }
+    else if (node instanceof DependencyScopeNode) {
+      writeDependencyNode(writer, context, (DependencyScopeNode)node);
+    }
+    else if (node instanceof UnknownDependencyNode) {
+      writeDependencyNode(writer, context, (UnknownDependencyNode)node);
+    }
+  }
+
+  private static void writeDependencyNode(final IonWriter writer,
+                                          final WriteContext context,
+                                          final ProjectDependencyNode node)
+    throws IOException {
+    context.dependencyNodeCollector.add(node, new ObjectCollector.Processor<IOException>() {
+      @Override
+      public void process(boolean isAdded, int objectId) throws IOException {
+        writer.stepIn(IonType.STRUCT);
+        writer.setFieldName(OBJECT_ID_FIELD);
+        writer.writeInt(objectId);
+        if (isAdded) {
+          writer.setFieldName("_type");
+          writer.writeString(ProjectDependencyNode.class.getSimpleName());
+          writeDependencyCommonFields(writer, node);
+          writeString(writer, "projectName", node.getProjectName());
+          writeDependenciesField(writer, context, node);
+        }
+        writer.stepOut();
+      }
+    });
+  }
+
+  private static void writeDependencyNode(final IonWriter writer,
+                                          final WriteContext context,
+                                          final ArtifactDependencyNode node)
+    throws IOException {
+    context.dependencyNodeCollector.add(node, new ObjectCollector.Processor<IOException>() {
+      @Override
+      public void process(boolean isAdded, int objectId) throws IOException {
+        writer.stepIn(IonType.STRUCT);
+        writer.setFieldName(OBJECT_ID_FIELD);
+        writer.writeInt(objectId);
+        if (isAdded) {
+          writer.setFieldName("_type");
+          writer.writeString(ArtifactDependencyNode.class.getSimpleName());
+          writeDependencyCommonFields(writer, node);
+          writeString(writer, "group", node.getGroup());
+          writeString(writer, "module", node.getModule());
+          writeString(writer, "version", node.getVersion());
+          writeDependenciesField(writer, context, node);
+        }
+        writer.stepOut();
+      }
+    });
+  }
+
+  private static void writeDependencyNode(final IonWriter writer,
+                                          final WriteContext context,
+                                          final FileCollectionDependencyNode node)
+    throws IOException {
+    context.dependencyNodeCollector.add(node, new ObjectCollector.Processor<IOException>() {
+      @Override
+      public void process(boolean isAdded, int objectId) throws IOException {
+        writer.stepIn(IonType.STRUCT);
+        writer.setFieldName(OBJECT_ID_FIELD);
+        writer.writeInt(objectId);
+        if (isAdded) {
+          writer.setFieldName("_type");
+          writer.writeString(FileCollectionDependencyNode.class.getSimpleName());
+          writeDependencyCommonFields(writer, node);
+          writeString(writer, "displayName", node.getDisplayName());
+          writeString(writer, "path", node.getPath());
+          writeDependenciesField(writer, context, node);
+        }
+        writer.stepOut();
+      }
+    });
+  }
+
+  private static void writeDependencyNode(final IonWriter writer,
+                                          final WriteContext context,
+                                          final ReferenceNode node)
+    throws IOException {
+    context.dependencyNodeCollector.add(node, new ObjectCollector.Processor<IOException>() {
+      @Override
+      public void process(boolean isAdded, int objectId) throws IOException {
+        writer.stepIn(IonType.STRUCT);
+        writer.setFieldName(OBJECT_ID_FIELD);
+        writer.writeInt(objectId);
+        if (isAdded) {
+          writer.setFieldName("_type");
+          writer.writeString(ReferenceNode.class.getSimpleName());
+          writeDependencyCommonFields(writer, node);
+        }
+        writer.stepOut();
+      }
+    });
+  }
+
+  private static void writeDependencyNode(final IonWriter writer,
+                                          final WriteContext context,
+                                          final DependencyScopeNode node)
+    throws IOException {
+    context.dependencyNodeCollector.add(node, new ObjectCollector.Processor<IOException>() {
+      @Override
+      public void process(boolean isAdded, int objectId) throws IOException {
+        writer.stepIn(IonType.STRUCT);
+        writer.setFieldName(OBJECT_ID_FIELD);
+        writer.writeInt(objectId);
+        if (isAdded) {
+          writer.setFieldName("_type");
+          writer.writeString(DependencyScopeNode.class.getSimpleName());
+          writeDependencyCommonFields(writer, node);
+          writeString(writer, "scope", node.getScope());
+          writeString(writer, "displayName", node.getDisplayName());
+          writeString(writer, "description", node.getDescription());
+          writeDependenciesField(writer, context, node);
+        }
+        writer.stepOut();
+      }
+    });
+  }
+
+  private static void writeDependencyNode(final IonWriter writer,
+                                          final WriteContext context,
+                                          final UnknownDependencyNode node)
+    throws IOException {
+    context.dependencyNodeCollector.add(node, new ObjectCollector.Processor<IOException>() {
+      @Override
+      public void process(boolean isAdded, int objectId) throws IOException {
+        writer.stepIn(IonType.STRUCT);
+        writer.setFieldName(OBJECT_ID_FIELD);
+        writer.writeInt(objectId);
+        if (isAdded) {
+          writer.setFieldName("_type");
+          writer.writeString(UnknownDependencyNode.class.getSimpleName());
+          writeDependencyCommonFields(writer, node);
+          writeString(writer, "name", node.getDisplayName());
+          writeDependenciesField(writer, context, node);
+        }
+        writer.stepOut();
+      }
+    });
+  }
+
+  private static void writeDependencyCommonFields(IonWriter writer, DependencyNode node)
+    throws IOException {
+    writeLong(writer, "id", node.getId());
+    writeString(writer, "resolutionState", node.getResolutionState());
+  }
+
+  private static void writeDependenciesField(IonWriter writer,
+                                             WriteContext context,
+                                             DependencyNode dependencyNode) throws IOException {
+    writer.setFieldName("dependencies");
+    writer.stepIn(IonType.LIST);
+    for (DependencyNode componentDependencies : dependencyNode.getDependencies()) {
+      writeDependencyNode(writer, context, componentDependencies);
+    }
+    writer.stepOut();
+  }
+
+  @Nullable
+  private static ProjectDependencies read(final IonReader reader, final ReadContext context) {
+    if (reader.next() == null) return null;
+    reader.stepIn();
+
+    ProjectDependencies model =
+      context.objectMap.computeIfAbsent(readInt(reader, OBJECT_ID_FIELD), new IntObjectMap.SimpleObjectFactory<ProjectDependenciesImpl>() {
+
+        @Override
+        public ProjectDependenciesImpl create() {
+          ProjectDependenciesImpl dependencies = new ProjectDependenciesImpl();
+          List<ComponentDependencies> componentsDependencies = readComponentsDependencies(reader, context);
+          for (ComponentDependencies componentDependencies : componentsDependencies) {
+            dependencies.add(componentDependencies);
+          }
+          return dependencies;
+        }
+      });
+    reader.stepOut();
+    return model;
+  }
+
+  private static List<ComponentDependencies> readComponentsDependencies(IonReader reader, ReadContext context) {
+    List<ComponentDependencies> list = new ArrayList<ComponentDependencies>();
+    reader.next();
+    reader.stepIn();
+    ComponentDependencies componentDependencies;
+    while ((componentDependencies = readComponentDependencies(reader, context)) != null) {
+      list.add(componentDependencies);
+    }
+    reader.stepOut();
+    return list;
+  }
+
+  @Nullable
+  private static ComponentDependencies readComponentDependencies(final IonReader reader, final ReadContext context) {
+    if (reader.next() == null) return null;
+    reader.stepIn();
+    ComponentDependencies dependency =
+      context.componentDependenciesMap
+        .computeIfAbsent(readInt(reader, OBJECT_ID_FIELD), new IntObjectMap.SimpleObjectFactory<ComponentDependencies>() {
+          @Override
+          public ComponentDependencies create() {
+            String componentName = assertNotNull(readString(reader, "name"));
+            DependencyScopeNode compileDependencies = (DependencyScopeNode)assertNotNull(readDependencyNode(reader, context, "compile"));
+            DependencyScopeNode runtimeDependencies = (DependencyScopeNode)assertNotNull(readDependencyNode(reader, context, "runtime"));
+            return new ComponentDependenciesImpl(componentName, compileDependencies, runtimeDependencies);
+          }
+        });
+    reader.stepOut();
+    return dependency;
+  }
+
+  private static DependencyNode readDependencyNode(final IonReader reader, final ReadContext context, @Nullable String fieldName) {
+    if (reader.next() == null) return null;
+    assertFieldName(reader, fieldName);
+    reader.stepIn();
+
+    DependencyNode dependency =
+      context.nodesMap
+        .computeIfAbsent(readInt(reader, OBJECT_ID_FIELD), new IntObjectMap.ObjectFactory<DependencyNode>() {
+
+          @Override
+          public DependencyNode newInstance() {
+            String type = readString(reader, "_type");
+            long id = readLong(reader, "id");
+            String resolutionState = readString(reader, "resolutionState");
+            DependencyNode node;
+            if (ProjectDependencyNode.class.getSimpleName().equals(type)) {
+              String projectName = assertNotNull(readString(reader, "projectName"));
+              node = new ProjectDependencyNodeImpl(id, projectName);
+            }
+            else if (ArtifactDependencyNode.class.getSimpleName().equals(type)) {
+              String group = assertNotNull(readString(reader, "group"));
+              String module = assertNotNull(readString(reader, "module"));
+              String version = assertNotNull(readString(reader, "version"));
+              node = new ArtifactDependencyNodeImpl(id, group, module, version);
+            }
+            else if (FileCollectionDependencyNode.class.getSimpleName().equals(type)) {
+              String displayName = assertNotNull(readString(reader, "displayName"));
+              String path = assertNotNull(readString(reader, "path"));
+              node = new FileCollectionDependencyNodeImpl(id, displayName, path);
+            }
+            else if (DependencyScopeNode.class.getSimpleName().equals(type)) {
+              String scope = assertNotNull(readString(reader, "scope"));
+              String displayName = assertNotNull(readString(reader, "displayName"));
+              String description = assertNotNull(readString(reader, "description"));
+              node = new DependencyScopeNode(id, scope, displayName, description);
+            }
+            else if (ReferenceNode.class.getSimpleName().equals(type)) {
+              node = new ReferenceNode(id);
+            }
+            else if (UnknownDependencyNode.class.getSimpleName().equals(type)) {
+              String name = assertNotNull(readString(reader, "name"));
+              node = new UnknownDependencyNode(id, name);
+            }
+            else {
+              throw new RuntimeException("Unsupported dependency node");
+            }
+            if (node instanceof AbstractDependencyNode) {
+              ((AbstractDependencyNode)node).setResolutionState(resolutionState);
+            }
+            return node;
+          }
+
+          @Override
+          public void fill(DependencyNode node) {
+            if (node instanceof AbstractDependencyNode) {
+              node.getDependencies().addAll(readDependencyNodes(reader, context));
+            }
+          }
+        });
+    reader.stepOut();
+    return dependency;
+  }
+
+  private static Collection<? extends DependencyNode> readDependencyNodes(IonReader reader, ReadContext context) {
+    List<DependencyNode> list = new ArrayList<DependencyNode>();
+    reader.next();
+    reader.stepIn();
+    DependencyNode node;
+    while ((node = readDependencyNode(reader, context, null)) != null) {
+      list.add(node);
+    }
+    reader.stepOut();
+    return list;
+  }
+
+  private static class ReadContext {
+    private final IntObjectMap<ProjectDependenciesImpl> objectMap = new IntObjectMap<ProjectDependenciesImpl>();
+    private final IntObjectMap<ComponentDependencies> componentDependenciesMap = new IntObjectMap<ComponentDependencies>();
+    private final IntObjectMap<DependencyNode> nodesMap = new IntObjectMap<DependencyNode>();
+  }
+
+  private static class WriteContext {
+    private final ObjectCollector<ProjectDependencies, IOException> objectCollector =
+      new ObjectCollector<ProjectDependencies, IOException>();
+    private final ObjectCollector<ComponentDependencies, IOException> componentDependenciesCollector =
+      new ObjectCollector<ComponentDependencies, IOException>();
+    private final ObjectCollector<DependencyNode, IOException> dependencyNodeCollector =
+      new ObjectCollector<DependencyNode, IOException>();
+  }
+}
+
index b0d43ce43f49718f73df6abb0d758143db78db20..28f57d8edda94cef7e77d1e71e6314291ca5ce35 100644 (file)
@@ -1,6 +1,7 @@
 // Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
 package org.jetbrains.plugins.gradle.tooling.serialization
 
+import com.intellij.openapi.externalSystem.model.project.dependencies.ProjectDependencies
 import org.assertj.core.api.Assertions.assertThat
 import org.gradle.api.artifacts.Dependency
 import org.gradle.util.GradleVersion
@@ -128,6 +129,12 @@ class ToolingSerializerTest {
     doTest(AnnotationProcessingModelImpl::class.java)
   }
 
+  @Test
+  @Throws(Exception::class)
+  fun `project dependencies serialization test`() {
+    doTest(ProjectDependencies::class.java)
+  }
+
   @Throws(IOException::class)
   private fun <T> doTest(modelClazz: Class<T>) {
     doTest(modelClazz, null)