/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/*
 * $Id$
 */

// ---------------------------------------------------------------------------
//  Includes
// ---------------------------------------------------------------------------
#include <xercesc/util/regx/RegularExpression.hpp>
#include <xercesc/util/PlatformUtils.hpp>
#include <xercesc/util/regx/Match.hpp>
#include <xercesc/util/regx/RangeToken.hpp>
#include <xercesc/util/regx/RegxDefs.hpp>
#include <xercesc/util/regx/XMLUniCharacter.hpp>
#include <xercesc/util/regx/ParserForXMLSchema.hpp>
#include <xercesc/util/Janitor.hpp>
#include <xercesc/util/ParseException.hpp>
#include <xercesc/util/IllegalArgumentException.hpp>
#include <xercesc/framework/XMLBuffer.hpp>
#include <xercesc/util/OutOfMemoryException.hpp>
#include <xercesc/util/XMLInitializer.hpp>
#include <xercesc/util/XMLUniDefs.hpp>
#include <xercesc/util/ValueStackOf.hpp>

XERCES_CPP_NAMESPACE_BEGIN

// ---------------------------------------------------------------------------
//  Static member data initialization
// ---------------------------------------------------------------------------
const unsigned int RegularExpression::IGNORE_CASE = 2;
const unsigned int RegularExpression::SINGLE_LINE = 4;
const unsigned int RegularExpression::MULTIPLE_LINE = 8;
const unsigned int RegularExpression::EXTENDED_COMMENT = 16;
const unsigned int RegularExpression::PROHIBIT_HEAD_CHARACTER_OPTIMIZATION = 128;
const unsigned int RegularExpression::PROHIBIT_FIXED_STRING_OPTIMIZATION = 256;
const unsigned int RegularExpression::XMLSCHEMA_MODE = 512;
RangeToken*        RegularExpression::fWordRange = 0;

bool RegularExpression::matchIgnoreCase(const XMLInt32 ch1,
                                        const XMLInt32 ch2) const
{
    if (ch1 >= 0x10000)
    {
        XMLCh string1[2];
        XMLCh string2[2];

        RegxUtil::decomposeToSurrogates(ch1, string1[0], string1[1]);

        if (ch2 >= 0x10000)
        {
            RegxUtil::decomposeToSurrogates(ch2, string2[0], string2[1]);
        }
        else
        {
            // XMLString::compareNIString is broken, because it assume the
            // two strings must be of the same length.  Note that two strings
            // of different length could compare as equal, because there is no
            // guarantee that a Unicode code point that is encoded in UTF-16 as
            // a surrogate pair does not have a case mapping to a code point
            // that is not in the surrogate range.  Just to be safe, we pad the
            // shorter string with a space, which cannot hvae a case mapping.
            string2[0] = (XMLCh)ch2;
            string2[1] = chSpace;
        }

        return (0==XMLString::compareNIString(string1, string2, 2));
    }
    else if (ch2 >= 0x10000)
    {
        const XMLCh string1[2] = { (XMLCh)ch1, chSpace };
        XMLCh string2[2];

        RegxUtil::decomposeToSurrogates(ch2, string2[0], string2[1]);

        return (0==XMLString::compareNIString(string1, string2, 2));
    }
    else
    {
        const XMLCh  char1 = (XMLCh)ch1;
        const XMLCh  char2 = (XMLCh)ch2;

        return (0==XMLString::compareNIString(&char1, &char2, 1));
    }
}



// ---------------------------------------------------------------------------
//  RegularExpression::Context: Constructors and Destructor
// ---------------------------------------------------------------------------
RegularExpression::Context::Context(MemoryManager* const manager) :
    fAdoptMatch(false)
    , fStart(0)
    , fLimit(0)
    , fLength(0)
    , fSize(0)
    , fStringMaxLen(0)
    , fOffsets(0)
    , fMatch(0)
    , fString(0)
    , fOptions(0)
    , fMemoryManager(manager)
{
}

RegularExpression::Context::Context(Context* src) :
    fAdoptMatch(false)
    , fStart(src->fStart)
    , fLimit(src->fLimit)
    , fLength(src->fLength)
    , fSize(src->fSize)
    , fStringMaxLen(src->fStringMaxLen)
    , fOffsets(0)
    , fMatch(0)
    , fString(src->fString)
    , fOptions(src->fOptions)
    , fMemoryManager(src->fMemoryManager)
{
    if(src->fOffsets)
    {
        fOffsets = (int*) fMemoryManager->allocate(fSize* sizeof(int));
        for (int i = 0; i< fSize; i++)
            fOffsets[i] = src->fOffsets[i];
    }
    if(src->fMatch)
    {
        fMatch=new (fMemoryManager) Match(*src->fMatch);
        fAdoptMatch=true;
    }
}

RegularExpression::Context& RegularExpression::Context::operator=(const RegularExpression::Context& other)
{
    if (this != &other)
    {
        fStart=other.fStart;
        fLimit=other.fLimit;
        fLength=other.fLength;
        fStringMaxLen=other.fStringMaxLen;
        fString=other.fString;
        fOptions=other.fOptions;

        // if offset and match are already allocated with the right size, reuse them 
        // (fMatch can be provided by the user to get the data back)
        if(fMatch && other.fMatch && fMatch->getNoGroups()==other.fMatch->getNoGroups())
            *fMatch=*other.fMatch;
        else
        {
            if (fAdoptMatch)
                delete fMatch;
            fMatch=0;
            if(other.fMatch)
            {
                fMatch=new (other.fMemoryManager) Match(*other.fMatch);
                fAdoptMatch=true;
            }
        }

        if (fOffsets && other.fOffsets && fSize==other.fSize)
        {
            for (int i = 0; i< fSize; i++)
                fOffsets[i] = other.fOffsets[i];
        }
        else
        {
            if(fOffsets)
                fMemoryManager->deallocate(fOffsets);//delete [] fOffsets;
            fOffsets=0;
            fSize=other.fSize;
            if(other.fOffsets)
            {
                fOffsets = (int*) other.fMemoryManager->allocate(fSize* sizeof(int));
                for (int i = 0; i< fSize; i++)
                    fOffsets[i] = other.fOffsets[i];
            }
        }

        fMemoryManager=other.fMemoryManager;
    }

    return *this;
}

