/***************************************************************************
 *   Copyright (c) 2015 Eivind Kvedalen <eivind@kvedalen.name>             *
 *                                                                         *
 *   This file is part of the FreeCAD CAx development system.              *
 *                                                                         *
 *   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., 59 Temple Place,         *
 *   Suite 330, Boston, MA  02111-1307, USA                                *
 *                                                                         *
 ***************************************************************************/

#include "PreCompiled.h"

#ifndef _PreComp_
#	include <cassert>
#endif

#include <limits>
#include <iomanip>

#include <boost/algorithm/string/predicate.hpp>

/// Here the FreeCAD includes sorted by Base,App,Gui......
#include <Base/GeometryPyCXX.h>
#include <App/ComplexGeoData.h>
#include "Property.h"
#include "Application.h"
#include "Document.h"
#include "DocumentObject.h"
#include "ObjectIdentifier.h"
#include "ExpressionParser.h"
#include <Base/Tools.h>
#include <Base/Interpreter.h>
#include <Base/QuantityPy.h>
#include <App/Link.h>
#include <Base/Console.h>

FC_LOG_LEVEL_INIT("Expression",true,true)

using namespace App;
using namespace Base;

// Path class

/**
 * @brief Quote input string according to quoting rules for an expression: because " and ' are
 * used to designate inch and foot units, strings are quoted as <<string>>.
 *
 * @param input
 * @return
 */

std::string App::quote(const std::string &input, bool toPython)
{
    std::stringstream output;

    std::string::const_iterator cur = input.begin();
    std::string::const_iterator end = input.end();

    output << (toPython?"'":"<<");
    while (cur != end) {
        switch (*cur) {
        case '\t':
            output << "\\t";
            break;
        case '\n':
            output << "\\n";
            break;
        case '\r':
            output << "\\r";
            break;
        case '\\':
            output << "\\\\";
            break;
        case '\'':
            output << "\\'";
            break;
        case '"':
            output << "\\\"";
            break;
        case '>':
            output << (toPython?">":"\\>");
            break;
        default:
            output << *cur;
        }
        ++cur;
    }
    output << (toPython?"'":">>");

    return output.str();
}


/**
 * @brief Construct an ObjectIdentifier object, given an owner and a single-value property.
 * @param _owner Owner of property.
 * @param property Name of property.
 */

ObjectIdentifier::ObjectIdentifier(const App::PropertyContainer * _owner,
        const std::string & property, int index)
    : owner(0)
    , documentNameSet(false)
    , documentObjectNameSet(false)
    , localProperty(false)
    , _hash(0)
{
    if (_owner) {
        const DocumentObject * docObj = freecad_dynamic_cast<const DocumentObject>(_owner);
        if (!docObj)
            FC_THROWM(Base::RuntimeError,"Property must be owned by a document object.");
        owner = const_cast<DocumentObject*>(docObj);

        if (property.size() > 0) {
            setDocumentObjectName(docObj);
        }
    }
    if (property.size() > 0) {
        addComponent(SimpleComponent(property));
        if(index!=INT_MAX)
            addComponent(ArrayComponent(index));
    }
}

ObjectIdentifier::ObjectIdentifier(const App::PropertyContainer * _owner, bool localProperty)
    : owner(0)
    , documentNameSet(false)
    , documentObjectNameSet(false)
    , localProperty(localProperty)
    , _hash(0)
{
    if (_owner) {
        const DocumentObject * docObj = freecad_dynamic_cast<const DocumentObject>(_owner);
        if (!docObj)
            FC_THROWM(Base::RuntimeError,"Property must be owned by a document object.");
        owner = const_cast<DocumentObject*>(docObj);
    }
}

/**
 * @brief Construct an ObjectIdentifier object given a property. The property is assumed to be single-valued.
 * @param prop Property to construct object identifier for.
 */

ObjectIdentifier::ObjectIdentifier(const Property &prop, int index)
    : owner(0)
    , documentNameSet(false)
    , documentObjectNameSet(false)
    , localProperty(false)
    , _hash(0)
{
    DocumentObject * docObj = freecad_dynamic_cast<DocumentObject>(prop.getContainer());

    if (!docObj)
        FC_THROWM(Base::TypeError,"Property must be owned by a document object.");

    owner = const_cast<DocumentObject*>(docObj);

    setDocumentObjectName(docObj);

    addComponent(SimpleComponent(String(prop.getName())));
    if(index!=INT_MAX)
        addComponent(ArrayComponent(index));
}

/**
 * @brief Get the name of the property.
 * @return Name
 */

std::string App::ObjectIdentifier::getPropertyName() const
{
    ResolveResults result(*this);

    assert(result.propertyIndex >=0 && static_cast<std::size_t>(result.propertyIndex) < components.size());

    return components[result.propertyIndex].getName();
}

/**
 * @brief Get Component at given index \a i.
 * @param i: Index to get
 * @param idx: optional return of adjusted component index
 * @return A component.
 */

const App::ObjectIdentifier::Component &App::ObjectIdentifier::getPropertyComponent(int i, int *idx) const
{
    ResolveResults result(*this);

    i += result.propertyIndex;
    if (i < 0 || i >= static_cast<int>(components.size()))
        FC_THROWM(Base::ValueError, "Invalid property component index");

    if (idx)
        *idx = i;

    return components[i];
}

void App::ObjectIdentifier::setComponent(int idx, Component &&comp)
{
    if (idx < 0 || idx >= static_cast<int>(components.size()))
        FC_THROWM(Base::ValueError, "Invalid component index");
    components[idx] = std::move(comp);
    _cache.clear();
}

void App::ObjectIdentifier::setComponent(int idx, const Component &comp)
{
    setComponent(idx, Component(comp));
}

std::vector<ObjectIdentifier::Component> ObjectIdentifier::getPropertyComponents() const {
    if(components.size()<=1 || documentObjectName.getString().empty())
        return components;
    ResolveResults result(*this);
    if(result.propertyIndex==0)
        return components;
    std::vector<ObjectIdentifier::Component> res;
    res.insert(res.end(),components.begin()+result.propertyIndex,components.end());
    return res;
}

/**
 * @brief Compare object identifier with \a other.
 * @param other Other object identifier.
 * @return true if they are equal.
 */

bool ObjectIdentifier::operator ==(const ObjectIdentifier &other) const
{
    return owner==other.owner && toString() == other.toString();
}

/**
 * @brief Compare object identifier with \a other.
 * @param other Other object identifier
 * @return true if they differ from each other.
 */

bool ObjectIdentifier::operator !=(const ObjectIdentifier &other) const
{
    return !(operator==)(other);
}

/**
 * @brief Compare object identifier with other.
 * @param other Other object identifier.
 * @return true if this object is less than the other.
 */

bool ObjectIdentifier::operator <(const ObjectIdentifier &other) const
{
    if(owner < other.owner)
        return true;
    if(owner > other.owner)
        return false;
    return toString() < other.toString();
}

/**
 * @brief Return number of components.
 * @return Number of components in this identifier.
 */

int ObjectIdentifier::numComponents() const
{
    return components.size();
}

/**
 * @brief Compute number of sub components, i.e excluding the property.
 * @return Number of components.
 */

int ObjectIdentifier::numSubComponents() const
{
    ResolveResults result(*this);

    return components.size() - result.propertyIndex;
}

