Monday, July 16, 2018

A minor mistake from make_split_iterator

Recently, I found out there are some minor mistake lie underneath the following function:
FileNode* FileBot::constructParentPath(boost::filesystem::path inputPath)
{
    ...
    ...

    typedef split_iterator<string::iterator> string_split_iterator;
    for( string_split_iterator it = make_split_iterator(path, first_finder(GENERIC_PATH_SEPARATOR, is_equal()));
         it != string_split_iterator();
         ++it)
    {
       FileNode *node = new FileNode();
       node->setType('d');
       node->setName(copy_range<string>(*it));

       BOOST_LOG_TRIVIAL(info) << "value inserted: [" << copy_range<string>(*it) << "]";

       if( parent == NULL )
           fileList.push_back(*node);
       else {
           parent->sibling.push_back(*node);
           node->setParentNode(parent);
       }

       parent = node;
       ...
       ...
    }
}
This mistake would not cause any major crash, it just doesn't look "pretty" in terms of behavioral design. Before I go into the details, let's consider following 2 possible inputs:
  1. /home/user
  2. C:/home/user 
The output for entry 2 will generate a nice and beautiful link-list as shown in the image below:
But entry 1 would become like this, the first node in the link-list doesn't contain any name.
 
Why this could happen? I though the code will automatically handle the first token when it is empty? And I also realized this only happened to Linux due to Linux file system structure design behave differently from Windows. To prove my assumption, I run a series of test on make_split_iterator(), and the result shows the method does return an empty string. Another test on boost::split() also having this issue. The only one success is boost::tokenizer().

BOOST_AUTO_TEST_CASE(TL_split_words, *boost::unit_test::precondition(skipTest(false)) )
{
    string win = "D:/home/user";
    string lin = "/home/user";

    vector<string> words;
    string str, firstElement;

    cout << "***** version 1 *****" << endl;
    cout << "Windows platform: ";

    str = win;
    boost::split(words, str, boost::is_any_of(GENERIC_PATH_SEPARATOR), boost::token_compress_on);

    for(string w : words) {
        cout << "[" << w << "]";
    }
    cout << endl;

    firstElement = words.at(0);

    BOOST_TEST(words.size() == 4);
    BOOST_TEST(firstElement.compare("D:") == 0);

    words.clear();
    str = lin;
    boost::split(words, str, boost::is_any_of(GENERIC_PATH_SEPARATOR), boost::token_compress_on);

    cout << "Linux platform: ";
    for(string w : words) {
        cout << "[" << w << "]";
    }
    cout << endl;

    firstElement = words.at(0);

    BOOST_TEST(words.size() == 4);
    BOOST_TEST(firstElement.compare("") == 0);

    cout << "***** version 2 *****" << endl;
    cout << "Windows platform: ";

    str = win;

    typedef split_iterator< string::iterator> string_split_iterator;
    for( string_split_iterator it = make_split_iterator(str, first_finder(GENERIC_PATH_SEPARATOR, is_equal()));
         it != string_split_iterator();
         ++it)
    {
        cout << "[" << copy_range<string>(*it) << "]";
    }
    cout << endl;

    cout << "Linux platform: ";
    str = lin;

    typedef split_iterator<string::iterator> string_split_iterator;
    for( string_split_iterator it = make_split_iterator(str, first_finder(GENERIC_PATH_SEPARATOR, is_equal()));
         it != string_split_iterator();
         ++it)
    {
        cout << "[" << copy_range<string>(*it) << "]";
    }
    cout << endl;

    cout << "***** version 3 *****" << endl;
    cout << "Windows platform: ";

    str = win;

    boost::char_separator<char> sep{GENERIC_PATH_SEPARATOR.c_str()};
    boost::tokenizer<boost::char_separator<char> > token{str, sep};

    for( const auto &t : token ) {
        cout << "[" << t << "]";
    }
    cout << endl;

    cout << "Linux platform: ";
    str = lin;
    token = {str, sep};

    for( const auto &t : token ) {
        cout << "[" << t << "]";
    }
    cout << endl;
}
The output of this test would be like this:
***** version 1 *****
Windows platform: [D:][home][user]
Linux platform: [][home][user]
***** version 2 *****
Windows platform: [D:][home][user]
Linux platform: [][home][user]
***** version 3 *****
Windows platform: [D:][home][user]
Linux platform: [home][user]
I admit that I have overlooked the code, it is just too detail which something I have missed. Although the test doesn't fail, but the consequent impact would cause the link-list contain a node without a name.

