Monday, February 20, 2017

Using Boost.Log in the project

Boost.Log, just like other module, it must be built first before it can be used. But most of the time I will just go for the default compilation, just like below

> ./b2 --with-log 

According to this documentation, I can have a lot more option to the build. Say for example -DBOOST_LOG_DYN_LINK, below is the definition of the macro:

BOOST_LOG_DYN_LINK If defined in user code, the library will assume the binary is built as a dynamically loaded library ("dll" or "so"). Otherwise it is assumed that the library is built in static mode. This macro must be either defined or not defined for all translation units of user application that uses logging. This macro can help with auto-linking on platforms that support it.

When I build with -DBOOST_LOG_DYN_LINK just like the sample below:

> ./b2 --with-log define=BOOST_LOG_DYN_LINK

I must have the following statement in the .pro file:

DEFINES += BOOST_LOG_DYN_LINK

Take a closer look into the console output. Notice that BOOST_LOG_DYN_LINK option is added into g++ build command:
g++ -c -pipe -g -std=gnu++0x -Wall -W -D_REENTRANT -fPIC -DBOOST_LOG_DYN_LINK -DQT_QML_DEBUG 
-DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_CORE_LIB -I../../backupUtil -I. -I../../../tool/boost_1_61_0 -I../../backupUtil 
-I../../../tool/Qt5.6.0/5.6/gcc_64/include -I../../../tool/Qt5.6.0/5.6/gcc_64/include/QtWidgets 
-I../../../tool/Qt5.6.0/5.6/gcc_64/include/QtGui -I../../../tool/Qt5.6.0/5.6/gcc_64/include/QtCore -I. -I. 
-I../../../tool/Qt5.6.0/5.6/gcc_64/mkspecs/linux-g++ -o main.o ../main.cpp
If the above define statement is missing from the .pro file, the compiler will throw out the link error. Tracing that error could lead to a wrong way fixing the problem because the root cause wasn't there. This documentation would give some hints on how this thing could be fixed. Below is the important text from that documentation:
<version><linkage>_<threading>_<system>
  • The <version> component describes the library major version. It is currently v2.
  • The <linkage> component tells whether the library is linked statically or dynamically. It is s if the library is linked statically and empty otherwise.
  • The <threading> component is st for single-threaded builds and mt for multi-threaded ones.
  • The <system> component describes the underlying OS API used by the library. Currently, it is only specified for multi-threaded builds. Depending on the target platform and configuration, it can be posix, nt5 or nt6.
Same to the default Boost.Log build without BOOST_LOG_LINK, link error will be throw if the define statement was declared in the .pro file.

Saturday, January 21, 2017

replace multiple character in a string

As mention before, unit test is just a way to gauge my code doing the right thing. But I still miss the functional test. Take the following piece as an example:
    int seperatorPos = filename.find("/");
    if(seperatorPos > 0) {
        filename.replace(seperatorPos, 1, "_");
    }
This piece supposes to construct the index file name from the given path. For example, if the given path is path_A/path_B, then the index file name should be path_A_path_B. The unit test has done a pretty good job, but in a real world environment, the path could be path_A/path_B/path_C, in such a situation, that piece could fail. This is the drawback of unit test, it can't simulate the actual use case. To fix the defects, that piece needs to identify when it has searched through the entire string. As you guess it, I need a while loop to do this, here is the solution:
    int seperatorPos = filename.find("/");
    while( seperatorPos != string::npos ) {
        filename.replace(seperatorPos, 1, "_");
        seperatorPos = filename.find("/");
    }
Then this is the final piece that meets my requirement.

Wednesday, December 28, 2016

Unit test my code

Now is the time for serious thing - unit testing. To make sure I'm doing the right thing with my code. Frankly speaking, implementing unit test in C++ is my first time. Not even heard of it in the past. Wasting a lot of my time discovering C++ unit test framework, surprisingly, Boost's test framework was quite user friendly.

To adopt this thing into my project, I've come an idea that is suited for my test case. The idea like this, for each test case, 2 folders will be created to mimic the source location and destination location. Thus I have a class named PrepareFile for this purpose, the constructor will invoke when each test case begins, and the destructor will invoke when the test case has ended:
class PrepareFile
{
public:
    PrepareFile() {
        create_directory("path_A");
        create_directory("path_B");

        BOOST_TEST_MESSAGE("Creating test folder");
    }
    ~PrepareFile() {
        remove_all("path_A");
        remove_all("path_B");

        BOOST_TEST_MESSAGE("Removing test folder");
    }
};

/*  Test case: Cap_T1
 *  Test scenario: path_A has one file
 *  Test result: one file is captured in index file
 */
BOOST_FIXTURE_TEST_CASE(Cap_T1, PrepareFile)
{
    ...
}

