class JsonReferenceReplacer {

    protected void performTokenReplacement(JsonNode node, JsonNodeReplacer jsonNodeReplacer, Map<String, JsonNode> parameterMap, ReferenceReplacementResult report)
    {
        final String nodeValue = node.asText();
        if (nodeValue != null)
        {
            boolean fullNodeReplace = false;
            Matcher matcher = getMatcher(nodeValue);
            // exact match (not building a composite string)
            if (matcher.matches())
            {
                String referenceName = matcher.group(REFERENCE_GROUP_NAME);
                String referencePath = matcher.group(PATH_GROUP_NAME);

                //logger.debug("Checking for token [{}] in node [{}]", translatedValue, nodeId);
                JsonNode parameterValue = getParameterValue(parameterMap, referenceName, referencePath, report);

                if (parameterValue != null)
                {
                    fullNodeReplace = true;
                    if(parameterValue.isObject()
                        || parameterValue.isArray()
                        || parameterValue.isTextual()
                        || parameterValue.isNumber()
                        || parameterValue == NullNode.getInstance())
                    {
                        jsonNodeReplacer.updateValue(parameterValue);
                    }
                }
            }
            if(!fullNodeReplace)
            {
                // look for any internal matches (composite string)
                // reset the matcher (necessary because of the .matches call above)
                matcher.reset();
                Map<String, String> replacementValues = new HashMap<>();
                while(matcher.find())
                {
                    String referenceName = matcher.group(REFERENCE_GROUP_NAME);
                    String referencePath = matcher.group(PATH_GROUP_NAME);

                    //logger.debug("Checking for token [{}] in node [{}]", translatedValue, nodeId);
                    JsonNode parameterValue = getParameterValue(parameterMap, referenceName, referencePath, report);
                    if (parameterValue != null)
                    {
                        if(!parameterValue.isValueNode())
                        {
                            report.addInvalidReference(String.format("%1$s cannot be used in composite string", referenceName));
                        }
                        else if(parameterValue.isValueNode())
                        {
                            // get the entire match so it can be replaced as necessary
                            // NOTE: if the same item is added again it is harmless
                            replacementValues.put(matcher.group(0), parameterValue.asText());
                        }
                    }
                }
                if(replacementValues.size() > 0)
                {
                    String newValue = nodeValue;
                    for(Map.Entry<String,String> kvp : replacementValues.entrySet())
                    {
                        newValue = StringUtils.replace(newValue, kvp.getKey(), kvp.getValue());
                    }
                    jsonNodeReplacer.updateValue(newValue);
                }
            }
        }
    }

    public  JsonReferenceReplacer();
    public  JsonReferenceReplacer(String referencePrefix, String referenceSeparator, String referenceSuffix, String fallbackValueSeparator);

}