<commit_before>/*********************************************************************
 * Software License Agreement (BSD License)
 *
 *  Copyright (c) 2013, Unbounded Robotics Inc.
 *  Copyright (c) 2012, Willow Garage, Inc.
 *  All rights reserved.
 *
 *  Redistribution and use 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 Willow Garage nor the names of its
 *     contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission.
 *
 *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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 OWNER 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.
 *********************************************************************/

/* Author: Michael Ferguson, Ioan Sucan, E. Gil Jones */

#include <ros/ros.h>
#include <moveit_simple_controller_manager/action_based_controller_handle.h>
#include <moveit_simple_controller_manager/gripper_controller_handle.h>
#include <moveit_simple_controller_manager/follow_joint_trajectory_controller_handle.h>
#include <pluginlib/class_list_macros.h>
#include <algorithm>
#include <map>

namespace moveit_simple_controller_manager
{

class MoveItSimpleControllerManager : public moveit_controller_manager::MoveItControllerManager
{
public:

  MoveItSimpleControllerManager() : node_handle_("~")
  {
    if (!node_handle_.hasParam("controller_list"))
    {
      ROS_ERROR_STREAM("MoveitSimpleControllerManager: No controller_list specified.");
      return;
    }

    XmlRpc::XmlRpcValue controller_list;
    node_handle_.getParam("controller_list", controller_list);
    if (controller_list.getType() != XmlRpc::XmlRpcValue::TypeArray)
    {
      ROS_ERROR("MoveitSimpleControllerManager: controller_list should be specified as an array");
      return;
    }

    /* actually create each controller */
    for (int i = 0 ; i < controller_list.size() ; ++i)
    {
      if (!controller_list[i].hasMember("name") || !controller_list[i].hasMember("joints"))
      {
        ROS_ERROR("MoveitSimpleControllerManager: Name and joints must be specifed for each controller");
        continue;
      }

      try
      {
        std::string name = std::string(controller_list[i]["name"]);

        std::string action_ns;
        if (controller_list[i].hasMember("ns"))
        {
          /* TODO: this used to be called "ns", renaming to "action_ns" and will remove in the future */
          action_ns = std::string(controller_list[i]["ns"]);
          ROS_WARN("MoveitSimpleControllerManager: use of 'ns' is deprecated, use 'action_ns' instead.");
        }
        else if (controller_list[i].hasMember("action_ns"))
          action_ns = std::string(controller_list[i]["action_ns"]);
        else
          ROS_WARN("MoveitSimpleControllerManager: please note that 'action_ns' no longer has a default value.");

        if (controller_list[i]["joints"].getType() != XmlRpc::XmlRpcValue::TypeArray)
        {
          ROS_ERROR_STREAM("MoveitSimpleControllerManager: The list of joints for controller " << name << " is not specified as an array");
          continue;
        }

        if (!controller_list[i].hasMember("type"))
        {
          ROS_ERROR_STREAM("MoveitSimpleControllerManager: No type specified for controller " << name);
          continue;
        }

        std::string type = std::string(controller_list[i]["type"]);

        ActionBasedControllerHandleBasePtr new_handle;
        if ( type == "GripperCommand" )
        {
          new_handle.reset(new GripperControllerHandle(name, action_ns));
          if (static_cast<GripperControllerHandle*>(new_handle.get())->isConnected())
          {
            if (controller_list[i].hasMember("parallel"))
            {
              if (controller_list[i]["joints"].size() != 2)
              {
                ROS_ERROR_STREAM("MoveItSimpleControllerManager: Parallel Gripper requires exactly two joints");
                continue;
              }
              static_cast<GripperControllerHandle*>(new_handle.get())->setParallelJawGripper(controller_list[i]["joints"][0], controller_list[i]["joints"][1]);
            }
            else
            {
              if (controller_list[i].hasMember("command_joint"))
                static_cast<GripperControllerHandle*>(new_handle.get())->setCommandJoint(controller_list[i]["command_joint"]);
              else
                static_cast<GripperControllerHandle*>(new_handle.get())->setCommandJoint(controller_list[i]["joints"][0]);
            }

            if (controller_list[i].hasMember("allow_failure"))
                static_cast<GripperControllerHandle*>(new_handle.get())->allowFailure(true);

            ROS_INFO_STREAM("MoveitSimpleControllerManager: Added GripperCommand controller for " << name );
            controllers_[name] = new_handle;
          }
        }
        else if ( type == "FollowJointTrajectory" )
        {
          new_handle.reset(new FollowJointTrajectoryControllerHandle(name, action_ns));
          if (static_cast<FollowJointTrajectoryControllerHandle*>(new_handle.get())->isConnected())
          {
            ROS_INFO_STREAM("MoveitSimpleControllerManager: Added FollowJointTrajectory controller for " << name );
            controllers_[name] = new_handle;
          }
        }
        else
        {
          ROS_ERROR("Unknown controller type: '%s'", type.c_str());
          continue;
        }
        if (!controllers_[name])
        {
          controllers_.erase(name);
          continue;
        }

        /* add list of joints, used by controller manager and moveit */
        for (int j = 0 ; j < controller_list[i]["joints"].size() ; ++j)
          controllers_[name]->addJoint(std::string(controller_list[i]["joints"][j]));
      }
      catch (...)
      {
        ROS_ERROR("MoveitSimpleControllerManager: Unable to parse controller information");
      }
    }
  }

  virtual ~MoveItSimpleControllerManager()
  {
  }

  /*
   * Get a controller, by controller name (which was specified in the controllers.yaml
   */
  virtual moveit_controller_manager::MoveItControllerHandlePtr getControllerHandle(const std::string &name)
  {
    std::map<std::string, ActionBasedControllerHandleBasePtr>::const_iterator it = controllers_.find(name);
    if (it != controllers_.end())
      return static_cast<moveit_controller_manager::MoveItControllerHandlePtr>(it->second);
    else
      ROS_FATAL_STREAM("No such controller: " << name);
    return moveit_controller_manager::MoveItControllerHandlePtr();
  }

  /*
   * Get the list of controller names.
   */
  virtual void getControllersList(std::vector<std::string> &names)
  {
    for (std::map<std::string, ActionBasedControllerHandleBasePtr>::const_iterator it = controllers_.begin() ; it != controllers_.end() ; ++it)
      names.push_back(it->first);
    ROS_INFO_STREAM("Returned " << names.size() << " controllers in list");
  }

  /*
   * This plugin assumes that all controllers are already active -- and if they are not, well, it has no way to deal with it anyways!
   */
  virtual void getActiveControllers(std::vector<std::string> &names)
  {
    getControllersList(names);
  }

  /*
   * Controller must be loaded to be active, see comment above about active controllers...
   */
  virtual void getLoadedControllers(std::vector<std::string> &names)
  {
    getControllersList(names);
  }

  /*
   * Get the list of joints that a controller can control.
   */
  virtual void getControllerJoints(const std::string &name, std::vector<std::string> &joints)
  {
    std::map<std::string, ActionBasedControllerHandleBasePtr>::const_iterator it = controllers_.find(name);
    if (it != controllers_.end())
    {
      it->second->getJoints(joints);
    }
    else
    {
      ROS_WARN("The joints for controller '%s' are not known. Perhaps the controller configuration is not loaded on the param server?", name.c_str());
      joints.clear();
    }
  }

  /*
   * Controllers are all active and default -- that's what makes this thing simple.
   */
  virtual moveit_controller_manager::MoveItControllerManager::ControllerState getControllerState(const std::string &name)
  {
    moveit_controller_manager::MoveItControllerManager::ControllerState state;
    state.active_ = true;
    state.default_ = true;
    return state;
  }

  /* Cannot switch our controllers */
  virtual bool switchControllers(const std::vector<std::string> &activate, const std::vector<std::string> &deactivate) { return false; }

protected:

  ros::NodeHandle node_handle_;
  std::map<std::string, ActionBasedControllerHandleBasePtr> controllers_;
};

} // end namespace moveit_simple_controller_manager

PLUGINLIB_EXPORT_CLASS(moveit_simple_controller_manager::MoveItSimpleControllerManager,
                       moveit_controller_manager::MoveItControllerManager);
<commit_msg>Removed redundant logging information<commit_after>/*********************************************************************
 * Software License Agreement (BSD License)
 *
 *  Copyright (c) 2013, Unbounded Robotics Inc.
 *  Copyright (c) 2012, Willow Garage, Inc.
 *  All rights reserved.
 *
 *  Redistribution and use 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 Willow Garage nor the names of its
 *     contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission.
 *
 *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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 OWNER 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.
 *********************************************************************/

/* Author: Michael Ferguson, Ioan Sucan, E. Gil Jones */

#include <ros/ros.h>
#include <moveit_simple_controller_manager/action_based_controller_handle.h>
#include <moveit_simple_controller_manager/gripper_controller_handle.h>
#include <moveit_simple_controller_manager/follow_joint_trajectory_controller_handle.h>
#include <pluginlib/class_list_macros.h>
#include <algorithm>
#include <map>

namespace moveit_simple_controller_manager
{

class MoveItSimpleControllerManager : public moveit_controller_manager::MoveItControllerManager
{
public:

  MoveItSimpleControllerManager() : node_handle_("~")
  {
    if (!node_handle_.hasParam("controller_list"))
    {
      ROS_ERROR_STREAM("No controller_list specified.");
      return;
    }

    XmlRpc::XmlRpcValue controller_list;
    node_handle_.getParam("controller_list", controller_list);
    if (controller_list.getType() != XmlRpc::XmlRpcValue::TypeArray)
    {
      ROS_ERROR("Parameter controller_list should be specified as an array");
      return;
    }

    /* actually create each controller */
    for (int i = 0 ; i < controller_list.size() ; ++i)
    {
      if (!controller_list[i].hasMember("name") || !controller_list[i].hasMember("joints"))
      {
        ROS_ERROR("Name and joints must be specifed for each controller");
        continue;
      }

      try
      {
        std::string name = std::string(controller_list[i]["name"]);

        std::string action_ns;
        if (controller_list[i].hasMember("ns"))
        {
          /* TODO: this used to be called "ns", renaming to "action_ns" and will remove in the future */
          action_ns = std::string(controller_list[i]["ns"]);
          ROS_WARN("Use of 'ns' is deprecated, use 'action_ns' instead.");
        }
        else if (controller_list[i].hasMember("action_ns"))
          action_ns = std::string(controller_list[i]["action_ns"]);
        else
          ROS_WARN("Please note that 'action_ns' no longer has a default value.");

        if (controller_list[i]["joints"].getType() != XmlRpc::XmlRpcValue::TypeArray)
        {
          ROS_ERROR_STREAM("The list of joints for controller " << name << " is not specified as an array");
          continue;
        }

        if (!controller_list[i].hasMember("type"))
        {
          ROS_ERROR_STREAM("No type specified for controller " << name);
          continue;
        }

        std::string type = std::string(controller_list[i]["type"]);

        ActionBasedControllerHandleBasePtr new_handle;
        if ( type == "GripperCommand" )
        {
          new_handle.reset(new GripperControllerHandle(name, action_ns));
          if (static_cast<GripperControllerHandle*>(new_handle.get())->isConnected())
          {
            if (controller_list[i].hasMember("parallel"))
            {
              if (controller_list[i]["joints"].size() != 2)
              {
                ROS_ERROR_STREAM("MoveItSimpleControllerManager: Parallel Gripper requires exactly two joints");
                continue;
              }
              static_cast<GripperControllerHandle*>(new_handle.get())->setParallelJawGripper(controller_list[i]["joints"][0], controller_list[i]["joints"][1]);
            }
            else
            {
              if (controller_list[i].hasMember("command_joint"))
                static_cast<GripperControllerHandle*>(new_handle.get())->setCommandJoint(controller_list[i]["command_joint"]);
              else
                static_cast<GripperControllerHandle*>(new_handle.get())->setCommandJoint(controller_list[i]["joints"][0]);
            }

            if (controller_list[i].hasMember("allow_failure"))
                static_cast<GripperControllerHandle*>(new_handle.get())->allowFailure(true);

            ROS_INFO_STREAM("Added GripperCommand controller for " << name );
            controllers_[name] = new_handle;
          }
        }
        else if ( type == "FollowJointTrajectory" )
        {
          new_handle.reset(new FollowJointTrajectoryControllerHandle(name, action_ns));
          if (static_cast<FollowJointTrajectoryControllerHandle*>(new_handle.get())->isConnected())
          {
            ROS_INFO_STREAM("Added FollowJointTrajectory controller for " << name );
            controllers_[name] = new_handle;
          }
        }
        else
        {
          ROS_ERROR("Unknown controller type: '%s'", type.c_str());
          continue;
        }
        if (!controllers_[name])
        {
          controllers_.erase(name);
          continue;
        }

        /* add list of joints, used by controller manager and moveit */
        for (int j = 0 ; j < controller_list[i]["joints"].size() ; ++j)
          controllers_[name]->addJoint(std::string(controller_list[i]["joints"][j]));
      }
      catch (...)
      {
        ROS_ERROR("Unable to parse controller information");
      }
    }
  }

  virtual ~MoveItSimpleControllerManager()
  {
  }

  /*
   * Get a controller, by controller name (which was specified in the controllers.yaml
   */
  virtual moveit_controller_manager::MoveItControllerHandlePtr getControllerHandle(const std::string &name)
  {
    std::map<std::string, ActionBasedControllerHandleBasePtr>::const_iterator it = controllers_.find(name);
    if (it != controllers_.end())
      return static_cast<moveit_controller_manager::MoveItControllerHandlePtr>(it->second);
    else
      ROS_FATAL_STREAM("No such controller: " << name);
    return moveit_controller_manager::MoveItControllerHandlePtr();
  }

  /*
   * Get the list of controller names.
   */
  virtual void getControllersList(std::vector<std::string> &names)
  {
    for (std::map<std::string, ActionBasedControllerHandleBasePtr>::const_iterator it = controllers_.begin() ; it != controllers_.end() ; ++it)
      names.push_back(it->first);
    ROS_INFO_STREAM("Returned " << names.size() << " controllers in list");
  }

  /*
   * This plugin assumes that all controllers are already active -- and if they are not, well, it has no way to deal with it anyways!
   */
  virtual void getActiveControllers(std::vector<std::string> &names)
  {
    getControllersList(names);
  }

  /*
   * Controller must be loaded to be active, see comment above about active controllers...
   */
  virtual void getLoadedControllers(std::vector<std::string> &names)
  {
    getControllersList(names);
  }

  /*
   * Get the list of joints that a controller can control.
   */
  virtual void getControllerJoints(const std::string &name, std::vector<std::string> &joints)
  {
    std::map<std::string, ActionBasedControllerHandleBasePtr>::const_iterator it = controllers_.find(name);
    if (it != controllers_.end())
    {
      it->second->getJoints(joints);
    }
    else
    {
      ROS_WARN("The joints for controller '%s' are not known. Perhaps the controller configuration is not loaded on the param server?", name.c_str());
      joints.clear();
    }
  }

  /*
   * Controllers are all active and default -- that's what makes this thing simple.
   */
  virtual moveit_controller_manager::MoveItControllerManager::ControllerState getControllerState(const std::string &name)
  {
    moveit_controller_manager::MoveItControllerManager::ControllerState state;
    state.active_ = true;
    state.default_ = true;
    return state;
  }

  /* Cannot switch our controllers */
  virtual bool switchControllers(const std::vector<std::string> &activate, const std::vector<std::string> &deactivate) { return false; }

protected:

  ros::NodeHandle node_handle_;
  std::map<std::string, ActionBasedControllerHandleBasePtr> controllers_;
};

} // end namespace moveit_simple_controller_manager

PLUGINLIB_EXPORT_CLASS(moveit_simple_controller_manager::MoveItSimpleControllerManager,
                       moveit_controller_manager::MoveItControllerManager);
<|endoftext|>


<commit_before>/*
   This file is part of the clazy static checker.

  Copyright (C) 2015 Sergio Martins <smartins@kde.org>

  This library is free software; you can redistribute it and/or
  modify it under the terms of the GNU Library General Public
  License as published by the Free Software Foundation; either
  version 2 of the License, or (at your option) any later version.

  This library 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
  Library General Public License for more details.

  You should have received a copy of the GNU Library General Public License
  along with this library; see the file COPYING.LIB.  If not, write to
  the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
  Boston, MA 02110-1301, USA.
*/

#include "qstringref.h"
#include "Utils.h"
#include "HierarchyUtils.h"
#include "checkmanager.h"
#include "StringUtils.h"
#include "FixItUtils.h"

#include <clang/AST/AST.h>
#include <vector>
#include <clang/Lex/Lexer.h>

using namespace clang;
using namespace std;

enum Fixit {
    FixitNone = 0,
    FixitUseQStringRef = 0x1,
};

StringRefCandidates::StringRefCandidates(const std::string &name, const clang::CompilerInstance &ci)
    : CheckBase(name, ci)
{
}

static bool isInterestingFirstMethod(CXXMethodDecl *method)
{
    if (!method || method->getParent()->getNameAsString() != "QString")
        return false;

    static const vector<string> list = { "left", "mid", "right" };
    return clazy_std::contains(list, method->getNameAsString());
}

static bool isInterestingSecondMethod(CXXMethodDecl *method, clang::LangOptions lo)
{
    if (!method)
        return false;

    if (method->getParent()->getNameAsString() != "QString")
        return false;

    static const vector<string> list = { "compare", "contains", "count", "startsWith", "endsWith", "indexOf",
                                         "isEmpty", "isNull", "lastIndexOf", "length", "size", "toDouble", "toInt",
                                         "toUInt", "toULong", "toULongLong", "toUShort", "toUcs4"};

    if (!clazy_std::contains(list, method->getNameAsString()))
        return false;

    return !StringUtils::anyArgIsOfAnySimpleType(method, {"QRegExp", "QRegularExpression"}, lo);
}

static bool isMethodReceivingQStringRef(CXXMethodDecl *method)
{
    static const vector<string> list = { "append", "compare", "count", "indexOf", "endsWith", "lastIndexOf", "localAwareCompare", "startsWidth", "operator+=" };

    if (!method || method->getParent()->getNameAsString() != "QString")
        return false;

    return clazy_std::contains(list, method->getNameAsString());
}

void StringRefCandidates::VisitStmt(clang::Stmt *stmt)
{
    // Here we look for code like str.firstMethod().secondMethod(), where firstMethod() is for example mid() and secondMethod is for example, toInt()

    CallExpr *call = dyn_cast<CallExpr>(stmt);
    if (!call)
        return;

    if (processCase1(dyn_cast<CXXMemberCallExpr>(call)))
        return;

    processCase2(call);
}

// Catches cases like: int i = s.mid(1, 1).toInt()
bool StringRefCandidates::processCase1(CXXMemberCallExpr *memberCall)
{
    if (!memberCall)
        return false;

    // In the AST secondMethod() is parent of firstMethod() call, and will be visited first (because at runtime firstMethod() is resolved first().
    // So check for interesting second method first
    CXXMethodDecl *method = memberCall->getMethodDecl();
    if (!isInterestingSecondMethod(method, lo()))
        return false;

    vector<CallExpr *> callExprs = Utils::callListForChain(memberCall);
    if (callExprs.size() < 2)
        return false;

    // The list now contains {secondMethod(), firstMethod() }
    CXXMemberCallExpr *firstMemberCall = dyn_cast<CXXMemberCallExpr>(callExprs.at(1));

    if (!firstMemberCall || !isInterestingFirstMethod(firstMemberCall->getMethodDecl()))
        return false;
    const string firstMethodName = firstMemberCall->getMethodDecl()->getNameAsString();

    std::vector<FixItHint> fixits;
    if (isFixitEnabled(FixitUseQStringRef)) {
        fixits = fixit(firstMemberCall);
    }
    emitWarning(firstMemberCall->getLocEnd(), "Use " + firstMethodName + "Ref() instead", fixits);
    return true;
}

// Catches cases like: s.append(s2.mid(1, 1));
bool StringRefCandidates::processCase2(CallExpr *call)
{
    CXXMemberCallExpr *memberCall = dyn_cast<CXXMemberCallExpr>(call);
    CXXOperatorCallExpr *operatorCall = dyn_cast<CXXOperatorCallExpr>(call);

    CXXMethodDecl *method = nullptr;
    if (memberCall) {
        method = memberCall->getMethodDecl();
    } else if (operatorCall && operatorCall->getCalleeDecl()) {
        Decl *decl = operatorCall->getCalleeDecl();
        method = dyn_cast<CXXMethodDecl>(decl);
    }

    if (!isMethodReceivingQStringRef(method))
        return false;

    Expr *firstArgument = call->getNumArgs() > 0 ? call->getArg(0) : nullptr;
    MaterializeTemporaryExpr *temp = firstArgument ? dyn_cast<MaterializeTemporaryExpr>(firstArgument) : nullptr;
    if (!temp) {
        Expr *secondArgument = call->getNumArgs() > 1 ? call->getArg(1) : nullptr;
        temp = secondArgument ? dyn_cast<MaterializeTemporaryExpr>(secondArgument) : nullptr;
        if (!temp) // For the CXXOperatorCallExpr it's in the second argument
            return false;
    }

    CallExpr *innerCall = HierarchyUtils::getFirstChildOfType2<CallExpr>(temp);
    CXXMemberCallExpr *innerMemberCall = innerCall ? dyn_cast<CXXMemberCallExpr>(innerCall) : nullptr;
    if (!innerMemberCall)
        return false;

    CXXMethodDecl *innerMethod = innerMemberCall->getMethodDecl();
    if (!isInterestingFirstMethod(innerMethod))
        return false;

    std::vector<FixItHint> fixits;
    if (isFixitEnabled(FixitUseQStringRef)) {
        fixits = fixit(innerMemberCall);
    }

    emitWarning(call->getLocStart(), "Use " + innerMethod->getNameAsString() + "Ref() instead", fixits);
    return true;
}