bool ObjectIdentifier::verify(const App::Property &prop, bool silent) const {
    ResolveResults result(*this);
    if(components.size() - result.propertyIndex != 1) {
        if(silent) return false;
        FC_THROWM(Base::ValueError,"Invalid property path: single component expected");
    }
    if(!components[result.propertyIndex].isSimple()) {
        if(silent) return false;
        FC_THROWM(Base::ValueError,"Invalid property path: simple component expected");
    }
    const std::string &name = components[result.propertyIndex].getName();
    CellAddress addr;
    bool isAddress = addr.parseAbsoluteAddress(name.c_str());
    if((isAddress && addr.toString(true) != prop.getName()) ||
       (!isAddress && name!=prop.getName()))
    {
        if(silent) return false;
        FC_THROWM(Base::ValueError,"Invalid property path: name mismatch");
    }
    return true;
}

/**
 * @brief Create a string representation of this object identifier.
 *
 * An identifier is written as document#documentobject.property.subproperty1...subpropertyN
 * document# may be dropped; it is assumed to be within owner's document. If documentobject is dropped,
 * the property is assumed to be owned by the owner specified in the object identifiers constructor.
 *
 * @return A string
 */

const std::string &ObjectIdentifier::toString() const
{
    if(_cache.size() || !owner)
        return _cache;

    std::ostringstream s;
    ResolveResults result(*this);

    if(result.propertyIndex >= (int)components.size())
        return _cache;

    if(localProperty ||
       (result.resolvedProperty &&
        result.resolvedDocumentObject==owner &&
        components.size()>1 &&
        components[1].isSimple() &&
        result.propertyIndex==0))
    {
        s << '.';
    }else if (documentNameSet && documentName.getString().size()) {
        if(documentObjectNameSet && documentObjectName.getString().size())
            s << documentName.toString() << "#"
              << documentObjectName.toString() << '.';
        else if(result.resolvedDocumentObjectName.getString().size())
            s << documentName.toString() << "#"
              << result.resolvedDocumentObjectName.toString() << '.';
    } else if (documentObjectNameSet && documentObjectName.getString().size()) {
        s << documentObjectName.toString() << '.';
    } else if (result.propertyIndex > 0) {
        components[0].toString(s);
        s << '.';
    }

    if(subObjectName.getString().size())
        s << subObjectName.toString() << '.';

    s << components[result.propertyIndex].getName();
    getSubPathStr(s,result);
    const_cast<ObjectIdentifier*>(this)->_cache = s.str();
    return _cache;
}

std::string ObjectIdentifier::toPersistentString() const {

    if(!owner)
        return std::string();

    std::ostringstream s;
    ResolveResults result(*this);

    if(result.propertyIndex >= (int)components.size())
        return std::string();

    if(localProperty ||
       (result.resolvedProperty &&
        result.resolvedDocumentObject==owner &&
        components.size()>1 &&
        components[1].isSimple() &&
        result.propertyIndex==0))
    {
        s << '.';
    }else if(result.resolvedDocumentObject &&
        result.resolvedDocumentObject!=owner &&
        result.resolvedDocumentObject->isExporting())
    {
        s << result.resolvedDocumentObject->getExportName(true);
        if(documentObjectName.isRealString())
            s << '@';
        s << '.';
    } else if (documentNameSet && documentName.getString().size()) {
        if(documentObjectNameSet && documentObjectName.getString().size())
            s << documentName.toString() << "#"
                << documentObjectName.toString() << '.';
        else if(result.resolvedDocumentObjectName.getString().size())
            s << documentName.toString() << "#"
                << result.resolvedDocumentObjectName.toString() << '.';
    } else if (documentObjectNameSet && documentObjectName.getString().size()) {
        s << documentObjectName.toString() << '.';
    } else if (result.propertyIndex > 0) {
        components[0].toString(s);
        s << '.';
    }

    if(subObjectName.getString().size()) {
        const char *subname = subObjectName.getString().c_str();
        std::string exportName;
        s << String(PropertyLinkBase::exportSubName(exportName,
                        result.resolvedDocumentObject,subname),true).toString() << '.';
    }

    s << components[result.propertyIndex].getName();
    getSubPathStr(s,result);
    return s.str();
}

std::size_t ObjectIdentifier::hash() const
{
    if(_hash && _cache.size())
        return _hash;
    const_cast<ObjectIdentifier*>(this)->_hash = boost::hash_value(toString());
    return _hash;
}

bool ObjectIdentifier::replaceObject(ObjectIdentifier &res, const App::DocumentObject *parent,
            App::DocumentObject *oldObj, App::DocumentObject *newObj) const
{
    ResolveResults result(*this);

    if(!result.resolvedDocumentObject)
        return false;

    auto r = PropertyLinkBase::tryReplaceLink(owner, result.resolvedDocumentObject,
            parent, oldObj, newObj, subObjectName.getString().c_str());

    if(!r.first)
        return false;

    res = *this;
    if(r.first != result.resolvedDocumentObject) {
        if(r.first->getDocument()!=owner->getDocument()) {
            auto doc = r.first->getDocument();
            bool useLabel = res.documentName.isRealString();
            const char *name = useLabel?doc->Label.getValue():doc->getName();
            res.setDocumentName(String(name, useLabel), true);
        }
        if(documentObjectName.isRealString())
            res.documentObjectName = String(r.first->Label.getValue(),true);
        else
            res.documentObjectName = String(r.first->getNameInDocument(),false,true);
    }
    res.subObjectName = String(r.second,true);
    res._cache.clear();
    res.shadowSub.first.clear();
    res.shadowSub.second.clear();
    return true;
}

/**
 * @brief Escape toString representation so it is suitable for being embedded in a python command.
 * @return Escaped string.
 */

std::string ObjectIdentifier::toEscapedString() const
{
    return Base::Tools::escapedUnicodeFromUtf8(toString().c_str());
}

bool ObjectIdentifier::updateLabelReference(
        App::DocumentObject *obj, const std::string &ref, const char *newLabel)
{
    if(!owner)
        return false;

    ResolveResults result(*this);

    if(subObjectName.getString().size() && result.resolvedDocumentObject) {
        std::string sub = PropertyLinkBase::updateLabelReference(
                result.resolvedDocumentObject, subObjectName.getString().c_str(), obj,ref,newLabel);
        if(sub.size()) {
            subObjectName = String(sub,true);
            _cache.clear();
            return true;
        }
    }

    if(result.resolvedDocument != obj->getDocument())
        return false;

    if(documentObjectName.getString().size()) {
        if(documentObjectName.isForceIdentifier())
            return false;

        if(!documentObjectName.isRealString() &&
           documentObjectName.getString()==obj->getNameInDocument())
            return false;

        if(documentObjectName.getString()!=obj->Label.getValue())
            return false;

        documentObjectName = ObjectIdentifier::String(newLabel, true);

        _cache.clear();
        return true;
    }

    if (result.resolvedDocumentObject==obj &&
        result.propertyIndex == 1 &&
        result.resolvedDocumentObjectName.isRealString() &&
        result.resolvedDocumentObjectName.getString()==obj->Label.getValue())
    {
        components[0].name = ObjectIdentifier::String(newLabel, true);
        _cache.clear();
        return true;
    }

    // If object identifier uses the label then resolving the document object will fail.
    // So, it must be checked if using the new label will succeed
    if (components.size()>1 && components[0].getName()==obj->Label.getValue()) {
        ObjectIdentifier id(*this);
        id.components[0].name.str = newLabel;

        ResolveResults result(id);

        if (result.propertyIndex == 1 && result.resolvedDocumentObject == obj) {
            components[0].name = id.components[0].name;
            _cache.clear();
            return true;
        }
    }

    return false;
}

