<commit_before>/*
Highly Optimized Object-Oriented Molecular Dynamics (HOOMD) Open
Source Software License
Copyright (c) 2008 Ames Laboratory Iowa State University
All rights reserved.

Redistribution and use of HOOMD, in source and binary forms, with or
without modification, are permitted, provided that the following
conditions are met:

* Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.

* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.

* Neither the name of the copyright holder nor the names HOOMD's
contributors may be used to endorse or promote products derived from this
software without specific prior written permission.

Disclaimer

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER AND
CONTRIBUTORS ``AS IS''  AND ANY EXPRESS OR IMPLIED WARRANTIES,
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.

IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS  BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
THE POSSIBILITY OF SUCH DAMAGE.
*/

#ifdef WIN32
#pragma warning( push )
#pragma warning( disable : 4103 4244 )
#endif

#include <iostream>

//You need a fft defined in order to pass this text
#ifdef USE_FFTW

//! Name the unit test module
#define BOOST_TEST_MODULE ElectrostaticLongRangePPPMTests
#include "boost_utf_configure.h"

#include <boost/test/floating_point_comparison.hpp>
#include <boost/bind.hpp>
#include <boost/function.hpp>
#include <boost/shared_ptr.hpp>

#include "ElectrostaticLongRangePPPM.h"
#include "FFTClass.h"
#include "FftwWrapper.h"

#include "ParticleData.h"

#include <math.h>

using namespace std;
using namespace boost;

/*! \file ElectrostaticLongRange_PPPM_test.cc
	\brief Implements unit tests for ElectrostaticLongRangePPPM and descendants
	\ingroup unit_tests
*/

//! Helper macro for testing if two numbers are close
#define MY_BOOST_CHECK_CLOSE(a,b,c) BOOST_CHECK_CLOSE(a,Scalar(b),Scalar(c))
//! Helper macro for testing if a number is small
#define MY_BOOST_CHECK_SMALL(a,c) BOOST_CHECK_SMALL(a,Scalar(c))

//! Tolerance in percent to use for comparing various ElectrostaticLongRangePPPM to each other
const Scalar tol = Scalar(1);
//! minimum force worth computing
const Scalar MIN_force=Scalar(1.0e-9);

//! Typedef ElectrostaticLongRangePPPM factory
typedef function<shared_ptr<ElectrostaticLongRangePPPM> (shared_ptr<ParticleData> pdata,unsigned int Mmesh_x,unsigned int Mmesh_y,unsigned int Mmesh_z, unsigned int P_order_a, Scalar alpha, shared_ptr<FFTClass> FFTP,bool third_law_m)> LongRangePPPM_creator;

//! Test the ability of the Short Range Electrostatic force compute to actually calculate forces
/*!
	\note With the creator as a parameter, the same code can be used to test any derived child
		of ElectrostaticLongRangePPPM
*/
void LongRangePPPM_PositionGrid(LongRangePPPM_creator LongRangePPPM_object_n1)
	{
	cout << "Testing charge distribution on the grid in class ElectrostaticLongRangePPPM" << endl;
	// Simple test to check that the charge is defined correctly on the grid
	shared_ptr<ParticleData> pdata_6(new ParticleData(6, BoxDim(20.0,40.0,60.0), 1));
	ParticleDataArrays arrays = pdata_6->acquireReadWrite();

	// six charges are located near the edge of the box
	arrays.x[0]=Scalar(-9.6);arrays.y[0]=Scalar(0.0);arrays.z[0]=Scalar(0.0);arrays.charge[0]=1.0;
    arrays.x[1]=Scalar(9.6);arrays.y[1]=Scalar(0.0);arrays.z[1]=Scalar(0.0);arrays.charge[1]=-2.0;
	arrays.x[2]=Scalar(0.0);arrays.y[2]=Scalar(-19.5);arrays.z[2]=Scalar(0.0);arrays.charge[2]=1.0;
    arrays.x[3]=Scalar(0.0);arrays.y[3]=Scalar(19.5);arrays.z[3]=Scalar(0.0);arrays.charge[3]=3.0;
    arrays.x[4]=Scalar(0.0);arrays.y[4]=Scalar(0.0);arrays.z[4]=Scalar(-29.4);arrays.charge[4]=-1.0;
    arrays.x[5]=Scalar(0.0);arrays.y[5]=Scalar(0.0);arrays.z[5]=Scalar(29.4);arrays.charge[5]=-2.0;

	// allow for acquiring data in the future
	pdata_6->release();

    // Define mesh parameters as well as order of the distribution, etc..
	unsigned int Nmesh_x=40;
	unsigned int Nmesh_y=80;
	unsigned int Nmesh_z=120;
	unsigned int P_order=6;
	Scalar alpha=4.0;
	shared_ptr<FftwWrapper> FFTW(new  FftwWrapper(Nmesh_x,Nmesh_y,Nmesh_z));
	bool third_law=false;

	shared_ptr<ElectrostaticLongRangePPPM> PPPM_6=LongRangePPPM_object_n1(pdata_6,Nmesh_x,Nmesh_y,Nmesh_z,P_order,alpha,FFTW,third_law);
	// An ElectrostaticLongRangePPPM object with specified value of grid parameters, alpha, and fft routine instantiated

	// now let us check that the charges are correctly distributed

	//First check that the polynomials used to spread the charge on the grid are what they should be
	//This may eliminate unpleasant bugs

	//The values of the coefficents are taken from Appendix E in the Deserno and Holm paper

	Scalar **Exact=new Scalar*[P_order];
	for(unsigned int i=0;i<P_order;i++) Exact[i]=new Scalar[P_order];

	Exact[0][0]=1.0;Exact[0][1]=-10.0;Exact[0][2]=40.0;Exact[0][3]=-80.0;Exact[0][4]=80.0;Exact[0][5]=-32.0;
	Exact[1][0]=237.0;Exact[1][1]=-750.0;Exact[1][2]=840.0;Exact[1][3]=-240.0;Exact[1][4]=-240.0;Exact[1][5]=160.0;
	Exact[2][0]=1682.0;Exact[2][1]=-1540.0;Exact[2][2]=-880.0;Exact[2][3]=1120.0;Exact[2][4]=160.0;Exact[2][5]=-320.0;
	Exact[3][0]=1682.0;Exact[3][1]=1540.0;Exact[3][2]=880.0;Exact[3][3]=-1120.0;Exact[3][4]=-160.0;Exact[3][5]=320.0;
	Exact[4][0]=237.0;Exact[4][1]=750.0;Exact[4][2]=840.0;Exact[4][3]=240.0;Exact[4][4]=-240.0;Exact[4][5]=-160.0;
	Exact[5][0]=1.0;Exact[5][1]=10.0;Exact[5][2]=40.0;Exact[5][3]=80.0;Exact[5][4]=80.0;Exact[5][5]=32.0;

	for(unsigned int i=0;i<P_order;i++){
		for(unsigned int j=0;j<P_order;j++){
				Exact[i][j]*=static_cast<Scalar>(1/3840.0);
		}
	}

	for(unsigned int i=0;i<P_order;i++){
		for(unsigned int j=0;j<P_order;j++){
				MY_BOOST_CHECK_CLOSE(Exact[i][j],PPPM_6->Poly_coeff_Grid(i,j),tol);
		}
	}

	//Check passed, now let us compute the charges on the grid

}

//! ElectrostaticShortRange creator for unit tests
shared_ptr<ElectrostaticLongRangePPPM> base_class_PPPM_creator(shared_ptr<ParticleData> pdata,unsigned int Nmesh_x,unsigned int Nmesh_y,unsigned int Nmesh_z, unsigned int P_order, Scalar alpha,shared_ptr<FFTClass> FFTW,bool third_law_m)
	{
	return shared_ptr<ElectrostaticLongRangePPPM>(new ElectrostaticLongRangePPPM(pdata, Nmesh_x, Nmesh_y, Nmesh_z, P_order,alpha,FFTW,third_law_m));
	}

//! boost test case for particle test on CPU
BOOST_AUTO_TEST_CASE(LongRangePPPM_PositionGrid_test)
{
	LongRangePPPM_creator LongRangePPPM_creator_base = bind(base_class_PPPM_creator, _1, _2, _3, _4, _5,_6,_7,_8);
	LongRangePPPM_PositionGrid(LongRangePPPM_creator_base);
}

#endif

#ifdef WIN32
#pragma warning( pop )
#endif
<commit_msg>Fixed another minor documentation warning in windows. Stop committing code that generates warning, people!<commit_after>/*
Highly Optimized Object-Oriented Molecular Dynamics (HOOMD) Open
Source Software License
Copyright (c) 2008 Ames Laboratory Iowa State University
All rights reserved.

Redistribution and use of HOOMD, in source and binary forms, with or
without modification, are permitted, provided that the following
conditions are met:

* Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.

* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.

* Neither the name of the copyright holder nor the names HOOMD's
contributors may be used to endorse or promote products derived from this
software without specific prior written permission.

Disclaimer

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER AND
CONTRIBUTORS ``AS IS''  AND ANY EXPRESS OR IMPLIED WARRANTIES,
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.

IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS  BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
THE POSSIBILITY OF SUCH DAMAGE.
*/

#ifdef WIN32
#pragma warning( push )
#pragma warning( disable : 4103 4244 )
#endif

#include <iostream>

//You need a fft defined in order to pass this text
#ifdef USE_FFTW

//! Name the unit test module
#define BOOST_TEST_MODULE ElectrostaticLongRangePPPMTests
#include "boost_utf_configure.h"

#include <boost/test/floating_point_comparison.hpp>
#include <boost/bind.hpp>
#include <boost/function.hpp>
#include <boost/shared_ptr.hpp>

#include "ElectrostaticLongRangePPPM.h"
#include "FFTClass.h"
#include "FftwWrapper.h"

#include "ParticleData.h"

#include <math.h>

using namespace std;
using namespace boost;

/*! \file ElectrostaticLongRange_PPPM_test.cc
	\brief Implements unit tests for ElectrostaticLongRangePPPM and descendants
	\ingroup unit_tests
*/

//! Helper macro for testing if two numbers are close
#define MY_BOOST_CHECK_CLOSE(a,b,c) BOOST_CHECK_CLOSE(a,Scalar(b),Scalar(c))
//! Helper macro for testing if a number is small
#define MY_BOOST_CHECK_SMALL(a,c) BOOST_CHECK_SMALL(a,Scalar(c))

//! Tolerance in percent to use for comparing various ElectrostaticLongRangePPPM to each other
const Scalar tol = Scalar(1);
//! minimum force worth computing
const Scalar MIN_force=Scalar(1.0e-9);

//! Typedef ElectrostaticLongRangePPPM factory
typedef function<shared_ptr<ElectrostaticLongRangePPPM> (shared_ptr<ParticleData> pdata,unsigned int Mmesh_x,unsigned int Mmesh_y,unsigned int Mmesh_z, unsigned int P_order_a, Scalar alpha, shared_ptr<FFTClass> FFTP,bool third_law_m)> LongRangePPPM_creator;