std::vector<FixItHint> StringRefCandidates::fixit(CXXMemberCallExpr *call)
{
    MemberExpr *memberExpr = HierarchyUtils::getFirstChildOfType<MemberExpr>(call);
    if (!memberExpr) {
        queueManualFixitWarning(call->getLocStart(), FixitUseQStringRef, "Internal error 1");
        return {};
    }

    auto insertionLoc = Lexer::getLocForEndOfToken(memberExpr->getLocEnd(), 0, sm(), lo());
    // llvm::errs() << insertionLoc.printToString(sm()) << "\n";
    if (!insertionLoc.isValid()) {
        queueManualFixitWarning(call->getLocStart(), FixitUseQStringRef, "Internal error 2");
        return {};
    }

    std::vector<FixItHint> fixits;
    fixits.push_back(FixItUtils::createInsertion(insertionLoc, "Ref"));
    return fixits;

}

const char *const s_checkName = "qstring-ref";
REGISTER_CHECK_WITH_FLAGS(s_checkName, StringRefCandidates, CheckLevel0)
REGISTER_FIXIT(FixitUseQStringRef, "fix-missing-qstringref", s_checkName)
<commit_msg>cleanup<commit_after>/*
   This file is part of the clazy static checker.

  Copyright (C) 2015 Sergio Martins <smartins@kde.org>

  This library is free software; you can redistribute it and/or
  modify it under the terms of the GNU Library General Public
  License as published by the Free Software Foundation; either
  version 2 of the License, or (at your option) any later version.

  This library 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
  Library General Public License for more details.

  You should have received a copy of the GNU Library General Public License
  along with this library; see the file COPYING.LIB.  If not, write to
  the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
  Boston, MA 02110-1301, USA.
*/

#include "qstringref.h"
#include "Utils.h"
#include "HierarchyUtils.h"
#include "checkmanager.h"
#include "StringUtils.h"
#include "FixItUtils.h"

#include <clang/AST/AST.h>
#include <vector>
#include <clang/Lex/Lexer.h>

using namespace clang;
using namespace std;

enum Fixit {
    FixitNone = 0,
    FixitUseQStringRef = 0x1,
};

StringRefCandidates::StringRefCandidates(const std::string &name, const clang::CompilerInstance &ci)
    : CheckBase(name, ci)
{
}

static bool isInterestingFirstMethod(CXXMethodDecl *method)
{
    if (!method || method->getParent()->getNameAsString() != "QString")
        return false;

    static const vector<string> list = { "left", "mid", "right" };
    return clazy_std::contains(list, method->getNameAsString());
}

static bool isInterestingSecondMethod(CXXMethodDecl *method, clang::LangOptions lo)
{
    if (!method || method->getParent()->getNameAsString() != "QString")
        return false;

    static const vector<string> list = { "compare", "contains", "count", "startsWith", "endsWith", "indexOf",
                                         "isEmpty", "isNull", "lastIndexOf", "length", "size", "toDouble", "toInt",
                                         "toUInt", "toULong", "toULongLong", "toUShort", "toUcs4"};

    if (!clazy_std::contains(list, method->getNameAsString()))
        return false;

    return !StringUtils::anyArgIsOfAnySimpleType(method, {"QRegExp", "QRegularExpression"}, lo);
}

static bool isMethodReceivingQStringRef(CXXMethodDecl *method)
{
    static const vector<string> list = { "append", "compare", "count", "indexOf", "endsWith", "lastIndexOf", "localAwareCompare", "startsWidth", "operator+=" };

    if (!method || method->getParent()->getNameAsString() != "QString")
        return false;

    return clazy_std::contains(list, method->getNameAsString());
}

void StringRefCandidates::VisitStmt(clang::Stmt *stmt)
{
    // Here we look for code like str.firstMethod().secondMethod(), where firstMethod() is for example mid() and secondMethod is for example, toInt()

    CallExpr *call = dyn_cast<CallExpr>(stmt);
    if (!call || processCase1(dyn_cast<CXXMemberCallExpr>(call)))
        return;

    processCase2(call);
}

// Catches cases like: int i = s.mid(1, 1).toInt()
bool StringRefCandidates::processCase1(CXXMemberCallExpr *memberCall)
{
    if (!memberCall)
        return false;

    // In the AST secondMethod() is parent of firstMethod() call, and will be visited first (because at runtime firstMethod() is resolved first().
    // So check for interesting second method first
    CXXMethodDecl *method = memberCall->getMethodDecl();
    if (!isInterestingSecondMethod(method, lo()))
        return false;

    vector<CallExpr *> callExprs = Utils::callListForChain(memberCall);
    if (callExprs.size() < 2)
        return false;

    // The list now contains {secondMethod(), firstMethod() }
    CXXMemberCallExpr *firstMemberCall = dyn_cast<CXXMemberCallExpr>(callExprs.at(1));

    if (!firstMemberCall || !isInterestingFirstMethod(firstMemberCall->getMethodDecl()))
        return false;
    const string firstMethodName = firstMemberCall->getMethodDecl()->getNameAsString();

    std::vector<FixItHint> fixits;
    if (isFixitEnabled(FixitUseQStringRef)) {
        fixits = fixit(firstMemberCall);
    }
    emitWarning(firstMemberCall->getLocEnd(), "Use " + firstMethodName + "Ref() instead", fixits);
    return true;
}

// Catches cases like: s.append(s2.mid(1, 1));
bool StringRefCandidates::processCase2(CallExpr *call)
{
    CXXMemberCallExpr *memberCall = dyn_cast<CXXMemberCallExpr>(call);
    CXXOperatorCallExpr *operatorCall = dyn_cast<CXXOperatorCallExpr>(call);

    CXXMethodDecl *method = nullptr;
    if (memberCall) {
        method = memberCall->getMethodDecl();
    } else if (operatorCall && operatorCall->getCalleeDecl()) {
        Decl *decl = operatorCall->getCalleeDecl();
        method = dyn_cast<CXXMethodDecl>(decl);
    }

    if (!isMethodReceivingQStringRef(method))
        return false;

    Expr *firstArgument = call->getNumArgs() > 0 ? call->getArg(0) : nullptr;
    MaterializeTemporaryExpr *temp = firstArgument ? dyn_cast<MaterializeTemporaryExpr>(firstArgument) : nullptr;
    if (!temp) {
        Expr *secondArgument = call->getNumArgs() > 1 ? call->getArg(1) : nullptr;
        temp = secondArgument ? dyn_cast<MaterializeTemporaryExpr>(secondArgument) : nullptr;
        if (!temp) // For the CXXOperatorCallExpr it's in the second argument
            return false;
    }

    CallExpr *innerCall = HierarchyUtils::getFirstChildOfType2<CallExpr>(temp);
    CXXMemberCallExpr *innerMemberCall = innerCall ? dyn_cast<CXXMemberCallExpr>(innerCall) : nullptr;
    if (!innerMemberCall)
        return false;

    CXXMethodDecl *innerMethod = innerMemberCall->getMethodDecl();
    if (!isInterestingFirstMethod(innerMethod))
        return false;

    std::vector<FixItHint> fixits;
    if (isFixitEnabled(FixitUseQStringRef)) {
        fixits = fixit(innerMemberCall);
    }

    emitWarning(call->getLocStart(), "Use " + innerMethod->getNameAsString() + "Ref() instead", fixits);
    return true;
}

std::vector<FixItHint> StringRefCandidates::fixit(CXXMemberCallExpr *call)
{
    MemberExpr *memberExpr = HierarchyUtils::getFirstChildOfType<MemberExpr>(call);
    if (!memberExpr) {
        queueManualFixitWarning(call->getLocStart(), FixitUseQStringRef, "Internal error 1");
        return {};
    }

    auto insertionLoc = Lexer::getLocForEndOfToken(memberExpr->getLocEnd(), 0, sm(), lo());
    // llvm::errs() << insertionLoc.printToString(sm()) << "\n";
    if (!insertionLoc.isValid()) {
        queueManualFixitWarning(call->getLocStart(), FixitUseQStringRef, "Internal error 2");
        return {};
    }

    std::vector<FixItHint> fixits;
    fixits.push_back(FixItUtils::createInsertion(insertionLoc, "Ref"));
    return fixits;

}

const char *const s_checkName = "qstring-ref";
REGISTER_CHECK_WITH_FLAGS(s_checkName, StringRefCandidates, CheckLevel0)
REGISTER_FIXIT(FixitUseQStringRef, "fix-missing-qstringref", s_checkName)
<|endoftext|>


<commit_before>/*! \file
 *
 * \brief Storage of generic data via (key, value) pair (source)
 * \author Benjamin Pritchard (ben@bennyp.org)
 */

#include <boost/python.hpp>

#include "bpmodule/options/OptionHolder.hpp"
#include "bpmodule/options/OptionTypes.hpp"
#include "bpmodule/output/Output.hpp"
#include "bpmodule/python_helper/Convert.hpp"
#include "bpmodule/exception/OptionException.hpp"
#include "bpmodule/output/FormatStr.hpp"

using bpmodule::python_helper::PythonType;
using bpmodule::python_helper::StrToPythonType;
using bpmodule::python_helper::GetPyClass;
using bpmodule::python_helper::DetermineType;
using bpmodule::python_helper::TestConvertToPy;
using bpmodule::python_helper::ConvertToPy;
using bpmodule::python_helper::TestConvertToCpp;
using bpmodule::python_helper::ConvertToCpp;

using bpmodule::exception::OptionException;