bool ObjectIdentifier::relabeledDocument(ExpressionVisitor &v,
        const std::string &oldLabel, const std::string &newLabel)
{
    if (documentNameSet && documentName.isRealString() && documentName.getString()==oldLabel) {
        v.aboutToChange();
        documentName = String(newLabel,true);
        _cache.clear();
        return true;
    }
    return false;
}

/**
 * @brief Get sub field part of a property as a string.
 * @return String representation of path.
 */

void ObjectIdentifier::getSubPathStr(std::ostream &s, const ResolveResults &result, bool toPython) const
{
    std::vector<Component>::const_iterator i = components.begin() + result.propertyIndex + 1;
    while (i != components.end()) {
        if(i->isSimple())
            s << '.';
        i->toString(s,toPython);
        ++i;
    }
}

std::string ObjectIdentifier::getSubPathStr(bool toPython) const {
    std::ostringstream ss;
    getSubPathStr(ss,ResolveResults(*this),toPython);
    return ss.str();
}


/**
 * @brief Construct a Component part
 * @param _name Name of component
 * @param _type Type; simple, array, range or map
 * @param _begin Array index or beginning of a Range, or INT_MAX for other type.
 * @param _end ending of a Range, or INT_MAX for other type.
 */

ObjectIdentifier::Component::Component(const String &_name,
        ObjectIdentifier::Component::typeEnum _type, int _begin, int _end, int _step)
    : name(_name)
    , type(_type)
    , begin(_begin)
    , end(_end)
    , step(_step)
{
}

ObjectIdentifier::Component::Component(String &&_name,
        ObjectIdentifier::Component::typeEnum _type, int _begin, int _end, int _step)
    : name(std::move(_name))
    , type(_type)
    , begin(_begin)
    , end(_end)
    , step(_step)
{
}


size_t ObjectIdentifier::Component::getIndex(size_t count) const {
    if(begin>=0) {
        if(begin<(int)count)
            return begin;
    }else {
        int idx = begin + (int)count;
        if(idx >= 0)
            return idx;
    }
    FC_THROWM(Base::IndexError, "Array out of bound: " << begin << ", " << count);
}

Py::Object ObjectIdentifier::Component::get(const Py::Object &pyobj) const {
    Py::Object res;
    if(isSimple()) {
        if(!pyobj.hasAttr(getName()))
            FC_THROWM(Base::AttributeError, "No attribute named '" << getName() << "'");
        res = pyobj.getAttr(getName());
    } else if(isArray()) {
        if(pyobj.isMapping())
            res = Py::Mapping(pyobj).getItem(Py::Int(begin));
        else
            res = Py::Sequence(pyobj).getItem(begin);
    }else if(isMap())
        res = Py::Mapping(pyobj).getItem(getName());
    else {
        assert(isRange());
        Py::Object slice(PySlice_New(Py::Int(begin).ptr(),
                                    end!=INT_MAX?Py::Int(end).ptr():0,
                                    step!=1?Py::Int(step).ptr():0),true);
        PyObject *r = PyObject_GetItem(pyobj.ptr(),slice.ptr());
        if(!r)
            Base::PyException::ThrowException();
        res = Py::asObject(r);
    }
    if(!res.ptr())
        Base::PyException::ThrowException();
    if(PyModule_Check(res.ptr()) && !ExpressionParser::isModuleImported(res.ptr()))
        FC_THROWM(Base::RuntimeError, "Module '" << getName() << "' access denied.");
    return res;
}

void ObjectIdentifier::Component::set(Py::Object &pyobj, const Py::Object &value) const {
    if(isSimple()) {
        if(PyObject_SetAttrString(*pyobj, getName().c_str(), *value ) == -1)
            Base::PyException::ThrowException();
    } else if(isArray()) {
        if(pyobj.isMapping())
            Py::Mapping(pyobj).setItem(Py::Int(begin),value);
        else
            Py::Sequence(pyobj).setItem(begin,value);
    }else if(isMap())
        Py::Mapping(pyobj).setItem(getName(),value);
    else {
        assert(isRange());
        Py::Object slice(PySlice_New(Py::Int(begin).ptr(),
                                    end!=INT_MAX?Py::Int(end).ptr():0,
                                    step!=1?Py::Int(step).ptr():0),true);
        if(PyObject_SetItem(pyobj.ptr(),slice.ptr(),value.ptr())<0)
            Base::PyException::ThrowException();
    }
}

void ObjectIdentifier::Component::del(Py::Object &pyobj) const {
    if(isSimple())
        pyobj.delAttr(getName());
    else if(isArray()) {
        if(pyobj.isMapping())
            Py::Mapping(pyobj).delItem(Py::Int(begin));
        else
            PySequence_DelItem(pyobj.ptr(),begin);
    } else if(isMap())
        Py::Mapping(pyobj).delItem(getName());
    else {
        assert(isRange());
        Py::Object slice(PySlice_New(Py::Int(begin).ptr(),
                                    end!=INT_MAX?Py::Int(end).ptr():0,
                                    step!=1?Py::Int(step).ptr():0),true);
        if(PyObject_DelItem(pyobj.ptr(),slice.ptr())<0)
            Base::PyException::ThrowException();
    }
}

/**
 * @brief Create a simple component part with the given name
 * @param _component Name of component.
 * @return A new Component object.
 */

ObjectIdentifier::Component ObjectIdentifier::Component::SimpleComponent(const char *_component)
{
    return Component(String(_component));
}

/**
 * @brief Create a simple component part with the given name
 * @param _component Name of component.
 * @return A new Component object.
 */

ObjectIdentifier::Component ObjectIdentifier::Component::SimpleComponent(const ObjectIdentifier::String &_component)
{
    return Component(_component);
}

ObjectIdentifier::Component ObjectIdentifier::Component::SimpleComponent(ObjectIdentifier::String &&_component)
{
    return Component(std::move(_component));
}

/**
 * @brief Create an array component with given name and index.
 * @param _component Name of component
 * @param _index Index of component
 * @return A new Component object.
 */

ObjectIdentifier::Component ObjectIdentifier::Component::ArrayComponent(int _index)
{
    return Component(String(), Component::ARRAY, _index);
}

/**
 * @brief Create a map component with given name and key.
 * @param _component Name of component
 * @param _key Key of component
 * @return A new Component object.
 */

ObjectIdentifier::Component ObjectIdentifier::Component::MapComponent(const String & _key)
{
    return Component(_key, Component::MAP);
}

ObjectIdentifier::Component ObjectIdentifier::Component::MapComponent(String &&_key)
{
    return Component(std::move(_key), Component::MAP);
}


/**
 * @brief Create a range component with given begin and end.
 * @param _begin beginning index of the range
 * @param _end ending index of the range
 * @return A new Component object.
 */