//! Test the ability of the Short Range Electrostatic force compute to actually calculate forces
/*! \param LongRangePPPM_object_n1 I have no idea: the write of this code needs to document it better
	\note With the creator as a parameter, the same code can be used to test any derived child
		of ElectrostaticLongRangePPPM
*/
void LongRangePPPM_PositionGrid(LongRangePPPM_creator LongRangePPPM_object_n1)
	{
	cout << "Testing charge distribution on the grid in class ElectrostaticLongRangePPPM" << endl;
	// Simple test to check that the charge is defined correctly on the grid
	shared_ptr<ParticleData> pdata_6(new ParticleData(6, BoxDim(20.0,40.0,60.0), 1));
	ParticleDataArrays arrays = pdata_6->acquireReadWrite();

	// six charges are located near the edge of the box
	arrays.x[0]=Scalar(-9.6);arrays.y[0]=Scalar(0.0);arrays.z[0]=Scalar(0.0);arrays.charge[0]=1.0;
    arrays.x[1]=Scalar(9.6);arrays.y[1]=Scalar(0.0);arrays.z[1]=Scalar(0.0);arrays.charge[1]=-2.0;
	arrays.x[2]=Scalar(0.0);arrays.y[2]=Scalar(-19.5);arrays.z[2]=Scalar(0.0);arrays.charge[2]=1.0;
    arrays.x[3]=Scalar(0.0);arrays.y[3]=Scalar(19.5);arrays.z[3]=Scalar(0.0);arrays.charge[3]=3.0;
    arrays.x[4]=Scalar(0.0);arrays.y[4]=Scalar(0.0);arrays.z[4]=Scalar(-29.4);arrays.charge[4]=-1.0;
    arrays.x[5]=Scalar(0.0);arrays.y[5]=Scalar(0.0);arrays.z[5]=Scalar(29.4);arrays.charge[5]=-2.0;

	// allow for acquiring data in the future
	pdata_6->release();

    // Define mesh parameters as well as order of the distribution, etc..
	unsigned int Nmesh_x=40;
	unsigned int Nmesh_y=80;
	unsigned int Nmesh_z=120;
	unsigned int P_order=6;
	Scalar alpha=4.0;
	shared_ptr<FftwWrapper> FFTW(new  FftwWrapper(Nmesh_x,Nmesh_y,Nmesh_z));
	bool third_law=false;

	shared_ptr<ElectrostaticLongRangePPPM> PPPM_6=LongRangePPPM_object_n1(pdata_6,Nmesh_x,Nmesh_y,Nmesh_z,P_order,alpha,FFTW,third_law);
	// An ElectrostaticLongRangePPPM object with specified value of grid parameters, alpha, and fft routine instantiated

	// now let us check that the charges are correctly distributed

	//First check that the polynomials used to spread the charge on the grid are what they should be
	//This may eliminate unpleasant bugs

	//The values of the coefficents are taken from Appendix E in the Deserno and Holm paper

	Scalar **Exact=new Scalar*[P_order];
	for(unsigned int i=0;i<P_order;i++) Exact[i]=new Scalar[P_order];

	Exact[0][0]=1.0;Exact[0][1]=-10.0;Exact[0][2]=40.0;Exact[0][3]=-80.0;Exact[0][4]=80.0;Exact[0][5]=-32.0;
	Exact[1][0]=237.0;Exact[1][1]=-750.0;Exact[1][2]=840.0;Exact[1][3]=-240.0;Exact[1][4]=-240.0;Exact[1][5]=160.0;
	Exact[2][0]=1682.0;Exact[2][1]=-1540.0;Exact[2][2]=-880.0;Exact[2][3]=1120.0;Exact[2][4]=160.0;Exact[2][5]=-320.0;
	Exact[3][0]=1682.0;Exact[3][1]=1540.0;Exact[3][2]=880.0;Exact[3][3]=-1120.0;Exact[3][4]=-160.0;Exact[3][5]=320.0;
	Exact[4][0]=237.0;Exact[4][1]=750.0;Exact[4][2]=840.0;Exact[4][3]=240.0;Exact[4][4]=-240.0;Exact[4][5]=-160.0;
	Exact[5][0]=1.0;Exact[5][1]=10.0;Exact[5][2]=40.0;Exact[5][3]=80.0;Exact[5][4]=80.0;Exact[5][5]=32.0;

	for(unsigned int i=0;i<P_order;i++){
		for(unsigned int j=0;j<P_order;j++){
				Exact[i][j]*=static_cast<Scalar>(1/3840.0);
		}
	}

	for(unsigned int i=0;i<P_order;i++){
		for(unsigned int j=0;j<P_order;j++){
				MY_BOOST_CHECK_CLOSE(Exact[i][j],PPPM_6->Poly_coeff_Grid(i,j),tol);
		}
	}

	//Check passed, now let us compute the charges on the grid

}

//! ElectrostaticShortRange creator for unit tests
shared_ptr<ElectrostaticLongRangePPPM> base_class_PPPM_creator(shared_ptr<ParticleData> pdata,unsigned int Nmesh_x,unsigned int Nmesh_y,unsigned int Nmesh_z, unsigned int P_order, Scalar alpha,shared_ptr<FFTClass> FFTW,bool third_law_m)
	{
	return shared_ptr<ElectrostaticLongRangePPPM>(new ElectrostaticLongRangePPPM(pdata, Nmesh_x, Nmesh_y, Nmesh_z, P_order,alpha,FFTW,third_law_m));
	}

//! boost test case for particle test on CPU
BOOST_AUTO_TEST_CASE(LongRangePPPM_PositionGrid_test)
{
	LongRangePPPM_creator LongRangePPPM_creator_base = bind(base_class_PPPM_creator, _1, _2, _3, _4, _5,_6,_7,_8);
	LongRangePPPM_PositionGrid(LongRangePPPM_creator_base);
}

#endif

#ifdef WIN32
#pragma warning( pop )
#endif
<|endoftext|>


<commit_before>/******************************************************************************
*       SOFA, Simulation Open-Framework Architecture, development version     *
*                (c) 2006-2017 INRIA, USTL, UJF, CNRS, MGH                    *
*                                                                             *
* This program is free software; you can redistribute it and/or modify it     *
* under the terms of the GNU Lesser General Public License as published by    *
* the Free Software Foundation; either version 2.1 of the License, or (at     *
* your option) any later version.                                             *
*                                                                             *
* This program is distributed in the hope that it will be useful, but WITHOUT *
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or       *
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License *
* for more details.                                                           *
*                                                                             *
* You should have received a copy of the GNU Lesser General Public License    *
* along with this program. If not, see <http://www.gnu.org/licenses/>.        *
*******************************************************************************
* Authors: The SOFA Team and external contributors (see Authors.txt)          *
*                                                                             *
* Contact information: contact@sofa-framework.org                             *
******************************************************************************/
#include <SofaTest/TestMessageHandler.h>

////////////////////////////////////////////////////////////////////////////////////////////////////
/// This file organization:
///     - PRIVATE DECLARATION  (the class that are only used internally)
///     - PRIVATE DEFINITION   (the implementation of the private classes)
///     - PUBLIC  DEFINITION   (the implementation of the public classes)
////////////////////////////////////////////////////////////////////////////////////////////////////
namespace sofa
{

namespace helper
{

namespace logging
{

////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////// PRIVATE DECLARATION /////////////////////////////////////////////////
/// Here are declared the classes that are only used for internal use.
/// In case someone want to use them it is easy to move that in .h file.
/// Until that happens, keeps these here to hide the implementation details from the users of the .h
/// And accelerate compilation of Sofa :)
////////////////////////////////////////////////////////////////////////////////////////////////////
class GtestMessageFrame
{
public:
    virtual ~GtestMessageFrame() {}

    virtual void process(Message& /*m*/) {}
    virtual void finalize() {}

public:
    Message::Type m_type;
    const char* m_filename;
    int   m_lineno ;
};

class GtestMessageFrameFailure : public GtestMessageFrame
{
public:

    GtestMessageFrameFailure(Message::Type type,
                             const char* filename, int lineno) ;
    virtual void process(Message& message) ;
};

class GtestMessageFrameFailureWhenMissing  : public GtestMessageFrame
{
public:
    bool  m_gotMessage {false} ;

    GtestMessageFrameFailureWhenMissing(Message::Type type,
                                        const char* filename,  int lineno) ;

    virtual void process(Message& message) ;
    virtual void finalize() ;
};

class GtestMessageFrameIgnore  : public GtestMessageFrame
{
public:
    GtestMessageFrameIgnore(Message::Type type) ;
};

class SOFA_TestPlugin_API GtestMessageHandler : public MessageHandler
{
    std::vector<std::vector<GtestMessageFrame*> > m_gtestframes;

public:
    GtestMessageHandler(Message::Class mclass) ;
    virtual ~ GtestMessageHandler();

    /// Inherited from MessageHandler
    virtual void process(Message& m) ;
    void pushFrame(Message::Type type, GtestMessageFrame* frame)  ;
    void popFrame(Message::Type type) ;
};

class SOFA_TestPlugin_API MainGtestMessageHandlerPrivate
{
public:
    static GtestMessageHandler& getInstance() ;
    static void pushFrame(Message::Type type, GtestMessageFrame* frame) ;
    static void popFrame(Message::Type type) ;
};

////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////// DEFINITION OF PRIVATE CLASSES //////////////////////////////////
///
///
////////////////////////////////////////////////////////////////////////////////////////////////////
GtestMessageFrameFailure::GtestMessageFrameFailure(Message::Type type,
                                                   const char* filename,
                                                   int lineno)
{
    m_type = type;
    m_filename = filename;
    m_lineno = lineno;
}

void GtestMessageFrameFailure::process(Message& message) {
    std::stringstream backlog;
    backlog << "====================== Messages =======================" << std::endl ;
    backlog << message << std::endl ;
    backlog << "===============================================================" << std::endl ;

    ADD_FAILURE_AT(m_filename, m_lineno) << "A message of type '" << toString(message.type())
                                         << "' was not expected but it was received. " << std::endl
                                         << backlog.str() ;
}

GtestMessageFrameFailureWhenMissing::GtestMessageFrameFailureWhenMissing(Message::Type type,
                                                                         const char* filename,
                                                                         int lineno)
{
    m_type = type;
    m_filename = filename;
    m_lineno = lineno;
}

void  GtestMessageFrameFailureWhenMissing::process(Message& message) {
    SOFA_UNUSED(message);
    m_gotMessage = true ;
}

void GtestMessageFrameFailureWhenMissing::finalize(){
    if(!m_gotMessage)
        ADD_FAILURE_AT(m_filename, m_lineno) << "A message of type '" << toString(m_type)
                                             << "' was expected but none was received. " << std::endl ;
}

GtestMessageFrameIgnore::GtestMessageFrameIgnore(Message::Type type)
{
    m_type = type;
    m_filename = "";
    m_lineno = -1;
}

GtestMessageHandler::GtestMessageHandler(Message::Class mclass)
{
    for(unsigned int i=0; i < Message::TypeCount ; ++i)
    {
        m_gtestframes.push_back( std::vector<GtestMessageFrame*>({new GtestMessageFrame()}) ) ;
    }
}

void GtestMessageHandler::process(Message& m)
{
    m_gtestframes[m.type()].back()->process(m) ;
}

GtestMessageHandler::~GtestMessageHandler()
{
    for(unsigned int i=0; i < Message::TypeCount ; ++i)
    {
        assert(m_gtestframes.size() >= 1) ;
        delete m_gtestframes[i][0] ;
    }
}

void GtestMessageHandler::pushFrame(Message::Type type,
                                    GtestMessageFrame* frame){
    m_gtestframes[type].push_back(frame) ;
}

void GtestMessageHandler::popFrame(Message::Type type){
    m_gtestframes[type].pop_back() ;
}

MessageHandler* MainGtestMessageHandler::getInstance(){
    return &MainGtestMessageHandlerPrivate::getInstance() ;
}

GtestMessageHandler& MainGtestMessageHandlerPrivate::getInstance(){
    static GtestMessageHandler instance(Message::Runtime) ;
    return instance ;
}

void MainGtestMessageHandlerPrivate::pushFrame(Message::Type type, GtestMessageFrame *frame){
    getInstance().pushFrame(type, frame) ;
}

void MainGtestMessageHandlerPrivate::popFrame(Message::Type type){
    getInstance().popFrame(type) ;
}

////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////// DEFINITION OF PUBLIC CLASSES ///////////////////////////////////
///
///
////////////////////////////////////////////////////////////////////////////////////////////////////
MessageAsTestFailure::MessageAsTestFailure(Message::Type type,
                                               const char* filename, int lineno)
{
    m_frame = new GtestMessageFrameFailure(type, filename, lineno) ;
    MainGtestMessageHandlerPrivate::pushFrame(type, m_frame) ;
}

MessageAsTestFailure::~MessageAsTestFailure(){
    MainGtestMessageHandlerPrivate::popFrame( m_frame->m_type ) ;
    m_frame->finalize() ;
    delete m_frame ;
}

ExpectMessage::ExpectMessage(Message::Type type,
                               const char* filename, int lineno)
{
    m_frame = new GtestMessageFrameFailureWhenMissing(type, filename, lineno);
    MainGtestMessageHandlerPrivate::pushFrame(type, m_frame ) ;
}

ExpectMessage::~ExpectMessage(){
    MainGtestMessageHandlerPrivate::popFrame(m_frame->m_type) ;
    m_frame->finalize() ;
    delete m_frame ;
}

IgnoreMessage::IgnoreMessage(Message::Type type)
{
    m_frame = new GtestMessageFrameIgnore(type);
    MainGtestMessageHandlerPrivate::pushFrame(type, m_frame ) ;
}

IgnoreMessage::~IgnoreMessage(){
    MainGtestMessageHandlerPrivate::popFrame(m_frame->m_type) ;
    m_frame->finalize() ;
    delete m_frame ;
}

} // logging
} // helper

} // sofa

<commit_msg>[SofaTest] Remove a compilation warning.<commit_after>/******************************************************************************
*       SOFA, Simulation Open-Framework Architecture, development version     *
*                (c) 2006-2017 INRIA, USTL, UJF, CNRS, MGH                    *
*                                                                             *
* This program is free software; you can redistribute it and/or modify it     *
* under the terms of the GNU Lesser General Public License as published by    *
* the Free Software Foundation; either version 2.1 of the License, or (at     *
* your option) any later version.                                             *
*                                                                             *
* This program is distributed in the hope that it will be useful, but WITHOUT *
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or       *
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License *
* for more details.                                                           *
*                                                                             *
* You should have received a copy of the GNU Lesser General Public License    *
* along with this program. If not, see <http://www.gnu.org/licenses/>.        *
*******************************************************************************
* Authors: The SOFA Team and external contributors (see Authors.txt)          *
*                                                                             *
* Contact information: contact@sofa-framework.org                             *
******************************************************************************/
#include <SofaTest/TestMessageHandler.h>