RegularExpression::Context::~Context()
{
    if (fOffsets)
        fMemoryManager->deallocate(fOffsets);//delete [] fOffsets;

    if (fAdoptMatch)
        delete fMatch;
}

// ---------------------------------------------------------------------------
//  RegularExpression::Context: Public methods
// ---------------------------------------------------------------------------
void RegularExpression::Context::reset(const XMLCh* const string
                                       , const XMLSize_t stringLen
                                       , const XMLSize_t start
                                       , const XMLSize_t limit
                                       , const int noClosures
                                       , const unsigned int options)
{
    fString = string;
    fStringMaxLen = stringLen;
    fStart = start;
    fLimit = limit;
    fLength = fLimit - fStart;
    if (fAdoptMatch)
        delete fMatch;
    fMatch = 0;

    if (fSize != noClosures) {
        if (fOffsets)
            fMemoryManager->deallocate(fOffsets);//delete [] fOffsets;
        fOffsets = (int*) fMemoryManager->allocate(noClosures * sizeof(int));//new int[noClosures];
    }

    fSize = noClosures;
    fOptions = options;

    for (int i = 0; i< fSize; i++)
        fOffsets[i] = -1;
}

bool RegularExpression::Context::nextCh(XMLInt32& ch, XMLSize_t& offset)
{
    ch = fString[offset];

    if (RegxUtil::isHighSurrogate(ch)) {
        if ((offset + 1 < fLimit) && RegxUtil::isLowSurrogate(fString[offset+1])) {
            ch = RegxUtil::composeFromSurrogate(ch, fString[++offset]);
        }
        else return false;
    }
    else if (RegxUtil::isLowSurrogate(ch)) {
        return false;
    }

    return true;
}

// ---------------------------------------------------------------------------
//  RegularExpression: Constructors and Destructors
// ---------------------------------------------------------------------------

typedef JanitorMemFunCall<RegularExpression>    CleanupType;

RegularExpression::RegularExpression(const char* const pattern,
                                     MemoryManager* const manager)
    :fHasBackReferences(false),
     fFixedStringOnly(false),
     fNoGroups(0),
     fMinLength(0),
     fNoClosures(0),
     fOptions(0),
     fBMPattern(0),
     fPattern(0),
     fFixedString(0),
     fOperations(0),
     fTokenTree(0),
     fFirstChar(0),
     fOpFactory(manager),
     fTokenFactory(0),
     fMemoryManager(manager)
{
    CleanupType cleanup(this, &RegularExpression::cleanUp);

    try {

        XMLCh* tmpBuf = XMLString::transcode(pattern, fMemoryManager);
        ArrayJanitor<XMLCh> janBuf(tmpBuf, fMemoryManager);
        setPattern(tmpBuf);
    }
    catch(const OutOfMemoryException&)
    {
        cleanup.release();

        throw;
    }

    cleanup.release();
}

RegularExpression::RegularExpression(const char* const pattern,
                                     const char* const options,
                                     MemoryManager* const manager)
    :fHasBackReferences(false),
     fFixedStringOnly(false),
     fNoGroups(0),
     fMinLength(0),
     fNoClosures(0),
     fOptions(0),
     fBMPattern(0),
     fPattern(0),
     fFixedString(0),
     fOperations(0),
     fTokenTree(0),
     fFirstChar(0),
     fOpFactory(manager),
     fTokenFactory(0),
     fMemoryManager(manager)
{
    CleanupType cleanup(this, &RegularExpression::cleanUp);

    try {

        XMLCh* tmpBuf = XMLString::transcode(pattern, fMemoryManager);
        ArrayJanitor<XMLCh> janBuf(tmpBuf, fMemoryManager);
        XMLCh* tmpOptions = XMLString::transcode(options, fMemoryManager);
        ArrayJanitor<XMLCh> janOps(tmpOptions, fMemoryManager);
        setPattern(tmpBuf, tmpOptions);
    }
    catch(const OutOfMemoryException&)
    {
        cleanup.release();

        throw;
    }

    cleanup.release();
}


RegularExpression::RegularExpression(const XMLCh* const pattern,
                                     MemoryManager* const manager)
    :fHasBackReferences(false),
     fFixedStringOnly(false),
     fNoGroups(0),
     fMinLength(0),
     fNoClosures(0),
     fOptions(0),
     fBMPattern(0),
     fPattern(0),
     fFixedString(0),
     fOperations(0),
     fTokenTree(0),
     fFirstChar(0),
     fOpFactory(manager),
     fTokenFactory(0),
     fMemoryManager(manager)
{
    CleanupType cleanup(this, &RegularExpression::cleanUp);

    try {

        setPattern(pattern);
    }
    catch(const OutOfMemoryException&)
    {
        cleanup.release();

        throw;
    }

    cleanup.release();
}

RegularExpression::RegularExpression(const XMLCh* const pattern,
                                     const XMLCh* const options,
                                     MemoryManager* const manager)
    :fHasBackReferences(false),
     fFixedStringOnly(false),
     fNoGroups(0),
     fMinLength(0),
     fNoClosures(0),
     fOptions(0),
     fBMPattern(0),
     fPattern(0),
     fFixedString(0),
     fOperations(0),
     fTokenTree(0),
     fFirstChar(0),
     fOpFactory(manager),
     fTokenFactory(0),
     fMemoryManager(manager)
{
    CleanupType cleanup(this, &RegularExpression::cleanUp);

    try {

        setPattern(pattern, options);
    }
    catch(const OutOfMemoryException&)
    {
        cleanup.release();

        throw;
    }

    cleanup.release();
}

RegularExpression::~RegularExpression() {

    cleanUp();
}

// ---------------------------------------------------------------------------
//  RegularExpression: Setter methods
// ---------------------------------------------------------------------------

RegxParser* RegularExpression::getRegexParser(const int options, MemoryManager* const manager)
{
    // the following construct causes an error in an Intel 7.1 32 bit compiler for
    // red hat linux 7.2
    // (when an exception is thrown the wrong object is deleted)
    //RegxParser* regxParser = isSet(fOptions, XMLSCHEMA_MODE)
    //    ? new (fMemoryManager) ParserForXMLSchema(fMemoryManager)
    //    : new (fMemoryManager) RegxParser(fMemoryManager);
    if (isSet(options, XMLSCHEMA_MODE))
        return new (manager) ParserForXMLSchema(manager);

    return new (manager) RegxParser(manager);
}

