com.evolveum.midpoint.prism.xnode.PrimitiveXNode.java Source code

Java tutorial

Introduction

Here is the source code for com.evolveum.midpoint.prism.xnode.PrimitiveXNode.java

Source

/*
 * Copyright (c) 2014 Evolveum
 *
 * Licensed 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.
 */
package com.evolveum.midpoint.prism.xnode;

import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;

import javax.xml.namespace.QName;

import com.evolveum.midpoint.prism.util.CloneUtil;
import com.evolveum.midpoint.prism.xml.XsdTypeMapper;
import com.evolveum.midpoint.util.logging.Trace;
import com.evolveum.midpoint.util.logging.TraceManager;

import org.apache.commons.lang.StringUtils;

import com.evolveum.midpoint.prism.Visitor;
import com.evolveum.midpoint.prism.polystring.PolyString;
import com.evolveum.midpoint.prism.xml.XmlTypeConverter;
import com.evolveum.midpoint.util.DebugUtil;
import com.evolveum.midpoint.util.DisplayableValue;
import com.evolveum.midpoint.util.PrettyPrinter;
import com.evolveum.midpoint.util.QNameUtil;
import com.evolveum.midpoint.util.exception.SchemaException;

import org.apache.commons.lang.Validate;

public class PrimitiveXNode<T> extends XNode implements Serializable {

    private static final Trace LOGGER = TraceManager.getTrace(PrimitiveXNode.class);

    /*
     * Invariants:
     *   - At most one of value-valueParser may be null.
     *   - If value is non-null, super.typeName must be non-null.
     */
    private T value;
    private ValueParser<T> valueParser;

    /**
     * If set to true then this primitive value either came from an attribute
     * or we prefer this to be represented as an attribute (if the target format
     * is capable of representing attributes)
     */
    private boolean isAttribute = false;

    public PrimitiveXNode() {
        super();
    }

    public PrimitiveXNode(T value) {
        super();
        this.value = value;
    }

    public void parseValue(QName typeName) throws SchemaException {
        Validate.notNull(typeName, "Cannot parse primitive XNode without knowing its type");
        if (valueParser != null) {
            value = valueParser.parse(typeName);
            // Necessary. It marks that the value is parsed. It also frees some memory.
            valueParser = null;
        }
    }

    public T getValue() {
        return value;
    }

    public T getParsedValue(QName typeName) throws SchemaException {
        if (!isParsed()) {
            parseValue(typeName);
        }
        return value;
    }

    public ValueParser<T> getValueParser() {
        return valueParser;
    }

    public void setValueParser(ValueParser<T> valueParser) {
        Validate.notNull(valueParser, "Value parser cannot be null");
        this.valueParser = valueParser;
        this.value = null;
    }

    public void setValue(T value, QName typeQName) {
        if (value != null) {
            if (typeQName == null) {
                // last desperate attempt to determine type name from the value type
                typeQName = XsdTypeMapper.getJavaToXsdMapping(value.getClass());
                if (typeQName == null) {
                    throw new IllegalStateException("Cannot determine type QName for a value of '" + value + "'"); // todo show only class? (security/size reasons)
                }
            }
        }
        this.setTypeQName(typeQName);
        this.value = value;
        this.valueParser = null;
    }

    public boolean isParsed() {
        return valueParser == null;
    }

    public boolean isAttribute() {
        return isAttribute;
    }

    public void setAttribute(boolean isAttribute) {
        this.isAttribute = isAttribute;
    }

    public boolean isEmpty() {
        if (!isParsed()) {
            return valueParser.isEmpty();
        }
        if (value == null) {
            return true;
        }
        if (value instanceof String) {
            return StringUtils.isBlank((String) value);
        }
        return false;
    }

    /**
     * Returns parsed value without actually changing node state from UNPARSED to PARSED
     * (if node is originally unparsed).
     *
     * Useful when we are not sure about the type name and do not want to record parsed
     * value based on wrong type name.
     */
    public T getParsedValueWithoutRecording(QName typeName) throws SchemaException {
        Validate.notNull(typeName, "typeName");
        if (isParsed()) {
            return value;
        } else {
            return valueParser.parse(typeName);
        }
    }

    /**
    * Returns a value that is correctly string-formatted according
    * to its type definition. Works properly only if definition is set.
    */
    public String getFormattedValue() {
        //      if (getTypeQName() == null) {
        //         throw new IllegalStateException("Cannot fetch formatted value if type definition is not set");
        //      }
        if (!isParsed()) {
            throw new IllegalStateException("Cannot fetch formatted value if the xnode is not parsed");
        }
        return formatValue(value);
    }

    /**
     * Returns formatted parsed value without actually changing node state from UNPARSED to PARSED
     * (if node is originally unparsed).
     *
     * Useful e.g. to serialize nodes that have a type declaration but were not parsed yet.
     *
     * Experimental. Should be thought through yet.
     *
     * @return properly formatted value
     */
    public String getGuessedFormattedValue() throws SchemaException {
        if (isParsed()) {
            return getFormattedValue();
        }
        if (getTypeQName() == null) {
            throw new IllegalStateException("Cannot fetch formatted value if type definition is not set");
        }
        T value = valueParser.parse(getTypeQName());
        return formatValue(value);
    }