namespace bpmodule {
namespace options {
namespace detail {

///////////////////////////////////////////////////
// These are the allowed types of OptionHolder
///////////////////////////////////////////////////
template class OptionHolder<OptionInt>;
template class OptionHolder<OptionFloat>;
template class OptionHolder<bool>;
template class OptionHolder<std::string>;
template class OptionHolder<std::vector<OptionInt>>;
template class OptionHolder<std::vector<OptionFloat>>;
template class OptionHolder<std::vector<bool>>;
template class OptionHolder<std::vector<std::string>>;

///////////////////////////////////////////
// Forward declarations of free functions
///////////////////////////////////////////
template<typename T>
static void PrintOption_(const OptionHolder<T> & oph);
template<typename T>
static void PrintOption_(const OptionHolder<std::vector<T>> & oph);

///////////////////////////////////////////////////
// OptionHolder members
///////////////////////////////////////////////////
template<typename T>
OptionHolder<T>::OptionHolder(const std::string & key, T * def,
                              ValidatorFunc validator, bool required,
                              PythonType pytype,
                              const std::string & help)
    : OptionBase(key, required, pytype, help),
      default_(def),
      validator_(validator)
{
    // check the default using the validator
    if(def != nullptr)
        ValidateOrThrow_(*default_, "initial default");
    if(def != nullptr && required)
        throw OptionException("Default value supplied for required option", Key());
}

template<typename T>
OptionHolder<T>::OptionHolder(const OptionHolder & oph)
    : OptionBase(oph),
      validator_(oph.validator_)
{
    if(oph.value_)
        value_ = std::unique_ptr<T>(new T(*oph.value_));
    if(oph.default_)
        default_ = std::unique_ptr<T>(new T(*oph.default_));
}

template<typename T>
void OptionHolder<T>::Change(const T & value)
{
    ValidateOrThrow_(value, "new value");
    value_ = std::unique_ptr<T>(new T(value));
}

template<typename T>
const T & OptionHolder<T>::Get(void) const
{
    if(value_)
        return *value_;
    else if(default_)
        return *default_;
    else
        throw OptionException("Option does not have a value", Key());
}

template<typename T>
const T & OptionHolder<T>::GetDefault(void) const
{
    if(default_)
        return *default_;
    else
        throw OptionException("Option does not have a default", Key());
}

template<typename T>
OptionBase * OptionHolder<T>::Clone(void) const
{
    return new OptionHolder<T>(*this);
}

template<typename T>
constexpr const char * OptionHolder<T>::Type(void) const noexcept
{
    return typeid(T).name();
}

template<typename T>
bool OptionHolder<T>::HasValue(void) const noexcept
{
    return bool(value_) || bool(default_);
}

template<typename T>
bool OptionHolder<T>::HasDefault(void) const noexcept
{
    return bool(default_);
}

template<typename T>
bool OptionHolder<T>::IsDefault(void) const
{
    if(!value_ && default_)
        return true;
    else
        return value_ && default_ && (*value_ == *default_);
}

template<typename T>
void OptionHolder<T>::ResetToDefault(void) noexcept
{
    value_.reset();
}

template<typename T>
void OptionHolder<T>::Print(void) const
{
    PrintOption_(*this);
}

template<typename T>
void OptionHolder<T>::ValidateOrThrow_(const T & value, const std::string & desc) const
{
    if(!validator_(value))
    {
        //! \todo add exception info from validator?
        if(!OptionBase::IsExpert())
            throw OptionException("Value is not valid for this option", Key());
        else
            output::Warning("Value for option %1% \"%2\" is invalid. Ignoring\n", desc, Key());
    }
}

/////////////////////////////////////////
// Python-related functions
/////////////////////////////////////////
template<typename T>
boost::python::object OptionHolder<T>::GetPy(void) const
{
    if(!TestConvertToPy(Get()))
        throw OptionException("Cannot convert option value to python object", Key(),
                                         "valuetype", Type());

    return ConvertToPy(Get());
}

template<typename T>
void OptionHolder<T>::ChangePy(const boost::python::object & obj)
{
    if(!TestConvertToCpp<T>(obj))
        throw OptionException("Cannot convert python object to option value", Key(),
                                         "valuetype", Type(),
                                         "pythontype", GetPyClass(obj));

    // will validate inside Change()
    Change(ConvertToCpp<T>(obj));
}

/////////////////////////////////////////
// Printing functions (free functions)
/////////////////////////////////////////

//////////////////
// Helpers
//////////////////
template<typename T>
static std::string OptToString_(const T & opt)
{
    return std::to_string(opt);
}

static std::string OptToString_(const std::string & opt)
{
    return opt;
}

static std::string OptToString_(const bool & opt)
{
    return (opt ? "True" : "False");
}

template<typename T>
static void PrintOption_(const OptionHolder<T> & oph)
{
    std::string optline = FormatStr("          %|1$-20|      %|2$-20|      %|3$-20|      %|4$-20|     %|5$-10|       %6%\n",
                                    oph.Key(),                                                         // name/key
                                    PythonTypeToStr(oph.PyType()),                      // type
                                    (oph.HasValue() ? OptToString_(oph.Get()) : "(none)"),             // value
                                    (oph.HasDefault() ? OptToString_(oph.GetDefault()) : "(none)"),    // default
                                    (oph.IsRequired() ? "True" : "False"),                             // required
                                    oph.Help());                                                       // help/description

    if(!oph.IsValid())
        output::Error(optline);

    if(!oph.IsDefault())
        output::Changed(optline);
    else
        output::Output(optline);
}

template<typename T>
static void PrintOption_(const OptionHolder<std::vector<T>> & oph)
{
    size_t nrows = 0;

    std::string valstr, defstr;

    if(oph.HasValue())
    {
        auto valuevec = oph.Get();
        nrows = valuevec.size();

        if(valuevec.size() == 0)
            valstr = "(empty)";
        else
            valstr = OptToString_(valuevec[0]);
    }
    else
        valstr = "(none)";

    if(oph.HasDefault())
    {
        auto defvec = oph.GetDefault();
        nrows = std::max(nrows, defvec.size());

        if(defvec.size() == 0)
            defstr = "(empty)";
        else
            defstr = OptToString_(defvec[0]);
    }
    else
        defstr = "(none)";

    std::vector<std::string> optlines;
    optlines.push_back(FormatStr("          %|1$-20|      %|2$-20|      %|3$-20|      %|4$-20|     %|5$-10|       %6%\n",
                                 oph.Key(),                                                         // name/key
                                 PythonTypeToStr(oph.PyType()),                      // type
                                 valstr,                                                            // value
                                 defstr,                                                            // default
                                 (oph.IsRequired() ? "True" : "False"),                             // required
                                 oph.Help()));                                                      // help/description

    // start at 1 since we did the first separately
    for(size_t i = 1; i < nrows; i++)
    {
        std::string valstr, defstr;

        if(oph.HasValue())
        {
            auto valuevec = oph.Get();
            if(valuevec.size() > i)
                valstr = OptToString_(valuevec[i]);
        }

        if(oph.HasDefault())
        {
            auto defvec = oph.GetDefault();
            if(defvec.size() > i)
                defstr = OptToString_(defvec[i]);
        }

        optlines.push_back(FormatStr("          %|1$-20|      %|2$-20|      %|3$-20|      %|4$-20|     %|5$-10|       %6%\n",
                                     "",              // name/key
                                     "",              // type
                                     valstr,          // value
                                     defstr,          // default
                                     "",              // required
                                     ""));            // help/description

    }

    for(const auto & it : optlines)
    {
        if(!oph.IsValid())
            output::Error(it);
        else if(!oph.IsDefault())
            output::Changed(it);
        else
            output::Output(it);
    }

}

////////////////////////////////////////////////////////
// CreateOptionHolder & helper functions
////////////////////////////////////////////////////////
template<typename T>
static bool ValidateWrapper(const boost::python::object & val, T arg)
{
    return boost::python::extract<bool>(val.attr("Validate")(arg));
}

template<typename T>
static bool EmptyValidator(T arg)
{
    return true;
}

template<typename T>
static OptionBasePtr CreateOptionHolder(const std::string & key, const boost::python::tuple & tup)
{
    PythonType ptype_default = DetermineType(tup[1]);

    T * def = nullptr;

    if(ptype_default != PythonType::None)
    {
        if(!TestConvertToCpp<T>(tup[1]))
            throw OptionException("Default for option cannot be converted from python",
                                  key,
                                  "fromtype", GetPyClass(tup[1]),
                                  "totype", typeid(T).name());

        // shouldn't throw given the above
        def = new T(ConvertToCpp<T>(tup[1]));
    }

    // Already checked
    /*
    PythonType ptype_type = DetermineType(tup[0]);
    if(ptype_type != PythonType::String)
        throw OptionException("\"Type\" element of tuple is not a bool", key, "type", GetPyClass(tup[0]));
    */

    PythonType ptype_required = DetermineType(tup[2]);
    if(ptype_required != PythonType::Bool)
        throw OptionException("\"Required\" element of tuple is not a bool", key, "type", GetPyClass(tup[2]));

    PythonType ptype_help = DetermineType(tup[4]);
    if(ptype_help != PythonType::String)
        throw OptionException("\"Help\" element of tuple is not a string", key, "type", GetPyClass(tup[4]));

    bool req = boost::python::extract<bool>(tup[2]);
    std::string help = boost::python::extract<std::string>(tup[4]);
    PythonType pytype = StrToPythonType(boost::python::extract<std::string>(tup[0]));

    //! \todo Check to make sure validator object is callable

    // Check if validator is given. If not, use EmptyValidator
    typename OptionHolder<T>::ValidatorFunc validator = EmptyValidator<T>;

    if(DetermineType(tup[3]) != PythonType::None)
        validator = std::bind(ValidateWrapper<T>, tup[3], std::placeholders::_1);

    return OptionBasePtr(new OptionHolder<T>(key, def, validator, req, pytype, help));
}

OptionBasePtr OptionHolderFactory(const std::string & key, const boost::python::object & obj)
{
    PythonType ptype = DetermineType(obj);
    if(ptype != PythonType::Tuple)
        throw OptionException("Object for option is not a tuple", key, "pythontype", GetPyClass(obj));

    boost::python::tuple tup = boost::python::extract<boost::python::tuple>(obj);

    int length = boost::python::extract<int>(tup.attr("__len__")());
    if(length != 5)
        throw OptionException("Python options tuple does not have 5 elements", key, "length", std::to_string(length));

    if(!TestConvertToCpp<std::string>(tup[0]))
        throw OptionException("\"Type\" element of tuple is not a string", key, "type", GetPyClass(tup[0]));

    std::string type = ConvertToCpp<std::string>(tup[0]);

    switch(StrToPythonType(type))
    {
        case PythonType::Bool:
            return CreateOptionHolder<bool>(key, tup);
        case PythonType::Int:
            return CreateOptionHolder<OptionInt>(key, tup);
        case PythonType::Float:
            return CreateOptionHolder<OptionFloat>(key, tup);
        case PythonType::String:
            return CreateOptionHolder<std::string>(key, tup);
        case PythonType::ListBool:
            return CreateOptionHolder<std::vector<bool>>(key, tup);
        case PythonType::ListInt:
            return CreateOptionHolder<std::vector<OptionInt>>(key, tup);
        case PythonType::ListFloat:
            return CreateOptionHolder<std::vector<OptionFloat>>(key, tup);
        case PythonType::ListString:
            return CreateOptionHolder<std::vector<std::string>>(key, tup);
        default:
            throw OptionException("Cannot convert python type to option", key, "type", type);
    }
}

} // close namespace detail
} // close namespace options
} // close namespace bpmodule

<commit_msg>Check exceptions from convert. These may throw even if the check succeeds<commit_after>/*! \file
 *
 * \brief Storage of generic data via (key, value) pair (source)
 * \author Benjamin Pritchard (ben@bennyp.org)
 */