void RegularExpression::setPattern(const XMLCh* const pattern,
                                   const XMLCh* const options)
{

    fTokenFactory = new (fMemoryManager) TokenFactory(fMemoryManager);
    fOptions = parseOptions(options);
    fPattern = XMLString::replicate(pattern, fMemoryManager);

    RegxParser* regxParser=getRegexParser(fOptions, fMemoryManager);

    if (regxParser)
        regxParser->setTokenFactory(fTokenFactory);

    Janitor<RegxParser> janRegxParser(regxParser);
    fTokenTree = regxParser->parse(fPattern, fOptions);
    fNoGroups = regxParser->getNoParen();
    fHasBackReferences = regxParser->hasBackReferences();

    prepare();
}

// ---------------------------------------------------------------------------
//  RegularExpression: Matching methods
// ---------------------------------------------------------------------------
bool RegularExpression::matches(const char* const expression
                                , MemoryManager* const manager) const
{
    XMLCh* tmpBuf = XMLString::transcode(expression, manager);
    ArrayJanitor<XMLCh> janBuf(tmpBuf, manager);
    return matches(tmpBuf, 0, XMLString::stringLen(tmpBuf), 0, manager);
}

bool RegularExpression::matches(const char* const expression
                                , const XMLSize_t start, const XMLSize_t end
                                , MemoryManager* const manager) const
{

    XMLCh* tmpBuf = XMLString::transcode(expression, manager);
    ArrayJanitor<XMLCh> janBuf(tmpBuf, manager);
    return matches(tmpBuf, start, end, 0, manager);
}

bool RegularExpression::matches(const char* const expression
                                , Match* const match
                                , MemoryManager* const manager) const
{

    XMLCh* tmpBuf = XMLString::transcode(expression, manager);
    ArrayJanitor<XMLCh> janBuf(tmpBuf, manager);
    return matches(tmpBuf, 0, XMLString::stringLen(tmpBuf), match, manager);
}

bool RegularExpression::matches(const char* const expression, const XMLSize_t start
                                , const XMLSize_t end, Match* const pMatch
                                , MemoryManager* const manager) const
{

    XMLCh* tmpBuf = XMLString::transcode(expression, manager);
    ArrayJanitor<XMLCh> janBuf(tmpBuf, manager);
    return matches(tmpBuf, start, end, pMatch, manager);
}


// ---------------------------------------------------------------------------
//  RegularExpression: Matching methods - Wide char version
// ---------------------------------------------------------------------------
bool RegularExpression::matches(const XMLCh* const expression, MemoryManager* const manager) const
{
    return matches(expression, 0, XMLString::stringLen(expression), 0, manager);
}

bool RegularExpression::matches(const XMLCh* const expression
                                , const XMLSize_t start, const XMLSize_t end
                                , MemoryManager* const manager) const
{
    return matches(expression, start, end, 0, manager);
}

bool RegularExpression::matches(const XMLCh* const expression
                                , Match* const match
                                , MemoryManager* const manager) const
{
    return matches(expression, 0, XMLString::stringLen(expression), match, manager);
}

bool RegularExpression::matches(const XMLCh* const expression, const XMLSize_t start
                                , const XMLSize_t end, Match* const pMatch
                                , MemoryManager* const manager) const
{

    Context context(manager);
    XMLSize_t strLength = XMLString::stringLen(expression);

    context.reset(expression, strLength, start, end, fNoClosures, fOptions);

    bool adoptMatch = false;
    Match* lMatch = pMatch;

    if (lMatch != 0) {
        lMatch->setNoGroups(fNoGroups);
    }
    else if (fHasBackReferences) {
        lMatch = new (manager) Match(manager);
        lMatch->setNoGroups(fNoGroups);
        adoptMatch = true;
    }

    if (context.fAdoptMatch)
        delete context.fMatch;
    context.fMatch = lMatch;
    context.fAdoptMatch = adoptMatch;

    if (isSet(fOptions, XMLSCHEMA_MODE)) {

        int matchEnd = match(&context, fOperations, context.fStart);

        if (matchEnd == (int)context.fLimit) {

            if (context.fMatch != 0) {

                context.fMatch->setStartPos(0, (int)context.fStart);
                context.fMatch->setEndPos(0, matchEnd);
            }
            return true;
        }

        return false;
    }

    /*
     * If the pattern has only fixed string, use Boyer-Moore
     */
    if (fFixedStringOnly) {

        int ret = fBMPattern->matches(expression, context.fStart, context.fLimit);
        if (ret >= 0) {

            if (context.fMatch != 0) {
                context.fMatch->setStartPos(0, ret);
                context.fMatch->setEndPos(0, (int)(ret + XMLString::stringLen(fPattern)));
            }
            return true;
        }
        return false;
    }

    /*
     * If the pattern contains a fixed string, we check with Boyer-Moore
     * whether the text contains the fixed string or not. If not found
     * return false
     */
    if (fFixedString != 0) {

        int ret = fBMPattern->matches(expression, context.fStart, context.fLimit);

        if (ret < 0) { // No match
            return false;
        }
    }

    // if the length is less than the minimum length, we cannot possibly match
    if(context.fLimit<fMinLength)
        return false;

    XMLSize_t limit = context.fLimit - fMinLength;
    XMLSize_t matchStart;
    int matchEnd = -1;

    /*
     * Check whether the expression start with ".*"
     */
    if (fOperations != 0 && (fOperations->getOpType() == Op::O_CLOSURE || fOperations->getOpType() == Op::O_FINITE_CLOSURE)
        && fOperations->getChild()->getOpType() == Op::O_DOT) {

        if (isSet(fOptions, SINGLE_LINE)) {
            matchStart = context.fStart;
            matchEnd = match(&context, fOperations, matchStart);
        }
        else {
            bool previousIsEOL = true;

            for (matchStart=context.fStart; matchStart<=limit; matchStart++) {

                XMLCh ch = expression[matchStart];
                if (RegxUtil::isEOLChar(ch)) {
                    previousIsEOL = true;
                }
                else {

                    if (previousIsEOL) {
                        if (0 <= (matchEnd = match(&context, fOperations,
                                                   matchStart)))
                            break;
                    }

                    previousIsEOL = false;
                }
            }
        }
    }
    else {
        /*
         *    Optimization against the first char
         */
        if (fFirstChar != 0) {
            bool ignoreCase = isSet(fOptions, IGNORE_CASE);
            RangeToken* range = fFirstChar;

            if (ignoreCase)
                range = fFirstChar->getCaseInsensitiveToken(fTokenFactory);

            for (matchStart=context.fStart; matchStart<=limit; matchStart++) {

                XMLInt32 ch;

                if (!context.nextCh(ch, matchStart))
                    break;

                if (!range->match(ch))
                    continue;

                if (0 <= (matchEnd = match(&context,fOperations,matchStart)))
                    break;
            }
        }
        else {

            /*
             *    Straightforward matching
             */
            for (matchStart=context.fStart; matchStart<=limit; matchStart++) {

                if (0 <= (matchEnd = match(&context,fOperations,matchStart)))
                    break;
            }
        }
    }

    if (matchEnd >= 0) {

        if (context.fMatch != 0) {

            context.fMatch->setStartPos(0, (int)matchStart);
            context.fMatch->setEndPos(0, matchEnd);
        }
        return true;
    }
    return false;
}