    private String formatValue(T value) {
        if (value instanceof PolyString) {
            return ((PolyString) value).getOrig();
        }
        if (value instanceof QName) {
            return QNameUtil.qNameToUri((QName) value);
        }
        if (value instanceof DisplayableValue) {
            return ((DisplayableValue) value).getValue().toString();
        }

        if (value != null && value.getClass().isEnum()) {
            return value.toString();
        }

        return XmlTypeConverter.toXmlTextContent(value, null);
    }

    @Override
    public void accept(Visitor visitor) {
        visitor.visit(this);
    }

    @Override
    public String debugDump(int indent) {
        StringBuilder sb = new StringBuilder();
        DebugUtil.indentDebugDump(sb, indent);
        valueToString(sb);
        String dumpSuffix = dumpSuffix();
        if (dumpSuffix != null) {
            sb.append(dumpSuffix);
        }
        return sb.toString();
    }

    @Override
    public String getDesc() {
        return "primitive";
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder("XNode(primitive:");
        valueToString(sb);
        if (isAttribute) {
            sb.append(",attr");
        }
        sb.append(")");
        return sb.toString();
    }

    private void valueToString(StringBuilder sb) {
        if (value == null) {
            sb.append("parser ").append(valueParser);
        } else {
            sb.append(PrettyPrinter.prettyPrint(value));
            sb.append(" (").append(value.getClass()).append(")");
        }
    }

    /**
     * Returns the value represented as string - in the best format that we can.
     * There is no guarantee that the returned value will be precise.
     * This method is used as a "last instance" if everything else fails.
     * Invocation of this method will not change the state of this xnode, e.g.
     * it will NOT cause it to be parsed.
     */
    public String getStringValue() {
        if (isParsed()) {
            if (getTypeQName() != null) {
                return getFormattedValue();
            } else {
                if (value == null) {
                    return null;
                } else {
                    return value.toString();
                }
            }
        } else {
            return valueParser.getStringValue();
        }
    }

    /**
     * This method is used with conjunction with getStringValue, typically when serializing unparsed values.
     * Because the string value can represent QName or ItemPath, we have to provide relevant namespace declarations.
     *
     * Because we cannot know for sure, we are allowed to return namespace declarations that are not actually used.
     * We should minimize number of such declarations, however.
     *
     * Current implementation simply grabs all potential namespace declarations and searches
     * the xnode's string value for any 'prefix:' substrings. I'm afraid it is all we can do for now.
     *
     * THIS METHOD SHOULD BE CALLED ONLY ON EITHER UNPARSED OR EMPTY NODES.
     *
     * @return
     */
    public Map<String, String> getRelevantNamespaceDeclarations() {
        Map<String, String> retval = new HashMap<>();
        if (isEmpty()) {
            return retval;
        }
        if (valueParser == null) {
            throw new IllegalStateException(
                    "getRelevantNamespaceDeclarations called on parsed primitive XNode: " + this);
        }
        Map<String, String> candidateNamespaces = valueParser.getPotentiallyRelevantNamespaces();
        if (candidateNamespaces == null) {
            return retval;
        }
        String stringValue = getStringValue();
        if (stringValue == null) {
            return retval;
        }
        for (Map.Entry<String, String> candidateNamespace : candidateNamespaces.entrySet()) {
            String prefix = candidateNamespace.getKey();
            if (stringValue.contains(prefix + ":")) {
                retval.put(candidateNamespace.getKey(), candidateNamespace.getValue());
            }
        }
        return retval;
    }

    @Override
    public boolean equals(Object obj) {
        if (!(obj instanceof PrimitiveXNode)) {
            return false;
        }

        PrimitiveXNode other = (PrimitiveXNode) obj;
        if (other.isParsed() && isParsed()) {
            return value.equals(other.value);
        } else if (!other.isParsed() && !isParsed()) {
            // TODO consider problem with namespaces (if string value is QName/ItemPath its meaning can depend on namespace declarations that are placed outside the element)
            String thisStringVal = this.getStringValue();
            String otherStringVal = other.getStringValue();
            return thisStringVal.equals(otherStringVal);
        } else if (other.isParsed() && !isParsed()) {
            String thisStringValue = this.getStringValue();
            String otherStringValue = String.valueOf(other.value);
            return otherStringValue.equals(thisStringValue);
        } else if (!other.isParsed() && isParsed()) {
            String thisStringValue = String.valueOf(value);
            ;
            String otherStringValue = other.getStringValue();
            return thisStringValue.equals(otherStringValue);
        }

        return false;
    }

    /**
     * The basic idea of equals() is:
     *  - if parsed, compare the value;
     *  - if unparsed, compare getStringValue()
     * Therefore the hashcode is generated based on value (if parsed) or getStringValue() (if unparsed).
     */
    @Override
    public int hashCode() {
        Object objectToHash;
        if (isParsed()) {
            objectToHash = value;
        } else {
            // TODO consider problem with namespaces (if string value is QName/ItemPath its meaning can depend on namespace declarations that are placed outside the element)
            objectToHash = getStringValue();
        }
        return objectToHash != null ? objectToHash.hashCode() : 0;
    }

    PrimitiveXNode cloneInternal() {

        PrimitiveXNode clone;
        if (value != null) {
            // if we are parsed, things are much simpler
            clone = new PrimitiveXNode(CloneUtil.clone(getValue()));
        } else {
            clone = new PrimitiveXNode();
            clone.valueParser = valueParser; // for the time being we simply don't clone the valueParser
        }

        clone.isAttribute = this.isAttribute;
        clone.copyCommonAttributesFrom(this);
        return clone;
    }
}