ObjectIdentifier::Component ObjectIdentifier::Component::RangeComponent(int _begin, int _end, int _step)
{
    return Component(String(), Component::RANGE, _begin, _end, _step);
}

/**
 * @brief Comparison operator for Component objects.
 * @param other The object we want to compare to.
 * @return true if they are equal, false if not.
 */

bool ObjectIdentifier::Component::operator ==(const ObjectIdentifier::Component &other) const
{
    if (type != other.type)
        return false;

    switch (type) {
    case SIMPLE:
    case MAP:
        return name == other.name;
    case ARRAY:
        return begin == other.begin;
    case RANGE:
        return begin == other.begin && end == other.end && step==other.step;
    default:
        assert(0);
        return false;
    }
}

/**
 * @brief Create a string representation of a component.
 * @return A string representing the component.
 */

void ObjectIdentifier::Component::toString(std::ostream &ss, bool toPython) const
{
    switch (type) {
    case Component::SIMPLE:
        ss << name.getString();
        break;
    case Component::MAP:
        ss << "[" << name.toString(toPython) << "]";
        break;
    case Component::ARRAY:
        ss << "[" << begin << "]";
        break;
    case Component::RANGE:
        ss << '[';
        if(begin!=INT_MAX)
            ss << begin;
        ss << ':';
        if(end!=INT_MAX)
            ss << end;
        if(step!=1)
            ss << ':' << step;
        ss << ']';
        break;
    default:
        assert(0);
    }
}

enum ResolveFlags {
    ResolveByIdentifier,
    ResolveByLabel,
    ResolveAmbiguous,
};

/**
 * @brief Search for the document object given by name in doc.
 *
 * Name might be the internal name or a label. In any case, it must uniquely define
 * the document object.
 *
 * @param doc Document to search
 * @param name Name to search for.
 * @return Pointer to document object if a unique pointer is found, 0 otherwise.
 */

App::DocumentObject * ObjectIdentifier::getDocumentObject(const App::Document * doc,
        const String & name, std::bitset<32> &flags)
{
    DocumentObject * objectById = 0;
    DocumentObject * objectByLabel = 0;

    if(!name.isRealString()) {
        // No object found with matching label, try using name directly
        objectById = doc->getObject(static_cast<const char*>(name));

        if (objectById) {
            flags.set(ResolveByIdentifier);
            return objectById;
        }
        if(name.isForceIdentifier())
            return 0;
    }

    std::vector<DocumentObject*> docObjects = doc->getObjects();
    for (std::vector<DocumentObject*>::iterator j = docObjects.begin(); j != docObjects.end(); ++j) {
        if (strcmp((*j)->Label.getValue(), static_cast<const char*>(name)) == 0) {
            // Found object with matching label
            if (objectByLabel != 0)  {
                FC_WARN("duplicate object label " << doc->getName() << '#' << name);
                return 0;
            }
            objectByLabel = *j;
        }
    }

    if (objectByLabel == 0 && objectById == 0) // Not found at all
        return 0;
    else if (objectByLabel == 0) { // Found by name
        flags.set(ResolveByIdentifier);
        return objectById;
    }
    else if (objectById == 0) { // Found by label
        flags.set(ResolveByLabel);
        return objectByLabel;
    }
    else if (objectByLabel == objectById) { // Found by both name and label, same object
        flags.set(ResolveByIdentifier);
        flags.set(ResolveByLabel);
        return objectByLabel;
    }
    else {
        flags.set(ResolveAmbiguous);
        return 0; // Found by both name and label, two different objects
    }
}

/**
 * @brief Resolve the object identifier to a concrete document, documentobject, and property.
 *
 * This method is a helper method that fills out data in the given ResolveResults object.
 *
 */

void ObjectIdentifier::resolve(ResolveResults &results) const
{
    if(!owner)
        return;

    bool docAmbiguous = false;

    /* Document name specified? */
    if (documentName.getString().size() > 0) {
        results.resolvedDocument = getDocument(documentName,&docAmbiguous);
        results.resolvedDocumentName = documentName;
    }
    else {
        results.resolvedDocument = owner->getDocument();
        results.resolvedDocumentName = String(results.resolvedDocument->getName(), false, true);
    }

    results.subObjectName = subObjectName;
    results.propertyName = "";
    results.propertyIndex = 0;

    // Assume document name and object name from owner if not found
    if (results.resolvedDocument == 0) {
        if (documentName.getString().size() > 0) {
            if(docAmbiguous)
                results.flags.set(ResolveAmbiguous);
            return;
        }

        results.resolvedDocument = owner->getDocument();
        if (results.resolvedDocument == 0)
            return;
    }

    results.resolvedDocumentName = String(results.resolvedDocument->getName(), false, true);

    /* Document object name specified? */
    if (documentObjectName.getString().size() > 0) {
        results.resolvedDocumentObjectName = documentObjectName;
        results.resolvedDocumentObject = getDocumentObject(
                results.resolvedDocument, documentObjectName, results.flags);
        if (!results.resolvedDocumentObject)
            return;

        if (components.size() > 0) {
            results.propertyName = components[0].name.getString();
            results.propertyIndex = 0;
            results.getProperty(*this);
        }
        else
            return;
    }
    else {
        /* Document object name not specified, resolve from path */

        /* One component? */
        if (components.size() == 1 || (components.size()>1 && !components[0].isSimple())) {
            /* Yes -- then this must be a property, so we get the document object's name from the owner */
            results.resolvedDocumentObjectName = String(owner->getNameInDocument(), false, true);
            results.resolvedDocumentObject = owner;
            results.propertyName = components[0].name.getString();
            results.propertyIndex = 0;
            results.getProperty(*this);
        }
        else if (components.size() >= 2) {
            /* No --  */
            if (!components[0].isSimple())
                return;

            results.resolvedDocumentObject = getDocumentObject(
                    results.resolvedDocument, components[0].name, results.flags);

            /* Possible to resolve component to a document object? */
            if (results.resolvedDocumentObject) {
                /* Yes */
                results.resolvedDocumentObjectName = String(
                        components[0].name, false, results.flags.test(ResolveByIdentifier));
                results.propertyName = components[1].name.getString();
                results.propertyIndex = 1;
                results.getProperty(*this);
                if(!results.resolvedProperty) {
                    // If the second component is not a property name, try to
                    // interpret the first component as the property name.
                    DocumentObject *sobj = 0;
                    results.resolvedProperty = resolveProperty(
                            owner,components[0].name,sobj,results.propertyType);
                    if(results.resolvedProperty) {
                        results.propertyName = components[0].name.getString();
                        results.resolvedDocument = owner->getDocument();
                        results.resolvedDocumentName = String(results.resolvedDocument->getName(), false, true);
                        results.resolvedDocumentObjectName = String(owner->getNameInDocument(), false, true);
                        results.resolvedDocumentObject = owner;
                        results.resolvedSubObject = sobj;
                        results.propertyIndex = 0;
                    }
                }
            }
            else if (documentName.getString().empty()) {
                /* No, assume component is a property, and get document object's name from owner */
                results.resolvedDocument = owner->getDocument();
                results.resolvedDocumentName = String(results.resolvedDocument->getName(), false, true);
                results.resolvedDocumentObjectName = String(owner->getNameInDocument(), false, true);
                results.resolvedDocumentObject = owner->getDocument()->getObject(owner->getNameInDocument());
                results.propertyIndex = 0;
                results.propertyName = components[results.propertyIndex].name.getString();
                results.getProperty(*this);
            }
        }
        else
            return;
    }
}