// ---------------------------------------------------------------------------
//  RegularExpression: Tokenize methods
// ---------------------------------------------------------------------------
RefArrayVectorOf<XMLCh>* RegularExpression::tokenize(const char* const expression,
                                                     MemoryManager* const manager) const
{

  XMLCh* tmpBuf = XMLString::transcode(expression, manager);
  ArrayJanitor<XMLCh> janBuf(tmpBuf, manager);
  return tokenize(tmpBuf, 0, XMLString::stringLen(tmpBuf), manager);
}

RefArrayVectorOf<XMLCh>* RegularExpression::tokenize(const char* const expression,
                                                     const XMLSize_t start, const XMLSize_t end,
                                                     MemoryManager* const manager) const
{

  XMLCh* tmpBuf = XMLString::transcode(expression, manager);
  ArrayJanitor<XMLCh> janBuf(tmpBuf, manager);
  return tokenize(tmpBuf, start, end, manager);
}



// ---------------------------------------------------------------------------
//  RegularExpression: Tokenize methods - Wide char version
// ---------------------------------------------------------------------------
RefArrayVectorOf<XMLCh>* RegularExpression::tokenize(const XMLCh* const expression,
                                                     MemoryManager* const manager) const
{
    return tokenize(expression, 0, XMLString::stringLen(expression), manager);
}

RefArrayVectorOf<XMLCh>* RegularExpression::tokenize(const XMLCh* const matchString,
                                                     const XMLSize_t start, const XMLSize_t end,
                                                     MemoryManager* const manager) const
{
    // check if matches zero length string - throw error if so
    if(matches(XMLUni::fgZeroLenString, manager)){
        ThrowXMLwithMemMgr(RuntimeException, XMLExcepts::Regex_RepPatMatchesZeroString, manager);
    }

    RefVectorOf<Match> *subEx = new (manager) RefVectorOf<Match>(10, true, manager);
    Janitor<RefVectorOf<Match> > janSubEx(subEx);

    allMatches(matchString, start, end, subEx, manager);

    RefArrayVectorOf<XMLCh> *tokens = new (manager) RefArrayVectorOf<XMLCh>(16, true, manager);
    XMLSize_t tokStart = start;

    XMLSize_t i = 0;
    for(; i < subEx->size(); ++i) {
        Match *match = subEx->elementAt(i);
        XMLSize_t matchStart = match->getStartPos(0);

        XMLCh *token = (XMLCh*)manager->allocate((matchStart + 1 - tokStart) * sizeof(XMLCh));
        XMLString::subString(token, matchString, tokStart, matchStart, manager);
        tokens->addElement(token);

        tokStart = match->getEndPos(0);
    }

    XMLCh *token = (XMLCh*)manager->allocate((end + 1 - tokStart) * sizeof(XMLCh));
    XMLString::subString(token, matchString, tokStart, end, manager);
    tokens->addElement(token);

    return tokens;
}

void RegularExpression::allMatches(const XMLCh* const matchString, const XMLSize_t start, const XMLSize_t end,
                                   RefVectorOf<Match> *subEx, MemoryManager* const manager) const
{
    Context context(manager);
    context.reset(matchString, XMLString::stringLen(matchString), start, end, fNoClosures, fOptions);

    context.fMatch = new (manager) Match(manager);
    context.fMatch->setNoGroups(fNoGroups);
    context.fAdoptMatch = true;

    XMLSize_t matchStart = start;
    while(matchStart <= end) {
        XMLSize_t matchEnd = match(&context, fOperations, matchStart);
        if(matchEnd != (XMLSize_t)-1) {
            context.fMatch->setStartPos(0, (int)matchStart);
            context.fMatch->setEndPos(0, (int)matchEnd);

            subEx->addElement(context.fMatch);

            context.fMatch = new (manager) Match(*(context.fMatch));
            context.fAdoptMatch = true;

            matchStart = matchEnd;
        } else {
            ++matchStart;
        }
    }
}


// -----------------------------------------------------------------------
//  RegularExpression: Replace methods
// -----------------------------------------------------------------------
XMLCh* RegularExpression::replace(const char* const matchString,
                                  const char* const replaceString,
                                  MemoryManager* const manager) const
{

    XMLCh* tmpBuf = XMLString::transcode(matchString, manager);
    ArrayJanitor<XMLCh> janBuf(tmpBuf, manager);
    XMLCh* tmpBuf2 = XMLString::transcode(replaceString, manager);
    ArrayJanitor<XMLCh> janBuf2(tmpBuf2, manager);

    return replace(tmpBuf, tmpBuf2, 0, XMLString::stringLen(tmpBuf), manager);
}

XMLCh* RegularExpression::replace(const char* const matchString,
                                  const char* const replaceString,
                                  const XMLSize_t start, const XMLSize_t end,
                                  MemoryManager* const manager) const
{

    XMLCh* tmpBuf = XMLString::transcode(matchString, manager);
    ArrayJanitor<XMLCh> janBuf(tmpBuf, manager);
    XMLCh* tmpBuf2 = XMLString::transcode(replaceString, manager);
    ArrayJanitor<XMLCh> janBuf2(tmpBuf2, manager);

    return replace(tmpBuf, tmpBuf2, start, end, manager);
}


