From 1c3044c9488d83649c354747b06827204ab89905 Mon Sep 17 00:00:00 2001 From: Lew Rossman Date: Sun, 14 Oct 2018 11:05:15 -0400 Subject: [PATCH 1/7] Fix #211 for EN_setreport --- src/epanet.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/epanet.c b/src/epanet.c index d021313..f209da7 100644 --- a/src/epanet.c +++ b/src/epanet.c @@ -1377,9 +1377,10 @@ int DLLEXPORT EN_setreport(EN_ProjectHandle ph, char *s) { if (!p->Openflag) return set_error(p->error_handle, 102); - if (strlen(s) > MAXLINE) + if (strlen(s) >= MAXLINE) return set_error(p->error_handle, 250); strcpy(s1, s); + strcat(s1, "\n"); if (setreport(p, s1) > 0) return set_error(p->error_handle, 250); else From 1fce14efb87f19e51bf920121fcb0bc53c62c1ed Mon Sep 17 00:00:00 2001 From: Lew Rossman Date: Mon, 15 Oct 2018 11:34:25 -0400 Subject: [PATCH 2/7] Increased size of MAXMSG to 255 (#189) --- src/input1.c | 6 +++--- src/input2.c | 2 +- src/output.c | 6 +++--- src/types.h | 5 +++-- 4 files changed, 10 insertions(+), 9 deletions(-) diff --git a/src/input1.c b/src/input1.c index 3eb5585..1e59b80 100644 --- a/src/input1.c +++ b/src/input1.c @@ -104,9 +104,9 @@ void setdefaults(EN_Project *pr) time_options_t *time = &pr->time_options; out_file_t *out = &pr->out_files; - strncpy(pr->Title[0], "", MAXMSG); - strncpy(pr->Title[1], "", MAXMSG); - strncpy(pr->Title[2], "", MAXMSG); + strncpy(pr->Title[0], "", TITLELEN); + strncpy(pr->Title[1], "", TITLELEN); + strncpy(pr->Title[2], "", TITLELEN); strncpy(out->TmpDir, "", MAXFNAME); strncpy(out->TmpFname, "", MAXFNAME); strncpy(out->HydFname, "", MAXFNAME); diff --git a/src/input2.c b/src/input2.c index c40d029..717ef81 100644 --- a/src/input2.c +++ b/src/input2.c @@ -292,7 +292,7 @@ int newline(EN_Project *pr, int sect, char *line) n = (int)strlen(line); if (line[n - 1] == 10) line[n - 1] = ' '; - strncpy(pr->Title[par->Ntitle], line, MAXMSG); + strncpy(pr->Title[par->Ntitle], line, TITLELEN); par->Ntitle++; } return (0); diff --git a/src/output.c b/src/output.c index b91ab83..a9ed2a8 100644 --- a/src/output.c +++ b/src/output.c @@ -87,9 +87,9 @@ int savenetdata(EN_Project *pr) fwrite(ibuf, sizeof(INT4), 15, outFile); /* Write string variables to outFile */ - fwrite(pr->Title[0], sizeof(char), MAXMSG + 1, outFile); - fwrite(pr->Title[1], sizeof(char), MAXMSG + 1, outFile); - fwrite(pr->Title[2], sizeof(char), MAXMSG + 1, outFile); + fwrite(pr->Title[0], sizeof(char), TITLELEN + 1, outFile); + fwrite(pr->Title[1], sizeof(char), TITLELEN + 1, outFile); + fwrite(pr->Title[2], sizeof(char), TITLELEN + 1, outFile); fwrite(par->InpFname, sizeof(char), MAXFNAME + 1, outFile); fwrite(rep->Rpt2Fname, sizeof(char), MAXFNAME + 1, outFile); fwrite(qu->ChemName, sizeof(char), MAXID + 1, outFile); diff --git a/src/types.h b/src/types.h index c2350d0..7b9358d 100755 --- a/src/types.h +++ b/src/types.h @@ -48,8 +48,9 @@ typedef int INT4; #define ENGINE_VERSION 201 #define EOFMARK 0x1A /* Use 0x04 for UNIX systems */ #define MAXTITLE 3 /* Max. # title lines */ +#define TITLELEN 79 // Max. # characters in a title line #define MAXID 31 /* Max. # characters in ID name */ -#define MAXMSG 79 /* Max. # characters in message text */ +#define MAXMSG 255 /* Max. # characters in message text */ #define MAXLINE 255 /* Max. # characters read from input line */ #define MAXFNAME 259 /* Max. # characters in file name */ #define MAXTOKS 40 /* Max. items per line of input */ @@ -900,7 +901,7 @@ typedef struct EN_Project { Openflag, /// Toolkit open flag Warnflag, /// Warning flag Msg[MAXMSG+1], /// General-purpose string: errors, messages - Title[MAXTITLE][MAXMSG+1], /// Problem title + Title[MAXTITLE][TITLELEN+1],/// Project title MapFname[MAXFNAME+1]; /// Map file name error_handle_t* error_handle; //Simple error manager From 76419e0b4cacb7a8a2cfe455ee876321aef35ad6 Mon Sep 17 00:00:00 2001 From: Lew Rossman Date: Wed, 17 Oct 2018 09:05:16 -0400 Subject: [PATCH 3/7] Added EN_setnodeid and EN_setlinkid (Issue #306) --- include/epanet2.bas | 2 ++ include/epanet2.h | 18 +++++++++++++ include/epanet2.vb | 2 ++ src/epanet.c | 64 +++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 86 insertions(+) diff --git a/include/epanet2.bas b/include/epanet2.bas index cb62b96..c4516f1 100644 --- a/include/epanet2.bas +++ b/include/epanet2.bas @@ -250,7 +250,9 @@ Public Const EN_G_CURVE = 4 ' General\default curve Declare Function ENsetflowunits Lib "epanet2.dll" (ByVal code As Long) As Long Declare Function ENaddcontrol Lib "epanet2.dll" (Cindex As Long, ByVal Ctype As Long, ByVal Lindex As Long, ByVal setting As Single, ByVal Nindex As Long, ByVal Level As Single) As Long Declare Function ENsetcontrol Lib "epanet2.dll" (ByVal Cindex As Long, ByVal Ctype As Long, ByVal Lindex As Long, ByVal setting As Single, ByVal Nindex As Long, ByVal Level As Single) As Long + Declare Function ENsetnodeid Lib "epanet2.dll" (ByVal index As Long, ByVal newid As String) As Long Declare Function ENsetnodevalue Lib "epanet2.dll" (ByVal index As Long, ByVal code As Long, ByVal value As Single) As Long + Declare Function ENsetlinkid Lib "epanet2.dll" (ByVal index As Long, ByVal newid As String) As Long Declare Function ENsetlinkvalue Lib "epanet2.dll" (ByVal index As Long, ByVal code As Long, ByVal value As Single) As Long Declare Function ENaddpattern Lib "epanet2.dll" (ByVal id As String) As Long Declare Function ENsetpattern Lib "epanet2.dll" (ByVal index As Long, F As Any, ByVal N As Long) As Long diff --git a/include/epanet2.h b/include/epanet2.h index 9147912..1593783 100644 --- a/include/epanet2.h +++ b/include/epanet2.h @@ -842,6 +842,14 @@ extern "C" { @return Error code. */ int DLLEXPORT ENsetcontrol(int cindex, int ctype, int lindex, EN_API_FLOAT_TYPE setting, int nindex, EN_API_FLOAT_TYPE level); + + /** + @brief Change the ID name for a node. + @param index The index of a node. First node is index 1. + @param newid A string containing the node's new ID name. + @return Error code. + */ + int DLLEXPORT ENsetnodeid(int index, char *newid); /** @brief Set a property value for a node. @@ -852,6 +860,14 @@ extern "C" { @see EN_NodeProperty */ int DLLEXPORT ENsetnodevalue(int index, int code, EN_API_FLOAT_TYPE v); + + /** + @brief Change the ID name for a link. + @param index The index of a link. First link is index 1. + @param newid A string containing the link's new ID name. + @return Error code. + */ + int DLLEXPORT ENsetlinkid(int index, char *newid); /** @brief Set a property value for a link. @@ -1297,7 +1313,9 @@ extern "C" { int DLLEXPORT EN_addcontrol(EN_ProjectHandle ph, int *cindex, int ctype, int lindex, EN_API_FLOAT_TYPE setting, int nindex, EN_API_FLOAT_TYPE level); int DLLEXPORT EN_setcontrol(EN_ProjectHandle ph, int cindex, int ctype, int lindex, EN_API_FLOAT_TYPE setting, int nindex, EN_API_FLOAT_TYPE level); + int DLLEXPORT EN_setnodeid(EN_ProjectHandle ph, int index, char *newid); int DLLEXPORT EN_setnodevalue(EN_ProjectHandle ph, int index, int code, EN_API_FLOAT_TYPE v); + int DLLEXPORT EN_setlinkid(EN_ProjectHandle ph, int index, char *newid); int DLLEXPORT EN_setlinkvalue(EN_ProjectHandle ph, int index, int code, EN_API_FLOAT_TYPE v); int DLLEXPORT EN_addpattern(EN_ProjectHandle ph, char *id); int DLLEXPORT EN_setpattern(EN_ProjectHandle ph, int index, EN_API_FLOAT_TYPE *f, int len); diff --git a/include/epanet2.vb b/include/epanet2.vb index 0af9f70..5898dfd 100644 --- a/include/epanet2.vb +++ b/include/epanet2.vb @@ -227,7 +227,9 @@ Public Const EN_CUSTOM = 2 ' user-defined custom curve Declare Function ENgetversion Lib "epanet2.dll" (ByRef Value As Int32) As Int32 Declare Function ENsetcontrol Lib "epanet2.dll" (ByVal Cindex As Int32, ByVal CtlType As Int32, ByVal Lindex As Int32, ByVal Setting As Single, ByVal Nindex As Int32, ByVal Level As Single) As Int32 + Declare Function ENsetnodeid Lib "epanet2.dll" (ByVal index As Int32, ByVal newid As String) As Int32 Declare Function ENsetnodevalue Lib "epanet2.dll" (ByVal Index As Int32, ByVal Code As Int32, ByVal Value As Single) As Int32 + Declare Function ENsetlinkid Lib "epanet2.dll" (ByVal index As Int32, ByVal newid As String) As Int32 Declare Function ENsetlinkvalue Lib "epanet2.dll" (ByVal Index As Int32, ByVal Code As Int32, ByVal Value As Single) As Int32 Declare Function ENsetpattern Lib "epanet2.dll" (ByVal Index as Int32, ByRef F as Single, ByVal N as Int32) as Int32 Declare Function ENsetpatternvalue Lib "epanet2.dll" (ByVal Index As Int32, ByVal Period As Int32, ByVal Value As Single) As Int32 diff --git a/src/epanet.c b/src/epanet.c index f209da7..aa2da3b 100644 --- a/src/epanet.c +++ b/src/epanet.c @@ -437,10 +437,18 @@ int DLLEXPORT ENaddcontrol(int *cindex, int ctype, int lindex, level); } +int DLLEXPORT ENsetnodeid(int index, char *newid) { + return EN_setnodeid(_defaultModel, index, newid); +} + int DLLEXPORT ENsetnodevalue(int index, int code, EN_API_FLOAT_TYPE v) { return EN_setnodevalue(_defaultModel, index, code, v); } +int DLLEXPORT ENsetlinkid(int index, char *newid) { + return EN_setlinkid(_defaultModel, index, newid); +} + int DLLEXPORT ENsetlinkvalue(int index, int code, EN_API_FLOAT_TYPE v) { return EN_setlinkvalue(_defaultModel, index, code, v); } @@ -2892,6 +2900,34 @@ int DLLEXPORT EN_setcontrol(EN_ProjectHandle ph, int cindex, int ctype, int lind return set_error(p->error_handle, 0); } +int DLLEXPORT EN_setnodeid(EN_ProjectHandle ph, int index, char *newid) +{ + EN_Project *p = (EN_Project*)ph; + EN_Network *net = &p->network; + size_t n; + + // Check for valid arguments + if (index <= 0 || index > net->Nnodes) + { + return set_error(p->error_handle, 203); + } + n = strlen(newid); + if (n < 1 || n > MAXID) return set_error(p->error_handle, 209); + if (strcspn(newid, " ;") < n) return set_error(p->error_handle, 209); + + // Check if another node with same name exists + if (ENHashTableFind(net->NodeHashTable, newid) > 0) + { + return set_error(p->error_handle, 215); + } + + // Replace the existing node ID with the new value + ENHashTableDelete(net->NodeHashTable, net->Node[index].ID); + strncpy(net->Node[index].ID, newid, MAXID); + ENHashTableInsert(net->NodeHashTable, net->Node[index].ID, index); + return set_error(p->error_handle, 0); +} + int DLLEXPORT EN_setnodevalue(EN_ProjectHandle ph, int index, int code, EN_API_FLOAT_TYPE v) /*---------------------------------------------------------------- ** Input: index = node index @@ -3158,6 +3194,34 @@ int DLLEXPORT EN_setnodevalue(EN_ProjectHandle ph, int index, int code, EN_API_F return set_error(p->error_handle, 0); } +int DLLEXPORT EN_setlinkid(EN_ProjectHandle ph, int index, char *newid) +{ + EN_Project *p = (EN_Project*)ph; + EN_Network *net = &p->network; + size_t n; + + // Check for valid arguments + if (index <= 0 || index > net->Nlinks) + { + return set_error(p->error_handle, 204); + } + n = strlen(newid); + if (n < 1 || n > MAXID) return set_error(p->error_handle, 211); + if (strcspn(newid, " ;") < n) return set_error(p->error_handle, 211); + + // Check if another link with same name exists + if (ENHashTableFind(net->LinkHashTable, newid) > 0) + { + return set_error(p->error_handle, 215); + } + + // Replace the existing link ID with the new value + ENHashTableDelete(net->LinkHashTable, net->Link[index].ID); + strncpy(net->Link[index].ID, newid, MAXID); + ENHashTableInsert(net->LinkHashTable, net->Link[index].ID, index); + return set_error(p->error_handle, 0); +} + int DLLEXPORT EN_setlinkvalue(EN_ProjectHandle ph, int index, int code, EN_API_FLOAT_TYPE v) From b3e84e0c42e5b8319680c4bc55c3e5f1a3ba7fe8 Mon Sep 17 00:00:00 2001 From: Lew Rossman Date: Wed, 17 Oct 2018 10:17:00 -0400 Subject: [PATCH 4/7] Added unit test for ENsetnodeid and ENsetlinkid functions --- tests/test_setid.cpp | 86 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 86 insertions(+) create mode 100644 tests/test_setid.cpp diff --git a/tests/test_setid.cpp b/tests/test_setid.cpp new file mode 100644 index 0000000..bd50a6e --- /dev/null +++ b/tests/test_setid.cpp @@ -0,0 +1,86 @@ +// +// test_setid.cpp +// + +/* +This is a test for the API functions that change a node or link ID name. +A node and link name are changed, the network is saved, reopened and the new names are checked. +*/ + +#define BOOST_TEST_MODULE "toolkit" +#include + +#include +#include "epanet2.h" + +// NOTE: Project Home needs to be updated to run unit test +#define DATA_PATH_INP "./net1.inp" +#define DATA_PATH_RPT "./test.rpt" +#define DATA_PATH_OUT "./test.out" + +using namespace std; + +BOOST_AUTO_TEST_SUITE (test_toolkit) + +BOOST_AUTO_TEST_CASE(test_setid) +{ + int error = 0; + int index; + char errmsg[256]; + char id[80]; + char newid[80]; + + std::string path_inp = std::string(DATA_PATH_INP); + std::string path_rpt = std::string(DATA_PATH_RPT); + std::string path_out = std::string(DATA_PATH_OUT); + + error = ENopen(path_inp.c_str(), path_rpt.c_str(), path_out.c_str()); + BOOST_REQUIRE(error == 0); + + // Test of illegal node name change + error = ENgetnodeid(3, id); + BOOST_REQUIRE(error == 0); + strncpy(newid, "Illegal; node name", 79); + error = ENsetnodeid(3, newid); + BOOST_REQUIRE(error > 0); + + // Test of legal node name change + strncpy(newid, "Node3", 79); + error = ENsetnodeid(3, newid); + BOOST_REQUIRE(error == 0); + + // Test of illegal link name change + error = ENgetlinkid(3, id); + BOOST_REQUIRE(error == 0); + strncpy(newid, "Illegal; link name", 79); + error = ENsetlinkid(3, newid); + BOOST_REQUIRE(error > 0); + + // Test of legal link name change + strncpy(newid, "Link3", 79); + error = ENsetlinkid(3, newid); + BOOST_REQUIRE(error == 0); + + // Save the project + error = ENsaveinpfile("net1_setid.inp"); + BOOST_REQUIRE(error == 0); + error = ENclose(); + BOOST_REQUIRE(error == 0); + + // Re-open the saved project + error = ENopen("net1_setid.inp", path_rpt.c_str(), path_out.c_str()); + BOOST_REQUIRE(error == 0); + + // Check that 3rd node has its new name + error = ENgetnodeindex((char *)"Node3", &index); + BOOST_REQUIRE(error == 0); + BOOST_REQUIRE(index == 3); + + // Check that 3rd link has its new name + error = ENgetlinkindex((char *)"Link3", &index); + BOOST_REQUIRE(error == 0); + BOOST_REQUIRE(index == 3); + ENclose(); +} + +BOOST_AUTO_TEST_SUITE_END() \ No newline at end of file From 60a79de4a9d8b4a87cbe0648b48bce2da18c5c76 Mon Sep 17 00:00:00 2001 From: Lew Rossman Date: Thu, 18 Oct 2018 10:03:09 -0400 Subject: [PATCH 5/7] Fixed bug in hash.c A bug in the delete function in hash.c was preventing the unit test of the new set ID API functions from running properly. In fixing this bug the entire hash table code was refactored to make it look more like the mempool service routines. Also the need to copy the string passed into the table's insert function was eliminated. --- ReleaseNotes2_2.md | 4 +- src/epanet.c | 67 +++++------ src/hash.c | 269 ++++++++++++++++++++++--------------------- src/hash.h | 24 ++-- src/input2.c | 4 +- src/types.h | 7 +- tests/test_setid.cpp | 59 +++++----- 7 files changed, 212 insertions(+), 222 deletions(-) diff --git a/ReleaseNotes2_2.md b/ReleaseNotes2_2.md index 618499b..2b335f3 100644 --- a/ReleaseNotes2_2.md +++ b/ReleaseNotes2_2.md @@ -129,8 +129,6 @@ Both network files are available [here](https://doi.org/10.23719/1375314). ## New API functions |Function|Description| |--|--| -|`ENaddlink`| | -|`ENaddnode`| | |`ENgetcurvetype`| | |`ENgetdemandmodel`|| |`ENsetdemandmodel`|| @@ -156,6 +154,8 @@ Both network files are available [here](https://doi.org/10.23719/1375314). |`ENaddlink`|| |`ENdeletelink`|| |`ENdeletenode`|| +| `ENsetnodeid` || +| `ENsetlinkid` || |`ENgetdemandname`|| |`ENsetdemandname`|| diff --git a/src/epanet.c b/src/epanet.c index aa2da3b..2c9017b 100644 --- a/src/epanet.c +++ b/src/epanet.c @@ -89,9 +89,9 @@ This module calls the following functions that reside in other modules: writelogo() writereport() HASH.C - ENHashTablecreate() - ENHashTableFind() - ENHashTableFree() + hashtable_create() + hashtable_find() + hashtable_free() The macro ERRCODE(x) is defined in TYPES.H. It says if the current value of the error code variable (errcode) is not fatal (< 100) then @@ -855,9 +855,8 @@ int DLLEXPORT EN_close(EN_ProjectHandle ph) writetime(p, FMT105); } freedata(p); - + out = &p->out_files; - if (out->TmpOutFile != out->OutFile) { if (out->TmpOutFile != NULL) { fclose(out->TmpOutFile); @@ -887,7 +886,7 @@ int DLLEXPORT EN_close(EN_ProjectHandle ph) remove(out->HydFname); if (out->Outflag == SCRATCH) remove(out->OutFname); - + p->Openflag = FALSE; p->hydraulics.OpenHflag = FALSE; p->save_options.SaveHflag = FALSE; @@ -2916,15 +2915,15 @@ int DLLEXPORT EN_setnodeid(EN_ProjectHandle ph, int index, char *newid) if (strcspn(newid, " ;") < n) return set_error(p->error_handle, 209); // Check if another node with same name exists - if (ENHashTableFind(net->NodeHashTable, newid) > 0) + if (hashtable_find(net->NodeHashTable, newid) > 0) { return set_error(p->error_handle, 215); } // Replace the existing node ID with the new value - ENHashTableDelete(net->NodeHashTable, net->Node[index].ID); + hashtable_delete(net->NodeHashTable, net->Node[index].ID); strncpy(net->Node[index].ID, newid, MAXID); - ENHashTableInsert(net->NodeHashTable, net->Node[index].ID, index); + hashtable_insert(net->NodeHashTable, net->Node[index].ID, index); return set_error(p->error_handle, 0); } @@ -3210,15 +3209,15 @@ int DLLEXPORT EN_setlinkid(EN_ProjectHandle ph, int index, char *newid) if (strcspn(newid, " ;") < n) return set_error(p->error_handle, 211); // Check if another link with same name exists - if (ENHashTableFind(net->LinkHashTable, newid) > 0) + if (hashtable_find(net->LinkHashTable, newid) > 0) { return set_error(p->error_handle, 215); } // Replace the existing link ID with the new value - ENHashTableDelete(net->LinkHashTable, net->Link[index].ID); + hashtable_delete(net->LinkHashTable, net->Link[index].ID); strncpy(net->Link[index].ID, newid, MAXID); - ENHashTableInsert(net->LinkHashTable, net->Link[index].ID, index); + hashtable_insert(net->LinkHashTable, net->Link[index].ID, index); return set_error(p->error_handle, 0); } @@ -4325,8 +4324,8 @@ int allocdata(EN_Project *p) parser_data_t *par; /* Allocate node & link ID hash tables */ - p->network.NodeHashTable = ENHashTableCreate(); - p->network.LinkHashTable = ENHashTableCreate(); + p->network.NodeHashTable = hashtable_create(); + p->network.LinkHashTable = hashtable_create(); ERRCODE(MEMCHECK(p->network.NodeHashTable)); ERRCODE(MEMCHECK(p->network.LinkHashTable)); @@ -4498,14 +4497,14 @@ void freedata(EN_Project *p) } free(net->Node); } - + /* Free memory for other network objects */ free(net->Link); free(net->Tank); free(net->Pump); free(net->Valve); free(net->Control); - + /* Free memory for time patterns */ if (net->Pattern != NULL) { for (j = 0; j <= par->MaxPats; j++) @@ -4521,20 +4520,19 @@ void freedata(EN_Project *p) } free(net->Curve); } - + /* Free memory for node coordinates */ if (p->parser.Coordflag == TRUE) { free(net->Coord); } - + /* Free memory for rule base (see RULES.C) */ freerules(p); - + /* Free hash table memory */ - if (net->NodeHashTable != NULL) - ENHashTableFree(net->NodeHashTable); - if (net->LinkHashTable != NULL) - ENHashTableFree(net->LinkHashTable); + if (net->NodeHashTable != NULL) hashtable_free(net->NodeHashTable); + + if (net->LinkHashTable != NULL) hashtable_free(net->LinkHashTable); } /* @@ -4637,7 +4635,7 @@ int findnode(EN_Network *n, char *id) **---------------------------------------------------------------- */ { - return (ENHashTableFind(n->NodeHashTable, id)); + return (hashtable_find(n->NodeHashTable, id)); } int findlink(EN_Network *n, char *id) @@ -4649,7 +4647,7 @@ int findlink(EN_Network *n, char *id) **---------------------------------------------------------------- */ { - return (ENHashTableFind(n->LinkHashTable, id)); + return (hashtable_find(n->LinkHashTable, id)); } int findtank(EN_Network *n, int index) @@ -5055,7 +5053,7 @@ int DLLEXPORT EN_addnode(EN_ProjectHandle ph, char *id, EN_NodeType nodeType) { // shift rest of Node array for (index = net->Nnodes; index >= net->Njuncs; index--) { - ENHashTableUpdate(net->NodeHashTable, net->Node[index].ID, index + 1); + hashtable_update(net->NodeHashTable, net->Node[index].ID, index + 1); net->Node[index + 1] = net->Node[index]; net->Coord[index + 1] = net->Coord[index]; } @@ -5171,7 +5169,7 @@ int DLLEXPORT EN_addnode(EN_ProjectHandle ph, char *id, EN_NodeType nodeType) { coord->Y = 0; /* Insert new node into hash table */ - ENHashTableInsert(net->NodeHashTable, node->ID, nIdx); /* see HASH.C */ + hashtable_insert(net->NodeHashTable, node->ID, nIdx); /* see HASH.C */ return set_error(p->error_handle, 0); } @@ -5194,8 +5192,8 @@ int DLLEXPORT EN_addlink(EN_ProjectHandle ph, char *id, EN_LinkType linkType, ch return set_error(p->error_handle, 215); /* Lookup the from and to nodes */ - N1 = ENHashTableFind(net->NodeHashTable, fromNode); - N2 = ENHashTableFind(net->NodeHashTable, toNode); + N1 = hashtable_find(net->NodeHashTable, fromNode); + N2 = hashtable_find(net->NodeHashTable, toNode); if (N1 == 0 || N2 == 0) { return set_error(p->error_handle, 203); @@ -5277,7 +5275,7 @@ int DLLEXPORT EN_addlink(EN_ProjectHandle ph, char *id, EN_LinkType linkType, ch link->Rpt = 0; strcpy(link->Comment, ""); - ENHashTableInsert(net->LinkHashTable, link->ID, n); + hashtable_insert(net->LinkHashTable, link->ID, n); return set_error(p->error_handle, 0); } @@ -5300,15 +5298,14 @@ int DLLEXPORT EN_deletelink(EN_ProjectHandle ph, int index) { link = &net->Link[index]; // remove from hash table - ENHashTableDelete(net->LinkHashTable, link->ID); - ENHashTableFind(net->LinkHashTable, link->ID); + hashtable_delete(net->LinkHashTable, link->ID); // shift link and pump arrays to re-sort link indices net->Nlinks--; for (i = index; i <= net->Nlinks - 1; i++) { net->Link[i] = net->Link[i + 1]; // update hashtable - ENHashTableUpdate(net->LinkHashTable, net->Link[i].ID, i); + hashtable_update(net->LinkHashTable, net->Link[i].ID, i); } for (i = 1; i <= net->Npumps; i++) { if (net->Pump[i].Link > index) { @@ -5361,14 +5358,14 @@ int DLLEXPORT EN_deletenode(EN_ProjectHandle ph, int index) { // TODO: check for existing controls/rules that reference this node? // remove from hash table - ENHashTableDelete(net->NodeHashTable, net->Node[index].ID); + hashtable_delete(net->NodeHashTable, net->Node[index].ID); // shift node and coord array to remove node for (i = index; i <= net->Nnodes - 1; i++) { net->Node[i] = net->Node[i + 1]; net->Coord[i] = net->Coord[i + 1]; // update hashtable - ENHashTableUpdate(net->NodeHashTable, net->Node[i].ID, i); + hashtable_update(net->NodeHashTable, net->Node[i].ID, i); } // update tank array diff --git a/src/hash.c b/src/hash.c index ac31246..2d007e2 100755 --- a/src/hash.c +++ b/src/hash.c @@ -1,21 +1,20 @@ /*----------------------------------------------------------------------------- ** hash.c ** - ** Implementation of a simple Hash Table for string storage & retrieval + ** Implementation of a simple Hash Table that uses a string pointer + ** as a key and an associated integer as data. ** ** Written by L. Rossman - ** Last Updated on 6/19/03 + ** Last Updated on 10/17/18 ** - ** The hash table data structure (HTable) is defined in "hash.h". ** Interface Functions: - ** HTcreate() - creates a hash table - ** HTinsert() - inserts a string & its index value into a hash table - ** HTfind() - retrieves the index value of a string from a table - ** HTfree() - frees a hash table + ** hashtable_create - creates a hash table + ** hashtable_insert - inserts a string & its data value into a table + ** hashtable_find - retrieves the data value associated with a string + ** hashtable_findkey - retrieves the key associated with a data value + ** hashtable_delete - deletes an entry from a table + ** hashtable_free - frees a hash table ** - ********************************************************************* - ** NOTE: This is a modified version of the original HASH.C module. - ********************************************************************* */ #ifndef __APPLE__ @@ -26,144 +25,148 @@ #include #include "hash.h" -unsigned int _enHash(char *str); -unsigned int _enHash(char *str) -{ - unsigned int hash = 5381; - unsigned int retHash; - int c; - while ((c = *str++)) { - hash = ((hash << 5) + hash) + c; /* hash * 33 + c */ - } - retHash = hash % ENHASHTABLEMAXSIZE; - - return retHash; -} +#define HASHTABLEMAXSIZE 128000 -ENHashTable *ENHashTableCreate() +typedef struct DataEntryStruct { - int i; - ENHashTable *ht = (ENHashTable *) calloc(ENHASHTABLEMAXSIZE, sizeof(ENHashTable)); - if (ht != NULL) { - for (i=0; i= ENHASHTABLEMAXSIZE ) { - return(0); - } - entry = (ENHashEntry *) malloc(sizeof(ENHashEntry)); - if (entry == NULL) { - return(0); - } - len = strlen(key) + 1; - entry->key = calloc(len, sizeof(char)); - strncpy(entry->key, key, len); - entry->data = data; - entry->next = ht[i]; - ht[i] = entry; - return(1); -} - -/* Abel Heinsbroek: Added function to update the hash table value for a given key */ -int ENHashTableUpdate(ENHashTable *ht, char *key, int new_data) -{ - unsigned int i = _enHash(key); - ENHashEntry *entry; - if ( i >= ENHASHTABLEMAXSIZE ) { - return(NOTFOUND); - } - entry = ht[i]; - while (entry != NULL) - { - if ( strcmp(entry->key,key) == 0 ) { - entry->data = new_data; - return(1); + int i; + HashTable *ht = (HashTable *) calloc(HASHTABLEMAXSIZE, sizeof(HashTable)); + if (ht != NULL) + { + for (i = 0; i < HASHTABLEMAXSIZE; i++) ht[i] = NULL; } - entry = entry->next; - } - return(NOTFOUND); + return ht; } -int ENHashTableDelete(ENHashTable *ht, char *key) { - unsigned int i = _enHash(key); - ENHashEntry *entry; - if ( i >= ENHASHTABLEMAXSIZE ) { - return(NOTFOUND); - } - entry = ht[i]; - while (entry != NULL) - { - if (strcmp(entry->key, key) == 0) { - entry->key = ""; - return(1); - } - entry = entry->next; - } - - return(NOTFOUND); -} - -int ENHashTableFind(ENHashTable *ht, char *key) +int hashtable_insert(HashTable *ht, char *key, int data) { - unsigned int i = _enHash(key); - - ENHashEntry *entry; - if ( i >= ENHASHTABLEMAXSIZE ) { - return(NOTFOUND); - } - - entry = ht[i]; - while (entry != NULL) - { - if ( strcmp(entry->key,key) == 0 ) { - return(entry->data); - } - entry = entry->next; - } - return(NOTFOUND); + unsigned int i = gethash(key); + DataEntry *entry; + if ( i >= HASHTABLEMAXSIZE ) return 0; + entry = (DataEntry *) malloc(sizeof(DataEntry)); + if (entry == NULL) return(0); + entry->key = key; + entry->data = data; + entry->next = ht[i]; + ht[i] = entry; + return 1; } -char *ENHashTableFindKey(ENHashTable *ht, char *key) +int hashtable_update(HashTable *ht, char *key, int new_data) { - unsigned int i = _enHash(key); - ENHashEntry *entry; - if ( i >= ENHASHTABLEMAXSIZE ) { - return(NULL); - } - entry = ht[i]; - while (entry != NULL) - { - if ( strcmp(entry->key,key) == 0 ) { - return(entry->key); - } - entry = entry->next; - } - return(NULL); -} - -void ENHashTableFree(ENHashTable *ht) -{ - ENHashEntry *entry, *nextentry; - int i; - for (i=0; i= HASHTABLEMAXSIZE ) return NOTFOUND; entry = ht[i]; while (entry != NULL) { - nextentry = entry->next; - free(entry->key); - free(entry); - entry = nextentry; + if ( strcmp(entry->key, key) == 0 ) + { + entry->data = new_data; + return 1; + } + entry = entry->next; } - } - free(ht); + return NOTFOUND; +} + +int hashtable_delete(HashTable *ht, char *key) +{ + unsigned int i = gethash(key); + DataEntry *entry, *preventry; + + if ( i >= HASHTABLEMAXSIZE ) return NOTFOUND; + + // Check if first entry in bucket i to be deleted + entry = ht[i]; + if (strcmp(entry->key, key) == 0) + { + ht[i] = entry->next; + free(entry); + return 1; + } + + // Check remaining entries in bucket i + preventry = ht[i]; + entry = ht[i]->next; + while (entry != NULL) + { + if (strcmp(entry->key, key) == 0) + { + preventry->next = entry->next; + free(entry); + return 1; + } + preventry = entry; + entry = entry->next; + } + return NOTFOUND; +} + +int hashtable_find(HashTable *ht, char *key) +{ + unsigned int i = gethash(key); + DataEntry *entry; + if ( i >= HASHTABLEMAXSIZE ) return NOTFOUND; + entry = ht[i]; + while (entry != NULL) + { + if ( strcmp(entry->key, key) == 0 ) + { + return entry->data; + } + entry = entry->next; + } + return NOTFOUND; +} + +char *hashtable_findkey(HashTable *ht, char *key) +{ + unsigned int i = gethash(key); + DataEntry *entry; + if ( i >= HASHTABLEMAXSIZE ) return NULL; + entry = ht[i]; + while (entry != NULL) + { + if ( strcmp(entry->key, key) == 0 ) return entry->key; + entry = entry->next; + } + return NULL; +} + +void hashtable_free(HashTable *ht) +{ + DataEntry *entry, *nextentry; + int i; + for (i = 0; i < HASHTABLEMAXSIZE; i++) + { + entry = ht[i]; + while (entry != NULL) + { + nextentry = entry->next; + free(entry); + entry = nextentry; + } + } + free(ht); } diff --git a/src/hash.h b/src/hash.h index 1285c11..612b8d3 100755 --- a/src/hash.h +++ b/src/hash.h @@ -7,24 +7,16 @@ #ifndef HASH_H #define HASH_H -#define ENHASHTABLEMAXSIZE 128000 #define NOTFOUND 0 -typedef struct HTentryStruct -{ - char *key; - int data; - struct HTentryStruct *next; -} ENHashEntry; +typedef struct DataEntryStruct *HashTable; -typedef ENHashEntry *ENHashTable; +HashTable *hashtable_create(void); +int hashtable_insert(HashTable *, char *, int); +int hashtable_find(HashTable *, char *); +char *hashtable_findkey(HashTable *, char *); +void hashtable_free(HashTable *); +int hashtable_update(HashTable *ht, char *key, int new_data); +int hashtable_delete(HashTable *ht, char *key); -ENHashTable *ENHashTableCreate(void); -int ENHashTableInsert(ENHashTable *, char *, int); -int ENHashTableFind(ENHashTable *, char *); -char *ENHashTableFindKey(ENHashTable *, char *); -void ENHashTableFree(ENHashTable *); -int ENHashTableUpdate(ENHashTable *ht, char *key, int new_data); -int ENHashTableDelete(ENHashTable *ht, char *key); - #endif diff --git a/src/input2.c b/src/input2.c index 717ef81..d141d72 100644 --- a/src/input2.c +++ b/src/input2.c @@ -472,7 +472,7 @@ int addnodeID(EN_Network *net, int n, char *id) return (0); /* see EPANET.C */ } strncpy(net->Node[n].ID, id, MAXID); - ENHashTableInsert(net->NodeHashTable, net->Node[n].ID, n); /* see HASH.C */ + hashtable_insert(net->NodeHashTable, net->Node[n].ID, n); /* see HASH.C */ return (1); } @@ -490,7 +490,7 @@ int addlinkID(EN_Network *net, int n, char *id) return (0); /* see EPANET.C */ } strncpy(net->Link[n].ID, id, MAXID); - ENHashTableInsert(net->LinkHashTable, net->Link[n].ID, n); /* see HASH.C */ + hashtable_insert(net->LinkHashTable, net->Link[n].ID, n); /* see HASH.C */ return (1); } diff --git a/src/types.h b/src/types.h index 7b9358d..1984b28 100755 --- a/src/types.h +++ b/src/types.h @@ -874,9 +874,10 @@ typedef struct { Scurve *Curve; /* Curve data */ Scoord *Coord; /* Coordinate data */ Scontrol *Control; /* Control data */ - ENHashTable *NodeHashTable, - *LinkHashTable; /* Hash tables for ID labels */ - Padjlist *Adjlist; /* Node adjacency lists */ + HashTable + *NodeHashTable, + *LinkHashTable; /* Hash tables for ID labels */ + Padjlist *Adjlist; /* Node adjacency lists */ } EN_Network; diff --git a/tests/test_setid.cpp b/tests/test_setid.cpp index bd50a6e..5a03b01 100644 --- a/tests/test_setid.cpp +++ b/tests/test_setid.cpp @@ -1,6 +1,4 @@ -// -// test_setid.cpp -// +// Test of ENsetid EPANET API Function /* This is a test for the API functions that change a node or link ID name. @@ -10,77 +8,76 @@ A node and link name are changed, the network is saved, reopened and the new nam #define BOOST_TEST_MODULE "toolkit" #include -#include +#include #include "epanet2.h" -// NOTE: Project Home needs to be updated to run unit test -#define DATA_PATH_INP "./net1.inp" -#define DATA_PATH_RPT "./test.rpt" -#define DATA_PATH_OUT "./test.out" +#define DATA_PATH_INP "net1.inp" +#define DATA_PATH_RPT "test.rpt" +#define DATA_PATH_OUT "" using namespace std; BOOST_AUTO_TEST_SUITE (test_toolkit) BOOST_AUTO_TEST_CASE(test_setid) +{ + +int main(int argc, char *argv[]) { int error = 0; int index; - char errmsg[256]; - char id[80]; - char newid[80]; + string newid; std::string path_inp = std::string(DATA_PATH_INP); std::string path_rpt = std::string(DATA_PATH_RPT); std::string path_out = std::string(DATA_PATH_OUT); - error = ENopen(path_inp.c_str(), path_rpt.c_str(), path_out.c_str()); + error = ENopen(path_inp.c_str(), path_rpt.c_str(), ""); BOOST_REQUIRE(error == 0); // Test of illegal node name change - error = ENgetnodeid(3, id); - BOOST_REQUIRE(error == 0); - strncpy(newid, "Illegal; node name", 79); - error = ENsetnodeid(3, newid); + newid = "Illegal; node name"; + error = ENsetnodeid(3, (char *)newid.c_str()); BOOST_REQUIRE(error > 0); // Test of legal node name change - strncpy(newid, "Node3", 79); - error = ENsetnodeid(3, newid); + newid = "Node3"; + error = ENsetnodeid(3, (char *)newid.c_str()); BOOST_REQUIRE(error == 0); - + // Test of illegal link name change - error = ENgetlinkid(3, id); - BOOST_REQUIRE(error == 0); - strncpy(newid, "Illegal; link name", 79); - error = ENsetlinkid(3, newid); + newid = "Illegal; link name"; + error = ENsetlinkid(3, (char *)newid.c_str()); BOOST_REQUIRE(error > 0); // Test of legal link name change - strncpy(newid, "Link3", 79); - error = ENsetlinkid(3, newid); + newid = "Link3"; + error = ENsetlinkid(3, (char *)newid.c_str()); BOOST_REQUIRE(error == 0); // Save the project error = ENsaveinpfile("net1_setid.inp"); BOOST_REQUIRE(error == 0); + error = ENclose(); BOOST_REQUIRE(error == 0); - + // Re-open the saved project - error = ENopen("net1_setid.inp", path_rpt.c_str(), path_out.c_str()); + error = ENopen("net1_setid.inp", path_rpt.c_str(), ""); BOOST_REQUIRE(error == 0); // Check that 3rd node has its new name - error = ENgetnodeindex((char *)"Node3", &index); + error = ENgetnodeindex("Node3", &index); BOOST_REQUIRE(error == 0); BOOST_REQUIRE(index == 3); // Check that 3rd link has its new name - error = ENgetlinkindex((char *)"Link3", &index); + error = ENgetlinkindex("Link3", &index); BOOST_REQUIRE(error == 0); BOOST_REQUIRE(index == 3); - ENclose(); + + error = ENclose(); + BOOST_REQUIRE(error == 0); } -BOOST_AUTO_TEST_SUITE_END() \ No newline at end of file +BOOST_AUTO_TEST_SUITE_END() From 0041772aa0cd4ed25126bbd8eedaddcf8b8e51d6 Mon Sep 17 00:00:00 2001 From: Lew Rossman Date: Thu, 18 Oct 2018 10:16:47 -0400 Subject: [PATCH 6/7] Corrected test_setid unit test code --- tests/test_setid.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/tests/test_setid.cpp b/tests/test_setid.cpp index 5a03b01..2b3693e 100644 --- a/tests/test_setid.cpp +++ b/tests/test_setid.cpp @@ -20,9 +20,6 @@ using namespace std; BOOST_AUTO_TEST_SUITE (test_toolkit) BOOST_AUTO_TEST_CASE(test_setid) -{ - -int main(int argc, char *argv[]) { int error = 0; int index; From f7346cef5febab6b3e32846ad0f7de6407b7246f Mon Sep 17 00:00:00 2001 From: Lew Rossman Date: Fri, 19 Oct 2018 10:36:39 -0400 Subject: [PATCH 7/7] Modified re-factored version of hash.c --- src/epanet.c | 1 - src/hash.c | 43 ++++++++++++++++++++++++------------------- tests/test_setid.cpp | 35 +++++++++++++++++++++-------------- 3 files changed, 45 insertions(+), 34 deletions(-) diff --git a/src/epanet.c b/src/epanet.c index 2c9017b..1229139 100644 --- a/src/epanet.c +++ b/src/epanet.c @@ -850,7 +850,6 @@ int DLLEXPORT EN_close(EN_ProjectHandle ph) out_file_t *out; EN_Project *p = (EN_Project*)ph; - if (p->Openflag) { writetime(p, FMT105); } diff --git a/src/hash.c b/src/hash.c index 2d007e2..99c4cb4 100755 --- a/src/hash.c +++ b/src/hash.c @@ -1,11 +1,11 @@ /*----------------------------------------------------------------------------- ** hash.c ** - ** Implementation of a simple Hash Table that uses a string pointer - ** as a key and an associated integer as data. + ** Implementation of a simple Hash Table that uses a string as a key + ** and an associated integer as data. ** ** Written by L. Rossman - ** Last Updated on 10/17/18 + ** Last Updated on 10/19/18 ** ** Interface Functions: ** hashtable_create - creates a hash table @@ -47,6 +47,14 @@ unsigned int gethash(char *str) return retHash; } +char *dupstr(const char *s) +{ + size_t size = strlen(s) + 1; + char *p = malloc(size); + if (p) memcpy(p, s, size); + return p; +} + HashTable *hashtable_create() { int i; @@ -65,7 +73,7 @@ int hashtable_insert(HashTable *ht, char *key, int data) if ( i >= HASHTABLEMAXSIZE ) return 0; entry = (DataEntry *) malloc(sizeof(DataEntry)); if (entry == NULL) return(0); - entry->key = key; + entry->key = dupstr(key); entry->data = data; entry->next = ht[i]; ht[i] = entry; @@ -76,6 +84,7 @@ int hashtable_update(HashTable *ht, char *key, int new_data) { unsigned int i = gethash(key); DataEntry *entry; + if ( i >= HASHTABLEMAXSIZE ) return NOTFOUND; entry = ht[i]; while (entry != NULL) @@ -97,23 +106,15 @@ int hashtable_delete(HashTable *ht, char *key) if ( i >= HASHTABLEMAXSIZE ) return NOTFOUND; - // Check if first entry in bucket i to be deleted + preventry = NULL; entry = ht[i]; - if (strcmp(entry->key, key) == 0) - { - ht[i] = entry->next; - free(entry); - return 1; - } - - // Check remaining entries in bucket i - preventry = ht[i]; - entry = ht[i]->next; while (entry != NULL) { if (strcmp(entry->key, key) == 0) { - preventry->next = entry->next; + if (preventry == NULL) ht[i] = entry->next; + else preventry->next = entry->next; + free(entry->key); free(entry); return 1; } @@ -127,6 +128,7 @@ int hashtable_find(HashTable *ht, char *key) { unsigned int i = gethash(key); DataEntry *entry; + if ( i >= HASHTABLEMAXSIZE ) return NOTFOUND; entry = ht[i]; while (entry != NULL) @@ -158,15 +160,18 @@ void hashtable_free(HashTable *ht) { DataEntry *entry, *nextentry; int i; + for (i = 0; i < HASHTABLEMAXSIZE; i++) { entry = ht[i]; while (entry != NULL) { - nextentry = entry->next; - free(entry); - entry = nextentry; + nextentry = entry->next; + free(entry->key); + free(entry); + entry = nextentry; } + ht[i] = NULL; } free(ht); } diff --git a/tests/test_setid.cpp b/tests/test_setid.cpp index 2b3693e..ee9e1da 100644 --- a/tests/test_setid.cpp +++ b/tests/test_setid.cpp @@ -1,4 +1,5 @@ // Test of ENsetid EPANET API Function +#define _CRT_SECURE_NO_DEPRECATE /* This is a test for the API functions that change a node or link ID name. @@ -11,9 +12,9 @@ A node and link name are changed, the network is saved, reopened and the new nam #include #include "epanet2.h" -#define DATA_PATH_INP "net1.inp" -#define DATA_PATH_RPT "test.rpt" -#define DATA_PATH_OUT "" +#define DATA_PATH_INP "./net1.inp" +#define DATA_PATH_RPT "./test.rpt" +#define DATA_PATH_OUT "./test.out" using namespace std; @@ -24,57 +25,63 @@ BOOST_AUTO_TEST_CASE(test_setid) int error = 0; int index; string newid; + + EN_ProjectHandle ph = NULL; + EN_createproject(&ph); std::string path_inp = std::string(DATA_PATH_INP); std::string path_rpt = std::string(DATA_PATH_RPT); std::string path_out = std::string(DATA_PATH_OUT); - error = ENopen(path_inp.c_str(), path_rpt.c_str(), ""); + error = EN_open(ph, path_inp.c_str(), path_rpt.c_str(), ""); BOOST_REQUIRE(error == 0); // Test of illegal node name change newid = "Illegal; node name"; - error = ENsetnodeid(3, (char *)newid.c_str()); + error = EN_setnodeid(ph, 3, (char *)newid.c_str()); BOOST_REQUIRE(error > 0); // Test of legal node name change newid = "Node3"; - error = ENsetnodeid(3, (char *)newid.c_str()); + error = EN_setnodeid(ph, 3, (char *)newid.c_str()); BOOST_REQUIRE(error == 0); // Test of illegal link name change newid = "Illegal; link name"; - error = ENsetlinkid(3, (char *)newid.c_str()); + error = EN_setlinkid(ph, 3, (char *)newid.c_str()); BOOST_REQUIRE(error > 0); // Test of legal link name change newid = "Link3"; - error = ENsetlinkid(3, (char *)newid.c_str()); + error = EN_setlinkid(ph, 3, (char *)newid.c_str()); BOOST_REQUIRE(error == 0); // Save the project - error = ENsaveinpfile("net1_setid.inp"); + error = EN_saveinpfile(ph, "net1_setid.inp"); BOOST_REQUIRE(error == 0); - error = ENclose(); + error = EN_close(ph); BOOST_REQUIRE(error == 0); + EN_deleteproject(&ph); // Re-open the saved project - error = ENopen("net1_setid.inp", path_rpt.c_str(), ""); + EN_createproject(&ph); + error = EN_open(ph, "net1_setid.inp", path_rpt.c_str(), ""); BOOST_REQUIRE(error == 0); // Check that 3rd node has its new name - error = ENgetnodeindex("Node3", &index); + error = EN_getnodeindex(ph, "Node3", &index); BOOST_REQUIRE(error == 0); BOOST_REQUIRE(index == 3); // Check that 3rd link has its new name - error = ENgetlinkindex("Link3", &index); + error = EN_getlinkindex(ph, "Link3", &index); BOOST_REQUIRE(error == 0); BOOST_REQUIRE(index == 3); - error = ENclose(); + error = EN_close(ph); BOOST_REQUIRE(error == 0); + EN_deleteproject(&ph); } BOOST_AUTO_TEST_SUITE_END()