Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 74 additions & 0 deletions include/CXXGraph/Graph/Graph_decl.h
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,39 @@ std::ostream &operator<<(std::ostream &o, const AdjacencyList<T> &adj);
/// Class that implement the Graph. ( This class is not Thread Safe )
template <typename T>
class Graph {
public:
/**
* @brief Write the graph to a binary file
* @param workingDir The parent directory of the output file
* @param fileName The output filename (without extension)
* @param writeNodeFeatures Whether to include node features
* @param writeEdgeWeights Whether to include edge weights
* @return 0 if successful, negative value on error:
* -1: Cannot open file
* -2: Write error
*/
int writeToBinaryFile(const std::string &workingDir,
const std::string &fileName,
bool writeNodeFeatures = false,
bool writeEdgeWeights = true) const;

/**
* @brief Read the graph from a binary file
* @param workingDir The parent directory of the input file
* @param fileName The input filename (without extension)
* @param readNodeFeatures Whether to read node features
* @param readEdgeWeights Whether to read edge weights
* @return 0 if successful, negative value on error:
* -1: Cannot open file
* -2: Invalid file format
* -3: Unsupported version
* -4: Read error
*/
int readFromBinaryFile(const std::string &workingDir,
const std::string &fileName,
bool readNodeFeatures = false,
bool readEdgeWeights = true);

private:
T_EdgeSet<T> edgeSet = {};
T_NodeSet<T> isolatedNodesSet = {};
Expand All @@ -104,6 +137,47 @@ class Graph {
int writeToDot(const std::string &workingDir, const std::string &OFileName,
const std::string &graphName) const;
int readFromDot(const std::string &workingDir, const std::string &fileName);

// Binary file format constants
static constexpr uint32_t BINARY_MAGIC_NUMBER = 0x47525048; // "GRPH"
static constexpr uint32_t BINARY_VERSION = 1;
static constexpr uint64_t BINARY_FLAG_HAS_NODE_FEATURES = 0x01;
static constexpr uint64_t BINARY_FLAG_HAS_EDGE_WEIGHTS = 0x02;

// Type trait to check if T is serializable to binary
template <typename U, typename = void>
struct is_binary_serializable : std::false_type {};

template <typename U>
struct is_binary_serializable<
U, std::void_t<decltype(std::declval<std::ofstream &>().write(
reinterpret_cast<const char *>(&std::declval<const U &>()),
sizeof(U)))>> : std::is_trivially_copyable<U> {};

// Helper functions for binary I/O
void writeBinaryString(std::ofstream &out, const std::string &str) const;
std::string readBinaryString(std::ifstream &in) const;

/**
* @brief Write the graph to a binary file
* @param filepath The full path to the output file
* @param writeNodeFeatures Whether to include node features
* @param writeEdgeWeights Whether to include edge weights
* @return 0 if successful, negative value on error
*/
int writeToBinary(const std::string &filepath, bool writeNodeFeatures,
bool writeEdgeWeights) const;

/**
* @brief Read the graph from a binary file
* @param filepath The full path to the input file
* @param readNodeFeatures Whether to read node features
* @param readEdgeWeights Whether to read edge weights
* @return 0 if successful, negative value on error
*/
int readFromBinary(const std::string &filepath, bool readNodeFeatures,
bool readEdgeWeights);

void recreateGraph(
std::unordered_map<std::string, std::pair<std::string, std::string>>
&edgeMap,
Expand Down
132 changes: 131 additions & 1 deletion include/CXXGraph/Graph/IO/InputOperation_impl.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,126 @@
return 0;
}

template <typename T>
int Graph<T>::readFromBinaryFile(const std::string &workingDir,
const std::string &fileName,
bool readNodeFeatures, bool readEdgeWeights) {
std::string filepath = workingDir + "/" + fileName + ".bin";
return readFromBinary(filepath, readNodeFeatures, readEdgeWeights);
}

template <typename T>
int Graph<T>::readFromBinary(const std::string &filepath, bool readNodeFeatures,
bool readEdgeWeights) {
std::ifstream in(filepath, std::ios::binary);
if (!in.is_open()) {
return -1;
}

try {
// Read and verify header
uint32_t magic;
in.read(reinterpret_cast<char *>(&magic), sizeof(magic));

Check failure

Code scanning / Eslint-9 (reported by Codacy)

Check buffer boundaries if used in a loop including recursive loops (CWE-120, CWE-20). Error

Check buffer boundaries if used in a loop including recursive loops (CWE-120, CWE-20).
if (magic != BINARY_MAGIC_NUMBER) {
return -2; // Invalid file format
}

uint32_t version;
in.read(reinterpret_cast<char *>(&version), sizeof(version));

Check failure

Code scanning / Eslint-9 (reported by Codacy)

Check buffer boundaries if used in a loop including recursive loops (CWE-120, CWE-20). Error

Check buffer boundaries if used in a loop including recursive loops (CWE-120, CWE-20).
if (version != BINARY_VERSION) {
return -3; // Unsupported version
}

uint64_t numNodes, numEdges, flags;
in.read(reinterpret_cast<char *>(&numNodes), sizeof(numNodes));

Check failure

Code scanning / Eslint-9 (reported by Codacy)

Check buffer boundaries if used in a loop including recursive loops (CWE-120, CWE-20). Error

Check buffer boundaries if used in a loop including recursive loops (CWE-120, CWE-20).
in.read(reinterpret_cast<char *>(&numEdges), sizeof(numEdges));

Check failure

Code scanning / Eslint-9 (reported by Codacy)

Check buffer boundaries if used in a loop including recursive loops (CWE-120, CWE-20). Error

Check buffer boundaries if used in a loop including recursive loops (CWE-120, CWE-20).
in.read(reinterpret_cast<char *>(&flags), sizeof(flags));

Check failure

Code scanning / Eslint-9 (reported by Codacy)

Check buffer boundaries if used in a loop including recursive loops (CWE-120, CWE-20). Error

Check buffer boundaries if used in a loop including recursive loops (CWE-120, CWE-20).

bool hasNodeFeatures = (flags & BINARY_FLAG_HAS_NODE_FEATURES) != 0;
bool hasEdgeWeights = (flags & BINARY_FLAG_HAS_EDGE_WEIGHTS) != 0;

// Read nodes
std::unordered_map<std::string, shared<Node<T>>> nodeMap;
for (uint64_t i = 0; i < numNodes; ++i) {
std::string nodeId = readBinaryString(in);

T nodeData{};
if (hasNodeFeatures && readNodeFeatures) {
uint32_t dataSize;
in.read(reinterpret_cast<char *>(&dataSize), sizeof(dataSize));

Check failure

Code scanning / Eslint-9 (reported by Codacy)

Check buffer boundaries if used in a loop including recursive loops (CWE-120, CWE-20). Error

Check buffer boundaries if used in a loop including recursive loops (CWE-120, CWE-20).

if (dataSize > 0 && is_binary_serializable<T>::value) {
in.read(reinterpret_cast<char *>(&nodeData), sizeof(T));

Check failure

Code scanning / Eslint-9 (reported by Codacy)

Check buffer boundaries if used in a loop including recursive loops (CWE-120, CWE-20). Error

Check buffer boundaries if used in a loop including recursive loops (CWE-120, CWE-20).
}
} else if (hasNodeFeatures) {
// Skip node data if present but not reading
uint32_t dataSize;
in.read(reinterpret_cast<char *>(&dataSize), sizeof(dataSize));

Check failure

Code scanning / Eslint-9 (reported by Codacy)

Check buffer boundaries if used in a loop including recursive loops (CWE-120, CWE-20). Error

Check buffer boundaries if used in a loop including recursive loops (CWE-120, CWE-20).
if (dataSize > 0) {
in.seekg(dataSize, std::ios::cur);
}
} else {
uint32_t dataSize;
in.read(reinterpret_cast<char *>(&dataSize), sizeof(dataSize));

Check failure

Code scanning / Eslint-9 (reported by Codacy)

Check buffer boundaries if used in a loop including recursive loops (CWE-120, CWE-20). Error

Check buffer boundaries if used in a loop including recursive loops (CWE-120, CWE-20).
}

auto node = std::make_shared<Node<T>>(nodeId, std::move(nodeData));
nodeMap[nodeId] = node;
}

// Read edges
for (uint64_t i = 0; i < numEdges; ++i) {
std::string edgeId = readBinaryString(in);
std::string node1Id = readBinaryString(in);
std::string node2Id = readBinaryString(in);

uint8_t edgeFlags;
in.read(reinterpret_cast<char *>(&edgeFlags), sizeof(edgeFlags));

Check failure

Code scanning / Eslint-9 (reported by Codacy)

Check buffer boundaries if used in a loop including recursive loops (CWE-120, CWE-20). Error

Check buffer boundaries if used in a loop including recursive loops (CWE-120, CWE-20).

bool isDirected = (edgeFlags & 0x01) != 0;
bool isWeighted = (edgeFlags & 0x02) != 0;

double weight = 0.0;
if (hasEdgeWeights && isWeighted) {
if (readEdgeWeights) {
in.read(reinterpret_cast<char *>(&weight), sizeof(weight));

Check failure

Code scanning / Eslint-9 (reported by Codacy)

Check buffer boundaries if used in a loop including recursive loops (CWE-120, CWE-20). Error

Check buffer boundaries if used in a loop including recursive loops (CWE-120, CWE-20).
} else {
in.seekg(sizeof(double), std::ios::cur);
}
}

auto node1 = nodeMap[node1Id];
auto node2 = nodeMap[node2Id];

shared<Edge<T>> edge;
if (isDirected) {
if (isWeighted && readEdgeWeights) {
edge = std::make_shared<DirectedWeightedEdge<T>>(edgeId, node1, node2,
weight);
} else {
edge = std::make_shared<DirectedEdge<T>>(edgeId, node1, node2);
}
} else {
if (isWeighted && readEdgeWeights) {
edge = std::make_shared<UndirectedWeightedEdge<T>>(edgeId, node1,
node2, weight);
} else {
edge = std::make_shared<UndirectedEdge<T>>(edgeId, node1, node2);
}
}

this->addEdge(edge);
}

in.close();
return 0;

Check notice on line 364 in include/CXXGraph/Graph/IO/InputOperation_impl.hpp

View check run for this annotation

codefactor.io / CodeFactor

include/CXXGraph/Graph/IO/InputOperation_impl.hpp#L364

Redundant blank line at the end of a code block should be deleted. (whitespace/blank_line)
} catch (const std::exception &e) {
in.close();
return -4;
}
}

template <typename T>

Check notice on line 371 in include/CXXGraph/Graph/IO/InputOperation_impl.hpp

View check run for this annotation

codefactor.io / CodeFactor

include/CXXGraph/Graph/IO/InputOperation_impl.hpp#L260-L371

Complex Method
int Graph<T>::readFromDot(const std::string &workingDir,
const std::string &fileName) {
Expand Down Expand Up @@ -436,5 +556,15 @@
}
}

// Helper function to read string with length prefix
template <typename T>
std::string Graph<T>::readBinaryString(std::ifstream &in) const {
uint32_t len;
in.read(reinterpret_cast<char *>(&len), sizeof(len));

Check failure

Code scanning / Eslint-9 (reported by Codacy)

Check buffer boundaries if used in a loop including recursive loops (CWE-120, CWE-20). Error

Check buffer boundaries if used in a loop including recursive loops (CWE-120, CWE-20).
std::string str(len, '\0');
in.read(&str[0], len);

Check failure

Code scanning / Eslint-9 (reported by Codacy)

Check buffer boundaries if used in a loop including recursive loops (CWE-120, CWE-20). Error

Check buffer boundaries if used in a loop including recursive loops (CWE-120, CWE-20).
return str;
}

} // namespace CXXGraph
#endif // __CXXGRAPH_INPUTOPERATION_IMPL_H__
#endif // __CXXGRAPH_INPUTOPERATION_IMPL_H__
105 changes: 104 additions & 1 deletion include/CXXGraph/Graph/IO/OutputOperation_impl.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,100 @@
return 0;
}