// ---------------------------------------------------------------------------
//  RegularExpression: Replace methods - Wide char version
// ---------------------------------------------------------------------------
XMLCh* RegularExpression::replace(const XMLCh* const matchString,
                                  const XMLCh* const replaceString,
                                  MemoryManager* const manager) const
{

    return replace(matchString, replaceString, 0,
                   XMLString::stringLen(matchString), manager);
}

XMLCh* RegularExpression::replace(const XMLCh* const matchString,
                                  const XMLCh* const replaceString,
                                  const XMLSize_t start, const XMLSize_t end,
                                  MemoryManager* const manager) const
{
    // check if matches zero length string - throw error if so
    if(matches(XMLUni::fgZeroLenString, manager)){
        ThrowXMLwithMemMgr(RuntimeException, XMLExcepts::Regex_RepPatMatchesZeroString, manager);
    }

    RefVectorOf<Match> *subEx = new (manager) RefVectorOf<Match>(10, true, manager);
    Janitor<RefVectorOf<Match> > janSubEx(subEx);

    allMatches(matchString, start, end, subEx, manager);

    XMLBuffer result(1023, manager);
    int tokStart = (int)start;

    XMLSize_t i = 0;
    for(; i < subEx->size(); ++i) {
        Match *match = subEx->elementAt(i);
        int matchStart = match->getStartPos(0);

        if(matchStart > tokStart)
            result.append(matchString + tokStart, matchStart - tokStart);
        subInExp(replaceString, matchString, match, result, manager);

        tokStart = match->getEndPos(0);
    }

    if(end > (XMLSize_t)tokStart)
        result.append(matchString + tokStart, end - tokStart);

    return XMLString::replicate(result.getRawBuffer(), manager);
}

/*
 * Helper for Replace. This method prepares the replacement string by substituting
 * in actual values for parenthesized sub expressions.
 *
 * An error will be thrown if:
 *  1) there is chBackSlash not followed by a chDollarSign or chBackSlash
 *  2) there is an unescaped chDollarSign which is not followed by a digit
 *
 */
void RegularExpression::subInExp(const XMLCh* const repString,
                                 const XMLCh* const origString,
                                 const Match* subEx,
                                 XMLBuffer &result,
                                 MemoryManager* const manager) const
{
    int numSubExp = subEx->getNoGroups() - 1;

    for(const XMLCh *ptr = repString; *ptr != chNull; ++ptr) {
        if(*ptr == chDollarSign) {
            ++ptr;

            // check that after the $ is a digit
            if(!XMLString::isDigit(*ptr)) {
                // invalid replace string - $ must be followed by a digit
                ThrowXMLwithMemMgr(RuntimeException, XMLExcepts::Regex_InvalidRepPattern, manager);
            }

            int index = *ptr - chDigit_0;

            const XMLCh *dig = ptr + 1;
            while(XMLString::isDigit(*dig)) {
                int newIndex = index * 10 + (*dig - chDigit_0);
                if(newIndex > numSubExp) break;

                index = newIndex;
                ptr = dig;
                ++dig;
            }

            // now check that the index is legal
            if(index <= numSubExp) {
                int start = subEx->getStartPos(index);
                int end = subEx->getEndPos(index);

                // now copy the substring into the new string
                if(start < end) {
                    result.append(origString + start, end - start);
                }
            }

        } else {
            if(*ptr == chBackSlash) {
                ++ptr;

                // if you have a slash and then a character that's not a $ or /,
                // then it's an invalid replace string
                if(*ptr != chDollarSign && *ptr != chBackSlash) {
                    ThrowXMLwithMemMgr(RuntimeException, XMLExcepts::Regex_InvalidRepPattern, manager);
                }
            }

            result.append(*ptr);
        }
    }
}


// -----------------------------------------------------------------------
//  Static initialize and cleanup methods
// -----------------------------------------------------------------------
void
XMLInitializer::initializeRegularExpression()
{
    RegularExpression::staticInitialize(XMLPlatformUtils::fgMemoryManager);
}

void
XMLInitializer::terminateRegularExpression()
{
    RegularExpression::staticCleanup();
}

void
RegularExpression::staticInitialize(MemoryManager* memoryManager)
{
    fWordRange = TokenFactory::staticGetRange(fgUniIsWord, false);

    if (fWordRange == 0)
        ThrowXMLwithMemMgr1(RuntimeException, XMLExcepts::Regex_RangeTokenGetError, fgUniIsWord, memoryManager);
}

// ---------------------------------------------------------------------------
//  RegularExpression: Helpers methods
// ---------------------------------------------------------------------------
int RegularExpression::getOptionValue(const XMLCh ch) {

    int ret = 0;

    switch (ch) {

        case chLatin_i:
            ret = IGNORE_CASE;
            break;
        case chLatin_m:
            ret = MULTIPLE_LINE;
            break;
        case chLatin_s:
            ret = SINGLE_LINE;
            break;
        case chLatin_x:
            ret = EXTENDED_COMMENT;
            break;
        case chLatin_F:
            ret = PROHIBIT_FIXED_STRING_OPTIMIZATION;
            break;
        case chLatin_H:
            ret = PROHIBIT_HEAD_CHARACTER_OPTIMIZATION;
            break;
        case chLatin_X:
            ret = XMLSCHEMA_MODE;
            break;
        default:
            break;
    }

    return ret;
}

struct RE_RuntimeContext {
    const Op    *op_;
    XMLSize_t   offs_;

    RE_RuntimeContext(const Op *op, XMLSize_t offs) : op_(op), offs_(offs) { }
};