/**
 * @brief Find a document with the given name.
 * @param name Name of document
 * @return Pointer to document, or 0 if it is not found or not uniquely defined by name.
 */

Document * ObjectIdentifier::getDocument(String name, bool *ambiguous) const
{
    if (name.getString().size() == 0)
        name = getDocumentName();

    App::Document * docById = 0;

    if(!name.isRealString()) {
        docById = App::GetApplication().getDocument(name);
        if (name.isForceIdentifier())
            return docById;
    }

    App::Document * docByLabel = 0;
    const std::vector<App::Document*> docs = App::GetApplication().getDocuments();

    for (std::vector<App::Document*>::const_iterator i = docs.begin(); i != docs.end(); ++i) {
        if ((*i)->Label.getValue() == name.getString()) {
            /* Multiple hits for same label? */
            if (docByLabel != 0) {
                if(ambiguous) *ambiguous = true;
                return 0;
            }
            docByLabel = *i;
        }
    }

    /* Not found on id? */
    if (docById == 0)
        return docByLabel; // Either not found at all, or on label
    else {
        /* Not found on label? */
        if (docByLabel == 0) /* Then return doc by id */
            return docById;

        /* docByLabel and docById could be equal; that is ok */
        if(docByLabel==docById)
            return docById;
        if(ambiguous) *ambiguous = true;
        return 0;
    }
}

/**
 * @brief Get the document object for the object identifier.
 * @return Pointer to document object, or 0 if not found or uniquely defined.
 */

DocumentObject *ObjectIdentifier::getDocumentObject() const
{
    const App::Document * doc = getDocument();
    std::bitset<32> dummy;

    if (!doc)
        return 0;

    ResolveResults result(*this);

    return getDocumentObject(doc, result.resolvedDocumentObjectName, dummy);
}


enum PseudoPropertyType {
    PseudoNone,
    PseudoShape,
    PseudoPlacement,
    PseudoMatrix,
    PseudoLinkPlacement,
    PseudoLinkMatrix,
    PseudoSelf,
    PseudoApp,
    PseudoPart,
    PseudoRegex,
    PseudoBuiltins,
    PseudoMath,
    PseudoCollections,
    PseudoGui,
    PseudoCadquery,
};

std::pair<DocumentObject*,std::string> ObjectIdentifier::getDep(std::vector<std::string> *labels) const {
    ResolveResults result(*this);
    if(labels) {
        if(documentObjectName.getString().size()) {
            if(documentObjectName.isRealString())
                labels->push_back(documentObjectName.getString());
        } else if(result.propertyIndex == 1)
            labels->push_back(components[0].name.getString());
        if(subObjectName.getString().size())
            PropertyLinkBase::getLabelReferences(*labels,subObjectName.getString().c_str());
    }
    if(subObjectName.getString().empty()) {
        if(result.propertyType==PseudoNone) {
            CellAddress addr;
            if(addr.parseAbsoluteAddress(result.propertyName.c_str()))
                return std::make_pair(result.resolvedDocumentObject,addr.toString(true));
            return std::make_pair(result.resolvedDocumentObject,result.propertyName);
        }else if(result.propertyType == PseudoSelf
                    && result.resolvedDocumentObject
                    && result.propertyIndex+1 < (int)components.size())
        {
            return std::make_pair(result.resolvedDocumentObject,
                    components[result.propertyIndex+1].getName());
        }
    }
    return std::make_pair(result.resolvedDocumentObject,std::string());
}

/**
 * @brief Get components as a string list.
 * @return List of strings.
 */

std::vector<std::string> ObjectIdentifier::getStringList() const
{
    std::vector<std::string> l;
    ResolveResults result(*this);

    if(!result.resolvedProperty || result.resolvedDocumentObject != owner) {
        if (documentNameSet)
            l.push_back(documentName.toString());

        if (documentObjectNameSet)
            l.push_back(documentObjectName.toString());
    }
    if(subObjectName.getString().size()) {
        l.back() += subObjectName.toString();
    }
    std::vector<Component>::const_iterator i = components.begin();
    while (i != components.end()) {
        std::ostringstream ss;
        i->toString(ss);
        l.push_back(ss.str());
        ++i;
    }

    return l;
}

/**
 * @brief Construct the simplest possible object identifier relative to another.
 * @param other The other object identifier.
 * @return A new simplified object identifier.
 */

ObjectIdentifier ObjectIdentifier::relativeTo(const ObjectIdentifier &other) const
{
    ObjectIdentifier result(other.getOwner());
    ResolveResults thisresult(*this);
    ResolveResults otherresult(other);

    if (otherresult.resolvedDocument != thisresult.resolvedDocument)
        result.setDocumentName(std::move(thisresult.resolvedDocumentName), true);
    if (otherresult.resolvedDocumentObject != thisresult.resolvedDocumentObject)
        result.setDocumentObjectName(
                std::move(thisresult.resolvedDocumentObjectName), true, String(subObjectName));

    for (std::size_t i = thisresult.propertyIndex; i < components.size(); ++i)
        result << components[i];

    return result;
}

/**
 * @brief Parse a string to create an object identifier.
 *
 * This method throws an exception if the string is invalid.
 *
 * @param docObj Document object that will own this object identifier.
 * @param str String to parse
 * @return A new object identifier.
 */

ObjectIdentifier ObjectIdentifier::parse(const DocumentObject *docObj, const std::string &str)
{
    std::unique_ptr<Expression> expr(ExpressionParser::parse(docObj, str.c_str()));
    VariableExpression * v = freecad_dynamic_cast<VariableExpression>(expr.get());

    if (v)
        return v->getPath();
    else
        FC_THROWM(Base::RuntimeError,"Invalid property specification.");
}

std::string ObjectIdentifier::resolveErrorString() const
{
    ResolveResults result(*this);

    return result.resolveErrorString();
}

/**
 * @brief << operator, used to add a component to the object identifier.
 * @param value Component object
 * @return Reference to itself.
 */

ObjectIdentifier &ObjectIdentifier::operator <<(const ObjectIdentifier::Component &value)
{
    components.push_back(value);
    _cache.clear();
    return *this;
}

ObjectIdentifier &ObjectIdentifier::operator <<(ObjectIdentifier::Component &&value)
{
    components.push_back(std::move(value));
    _cache.clear();
    return *this;
}


/**
 * @brief Get pointer to property pointed to by this object identifier.
 * @return Point to property if it is uniquely defined, or 0 otherwise.
 */

Property *ObjectIdentifier::getProperty(int *ptype) const
{
    ResolveResults result(*this);
    if(ptype)
        *ptype = result.propertyType;
    return result.resolvedProperty;
}