////////////////////////////////////////////////////////////////////////////////////////////////////
/// This file organization:
///     - PRIVATE DECLARATION  (the class that are only used internally)
///     - PRIVATE DEFINITION   (the implementation of the private classes)
///     - PUBLIC  DEFINITION   (the implementation of the public classes)
////////////////////////////////////////////////////////////////////////////////////////////////////
namespace sofa
{

namespace helper
{

namespace logging
{

////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////// PRIVATE DECLARATION /////////////////////////////////////////////////
/// Here are declared the classes that are only used for internal use.
/// In case someone want to use them it is easy to move that in .h file.
/// Until that happens, keeps these here to hide the implementation details from the users of the .h
/// And accelerate compilation of Sofa :)
////////////////////////////////////////////////////////////////////////////////////////////////////
class GtestMessageFrame
{
public:
    virtual ~GtestMessageFrame() {}

    virtual void process(Message& /*m*/) {}
    virtual void finalize() {}

public:
    Message::Type m_type;
    const char* m_filename;
    int   m_lineno ;
};

class GtestMessageFrameFailure : public GtestMessageFrame
{
public:

    GtestMessageFrameFailure(Message::Type type,
                             const char* filename, int lineno) ;
    virtual void process(Message& message) ;
};

class GtestMessageFrameFailureWhenMissing  : public GtestMessageFrame
{
public:
    bool  m_gotMessage {false} ;

    GtestMessageFrameFailureWhenMissing(Message::Type type,
                                        const char* filename,  int lineno) ;

    virtual void process(Message& message) ;
    virtual void finalize() ;
};

class GtestMessageFrameIgnore  : public GtestMessageFrame
{
public:
    GtestMessageFrameIgnore(Message::Type type) ;
};

class SOFA_TestPlugin_API GtestMessageHandler : public MessageHandler
{
    std::vector<std::vector<GtestMessageFrame*> > m_gtestframes;

public:
    GtestMessageHandler(Message::Class mclass) ;
    virtual ~ GtestMessageHandler();

    /// Inherited from MessageHandler
    virtual void process(Message& m) ;
    void pushFrame(Message::Type type, GtestMessageFrame* frame)  ;
    void popFrame(Message::Type type) ;
};

class SOFA_TestPlugin_API MainGtestMessageHandlerPrivate
{
public:
    static GtestMessageHandler& getInstance() ;
    static void pushFrame(Message::Type type, GtestMessageFrame* frame) ;
    static void popFrame(Message::Type type) ;
};

////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////// DEFINITION OF PRIVATE CLASSES //////////////////////////////////
///
///
////////////////////////////////////////////////////////////////////////////////////////////////////
GtestMessageFrameFailure::GtestMessageFrameFailure(Message::Type type,
                                                   const char* filename,
                                                   int lineno)
{
    m_type = type;
    m_filename = filename;
    m_lineno = lineno;
}

void GtestMessageFrameFailure::process(Message& message) {
    std::stringstream backlog;
    backlog << "====================== Messages =======================" << std::endl ;
    backlog << message << std::endl ;
    backlog << "===============================================================" << std::endl ;

    ADD_FAILURE_AT(m_filename, m_lineno) << "A message of type '" << toString(message.type())
                                         << "' was not expected but it was received. " << std::endl
                                         << backlog.str() ;
}

GtestMessageFrameFailureWhenMissing::GtestMessageFrameFailureWhenMissing(Message::Type type,
                                                                         const char* filename,
                                                                         int lineno)
{
    m_type = type;
    m_filename = filename;
    m_lineno = lineno;
}

void  GtestMessageFrameFailureWhenMissing::process(Message& message) {
    SOFA_UNUSED(message);
    m_gotMessage = true ;
}

void GtestMessageFrameFailureWhenMissing::finalize(){
    if(!m_gotMessage)
        ADD_FAILURE_AT(m_filename, m_lineno) << "A message of type '" << toString(m_type)
                                             << "' was expected but none was received. " << std::endl ;
}

GtestMessageFrameIgnore::GtestMessageFrameIgnore(Message::Type type)
{
    m_type = type;
    m_filename = "";
    m_lineno = -1;
}

GtestMessageHandler::GtestMessageHandler(Message::Class mclass)
{
    SOFA_UNUSED(mclass) ;
    for(unsigned int i=0; i < Message::TypeCount ; ++i)
    {
        m_gtestframes.push_back( std::vector<GtestMessageFrame*>({new GtestMessageFrame()}) ) ;
    }
}

void GtestMessageHandler::process(Message& m)
{
    m_gtestframes[m.type()].back()->process(m) ;
}

GtestMessageHandler::~GtestMessageHandler()
{
    for(unsigned int i=0; i < Message::TypeCount ; ++i)
    {
        assert(m_gtestframes.size() >= 1) ;
        delete m_gtestframes[i][0] ;
    }
}

void GtestMessageHandler::pushFrame(Message::Type type,
                                    GtestMessageFrame* frame){
    m_gtestframes[type].push_back(frame) ;
}

void GtestMessageHandler::popFrame(Message::Type type){
    m_gtestframes[type].pop_back() ;
}

MessageHandler* MainGtestMessageHandler::getInstance(){
    return &MainGtestMessageHandlerPrivate::getInstance() ;
}

GtestMessageHandler& MainGtestMessageHandlerPrivate::getInstance(){
    static GtestMessageHandler instance(Message::Runtime) ;
    return instance ;
}

void MainGtestMessageHandlerPrivate::pushFrame(Message::Type type, GtestMessageFrame *frame){
    getInstance().pushFrame(type, frame) ;
}

void MainGtestMessageHandlerPrivate::popFrame(Message::Type type){
    getInstance().popFrame(type) ;
}

////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////// DEFINITION OF PUBLIC CLASSES ///////////////////////////////////
///
///
////////////////////////////////////////////////////////////////////////////////////////////////////
MessageAsTestFailure::MessageAsTestFailure(Message::Type type,
                                               const char* filename, int lineno)
{
    m_frame = new GtestMessageFrameFailure(type, filename, lineno) ;
    MainGtestMessageHandlerPrivate::pushFrame(type, m_frame) ;
}

MessageAsTestFailure::~MessageAsTestFailure(){
    MainGtestMessageHandlerPrivate::popFrame( m_frame->m_type ) ;
    m_frame->finalize() ;
    delete m_frame ;
}

ExpectMessage::ExpectMessage(Message::Type type,
                               const char* filename, int lineno)
{
    m_frame = new GtestMessageFrameFailureWhenMissing(type, filename, lineno);
    MainGtestMessageHandlerPrivate::pushFrame(type, m_frame ) ;
}

ExpectMessage::~ExpectMessage(){
    MainGtestMessageHandlerPrivate::popFrame(m_frame->m_type) ;
    m_frame->finalize() ;
    delete m_frame ;
}

IgnoreMessage::IgnoreMessage(Message::Type type)
{
    m_frame = new GtestMessageFrameIgnore(type);
    MainGtestMessageHandlerPrivate::pushFrame(type, m_frame ) ;
}

IgnoreMessage::~IgnoreMessage(){
    MainGtestMessageHandlerPrivate::popFrame(m_frame->m_type) ;
    m_frame->finalize() ;
    delete m_frame ;
}

} // logging
} // helper

} // sofa

<|endoftext|>


<commit_before>
#include "consensus/params.h"
#include "consensus/upgrades.h"

namespace Consensus {

bool Params::NetworkUpgradeActive(int nHeight, Consensus::UpgradeIndex idx) const
{
        return NetworkUpgradeState(nHeight, *this, idx) == UPGRADE_ACTIVE;
}

} // End consensus namespace<commit_msg>[Consensus] NetworkUpgradeActive: return false and log if height<0<commit_after>// Copyright (c) 2019 The Zcash developers
// Copyright (c) 2020 The PIVX developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or https://www.opensource.org/licenses/mit-license.php .

#include "consensus/params.h"
#include "consensus/upgrades.h"
#include "util.h"

namespace Consensus {

bool Params::NetworkUpgradeActive(int nHeight, Consensus::UpgradeIndex idx) const
{
    if (idx >= Consensus::MAX_NETWORK_UPGRADES)
        return error("%s: Upgrade index out of bounds: %d >= %d",
                __func__, idx, Consensus::MAX_NETWORK_UPGRADES);

    if (nHeight < 0)
        return error("%s: Requested state for upgrade %s at negative height %d",
                __func__, NetworkUpgradeInfo[idx].strName, nHeight);

    return NetworkUpgradeState(nHeight, *this, idx) == UPGRADE_ACTIVE;
}

} // End consensus namespace
<|endoftext|>


<commit_before>/*
Highly Optimized Object-Oriented Molecular Dynamics (HOOMD) Open
Source Software License
Copyright (c) 2008 Ames Laboratory Iowa State University
All rights reserved.

Redistribution and use of HOOMD, in source and binary forms, with or
without modification, are permitted, provided that the following
conditions are met:

* Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.

* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.

* Neither the name of the copyright holder nor the names HOOMD's
contributors may be used to endorse or promote products derived from this
software without specific prior written permission.

Disclaimer

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER AND
CONTRIBUTORS ``AS IS''  AND ANY EXPRESS OR IMPLIED WARRANTIES,
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.

IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS  BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
THE POSSIBILITY OF SUCH DAMAGE.
*/

#ifdef WIN32
#pragma warning( push )
#pragma warning( disable : 4103 4244 )
#endif

#include <iostream>

//! Name the unit test module
#define BOOST_TEST_MODULE ElectrostaticLongRangePPPMTests
#include "boost_utf_configure.h"

#include <boost/test/floating_point_comparison.hpp>
#include <boost/bind.hpp>
#include <boost/function.hpp>
#include <boost/shared_ptr.hpp>

#include "ParticleData.h"

#include <math.h>

using namespace std;
using namespace boost;

//You need a fft defined in order to pass this text
#ifdef USE_FFTW

#include "ElectrostaticLongRangePPPM.h"
#include "FFTClass.h"
#include "FftwWrapper.h"

/*! \file ElectrostaticLongRange_PPPM_test.cc
	\brief Implements unit tests for ElectrostaticLongRangePPPM and descendants
	\ingroup unit_tests
*/

//! Helper macro for testing if two numbers are close
#define MY_BOOST_CHECK_CLOSE(a,b,c) BOOST_CHECK_CLOSE(a,Scalar(b),Scalar(c))
//! Helper macro for testing if a number is small
#define MY_BOOST_CHECK_SMALL(a,c) BOOST_CHECK_SMALL(a,Scalar(c))

//! Tolerance in percent to use for comparing various ElectrostaticLongRangePPPM to each other
const Scalar tol = Scalar(1);
//! minimum force worth computing
const Scalar MIN_force=Scalar(1.0e-9);

//! Typedef ElectrostaticLongRangePPPM factory
typedef function<shared_ptr<ElectrostaticLongRangePPPM> (shared_ptr<ParticleData> pdata,unsigned int Mmesh_x,unsigned int Mmesh_y,unsigned int Mmesh_z, unsigned int P_order_a, Scalar alpha, shared_ptr<FFTClass> FFTP,bool third_law_m)> LongRangePPPM_creator;