/*  Test case: Cap_T2
 *  Test scenario: path_A has one file, path_B has one file.
 *  Test result: files in both paths has captured in index file.
 */
BOOST_FIXTURE_TEST_CASE(Cap_T2, PrepareFile)
{
    ...
}
I like this test framework structure because it could provide me a clean test environment. Anyhow, this is just a tiny test in my code, SIT effort is still required.

Saturday, December 24, 2016

Generate WSDL file with wsgen

Today I'm running a Web Service experiment again. But I just feel that I'm making a tutorial right now. No way, this is just a finding on my silly mistake though. Assuming I have the following code setup:
package org.huahsin.main.bo;

...

@WebService(targetNamespace = "http://main.huahsin.org/")
public interface IPersonService {

 @WebMethod
 public String getPerson(String name);
}


package org.huahsin.main;

...

@WebService(serviceName = "PersonService", 
   endpointInterface = "org.huahsin.main.bo.IPersonService",
   portName = "PersonServicePort")
public class PersonService implements IPersonService {

 @Override
 public String getPerson(String name) {
  return "Hello World " + name;
 }

}
Take good care of the endpointInterface, I mess up this name with the implementation class name, that suppose belong to an Interface. Next is to fire up the WSDL through HTTP. Previously I was using this trick to make a WSDL file:
public class PersonClient {

 public static void main(String[] args) {
  Endpoint.publish("http://localhost:8080/ws", new PersonService());
 }
}
And then hit the URL http://localhost:8080/ws?wsdl in the browser, copy the content in the WSDL file. Actually wsgen has already covered the WSDL file generation with -wsdl as shown following command:

wsgen -keep -wsdl -cp ./WebContent/WEB-INF/classes org.huahsin.main.PersonService

There will be some minor modification on the REPLACE_WITH_ACTUAL_URL after WSDL file is generated:
  <service name="PersonService">
    <port binding="tns:PersonServicePortBinding" name="PersonServicePort">
      <soap:address location="REPLACE_WITH_ACTUAL_URL"/>
    </port>
  </service>
Just replace any URL as you like, make sure the IP and port are accessible, it is very useful to determine what are the service available currently deployed.

* Do take note that when I deploy this code into WebSphere Liberty Profile (v8.5.5.8 as of this writing) with CXF runtime bundle with my code, it causes conflict error. According to the documentation, I should not enable jaxws-2.2 feature.
If you application provides its own copy of CXF JAR files as the application libraries, for example, in the WEB-INF/lib directory of a web application, you cannot enable the jaxws-2.2 feature in the server.xml file.

Thursday, December 22, 2016

New use case identified.

The experiment in my test lab was so successful. Feeling good? Not too early. Things always not working when put into real environment. Just like the use case diagram below.
Figure 1: Files are captured into index file
Notice that the files are captured into a file (index file) but not a destination. There are 2 problems I need to solve when I draft this design:
  1. to let the program know there are some files has been removed and thus the copy in destination must remove as well. 
  2. file exist in destination path but not in source path are consider orphan file. I'm trying to prevent these orphan file being removed from destination path.
Here are the requirements during the capturing process:
  • First scan will be done on the destination location.
  • Next scan will be done on the origin location.
  • If the file shown in origin location, but missing in a destination location, put a plus sign to indicate this file need to be added in. During the sync process, the file will be copied to the destination.
  • If the file was missing in origin location, but shown in the destination location, put a minus sign to indicate the file need to be removed. During the sync process, the file will be removed from the destination.
This was a real damn hard work logic. First thing first, I'll load up the content of index files into memory called fileContent through the constructor, this is the only chance I can load them up into memory when the FilePath object got intialized.
void FilePath::captureExistingFile()
{
    std::ifstream indexFile(indexFilePath, ios_base::in);
    string line = "";
    while( std::getline(indexFile, line) ) {
        fileContent.insert(line);
    }
    indexFile.close();
}


class FilePath
{
...
...

private:
    set<string> fileContent;
    path rootPath;
    std::ofstream indexFile;
};