Property *ObjectIdentifier::resolveProperty(const App::DocumentObject *obj,
        const char *propertyName, App::DocumentObject *&sobj, int &ptype) const
{
    if(obj && subObjectName.getString().size()) {
        sobj = obj->getSubObject(subObjectName);
        obj = sobj;
    }
    if(!obj)
        return 0;

    static std::map<std::string,int> _props = {
        {"_shape",PseudoShape},
        {"_pla",PseudoPlacement},
        {"_matrix",PseudoMatrix},
        {"__pla",PseudoLinkPlacement},
        {"__matrix",PseudoLinkMatrix},
        {"_self",PseudoSelf},
        {"_app",PseudoApp},
        {"_part",PseudoPart},
        {"_re",PseudoRegex},
        {"_py", PseudoBuiltins},
        {"_math", PseudoMath},
        {"_coll", PseudoCollections},
        {"_gui",PseudoGui},
        {"_cq",PseudoCadquery},
    };
    auto it = _props.find(propertyName);
    if(it == _props.end())
        ptype = PseudoNone;
    else {
        ptype = it->second;
        if(ptype != PseudoShape &&
           subObjectName.getString().size() &&
           !boost::ends_with(subObjectName.getString(),"."))
        {
            return 0;
        }
        return &const_cast<App::DocumentObject*>(obj)->Label; //fake the property
    }

    auto prop = obj->getPropertyByName(propertyName);
    if(prop && !prop->testStatus(Property::Hidden) && !(prop->getType() & PropertyType::Prop_Hidden))
        return prop;

    auto linked = obj->getLinkedObject(true);
    if(!linked || linked==obj) {
        auto ext = obj->getExtensionByType<App::LinkBaseExtension>(true);
        if(!ext)
            return prop;
        linked = ext->getTrueLinkedObject(true);
        if(!linked || linked==obj)
            return prop;
    }

    auto linkedProp = linked->getPropertyByName(propertyName);
    return linkedProp?linkedProp:prop;
}



/**
 * @brief Create a canonical representation of an object identifier.
 *
 * The main work is actually done by the property's virtual canonicalPath(...) method,
 * which is invoked by this call.
 *
 * @return A new object identifier.
 */

ObjectIdentifier ObjectIdentifier::canonicalPath() const
{
    ObjectIdentifier res(*this);
    ResolveResults result(res);
    if(result.resolvedDocumentObject && result.resolvedDocumentObject!=owner) {
        res.owner = result.resolvedDocumentObject;
        res._cache.clear();
    }
    res.resolveAmbiguity(result);
    if(!result.resolvedProperty || result.propertyType!=PseudoNone)
        return res;
    return result.resolvedProperty->canonicalPath(res);
}

static const std::map<std::string,std::string> *_DocumentMap;
ObjectIdentifier::DocumentMapper::DocumentMapper(const std::map<std::string,std::string> &map)
{
    assert(!_DocumentMap);
    _DocumentMap = &map;
}

ObjectIdentifier::DocumentMapper::~DocumentMapper()
{
    _DocumentMap = 0;
}

/**
 * @brief Set the document name for this object identifier.
 *
 * If force is true, the document name will always be included in the string representation.
 *
 * @param name Name of document object.
 * @param force Force name to be set
 */

void ObjectIdentifier::setDocumentName(ObjectIdentifier::String &&name, bool force)
{
    if(name.getString().empty())
        force = false;
    documentNameSet = force;
    _cache.clear();
    if(name.getString().size() && _DocumentMap) {
        if(name.isRealString()) {
            auto iter = _DocumentMap->find(name.toString());
            if(iter!=_DocumentMap->end()) {
                documentName = String(iter->second,true);
                return;
            }
        }else{
            auto iter = _DocumentMap->find(name.getString());
            if(iter!=_DocumentMap->end()) {
                documentName = String(iter->second,false,true);
                return;
            }
        }
    }
    documentName = std::move(name);
}

/**
 * @brief Get the document name from this object identifier
 *
 * @return Document name as a String object.
 */

ObjectIdentifier::String ObjectIdentifier::getDocumentName() const
{
    ResolveResults result(*this);

    return result.resolvedDocumentName;
}

/**
 * @brief Set the document object name of this object identifier.
 *
 * If force is true, the document object will not be resolved dynamically from the
 * object identifier's components, but used as given by this method.
 *
 * @param name Name of document object.
 * @param force Force name to be set.
 */

void ObjectIdentifier::setDocumentObjectName(ObjectIdentifier::String &&name, bool force,
        ObjectIdentifier::String &&subname, bool checkImport)
{
    if(checkImport) {
        name.checkImport(owner);
        subname.checkImport(owner,0,&name);
    }

    documentObjectName = std::move(name);
    documentObjectNameSet = force;
    subObjectName = std::move(subname);

    _cache.clear();
}

void ObjectIdentifier::setDocumentObjectName(const App::DocumentObject *obj, bool force,
        ObjectIdentifier::String &&subname, bool checkImport)
{
    if(!owner || !obj || !obj->getNameInDocument() || !obj->getDocument())
        FC_THROWM(Base::RuntimeError,"invalid object");

    if(checkImport)
        subname.checkImport(owner,obj);

    if(obj == owner)
        force = false;
    else
        localProperty = false;
    if(obj->getDocument() == owner->getDocument())
        setDocumentName(String());
    else if(!documentNameSet) {
        if(obj->getDocument() == owner->getDocument())
            setDocumentName(String());
        else {
            documentNameSet = true;
            documentName = String(obj->getDocument()->getName(),false,true);
        }
    }else if(documentName.isRealString())
        documentName = String(obj->getDocument()->Label.getStrValue(),true);
    else
        documentName = String(obj->getDocument()->getName(),false,true);

    documentObjectNameSet = force;
    documentObjectName = String(obj->getNameInDocument(),false,true);
    subObjectName = std::move(subname);

    _cache.clear();
}


/**
 * @brief Get the document object name
 * @return String with name of document object as resolved by object identifier.
 */

ObjectIdentifier::String ObjectIdentifier::getDocumentObjectName() const
{
    ResolveResults result(*this);

    return result.resolvedDocumentObjectName;
}

bool ObjectIdentifier::hasDocumentObjectName(bool forced) const {
    return !documentObjectName.getString().empty() && (!forced || documentObjectNameSet);
}

/**
 * @brief Get a string representation of this object identifier.
 * @return String representation.
 */

std::string ObjectIdentifier::String::toString(bool toPython) const
{
    if (isRealString())
        return quote(str,toPython);
    else
        return str;
}

void ObjectIdentifier::String::checkImport(const App::DocumentObject *owner,
        const App::DocumentObject *obj, String *objName)
{
    if(owner && owner->getDocument() && str.size() &&
       ExpressionParser::ExpressionImporter::reader()) {
        auto reader = ExpressionParser::ExpressionImporter::reader();
        if (obj || objName) {
            bool restoreLabel = false;
            str = PropertyLinkBase::importSubName(*reader,str.c_str(),restoreLabel);
            if (restoreLabel) {
                if (!obj) {
                    std::bitset<32> flags;
                    obj = getDocumentObject(owner->getDocument(),*objName,flags);
                    if (!obj) {
                        FC_ERR("Cannot find object " << objName->toString());
                    }
                }

                if (obj) {
                    PropertyLinkBase::restoreLabelReference(obj,str);
                }
            }
        }
        else if (str.back()!='@') {
            str = reader->getName(str.c_str());
        }
        else {
            str.resize(str.size()-1);
            auto mapped = reader->getName(str.c_str());
            auto obj = owner->getDocument()->getObject(mapped);
            if (!obj) {
                FC_ERR("Cannot find object " << str);
            }
            else {
                isString = true;
                forceIdentifier = false;
                str = obj->Label.getValue();
            }
        }
    }
}