int RegularExpression::match(Context* const context, const Op* const operations,
                             XMLSize_t offset) const
{
    ValueStackOf<RE_RuntimeContext>* opStack=NULL;
    Janitor<ValueStackOf<RE_RuntimeContext> > janStack(NULL);
    if(context->fLimit > 256)
    {
        opStack=new ValueStackOf<RE_RuntimeContext>(16, context->fMemoryManager);
        janStack.reset(opStack);
    }
    const Op* tmpOp = operations;
    bool ignoreCase = isSet(context->fOptions, IGNORE_CASE);
    int doReturn;

    while (tmpOp != 0) {
        // no one wants to return -5, only -1, 0, and greater
        doReturn = -5;

        if (offset > context->fLimit || offset < context->fStart)
            doReturn = -1;
        else
        {
            switch(tmpOp->getOpType()) {
                case Op::O_CHAR:
                    if (!matchChar(context, tmpOp->getData(), offset, ignoreCase))
                        doReturn = -1;
                    else
                        tmpOp = tmpOp->getNextOp();
                    break;
                case Op::O_DOT:
                    if (!matchDot(context, offset))
                        doReturn = -1;
                    else
                        tmpOp = tmpOp->getNextOp();
                    break;
                case Op::O_RANGE:
                case Op::O_NRANGE:
                    if (!matchRange(context, tmpOp, offset, ignoreCase))
                        doReturn = -1;
                    else
                        tmpOp = tmpOp->getNextOp();
                    break;
                case Op::O_ANCHOR:
                    if (!matchAnchor(context, tmpOp->getData(), offset))
                        doReturn = -1;
                    else
                        tmpOp = tmpOp->getNextOp();
                    break;
                case Op::O_BACKREFERENCE:
                    if (!matchBackReference(context, tmpOp->getData(), offset,
                                            ignoreCase))
                        doReturn = -1;
                    else
                        tmpOp = tmpOp->getNextOp();
                    break;
                case Op::O_STRING:
                    if (!matchString(context, tmpOp->getLiteral(), offset, ignoreCase))
                        doReturn = -1;
                    else
                        tmpOp = tmpOp->getNextOp();
                    break;
                case Op::O_FINITE_CLOSURE:
                {
                    XMLInt32 id = tmpOp->getData();
                    // if id is not -1, it's a closure with a child token having a minumum length,
                    // where id is the index of the fOffsets array where its status is stored
                    if (id >= 0) {
                        int prevOffset = context->fOffsets[id];
                        if (prevOffset < 0 || prevOffset != (int)offset) {
                            context->fOffsets[id] = (int)offset;
                        }
                        else {
                            // the status didn't change, we haven't found other copies; move on to the next match
                            context->fOffsets[id] = -1;
                            tmpOp = tmpOp->getNextOp();
                            break;
                        }
                    }

                    // match the subitems until they do
                    int ret;
                    while((ret = match(context, tmpOp->getChild(), offset)) != -1)
                    {
                        if(offset == (XMLSize_t)ret)
                            break;
                        offset = ret;
                    }

                    if (id >= 0) {
                        // loop has ended, reset the status for this closure
                        context->fOffsets[id] = -1;
                    }
                    tmpOp = tmpOp->getNextOp();
                }
                break;
                case Op::O_FINITE_NONGREEDYCLOSURE:
                {
                    int ret = match(context,tmpOp->getNextOp(),offset);
                    if (ret >= 0)
                        doReturn = ret;
                    else
                    {
                        // match the subitems until they do
                        int ret;
                        while((ret = match(context, tmpOp->getChild(), offset)) != -1)
                        {
                            if(offset == (XMLSize_t)ret)
                                break;
                            offset = ret;
                        }
                        tmpOp = tmpOp->getNextOp();
                    }
                }
                break;
                case Op::O_CLOSURE:
                {
                    XMLInt32 id = tmpOp->getData();
                    // if id is not -1, it's a closure with a child token having a minumum length,
                    // where id is the index of the fOffsets array where its status is stored
                    if (id >= 0) {
                        int prevOffset = context->fOffsets[id];
                        if (prevOffset < 0 || prevOffset != (int)offset) {
                            context->fOffsets[id] = (int)offset;
                        }
                        else {
                            // the status didn't change, we haven't found other copies; move on to the next match
                            context->fOffsets[id] = -1;
                            tmpOp = tmpOp->getNextOp();
                            break;
                        }
                    }

                    if(opStack!=NULL)
                    {
                        opStack->push(RE_RuntimeContext(tmpOp, offset));
                        tmpOp = tmpOp->getChild();
                    }
                    else
                    {
                        int ret = match(context, tmpOp->getChild(), offset);
                        if (id >= 0) {
                            context->fOffsets[id] = -1;
                        }
                        if (ret >= 0)
                            doReturn = ret;
                        else
                            tmpOp = tmpOp->getNextOp();
                    }
                }
                break;
                case Op::O_QUESTION:
                {
                    if(opStack!=NULL)
                    {
                        opStack->push(RE_RuntimeContext(tmpOp, offset));
                        tmpOp = tmpOp->getChild();
                    }
                    else
                    {
                        int ret = match(context, tmpOp->getChild(), offset);
                        if (ret >= 0)
                            doReturn = ret;
                        else
                            tmpOp = tmpOp->getNextOp();
                    }
                }
                break;
                case Op::O_NONGREEDYCLOSURE:
                case Op::O_NONGREEDYQUESTION:
                {
                    int ret = match(context,tmpOp->getNextOp(),offset);
                    if (ret >= 0)
                        doReturn = ret;
                    else
                        tmpOp = tmpOp->getChild();
                }
                break;
                case Op::O_UNION:
                    doReturn = matchUnion(context, tmpOp, offset);
                    break;
                case Op::O_CAPTURE:
                    if (context->fMatch != 0 && tmpOp->getData() != 0)
                        doReturn = matchCapture(context, tmpOp, offset);
                    else
                        tmpOp = tmpOp->getNextOp();
                    break;
            }
        }
        if (doReturn != -5) {
            if (opStack==NULL || opStack->size() == 0)
                return doReturn;
            RE_RuntimeContext ctx = opStack->pop();
            tmpOp = ctx.op_;
            offset = ctx.offs_;
            if (tmpOp->getOpType() == Op::O_CLOSURE) {
                XMLInt32 id = tmpOp->getData();
                if (id >= 0) {
                    // loop has ended, reset the status for this closure
                    context->fOffsets[id] = -1;
                }
            }
            if (tmpOp->getOpType() == Op::O_CLOSURE || tmpOp->getOpType() == Op::O_QUESTION) {
                if (doReturn >= 0)
                    return doReturn;
            }
            tmpOp = tmpOp->getNextOp();
        }
    }

    return (int)offset;
}

bool RegularExpression::matchChar(Context* const context,
                                  const XMLInt32 ch, XMLSize_t& offset,
                                  const bool ignoreCase) const
{
    if (offset >= context->fLimit)
        return false;

    XMLInt32 strCh = 0;

    if (!context->nextCh(strCh, offset))
        return false;

    bool match = ignoreCase ? matchIgnoreCase(ch, strCh)
                            : (ch == strCh);
    if (!match)
        return false;

    ++offset;

    return true;
}