//! Test the ability of the Short Range Electrostatic force compute to actually calculate forces
/*! \param LongRangePPPM_object_n1 I have no idea: the write of this code needs to document it better
	\note With the creator as a parameter, the same code can be used to test any derived child
		of ElectrostaticLongRangePPPM
*/
void LongRangePPPM_PositionGrid(LongRangePPPM_creator LongRangePPPM_object_n1)
	{
	cout << "Testing charge distribution on the grid in class ElectrostaticLongRangePPPM" << endl;
	// Simple test to check that the charge is defined correctly on the grid
	shared_ptr<ParticleData> pdata_6(new ParticleData(6, BoxDim(20.0,40.0,60.0), 1));
	ParticleDataArrays arrays = pdata_6->acquireReadWrite();

	// six charges are located near the edge of the box
	arrays.x[0]=Scalar(-9.6);arrays.y[0]=Scalar(0.0);arrays.z[0]=Scalar(0.0);arrays.charge[0]=1.0;
    arrays.x[1]=Scalar(9.6);arrays.y[1]=Scalar(0.0);arrays.z[1]=Scalar(0.0);arrays.charge[1]=-2.0;
	arrays.x[2]=Scalar(0.0);arrays.y[2]=Scalar(-19.5);arrays.z[2]=Scalar(0.0);arrays.charge[2]=1.0;
    arrays.x[3]=Scalar(0.0);arrays.y[3]=Scalar(19.5);arrays.z[3]=Scalar(0.0);arrays.charge[3]=3.0;
    arrays.x[4]=Scalar(0.0);arrays.y[4]=Scalar(0.0);arrays.z[4]=Scalar(-29.4);arrays.charge[4]=-1.0;
    arrays.x[5]=Scalar(0.0);arrays.y[5]=Scalar(0.0);arrays.z[5]=Scalar(29.4);arrays.charge[5]=-2.0;

	// allow for acquiring data in the future
	pdata_6->release();

    // Define mesh parameters as well as order of the distribution, etc..
	unsigned int Nmesh_x=40;
	unsigned int Nmesh_y=80;
	unsigned int Nmesh_z=120;
	unsigned int P_order=6;
	Scalar alpha=4.0;
	shared_ptr<FftwWrapper> FFTW(new  FftwWrapper(Nmesh_x,Nmesh_y,Nmesh_z));
	bool third_law=false;

	//shared_ptr<ElectrostaticLongRangePPPM> PPPM_6=LongRangePPPM_object_n1(pdata_6,Nmesh_x,Nmesh_y,Nmesh_z,P_order,alpha,FFTW,third_law);
	// An ElectrostaticLongRangePPPM object with specified value of grid parameters, alpha, and fft routine instantiated

	// now let us check that the charges are correctly distributed

	//First check that the polynomials used to spread the charge on the grid are what they should be
	//This may eliminate unpleasant bugs

	//The values of the coefficents are taken from Appendix E in the Deserno and Holm paper

	Scalar **Exact=new Scalar*[P_order];
	for(unsigned int i=0;i<P_order;i++) Exact[i]=new Scalar[P_order];

	Exact[0][0]=1.0;Exact[0][1]=-10.0;Exact[0][2]=40.0;Exact[0][3]=-80.0;Exact[0][4]=80.0;Exact[0][5]=-32.0;
	Exact[1][0]=237.0;Exact[1][1]=-750.0;Exact[1][2]=840.0;Exact[1][3]=-240.0;Exact[1][4]=-240.0;Exact[1][5]=160.0;
	Exact[2][0]=1682.0;Exact[2][1]=-1540.0;Exact[2][2]=-880.0;Exact[2][3]=1120.0;Exact[2][4]=160.0;Exact[2][5]=-320.0;
	Exact[3][0]=1682.0;Exact[3][1]=1540.0;Exact[3][2]=880.0;Exact[3][3]=-1120.0;Exact[3][4]=-160.0;Exact[3][5]=320.0;
	Exact[4][0]=237.0;Exact[4][1]=750.0;Exact[4][2]=840.0;Exact[4][3]=240.0;Exact[4][4]=-240.0;Exact[4][5]=-160.0;
	Exact[5][0]=1.0;Exact[5][1]=10.0;Exact[5][2]=40.0;Exact[5][3]=80.0;Exact[5][4]=80.0;Exact[5][5]=32.0;

	for(unsigned int i=0;i<P_order;i++){
		for(unsigned int j=0;j<P_order;j++){
				Exact[i][j]*=static_cast<Scalar>(1/3840.0);
		}
	}

	for(unsigned int i=0;i<P_order;i++){
		for(unsigned int j=0;j<P_order;j++){
				//MY_BOOST_CHECK_CLOSE(Exact[i][j],PPPM_6->Poly_coeff_Grid(i,j),tol);
		}
	}

	//Check passed, now let us compute the charges on the grid
	for(unsigned int i=0;i<P_order;i++) delete[] Exact[i];
	delete [] Exact;
}

//! ElectrostaticShortRange creator for unit tests
shared_ptr<ElectrostaticLongRangePPPM> base_class_PPPM_creator(shared_ptr<ParticleData> pdata,unsigned int Nmesh_x,unsigned int Nmesh_y,unsigned int Nmesh_z, unsigned int P_order, Scalar alpha,shared_ptr<FFTClass> FFTW,bool third_law_m)
	{
	return shared_ptr<ElectrostaticLongRangePPPM>(new ElectrostaticLongRangePPPM(pdata, Nmesh_x, Nmesh_y, Nmesh_z, P_order,alpha,FFTW,third_law_m));
	}

//! boost test case for particle test on CPU
BOOST_AUTO_TEST_CASE(LongRangePPPM_PositionGrid_test)
{
	LongRangePPPM_creator LongRangePPPM_creator_base = bind(base_class_PPPM_creator, _1, _2, _3, _4, _5,_6,_7,_8);
	LongRangePPPM_PositionGrid(LongRangePPPM_creator_base);
}

#else

// We can't have the unit test passing if the code wasn't even compiled!
BOOST_AUTO_TEST_CASE(dummy_test)
	{
	BOOST_FAIL("ElectrostaticLongRange_PPPM not compiled");
	}
#endif

#ifdef WIN32
#pragma warning( pop )
#endif
<commit_msg>Unit test not only compiles, it works. More tests are on the way. ticket #79<commit_after>/*
Highly Optimized Object-Oriented Molecular Dynamics (HOOMD) Open
Source Software License
Copyright (c) 2008 Ames Laboratory Iowa State University
All rights reserved.

Redistribution and use of HOOMD, in source and binary forms, with or
without modification, are permitted, provided that the following
conditions are met:

* Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.

* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.

* Neither the name of the copyright holder nor the names HOOMD's
contributors may be used to endorse or promote products derived from this
software without specific prior written permission.

Disclaimer

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER AND
CONTRIBUTORS ``AS IS''  AND ANY EXPRESS OR IMPLIED WARRANTIES,
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.

IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS  BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
THE POSSIBILITY OF SUCH DAMAGE.
*/

#ifdef WIN32
#pragma warning( push )
#pragma warning( disable : 4103 4244 )
#endif

#include <iostream>

//! Name the unit test module
#define BOOST_TEST_MODULE ElectrostaticLongRangePPPMTests
#include "boost_utf_configure.h"

#include <boost/test/floating_point_comparison.hpp>
#include <boost/bind.hpp>
#include <boost/function.hpp>
#include <boost/shared_ptr.hpp>

#include "ParticleData.h"
#include "IndexTransform.h"

#include <math.h>

using namespace std;
using namespace boost;

//You need a fft defined in order to pass this text
#ifdef USE_FFTW

#include "ElectrostaticLongRangePPPM.h"
#include "FFTClass.h"
#include "FftwWrapper.h"

/*! \file ElectrostaticLongRange_PPPM_test.cc
	\brief Implements unit tests for ElectrostaticLongRangePPPM and descendants
	\ingroup unit_tests
*/

//! Helper macro for testing if two numbers are close
#define MY_BOOST_CHECK_CLOSE(a,b,c) BOOST_CHECK_CLOSE(a,Scalar(b),Scalar(c))
//! Helper macro for testing if a number is small
#define MY_BOOST_CHECK_SMALL(a,c) BOOST_CHECK_SMALL(a,Scalar(c))

//! Tolerance in percent to use for comparing various ElectrostaticLongRangePPPM to each other
const Scalar tol = Scalar(1);
//! minimum force worth computing
const Scalar MIN_force=Scalar(1.0e-9);

//! Typedef ElectrostaticLongRangePPPM factory
typedef function<shared_ptr<ElectrostaticLongRangePPPM> (shared_ptr<ParticleData> pdata,unsigned int Mmesh_x,unsigned int Mmesh_y,unsigned int Mmesh_z, unsigned int P_order_a, Scalar alpha, shared_ptr<FFTClass> FFTP,bool third_law_m)> LongRangePPPM_creator;

//! Test the ability of the Short Range Electrostatic force compute to actually calculate forces
/*! \param LongRangePPPM_object_n1 I have no idea: the write of this code needs to document it better
	\note With the creator as a parameter, the same code can be used to test any derived child
		of ElectrostaticLongRangePPPM
*/
void LongRangePPPM_PositionGrid(LongRangePPPM_creator LongRangePPPM_object_n1)
	{
	cout << "Testing charge distribution on the grid in class ElectrostaticLongRangePPPM" << endl;
	// Simple test to check that the charge is defined correctly on the grid
	shared_ptr<ParticleData> pdata_6(new ParticleData(6, BoxDim(20.0,40.0,60.0), 1));
	ParticleDataArrays arrays = pdata_6->acquireReadWrite();

	// six charges are located near the edge of the box
	arrays.x[0]=Scalar(-9.6);arrays.y[0]=Scalar(0.0);arrays.z[0]=Scalar(0.0);arrays.charge[0]=1.0;
    arrays.x[1]=Scalar(9.6);arrays.y[1]=Scalar(0.0);arrays.z[1]=Scalar(0.0);arrays.charge[1]=-2.0;
	arrays.x[2]=Scalar(0.0);arrays.y[2]=Scalar(-19.5);arrays.z[2]=Scalar(0.0);arrays.charge[2]=1.0;
    arrays.x[3]=Scalar(0.0);arrays.y[3]=Scalar(19.5);arrays.z[3]=Scalar(0.0);arrays.charge[3]=3.0;
    arrays.x[4]=Scalar(0.0);arrays.y[4]=Scalar(0.0);arrays.z[4]=Scalar(-29.4);arrays.charge[4]=-1.0;
    arrays.x[5]=Scalar(0.0);arrays.y[5]=Scalar(0.0);arrays.z[5]=Scalar(29.4);arrays.charge[5]=-2.0;

	// allow for acquiring data in the future
	pdata_6->release();

    // Define mesh parameters as well as order of the distribution, etc..
	unsigned int Nmesh_x=20;
	unsigned int Nmesh_y=8;
	unsigned int Nmesh_z=20;
	unsigned int P_order=6;
	Scalar alpha=4.0;
	shared_ptr<FftwWrapper> FFTW(new  FftwWrapper(Nmesh_x,Nmesh_y,Nmesh_z));
	bool third_law=false;

	shared_ptr<ElectrostaticLongRangePPPM> PPPM_6=LongRangePPPM_object_n1(pdata_6,Nmesh_x,Nmesh_y,Nmesh_z,P_order,alpha,FFTW,third_law);
	// An ElectrostaticLongRangePPPM object with specified value of grid parameters, alpha, and fft routine instantiated

	// now let us check that the charges are correctly distributed

	//First check that the polynomials used to spread the charge on the grid are what they should be
	//This may eliminate unpleasant bugs

	//The values of the coefficents are taken from Appendix E in the Deserno and Holm paper

	Scalar **Exact=new Scalar*[P_order];
	for(unsigned int i=0;i<P_order;i++) Exact[i]=new Scalar[P_order];

	Exact[0][0]=1.0;Exact[0][1]=-10.0;Exact[0][2]=40.0;Exact[0][3]=-80.0;Exact[0][4]=80.0;Exact[0][5]=-32.0;
	Exact[1][0]=237.0;Exact[1][1]=-750.0;Exact[1][2]=840.0;Exact[1][3]=-240.0;Exact[1][4]=-240.0;Exact[1][5]=160.0;
	Exact[2][0]=1682.0;Exact[2][1]=-1540.0;Exact[2][2]=-880.0;Exact[2][3]=1120.0;Exact[2][4]=160.0;Exact[2][5]=-320.0;
	Exact[3][0]=1682.0;Exact[3][1]=1540.0;Exact[3][2]=-880.0;Exact[3][3]=-1120.0;Exact[3][4]=160.0;Exact[3][5]=320.0;
	Exact[4][0]=237.0;Exact[4][1]=750.0;Exact[4][2]=840.0;Exact[4][3]=240.0;Exact[4][4]=-240.0;Exact[4][5]=-160.0;
	Exact[5][0]=1.0;Exact[5][1]=10.0;Exact[5][2]=40.0;Exact[5][3]=80.0;Exact[5][4]=80.0;Exact[5][5]=32.0;

	for(unsigned int i=0;i<P_order;i++){
		for(unsigned int j=0;j<P_order;j++){
				Exact[i][j]*=static_cast<Scalar>(1/3840.0);
		}
	}

	for(unsigned int i=0;i<P_order;i++){
		for(unsigned int j=0;j<P_order;j++){
			    //high accuracy test 0.001%
				MY_BOOST_CHECK_CLOSE(Exact[i][j],PPPM_6->Poly_coeff_Grid(i,j),0.001*tol);
		}
	}

    //Check passed, Polynomial coeffs are good, now let us compute the charges on the grid

	// First define a matrix of real numbers
	Scalar *rho_by_hand=new Scalar[Nmesh_x*Nmesh_y*Nmesh_z];
	//Define a class to transform indices
	IndexTransform T;
	T.SetD3to1D(Nmesh_x,Nmesh_y,Nmesh_z);
	unsigned int ind;

	// Initialize to zero
	for(unsigned int i=0;i<Nmesh_x;i++){
	for(unsigned int j=0;j<Nmesh_y;j++){
	for(unsigned int k=0;k<Nmesh_z;k++){
		ind=T.D3To1D(i,j,k);
		rho_by_hand[ind]=0.0;
	}
	}
	}

	//Distribute the first point on the grid
	{
	unsigned int i,j,k;
	int i_h;
	Scalar xs=0.0;
	for(unsigned int l=0;l<P_order;l++){
		i_h=-static_cast<int>(P_order/2)+1+l;
		i=static_cast<unsigned int>(i_h+((Nmesh_x-i_h-1)/Nmesh_x)*Nmesh_x);
		xs=0.0;
		for(int ii=static_cast<int>(P_order)-1;ii>=0;ii--) xs+=Exact[l][i]+0.4*xs;
		for(unsigned int m=0;m<P_order;m++){
			j=Nmesh_y/2-P_order/2+1+m;
			for(unsigned int n=0;n<P_order;n++){
				k=Nmesh_z/2-P_order/2+1+n;
			ind=T.D3To1D(i,j,k);
			rho_by_hand[ind]=xs*Exact[m][0]*Exact[n][0];;
						}
				}
		}
	}

	delete[] rho_by_hand;

	for(unsigned int i=P_order;i>0;--i) delete[] Exact[i-1];
	delete[] Exact;
}