Py::Object ObjectIdentifier::access(const ResolveResults &result, Py::Object *value) const
{
    if(!result.resolvedDocumentObject || !result.resolvedProperty ||
       (subObjectName.getString().size() && !result.resolvedSubObject))
    {
        FC_THROWM(Base::RuntimeError, result.resolveErrorString()
           << " in '" << toString() << "'");
    }

    Py::Object pyobj;
    int ptype = result.propertyType;

    // NOTE! We do not keep reference of the imported module, assuming once
    // imported they'll live (because of sys.modules) till the application
    // dies.
#define GET_MODULE(_name) do {\
        static PyObject *pymod;\
        if(!pymod) {\
           pymod = PyImport_ImportModule(#_name);\
            if(!pymod)\
                Base::PyException::ThrowException();\
            else\
                Py_DECREF(pymod);\
        }\
        pyobj = Py::Object(pymod);\
    }while(0)

    size_t idx = result.propertyIndex+1;
    switch(ptype) {
    case PseudoApp:
        GET_MODULE(FreeCAD);
        break;
    case PseudoGui:
        GET_MODULE(FreeCADGui);
        break;
    case PseudoPart:
        GET_MODULE(Part);
        break;
    case PseudoCadquery:
        GET_MODULE(freecad.fc_cadquery);
        break;
    case PseudoRegex:
        GET_MODULE(re);
        break;
    case PseudoBuiltins:
#if PY_MAJOR_VERSION < 3
        GET_MODULE(__builtin__);
#else
        GET_MODULE(builtins);
#endif
        break;
    case PseudoMath:
        GET_MODULE(math);
        break;
    case PseudoCollections:
        GET_MODULE(collections);
        break;
    case PseudoShape: {
        GET_MODULE(Part);
        Py::Callable func(pyobj.getAttr("getShape"));
        Py::Tuple tuple(1);
        tuple.setItem(0,Py::Object(result.resolvedDocumentObject->getPyObject(),true));
        if(result.subObjectName.getString().empty())
            pyobj = func.apply(tuple);
        else{
            Py::Dict dict;
            dict.setItem("subname",Py::String(result.subObjectName.getString()));
            dict.setItem("needSubElement",Py::True());
            pyobj = func.apply(tuple,dict);
        }
        break;
    } default: {
        Base::Matrix4D mat;
        auto obj = result.resolvedDocumentObject;
        switch(ptype) {
        case PseudoPlacement:
        case PseudoMatrix:
        case PseudoLinkPlacement:
        case PseudoLinkMatrix:
            obj->getSubObject(result.subObjectName.getString().c_str(),0,&mat);
            break;
        default:
            break;
        }
        if(result.resolvedSubObject)
            obj = result.resolvedSubObject;
        switch(ptype) {
        case PseudoPlacement:
            pyobj = Py::Placement(Base::Placement(mat));
            break;
        case PseudoMatrix:
            pyobj = Py::Matrix(mat);
            break;
        case PseudoLinkPlacement:
        case PseudoLinkMatrix: {
            auto linked = obj->getLinkedObject(true,&mat,false);
            if(!linked || linked==obj) {
                auto ext = obj->getExtensionByType<App::LinkBaseExtension>(true);
                if(ext)
                    ext->getTrueLinkedObject(true,&mat);
            }
            if(ptype == PseudoLinkPlacement)
                pyobj = Py::Placement(Base::Placement(mat));
            else
                pyobj = Py::Matrix(mat);
            break;
        }
        case PseudoSelf:
            pyobj = Py::Object(obj->getPyObject(),true);
            break;
        default: {
            // NOTE! We cannot directly call Property::getPyObject(), but
            // instead, must obtain the property's python object through
            // DocumentObjectPy::getAttr(). Because, PyObjectBase has internal
            // attribute tracking only if we obtain attribute through
            // getAttr(). Without attribute tracking, we can't do things like
            //
            //      obj.Placement.Base.x = 10.
            //
            // What happens is that the when Python interpreter calls
            //
            //      Base.setAttr('x', 10),
            //
            // PyObjectBase will lookup Base's parent, i.e. Placement, and call
            //
            //      Placement.setAttr('Base', Base),
            //
            // and in turn calls
            //
            //      obj.setAttr('Placement',Placement)
            //
            // The tracking logic is implemented in PyObjectBase::__getattro/__setattro

            auto container = result.resolvedProperty->getContainer();
            if(container
                    && container!=result.resolvedDocumentObject
                    && container!=result.resolvedSubObject)
            {
                if(!container->isDerivedFrom(DocumentObject::getClassTypeId()))
                    FC_WARN("Invalid property container");
                else
                    obj = static_cast<DocumentObject*>(container);
            }
            pyobj = Py::Object(obj->getPyObject(),true);
            idx = result.propertyIndex;
            break;
        }}}
    }
    if(components.empty())
        return pyobj;
    size_t count = components.size();
    if(value) --count;
    assert(idx<=count);
    for(;idx<count;++idx)
        pyobj = components[idx].get(pyobj);
    if(value) {
        components[idx].set(pyobj,*value);
        return Py::Object();
    }
    return pyobj;
}

/**
 * @brief Get the value of the property or field pointed to by this object identifier.
 *
 * All type of objects are supported. Some types are casted to FC native
 * type, including: Int, Float, String, Unicode String, and Quantities. Others
 * are just kept as Python object wrapped by App::any.
 *
 * @param pathValue: if true, calls the property's getPathValue(), which is
 * necessary for Qunatities to work.
 *
 * @return The value of the property or field.
 */

App::any ObjectIdentifier::getValue(bool pathValue, bool *isPseudoProperty) const
{
    ResolveResults rs(*this);

    if(isPseudoProperty) {
        *isPseudoProperty = rs.propertyType!=PseudoNone;
        if(rs.propertyType == PseudoSelf
                && isLocalProperty()
                && rs.propertyIndex+1 < (int)components.size()
                && owner->getPropertyByName(components[rs.propertyIndex+1].getName().c_str()))
        {
            *isPseudoProperty = false;
        }
    }

    if(rs.resolvedProperty && rs.propertyType==PseudoNone && pathValue)
        return rs.resolvedProperty->getPathValue(*this);

    Base::PyGILStateLocker lock;
    try {
        return pyObjectToAny(access(rs));
    }catch(Py::Exception &) {
        Base::PyException::ThrowException();
    }
    return App::any();
}

Py::Object ObjectIdentifier::getPyValue(bool pathValue, bool *isPseudoProperty) const
{
    ResolveResults rs(*this);

    if(isPseudoProperty) {
        *isPseudoProperty = rs.propertyType!=PseudoNone;
        if(rs.propertyType == PseudoSelf
                && isLocalProperty()
                && rs.propertyIndex+1 < (int)components.size()
                && owner->getPropertyByName(components[rs.propertyIndex+1].getName().c_str()))
        {
            *isPseudoProperty = false;
        }
    }

    if(rs.resolvedProperty && rs.propertyType==PseudoNone && pathValue) {
        Py::Object res;
        if(rs.resolvedProperty->getPyPathValue(*this,res))
            return res;
    }

    try {
        return access(rs);
    }catch(Py::Exception &) {
        Base::PyException::ThrowException();
    }
    return Py::Object();
}