bool RegularExpression::matchDot(Context* const context, XMLSize_t& offset) const
{
    if (offset >= context->fLimit)
        return false;

    XMLInt32 strCh = 0;

    if (!context->nextCh(strCh, offset))
        return false;

    if (!isSet(context->fOptions, SINGLE_LINE)) {

        if (RegxUtil::isEOLChar(strCh))
            return false;
    }

    ++offset;
    return true;
}

bool RegularExpression::matchRange(Context* const context, const Op* const op,
                                   XMLSize_t& offset, const bool ignoreCase) const
{
    if (offset >= context->fLimit)
        return false;

    XMLInt32 strCh = 0;

    if (!context->nextCh(strCh, offset))
        return false;

    RangeToken* tok = (RangeToken *) op->getToken();
    bool match = false;

    if (ignoreCase) {
        tok = tok->getCaseInsensitiveToken(fTokenFactory);
    }

    match = tok->match(strCh);

    if (!match)
        return false;

    ++offset;
    return true;
}

bool RegularExpression::matchAnchor(Context* const context, const XMLInt32 ch,
                                    const XMLSize_t offset) const
{
    switch ((XMLCh) ch) {
    case chDollarSign:
        if (isSet(context->fOptions, MULTIPLE_LINE)) {
            if (!(offset == context->fLimit || (offset < context->fLimit
                && RegxUtil::isEOLChar(context->fString[offset]))))
                return false;
        }
        else {

            if (!(offset == context->fLimit
                || (offset+1 == context->fLimit
                    && RegxUtil::isEOLChar(context->fString[offset]))
                || (offset+2 == context->fLimit
                    && context->fString[offset] == chCR
                    && context->fString[offset+1] == chLF)))
                return false;
        }
        break;
    case chCaret:
        if (!isSet(context->fOptions, MULTIPLE_LINE)) {

            if (offset != context->fStart)
                return false;
        }
        else {

            if (!(offset == context->fStart || (offset > context->fStart
                && RegxUtil::isEOLChar(context->fString[offset-1]))))
                return false;
        }
        break;
    }

    return true;
}

bool RegularExpression::matchBackReference(Context* const context,
                                           const XMLInt32 refNo, XMLSize_t& offset,
                                           const bool ignoreCase) const
{
    if (refNo <=0 || refNo >= fNoGroups)
        ThrowXMLwithMemMgr(IllegalArgumentException, XMLExcepts::Regex_BadRefNo, context->fMemoryManager);

    // If the group we're matching against wasn't matched,
    // the back reference matches the empty string
    if (context->fMatch->getStartPos(refNo) < 0 || context->fMatch->getEndPos(refNo) < 0)
        return true;

    int start = context->fMatch->getStartPos(refNo);
    int length = context->fMatch->getEndPos(refNo) - start;

    if (int(context->fLimit - offset) < length)
        return false;

    bool match = ignoreCase ? XMLString::regionIMatches(context->fString,(int)offset,
                                                        context->fString,start,length)
                            : XMLString::regionMatches(context->fString, (int)offset,
                                                       context->fString, start,length);

    if (match) offset += length;
    return match;
}

bool RegularExpression::matchString(Context* const context,
                                    const XMLCh* const literal, XMLSize_t& offset,
                                    const bool ignoreCase) const
{
    XMLSize_t length = XMLString::stringLen(literal);

    if (context->fLimit - offset < length)
        return false;

    bool match = ignoreCase ? XMLString::regionIMatches(context->fString, (int)offset,
                                                        literal, 0, length)
                            : XMLString::regionMatches(context->fString, (int)offset,
                                                       literal, 0, length);
    if (match) offset += length;
    return match;
}

int RegularExpression::matchCapture(Context* const context, const Op* const op,
                                    XMLSize_t offset) const
{
    // No check is made for nullness of fMatch as the function is only called if
    // fMatch is not null.
    XMLInt32 index = op->getData();
    int save = (index > 0) ? context->fMatch->getStartPos(index)
                           : context->fMatch->getEndPos(-index);

    if (index > 0) {
        context->fMatch->setStartPos(index, (int)offset);
        int ret = match(context, op->getNextOp(), offset);
        if (ret < 0)
            context->fMatch->setStartPos(index, save);
        return ret;
    }

    context->fMatch->setEndPos(-index, (int)offset);
    int ret = match(context, op->getNextOp(), offset);
    if (ret < 0)
        context->fMatch->setEndPos(-index, save);
    return ret;
}

int RegularExpression::matchUnion(Context* const context,
                                   const Op* const op, XMLSize_t offset) const
{
    XMLSize_t opSize = op->getSize();

    Context bestResultContext;
    int bestResult=-1;
    for(XMLSize_t i=0; i < opSize; i++) {
        Context tmpContext(context);
        int ret = match(&tmpContext, op->elementAt(i), offset);
        if (ret >= 0 && (XMLSize_t)ret <= context->fLimit && ret>bestResult)
        {
            bestResult=ret;
            bestResultContext=tmpContext;
            // exit early, if we reached the end of the string
            if((XMLSize_t)ret == context->fLimit)
                break;
        }
    }
    if(bestResult!=-1)
        *context=bestResultContext;
    return bestResult;
}


int RegularExpression::parseOptions(const XMLCh* const options)
{

    if (options == 0)
        return 0;

    int opts = 0;
    XMLSize_t length = XMLString::stringLen(options);

    for (XMLSize_t i=0; i < length; i++) {

        int v = getOptionValue(options[i]);

        if (v == 0)
            ThrowXMLwithMemMgr1(ParseException, XMLExcepts::Regex_UnknownOption, options, fMemoryManager);

        opts |= v;
    }

    return opts;
}

void RegularExpression::compile(const Token* const token) {

    if (fOperations != 0)
        return;

    fNoClosures = 0;
    fOperations = compile(token, 0, false);
}

