This is an automated email from the ASF dual-hosted git repository.

xiaokang pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/incubator-graphar.git


The following commit(s) were added to refs/heads/main by this push:
     new 6f112c69 feat(java,info): refactor yaml saver api (#763)
6f112c69 is described below

commit 6f112c69fef71bec0483ab0e5142e18f1165d1ea
Author: Xiaokang Yang <[email protected]>
AuthorDate: Sun Sep 28 11:24:21 2025 +0800

    feat(java,info): refactor yaml saver api (#763)
    
    * refactor saver
    
    * format
    
    * update
    
    * update doc
    
    * add test case
    
    * update docs
    
    * update
    
    * fix import
    
    * update saver method and add test
    
    * fix
---
 docs/libraries/java/info/getting-started.md        |  60 ++++
 .../java/org/apache/graphar/info/EdgeInfo.java     |   7 +
 .../java/org/apache/graphar/info/GraphInfo.java    |  62 +++++
 .../java/org/apache/graphar/info/VertexInfo.java   |   7 +
 .../graphar/info/loader/BaseGraphInfoLoader.java   |  18 +-
 .../graphar/info/loader/ReaderGraphInfoLoader.java |   4 +-
 .../graphar/info/loader/StreamGraphInfoLoader.java |   8 +-
 .../graphar/info/loader/StringGraphInfoLoader.java |   4 +-
 .../graphar/info/saver/BaseGraphInfoSaver.java     |  62 +++++
 .../saver/{GraphSaver.java => GraphInfoSaver.java} |  12 +-
 .../graphar/info/saver/LocalYamlGraphSaver.java    |  85 ------
 .../impl/LocalFileSystemYamlGraphSaver.java}       |  27 +-
 .../org/apache/graphar/info/type/FileType.java     |  13 +
 .../org/apache/graphar/info/yaml/GraphYaml.java    |  25 +-
 .../apache/graphar/info/BaseFileSystemTest.java    |   2 +-
 .../apache/graphar/info/GraphInfoSaverTest.java    | 210 ++++++++++++++
 .../org/apache/graphar/info/GraphInfoUriTest.java  |  14 +-
 .../org/apache/graphar/info/GraphSaverTest.java    |  82 ------
 .../org/apache/graphar/info/TestDataFactory.java   |  12 +-
 .../java/org/apache/graphar/info/TestUtil.java     |  35 +++
 .../apache/graphar/info/TestVerificationUtils.java | 301 +++++++++++++++++++++
 21 files changed, 841 insertions(+), 209 deletions(-)

diff --git a/docs/libraries/java/info/getting-started.md 
b/docs/libraries/java/info/getting-started.md
index 27e88e5d..fb110e16 100644
--- a/docs/libraries/java/info/getting-started.md
+++ b/docs/libraries/java/info/getting-started.md
@@ -96,6 +96,66 @@ MyStringGraphInfoLoader customLoader = new 
MyStringGraphInfoLoader();
 GraphInfo graphInfo = 
customLoader.loadGraphInfo(URI.create("db://mydatabase/graphs/graph1/graph.yml"));
 ```
 
+### Save Graph Info
+
+The java-info module also provides functionality to save graph metadata to 
YAML files using the `GraphSaver` interface. Here's an example of how to use it:
+
+```java
+import org.apache.graphar.info.GraphInfo;
+import org.apache.graphar.info.saver.GraphInfoSaver;
+import org.apache.graphar.info.saver.impl.LocalFileSystemYamlGraphSaver;
+import java.net.URI;
+
+// Create or obtain a GraphInfo object
+GraphInfo graphInfo = createOrLoadGraphInfo(); // your method to create or 
load GraphInfo
+
+// Create a GraphSaver instance
+GraphInfoSaver graphSaver = new LocalFileSystemYamlGraphSaver();
+
+// Save the graph info to a directory
+String savePath = "/path/to/save/directory";
+try {
+    graphSaver.save(URI.create(savePath), graphInfo);
+    System.out.println("Graph info saved successfully to " + savePath);
+} catch (IOException e) {
+    System.err.println("Failed to save graph info: " + e.getMessage());
+    e.printStackTrace();
+}
+```
+
+This will save the graph metadata as a set of YAML files:
+- One main graph YAML file (e.g., `graph-name.graph.yaml`)
+- One YAML file for each vertex type (e.g., `person.vertex.yaml`)
+- One YAML file for each edge type (e.g., `person_knows_person.edge.yaml`)
+
+Alternatively, you can use the dump method to convert graph info to a string 
and store it anywhere:
+
+```java
+import org.apache.graphar.info.GraphInfo;
+import org.apache.graphar.info.EdgeInfo;
+import org.apache.graphar.info.VertexInfo;
+import java.net.URI;
+
+// Create or obtain a GraphInfo object
+GraphInfo graphInfo = createOrLoadGraphInfo(); // your method to create or 
load GraphInfo
+
+// Set custom storage URIs for vertex and edge info files
+for (VertexInfo vertexInfo : graphInfo.getVertexInfos()) {
+    graphInfo.setStoreUri(vertexInfo, URI.create("db://path/vertex/" + 
vertexInfo.getType() + ".vertex.yaml"));
+}
+
+for (EdgeInfo edgeInfo : graphInfo.getEdgeInfos()) {
+    graphInfo.setStoreUri(edgeInfo, URI.create("db://path/edge/" + 
edgeInfo.getConcat() + ".edge.yaml"));
+}
+
+// Convert graph info to YAML string
+String graphYamlString = graphInfo.dump();
+
+// Now you can store the YAML string anywhere you want
+// For example, save to a database, send over network, etc.
+saveYamlStringToDatabase(graphYamlString);
+```
+
 ### Building
 
 To build the graphar-info module, you need:
diff --git 
a/maven-projects/info/src/main/java/org/apache/graphar/info/EdgeInfo.java 
b/maven-projects/info/src/main/java/org/apache/graphar/info/EdgeInfo.java
index 57de3c44..cd18c16d 100644
--- a/maven-projects/info/src/main/java/org/apache/graphar/info/EdgeInfo.java
+++ b/maven-projects/info/src/main/java/org/apache/graphar/info/EdgeInfo.java
@@ -19,6 +19,7 @@
 
 package org.apache.graphar.info;
 
+import java.io.Writer;
 import java.net.URI;
 import java.util.ArrayList;
 import java.util.HashMap;
@@ -494,6 +495,12 @@ public class EdgeInfo {
         return getAdjacentListUri(adjListType).resolve("edge_count" + 
vertexChunkIndex);
     }
 
+    public void dump(Writer output) {
+        Yaml yaml = new Yaml(GraphYaml.getRepresenter(), 
GraphYaml.getDumperOptions());
+        EdgeYaml edgeYaml = new EdgeYaml(this);
+        yaml.dump(edgeYaml, output);
+    }
+
     public String dump() {
         Yaml yaml = new Yaml(GraphYaml.getRepresenter(), 
GraphYaml.getDumperOptions());
         EdgeYaml edgeYaml = new EdgeYaml(this);
diff --git 
a/maven-projects/info/src/main/java/org/apache/graphar/info/GraphInfo.java 
b/maven-projects/info/src/main/java/org/apache/graphar/info/GraphInfo.java
index c67a5428..e0ed2bfa 100644
--- a/maven-projects/info/src/main/java/org/apache/graphar/info/GraphInfo.java
+++ b/maven-projects/info/src/main/java/org/apache/graphar/info/GraphInfo.java
@@ -19,7 +19,10 @@
 
 package org.apache.graphar.info;
 
+import java.io.Writer;
 import java.net.URI;
+import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Optional;
@@ -37,6 +40,23 @@ public class GraphInfo {
     private final Map<String, VertexInfo> vertexType2VertexInfo;
     private final Map<String, EdgeInfo> edgeConcat2EdgeInfo;
     private final VersionInfo version;
+    private final Map<String, URI> types2StoreUri;
+
+    public GraphInfo(
+            String name,
+            Map<URI, VertexInfo> vertexInfos,
+            Map<URI, EdgeInfo> edgeInfos,
+            URI uri,
+            String version) {
+        this(
+                name,
+                new ArrayList<>(vertexInfos.values()),
+                new ArrayList<>(edgeInfos.values()),
+                uri,
+                version);
+        vertexInfos.forEach((key, value) -> types2StoreUri.put(value.getType() 
+ ".vertex", key));
+        edgeInfos.forEach((key, value) -> types2StoreUri.put(value.getConcat() 
+ ".edge", key));
+    }
 
     public GraphInfo(
             String name,
@@ -68,6 +88,7 @@ public class GraphInfo {
                         .collect(
                                 Collectors.toUnmodifiableMap(
                                         EdgeInfo::getConcat, 
Function.identity()));
+        this.types2StoreUri = new HashMap<>();
     }
 
     private GraphInfo(
@@ -103,6 +124,19 @@ public class GraphInfo {
         this.version = version;
         this.vertexType2VertexInfo = vertexType2VertexInfo;
         this.edgeConcat2EdgeInfo = edgeConcat2EdgeInfo;
+        this.types2StoreUri = new HashMap<>();
+    }
+
+    public void dump(URI storeUri, Writer output) {
+        Yaml yaml = new Yaml(GraphYaml.getRepresenter(), 
GraphYaml.getDumperOptions());
+        GraphYaml graphYaml = new GraphYaml(storeUri, this);
+        yaml.dump(graphYaml, output);
+    }
+
+    public String dump(URI storeUri) {
+        Yaml yaml = new Yaml(GraphYaml.getRepresenter(), 
GraphYaml.getDumperOptions());
+        GraphYaml graphYaml = new GraphYaml(storeUri, this);
+        return yaml.dump(graphYaml);
     }
 
     public String dump() {
@@ -212,6 +246,34 @@ public class GraphInfo {
         return version;
     }
 
+    public void setStoreUri(VertexInfo vertexInfo, URI storeUri) {
+        this.types2StoreUri.put(vertexInfo.getType() + ".vertex", storeUri);
+    }
+
+    public void setStoreUri(EdgeInfo edgeInfo, URI storeUri) {
+        this.types2StoreUri.put(edgeInfo.getConcat() + ".edge", storeUri);
+    }
+
+    public URI getStoreUri(VertexInfo vertexInfo) {
+        String type = vertexInfo.getType() + ".vertex";
+        if (types2StoreUri.containsKey(type)) {
+            return types2StoreUri.get(type);
+        }
+        return URI.create(type + ".yaml");
+    }
+
+    public URI getStoreUri(EdgeInfo edgeInfo) {
+        String type = edgeInfo.getConcat() + ".edge";
+        if (types2StoreUri.containsKey(type)) {
+            return types2StoreUri.get(type);
+        }
+        return URI.create(type + ".yaml");
+    }
+
+    public Map<String, URI> getTypes2Uri() {
+        return types2StoreUri;
+    }
+
     public boolean isValidated() {
         // Check if name is not empty and base URI is not null
         if (name == null || name.isEmpty() || baseUri == null) {
diff --git 
a/maven-projects/info/src/main/java/org/apache/graphar/info/VertexInfo.java 
b/maven-projects/info/src/main/java/org/apache/graphar/info/VertexInfo.java
index 94123a05..b4a14c5e 100644
--- a/maven-projects/info/src/main/java/org/apache/graphar/info/VertexInfo.java
+++ b/maven-projects/info/src/main/java/org/apache/graphar/info/VertexInfo.java
@@ -19,6 +19,7 @@
 
 package org.apache.graphar.info;
 
+import java.io.Writer;
 import java.net.URI;
 import java.util.HashSet;
 import java.util.List;
@@ -122,6 +123,12 @@ public class VertexInfo {
         return getBaseUri().resolve("vertex_count");
     }
 
+    public void dump(Writer output) {
+        Yaml yaml = new Yaml(GraphYaml.getRepresenter(), 
GraphYaml.getDumperOptions());
+        VertexYaml vertexYaml = new VertexYaml(this);
+        yaml.dump(vertexYaml, output);
+    }
+
     public String dump() {
         Yaml yaml = new Yaml(GraphYaml.getRepresenter(), 
GraphYaml.getDumperOptions());
         VertexYaml vertexYaml = new VertexYaml(this);
diff --git 
a/maven-projects/info/src/main/java/org/apache/graphar/info/loader/BaseGraphInfoLoader.java
 
b/maven-projects/info/src/main/java/org/apache/graphar/info/loader/BaseGraphInfoLoader.java
index 77b74f8b..50463373 100644
--- 
a/maven-projects/info/src/main/java/org/apache/graphar/info/loader/BaseGraphInfoLoader.java
+++ 
b/maven-projects/info/src/main/java/org/apache/graphar/info/loader/BaseGraphInfoLoader.java
@@ -21,8 +21,8 @@ package org.apache.graphar.info.loader;
 
 import java.io.IOException;
 import java.net.URI;
-import java.util.ArrayList;
-import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
 import java.util.stream.Collectors;
 import org.apache.graphar.info.EdgeInfo;
 import org.apache.graphar.info.GraphInfo;
@@ -41,7 +41,7 @@ public abstract class BaseGraphInfoLoader implements 
GraphInfoLoader {
 
     public abstract EdgeInfo loadEdgeInfo(URI edgeYamlUri) throws IOException;
 
-    public GraphInfo buildGraphInfoFromGraphYaml(URI baseUri, GraphYaml 
graphYaml)
+    protected GraphInfo buildGraphInfoFromGraphYaml(URI baseUri, GraphYaml 
graphYaml)
             throws IOException {
 
         URI defaultBaseUri = baseUri.resolve(".");
@@ -50,16 +50,16 @@ public abstract class BaseGraphInfoLoader implements 
GraphInfoLoader {
         }
 
         // load vertices
-        List<VertexInfo> vertexInfos = new 
ArrayList<>(graphYaml.getVertices().size());
+        Map<URI, VertexInfo> vertexInfos = new TreeMap<>();
         for (String vertexYamlPath : graphYaml.getVertices()) {
             URI vertexInfoUri = baseUri.resolve(vertexYamlPath);
-            vertexInfos.add(loadVertexInfo(vertexInfoUri));
+            vertexInfos.put(vertexInfoUri, loadVertexInfo(vertexInfoUri));
         }
         // load edges
-        List<EdgeInfo> edgeInfos = new 
ArrayList<>(graphYaml.getEdges().size());
+        Map<URI, EdgeInfo> edgeInfos = new TreeMap<>();
         for (String edgeYamlPath : graphYaml.getEdges()) {
             URI edgeInfoUri = baseUri.resolve(edgeYamlPath);
-            edgeInfos.add(loadEdgeInfo(edgeInfoUri));
+            edgeInfos.put(edgeInfoUri, loadEdgeInfo(edgeInfoUri));
         }
         return new GraphInfo(
                 graphYaml.getName(),
@@ -69,7 +69,7 @@ public abstract class BaseGraphInfoLoader implements 
GraphInfoLoader {
                 graphYaml.getVersion());
     }
 
-    public VertexInfo buildVertexInfoFromGraphYaml(VertexYaml vertexYaml) {
+    protected VertexInfo buildVertexInfoFromVertexYaml(VertexYaml vertexYaml) {
         return new VertexInfo(
                 vertexYaml.getType(),
                 vertexYaml.getChunk_size(),
@@ -80,7 +80,7 @@ public abstract class BaseGraphInfoLoader implements 
GraphInfoLoader {
                 vertexYaml.getVersion());
     }
 
-    public EdgeInfo buildEdgeInfoFromGraphYaml(EdgeYaml edgeYaml) {
+    protected EdgeInfo buildEdgeInfoFromEdgeYaml(EdgeYaml edgeYaml) {
         return new EdgeInfo(
                 edgeYaml.getSrc_type(),
                 edgeYaml.getEdge_type(),
diff --git 
a/maven-projects/info/src/main/java/org/apache/graphar/info/loader/ReaderGraphInfoLoader.java
 
b/maven-projects/info/src/main/java/org/apache/graphar/info/loader/ReaderGraphInfoLoader.java
index f83b826b..c6af59a4 100644
--- 
a/maven-projects/info/src/main/java/org/apache/graphar/info/loader/ReaderGraphInfoLoader.java
+++ 
b/maven-projects/info/src/main/java/org/apache/graphar/info/loader/ReaderGraphInfoLoader.java
@@ -49,7 +49,7 @@ public abstract class ReaderGraphInfoLoader extends 
BaseGraphInfoLoader {
         Reader yaml = readYaml(vertexYamlUri);
         Yaml vertexYamlLoader = new Yaml(new Constructor(VertexYaml.class, new 
LoaderOptions()));
         VertexYaml vertexYaml = vertexYamlLoader.load(yaml);
-        return buildVertexInfoFromGraphYaml(vertexYaml);
+        return buildVertexInfoFromVertexYaml(vertexYaml);
     }
 
     @Override
@@ -57,6 +57,6 @@ public abstract class ReaderGraphInfoLoader extends 
BaseGraphInfoLoader {
         Reader yaml = readYaml(edgeYamlUri);
         Yaml edgeYamlLoader = new Yaml(new Constructor(EdgeYaml.class, new 
LoaderOptions()));
         EdgeYaml edgeYaml = edgeYamlLoader.load(yaml);
-        return buildEdgeInfoFromGraphYaml(edgeYaml);
+        return buildEdgeInfoFromEdgeYaml(edgeYaml);
     }
 }
diff --git 
a/maven-projects/info/src/main/java/org/apache/graphar/info/loader/StreamGraphInfoLoader.java
 
b/maven-projects/info/src/main/java/org/apache/graphar/info/loader/StreamGraphInfoLoader.java
index 5292c622..65dc567f 100644
--- 
a/maven-projects/info/src/main/java/org/apache/graphar/info/loader/StreamGraphInfoLoader.java
+++ 
b/maven-projects/info/src/main/java/org/apache/graphar/info/loader/StreamGraphInfoLoader.java
@@ -48,9 +48,9 @@ public abstract class StreamGraphInfoLoader extends 
BaseGraphInfoLoader {
     @Override
     public VertexInfo loadVertexInfo(URI vertexYamlUri) throws IOException {
         InputStream yaml = readYaml(vertexYamlUri);
-        Yaml edgeYamlLoader = new Yaml(new Constructor(VertexYaml.class, new 
LoaderOptions()));
-        VertexYaml edgeYaml = edgeYamlLoader.load(yaml);
-        return buildVertexInfoFromGraphYaml(edgeYaml);
+        Yaml vertexYamlLoader = new Yaml(new Constructor(VertexYaml.class, new 
LoaderOptions()));
+        VertexYaml vertexYaml = vertexYamlLoader.load(yaml);
+        return buildVertexInfoFromVertexYaml(vertexYaml);
     }
 
     @Override
@@ -58,6 +58,6 @@ public abstract class StreamGraphInfoLoader extends 
BaseGraphInfoLoader {
         InputStream yaml = readYaml(edgeYamlUri);
         Yaml edgeYamlLoader = new Yaml(new Constructor(EdgeYaml.class, new 
LoaderOptions()));
         EdgeYaml edgeYaml = edgeYamlLoader.load(yaml);
-        return buildEdgeInfoFromGraphYaml(edgeYaml);
+        return buildEdgeInfoFromEdgeYaml(edgeYaml);
     }
 }
diff --git 
a/maven-projects/info/src/main/java/org/apache/graphar/info/loader/StringGraphInfoLoader.java
 
b/maven-projects/info/src/main/java/org/apache/graphar/info/loader/StringGraphInfoLoader.java
index cc82ab69..427e8e90 100644
--- 
a/maven-projects/info/src/main/java/org/apache/graphar/info/loader/StringGraphInfoLoader.java
+++ 
b/maven-projects/info/src/main/java/org/apache/graphar/info/loader/StringGraphInfoLoader.java
@@ -48,7 +48,7 @@ public abstract class StringGraphInfoLoader extends 
BaseGraphInfoLoader {
         String yaml = readYaml(vertexYamlUri);
         Yaml vertexYamlLoader = new Yaml(new Constructor(VertexYaml.class, new 
LoaderOptions()));
         VertexYaml vertexYaml = vertexYamlLoader.load(yaml);
-        return buildVertexInfoFromGraphYaml(vertexYaml);
+        return buildVertexInfoFromVertexYaml(vertexYaml);
     }
 
     @Override
@@ -56,6 +56,6 @@ public abstract class StringGraphInfoLoader extends 
BaseGraphInfoLoader {
         String yaml = readYaml(edgeYamlUri);
         Yaml edgeYamlLoader = new Yaml(new Constructor(EdgeYaml.class, new 
LoaderOptions()));
         EdgeYaml edgeYaml = edgeYamlLoader.load(yaml);
-        return buildEdgeInfoFromGraphYaml(edgeYaml);
+        return buildEdgeInfoFromEdgeYaml(edgeYaml);
     }
 }
diff --git 
a/maven-projects/info/src/main/java/org/apache/graphar/info/saver/BaseGraphInfoSaver.java
 
b/maven-projects/info/src/main/java/org/apache/graphar/info/saver/BaseGraphInfoSaver.java
new file mode 100644
index 00000000..a59bdcc7
--- /dev/null
+++ 
b/maven-projects/info/src/main/java/org/apache/graphar/info/saver/BaseGraphInfoSaver.java
@@ -0,0 +1,62 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.graphar.info.saver;
+
+import java.io.IOException;
+import java.io.Writer;
+import java.net.URI;
+import org.apache.graphar.info.EdgeInfo;
+import org.apache.graphar.info.GraphInfo;
+import org.apache.graphar.info.VertexInfo;
+
+public abstract class BaseGraphInfoSaver implements GraphInfoSaver {
+
+    public abstract Writer writeYaml(URI uri) throws IOException;
+
+    @Override
+    public void save(URI graphInfoUri, GraphInfo graphInfo) throws IOException 
{
+        Writer writer = writeYaml(graphInfoUri);
+        graphInfo.dump(graphInfoUri, writer);
+        writer.close();
+
+        for (VertexInfo vertexInfo : graphInfo.getVertexInfos()) {
+            URI saveUri = 
graphInfoUri.resolve(graphInfo.getStoreUri(vertexInfo));
+            save(saveUri, vertexInfo);
+        }
+        for (EdgeInfo edgeInfo : graphInfo.getEdgeInfos()) {
+            URI saveUri = 
graphInfoUri.resolve(graphInfo.getStoreUri(edgeInfo));
+            save(saveUri, edgeInfo);
+        }
+    }
+
+    @Override
+    public void save(URI vertexInfoUri, VertexInfo vertexInfo) throws 
IOException {
+        Writer vertexWriter = writeYaml(vertexInfoUri);
+        vertexInfo.dump(vertexWriter);
+        vertexWriter.close();
+    }
+
+    @Override
+    public void save(URI edgeInfoUri, EdgeInfo edgeInfo) throws IOException {
+        Writer edgeWriter = writeYaml(edgeInfoUri);
+        edgeInfo.dump(edgeWriter);
+        edgeWriter.close();
+    }
+}
diff --git 
a/maven-projects/info/src/main/java/org/apache/graphar/info/saver/GraphSaver.java
 
b/maven-projects/info/src/main/java/org/apache/graphar/info/saver/GraphInfoSaver.java
similarity index 71%
rename from 
maven-projects/info/src/main/java/org/apache/graphar/info/saver/GraphSaver.java
rename to 
maven-projects/info/src/main/java/org/apache/graphar/info/saver/GraphInfoSaver.java
index 5ee1d916..084409e6 100644
--- 
a/maven-projects/info/src/main/java/org/apache/graphar/info/saver/GraphSaver.java
+++ 
b/maven-projects/info/src/main/java/org/apache/graphar/info/saver/GraphInfoSaver.java
@@ -20,9 +20,15 @@
 package org.apache.graphar.info.saver;
 
 import java.io.IOException;
+import java.net.URI;
+import org.apache.graphar.info.EdgeInfo;
 import org.apache.graphar.info.GraphInfo;
+import org.apache.graphar.info.VertexInfo;
 
-@FunctionalInterface
-public interface GraphSaver {
-    void save(String path, GraphInfo graphInfo) throws IOException;
+public interface GraphInfoSaver {
+    void save(URI graphInfoUri, GraphInfo graphInfo) throws IOException;
+
+    void save(URI vertexInfoUri, VertexInfo vertexInfo) throws IOException;
+
+    void save(URI edgeInfoUri, EdgeInfo edgeInfo) throws IOException;
 }
diff --git 
a/maven-projects/info/src/main/java/org/apache/graphar/info/saver/LocalYamlGraphSaver.java
 
b/maven-projects/info/src/main/java/org/apache/graphar/info/saver/LocalYamlGraphSaver.java
deleted file mode 100644
index 7038fbb3..00000000
--- 
a/maven-projects/info/src/main/java/org/apache/graphar/info/saver/LocalYamlGraphSaver.java
+++ /dev/null
@@ -1,85 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-package org.apache.graphar.info.saver;
-
-import java.io.BufferedWriter;
-import java.io.IOException;
-import java.nio.file.FileSystems;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import org.apache.graphar.info.EdgeInfo;
-import org.apache.graphar.info.GraphInfo;
-import org.apache.graphar.info.VertexInfo;
-
-public class LocalYamlGraphSaver implements GraphSaver {
-
-    @Override
-    public void save(String path, GraphInfo graphInfo) throws IOException {
-        final Path outputPath =
-                FileSystems.getDefault()
-                        .getPath(
-                                path
-                                        + 
FileSystems.getDefault().getSeparator()
-                                        + graphInfo.getName()
-                                        + ".graph.yaml");
-        Files.createDirectories(outputPath.getParent());
-        Files.createFile(outputPath);
-        final BufferedWriter writer = Files.newBufferedWriter(outputPath);
-        writer.write(graphInfo.dump());
-        writer.close();
-
-        for (VertexInfo vertexInfo : graphInfo.getVertexInfos()) {
-            saveVertex(path, vertexInfo);
-        }
-        for (EdgeInfo edgeInfo : graphInfo.getEdgeInfos()) {
-            saveEdge(path, edgeInfo);
-        }
-    }
-
-    private void saveVertex(String path, VertexInfo vertexInfo) throws 
IOException {
-        final Path outputPath =
-                FileSystems.getDefault()
-                        .getPath(
-                                path
-                                        + 
FileSystems.getDefault().getSeparator()
-                                        + vertexInfo.getType()
-                                        + ".vertex.yaml");
-        Files.createDirectories(outputPath.getParent());
-        Files.createFile(outputPath);
-        final BufferedWriter writer = Files.newBufferedWriter(outputPath);
-        writer.write(vertexInfo.dump());
-        writer.close();
-    }
-
-    private void saveEdge(String path, EdgeInfo edgeInfo) throws IOException {
-        final Path outputPath =
-                FileSystems.getDefault()
-                        .getPath(
-                                path
-                                        + 
FileSystems.getDefault().getSeparator()
-                                        + edgeInfo.getConcat()
-                                        + ".edge.yaml");
-        Files.createDirectories(outputPath.getParent());
-        Files.createFile(outputPath);
-        final BufferedWriter writer = Files.newBufferedWriter(outputPath);
-        writer.write(edgeInfo.dump());
-        writer.close();
-    }
-}
diff --git 
a/maven-projects/info/src/main/java/org/apache/graphar/info/loader/GraphLoader.java
 
b/maven-projects/info/src/main/java/org/apache/graphar/info/saver/impl/LocalFileSystemYamlGraphSaver.java
similarity index 50%
rename from 
maven-projects/info/src/main/java/org/apache/graphar/info/loader/GraphLoader.java
rename to 
maven-projects/info/src/main/java/org/apache/graphar/info/saver/impl/LocalFileSystemYamlGraphSaver.java
index b21238c1..4c49c3af 100644
--- 
a/maven-projects/info/src/main/java/org/apache/graphar/info/loader/GraphLoader.java
+++ 
b/maven-projects/info/src/main/java/org/apache/graphar/info/saver/impl/LocalFileSystemYamlGraphSaver.java
@@ -17,12 +17,29 @@
  * under the License.
  */
 
-package org.apache.graphar.info.loader;
+package org.apache.graphar.info.saver.impl;
 
 import java.io.IOException;
-import org.apache.graphar.info.GraphInfo;
+import java.io.Writer;
+import java.net.URI;
+import java.nio.file.FileSystems;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardOpenOption;
+import org.apache.graphar.info.saver.BaseGraphInfoSaver;
 
-@FunctionalInterface
-public interface GraphLoader {
-    public GraphInfo load(String graphYamlPath) throws IOException;
+public class LocalFileSystemYamlGraphSaver extends BaseGraphInfoSaver {
+
+    public Writer writeYaml(URI uri) throws IOException {
+        Path outputPath = FileSystems.getDefault().getPath(uri.toString());
+        Path parentDir = outputPath.getParent();
+        if (parentDir != null) {
+            Files.createDirectories(parentDir);
+        }
+        return Files.newBufferedWriter(
+                outputPath,
+                StandardOpenOption.CREATE,
+                StandardOpenOption.TRUNCATE_EXISTING,
+                StandardOpenOption.WRITE);
+    }
 }
diff --git 
a/maven-projects/info/src/main/java/org/apache/graphar/info/type/FileType.java 
b/maven-projects/info/src/main/java/org/apache/graphar/info/type/FileType.java
index b45038b8..09e5bb19 100644
--- 
a/maven-projects/info/src/main/java/org/apache/graphar/info/type/FileType.java
+++ 
b/maven-projects/info/src/main/java/org/apache/graphar/info/type/FileType.java
@@ -24,6 +24,19 @@ public enum FileType {
     PARQUET,
     ORC;
 
+    public String toString() {
+        switch (this) {
+            case CSV:
+                return "csv";
+            case PARQUET:
+                return "parquet";
+            case ORC:
+                return "orc";
+            default:
+                throw new IllegalArgumentException("Unknown file type: " + 
this);
+        }
+    }
+
     public static FileType fromString(String fileType) {
         if (fileType == null) {
             return null;
diff --git 
a/maven-projects/info/src/main/java/org/apache/graphar/info/yaml/GraphYaml.java 
b/maven-projects/info/src/main/java/org/apache/graphar/info/yaml/GraphYaml.java
index bf665dcd..f5353967 100644
--- 
a/maven-projects/info/src/main/java/org/apache/graphar/info/yaml/GraphYaml.java
+++ 
b/maven-projects/info/src/main/java/org/apache/graphar/info/yaml/GraphYaml.java
@@ -19,6 +19,7 @@
 
 package org.apache.graphar.info.yaml;
 
+import java.net.URI;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Optional;
@@ -77,6 +78,10 @@ public class GraphYaml {
     }
 
     public GraphYaml(GraphInfo graphInfo) {
+        this(null, graphInfo);
+    }
+
+    public GraphYaml(URI graphInfoStoreUri, GraphInfo graphInfo) {
         this.name = graphInfo.getName();
         this.prefix = graphInfo.getPrefix();
         this.version =
@@ -86,11 +91,27 @@ public class GraphYaml {
                         .orElse(null);
         this.vertices =
                 graphInfo.getVertexInfos().stream()
-                        .map(vertexInfo -> vertexInfo.getType() + 
".vertex.yaml")
+                        .map(
+                                vertexInfo -> {
+                                    URI storeUri = 
graphInfo.getStoreUri(vertexInfo);
+                                    if (graphInfoStoreUri != null) {
+                                        storeUri =
+                                                
graphInfoStoreUri.resolve(".").relativize(storeUri);
+                                    }
+                                    return storeUri.toString();
+                                })
                         .collect(Collectors.toList());
         this.edges =
                 graphInfo.getEdgeInfos().stream()
-                        .map(edgeInfo -> edgeInfo.getConcat() + ".edge.yaml")
+                        .map(
+                                edgeInfo -> {
+                                    URI storeUri = 
graphInfo.getStoreUri(edgeInfo);
+                                    if (graphInfoStoreUri != null) {
+                                        storeUri =
+                                                
graphInfoStoreUri.resolve(".").relativize(storeUri);
+                                    }
+                                    return storeUri.toString();
+                                })
                         .collect(Collectors.toList());
         this.version =
                 Optional.of(graphInfo)
diff --git 
a/maven-projects/info/src/test/java/org/apache/graphar/info/BaseFileSystemTest.java
 
b/maven-projects/info/src/test/java/org/apache/graphar/info/BaseFileSystemTest.java
index 37c39230..84c46389 100644
--- 
a/maven-projects/info/src/test/java/org/apache/graphar/info/BaseFileSystemTest.java
+++ 
b/maven-projects/info/src/test/java/org/apache/graphar/info/BaseFileSystemTest.java
@@ -88,7 +88,7 @@ public abstract class BaseFileSystemTest {
      * content before creating the directory.
      */
     protected String createCleanTestDirectory(String subdirectoryName) {
-        String testDir = TEST_OUTPUT_DIR + "/" + subdirectoryName;
+        String testDir = TEST_OUTPUT_DIR + subdirectoryName;
         cleanupDirectory(testDir);
 
         try {
diff --git 
a/maven-projects/info/src/test/java/org/apache/graphar/info/GraphInfoSaverTest.java
 
b/maven-projects/info/src/test/java/org/apache/graphar/info/GraphInfoSaverTest.java
new file mode 100644
index 00000000..0c37f96e
--- /dev/null
+++ 
b/maven-projects/info/src/test/java/org/apache/graphar/info/GraphInfoSaverTest.java
@@ -0,0 +1,210 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.graphar.info;
+
+import java.net.URI;
+import java.nio.file.FileSystems;
+import java.util.ArrayList;
+import java.util.List;
+import 
org.apache.graphar.info.loader.impl.LocalFileSystemStringGraphInfoLoader;
+import org.apache.graphar.info.saver.GraphInfoSaver;
+import org.apache.graphar.info.saver.impl.LocalFileSystemYamlGraphSaver;
+import org.apache.graphar.info.yaml.EdgeYaml;
+import org.apache.graphar.info.yaml.GraphYaml;
+import org.apache.graphar.info.yaml.VertexYaml;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.yaml.snakeyaml.LoaderOptions;
+import org.yaml.snakeyaml.Yaml;
+import org.yaml.snakeyaml.constructor.Constructor;
+
+public class GraphInfoSaverTest extends BaseFileSystemTest {
+
+    private String testSaveDirectory;
+    private GraphInfoSaver graphInfoSaver;
+    private GraphInfo testGraphInfo;
+
+    @Before
+    public void setUp() {
+        verifyTestPrerequisites();
+        testSaveDirectory = createCleanTestDirectory("ldbc_sample");
+        graphInfoSaver = new LocalFileSystemYamlGraphSaver();
+        testGraphInfo = TestDataFactory.createSampleGraphInfo();
+    }
+
+    @After
+    public void tearDown() {
+        // Test data will be preserved for debugging - cleanup happens before 
next test run
+        System.out.println("Test data saved in: " + testSaveDirectory);
+    }
+
+    @Test
+    public void testDump() {
+        List<VertexInfo> vertexInfos = new ArrayList<>();
+        for (VertexInfo vertexInfo : testGraphInfo.getVertexInfos()) {
+            String vertexYamlString = vertexInfo.dump();
+            Yaml vertexYamlLoader =
+                    new Yaml(new Constructor(VertexYaml.class, new 
LoaderOptions()));
+            VertexYaml vertexYaml = vertexYamlLoader.load(vertexYamlString);
+            VertexInfo vertexInfoFromYaml = 
TestUtil.buildVertexInfoFromYaml(vertexYaml);
+            vertexInfos.add(vertexInfoFromYaml);
+        }
+        List<EdgeInfo> edgeInfos = new ArrayList<>();
+        for (EdgeInfo edgeInfo : testGraphInfo.getEdgeInfos()) {
+            String edgeYamlString = edgeInfo.dump();
+            Yaml edgeYamlLoader = new Yaml(new Constructor(EdgeYaml.class, new 
LoaderOptions()));
+            EdgeYaml EdgeYaml = edgeYamlLoader.load(edgeYamlString);
+            EdgeInfo EdgeInfoFromYaml = 
TestUtil.buildEdgeInfoFromYaml(EdgeYaml);
+            edgeInfos.add(EdgeInfoFromYaml);
+        }
+        String testGraphInfoString = testGraphInfo.dump();
+        Yaml GraphYamlLoader = new Yaml(new Constructor(GraphYaml.class, new 
LoaderOptions()));
+        GraphYaml graphYaml = GraphYamlLoader.load(testGraphInfoString);
+        GraphInfo graphInfoFromYaml =
+                new GraphInfo(
+                        graphYaml.getName(),
+                        vertexInfos,
+                        edgeInfos,
+                        graphYaml.getPrefix(),
+                        graphYaml.getVersion());
+        Assert.assertTrue(TestVerificationUtils.equalsGraphInfo(testGraphInfo, 
graphInfoFromYaml));
+    }
+
+    @Test
+    public void testDumpWithBaseUri() {
+        GraphInfo dumpTestGraphInfo = TestDataFactory.createSampleGraphInfo();
+        dumpTestGraphInfo.setStoreUri(
+                dumpTestGraphInfo.getVertexInfos().get(0),
+                URI.create("/tmp/verticesInfos/personInfo.vertex.yaml"));
+        String dumpTestGraphInfoString = 
dumpTestGraphInfo.dump(URI.create("/tmp/GraphInfos/"));
+        Yaml GraphYamlLoader = new Yaml(new Constructor(GraphYaml.class, new 
LoaderOptions()));
+        GraphYaml graphYaml = GraphYamlLoader.load(dumpTestGraphInfoString);
+        Assert.assertEquals(
+                "/tmp/verticesInfos/personInfo.vertex.yaml", 
graphYaml.getVertices().get(0));
+        dumpTestGraphInfo.setStoreUri(
+                dumpTestGraphInfo.getVertexInfos().get(0),
+                URI.create("/tmp/verticesInfos/personInfo.vertex.yaml"));
+        dumpTestGraphInfoString = 
dumpTestGraphInfo.dump(URI.create("/tmp/verticesInfos/"));
+        GraphYamlLoader = new Yaml(new Constructor(GraphYaml.class, new 
LoaderOptions()));
+        graphYaml = GraphYamlLoader.load(dumpTestGraphInfoString);
+        Assert.assertEquals("personInfo.vertex.yaml", 
graphYaml.getVertices().get(0));
+    }
+
+    @Test
+    public void testSave() {
+        try {
+            URI graphInfoUri =
+                    URI.create(
+                            testSaveDirectory
+                                    + FileSystems.getDefault().getSeparator()
+                                    + testGraphInfo.getName()
+                                    + ".graph.yaml");
+            graphInfoSaver.save(graphInfoUri, testGraphInfo);
+            TestVerificationUtils.verifyGraphInfoFilesSaved(testSaveDirectory, 
testGraphInfo);
+            LocalFileSystemStringGraphInfoLoader graphInfoLoader =
+                    new LocalFileSystemStringGraphInfoLoader();
+            GraphInfo graphInfo = graphInfoLoader.loadGraphInfo(graphInfoUri);
+            TestVerificationUtils.equalsGraphInfo(testGraphInfo, graphInfo);
+        } catch (Exception e) {
+            throw new RuntimeException("Failed to save graph info", e);
+        }
+    }
+
+    @Test
+    public void testSaveMinimalGraph() {
+        GraphInfo minimalGraph = TestDataFactory.createMinimalGraphInfo();
+        try {
+            URI graphInfoUri =
+                    URI.create(
+                            testSaveDirectory
+                                    + FileSystems.getDefault().getSeparator()
+                                    + minimalGraph.getName()
+                                    + ".graph.yaml");
+            graphInfoSaver.save(graphInfoUri, minimalGraph);
+            TestVerificationUtils.verifyGraphFileExists(testSaveDirectory, 
minimalGraph);
+            TestVerificationUtils.verifyVertexFilesExist(testSaveDirectory, 
minimalGraph);
+            LocalFileSystemStringGraphInfoLoader graphInfoLoader =
+                    new LocalFileSystemStringGraphInfoLoader();
+            GraphInfo graphInfo = graphInfoLoader.loadGraphInfo(graphInfoUri);
+            TestVerificationUtils.equalsGraphInfo(minimalGraph, graphInfo);
+            // No edge files expected for minimal graph
+        } catch (Exception e) {
+            throw new RuntimeException("Failed to save minimal graph info", e);
+        }
+    }
+
+    @Test
+    public void testSaveGraph2DiffPath() {
+        for (VertexInfo vertexInfo : testGraphInfo.getVertexInfos()) {
+            testGraphInfo.setStoreUri(
+                    vertexInfo,
+                    URI.create(testSaveDirectory + "/test_vertices/")
+                            .resolve(testGraphInfo.getStoreUri(vertexInfo)));
+        }
+
+        for (EdgeInfo edgeInfo : testGraphInfo.getEdgeInfos()) {
+            testGraphInfo.setStoreUri(
+                    edgeInfo,
+                    URI.create(testSaveDirectory + "/test_edges/")
+                            .resolve(testGraphInfo.getStoreUri(edgeInfo)));
+        }
+        try {
+            URI graphInfoUri =
+                    URI.create(
+                            testSaveDirectory
+                                    + FileSystems.getDefault().getSeparator()
+                                    + testGraphInfo.getName()
+                                    + ".graph.yaml");
+            graphInfoSaver.save(graphInfoUri, testGraphInfo);
+            TestVerificationUtils.verifyGraphFileExists(testSaveDirectory, 
testGraphInfo);
+            TestVerificationUtils.verifyVertexFilesExist(
+                    testSaveDirectory + "/test_vertices", testGraphInfo);
+            TestVerificationUtils.verifyEdgeFilesExist(
+                    testSaveDirectory + "/test_edges", testGraphInfo);
+            // No edge files expected for minimal graph
+            LocalFileSystemStringGraphInfoLoader graphInfoLoader =
+                    new LocalFileSystemStringGraphInfoLoader();
+            GraphInfo graphInfo = graphInfoLoader.loadGraphInfo(graphInfoUri);
+            TestVerificationUtils.equalsGraphInfo(testGraphInfo, graphInfo);
+        } catch (Exception e) {
+            throw new RuntimeException("Failed to save test graph info", e);
+        }
+    }
+
+    @Test
+    public void testSaveDirectoryCreation() {
+        String nestedDir = testSaveDirectory + "/nested/deep/directory";
+        try {
+            URI graphInfoUri =
+                    URI.create(
+                            nestedDir
+                                    + FileSystems.getDefault().getSeparator()
+                                    + testGraphInfo.getName()
+                                    + ".graph.yaml");
+            graphInfoSaver.save(graphInfoUri, testGraphInfo);
+            TestVerificationUtils.verifyDirectoryHasFiles(nestedDir);
+            TestVerificationUtils.verifyGraphInfoFilesSaved(nestedDir, 
testGraphInfo);
+        } catch (Exception e) {
+            throw new RuntimeException("Failed to save to nested directory", 
e);
+        }
+    }
+}
diff --git 
a/maven-projects/info/src/test/java/org/apache/graphar/info/GraphInfoUriTest.java
 
b/maven-projects/info/src/test/java/org/apache/graphar/info/GraphInfoUriTest.java
index 2607376f..78fb5616 100644
--- 
a/maven-projects/info/src/test/java/org/apache/graphar/info/GraphInfoUriTest.java
+++ 
b/maven-projects/info/src/test/java/org/apache/graphar/info/GraphInfoUriTest.java
@@ -20,8 +20,6 @@
 package org.apache.graphar.info;
 
 import java.net.URI;
-import org.apache.graphar.info.loader.BaseGraphInfoLoader;
-import 
org.apache.graphar.info.loader.impl.LocalFileSystemStringGraphInfoLoader;
 import org.apache.graphar.info.yaml.VertexYaml;
 import org.junit.Assert;
 import org.junit.Test;
@@ -35,8 +33,7 @@ public class GraphInfoUriTest {
     public void testBaseGraphInfo() {
         Yaml vertexYamlLoader = new Yaml(new Constructor(VertexYaml.class, new 
LoaderOptions()));
         VertexYaml vertexYaml = 
vertexYamlLoader.load(TestUtil.getBaseGraphInfoYaml());
-        BaseGraphInfoLoader baseGraphInfoLoader = new 
LocalFileSystemStringGraphInfoLoader();
-        VertexInfo vertexInfo = 
baseGraphInfoLoader.buildVertexInfoFromGraphYaml(vertexYaml);
+        VertexInfo vertexInfo = TestUtil.buildVertexInfoFromYaml(vertexYaml);
         Assert.assertEquals(URI.create("vertex/person/"), 
vertexInfo.getBaseUri());
         // absolute paths
         Assert.assertEquals(
@@ -52,8 +49,7 @@ public class GraphInfoUriTest {
     public void testS3GraphInfo() {
         Yaml vertexYamlLoader = new Yaml(new Constructor(VertexYaml.class, new 
LoaderOptions()));
         VertexYaml vertexYaml = 
vertexYamlLoader.load(TestUtil.getS3GraphInfoYaml());
-        BaseGraphInfoLoader baseGraphInfoLoader = new 
LocalFileSystemStringGraphInfoLoader();
-        VertexInfo vertexInfo = 
baseGraphInfoLoader.buildVertexInfoFromGraphYaml(vertexYaml);
+        VertexInfo vertexInfo = TestUtil.buildVertexInfoFromYaml(vertexYaml);
         Assert.assertEquals(URI.create("s3://graphar/vertex/person/"), 
vertexInfo.getBaseUri());
         // absolute paths
         Assert.assertEquals(
@@ -69,8 +65,7 @@ public class GraphInfoUriTest {
     public void testHdfsGraphInfo() {
         Yaml vertexYamlLoader = new Yaml(new Constructor(VertexYaml.class, new 
LoaderOptions()));
         VertexYaml vertexYaml = 
vertexYamlLoader.load(TestUtil.getHdfsGraphInfoYaml());
-        BaseGraphInfoLoader baseGraphInfoLoader = new 
LocalFileSystemStringGraphInfoLoader();
-        VertexInfo vertexInfo = 
baseGraphInfoLoader.buildVertexInfoFromGraphYaml(vertexYaml);
+        VertexInfo vertexInfo = TestUtil.buildVertexInfoFromYaml(vertexYaml);
         Assert.assertEquals(URI.create("hdfs://graphar/vertex/person/"), 
vertexInfo.getBaseUri());
         // absolute paths
         Assert.assertEquals(
@@ -86,8 +81,7 @@ public class GraphInfoUriTest {
     public void testFileGraphInfo() {
         Yaml vertexYamlLoader = new Yaml(new Constructor(VertexYaml.class, new 
LoaderOptions()));
         VertexYaml vertexYaml = 
vertexYamlLoader.load(TestUtil.getFileGraphInfoYaml());
-        BaseGraphInfoLoader baseGraphInfoLoader = new 
LocalFileSystemStringGraphInfoLoader();
-        VertexInfo vertexInfo = 
baseGraphInfoLoader.buildVertexInfoFromGraphYaml(vertexYaml);
+        VertexInfo vertexInfo = TestUtil.buildVertexInfoFromYaml(vertexYaml);
         Assert.assertEquals(URI.create("file:///graphar/vertex/person/"), 
vertexInfo.getBaseUri());
         // absolute paths
         Assert.assertEquals(
diff --git 
a/maven-projects/info/src/test/java/org/apache/graphar/info/GraphSaverTest.java 
b/maven-projects/info/src/test/java/org/apache/graphar/info/GraphSaverTest.java
deleted file mode 100644
index 928d6e70..00000000
--- 
a/maven-projects/info/src/test/java/org/apache/graphar/info/GraphSaverTest.java
+++ /dev/null
@@ -1,82 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-package org.apache.graphar.info;
-
-import org.apache.graphar.info.saver.GraphSaver;
-import org.apache.graphar.info.saver.LocalYamlGraphSaver;
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-
-public class GraphSaverTest extends BaseFileSystemTest {
-
-    private String testSaveDirectory;
-    private GraphSaver graphSaver;
-    private GraphInfo testGraphInfo;
-
-    @Before
-    public void setUp() {
-        verifyTestPrerequisites();
-        testSaveDirectory = createCleanTestDirectory("ldbc_sample");
-        graphSaver = new LocalYamlGraphSaver();
-        testGraphInfo = TestDataFactory.createSampleGraphInfo();
-    }
-
-    @After
-    public void tearDown() {
-        // Test data will be preserved for debugging - cleanup happens before 
next test run
-        System.out.println("Test data saved in: " + testSaveDirectory);
-    }
-
-    @Test
-    public void testSave() {
-        try {
-            graphSaver.save(testSaveDirectory, testGraphInfo);
-            TestVerificationUtils.verifyGraphInfoFilesSaved(testSaveDirectory, 
testGraphInfo);
-        } catch (Exception e) {
-            throw new RuntimeException("Failed to save graph info", e);
-        }
-    }
-
-    @Test
-    public void testSaveMinimalGraph() {
-        GraphInfo minimalGraph = TestDataFactory.createMinimalGraphInfo();
-        try {
-            graphSaver.save(testSaveDirectory, minimalGraph);
-            TestVerificationUtils.verifyGraphFileExists(testSaveDirectory, 
minimalGraph);
-            TestVerificationUtils.verifyVertexFilesExist(testSaveDirectory, 
minimalGraph);
-            // No edge files expected for minimal graph
-        } catch (Exception e) {
-            throw new RuntimeException("Failed to save minimal graph info", e);
-        }
-    }
-
-    @Test
-    public void testSaveDirectoryCreation() {
-        String nestedDir = testSaveDirectory + "/nested/deep/directory";
-        try {
-            graphSaver.save(nestedDir, testGraphInfo);
-            TestVerificationUtils.verifyDirectoryHasFiles(nestedDir);
-            TestVerificationUtils.verifyGraphInfoFilesSaved(nestedDir, 
testGraphInfo);
-        } catch (Exception e) {
-            throw new RuntimeException("Failed to save to nested directory", 
e);
-        }
-    }
-}
diff --git 
a/maven-projects/info/src/test/java/org/apache/graphar/info/TestDataFactory.java
 
b/maven-projects/info/src/test/java/org/apache/graphar/info/TestDataFactory.java
index 1c6cffd0..b2943488 100644
--- 
a/maven-projects/info/src/test/java/org/apache/graphar/info/TestDataFactory.java
+++ 
b/maven-projects/info/src/test/java/org/apache/graphar/info/TestDataFactory.java
@@ -38,7 +38,11 @@ public class TestDataFactory {
         VertexInfo personVertex = createPersonVertexInfo();
         EdgeInfo knowsEdge = createKnowsEdgeInfo();
         return new GraphInfo(
-                "ldbc_sample", List.of(personVertex), List.of(knowsEdge), "", 
"gar/v1");
+                "ldbc_sample",
+                List.of(personVertex),
+                List.of(knowsEdge),
+                "file:///test_path",
+                "gar/v1");
     }
 
     /** Creates a person vertex info for testing. */
@@ -48,11 +52,11 @@ public class TestDataFactory {
         Property lastName = createProperty("lastName", DataType.STRING, false, 
false);
         Property gender = createProperty("gender", DataType.STRING, false, 
true);
 
-        PropertyGroup idGroup = createPropertyGroup(List.of(id), FileType.CSV, 
"id/");
+        PropertyGroup idGroup = createPropertyGroup(List.of(id), 
FileType.PARQUET, "id/");
         PropertyGroup nameGroup =
                 createPropertyGroup(
                         List.of(firstName, lastName, gender),
-                        FileType.CSV,
+                        FileType.ORC,
                         "firstName_lastName_gender/");
 
         return new VertexInfo(
@@ -113,6 +117,6 @@ public class TestDataFactory {
         Property id = createIdProperty();
         PropertyGroup idGroup = createPropertyGroup(List.of(id), FileType.CSV, 
"id/");
         VertexInfo vertex = new VertexInfo("test", 100, List.of(idGroup), 
"vertex/test/", "gar/v1");
-        return new GraphInfo("test_graph", List.of(vertex), List.of(), "", 
"gar/v1");
+        return new GraphInfo("test_graph", List.of(vertex), List.of(), "/tmp", 
"gar/v1");
     }
 }
diff --git 
a/maven-projects/info/src/test/java/org/apache/graphar/info/TestUtil.java 
b/maven-projects/info/src/test/java/org/apache/graphar/info/TestUtil.java
index e1ebf6f6..880c6005 100644
--- a/maven-projects/info/src/test/java/org/apache/graphar/info/TestUtil.java
+++ b/maven-projects/info/src/test/java/org/apache/graphar/info/TestUtil.java
@@ -21,10 +21,15 @@ package org.apache.graphar.info;
 
 import java.net.URI;
 import java.util.List;
+import java.util.stream.Collectors;
 import org.apache.graphar.info.loader.GraphInfoLoader;
 import org.apache.graphar.info.type.AdjListType;
 import org.apache.graphar.info.type.DataType;
 import org.apache.graphar.info.type.FileType;
+import org.apache.graphar.info.yaml.AdjacentListYaml;
+import org.apache.graphar.info.yaml.EdgeYaml;
+import org.apache.graphar.info.yaml.PropertyGroupYaml;
+import org.apache.graphar.info.yaml.VertexYaml;
 
 public class TestUtil {
     private static String GAR_TEST_DATA = null;
@@ -51,6 +56,36 @@ public class TestUtil {
         return URI.create(getLdbcSampleGraphPath());
     }
 
+    public static VertexInfo buildVertexInfoFromYaml(VertexYaml vertexYaml) {
+        return new VertexInfo(
+                vertexYaml.getType(),
+                vertexYaml.getChunk_size(),
+                vertexYaml.getProperty_groups().stream()
+                        .map(PropertyGroupYaml::toPropertyGroup)
+                        .collect(Collectors.toList()),
+                vertexYaml.getPrefix(),
+                vertexYaml.getVersion());
+    }
+
+    public static EdgeInfo buildEdgeInfoFromYaml(EdgeYaml edgeYaml) {
+        return new EdgeInfo(
+                edgeYaml.getSrc_type(),
+                edgeYaml.getEdge_type(),
+                edgeYaml.getDst_type(),
+                edgeYaml.getChunk_size(),
+                edgeYaml.getSrc_chunk_size(),
+                edgeYaml.getDst_chunk_size(),
+                edgeYaml.isDirected(),
+                edgeYaml.getPrefix(),
+                edgeYaml.getVersion(),
+                edgeYaml.getAdj_lists().stream()
+                        .map(AdjacentListYaml::toAdjacentList)
+                        .collect(Collectors.toUnmodifiableList()),
+                edgeYaml.getProperty_groups().stream()
+                        .map(PropertyGroupYaml::toPropertyGroup)
+                        .collect(Collectors.toList()));
+    }
+
     public static final AdjacentList orderedBySource =
             new AdjacentList(AdjListType.ordered_by_source, FileType.CSV, 
"ordered_by_source/");
     public static final AdjacentList orderedByDest =
diff --git 
a/maven-projects/info/src/test/java/org/apache/graphar/info/TestVerificationUtils.java
 
b/maven-projects/info/src/test/java/org/apache/graphar/info/TestVerificationUtils.java
index c8c6bfdb..fe09e6e6 100644
--- 
a/maven-projects/info/src/test/java/org/apache/graphar/info/TestVerificationUtils.java
+++ 
b/maven-projects/info/src/test/java/org/apache/graphar/info/TestVerificationUtils.java
@@ -20,6 +20,9 @@
 package org.apache.graphar.info;
 
 import java.io.File;
+import java.util.List;
+import java.util.Map;
+import org.apache.graphar.info.type.AdjListType;
 import org.junit.Assert;
 
 /**
@@ -138,4 +141,302 @@ public class TestVerificationUtils {
         Assert.assertNotNull("Directory should be readable: " + directoryPath, 
files);
         Assert.assertTrue("Directory should contain files: " + directoryPath, 
files.length > 0);
     }
+
+    /**
+     * Compares two GraphInfo objects for equality, including their VertexInfo 
and EdgeInfo objects.
+     *
+     * @param expected the expected GraphInfo
+     * @param actual the actual GraphInfo
+     * @return true if both GraphInfo objects are equal, false otherwise
+     */
+    public static boolean equalsGraphInfo(GraphInfo expected, GraphInfo 
actual) {
+        if (expected == actual) {
+            return true;
+        }
+        if (expected == null || actual == null) {
+            return false;
+        }
+
+        Assert.assertEquals("GraphInfo name mismatch", expected.getName(), 
actual.getName());
+        Assert.assertEquals(
+                "GraphInfo baseUri mismatch", expected.getBaseUri(), 
actual.getBaseUri());
+        Assert.assertEquals(
+                "GraphInfo version mismatch",
+                expected.getVersion().toString(),
+                actual.getVersion().toString());
+        Assert.assertTrue(
+                "VertexInfo list mismatch",
+                equalsVertexInfoList(expected.getVertexInfos(), 
actual.getVertexInfos()));
+        Assert.assertTrue(
+                "EdgeInfo list mismatch",
+                equalsEdgeInfoList(expected.getEdgeInfos(), 
actual.getEdgeInfos()));
+        return true;
+    }
+
+    /**
+     * Compares two lists of VertexInfo objects for equality.
+     *
+     * @param expected the expected list of VertexInfo objects
+     * @param actual the actual list of VertexInfo objects
+     * @return true if both lists are equal, false otherwise
+     */
+    public static boolean equalsVertexInfoList(List<VertexInfo> expected, 
List<VertexInfo> actual) {
+        if (expected == actual) {
+            return true;
+        }
+        if (expected == null || actual == null || expected.size() != 
actual.size()) {
+            return false;
+        }
+
+        for (int i = 0; i < expected.size(); i++) {
+            Assert.assertTrue(
+                    "VertexInfo at index " + i + " mismatch",
+                    equalsVertexInfo(expected.get(i), actual.get(i)));
+        }
+        return true;
+    }
+
+    /**
+     * Compares two lists of EdgeInfo objects for equality.
+     *
+     * @param expected the expected list of EdgeInfo objects
+     * @param actual the actual list of EdgeInfo objects
+     * @return true if both lists are equal, false otherwise
+     */
+    public static boolean equalsEdgeInfoList(List<EdgeInfo> expected, 
List<EdgeInfo> actual) {
+        if (expected == actual) {
+            return true;
+        }
+        if (expected == null || actual == null || expected.size() != 
actual.size()) {
+            return false;
+        }
+
+        for (int i = 0; i < expected.size(); i++) {
+            Assert.assertTrue(
+                    "EdgeInfo at index " + i + " mismatch",
+                    equalsEdgeInfo(expected.get(i), actual.get(i)));
+        }
+        return true;
+    }
+
+    /**
+     * Compares two VertexInfo objects for equality.
+     *
+     * @param expected the expected VertexInfo
+     * @param actual the actual VertexInfo
+     * @return true if both VertexInfo objects are equal, false otherwise
+     */
+    public static boolean equalsVertexInfo(VertexInfo expected, VertexInfo 
actual) {
+        if (expected == actual) {
+            return true;
+        }
+        if (expected == null || actual == null) {
+            return false;
+        }
+
+        Assert.assertEquals("VertexInfo type mismatch", expected.getType(), 
actual.getType());
+        Assert.assertEquals(
+                "VertexInfo chunk size mismatch", expected.getChunkSize(), 
actual.getChunkSize());
+        Assert.assertEquals("VertexInfo prefix mismatch", 
expected.getPrefix(), actual.getPrefix());
+        Assert.assertEquals(
+                "VertexInfo version mismatch",
+                expected.getVersion().toString(),
+                actual.getVersion().toString());
+        Assert.assertTrue(
+                "VertexInfo property groups mismatch",
+                equalsPropertyGroupList(expected.getPropertyGroups(), 
actual.getPropertyGroups()));
+        return true;
+    }
+
+    /**
+     * Compares two EdgeInfo objects for equality.
+     *
+     * @param expected the expected EdgeInfo
+     * @param actual the actual EdgeInfo
+     * @return true if both EdgeInfo objects are equal, false otherwise
+     */
+    public static boolean equalsEdgeInfo(EdgeInfo expected, EdgeInfo actual) {
+        if (expected == actual) {
+            return true;
+        }
+        if (expected == null || actual == null) {
+            return false;
+        }
+
+        Assert.assertEquals(
+                "EdgeInfo source type mismatch", expected.getSrcType(), 
actual.getSrcType());
+        Assert.assertEquals(
+                "EdgeInfo edge type mismatch", expected.getEdgeType(), 
actual.getEdgeType());
+        Assert.assertEquals(
+                "EdgeInfo destination type mismatch", expected.getDstType(), 
actual.getDstType());
+        Assert.assertEquals(
+                "EdgeInfo chunk size mismatch", expected.getChunkSize(), 
actual.getChunkSize());
+        Assert.assertEquals(
+                "EdgeInfo source chunk size mismatch",
+                expected.getSrcChunkSize(),
+                actual.getSrcChunkSize());
+        Assert.assertEquals(
+                "EdgeInfo destination chunk size mismatch",
+                expected.getDstChunkSize(),
+                actual.getDstChunkSize());
+        Assert.assertEquals(
+                "EdgeInfo directed mismatch", expected.isDirected(), 
actual.isDirected());
+        Assert.assertEquals("EdgeInfo prefix mismatch", expected.getPrefix(), 
actual.getPrefix());
+        Assert.assertEquals(
+                "EdgeInfo version mismatch",
+                expected.getVersion().toString(),
+                actual.getVersion().toString());
+        Assert.assertTrue(
+                "EdgeInfo adjacent lists mismatch",
+                equalsAdjacentListMap(expected.getAdjacentLists(), 
actual.getAdjacentLists()));
+        Assert.assertTrue(
+                "EdgeInfo property groups mismatch",
+                equalsPropertyGroupList(expected.getPropertyGroups(), 
actual.getPropertyGroups()));
+        return true;
+    }
+
+    /**
+     * Compares two PropertyGroups objects for equality.
+     *
+     * @param expected the expected list of PropertyGroup objects
+     * @param actual the actual list of PropertyGroup objects
+     * @return true if both lists are equal, false otherwise
+     */
+    private static boolean equalsPropertyGroupList(
+            List<PropertyGroup> expected, List<PropertyGroup> actual) {
+        if (expected == actual) {
+            return true;
+        }
+        if (expected == null || actual == null || expected.size() != 
actual.size()) {
+            return false;
+        }
+
+        for (int i = 0; i < expected.size(); i++) {
+            Assert.assertTrue(
+                    "PropertyGroup at index " + i + " mismatch",
+                    equalsPropertyGroup(expected.get(i), actual.get(i)));
+        }
+        return true;
+    }
+
+    /**
+     * Compares two PropertyGroup objects for equality.
+     *
+     * @param expected the expected PropertyGroup
+     * @param actual the actual PropertyGroup
+     * @return true if both PropertyGroup objects are equal, false otherwise
+     */
+    private static boolean equalsPropertyGroup(PropertyGroup expected, 
PropertyGroup actual) {
+        if (expected == actual) {
+            return true;
+        }
+        if (expected == null || actual == null) {
+            return false;
+        }
+
+        Assert.assertEquals(
+                "PropertyGroup prefix mismatch", expected.getPrefix(), 
actual.getPrefix());
+        Assert.assertEquals(
+                "PropertyGroup file type mismatch", expected.getFileType(), 
actual.getFileType());
+        Assert.assertTrue(
+                "PropertyGroup properties mismatch",
+                equalsPropertyList(expected.getPropertyList(), 
actual.getPropertyList()));
+        return true;
+    }
+
+    /**
+     * Compares two lists of Property objects for equality.
+     *
+     * @param expected the expected list of Property objects
+     * @param actual the actual list of Property objects
+     * @return true if both lists are equal, false otherwise
+     */
+    private static boolean equalsPropertyList(List<Property> expected, 
List<Property> actual) {
+        if (expected == actual) {
+            return true;
+        }
+        if (expected == null || actual == null || expected.size() != 
actual.size()) {
+            return false;
+        }
+
+        for (int i = 0; i < expected.size(); i++) {
+            Assert.assertTrue(
+                    "Property at index " + i + " mismatch",
+                    equalsProperty(expected.get(i), actual.get(i)));
+        }
+        return true;
+    }
+
+    /**
+     * Compares two Property objects for equality.
+     *
+     * @param expected the expected Property
+     * @param actual the actual Property
+     * @return true if both Property objects are equal, false otherwise
+     */
+    private static boolean equalsProperty(Property expected, Property actual) {
+        if (expected == actual) {
+            return true;
+        }
+        if (expected == null || actual == null) {
+            return false;
+        }
+
+        Assert.assertEquals("Property name mismatch", expected.getName(), 
actual.getName());
+        Assert.assertEquals(
+                "Property primary flag mismatch", expected.isPrimary(), 
actual.isPrimary());
+        Assert.assertEquals(
+                "Property nullable flag mismatch", expected.isNullable(), 
actual.isNullable());
+        Assert.assertEquals(
+                "Property data type mismatch", expected.getDataType(), 
actual.getDataType());
+        return true;
+    }
+
+    /**
+     * Compares two maps of AdjacentList objects for equality.
+     *
+     * @param expected the expected map of AdjacentList objects
+     * @param actual the actual map of AdjacentList objects
+     * @return true if both maps are equal, false otherwise
+     */
+    private static boolean equalsAdjacentListMap(
+            Map<AdjListType, AdjacentList> expected, Map<AdjListType, 
AdjacentList> actual) {
+        if (expected == actual) {
+            return true;
+        }
+        if (expected == null || actual == null || expected.size() != 
actual.size()) {
+            return false;
+        }
+
+        for (Map.Entry<AdjListType, AdjacentList> entry : expected.entrySet()) 
{
+            AdjacentList actualValue = actual.get(entry.getKey());
+            Assert.assertTrue(
+                    "AdjacentList for type " + entry.getKey() + " mismatch",
+                    equalsAdjacentList(entry.getValue(), actualValue));
+        }
+        return true;
+    }
+
+    /**
+     * Compares two AdjacentList objects for equality.
+     *
+     * @param expected the expected AdjacentList
+     * @param actual the actual AdjacentList
+     * @return true if both AdjacentList objects are equal, false otherwise
+     */
+    private static boolean equalsAdjacentList(AdjacentList expected, 
AdjacentList actual) {
+        if (expected == actual) {
+            return true;
+        }
+        if (expected == null || actual == null) {
+            return false;
+        }
+
+        Assert.assertEquals("AdjacentList type mismatch", expected.getType(), 
actual.getType());
+        Assert.assertEquals(
+                "AdjacentList file type mismatch", expected.getFileType(), 
actual.getFileType());
+        Assert.assertEquals(
+                "AdjacentList prefix mismatch", expected.getPrefix(), 
actual.getPrefix());
+        return true;
+    }
 }


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to