//! ElectrostaticShortRange creator for unit tests
shared_ptr<ElectrostaticLongRangePPPM> base_class_PPPM_creator(shared_ptr<ParticleData> pdata,unsigned int Nmesh_x,unsigned int Nmesh_y,unsigned int Nmesh_z, unsigned int P_order, Scalar alpha,shared_ptr<FFTClass> FFTW,bool third_law_m)
	{
	return shared_ptr<ElectrostaticLongRangePPPM>(new ElectrostaticLongRangePPPM(pdata, Nmesh_x, Nmesh_y, Nmesh_z, P_order,alpha,FFTW,third_law_m));
	}

//! boost test case for particle test on CPU
BOOST_AUTO_TEST_CASE(LongRangePPPM_PositionGrid_test)
{
	LongRangePPPM_creator LongRangePPPM_creator_base = bind(base_class_PPPM_creator, _1, _2, _3, _4, _5,_6,_7,_8);
	LongRangePPPM_PositionGrid(LongRangePPPM_creator_base);
}

#else

// We can't have the unit test passing if the code wasn't even compiled!
BOOST_AUTO_TEST_CASE(dummy_test)
	{
	BOOST_FAIL("ElectrostaticLongRange_PPPM not compiled");
	}
#endif

#ifdef WIN32
#pragma warning( pop )
#endif
<|endoftext|>


<commit_before>#ifdef _WIN32

#include <assert.h>

#include "win_fsnotifier.h"

using namespace std;

//
// WatchPoint
//

static void CALLBACK listenCallback(_In_ ULONG_PTR arg) {
    WatchPoint* watchPoint = (WatchPoint*) arg;
    watchPoint->listen();
}

WatchPoint::WatchPoint(Server* server, const u16string& path, HANDLE directoryHandle, HANDLE serverThreadHandle) {
    this->server = server;
    this->path = path;
    this->buffer.reserve(EVENT_BUFFER_SIZE);
    ZeroMemory(&this->overlapped, sizeof(OVERLAPPED));
    this->overlapped.hEvent = this;
    this->directoryHandle = directoryHandle;
    listen();
}

WatchPoint::~WatchPoint() {
}

void WatchPoint::close() {
    BOOL ret = CancelIo(directoryHandle);
    if (!ret) {
        log_severe(server->getThreadEnv(), "Couldn't cancel I/O %p for '%ls': %d", directoryHandle, path.c_str(), GetLastError());
    }
    ret = CloseHandle(directoryHandle);
    if (!ret) {
        log_severe(server->getThreadEnv(), "Couldn't close handle %p for '%ls': %d", directoryHandle, path.c_str(), GetLastError());
    }
}

static void CALLBACK handleEventCallback(DWORD errorCode, DWORD bytesTransferred, LPOVERLAPPED overlapped) {
    WatchPoint* watchPoint = (WatchPoint*) overlapped->hEvent;

    if (errorCode == ERROR_OPERATION_ABORTED) {
        Server* server = watchPoint->server;
        log_fine(server->getThreadEnv(), "Finished watching '%ls'", watchPoint->path.c_str());
        server->reportFinished(*watchPoint);
        return;
    }
    // TODO Handle other error codes

    watchPoint->handleEvent(bytesTransferred);
}

void WatchPoint::listen() {
    BOOL success = ReadDirectoryChangesW(
        directoryHandle,        // handle to directory
        &buffer[0],             // read results buffer
        EVENT_BUFFER_SIZE,      // length of buffer
        TRUE,                   // include children
        EVENT_MASK,             // filter conditions
        NULL,                   // bytes returned
        &overlapped,            // overlapped buffer
        &handleEventCallback    // completion routine
    );
    if (!success) {
        log_warning(server->getThreadEnv(), "Couldn't start watching %p for '%ls', error = %d", directoryHandle, path.c_str(), GetLastError());
        throw FileWatcherException("Couldn't start watching");
    }
}

void WatchPoint::handleEvent(DWORD bytesTransferred) {
    if (bytesTransferred == 0) {
        // Got a buffer overflow => current changes lost => send INVALIDATE on root
        log_info(server->getThreadEnv(), "Detected overflow for %ls", path.c_str());
        server->reportEvent(FILE_EVENT_INVALIDATE, path);
    } else {
        int index = 0;
        for (;;) {
            FILE_NOTIFY_INFORMATION* current = (FILE_NOTIFY_INFORMATION*) &buffer[index];
            handlePathChanged(current);
            if (current->NextEntryOffset == 0) {
                fprintf(stderr, ">>>> Done\n");
                break;
            }
            index += current->NextEntryOffset;
        }
    }

    try {
        listen();
    } catch (const exception& e) {
        log_severe(server->getThreadEnv(), "Watching failed: %s", e.what());
        server->reportFinished(*this);
    }
}

bool isAbsoluteLocalPath(const u16string& path) {
    if (path.length() < 3) {
        return false;
    }
    return ((u'a' <= path[0] && path[0] <= u'z') || (u'A' <= path[0] && path[0] <= u'Z'))
        && path[1] == u':'
        && path[2] == u'\\';
}

bool isAbsoluteUncPath(const u16string& path) {
    if (path.length() < 3) {
        return false;
    }
    return path[0] == u'\\' && path[1] == u'\\';
}

bool isLongPath(const u16string& path) {
    return path.length() >= 4 && path.substr(0, 4) == u"\\\\?\\";
}

bool isUncLongPath(const u16string& path) {
    return path.length() >= 8 && path.substr(0, 8) == u"\\\\?\\UNC\\";
}

void convertToLongPathIfNeeded(u16string& path) {
    // Technically, this should be MAX_PATH (i.e. 260), except some Win32 API related
    // to working with directory paths are actually limited to 240. It is just
    // safer/simpler to cover both cases in one code path.
    if (path.length() <= 240) {
        return;
    }

    // It is already a long path, nothing to do here
    if (isLongPath(path)) {
        return;
    }

    if (isAbsoluteLocalPath(path)) {
        // Format: C:\... -> \\?\C:\...
        path.insert(0, u"\\\\?\\");
    } else if (isAbsoluteUncPath(path)) {
        // In this case, we need to skip the first 2 characters:
        // Format: \\server\share\... -> \\?\UNC\server\share\...
        path.erase(0, 2);
        path.insert(0, u"\\\\?\\UNC\\");
    } else {
        // It is some sort of unknown format, don't mess with it
    }
}

void WatchPoint::handlePathChanged(FILE_NOTIFY_INFORMATION* info) {
    wstring changedPathW = wstring(info->FileName, 0, info->FileNameLength / sizeof(wchar_t));
    u16string changedPath(changedPathW.begin(), changedPathW.end());
    // TODO Do we ever get an empty path?
    if (!changedPath.empty()) {
        changedPath.insert(0, 1, u'\\');
        changedPath.insert(0, path);
    }
    // TODO Remove long prefix for path once?
    if (isLongPath(changedPath)) {
        if (isUncLongPath(changedPath)) {
            changedPath.erase(0, 8).insert(0, u"\\\\");
        } else {
            changedPath.erase(0, 4);
        }
    }

    log_fine(server->getThreadEnv(), "Change detected: 0x%x '%ls'", info->Action, changedPathW.c_str());

    jint type;
    if (info->Action == FILE_ACTION_ADDED || info->Action == FILE_ACTION_RENAMED_NEW_NAME) {
        type = FILE_EVENT_CREATED;
    } else if (info->Action == FILE_ACTION_REMOVED || info->Action == FILE_ACTION_RENAMED_OLD_NAME) {
        type = FILE_EVENT_REMOVED;
    } else if (info->Action == FILE_ACTION_MODIFIED) {
        type = FILE_EVENT_MODIFIED;
    } else {
        log_warning(server->getThreadEnv(), "Unknown event 0x%x for %ls", info->Action, changedPathW.c_str());
        type = FILE_EVENT_UNKNOWN;
    }

    server->reportEvent(type, changedPath);
}

//
// Server
//

Server::Server(JNIEnv* env, jobject watcherCallback)
    : AbstractServer(env, watcherCallback) {
    startThread();
    // TODO Error handling
    SetThreadPriority(this->watcherThread.native_handle(), THREAD_PRIORITY_ABOVE_NORMAL);
}

void Server::terminate() {
    terminated = true;
}

Server::~Server() {
    list<u16string> paths;
    for (auto& watchPoint : watchPoints) {
        paths.push_back(watchPoint.first);
    }
    for (auto& path : paths) {
        executeOnThread(shared_ptr<Command>(new UnregisterPathCommand(path)));
    }
    executeOnThread(shared_ptr<Command>(new TerminateCommand()));

    if (watcherThread.joinable()) {
        watcherThread.join();
    }
}

void Server::runLoop(JNIEnv* env, function<void(exception_ptr)> notifyStarted) {
    notifyStarted(nullptr);

    while (!terminated || watchPoints.size() > 0) {
        SleepEx(INFINITE, true);
    }
}

static void CALLBACK processCommandsCallback(_In_ ULONG_PTR info) {
    Server* server = (Server*) info;
    server->processCommands();
}

void Server::wakeUpRunLoop() {
    QueueUserAPC(processCommandsCallback, watcherThread.native_handle(), (ULONG_PTR) this);
}

void Server::registerPath(const u16string& path) {
    u16string longPath = path;
    convertToLongPathIfNeeded(longPath);
    if (watchPoints.find(longPath) != watchPoints.end()) {
        throw FileWatcherException("Already watching path");
    }
    wstring pathW(longPath.begin(), longPath.end());
    HANDLE directoryHandle = CreateFileW(
        pathW.c_str(),          // pointer to the file name
        FILE_LIST_DIRECTORY,    // access (read/write) mode
        CREATE_SHARE,           // share mode
        NULL,                   // security descriptor
        OPEN_EXISTING,          // how to create
        CREATE_FLAGS,           // file attributes
        NULL                    // file with attributes to copy
    );

    if (directoryHandle == INVALID_HANDLE_VALUE) {
        log_severe(getThreadEnv(), "Couldn't get file handle for '%ls': %d", pathW.c_str(), GetLastError());
        // TODO Error handling
        return;
    }

    HANDLE threadHandle = GetCurrentThread();
    watchPoints.emplace(piecewise_construct,
        forward_as_tuple(longPath),
        forward_as_tuple(this, longPath, directoryHandle, threadHandle));
}

void Server::unregisterPath(const u16string& path) {
    u16string longPath = path;
    convertToLongPathIfNeeded(longPath);
    auto it = watchPoints.find(longPath);
    if (it == watchPoints.end()) {
        throw FileWatcherException("Cannot stop watching path that was never watched");
    }
    it->second.close();
}

void Server::reportFinished(const WatchPoint& watchPoint) {
    u16string path = watchPoint.path;
    watchPoints.erase(path);
}

void Server::reportEvent(jint type, const u16string& changedPath) {
    JNIEnv* env = getThreadEnv();
    reportChange(env, type, changedPath);
}

//
// JNI calls
//

JNIEXPORT jobject JNICALL
Java_net_rubygrapefruit_platform_internal_jni_WindowsFileEventFunctions_startWatcher(JNIEnv* env, jclass target, jobject javaCallback) {
    return wrapServer(env, [env, javaCallback]() {
        return new Server(env, javaCallback);
    });
}