Op* RegularExpression::compile(const Token* const token, Op* const next,
                               const bool reverse)
{

    Op* ret = 0;

    const Token::tokType tokenType = token->getTokenType();

    switch(tokenType) {
    case Token::T_DOT:
        ret = fOpFactory.createDotOp();
        ret->setNextOp(next);
        break;
    case Token::T_CHAR:
        ret = fOpFactory.createCharOp(token->getChar());
        ret->setNextOp(next);
        break;
    case Token::T_ANCHOR:
        ret = fOpFactory.createAnchorOp(token->getChar());
        ret->setNextOp(next);
        break;
    case Token::T_RANGE:
    case Token::T_NRANGE:
        ret = fOpFactory.createRangeOp(token);
        ret->setNextOp(next);
        break;
    case Token::T_STRING:
        ret = fOpFactory.createStringOp(token->getString());
        ret->setNextOp(next);
        break;
    case Token::T_BACKREFERENCE:
        ret = fOpFactory.createBackReferenceOp(token->getReferenceNo());
        ret->setNextOp(next);
        break;
    case Token::T_EMPTY:
        ret = next;
        break;
    case Token::T_CONCAT:
        ret = compileConcat(token, next, reverse);
        break;
    case Token::T_UNION:
        ret = compileUnion(token, next, reverse);
        break;
    case Token::T_CLOSURE:
    case Token::T_NONGREEDYCLOSURE:
        ret = compileClosure(token, next, reverse, tokenType);
        break;
    case Token::T_PAREN:
        ret = compileParenthesis(token, next, reverse);
        break;
    default:
        ThrowXMLwithMemMgr(RuntimeException, XMLExcepts::Regex_UnknownTokenType, fMemoryManager);
        break; // this line to be deleted
    }

    return ret;
}

/*
 * Prepares for matching. This method is called during construction.
 */
void RegularExpression::prepare() {

    compile(fTokenTree);

    fMinLength = fTokenTree->getMinLength();
    fFirstChar = 0;

    if (!isSet(fOptions, PROHIBIT_HEAD_CHARACTER_OPTIMIZATION) &&
        !isSet(fOptions, XMLSCHEMA_MODE))                            {

        RangeToken* rangeTok = fTokenFactory->createRange();
        Token::firstCharacterOptions result = fTokenTree->analyzeFirstCharacter(rangeTok, fOptions, fTokenFactory);

        if (result == Token::FC_TERMINAL) {

            rangeTok->compactRanges();
            fFirstChar = rangeTok;
        }

        rangeTok->createMap();

        if (isSet(fOptions, IGNORE_CASE))
        {
            rangeTok->getCaseInsensitiveToken(fTokenFactory);
        }
    }

    if (fOperations != 0 && fOperations->getNextOp() == 0 &&
        (fOperations->getOpType() == Op::O_STRING ||
         fOperations->getOpType() == Op::O_CHAR) &&
         !isSet(fOptions, IGNORE_CASE) )                      {

        fFixedStringOnly = true;

        if (fOperations->getOpType() == Op::O_STRING) {
            fMemoryManager->deallocate(fFixedString);//delete [] fFixedString;
            fFixedString = XMLString::replicate(fOperations->getLiteral(), fMemoryManager);
        }
        else{

            XMLInt32 ch = fOperations->getData();

            if ( ch >= 0x10000) { // add as constant
                fMemoryManager->deallocate(fFixedString);//delete [] fFixedString;
                fFixedString = RegxUtil::decomposeToSurrogates(ch, fMemoryManager);
            }
            else {

                XMLCh* dummyStr = (XMLCh*) fMemoryManager->allocate(2 * sizeof(XMLCh));//new XMLCh[2];
                dummyStr[0] = (XMLCh) fOperations->getData();
                dummyStr[1] = chNull;
                fMemoryManager->deallocate(fFixedString);//delete [] fFixedString;
                fFixedString = dummyStr;
            }
        }

        fBMPattern = new (fMemoryManager) BMPattern(fFixedString, 256,
                                                    isSet(fOptions, IGNORE_CASE), fMemoryManager);
    }
    else if (!isSet(fOptions, XMLSCHEMA_MODE) &&
             !isSet(fOptions, PROHIBIT_FIXED_STRING_OPTIMIZATION) &&
             !isSet(fOptions, IGNORE_CASE)) {

        int fixedOpts = 0;
        Token* tok = fTokenTree->findFixedString(fOptions, fixedOpts);

        fMemoryManager->deallocate(fFixedString);//delete [] fFixedString;

        fFixedString = (tok == 0) ? 0
            : XMLString::replicate(tok->getString(), fMemoryManager);

        if (fFixedString != 0 && XMLString::stringLen(fFixedString) < 2) {

            fMemoryManager->deallocate(fFixedString);//delete [] fFixedString;
            fFixedString = 0;
        }

        if (fFixedString != 0) {

            fBMPattern = new (fMemoryManager) BMPattern(fFixedString, 256,
                                                        isSet(fixedOpts, IGNORE_CASE), fMemoryManager);
        }
    }
}

bool RegularExpression::doTokenOverlap(const Op* op, Token* token)
{
    if(op->getOpType()==Op::O_RANGE)
    {
        RangeToken* t1=(RangeToken*)op->getToken();
        switch(token->getTokenType())
        {
        case Token::T_CHAR:
            return t1->match(token->getChar());
        case Token::T_STRING:
            return t1->match(*token->getString());
        case Token::T_RANGE:
            {
                try
                {
                    RangeToken tempRange(Token::T_RANGE, fMemoryManager);
                    tempRange.mergeRanges(t1);
                    tempRange.intersectRanges((RangeToken*)token);
                    return !tempRange.empty();
                }
                catch(RuntimeException&)
                {
                }
                break;
            }
        default:
            break;
        }
        return true;
    }

    XMLInt32 ch=0;
    if(op->getOpType()==Op::O_CHAR)
        ch=op->getData();
    else if(op->getOpType()==Op::O_STRING)
        ch=*op->getLiteral();

    if(ch!=0)
    {
        switch(token->getTokenType())
        {
        case Token::T_CHAR:
            return token->getChar()==ch;
        case Token::T_STRING:
            return *token->getString()==ch;
        case Token::T_RANGE:
        case Token::T_NRANGE:
            return ((RangeToken*)token)->match(ch);
        default:
            break;
        }
    }
    // in any other case, there is the chance that they overlap
    return true;
}

XERCES_CPP_NAMESPACE_END

/**
  *    End of file RegularExpression.cpp
  */