template <typename T>
int Graph<T>::writeToBinaryFile(const std::string &workingDir,
const std::string &fileName,
bool writeNodeFeatures,
bool writeEdgeWeights) const {
std::string filepath = workingDir + "/" + fileName + ".bin";
return writeToBinary(filepath, writeNodeFeatures, writeEdgeWeights);
}

template <typename T>
int Graph<T>::writeToBinary(const std::string &filepath, bool writeNodeFeatures,
bool writeEdgeWeights) const {
std::ofstream out(filepath, std::ios::binary);
if (!out.is_open()) {
return -1;
}

try {
// Write header
out.write(reinterpret_cast<const char *>(&BINARY_MAGIC_NUMBER),
sizeof(BINARY_MAGIC_NUMBER));
out.write(reinterpret_cast<const char *>(&BINARY_VERSION),
sizeof(BINARY_VERSION));

auto nodeSet = this->getNodeSet();
auto edgeSet = this->getEdgeSet();

uint64_t numNodes = nodeSet.size();
uint64_t numEdges = edgeSet.size();
uint64_t flags = 0;

if (writeNodeFeatures) flags |= BINARY_FLAG_HAS_NODE_FEATURES;
if (writeEdgeWeights) flags |= BINARY_FLAG_HAS_EDGE_WEIGHTS;

out.write(reinterpret_cast<const char *>(&numNodes), sizeof(numNodes));
out.write(reinterpret_cast<const char *>(&numEdges), sizeof(numEdges));
out.write(reinterpret_cast<const char *>(&flags), sizeof(flags));

// Write nodes
for (const auto &node : nodeSet) {
writeBinaryString(out, node->getUserId());

if (writeNodeFeatures) {
// For trivially copyable types, write directly
if constexpr (is_binary_serializable<T>::value) {
uint32_t dataSize = sizeof(T);
out.write(reinterpret_cast<const char *>(&dataSize),
sizeof(dataSize));
const T &data = node->getData();
out.write(reinterpret_cast<const char *>(&data), sizeof(T));
} else {
// For non-trivially copyable types, write 0 size
uint32_t dataSize = 0;
out.write(reinterpret_cast<const char *>(&dataSize),
sizeof(dataSize));
}
} else {
uint32_t dataSize = 0;
out.write(reinterpret_cast<const char *>(&dataSize), sizeof(dataSize));
}
}

// Write edges
for (const auto &edge : edgeSet) {
writeBinaryString(out, edge->getUserId());
writeBinaryString(out, edge->getNodePair().first->getUserId());
writeBinaryString(out, edge->getNodePair().second->getUserId());

uint8_t edgeFlags = 0;
if (edge->isDirected().has_value() && edge->isDirected().value()) {
edgeFlags |= 0x01;
}
if (edge->isWeighted().has_value() && edge->isWeighted().value()) {
edgeFlags |= 0x02;
}
out.write(reinterpret_cast<const char *>(&edgeFlags), sizeof(edgeFlags));

// Write weight if edge is weighted and we're saving weights
if (writeEdgeWeights && (edgeFlags & 0x02)) {
double weight =
std::dynamic_pointer_cast<const Weighted>(edge)->getWeight();
out.write(reinterpret_cast<const char *>(&weight), sizeof(weight));
}
}

out.close();
return 0;

Check notice on line 282 in include/CXXGraph/Graph/IO/OutputOperation_impl.hpp

View check run for this annotation

codefactor.io / CodeFactor

include/CXXGraph/Graph/IO/OutputOperation_impl.hpp#L282

Redundant blank line at the end of a code block should be deleted. (whitespace/blank_line)
} catch (const std::exception &e) {
out.close();
return -2;
}
}

template <typename T>

Check notice on line 289 in include/CXXGraph/Graph/IO/OutputOperation_impl.hpp

View check run for this annotation

codefactor.io / CodeFactor

include/CXXGraph/Graph/IO/OutputOperation_impl.hpp#L205-L289

Complex Method
int Graph<T>::writeToDot(const std::string &workingDir,
const std::string &OFileName,
Expand Down Expand Up @@ -284,6 +378,15 @@
}
}

// Helper function to write string with length prefix
template <typename T>
void Graph<T>::writeBinaryString(std::ofstream &out,
const std::string &str) const {
uint32_t len = static_cast<uint32_t>(str.length());
out.write(reinterpret_cast<const char *>(&len), sizeof(len));
out.write(str.c_str(), len);
}

template <typename T>
std::ostream &operator<<(std::ostream &os, const Graph<T> &graph) {
os << "Graph:\n";
Expand Down Expand Up @@ -355,4 +458,4 @@
}

} // namespace CXXGraph
#endif // __CXXGRAPH_OUTPUTOPERATION_IMPL_H__
#endif // __CXXGRAPH_OUTPUTOPERATION_IMPL_H__
Loading
Loading