#include <boost/python.hpp>

#include "bpmodule/options/OptionHolder.hpp"
#include "bpmodule/options/OptionTypes.hpp"
#include "bpmodule/output/Output.hpp"
#include "bpmodule/python_helper/Convert.hpp"
#include "bpmodule/exception/OptionException.hpp"
#include "bpmodule/output/FormatStr.hpp"

using bpmodule::python_helper::PythonType;
using bpmodule::python_helper::StrToPythonType;
using bpmodule::python_helper::GetPyClass;
using bpmodule::python_helper::DetermineType;
using bpmodule::python_helper::TestConvertToPy;
using bpmodule::python_helper::ConvertToPy;
using bpmodule::python_helper::TestConvertToCpp;
using bpmodule::python_helper::ConvertToCpp;

using bpmodule::exception::OptionException;

namespace bpmodule {
namespace options {
namespace detail {

///////////////////////////////////////////////////
// These are the allowed types of OptionHolder
///////////////////////////////////////////////////
template class OptionHolder<OptionInt>;
template class OptionHolder<OptionFloat>;
template class OptionHolder<bool>;
template class OptionHolder<std::string>;
template class OptionHolder<std::vector<OptionInt>>;
template class OptionHolder<std::vector<OptionFloat>>;
template class OptionHolder<std::vector<bool>>;
template class OptionHolder<std::vector<std::string>>;

///////////////////////////////////////////
// Forward declarations of free functions
///////////////////////////////////////////
template<typename T>
static void PrintOption_(const OptionHolder<T> & oph);
template<typename T>
static void PrintOption_(const OptionHolder<std::vector<T>> & oph);

///////////////////////////////////////////////////
// OptionHolder members
///////////////////////////////////////////////////
template<typename T>
OptionHolder<T>::OptionHolder(const std::string & key, T * def,
                              ValidatorFunc validator, bool required,
                              PythonType pytype,
                              const std::string & help)
    : OptionBase(key, required, pytype, help),
      default_(def),
      validator_(validator)
{
    // check the default using the validator
    if(def != nullptr)
        ValidateOrThrow_(*default_, "initial default");
    if(def != nullptr && required)
        throw OptionException("Default value supplied for required option", Key());
}

template<typename T>
OptionHolder<T>::OptionHolder(const OptionHolder & oph)
    : OptionBase(oph),
      validator_(oph.validator_)
{
    if(oph.value_)
        value_ = std::unique_ptr<T>(new T(*oph.value_));
    if(oph.default_)
        default_ = std::unique_ptr<T>(new T(*oph.default_));
}

template<typename T>
void OptionHolder<T>::Change(const T & value)
{
    ValidateOrThrow_(value, "new value");
    value_ = std::unique_ptr<T>(new T(value));
}

template<typename T>
const T & OptionHolder<T>::Get(void) const
{
    if(value_)
        return *value_;
    else if(default_)
        return *default_;
    else
        throw OptionException("Option does not have a value", Key());
}

template<typename T>
const T & OptionHolder<T>::GetDefault(void) const
{
    if(default_)
        return *default_;
    else
        throw OptionException("Option does not have a default", Key());
}

template<typename T>
OptionBase * OptionHolder<T>::Clone(void) const
{
    return new OptionHolder<T>(*this);
}

template<typename T>
constexpr const char * OptionHolder<T>::Type(void) const noexcept
{
    return typeid(T).name();
}

template<typename T>
bool OptionHolder<T>::HasValue(void) const noexcept
{
    return bool(value_) || bool(default_);
}

template<typename T>
bool OptionHolder<T>::HasDefault(void) const noexcept
{
    return bool(default_);
}

template<typename T>
bool OptionHolder<T>::IsDefault(void) const
{
    if(!value_ && default_)
        return true;
    else
        return value_ && default_ && (*value_ == *default_);
}

template<typename T>
void OptionHolder<T>::ResetToDefault(void) noexcept
{
    value_.reset();
}

template<typename T>
void OptionHolder<T>::Print(void) const
{
    PrintOption_(*this);
}

template<typename T>
void OptionHolder<T>::ValidateOrThrow_(const T & value, const std::string & desc) const
{
    if(!validator_(value))
    {
        //! \todo add exception info from validator?
        if(!OptionBase::IsExpert())
            throw OptionException("Value is not valid for this option", Key());
        else
            output::Warning("Value for option %1% \"%2\" is invalid. Ignoring\n", desc, Key());
    }
}

/////////////////////////////////////////
// Python-related functions
/////////////////////////////////////////
template<typename T>
boost::python::object OptionHolder<T>::GetPy(void) const
{
    if(!TestConvertToPy(Get()))
        throw OptionException("Cannot convert option value to python object", Key(),
                                         "valuetype", Type());

    try {
        return ConvertToPy(Get()); // may throw in extreme cases, even if test succeeds
    }
    catch(exception::GeneralException & ex)
    {
        throw OptionException(ex, Key(), "valuetype", Type());
    }
}

template<typename T>
void OptionHolder<T>::ChangePy(const boost::python::object & obj)
{
    if(!TestConvertToCpp<T>(obj))
        throw OptionException("Cannot convert python object to option value", Key(),
                                         "valuetype", Type(),
                                         "pythontype", GetPyClass(obj));

    T val;

    try {
        val = ConvertToCpp<T>(obj);  // may throw in extreme cases, even if test succeeds
    }
    catch(exception::GeneralException & ex)
    {
        throw OptionException(ex, Key(), "valuetype", Type());
    }

    // will validate inside Change()
    Change(val);
}

/////////////////////////////////////////
// Printing functions (free functions)
/////////////////////////////////////////

//////////////////
// Helpers
//////////////////
template<typename T>
static std::string OptToString_(const T & opt)
{
    return std::to_string(opt);
}

static std::string OptToString_(const std::string & opt)
{
    return opt;
}

static std::string OptToString_(const bool & opt)
{
    return (opt ? "True" : "False");
}

template<typename T>
static void PrintOption_(const OptionHolder<T> & oph)
{
    std::string optline = FormatStr("          %|1$-20|      %|2$-20|      %|3$-20|      %|4$-20|     %|5$-10|       %6%\n",
                                    oph.Key(),                                                         // name/key
                                    PythonTypeToStr(oph.PyType()),                      // type
                                    (oph.HasValue() ? OptToString_(oph.Get()) : "(none)"),             // value
                                    (oph.HasDefault() ? OptToString_(oph.GetDefault()) : "(none)"),    // default
                                    (oph.IsRequired() ? "True" : "False"),                             // required
                                    oph.Help());                                                       // help/description

    if(!oph.IsValid())
        output::Error(optline);

    if(!oph.IsDefault())
        output::Changed(optline);
    else
        output::Output(optline);
}

template<typename T>
static void PrintOption_(const OptionHolder<std::vector<T>> & oph)
{
    size_t nrows = 0;

    std::string valstr, defstr;

    if(oph.HasValue())
    {
        auto valuevec = oph.Get();
        nrows = valuevec.size();

        if(valuevec.size() == 0)
            valstr = "(empty)";
        else
            valstr = OptToString_(valuevec[0]);
    }
    else
        valstr = "(none)";

    if(oph.HasDefault())
    {
        auto defvec = oph.GetDefault();
        nrows = std::max(nrows, defvec.size());

        if(defvec.size() == 0)
            defstr = "(empty)";
        else
            defstr = OptToString_(defvec[0]);
    }
    else
        defstr = "(none)";

    std::vector<std::string> optlines;
    optlines.push_back(FormatStr("          %|1$-20|      %|2$-20|      %|3$-20|      %|4$-20|     %|5$-10|       %6%\n",
                                 oph.Key(),                                                         // name/key
                                 PythonTypeToStr(oph.PyType()),                      // type
                                 valstr,                                                            // value
                                 defstr,                                                            // default
                                 (oph.IsRequired() ? "True" : "False"),                             // required
                                 oph.Help()));                                                      // help/description

    // start at 1 since we did the first separately
    for(size_t i = 1; i < nrows; i++)
    {
        std::string valstr, defstr;

        if(oph.HasValue())
        {
            auto valuevec = oph.Get();
            if(valuevec.size() > i)
                valstr = OptToString_(valuevec[i]);
        }

        if(oph.HasDefault())
        {
            auto defvec = oph.GetDefault();
            if(defvec.size() > i)
                defstr = OptToString_(defvec[i]);
        }

        optlines.push_back(FormatStr("          %|1$-20|      %|2$-20|      %|3$-20|      %|4$-20|     %|5$-10|       %6%\n",
                                     "",              // name/key
                                     "",              // type
                                     valstr,          // value
                                     defstr,          // default
                                     "",              // required
                                     ""));            // help/description

    }

    for(const auto & it : optlines)
    {
        if(!oph.IsValid())
            output::Error(it);
        else if(!oph.IsDefault())
            output::Changed(it);
        else
            output::Output(it);
    }

}

////////////////////////////////////////////////////////
// CreateOptionHolder & helper functions
////////////////////////////////////////////////////////
template<typename T>
static bool ValidateWrapper(const boost::python::object & val, T arg)
{
    return boost::python::extract<bool>(val.attr("Validate")(arg));
}

template<typename T>
static bool EmptyValidator(T arg)
{
    return true;
}

template<typename T>
static OptionBasePtr CreateOptionHolder(const std::string & key, const boost::python::tuple & tup)
{
    PythonType ptype_default = DetermineType(tup[1]);

    T * def = nullptr;

    if(ptype_default != PythonType::None)
    {
        if(!TestConvertToCpp<T>(tup[1]))
            throw OptionException("Default for option cannot be converted from python",
                                  key,
                                  "fromtype", GetPyClass(tup[1]),
                                  "totype", typeid(T).name());

        // shouldn't throw given the above
        def = new T(ConvertToCpp<T>(tup[1]));
    }

    // Already checked
    /*
    PythonType ptype_type = DetermineType(tup[0]);
    if(ptype_type != PythonType::String)
        throw OptionException("\"Type\" element of tuple is not a bool", key, "type", GetPyClass(tup[0]));
    */

    PythonType ptype_required = DetermineType(tup[2]);
    if(ptype_required != PythonType::Bool)
        throw OptionException("\"Required\" element of tuple is not a bool", key, "type", GetPyClass(tup[2]));

    PythonType ptype_help = DetermineType(tup[4]);
    if(ptype_help != PythonType::String)
        throw OptionException("\"Help\" element of tuple is not a string", key, "type", GetPyClass(tup[4]));

    bool req = boost::python::extract<bool>(tup[2]);
    std::string help = boost::python::extract<std::string>(tup[4]);
    PythonType pytype = StrToPythonType(boost::python::extract<std::string>(tup[0]));

    //! \todo Check to make sure validator object is callable

    // Check if validator is given. If not, use EmptyValidator
    typename OptionHolder<T>::ValidatorFunc validator = EmptyValidator<T>;

    if(DetermineType(tup[3]) != PythonType::None)
        validator = std::bind(ValidateWrapper<T>, tup[3], std::placeholders::_1);

    return OptionBasePtr(new OptionHolder<T>(key, def, validator, req, pytype, help));
}

OptionBasePtr OptionHolderFactory(const std::string & key, const boost::python::object & obj)
{
    PythonType ptype = DetermineType(obj);
    if(ptype != PythonType::Tuple)
        throw OptionException("Object for option is not a tuple", key, "pythontype", GetPyClass(obj));

    boost::python::tuple tup = boost::python::extract<boost::python::tuple>(obj);

    int length = boost::python::extract<int>(tup.attr("__len__")());
    if(length != 5)
        throw OptionException("Python options tuple does not have 5 elements", key, "length", std::to_string(length));

    if(!TestConvertToCpp<std::string>(tup[0]))
        throw OptionException("\"Type\" element of tuple is not a string", key, "type", GetPyClass(tup[0]));

    std::string type = ConvertToCpp<std::string>(tup[0]);

    switch(StrToPythonType(type))
    {
        case PythonType::Bool:
            return CreateOptionHolder<bool>(key, tup);
        case PythonType::Int:
            return CreateOptionHolder<OptionInt>(key, tup);
        case PythonType::Float:
            return CreateOptionHolder<OptionFloat>(key, tup);
        case PythonType::String:
            return CreateOptionHolder<std::string>(key, tup);
        case PythonType::ListBool:
            return CreateOptionHolder<std::vector<bool>>(key, tup);
        case PythonType::ListInt:
            return CreateOptionHolder<std::vector<OptionInt>>(key, tup);
        case PythonType::ListFloat:
            return CreateOptionHolder<std::vector<OptionFloat>>(key, tup);
        case PythonType::ListString:
            return CreateOptionHolder<std::vector<std::string>>(key, tup);
        default:
            throw OptionException("Cannot convert python type to option", key, "type", type);
    }
}

} // close namespace detail
} // close namespace options
} // close namespace bpmodule