#endif
<commit_msg>Remove unnecessary callback<commit_after>#ifdef _WIN32

#include <assert.h>

#include "win_fsnotifier.h"

using namespace std;

//
// WatchPoint
//

WatchPoint::WatchPoint(Server* server, const u16string& path, HANDLE directoryHandle, HANDLE serverThreadHandle) {
    this->server = server;
    this->path = path;
    this->buffer.reserve(EVENT_BUFFER_SIZE);
    ZeroMemory(&this->overlapped, sizeof(OVERLAPPED));
    this->overlapped.hEvent = this;
    this->directoryHandle = directoryHandle;
    listen();
}

WatchPoint::~WatchPoint() {
}

void WatchPoint::close() {
    BOOL ret = CancelIo(directoryHandle);
    if (!ret) {
        log_severe(server->getThreadEnv(), "Couldn't cancel I/O %p for '%ls': %d", directoryHandle, path.c_str(), GetLastError());
    }
    ret = CloseHandle(directoryHandle);
    if (!ret) {
        log_severe(server->getThreadEnv(), "Couldn't close handle %p for '%ls': %d", directoryHandle, path.c_str(), GetLastError());
    }
}

static void CALLBACK handleEventCallback(DWORD errorCode, DWORD bytesTransferred, LPOVERLAPPED overlapped) {
    WatchPoint* watchPoint = (WatchPoint*) overlapped->hEvent;

    if (errorCode == ERROR_OPERATION_ABORTED) {
        Server* server = watchPoint->server;
        log_fine(server->getThreadEnv(), "Finished watching '%ls'", watchPoint->path.c_str());
        server->reportFinished(*watchPoint);
        return;
    }
    // TODO Handle other error codes

    watchPoint->handleEvent(bytesTransferred);
}

void WatchPoint::listen() {
    BOOL success = ReadDirectoryChangesW(
        directoryHandle,        // handle to directory
        &buffer[0],             // read results buffer
        EVENT_BUFFER_SIZE,      // length of buffer
        TRUE,                   // include children
        EVENT_MASK,             // filter conditions
        NULL,                   // bytes returned
        &overlapped,            // overlapped buffer
        &handleEventCallback    // completion routine
    );
    if (!success) {
        log_warning(server->getThreadEnv(), "Couldn't start watching %p for '%ls', error = %d", directoryHandle, path.c_str(), GetLastError());
        throw FileWatcherException("Couldn't start watching");
    }
}

void WatchPoint::handleEvent(DWORD bytesTransferred) {
    if (bytesTransferred == 0) {
        // Got a buffer overflow => current changes lost => send INVALIDATE on root
        log_info(server->getThreadEnv(), "Detected overflow for %ls", path.c_str());
        server->reportEvent(FILE_EVENT_INVALIDATE, path);
    } else {
        int index = 0;
        for (;;) {
            FILE_NOTIFY_INFORMATION* current = (FILE_NOTIFY_INFORMATION*) &buffer[index];
            handlePathChanged(current);
            if (current->NextEntryOffset == 0) {
                fprintf(stderr, ">>>> Done\n");
                break;
            }
            index += current->NextEntryOffset;
        }
    }

    try {
        listen();
    } catch (const exception& e) {
        log_severe(server->getThreadEnv(), "Watching failed: %s", e.what());
        server->reportFinished(*this);
    }
}

bool isAbsoluteLocalPath(const u16string& path) {
    if (path.length() < 3) {
        return false;
    }
    return ((u'a' <= path[0] && path[0] <= u'z') || (u'A' <= path[0] && path[0] <= u'Z'))
        && path[1] == u':'
        && path[2] == u'\\';
}

bool isAbsoluteUncPath(const u16string& path) {
    if (path.length() < 3) {
        return false;
    }
    return path[0] == u'\\' && path[1] == u'\\';
}

bool isLongPath(const u16string& path) {
    return path.length() >= 4 && path.substr(0, 4) == u"\\\\?\\";
}

bool isUncLongPath(const u16string& path) {
    return path.length() >= 8 && path.substr(0, 8) == u"\\\\?\\UNC\\";
}

void convertToLongPathIfNeeded(u16string& path) {
    // Technically, this should be MAX_PATH (i.e. 260), except some Win32 API related
    // to working with directory paths are actually limited to 240. It is just
    // safer/simpler to cover both cases in one code path.
    if (path.length() <= 240) {
        return;
    }

    // It is already a long path, nothing to do here
    if (isLongPath(path)) {
        return;
    }

    if (isAbsoluteLocalPath(path)) {
        // Format: C:\... -> \\?\C:\...
        path.insert(0, u"\\\\?\\");
    } else if (isAbsoluteUncPath(path)) {
        // In this case, we need to skip the first 2 characters:
        // Format: \\server\share\... -> \\?\UNC\server\share\...
        path.erase(0, 2);
        path.insert(0, u"\\\\?\\UNC\\");
    } else {
        // It is some sort of unknown format, don't mess with it
    }
}

void WatchPoint::handlePathChanged(FILE_NOTIFY_INFORMATION* info) {
    wstring changedPathW = wstring(info->FileName, 0, info->FileNameLength / sizeof(wchar_t));
    u16string changedPath(changedPathW.begin(), changedPathW.end());
    // TODO Do we ever get an empty path?
    if (!changedPath.empty()) {
        changedPath.insert(0, 1, u'\\');
        changedPath.insert(0, path);
    }
    // TODO Remove long prefix for path once?
    if (isLongPath(changedPath)) {
        if (isUncLongPath(changedPath)) {
            changedPath.erase(0, 8).insert(0, u"\\\\");
        } else {
            changedPath.erase(0, 4);
        }
    }

    log_fine(server->getThreadEnv(), "Change detected: 0x%x '%ls'", info->Action, changedPathW.c_str());

    jint type;
    if (info->Action == FILE_ACTION_ADDED || info->Action == FILE_ACTION_RENAMED_NEW_NAME) {
        type = FILE_EVENT_CREATED;
    } else if (info->Action == FILE_ACTION_REMOVED || info->Action == FILE_ACTION_RENAMED_OLD_NAME) {
        type = FILE_EVENT_REMOVED;
    } else if (info->Action == FILE_ACTION_MODIFIED) {
        type = FILE_EVENT_MODIFIED;
    } else {
        log_warning(server->getThreadEnv(), "Unknown event 0x%x for %ls", info->Action, changedPathW.c_str());
        type = FILE_EVENT_UNKNOWN;
    }

    server->reportEvent(type, changedPath);
}

//
// Server
//

Server::Server(JNIEnv* env, jobject watcherCallback)
    : AbstractServer(env, watcherCallback) {
    startThread();
    // TODO Error handling
    SetThreadPriority(this->watcherThread.native_handle(), THREAD_PRIORITY_ABOVE_NORMAL);
}

void Server::terminate() {
    terminated = true;
}

Server::~Server() {
    list<u16string> paths;
    for (auto& watchPoint : watchPoints) {
        paths.push_back(watchPoint.first);
    }
    for (auto& path : paths) {
        executeOnThread(shared_ptr<Command>(new UnregisterPathCommand(path)));
    }
    executeOnThread(shared_ptr<Command>(new TerminateCommand()));

    if (watcherThread.joinable()) {
        watcherThread.join();
    }
}

void Server::runLoop(JNIEnv* env, function<void(exception_ptr)> notifyStarted) {
    notifyStarted(nullptr);

    while (!terminated || watchPoints.size() > 0) {
        SleepEx(INFINITE, true);
    }
}

static void CALLBACK processCommandsCallback(_In_ ULONG_PTR info) {
    Server* server = (Server*) info;
    server->processCommands();
}

void Server::wakeUpRunLoop() {
    QueueUserAPC(processCommandsCallback, watcherThread.native_handle(), (ULONG_PTR) this);
}

void Server::registerPath(const u16string& path) {
    u16string longPath = path;
    convertToLongPathIfNeeded(longPath);
    if (watchPoints.find(longPath) != watchPoints.end()) {
        throw FileWatcherException("Already watching path");
    }
    wstring pathW(longPath.begin(), longPath.end());
    HANDLE directoryHandle = CreateFileW(
        pathW.c_str(),          // pointer to the file name
        FILE_LIST_DIRECTORY,    // access (read/write) mode
        CREATE_SHARE,           // share mode
        NULL,                   // security descriptor
        OPEN_EXISTING,          // how to create
        CREATE_FLAGS,           // file attributes
        NULL                    // file with attributes to copy
    );

    if (directoryHandle == INVALID_HANDLE_VALUE) {
        log_severe(getThreadEnv(), "Couldn't get file handle for '%ls': %d", pathW.c_str(), GetLastError());
        // TODO Error handling
        return;
    }

    HANDLE threadHandle = GetCurrentThread();
    watchPoints.emplace(piecewise_construct,
        forward_as_tuple(longPath),
        forward_as_tuple(this, longPath, directoryHandle, threadHandle));
}

void Server::unregisterPath(const u16string& path) {
    u16string longPath = path;
    convertToLongPathIfNeeded(longPath);
    auto it = watchPoints.find(longPath);
    if (it == watchPoints.end()) {
        throw FileWatcherException("Cannot stop watching path that was never watched");
    }
    it->second.close();
}

void Server::reportFinished(const WatchPoint& watchPoint) {
    u16string path = watchPoint.path;
    watchPoints.erase(path);
}

void Server::reportEvent(jint type, const u16string& changedPath) {
    JNIEnv* env = getThreadEnv();
    reportChange(env, type, changedPath);
}

//
// JNI calls
//

JNIEXPORT jobject JNICALL
Java_net_rubygrapefruit_platform_internal_jni_WindowsFileEventFunctions_startWatcher(JNIEnv* env, jclass target, jobject javaCallback) {
    return wrapServer(env, [env, javaCallback]() {
        return new Server(env, javaCallback);
    });
}

#endif
<|endoftext|>


<commit_before>#ifdef _WIN32

#include "win_fsnotifier.h"

using namespace std;

//
// WatchPoint
//

WatchPoint::WatchPoint(Server* server, size_t bufferSize, const u16string& path)
    : path(path)
    , status(NOT_LISTENING) {
    wstring pathW(path.begin(), path.end());
    HANDLE directoryHandle = CreateFileW(
        pathW.c_str(),          // pointer to the file name
        FILE_LIST_DIRECTORY,    // access (read/write) mode
        CREATE_SHARE,           // share mode
        NULL,                   // security descriptor
        OPEN_EXISTING,          // how to create
        CREATE_FLAGS,           // file attributes
        NULL                    // file with attributes to copy
    );
    if (directoryHandle == INVALID_HANDLE_VALUE) {
        throw FileWatcherException("Couldn't add watch", path, GetLastError());
    }
    this->directoryHandle = directoryHandle;

    this->server = server;
    this->buffer.reserve(bufferSize);
    ZeroMemory(&this->overlapped, sizeof(OVERLAPPED));
    this->overlapped.hEvent = this;
    switch (listen()) {
        case SUCCESS:
            break;
        case DELETED:
            throw FileWatcherException("Couldn't start watching because path is not a directory", path);
    }
}

bool WatchPoint::cancel() {
    if (status == LISTENING) {
        logToJava(FINE, "Cancelling %s", utf16ToUtf8String(path).c_str());
        status = CANCELLED;
        bool cancelled = (bool) CancelIoEx(directoryHandle, &overlapped);
        if (!cancelled) {
            status = FINISHED;
            DWORD lastError = GetLastError();
            if (lastError == ERROR_NOT_FOUND) {
                // Do nothing, looks like this is a typical scenario
                logToJava(FINE, "Watch point already finished %s", utf16ToUtf8String(path).c_str());
            } else {
                throw FileWatcherException("Couldn't cancel watch point", path, lastError);
            }
        }
        return cancelled;
    }
    return false;
}

WatchPoint::~WatchPoint() {
    try {
        if (cancel()) {
            SleepEx(0, true);
        }
    } catch (const exception& ex) {
        logToJava(WARNING, "Couldn't cancel watch point %s: %s", utf16ToUtf8String(path).c_str(), ex.what());
    }
}

static void CALLBACK handleEventCallback(DWORD errorCode, DWORD bytesTransferred, LPOVERLAPPED overlapped) {
    WatchPoint* watchPoint = (WatchPoint*) overlapped->hEvent;
    watchPoint->handleEventsInBuffer(errorCode, bytesTransferred);
}

bool WatchPoint::isValidDirectory() {
    wstring pathW(path.begin(), path.end());
    DWORD attrib = GetFileAttributesW(pathW.c_str());

    return (attrib != INVALID_FILE_ATTRIBUTES)
        && ((attrib & FILE_ATTRIBUTE_DIRECTORY) != 0);
}