Friday, July 6, 2018

Rebuild gSOAP from the source

I'm making a web service in C++. Never try it before, just want to find out how hard could it be? Unlike JAVA, the web service library in C++ isn't that popular, however, I still manage to get one. It is called gSOAP, I download it from sourceForge. And the experts are putting a thumbs up on it too.

For the first time, I am doing it on the Windows platform, it was working just fine. When I move on to the Linux machine, I got the following error pop up when I'm compiling my source:
In file included from soapH.h:16:0,
                 from HuahsinServiceBindingPort.nsmap:2,
                 from main.cpp:3:
soapStub.h:24:3: error: #error "GSOAP VERSION 20828 MISMATCH IN GENERATED CODE VERSUS LIBRARY CODE: PLEASE REINSTALL PACKAGE"
 # error "GSOAP VERSION 20828 MISMATCH IN GENERATED CODE VERSUS LIBRARY CODE: PLEASE REINSTALL PACKAGE"
   ^~~~~
What's happening just now? The error seems trying to remind me that the gSoap version isn't compatible with something else. This never happened in Windows. Wouldn't it be the cause of gSOAP I downloaded is for Windows only? Could it be some other thing else? Flashing back my memory what I did wrong, I downloaded the gSOAP from sourceForge, and then at the same time, I downloaded another version from Fedora repository. Then I generate the stub objects using the following command with Fedora's version, like this:
wsdl2h -o hello.h http://localhost:8080/services/hello?wsdl -t/usr/share/gsoap/WS/typemap.dat
soapcpp2 -jCL -I/usr/share/gsoap/import hello.h
And then I compile the source code with the stdsoap2.cpp from sourceForge's version.
g++ -g -o main.exe main.cpp soapC.cpp soapHuahsinServiceBindingPortProxy.cpp 
/home/kokhoe/tool/gsoap-2.8.68/gsoap/stdsoap2.cpp 
-I/home/kokhoe/tool/gsoap-2.8.68/gsoap 
-I. -std=c++11
Is this the reason why the compilation failed? Am I correct? I wasn't sure whether my assumption makes sense. As I read on the documentation, it does mention this:
This error is caused by mixing old with new gSOAP versions of the generated code and gSOAP libraries. Make sure to include the latest stdsoap2.h and link with the latest libgsoapXX.a libraries (or use the stdsoap2.c or stdsoap2.cpp source code).
I think this has given a clear sign on my mistake. I knew that I am going to make a fresh build from source, but still trying hard to find a lazy way, without making any hassle work to rebuild from the source. I am making a deep breath... thinking... and then keep on searching... for quite some time... and I found a step by step manual guide that could help me from building the source until the installation. It is accompanied with the source I downloaded. Thus, I made a bold decision to rebuild everything, including gSOAP.

During the build process, there are many libraries were missing, among those errors I have, following error tells no clue on what was missing. The compiler just stops like that with the given command like this:
gcc -DWITH_YACC -DWITH_FLEX -DSOAPCPP2_IMPORT_PATH="\"/usr/local/share/gsoap/import\"" 
-DLINUX -g -O2 -o soapcpp2 soapcpp2-soapcpp2_yacc.o soapcpp2-soapcpp2_lex.o soapcpp2-symbol2.o soapcpp2-error2.o 
soapcpp2-init2.o soapcpp2-soapcpp2.o -ly  
/usr/bin/ld: cannot find -ly
It gave me a hard time to identify the relationship between the -DWITH_YACC, -DWITH_FLEX, and -ly. There are the Bison and Flex libraries actually. Thank god. The rebuild process continues at last and my source was built with no error.