<|endoftext|>


<commit_before>#include "acmacs-base/rjson-v3-helper.hh"
#include "acmacs-chart-2/serum-circle.hh"
#include "acmacs-map-draw/mapi-settings.hh"
#include "acmacs-map-draw/select-filter.hh"
#include "acmacs-map-draw/draw.hh"

// ----------------------------------------------------------------------

static void report_circles(fmt::memory_buffer& report, const acmacs::chart::Antigens& antigens, const acmacs::chart::PointIndexList& antigen_indexes, const acmacs::chart::SerumCircle& empirical,
                           const acmacs::chart::SerumCircle& theoretical);

// ----------------------------------------------------------------------

//  "?homologous_titer": "1280",
//  "mark_serum":   {"fill": "lightblue", "outline": "black", "order": "raise", "label": {"format": "{full_name}", "offset": [0, 1.2], "color": "black", "size": 12}},
//  "mark_antigen": {"fill": "lightblue", "outline": "black", "order": "raise", "label": {"name_type": "{full_name}", "offset": [0, 1.2], "color": "black", "size": 12}}},

bool acmacs::mapi::v1::Settings::apply_serum_circles()
{
    using namespace std::string_view_literals;

    auto& chart = chart_draw().chart();
    auto antigens = chart.antigens();
    auto sera = chart.sera();
    auto titers = chart.titers();
    auto layout = chart_draw().layout();

    const auto serum_indexes = select_sera(getenv("serum"sv, "sera"sv));
    const auto fold = rjson::v3::read_number(getenv("fold"sv), 2.0);
    const auto forced_homologous_titer = rjson::v3::read_string(getenv("homologous_titer"sv));
    const auto verb = verbose_from(rjson::v3::read_bool(getenv("verbose"sv), false));
    const auto& antigen_selector{getenv("antigen"sv, "antigens"sv)};
    fmt::memory_buffer report;
    const size_t indent{2};
    for (auto serum_index : serum_indexes) {
        auto serum = sera->at(serum_index);
        fmt::format_to(report, "{:{}c}SR {} {} {}\n", ' ', indent, serum_index, serum->full_name(), serum->passage_type(acmacs::chart::reassortant_as_egg::no));
        if (!layout->point_has_coordinates(serum_index + antigens->size())) {
            fmt::format_to(report, "{:{}c}  *** serum is disconnected\n", ' ', indent);
        }
        else if (const auto antigen_indexes = select_antigens_for_serum_circle(serum_index, antigen_selector); !antigen_indexes.empty()) {
            const auto column_basis = chart.column_basis(serum_index, chart_draw().projection_no());
            acmacs::chart::SerumCircle empirical, theoretical;
            if (forced_homologous_titer.has_value()) {
                empirical = acmacs::chart::serum_circle_empirical(antigen_indexes, *forced_homologous_titer, serum_index, *layout, column_basis, *titers, fold, verb);
                theoretical = acmacs::chart::serum_circle_theoretical(*forced_homologous_titer, serum_index, column_basis, fold);
            }
            else {
                empirical = acmacs::chart::serum_circle_empirical(antigen_indexes, serum_index, *layout, column_basis, *titers, fold, verb);
                theoretical = acmacs::chart::serum_circle_theoretical(antigen_indexes, serum_index, column_basis, *titers, fold);
            }
            report_circles(report, *antigens, antigen_indexes, empirical, theoretical);

            const auto serum_passage = serum->passage_type(acmacs::chart::reassortant_as_egg::no);
            std::optional<size_t> mark_antigen;
            if (empirical.valid()) {
                make_circle(chart_draw().serum_circle(serum_index, Scaled{empirical.radius()}), serum_passage, getenv("empirical"sv));
                mark_antigen = empirical.per_antigen().front().antigen_no;
            }
            if (theoretical.valid()) {
                make_circle(chart_draw().serum_circle(serum_index, Scaled{theoretical.radius()}), serum_passage, getenv("theoretical"sv));
                if (!mark_antigen.has_value())
                    mark_antigen = theoretical.per_antigen().front().antigen_no;
            }
            if (!empirical.valid() && !theoretical.valid()) {
                if (const auto& fallback = getenv("fallback"sv); !fallback.is_null() && rjson::v3::read_bool(fallback["show"sv], true))
                    make_circle(chart_draw().serum_circle(serum_index, rjson::v3::read_number(fallback["radius"sv], Scaled{3.0})), serum_passage, fallback);
            }

            // mark antigen
            if (const auto& antigen_style = getenv("mark_antigen"sv); mark_antigen.has_value() && !antigen_style.is_null()) {
                const auto style = style_from(antigen_style);
                chart_draw().modify(*mark_antigen, style.style, drawing_order_from(antigen_style));
                acmacs::chart::PointIndexList indexes;
                indexes.insert(*mark_antigen);
                color_according_to_passage(*chart_draw().chart().antigens(), indexes, style);
                if (const auto& label = antigen_style["label"sv]; !label.is_null())
                    add_labels(indexes, 0, label);
            }

            // !!! sector markers (radius lines) in make_circle !!!

            // mark serum
        }
        else
            fmt::format_to(report, "{:{}c}  *** no homologous antigens selected (selector: {})\n", ' ', indent, antigen_selector);
        fmt::format_to(report, "\n");
    }
    if (rjson::v3::read_bool(getenv("report"sv), false))
        AD_INFO("Serum circles for {} sera\n{}", serum_indexes.size(), fmt::to_string(report));

    return true;

} // acmacs::mapi::v1::Settings::apply_serum_circles

// ----------------------------------------------------------------------

acmacs::chart::PointIndexList acmacs::mapi::v1::Settings::select_antigens_for_serum_circle(size_t serum_index, const rjson::v3::value& antigen_selector)
{
    acmacs::chart::PointIndexList antigen_indexes;
    auto& chart = chart_draw().chart();
    auto serum = chart.sera()->at(serum_index);
    if (!antigen_selector.is_null()) {
        antigen_indexes = select_antigens(antigen_selector);
        acmacs::map_draw::select::filter::name_in(*chart.antigens(), antigen_indexes, serum->name());
    }
    else {
        chart.set_homologous(acmacs::chart::find_homologous::all);
        antigen_indexes = serum->homologous_antigens();
    }
    return antigen_indexes;

} // acmacs::mapi::v1::Settings::select_antigens_for_serum_circle

// ----------------------------------------------------------------------

static void report_circles(fmt::memory_buffer& report, const acmacs::chart::Antigens& antigens, const acmacs::chart::PointIndexList& antigen_indexes, const acmacs::chart::SerumCircle& empirical,
                           const acmacs::chart::SerumCircle& theoretical)
{
    const auto find_data = [](const acmacs::chart::SerumCircle& data, size_t antigen_index) -> const acmacs::chart::detail::SerumCirclePerAntigen& {
        if (const auto found = find_if(std::begin(data.per_antigen()), std::end(data.per_antigen()), [antigen_index](const auto& en) { return en.antigen_no == antigen_index; });
            found != std::end(data.per_antigen()))
            return *found;
        else
            throw std::runtime_error{"internal error in report_circles..find_data"};
    };

    fmt::format_to(report, "     empir   theor   titer\n");
    for (const auto antigen_index : antigen_indexes) {
        const auto& empirical_data = find_data(empirical, antigen_index);
        const auto& theoretical_data = find_data(theoretical, antigen_index);
        std::string empirical_radius(6, ' '), theoretical_radius(6, ' '), empirical_report, theoretical_report;
        if (empirical_data.valid())
            empirical_radius = fmt::format("{:.4f}", *empirical_data.radius);
        else
            empirical_report.assign(empirical_data.report_reason());
        if (theoretical_data.valid())
            theoretical_radius = fmt::format("{:.4f}", *theoretical_data.radius);
        else
            theoretical_report.assign(theoretical_data.report_reason());
        fmt::format_to(report, "    {}  {}  {:>6s}   AG {:4d} {:40s}", empirical_radius, theoretical_radius, theoretical_data.titer, antigen_index, antigens[antigen_index]->full_name(),
                       empirical_report);
        if (!empirical_report.empty())
            fmt::format_to(report, " -- {}", empirical_report);
        else if (!theoretical_report.empty())
            fmt::format_to(report, " -- {}", theoretical_report);
        fmt::format_to(report, "\n");
    }
    std::string empirical_radius(6, ' '), theoretical_radius(6, ' ');
    if (empirical.valid())
        empirical_radius = fmt::format("{:.4f}", empirical.radius());
    if (theoretical.valid())
        theoretical_radius = fmt::format("{:.4f}", theoretical.radius());
    fmt::format_to(report, "  > {}  {}\n", empirical_radius, theoretical_radius);
}

// ----------------------------------------------------------------------