FilePath::FilePath(path thePath)
    : rootPath(thePath), searchDone(false)
{
    ...

    // capture the index file content into memory
    indexFilePath = rootPath + string(1, path::preferred_separator) + ((seperatorPos > 0) ? filename : converterX.to_bytes(thePath.wstring())) + ".i";

    if( exists(indexFilePath) ) {
        captureExistingFile();
        bFileExist = true;
    }

    indexFile.open(indexFilePath, ios_base::in | ios_base::out | ios_base::app);
}
That is for the requirement 1 and 2. To handle the requirement plus and minus flag, here is the work around:
void FilePath::captureFiles(path searchPath)
{
    bool bPlus = false, bMinus = false;
    vector<path> pathList;

    if (rootPath.size() == 0) {
        rootPath = searchPath;
    }

    if( exists(searchPath) ) {
        // capture the paths/files list under the source

        ...

        // scan through path and those files sit in that path
        // scan for files in sub directory if there is any sub directory in that path
        for(vector<path>::const_iterator it(pathList.begin()); it != pathList.end(); ++it) {

            ...

            file = file.substr(thePath.length() + 1, file.length());

            // remove the files when index file content is larger than actual path
            if( fileContent.size() > pathList.size() - 1 ) {
                set<string>::iterator result = std::find(std::begin(fileContent), std::end(fileContent), file);
                if( result != std::end(fileContent) ) {
                    fileContent.erase(file);
                    bMinus = true;
                }
            }
            // new file has arrive when index file does exists
            // this block wouldn't execute if the path has never been scan before
            else if( fileContent.size() != 0 ){
                set<string>::iterator result = std::find(std::begin(fileContent), std::end(fileContent), file);
                if( result == std::end(fileContent) ) {
                    bPlus = true;
                    fileContent.insert(file);
                }
                else {
                    continue;
                }
            }

            ...

            // it is a file
            else {
                fileSet.insert(file);

                string filename(file.begin(), file.end());

                if( bPlus ) {
                    bPlus = false;
                    indexFile << "+" << filename << endl;
                }
                else if( !bMinus ) {
                    bMinus = false;
                    indexFile << filename << endl;
                }
            }
        }
    }

    indexFile.close();

    // there are some files has been remove
    // replace the content of index file
    if( bFileExist && bMinus ) {
        indexFile.open(indexFilePath, ios_base::out | ios_base::trunc);
        for( set<string>::iterator it=fileSet.begin(); it != fileSet.end(); it++ ) {
            indexFile << *it << endl;
        }

        for( set<string>::iterator it=fileContent.begin(); it != fileContent.end(); it++ ) {
            indexFile << "-" << *it << endl;
        }
    }

}
Sometimes I can't even remember my work after a few years not looking on my code, duh~

Sunday, December 18, 2016

The searching mechanism is ready

The fun part of this program, file searching, is created. This module was dominated by boost::filesystem. I start off with some experiment on this module. Then slowly evolve piece by piece from my test lab, and now it has graduate become a man that took up the responsible on file searching. This is the attribute of the object:
class FilePath
{
public:
    FilePath();
    FilePath(path thePath);

    void captureFiles(path searchPath);
    set<wstring> getFileSet() { return fileSet; }


private:
    bool checkPathExists(path workingPath);


private:

    set<wstring> fileSet;
    path rootPath;
    bool searchDone = false;
    void clearFileSet() { fileSet.clear(); }
};
Each of this object will be responsible on each path. Only one and no more! The strongest power, captureFiles() is the most important function this object could ever had. It searches for files, and capture them into memory (cool huh?):
void FilePath::captureFiles(path searchPath)
{
    vector<path> pathList;

    if( rootPath.wstring().length() == 0 )
        rootPath = searchPath;

    if( exists(searchPath) ) {
        // capture the paths/files list under the source
        copy(directory_iterator(searchPath), directory_iterator(), back_inserter(pathList));

        // scan through each path/file to categorize them either path or file
        for(vector<path>::const_iterator it(pathList.begin()); it != pathList.end(); ++it) {
            wstring file = (*it).wstring();
            wstring thePath = rootPath.wstring();
            file = file.substr(thePath.length() + 1, file.length());

            // it is a directory
            if( is_directory(status(*it)) ) {
                captureFiles(*it);
            }
            // it is a file
            else {
                fileSet.insert(file);
            }
        }
    }

    searchDone = true;
}
The key of this object is the rootPath. When this object is summoned, one must not forget is to tell which path to go, just as shown below:
FilePath::FilePath(path thePath)
    :rootPath(thePath), searchDone(false)
{
}
If the key was missing during initialization, this object might behave abnormally.

Sunday, December 11, 2016

Going deep to the backup plan

To start off the backup plan, I will need Qt and Boost. Before this, I'm a fan of MFC, everything I did is for Windows (only). But now I was using Windows and Linux together, thus I think Qt would be my best support on UI work for cross platform environment. Boost contains many flavor type of algorithm that could ease up my development effort.

I start off this project with unit test. I am a true fan of Test Driven Development methodology. I've been using this methodology for years, and it proves the lesser defects report from the tester. So I believe that a quality product comes from quality code. A quality code depends on how well you design the code. And unit test would be the quality controller on my code.

Sometimes I do use unit tests code as my code documentation for other developers, this may not true for others, but I just so lazy to prepare functional documentation.

Well, that's all for the plan. Wish me luck!