/**
 * @brief Set value of a property or field pointed to by this object identifier.
 *
 * This method uses Python to do the actual work. and a limited set of types that
 * can be in the App::any variable is supported: Base::Quantity, double,
 * char*, const char*, int, unsigned int, short, unsigned short, char, and unsigned char.
 *
 * @param value Value to set
 */

void ObjectIdentifier::setValue(const App::any &value) const
{
    std::stringstream ss;
    ResolveResults rs(*this);
    if(rs.propertyType)
        FC_THROWM(Base::RuntimeError,"Cannot set pseudo property");

    Base::PyGILStateLocker lock;
    try {
        Py::Object pyvalue = pyObjectFromAny(value);
        access(rs,&pyvalue);
    }catch(Py::Exception &) {
        Base::PyException::ThrowException();
    }
}

const std::string &ObjectIdentifier::getSubObjectName(bool newStyle) const {
    if(newStyle && shadowSub.first.size())
        return shadowSub.first;
    if(shadowSub.second.size())
        return shadowSub.second;
    return subObjectName.getString();
}

const std::string &ObjectIdentifier::getSubObjectName() const {
    return subObjectName.getString();
}

void ObjectIdentifier::importSubNames(const ObjectIdentifier::SubNameMap &subNameMap)
{
    if(!owner || !owner->getDocument())
        return;
    ResolveResults result(*this);
    auto it = subNameMap.find(std::make_pair(result.resolvedDocumentObject,std::string()));
    if(it!=subNameMap.end()) {
        auto obj = owner->getDocument()->getObject(it->second.c_str());
        if(!obj) {
            FC_ERR("Failed to find import object " << it->second << " from "
                    << result.resolvedDocumentObject->getFullName());
            return;
        }
        documentNameSet = false;
        documentName.str.clear();
        if(documentObjectName.isRealString())
            documentObjectName.str = obj->Label.getValue();
        else
            documentObjectName.str = obj->getNameInDocument();
        _cache.clear();
    }
    if(subObjectName.getString().empty())
        return;
    it = subNameMap.find(std::make_pair(
                result.resolvedDocumentObject,subObjectName.str));
    if(it==subNameMap.end())
        return;
    subObjectName = String(it->second,true);
    _cache.clear();
    shadowSub.first.clear();
    shadowSub.second.clear();
}

bool ObjectIdentifier::updateElementReference(ExpressionVisitor &v,
        App::DocumentObject *feature, bool reverse)
{
    assert(v.getPropertyLink());
    if(subObjectName.getString().empty())
        return false;

    ResolveResults result(*this);
    if(!result.resolvedSubObject)
        return false;
    if(v.getPropertyLink()->_updateElementReference(
            feature,result.resolvedDocumentObject,subObjectName.str,shadowSub,reverse)) {
        _cache.clear();
        v.aboutToChange();
        return true;
    }
    return false;
}

bool ObjectIdentifier::adjustLinks(ExpressionVisitor &v, const std::set<App::DocumentObject *> &inList) {
    ResolveResults result(*this);
    if(!result.resolvedDocumentObject)
        return false;
    if(result.resolvedSubObject) {
        PropertyLinkSub prop;
        prop.setValue(result.resolvedDocumentObject, {subObjectName.getString()});
        if(prop.adjustLink(inList)) {
            v.aboutToChange();
            documentObjectName = String(prop.getValue()->getNameInDocument(),false,true);
            subObjectName = String(prop.getSubValues().front(),true);
            _cache.clear();
            return true;
        }
    }
    return false;
}

bool ObjectIdentifier::isTouched() const {
    try {
        ResolveResults result(*this);
        if(result.resolvedProperty) {
            if(result.propertyType==PseudoNone)
                return result.resolvedProperty->isTouched();
            else
                return result.resolvedDocumentObject->isTouched();
        }
    }catch(...) {}
    return false;
}

void ObjectIdentifier::resolveAmbiguity() {
    if(!owner || !owner->getNameInDocument() || isLocalProperty() ||
       (documentObjectNameSet && documentObjectName.getString().size() &&
        (documentObjectName.isRealString() || documentObjectName.isForceIdentifier())))
    {
        return;
    }

    ResolveResults result(*this);
    resolveAmbiguity(result);
}

void ObjectIdentifier::resolveAmbiguity(ResolveResults &result) {

    if(!result.resolvedDocumentObject)
        return;

    if(result.propertyIndex==1)
        components.erase(components.begin());

    String subname = subObjectName;
    if(result.resolvedDocumentObject == owner) {
        setDocumentObjectName(owner,false,std::move(subname));
    }else if(result.flags.test(ResolveByIdentifier))
        setDocumentObjectName(result.resolvedDocumentObject,true,std::move(subname));
    else
        setDocumentObjectName(
                String(result.resolvedDocumentObject->Label.getStrValue(),true,false),true,std::move(subname));

    if(result.resolvedDocumentObject->getDocument() == owner->getDocument())
        setDocumentName(String());
}

/** Construct and initialize a ResolveResults object, given an ObjectIdentifier instance.
 *
 * The constructor will invoke the ObjectIdentifier's resolve() method to initialize the object's data.
 */

ObjectIdentifier::ResolveResults::ResolveResults(const ObjectIdentifier &oi)
    : propertyIndex(0)
    , resolvedDocument(0)
    , resolvedDocumentName()
    , resolvedDocumentObject(0)
    , resolvedDocumentObjectName()
    , resolvedSubObject(0)
    , resolvedProperty(0)
    , propertyName()
    , propertyType(PseudoNone)
{
    oi.resolve(*this);
}

std::string ObjectIdentifier::ResolveResults::resolveErrorString() const
{
    std::ostringstream ss;
    if (resolvedDocument == 0) {
        if(flags.test(ResolveAmbiguous))
            ss << "Ambiguous document name/label '"
               << resolvedDocumentName.getString() << "'";
        else
            ss << "Document '" << resolvedDocumentName.toString() << "' not found";
    } else if (resolvedDocumentObject == 0) {
        if(flags.test(ResolveAmbiguous))
            ss << "Ambiguous document object name '"
                << resolvedDocumentObjectName.getString() << "'";
        else
            ss << "Document object '" << resolvedDocumentObjectName.toString()
                << "' not found";
    } else if (subObjectName.getString().size() && resolvedSubObject == 0) {
        ss << "Sub-object '" << resolvedDocumentObjectName.getString()
            << '.' << subObjectName.toString() << "' not found";
    } else if (resolvedProperty == 0) {
        if(propertyType != PseudoShape &&
           subObjectName.getString().size() &&
           !boost::ends_with(subObjectName.getString(),"."))
        {
            ss << "Non geometry subname reference must end with '.'";
        }else
            ss << "Property '" << propertyName << "' not found";
    }

    return ss.str();
}

void ObjectIdentifier::ResolveResults::getProperty(const ObjectIdentifier &oi) {
    resolvedProperty = oi.resolveProperty(
            resolvedDocumentObject,propertyName.c_str(),resolvedSubObject,propertyType);
}