void acmacs::mapi::v1::Settings::make_circle(map_elements::v1::SerumCircle& circle, std::string_view serum_passage, const rjson::v3::value& plot)
{
    using namespace std::string_view_literals;

    if (rjson::v3::read_bool(plot["show"sv], true)) {
        if (const auto outline_dash = rjson::v3::read_string(plot["outline_dash"sv], ""sv); outline_dash == "dash1"sv)
            circle.outline_dash1();
        else if (outline_dash == "dash2"sv)
            circle.outline_dash2();
        else if (outline_dash == "dash3"sv)
            circle.outline_dash3();
        else
            circle.outline_no_dash();

        const auto get_color = [serum_passage, this](const rjson::v3::value& source, Color dflt) -> Color {
            return std::visit(
                [serum_passage, dflt]<typename Val>(const Val& value) -> Color {
                    std::optional<acmacs::color::Modifier> color;
                    if constexpr (std::is_same_v<Val, acmacs::color::Modifier>)
                        color = value;
                    else if (serum_passage == "egg"sv) // Val = passage_color_t
                        color = value.egg;
                    else if (serum_passage == "cell"sv)
                        color = value.cell;
                    else if (serum_passage == "reassortant"sv)
                        color = value.reassortant;
                    if (color.has_value())
                        return *color;
                    else
                        return dflt;
                },
                color(source, dflt));
        };

        const auto outline{get_color(plot["outline"sv], PINK)};
        const auto outline_width{rjson::v3::read_number(plot["outline_width"sv], Pixels{1.0})};
        circle.outline(outline, outline_width);
        circle.fill(get_color(plot["fill"sv], TRANSPARENT));

        if (const auto& angles = plot["angles"sv]; !angles.is_null())
            circle.angles(rjson::v3::read_number<Rotation>(angles[0], Rotation{0}), rjson::v3::read_number<Rotation>(angles[1], Rotation{0}));
        if (const auto& radius_line = plot["radius_line"sv]; !radius_line.is_null()) {
            if (const auto dash = rjson::v3::read_string(radius_line["dash"sv], ""sv); dash == "dash1"sv)
                circle.radius_line_dash1();
            else if (dash == "dash2"sv)
                circle.radius_line_dash2();
            else if (dash == "dash3"sv)
                circle.radius_line_dash3();
            else
                circle.radius_line_no_dash();
            circle.radius_line(get_color(radius_line["color"sv], outline), rjson::v3::read_number(radius_line["line_width"sv], outline_width));
        }

        if (!plot["angle_degrees"sv].is_null() || !plot["radius_line_dash"sv].is_null() || !plot["radius_line_color"sv].is_null())
            AD_WARNING("\"angle_degrees\", \"radius_line_dash\", \"radius_line_color\" are deprecated and not supported");
    }

} // acmacs::mapi::v1::Settings::make_circle

// ----------------------------------------------------------------------
/// Local Variables:
/// eval: (if (fboundp 'eu-rename-buffer) (eu-rename-buffer))
/// End:
<commit_msg>mapi: serum circles<commit_after>#include "acmacs-base/rjson-v3-helper.hh"
#include "acmacs-chart-2/serum-circle.hh"
#include "acmacs-map-draw/mapi-settings.hh"
#include "acmacs-map-draw/select-filter.hh"
#include "acmacs-map-draw/draw.hh"

// ----------------------------------------------------------------------

static void report_circles(fmt::memory_buffer& report, const acmacs::chart::Antigens& antigens, const acmacs::chart::PointIndexList& antigen_indexes, const acmacs::chart::SerumCircle& empirical,
                           const acmacs::chart::SerumCircle& theoretical);

// ----------------------------------------------------------------------

//  "?homologous_titer": "1280",

bool acmacs::mapi::v1::Settings::apply_serum_circles()
{
    using namespace std::string_view_literals;

    auto& chart = chart_draw().chart();
    auto antigens = chart.antigens();
    auto sera = chart.sera();
    auto titers = chart.titers();
    auto layout = chart_draw().layout();

    const auto serum_indexes = select_sera(getenv("serum"sv, "sera"sv));
    const auto fold = rjson::v3::read_number(getenv("fold"sv), 2.0);
    const auto forced_homologous_titer = rjson::v3::read_string(getenv("homologous_titer"sv));
    const auto verb = verbose_from(rjson::v3::read_bool(getenv("verbose"sv), false));
    const auto& antigen_selector{getenv("antigen"sv, "antigens"sv)};
    fmt::memory_buffer report;
    const size_t indent{2};
    for (auto serum_index : serum_indexes) {
        auto serum = sera->at(serum_index);
        fmt::format_to(report, "{:{}c}SR {} {} {}\n", ' ', indent, serum_index, serum->full_name(), serum->passage_type(acmacs::chart::reassortant_as_egg::no));
        if (!layout->point_has_coordinates(serum_index + antigens->size())) {
            fmt::format_to(report, "{:{}c}  *** serum is disconnected\n", ' ', indent);
        }
        else if (const auto antigen_indexes = select_antigens_for_serum_circle(serum_index, antigen_selector); !antigen_indexes.empty()) {
            const auto column_basis = chart.column_basis(serum_index, chart_draw().projection_no());
            acmacs::chart::SerumCircle empirical, theoretical;
            if (forced_homologous_titer.has_value()) {
                empirical = acmacs::chart::serum_circle_empirical(antigen_indexes, *forced_homologous_titer, serum_index, *layout, column_basis, *titers, fold, verb);
                theoretical = acmacs::chart::serum_circle_theoretical(*forced_homologous_titer, serum_index, column_basis, fold);
            }
            else {
                empirical = acmacs::chart::serum_circle_empirical(antigen_indexes, serum_index, *layout, column_basis, *titers, fold, verb);
                theoretical = acmacs::chart::serum_circle_theoretical(antigen_indexes, serum_index, column_basis, *titers, fold);
            }
            report_circles(report, *antigens, antigen_indexes, empirical, theoretical);

            const auto serum_passage = serum->passage_type(acmacs::chart::reassortant_as_egg::no);
            std::optional<size_t> mark_antigen;
            bool mark_serum{false};
            if (empirical.valid()) {
                make_circle(chart_draw().serum_circle(serum_index, Scaled{empirical.radius()}), serum_passage, getenv("empirical"sv));
                mark_antigen = empirical.per_antigen().front().antigen_no;
                mark_serum = true;
            }
            if (theoretical.valid()) {
                make_circle(chart_draw().serum_circle(serum_index, Scaled{theoretical.radius()}), serum_passage, getenv("theoretical"sv));
                if (!mark_antigen.has_value())
                    mark_antigen = theoretical.per_antigen().front().antigen_no;
                mark_serum = true;
            }
            if (!empirical.valid() && !theoretical.valid()) {
                if (const auto& fallback = getenv("fallback"sv); !fallback.is_null() && rjson::v3::read_bool(fallback["show"sv], true)) {
                    make_circle(chart_draw().serum_circle(serum_index, rjson::v3::read_number(fallback["radius"sv], Scaled{3.0})), serum_passage, fallback);
                    mark_serum = true;
                }
            }

            // mark antigen
            if (const auto& antigen_style = getenv("mark_antigen"sv); mark_antigen.has_value() && !antigen_style.is_null()) {
                const auto style = style_from(antigen_style);
                chart_draw().modify(*mark_antigen, style.style, drawing_order_from(antigen_style));
                const acmacs::chart::PointIndexList indexes{*mark_antigen};
                color_according_to_passage(*antigens, indexes, style);
                if (const auto& label = antigen_style["label"sv]; !label.is_null())
                    add_labels(indexes, 0, label);
            }

            // mark serum
            if (const auto& serum_style = getenv("mark_serum"sv); mark_serum && !serum_style.is_null()) {
                const auto style = style_from(serum_style);
                chart_draw().modify(serum_index, style.style, drawing_order_from(serum_style));
                color_according_to_passage(*sera, acmacs::chart::PointIndexList{serum_index}, style);
                if (const auto& label = serum_style["label"sv]; !label.is_null())
                    add_labels(acmacs::chart::PointIndexList{serum_index + antigens->size()}, 0, label);
            }
        }
        else
            fmt::format_to(report, "{:{}c}  *** no homologous antigens selected (selector: {})\n", ' ', indent, antigen_selector);
        fmt::format_to(report, "\n");
    }
    if (rjson::v3::read_bool(getenv("report"sv), false))
        AD_INFO("Serum circles for {} sera\n{}", serum_indexes.size(), fmt::to_string(report));

    return true;

} // acmacs::mapi::v1::Settings::apply_serum_circles

// ----------------------------------------------------------------------

acmacs::chart::PointIndexList acmacs::mapi::v1::Settings::select_antigens_for_serum_circle(size_t serum_index, const rjson::v3::value& antigen_selector)
{
    acmacs::chart::PointIndexList antigen_indexes;
    auto& chart = chart_draw().chart();
    auto serum = chart.sera()->at(serum_index);
    if (!antigen_selector.is_null()) {
        antigen_indexes = select_antigens(antigen_selector);
        acmacs::map_draw::select::filter::name_in(*chart.antigens(), antigen_indexes, serum->name());
    }
    else {
        chart.set_homologous(acmacs::chart::find_homologous::all);
        antigen_indexes = serum->homologous_antigens();
    }
    return antigen_indexes;

} // acmacs::mapi::v1::Settings::select_antigens_for_serum_circle

// ----------------------------------------------------------------------

static void report_circles(fmt::memory_buffer& report, const acmacs::chart::Antigens& antigens, const acmacs::chart::PointIndexList& antigen_indexes, const acmacs::chart::SerumCircle& empirical,
                           const acmacs::chart::SerumCircle& theoretical)
{
    const auto find_data = [](const acmacs::chart::SerumCircle& data, size_t antigen_index) -> const acmacs::chart::detail::SerumCirclePerAntigen& {
        if (const auto found = find_if(std::begin(data.per_antigen()), std::end(data.per_antigen()), [antigen_index](const auto& en) { return en.antigen_no == antigen_index; });
            found != std::end(data.per_antigen()))
            return *found;
        else
            throw std::runtime_error{"internal error in report_circles..find_data"};
    };

    fmt::format_to(report, "     empir   theor   titer\n");
    for (const auto antigen_index : antigen_indexes) {
        const auto& empirical_data = find_data(empirical, antigen_index);
        const auto& theoretical_data = find_data(theoretical, antigen_index);
        std::string empirical_radius(6, ' '), theoretical_radius(6, ' '), empirical_report, theoretical_report;
        if (empirical_data.valid())
            empirical_radius = fmt::format("{:.4f}", *empirical_data.radius);
        else
            empirical_report.assign(empirical_data.report_reason());
        if (theoretical_data.valid())
            theoretical_radius = fmt::format("{:.4f}", *theoretical_data.radius);
        else
            theoretical_report.assign(theoretical_data.report_reason());
        fmt::format_to(report, "    {}  {}  {:>6s}   AG {:4d} {:40s}", empirical_radius, theoretical_radius, theoretical_data.titer, antigen_index, antigens[antigen_index]->full_name(),
                       empirical_report);
        if (!empirical_report.empty())
            fmt::format_to(report, " -- {}", empirical_report);
        else if (!theoretical_report.empty())
            fmt::format_to(report, " -- {}", theoretical_report);
        fmt::format_to(report, "\n");
    }
    std::string empirical_radius(6, ' '), theoretical_radius(6, ' ');
    if (empirical.valid())
        empirical_radius = fmt::format("{:.4f}", empirical.radius());
    if (theoretical.valid())
        theoretical_radius = fmt::format("{:.4f}", theoretical.radius());
    fmt::format_to(report, "  > {}  {}\n", empirical_radius, theoretical_radius);
}