ListenResult WatchPoint::listen() {
    BOOL success = ReadDirectoryChangesW(
        directoryHandle,              // handle to directory
        &buffer[0],                   // read results buffer
        (DWORD) buffer.capacity(),    // length of buffer
        TRUE,                         // include children
        EVENT_MASK,                   // filter conditions
        NULL,                         // bytes returned
        &overlapped,                  // overlapped buffer
        &handleEventCallback          // completion routine
    );
    if (success) {
        status = LISTENING;
        return SUCCESS;
    } else {
        status = FINISHED;
        DWORD lastError = GetLastError();
        if (lastError == ERROR_ACCESS_DENIED && !isValidDirectory()) {
            return DELETED;
        } else {
            throw FileWatcherException("Couldn't start watching", path, lastError);
        }
    }
}

void WatchPoint::handleEventsInBuffer(DWORD errorCode, DWORD bytesTransferred) {
    if (errorCode == ERROR_OPERATION_ABORTED) {
        logToJava(FINE, "Finished watching '%s', status = %d", utf16ToUtf8String(path).c_str(), status);
        BOOL ret = CloseHandle(directoryHandle);
        if (!ret) {
            logToJava(SEVERE, "Couldn't close handle %p for '%ls': %d", directoryHandle, utf16ToUtf8String(path).c_str(), GetLastError());
        }
        status = FINISHED;
        return;
    }

    if (status != LISTENING) {
        logToJava(FINE, "Ignoring incoming events for %s as watch-point is not listening (%d bytes, errorCode = %d, status = %d)",
            utf16ToUtf8String(path).c_str(), bytesTransferred, errorCode, status);
        return;
    }
    status = NOT_LISTENING;
    server->handleEvents(this, errorCode, buffer, bytesTransferred);
}

void Server::handleEvents(WatchPoint* watchPoint, DWORD errorCode, const vector<BYTE>& buffer, DWORD bytesTransferred) {
    unique_lock<mutex> lock(mutationMutex);
    JNIEnv* env = getThreadEnv();
    const u16string& path = watchPoint->path;

    try {
        if (errorCode != ERROR_SUCCESS) {
            if (errorCode == ERROR_ACCESS_DENIED && !watchPoint->isValidDirectory()) {
                reportChange(env, FILE_EVENT_REMOVED, path);
            } else {
                throw FileWatcherException("Error received when handling events", path, errorCode);
            }
        }

        if (terminated) {
            logToJava(FINE, "Ignoring incoming events for %s because server is terminating (%d bytes, status = %d)",
                utf16ToUtf8String(path).c_str(), bytesTransferred, watchPoint->status);
            return;
        }

        if (bytesTransferred == 0) {
            // Got a buffer overflow => current changes lost => send INVALIDATE on root
            logToJava(INFO, "Detected overflow for %s", utf16ToUtf8String(path).c_str());
            reportChange(env, FILE_EVENT_INVALIDATE, path);
        } else {
            int index = 0;
            for (;;) {
                FILE_NOTIFY_INFORMATION* current = (FILE_NOTIFY_INFORMATION*) &buffer[index];
                handleEvent(env, path, current);
                if (current->NextEntryOffset == 0) {
                    break;
                }
                index += current->NextEntryOffset;
            }
        }

        switch (watchPoint->listen()) {
            case SUCCESS:
                break;
            case DELETED:
                reportChange(env, FILE_EVENT_REMOVED, path);
                break;
        }
    } catch (const exception& ex) {
        reportError(env, ex);
    }
}

bool isAbsoluteLocalPath(const u16string& path) {
    if (path.length() < 3) {
        return false;
    }
    return ((u'a' <= path[0] && path[0] <= u'z') || (u'A' <= path[0] && path[0] <= u'Z'))
        && path[1] == u':'
        && path[2] == u'\\';
}

bool isAbsoluteUncPath(const u16string& path) {
    if (path.length() < 3) {
        return false;
    }
    return path[0] == u'\\' && path[1] == u'\\';
}

bool isLongPath(const u16string& path) {
    return path.length() >= 4 && path.substr(0, 4) == u"\\\\?\\";
}

bool isUncLongPath(const u16string& path) {
    return path.length() >= 8 && path.substr(0, 8) == u"\\\\?\\UNC\\";
}

// TODO How can this be done nicer, wihtout both unnecessary copy and in-place mutation?
void convertToLongPathIfNeeded(u16string& path) {
    // Technically, this should be MAX_PATH (i.e. 260), except some Win32 API related
    // to working with directory paths are actually limited to 240. It is just
    // safer/simpler to cover both cases in one code path.
    if (path.length() <= 240) {
        return;
    }

    // It is already a long path, nothing to do here
    if (isLongPath(path)) {
        return;
    }

    if (isAbsoluteLocalPath(path)) {
        // Format: C:\... -> \\?\C:\...
        path.insert(0, u"\\\\?\\");
    } else if (isAbsoluteUncPath(path)) {
        // In this case, we need to skip the first 2 characters:
        // Format: \\server\share\... -> \\?\UNC\server\share\...
        path.erase(0, 2);
        path.insert(0, u"\\\\?\\UNC\\");
    } else {
        // It is some sort of unknown format, don't mess with it
    }
}

void Server::handleEvent(JNIEnv* env, const u16string& path, FILE_NOTIFY_INFORMATION* info) {
    wstring changedPathW = wstring(info->FileName, 0, info->FileNameLength / sizeof(wchar_t));
    u16string changedPath(changedPathW.begin(), changedPathW.end());
    if (!changedPath.empty()) {
        changedPath.insert(0, 1, u'\\');
    }
    changedPath.insert(0, path);
    // TODO Remove long prefix for path once?
    if (isLongPath(changedPath)) {
        if (isUncLongPath(changedPath)) {
            changedPath.erase(0, 8).insert(0, u"\\\\");
        } else {
            changedPath.erase(0, 4);
        }
    }

    logToJava(FINE, "Change detected: 0x%x '%s'", info->Action, utf16ToUtf8String(changedPath).c_str());

    jint type;
    if (info->Action == FILE_ACTION_ADDED || info->Action == FILE_ACTION_RENAMED_NEW_NAME) {
        type = FILE_EVENT_CREATED;
    } else if (info->Action == FILE_ACTION_REMOVED || info->Action == FILE_ACTION_RENAMED_OLD_NAME) {
        type = FILE_EVENT_REMOVED;
    } else if (info->Action == FILE_ACTION_MODIFIED) {
        type = FILE_EVENT_MODIFIED;
    } else {
        logToJava(WARNING, "Unknown event 0x%x for %s", info->Action, utf16ToUtf8String(changedPath).c_str());
        type = FILE_EVENT_UNKNOWN;
    }

    reportChange(env, type, changedPath);
}

//
// Server
//

Server::Server(JNIEnv* env, size_t bufferSize, jobject watcherCallback)
    : AbstractServer(env, watcherCallback)
    , bufferSize(bufferSize) {
}

void Server::initializeRunLoop() {
    // TODO For some reason GetCurrentThread() returns a thread that doesn't accept APCs
    threadHandle = OpenThread(
        THREAD_ALL_ACCESS,
        false,
        GetCurrentThreadId()
    );
    if (threadHandle == NULL) {
        throw FileWatcherException("Couldn't open current thread", GetLastError());
    }
}

void Server::terminateRunLoop() {
    executeOnRunLoop([this]() {
        terminated = true;
        return true;
    });
}

Server::~Server() {
    terminate();
}

void Server::runLoop() {
    while (!terminated) {
        SleepEx(INFINITE, true);
    }

    // We have received termination, cancel all watchers
    unique_lock<mutex> lock(mutationMutex);
    logToJava(FINE, "Finished with run loop, now cancelling remaining watch points", NULL);
    int pendingWatchPoints = 0;
    for (auto& it : watchPoints) {
        auto& watchPoint = it.second;
        switch (watchPoint.status) {
            case LISTENING:
                try {
                    if (watchPoint.cancel()) {
                        pendingWatchPoints++;
                    }
                } catch (const exception& ex) {
                    logToJava(SEVERE, "%s", ex.what());
                }
                break;
            case CANCELLED:
                pendingWatchPoints++;
                break;
            default:
                break;
        }
    }

    // If there are any pending watchers, wait for them to finish
    if (pendingWatchPoints > 0) {
        logToJava(FINE, "Waiting for %d pending watch points to finish", pendingWatchPoints);
        SleepEx(0, true);
    }

    // Warn about  any unfinished watchpoints
    for (auto& it : watchPoints) {
        auto& watchPoint = it.second;
        switch (watchPoint.status) {
            case NOT_LISTENING:
            case FINISHED:
                break;
            default:
                logToJava(WARNING, "Watch point %s did not finish before termination timeout (status = %d)",
                    utf16ToUtf8String(watchPoint.path).c_str(), watchPoint.status);
                break;
        }
    }

    CloseHandle(threadHandle);
}

static void CALLBACK executeOnRunLoopCallback(_In_ ULONG_PTR info) {
    Command* command = (Command*) info;
    try {
        command->result = command->function();
    } catch (const exception&) {
        command->failure = current_exception();
    }
    unique_lock<mutex> lock(command->server->executionMutex);
    command->executed.notify_all();
}

bool Server::executeOnRunLoop(function<bool()> function) {
    Command command;
    command.function = function;
    command.server = this;
    DWORD ret = QueueUserAPC(executeOnRunLoopCallback, threadHandle, (ULONG_PTR) &command);
    if (ret == 0) {
        throw FileWatcherException("Received error while queuing APC", GetLastError());
    }
    unique_lock<mutex> lock(executionMutex);
    auto status = command.executed.wait_for(lock, THREAD_TIMEOUT);
    if (status == cv_status::timeout) {
        throw FileWatcherException("Execution timed out");
    } else if (command.failure) {
        rethrow_exception(command.failure);
    } else {
        return command.result;
    }
}

void Server::registerPaths(const vector<u16string>& paths) {
    executeOnRunLoop([this, paths]() {
        AbstractServer::registerPaths(paths);
        return true;
    });
}

bool Server::unregisterPaths(const vector<u16string>& paths) {
    return executeOnRunLoop([this, paths]() {
        return AbstractServer::unregisterPaths(paths);
    });
}

void Server::registerPath(const u16string& path) {
    u16string longPath = path;
    convertToLongPathIfNeeded(longPath);
    auto it = watchPoints.find(longPath);
    if (it != watchPoints.end()) {
        if (it->second.status != FINISHED) {
            throw FileWatcherException("Already watching path", path);
        }
        watchPoints.erase(it);
    }
    watchPoints.emplace(piecewise_construct,
        forward_as_tuple(longPath),
        forward_as_tuple(this, bufferSize, longPath));
}

bool Server::unregisterPath(const u16string& path) {
    u16string longPath = path;
    convertToLongPathIfNeeded(longPath);
    if (watchPoints.erase(longPath) == 0) {
        logToJava(INFO, "Path is not watched: %s", utf16ToUtf8String(path).c_str());
        return false;
    }
    return true;
}

//
// JNI calls
//

JNIEXPORT jobject JNICALL
Java_net_rubygrapefruit_platform_internal_jni_WindowsFileEventFunctions_startWatcher0(JNIEnv* env, jclass target, jint bufferSize, jobject javaCallback) {
    return wrapServer(env, new Server(env, bufferSize, javaCallback));
}

#endif
<commit_msg>Lock for condition_variable earlier<commit_after>#ifdef _WIN32

#include "win_fsnotifier.h"

using namespace std;

//
// WatchPoint
//

WatchPoint::WatchPoint(Server* server, size_t bufferSize, const u16string& path)
    : path(path)
    , status(NOT_LISTENING) {
    wstring pathW(path.begin(), path.end());
    HANDLE directoryHandle = CreateFileW(
        pathW.c_str(),          // pointer to the file name
        FILE_LIST_DIRECTORY,    // access (read/write) mode
        CREATE_SHARE,           // share mode
        NULL,                   // security descriptor
        OPEN_EXISTING,          // how to create
        CREATE_FLAGS,           // file attributes
        NULL                    // file with attributes to copy
    );
    if (directoryHandle == INVALID_HANDLE_VALUE) {
        throw FileWatcherException("Couldn't add watch", path, GetLastError());
    }
    this->directoryHandle = directoryHandle;

    this->server = server;
    this->buffer.reserve(bufferSize);
    ZeroMemory(&this->overlapped, sizeof(OVERLAPPED));
    this->overlapped.hEvent = this;
    switch (listen()) {
        case SUCCESS:
            break;
        case DELETED:
            throw FileWatcherException("Couldn't start watching because path is not a directory", path);
    }
}