// ----------------------------------------------------------------------

void acmacs::mapi::v1::Settings::make_circle(map_elements::v1::SerumCircle& circle, std::string_view serum_passage, const rjson::v3::value& plot)
{
    using namespace std::string_view_literals;

    if (rjson::v3::read_bool(plot["show"sv], true)) {
        if (const auto outline_dash = rjson::v3::read_string(plot["outline_dash"sv], ""sv); outline_dash == "dash1"sv)
            circle.outline_dash1();
        else if (outline_dash == "dash2"sv)
            circle.outline_dash2();
        else if (outline_dash == "dash3"sv)
            circle.outline_dash3();
        else
            circle.outline_no_dash();

        const auto get_color = [serum_passage, this](const rjson::v3::value& source, Color dflt) -> Color {
            return std::visit(
                [serum_passage, dflt]<typename Val>(const Val& value) -> Color {
                    std::optional<acmacs::color::Modifier> color;
                    if constexpr (std::is_same_v<Val, acmacs::color::Modifier>)
                        color = value;
                    else if (serum_passage == "egg"sv) // Val = passage_color_t
                        color = value.egg;
                    else if (serum_passage == "cell"sv)
                        color = value.cell;
                    else if (serum_passage == "reassortant"sv)
                        color = value.reassortant;
                    if (color.has_value())
                        return *color;
                    else
                        return dflt;
                },
                color(source, dflt));
        };

        const auto outline{get_color(plot["outline"sv], PINK)};
        const auto outline_width{rjson::v3::read_number(plot["outline_width"sv], Pixels{1.0})};
        circle.outline(outline, outline_width);
        circle.fill(get_color(plot["fill"sv], TRANSPARENT));

        if (const auto& angles = plot["angles"sv]; !angles.is_null())
            circle.angles(rjson::v3::read_number<Rotation>(angles[0], Rotation{0}), rjson::v3::read_number<Rotation>(angles[1], Rotation{0}));
        if (const auto& radius_line = plot["radius_line"sv]; !radius_line.is_null()) {
            if (const auto dash = rjson::v3::read_string(radius_line["dash"sv], ""sv); dash == "dash1"sv)
                circle.radius_line_dash1();
            else if (dash == "dash2"sv)
                circle.radius_line_dash2();
            else if (dash == "dash3"sv)
                circle.radius_line_dash3();
            else
                circle.radius_line_no_dash();
            circle.radius_line(get_color(radius_line["color"sv], outline), rjson::v3::read_number(radius_line["line_width"sv], outline_width));
        }

        if (!plot["angle_degrees"sv].is_null() || !plot["radius_line_dash"sv].is_null() || !plot["radius_line_color"sv].is_null())
            AD_WARNING("\"angle_degrees\", \"radius_line_dash\", \"radius_line_color\" are deprecated and not supported");
    }

} // acmacs::mapi::v1::Settings::make_circle

// ----------------------------------------------------------------------
/// Local Variables:
/// eval: (if (fboundp 'eu-rename-buffer) (eu-rename-buffer))
/// End:
<|endoftext|>


<commit_before><commit_msg>Replaced `#include` line with a forward declaration.<commit_after><|endoftext|>


<commit_before>// OS-specific networking includes
// -------------------------------
#ifdef __WIN32
    #include <winsock2.h>
    typedef int socklen_t;
#else
    extern "C" {
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
    #include <netdb.h>
    #include <fcntl.h>
    #include <stdlib.h>
    #include <string.h>
    }

    typedef int SOCKET;
    typedef sockaddr_in SOCKADDR_IN;
    typedef sockaddr SOCKADDR;
    #define SOCKET_ERROR -1
#endif

// Socket used for all communications
SOCKET sock;

// Address of the remote server
SOCKADDR_IN addr;

// Buffer used to return dynamic strings to the caller
#define BUFFER_SIZE 1024
char return_buffer[BUFFER_SIZE];

// exposed functions
// ------------------------------

const char* SUCCESS = "1\0"; // string representing success

#ifdef __WIN32
    #define DLL_EXPORT __declspec(dllexport)
#else
    #define DLL_EXPORT __attribute__ ((visibility ("default")))
#endif

// arg1: ip(in the xx.xx.xx.xx format)
// arg2: port(a short)
// return: NULL on failure, SUCCESS otherwise
extern "C" DLL_EXPORT const char* establish_connection(int n, char *v[])
{
    // extract args
    // ------------
    if(n < 2) return 0;
    const char* ip = v[0];
    const char* port_s = v[1];
    unsigned short port = atoi(port_s);

    // set up network stuff
    // --------------------
    #ifdef __WIN32
        WSADATA wsa;
        WSAStartup(MAKEWORD(2,0),&wsa);
    #endif
    sock = socket(AF_INET,SOCK_DGRAM,0);

    // make the socket non-blocking
    // ----------------------------
    #ifdef __WIN32
        unsigned long iMode=1;
        ioctlsocket(sock,FIONBIO,&iMode);
    #else
        fcntl(sock, F_SETFL, O_NONBLOCK);
    #endif

    // establish a connection to the server
    // ------------------------------------
    memset(&addr,0,sizeof(SOCKADDR_IN));
    addr.sin_family=AF_INET;
    addr.sin_port=htons(port);

    // convert the string representation of the ip to a byte representation
    addr.sin_addr.s_addr=inet_addr(ip);

    return SUCCESS;
}

// arg1: string message to send
// return: NULL on failure, SUCCESS otherwise
extern "C" DLL_EXPORT const char* send_message(int n, char *v[])
{
    // extract the args
    if(n < 1) return 0;
    const char* msg = v[0];

    // send the message
    int rc = sendto(sock,msg,strlen(msg),0,(SOCKADDR*)&addr,sizeof(SOCKADDR));

    // check for errors
    if (rc != -1) {
       return SUCCESS;
    }
    else {
       return 0;
    }
}

// no args
// return: message if any received, NULL otherwise
extern "C" DLL_EXPORT const char* recv_message(int n, char *v[])
{
    SOCKADDR_IN sender; // we will store the sender address here

    socklen_t sender_byte_length = sizeof(sender);

    // Try receiving messages until we receive one that's valid, or there are no more messages
    while(1) {
        int rc = recvfrom(sock, return_buffer, BUFFER_SIZE,0,(SOCKADDR*) &sender,&sender_byte_length);
        if(rc > 0) {
            // we could read something

            if(sender.sin_addr.s_addr != addr.sin_addr.s_addr) {
                continue; // not our connection, ignore and try again
            } else {
                return_buffer[rc] = '0'; // 0-terminate the string
                return return_buffer;
            }
        }
        else {
            break; // no more messages, stop trying to receive
        }
    }

    return 0;
}
<commit_msg>Fixed a derp in DLLSocket.dll<commit_after>// OS-specific networking includes
// -------------------------------
#ifdef __WIN32
    #include <winsock2.h>
    typedef int socklen_t;
#else
    extern "C" {
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
    #include <netdb.h>
    #include <fcntl.h>
    #include <stdlib.h>
    #include <string.h>
    }

    typedef int SOCKET;
    typedef sockaddr_in SOCKADDR_IN;
    typedef sockaddr SOCKADDR;
    #define SOCKET_ERROR -1
#endif

// Socket used for all communications
SOCKET sock;

// Address of the remote server
SOCKADDR_IN addr;

// Buffer used to return dynamic strings to the caller
#define BUFFER_SIZE 1024
char return_buffer[BUFFER_SIZE];

// exposed functions
// ------------------------------

const char* SUCCESS = "1\0"; // string representing success

#ifdef __WIN32
    #define DLL_EXPORT __declspec(dllexport)
#else
    #define DLL_EXPORT __attribute__ ((visibility ("default")))
#endif

// arg1: ip(in the xx.xx.xx.xx format)
// arg2: port(a short)
// return: NULL on failure, SUCCESS otherwise
extern "C" DLL_EXPORT const char* establish_connection(int n, char *v[])
{
    // extract args
    // ------------
    if(n < 2) return 0;
    const char* ip = v[0];
    const char* port_s = v[1];
    unsigned short port = atoi(port_s);

    // set up network stuff
    // --------------------
    #ifdef __WIN32
        WSADATA wsa;
        WSAStartup(MAKEWORD(2,0),&wsa);
    #endif
    sock = socket(AF_INET,SOCK_DGRAM,0);

    // make the socket non-blocking
    // ----------------------------
    #ifdef __WIN32
        unsigned long iMode=1;
        ioctlsocket(sock,FIONBIO,&iMode);
    #else
        fcntl(sock, F_SETFL, O_NONBLOCK);
    #endif

    // establish a connection to the server
    // ------------------------------------
    memset(&addr,0,sizeof(SOCKADDR_IN));
    addr.sin_family=AF_INET;
    addr.sin_port=htons(port);

    // convert the string representation of the ip to a byte representation
    addr.sin_addr.s_addr=inet_addr(ip);

    return SUCCESS;
}

// arg1: string message to send
// return: NULL on failure, SUCCESS otherwise
extern "C" DLL_EXPORT const char* send_message(int n, char *v[])
{
    // extract the args
    if(n < 1) return 0;
    const char* msg = v[0];

    // send the message
    int rc = sendto(sock,msg,strlen(msg),0,(SOCKADDR*)&addr,sizeof(SOCKADDR));

    // check for errors
    if (rc != -1) {
       return SUCCESS;
    }
    else {
       return 0;
    }
}

// no args
// return: message if any received, NULL otherwise
extern "C" DLL_EXPORT const char* recv_message(int n, char *v[])
{
    SOCKADDR_IN sender; // we will store the sender address here

    socklen_t sender_byte_length = sizeof(sender);

    // Try receiving messages until we receive one that's valid, or there are no more messages
    while(1) {
        int rc = recvfrom(sock, return_buffer, BUFFER_SIZE,0,(SOCKADDR*) &sender,&sender_byte_length);
        if(rc > 0) {
            // we could read something

            if(sender.sin_addr.s_addr != addr.sin_addr.s_addr) {
                continue; // not our connection, ignore and try again
            } else {
                return_buffer[rc] = 0; // 0-terminate the string
                return return_buffer;
            }
        }
        else {
            break; // no more messages, stop trying to receive
        }
    }

    return 0;
}
<|endoftext|>