bool WatchPoint::cancel() {
    if (status == LISTENING) {
        logToJava(FINE, "Cancelling %s", utf16ToUtf8String(path).c_str());
        status = CANCELLED;
        bool cancelled = (bool) CancelIoEx(directoryHandle, &overlapped);
        if (!cancelled) {
            status = FINISHED;
            DWORD lastError = GetLastError();
            if (lastError == ERROR_NOT_FOUND) {
                // Do nothing, looks like this is a typical scenario
                logToJava(FINE, "Watch point already finished %s", utf16ToUtf8String(path).c_str());
            } else {
                throw FileWatcherException("Couldn't cancel watch point", path, lastError);
            }
        }
        return cancelled;
    }
    return false;
}

WatchPoint::~WatchPoint() {
    try {
        if (cancel()) {
            SleepEx(0, true);
        }
    } catch (const exception& ex) {
        logToJava(WARNING, "Couldn't cancel watch point %s: %s", utf16ToUtf8String(path).c_str(), ex.what());
    }
}

static void CALLBACK handleEventCallback(DWORD errorCode, DWORD bytesTransferred, LPOVERLAPPED overlapped) {
    WatchPoint* watchPoint = (WatchPoint*) overlapped->hEvent;
    watchPoint->handleEventsInBuffer(errorCode, bytesTransferred);
}

bool WatchPoint::isValidDirectory() {
    wstring pathW(path.begin(), path.end());
    DWORD attrib = GetFileAttributesW(pathW.c_str());

    return (attrib != INVALID_FILE_ATTRIBUTES)
        && ((attrib & FILE_ATTRIBUTE_DIRECTORY) != 0);
}

ListenResult WatchPoint::listen() {
    BOOL success = ReadDirectoryChangesW(
        directoryHandle,              // handle to directory
        &buffer[0],                   // read results buffer
        (DWORD) buffer.capacity(),    // length of buffer
        TRUE,                         // include children
        EVENT_MASK,                   // filter conditions
        NULL,                         // bytes returned
        &overlapped,                  // overlapped buffer
        &handleEventCallback          // completion routine
    );
    if (success) {
        status = LISTENING;
        return SUCCESS;
    } else {
        status = FINISHED;
        DWORD lastError = GetLastError();
        if (lastError == ERROR_ACCESS_DENIED && !isValidDirectory()) {
            return DELETED;
        } else {
            throw FileWatcherException("Couldn't start watching", path, lastError);
        }
    }
}

void WatchPoint::handleEventsInBuffer(DWORD errorCode, DWORD bytesTransferred) {
    if (errorCode == ERROR_OPERATION_ABORTED) {
        logToJava(FINE, "Finished watching '%s', status = %d", utf16ToUtf8String(path).c_str(), status);
        BOOL ret = CloseHandle(directoryHandle);
        if (!ret) {
            logToJava(SEVERE, "Couldn't close handle %p for '%ls': %d", directoryHandle, utf16ToUtf8String(path).c_str(), GetLastError());
        }
        status = FINISHED;
        return;
    }

    if (status != LISTENING) {
        logToJava(FINE, "Ignoring incoming events for %s as watch-point is not listening (%d bytes, errorCode = %d, status = %d)",
            utf16ToUtf8String(path).c_str(), bytesTransferred, errorCode, status);
        return;
    }
    status = NOT_LISTENING;
    server->handleEvents(this, errorCode, buffer, bytesTransferred);
}

void Server::handleEvents(WatchPoint* watchPoint, DWORD errorCode, const vector<BYTE>& buffer, DWORD bytesTransferred) {
    unique_lock<mutex> lock(mutationMutex);
    JNIEnv* env = getThreadEnv();
    const u16string& path = watchPoint->path;

    try {
        if (errorCode != ERROR_SUCCESS) {
            if (errorCode == ERROR_ACCESS_DENIED && !watchPoint->isValidDirectory()) {
                reportChange(env, FILE_EVENT_REMOVED, path);
            } else {
                throw FileWatcherException("Error received when handling events", path, errorCode);
            }
        }

        if (terminated) {
            logToJava(FINE, "Ignoring incoming events for %s because server is terminating (%d bytes, status = %d)",
                utf16ToUtf8String(path).c_str(), bytesTransferred, watchPoint->status);
            return;
        }

        if (bytesTransferred == 0) {
            // Got a buffer overflow => current changes lost => send INVALIDATE on root
            logToJava(INFO, "Detected overflow for %s", utf16ToUtf8String(path).c_str());
            reportChange(env, FILE_EVENT_INVALIDATE, path);
        } else {
            int index = 0;
            for (;;) {
                FILE_NOTIFY_INFORMATION* current = (FILE_NOTIFY_INFORMATION*) &buffer[index];
                handleEvent(env, path, current);
                if (current->NextEntryOffset == 0) {
                    break;
                }
                index += current->NextEntryOffset;
            }
        }

        switch (watchPoint->listen()) {
            case SUCCESS:
                break;
            case DELETED:
                reportChange(env, FILE_EVENT_REMOVED, path);
                break;
        }
    } catch (const exception& ex) {
        reportError(env, ex);
    }
}

bool isAbsoluteLocalPath(const u16string& path) {
    if (path.length() < 3) {
        return false;
    }
    return ((u'a' <= path[0] && path[0] <= u'z') || (u'A' <= path[0] && path[0] <= u'Z'))
        && path[1] == u':'
        && path[2] == u'\\';
}

bool isAbsoluteUncPath(const u16string& path) {
    if (path.length() < 3) {
        return false;
    }
    return path[0] == u'\\' && path[1] == u'\\';
}

bool isLongPath(const u16string& path) {
    return path.length() >= 4 && path.substr(0, 4) == u"\\\\?\\";
}

bool isUncLongPath(const u16string& path) {
    return path.length() >= 8 && path.substr(0, 8) == u"\\\\?\\UNC\\";
}

// TODO How can this be done nicer, wihtout both unnecessary copy and in-place mutation?
void convertToLongPathIfNeeded(u16string& path) {
    // Technically, this should be MAX_PATH (i.e. 260), except some Win32 API related
    // to working with directory paths are actually limited to 240. It is just
    // safer/simpler to cover both cases in one code path.
    if (path.length() <= 240) {
        return;
    }

    // It is already a long path, nothing to do here
    if (isLongPath(path)) {
        return;
    }

    if (isAbsoluteLocalPath(path)) {
        // Format: C:\... -> \\?\C:\...
        path.insert(0, u"\\\\?\\");
    } else if (isAbsoluteUncPath(path)) {
        // In this case, we need to skip the first 2 characters:
        // Format: \\server\share\... -> \\?\UNC\server\share\...
        path.erase(0, 2);
        path.insert(0, u"\\\\?\\UNC\\");
    } else {
        // It is some sort of unknown format, don't mess with it
    }
}

void Server::handleEvent(JNIEnv* env, const u16string& path, FILE_NOTIFY_INFORMATION* info) {
    wstring changedPathW = wstring(info->FileName, 0, info->FileNameLength / sizeof(wchar_t));
    u16string changedPath(changedPathW.begin(), changedPathW.end());
    if (!changedPath.empty()) {
        changedPath.insert(0, 1, u'\\');
    }
    changedPath.insert(0, path);
    // TODO Remove long prefix for path once?
    if (isLongPath(changedPath)) {
        if (isUncLongPath(changedPath)) {
            changedPath.erase(0, 8).insert(0, u"\\\\");
        } else {
            changedPath.erase(0, 4);
        }
    }

    logToJava(FINE, "Change detected: 0x%x '%s'", info->Action, utf16ToUtf8String(changedPath).c_str());

    jint type;
    if (info->Action == FILE_ACTION_ADDED || info->Action == FILE_ACTION_RENAMED_NEW_NAME) {
        type = FILE_EVENT_CREATED;
    } else if (info->Action == FILE_ACTION_REMOVED || info->Action == FILE_ACTION_RENAMED_OLD_NAME) {
        type = FILE_EVENT_REMOVED;
    } else if (info->Action == FILE_ACTION_MODIFIED) {
        type = FILE_EVENT_MODIFIED;
    } else {
        logToJava(WARNING, "Unknown event 0x%x for %s", info->Action, utf16ToUtf8String(changedPath).c_str());
        type = FILE_EVENT_UNKNOWN;
    }

    reportChange(env, type, changedPath);
}

//
// Server
//

Server::Server(JNIEnv* env, size_t bufferSize, jobject watcherCallback)
    : AbstractServer(env, watcherCallback)
    , bufferSize(bufferSize) {
}

void Server::initializeRunLoop() {
    // TODO For some reason GetCurrentThread() returns a thread that doesn't accept APCs
    threadHandle = OpenThread(
        THREAD_ALL_ACCESS,
        false,
        GetCurrentThreadId()
    );
    if (threadHandle == NULL) {
        throw FileWatcherException("Couldn't open current thread", GetLastError());
    }
}

void Server::terminateRunLoop() {
    executeOnRunLoop([this]() {
        terminated = true;
        return true;
    });
}

Server::~Server() {
    terminate();
}

void Server::runLoop() {
    while (!terminated) {
        SleepEx(INFINITE, true);
    }

    // We have received termination, cancel all watchers
    unique_lock<mutex> lock(mutationMutex);
    logToJava(FINE, "Finished with run loop, now cancelling remaining watch points", NULL);
    int pendingWatchPoints = 0;
    for (auto& it : watchPoints) {
        auto& watchPoint = it.second;
        switch (watchPoint.status) {
            case LISTENING:
                try {
                    if (watchPoint.cancel()) {
                        pendingWatchPoints++;
                    }
                } catch (const exception& ex) {
                    logToJava(SEVERE, "%s", ex.what());
                }
                break;
            case CANCELLED:
                pendingWatchPoints++;
                break;
            default:
                break;
        }
    }

    // If there are any pending watchers, wait for them to finish
    if (pendingWatchPoints > 0) {
        logToJava(FINE, "Waiting for %d pending watch points to finish", pendingWatchPoints);
        SleepEx(0, true);
    }

    // Warn about  any unfinished watchpoints
    for (auto& it : watchPoints) {
        auto& watchPoint = it.second;
        switch (watchPoint.status) {
            case NOT_LISTENING:
            case FINISHED:
                break;
            default:
                logToJava(WARNING, "Watch point %s did not finish before termination timeout (status = %d)",
                    utf16ToUtf8String(watchPoint.path).c_str(), watchPoint.status);
                break;
        }
    }

    CloseHandle(threadHandle);
}

static void CALLBACK executeOnRunLoopCallback(_In_ ULONG_PTR info) {
    Command* command = (Command*) info;
    try {
        command->result = command->function();
    } catch (const exception&) {
        command->failure = current_exception();
    }
    unique_lock<mutex> lock(command->server->executionMutex);
    command->executed.notify_all();
}

bool Server::executeOnRunLoop(function<bool()> function) {
    Command command;
    command.function = function;
    command.server = this;
    unique_lock<mutex> lock(executionMutex);
    DWORD ret = QueueUserAPC(executeOnRunLoopCallback, threadHandle, (ULONG_PTR) &command);
    if (ret == 0) {
        throw FileWatcherException("Received error while queuing APC", GetLastError());
    }
    auto status = command.executed.wait_for(lock, THREAD_TIMEOUT);
    if (status == cv_status::timeout) {
        throw FileWatcherException("Execution timed out");
    } else if (command.failure) {
        rethrow_exception(command.failure);
    } else {
        return command.result;
    }
}

void Server::registerPaths(const vector<u16string>& paths) {
    executeOnRunLoop([this, paths]() {
        AbstractServer::registerPaths(paths);
        return true;
    });
}

bool Server::unregisterPaths(const vector<u16string>& paths) {
    return executeOnRunLoop([this, paths]() {
        return AbstractServer::unregisterPaths(paths);
    });
}

void Server::registerPath(const u16string& path) {
    u16string longPath = path;
    convertToLongPathIfNeeded(longPath);
    auto it = watchPoints.find(longPath);
    if (it != watchPoints.end()) {
        if (it->second.status != FINISHED) {
            throw FileWatcherException("Already watching path", path);
        }
        watchPoints.erase(it);
    }
    watchPoints.emplace(piecewise_construct,
        forward_as_tuple(longPath),
        forward_as_tuple(this, bufferSize, longPath));
}

bool Server::unregisterPath(const u16string& path) {
    u16string longPath = path;
    convertToLongPathIfNeeded(longPath);
    if (watchPoints.erase(longPath) == 0) {
        logToJava(INFO, "Path is not watched: %s", utf16ToUtf8String(path).c_str());
        return false;
    }
    return true;
}

//
// JNI calls
//

JNIEXPORT jobject JNICALL
Java_net_rubygrapefruit_platform_internal_jni_WindowsFileEventFunctions_startWatcher0(JNIEnv* env, jclass target, jint bufferSize, jobject javaCallback) {
    return wrapServer(env, new Server(env, bufferSize, javaCallback));
}

#endif
<|endoftext|>