/*   Copyright 2004 The Apache Software Foundation
 *
 *   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 org.apache.xmlbeans.impl.values;

import org.apache.xmlbeans.*;
import org.apache.xmlbeans.impl.common.*;
import org.apache.xmlbeans.impl.schema.SchemaTypeImpl;
import org.apache.xmlbeans.impl.schema.SchemaTypeVisitorImpl;
import org.apache.xmlbeans.impl.util.LongUTFDataInputStream;
import org.apache.xmlbeans.impl.util.MathUtil;
import org.apache.xmlbeans.impl.validator.Validator;
import org.w3c.dom.Node;
import org.xml.sax.ContentHandler;
import org.xml.sax.SAXException;
import org.xml.sax.ext.LexicalHandler;

import javax.xml.namespace.QName;
import javax.xml.stream.XMLStreamReader;
import java.io.*;
import java.lang.reflect.Array;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.*;
import java.util.function.Function;
import java.util.function.IntFunction;

public abstract class XmlObjectBase implements TypeStoreUser, Serializable, XmlObject, SimpleValue {
    public static final short MAJOR_VERSION_NUMBER = (short) 1; // for serialization
    public static final short MINOR_VERSION_NUMBER = (short) 1; // for serialization

    public static final short KIND_SETTERHELPER_SINGLETON = 1;
    public static final short KIND_SETTERHELPER_ARRAYITEM = 2;

    public final Object monitor() {
        if (has_store()) {
            return get_store().get_locale();
        }
        return this;
    }

    private static XmlObjectBase underlying(XmlObject obj) {
        if (obj == null) {
            return null;
        }
        if (obj instanceof XmlObjectBase) {
            return (XmlObjectBase) obj;
        }
        while (obj instanceof DelegateXmlObject) {
            obj = ((DelegateXmlObject) obj).underlyingXmlObject();
        }
        if (obj instanceof XmlObjectBase) {
            return (XmlObjectBase) obj;
        }
        throw new IllegalStateException("Non-native implementations of XmlObject should extend FilterXmlObject or implement DelegateXmlObject");
    }

    public final XmlObject copy() {
        if (preCheck()) {
            return _copy();
        } else {
            synchronized (monitor()) {
                return _copy();
            }
        }
    }

    public final XmlObject copy(XmlOptions options) {
        if (preCheck()) {
            return _copy(options);
        } else {
            synchronized (monitor()) {
                return _copy(options);
            }
        }
    }

    private boolean preCheck() {
//        if ( isImmutable() )
//            return true;
        if (has_store()) {
            return get_store().get_locale().noSync();
        }
        return false;
    }

    /**
     * Same as copy() but unsynchronized.
     * Warning: Using this method in mutithreaded environment can cause invalid states.
     */
    public final XmlObject _copy() {
        return _copy(null);
    }

    /**
     * Same as copy() but unsynchronized.
     * If Locale.COPY_USE_NEW_LOCALE is set in the options, a new locale will be created for the copy.
     * Warning: Using this method in mutithreaded environment can cause invalid states.
     */
    public final XmlObject _copy(XmlOptions xmlOptions) {
        // immutable objects don't get copied. They're immutable
        if (isImmutable()) {
            return this;
        }

        check_orphaned();

        SchemaTypeLoader stl = get_store().get_schematypeloader();

        return (XmlObject) get_store().copy(stl, schemaType(), xmlOptions);
    }

    public XmlDocumentProperties documentProperties() {
        try (XmlCursor cur = newCursorForce()) {
            return cur.documentProperties();
        }
    }

    public XMLStreamReader newXMLStreamReader() {
        return newXMLStreamReader(null);
    }

    public XMLStreamReader newXMLStreamReader(XmlOptions options) {
        try (XmlCursor cur = newCursorForce()) {
            return cur.newXMLStreamReader(makeInnerOptions(options));
        }
    }

    public InputStream newInputStream() {
        return newInputStream(null);
    }

    public InputStream newInputStream(XmlOptions options) {
        try (XmlCursor cur = newCursorForce()) {
            return cur.newInputStream(makeInnerOptions(options));
        }
    }

    public Reader newReader() {
        return newReader(null);
    }

    public Reader newReader(XmlOptions options) {
        try (XmlCursor cur = newCursorForce()) {
            return cur.newReader(makeInnerOptions(options));
        }
    }

    public Node getDomNode() {
        try (XmlCursor cur = newCursorForce()) {
            return cur.getDomNode();
        }
    }

    public Node newDomNode() {
        return newDomNode(null);
    }

    public Node newDomNode(XmlOptions options) {
        try (XmlCursor cur = newCursorForce()) {
            return cur.newDomNode(makeInnerOptions(options));
        }
    }

    public void save(ContentHandler ch, LexicalHandler lh, XmlOptions options) throws SAXException {
        try (XmlCursor cur = newCursorForce()) {
            cur.save(ch, lh, makeInnerOptions(options));
        }
    }

    public void save(File file, XmlOptions options) throws IOException {
        try (XmlCursor cur = newCursorForce()) {
            cur.save(file, makeInnerOptions(options));
        }
    }

    public void save(OutputStream os, XmlOptions options) throws IOException {
        try (XmlCursor cur = newCursorForce()) {
            cur.save(os, makeInnerOptions(options));
        }
    }

    public void save(Writer w, XmlOptions options) throws IOException {
        try (XmlCursor cur = newCursorForce()) {
            cur.save(w, makeInnerOptions(options));
        }
    }

    public void save(ContentHandler ch, LexicalHandler lh) throws SAXException {
        save(ch, lh, null);
    }

    public void save(File file) throws IOException {
        save(file, null);
    }

    public void save(OutputStream os) throws IOException {
        save(os, null);
    }

    public void save(Writer w) throws IOException {
        save(w, null);
    }

    public void dump() {
        try (XmlCursor cur = newCursorForce()) {
            cur.dump();
        }
    }

    public XmlCursor newCursorForce() {
        synchronized (monitor()) {
            return ensureStore().newCursor();
        }
    }

    private XmlObject ensureStore() {
        if ((_flags & FLAG_STORE) != 0) {
            return this;
        }

        check_dated();

        String value =
            (_flags & FLAG_NIL) != 0
                ? ""
                : compute_text(has_store() ? get_store() : null);

        XmlOptions options = new XmlOptions().setDocumentType(schemaType());

        XmlObject x = XmlObject.Factory.newInstance(options);

        try (XmlCursor c = x.newCursor()) {
            c.toNextToken();
            c.insertChars(value);
        }

        return x;
    }

    private static XmlOptions makeInnerOptions(XmlOptions options) {
        XmlOptions innerOptions = new XmlOptions(options);
        innerOptions.setSaveInner();
        return innerOptions;
    }

    public XmlCursor newCursor() {
        if ((_flags & FLAG_STORE) == 0) {
            throw new IllegalStateException("XML Value Objects cannot create cursors");
        }

        check_orphaned();

        // Note that new_cursor does not really need sync ....

        XmlLocale l = getXmlLocale();

        if (l.noSync()) {
            l.enter();
            try {
                return get_store().new_cursor();
            } finally {
                l.exit();
            }
        } else {
            synchronized (l) {
                l.enter();
                try {
                    return get_store().new_cursor();
                } finally {
                    l.exit();
                }
            }
        }

    }

    public abstract SchemaType schemaType();

    public SchemaType instanceType() {
        synchronized (monitor()) {
            return isNil() ? null : schemaType();
        }
    }

    private SchemaField schemaField() {
        SchemaType st = schemaType();
        SchemaField field;

        // First check if this field has an anonymous type
        field = st.getContainerField();

        if (field == null) {
            field = get_store().get_schema_field();
        }

        return field;
    }

    /**
     * Use _voorVc when you want to throw a ValueOutOfRangeException when
     * validating a simple type.
     */
    private static final class ValueOutOfRangeValidationContext implements ValidationContext {
        public void invalid(String message) {
            throw new XmlValueOutOfRangeException(message);
        }

        public void invalid(String code, Object[] args) {
            throw new XmlValueOutOfRangeException(code, args);
        }
    }

    /**
     * Used to supply validation context for the validate_value methods
     */
    private static final class ImmutableValueValidationContext implements ValidationContext {
        private final XmlObject _loc;
        private final Collection<XmlError> _coll;

        ImmutableValueValidationContext(Collection<XmlError> coll, XmlObject loc) {
            _coll = coll;
            _loc = loc;
        }

        public void invalid(String message) {
            _coll.add(XmlError.forObject(message, _loc));
        }

        public void invalid(String code, Object[] args) {
            _coll.add(XmlError.forObject(code, args, _loc));
        }
    }

    public static final ValidationContext _voorVc = new ValueOutOfRangeValidationContext();

    public boolean validate() {
        return validate(null);
    }

    public boolean validate(XmlOptions options) {
        if ((_flags & FLAG_STORE) == 0) {
            if ((_flags & FLAG_IMMUTABLE) != 0) {
                return validate_immutable(options);
            }

            throw new IllegalStateException(
                "XML objects with no underlying store cannot be validated");
        }

        synchronized (monitor()) {
            if ((_flags & FLAG_ORPHANED) != 0) {
                throw new XmlValueDisconnectedException();
            }

            SchemaField field = schemaField();
            SchemaType type = schemaType();

            TypeStore typeStore = get_store();

            Validator validator =
                new Validator(
                    type, field, typeStore.get_schematypeloader(), options, null);

            typeStore.validate(validator);

            return validator.isValid();
        }
    }

    private boolean validate_immutable(XmlOptions options) {
        Collection<XmlError> errorListener = options == null ? null : options.getErrorListener();
        XmlErrorWatcher watcher = new XmlErrorWatcher(errorListener);
        if (!(schemaType().isSimpleType() || options != null &&
                                             options.isValidateTextOnly())) {
            // cannot have any required attributes or elements
            SchemaProperty[] properties = schemaType().getProperties();
            for (SchemaProperty property : properties) {
                if (property.getMinOccurs().signum() > 0) {
                    // KHK: error code?
                    if (property.isAttribute()) {
                        watcher.add(XmlError.forObject(XmlErrorCodes.ELEM_COMPLEX_TYPE_LOCALLY_VALID$MISSING_REQUIRED_ATTRIBUTE, new Object[]{QNameHelper.pretty(property.getName()),}, this));
                    } else {
                        watcher.add(XmlError.forObject(XmlErrorCodes.ELEM_COMPLEX_TYPE_LOCALLY_VALID$MISSING_ELEMENT, new Object[]{property.getMinOccurs(), QNameHelper.pretty(property.getName()),}, this));
                    }
                }
            }

            if (schemaType().getContentType() != SchemaType.SIMPLE_CONTENT) {
                return !watcher.hasError(); // don't validate non-simple-content
            }
        }

        String text = (String) _textsource;
        if (text == null) {
            text = "";
        }
        validate_simpleval(text, new ImmutableValueValidationContext(watcher, this));
        return !watcher.hasError();
    }

    protected void validate_simpleval(String lexical, ValidationContext ctx) {
    }

    private static XmlObject[] _typedArray(XmlObject[] input) {
        if (input.length == 0) {
            return input;
        }
        SchemaType commonType = input[0].schemaType();
        if (commonType.equals(XmlObject.type) || commonType.isNoType()) {
            return input;
        }
        for (int i = 1; i < input.length; i++) {
            if (input[i].schemaType().isNoType()) {
                return input;
            }
            commonType = commonType.getCommonBaseType(input[i].schemaType());
            if (commonType.equals(XmlObject.type)) {
                return input;
            }
        }
        Class<? extends XmlObject> desiredClass = commonType.getJavaClass();
        while (desiredClass == null) {
            commonType = commonType.getBaseType();
            if (XmlObject.type.equals(commonType)) {
                return input;
            }
            desiredClass = commonType.getJavaClass();
        }

        XmlObject[] result = (XmlObject[]) Array.newInstance(desiredClass, input.length);
        System.arraycopy(input, 0, result, 0, input.length);
        return result;
    }

    public XmlObject[] selectPath(String path) {
        return selectPath(path, null);
    }

    public XmlObject[] selectPath(String path, XmlOptions options) {
        XmlObject[] selections;

        // all user-level code; doesn't need to be synchronized


        try (XmlCursor c = newCursor()) {
            if (c == null) {
                throw new XmlValueDisconnectedException();
            }

            c.selectPath(path, options);

            if (!c.hasNextSelection()) {
                selections = EMPTY_RESULT;
            } else {
                selections = new XmlObject[c.getSelectionCount()];

                for (int i = 0; c.toNextSelection(); i++) {
                    if ((selections[i] = c.getObject()) == null) {
                        if (!c.toParent() || (selections[i] = c.getObject()) == null) {
                            throw
                                new XmlRuntimeException(
                                    "Path must select only elements " +
                                    "and attributes");
                        }
                    }
                }
            }
        }

        return _typedArray(selections);
    }

    public XmlObject[] execQuery(String path) {
        return execQuery(path, null);
    }

    public XmlObject[] execQuery(String queryExpr, XmlOptions options) {
        synchronized (monitor()) {
            TypeStore typeStore = get_store();

            if (typeStore == null) {
                throw
                    new XmlRuntimeException(
                        "Cannot do XQuery on XML Value Objects");
            }
            return _typedArray(typeStore.exec_query(queryExpr, options));
        }
    }

    public XmlObject changeType(SchemaType type) {
        if (type == null) {
            throw new IllegalArgumentException("Invalid type (null)");
        }

        if ((_flags & FLAG_STORE) == 0) {
            throw
                new IllegalStateException(
                    "XML Value Objects cannot have thier type changed");
        }

        synchronized (monitor()) {
            check_orphaned();
            return (XmlObject) get_store().change_type(type);
        }
    }

    public XmlObject substitute(QName name, SchemaType type) {
        if (name == null) {
            throw new IllegalArgumentException("Invalid name (null)");
        }

        if (type == null) {
            throw new IllegalArgumentException("Invalid type (null)");
        }

        if ((_flags & FLAG_STORE) == 0) {
            throw
                new IllegalStateException(
                    "XML Value Objects cannot be used with substitution");
        }

        synchronized (monitor()) {
            check_orphaned();
            return (XmlObject) get_store().substitute(name, type);
        }
    }

    private int _flags;
    private Object _textsource;

    protected XmlObjectBase() {
        _flags = FLAG_NILLABLE | FLAG_NIL;
    }

    public void init_flags(SchemaProperty prop) {
        if (prop == null) {
            return;
        }

        if (prop.hasDefault() == SchemaProperty.VARIABLE ||
            prop.hasFixed() == SchemaProperty.VARIABLE ||
            prop.hasNillable() == SchemaProperty.VARIABLE) {
            return;
        }

        _flags &= ~FLAGS_ELEMENT;
        _flags |=
            (prop.hasDefault() == SchemaProperty.NEVER ? 0 : TypeStore.HASDEFAULT) |
            (prop.hasFixed() == SchemaProperty.NEVER ? 0 : TypeStore.FIXED) |
            (prop.hasNillable() == SchemaProperty.NEVER ? 0 : TypeStore.NILLABLE) |
            (FLAG_NOT_VARIABLE);
    }

    private static final int FLAG_NILLABLE = TypeStore.NILLABLE;
    private static final int FLAG_HASDEFAULT = TypeStore.HASDEFAULT;
    private static final int FLAG_FIXED = TypeStore.FIXED;
    private static final int FLAG_ATTRIBUTE = 8;
    private static final int FLAG_STORE = 16;
    private static final int FLAG_VALUE_DATED = 32;
    private static final int FLAG_NIL = 64;
    private static final int FLAG_NIL_DATED = 128;
    private static final int FLAG_ISDEFAULT = 256;
    private static final int FLAG_ELEMENT_DATED = 512;
    private static final int FLAG_SETTINGDEFAULT = 1024;
    private static final int FLAG_ORPHANED = 2048;
    private static final int FLAG_IMMUTABLE = 4096;
    private static final int FLAG_COMPLEXTYPE = 8192;
    private static final int FLAG_COMPLEXCONTENT = 16384;
    private static final int FLAG_NOT_VARIABLE = 32768;
    private static final int FLAG_VALIDATE_ON_SET = 65536;


    /**
     * The three dated flags are always stacked:
     * FLAG_ELEMENT_DATED implies FLAG_NIL_DATED is set
     * FLAG_NIL_DATED implies FLAG_TEXT_DATED is set.
     * checkers work on the flags from top to bottom.
     */
    private static final int FLAGS_DATED =
        FLAG_VALUE_DATED | FLAG_NIL_DATED | FLAG_ELEMENT_DATED;

    /**
     * The three element status flags have one interrlationshiop:
     * FLAG_FIXED implies FLAG_HASDEFAULT is set.
     * These flags are used when setting nils, defaults, strings.
     * Since an initial get implies setting from text, they're
     * also used during getting.
     */
    private static final int FLAGS_ELEMENT =
        FLAG_NILLABLE | FLAG_FIXED | FLAG_HASDEFAULT;


    /**
     * Called by restriction subclasses within their constructors to enable
     * complex type support.
     */
    protected void initComplexType(boolean complexType, boolean complexContent) {
        _flags |= (complexType ? FLAG_COMPLEXTYPE : 0) |
                  (complexContent ? FLAG_COMPLEXCONTENT : 0);
    }

    protected boolean _isComplexType() {
        return (_flags & FLAG_COMPLEXTYPE) != 0;
    }

    protected boolean _isComplexContent() {
        return (_flags & FLAG_COMPLEXCONTENT) != 0;
    }

    public void setValidateOnSet() {
        _flags |= FLAG_VALIDATE_ON_SET;
    }

    protected boolean _validateOnSet() {
        return (_flags & FLAG_VALIDATE_ON_SET) != 0;
    }

    /**
     * True if the value is nilled.
     */
    public final boolean isNil() {
        synchronized (monitor()) {
            check_dated();
            return ((_flags & FLAG_NIL) != 0);
        }
    }

    /**
     * True if the value is fixed.
     */
    public final boolean isFixed() {
        check_element_dated();
        return ((_flags & FLAG_FIXED) != 0);
    }

    /**
     * True if the value is allowed to be nil.
     */
    public final boolean isNillable() {
        check_element_dated();
        return ((_flags & FLAG_NILLABLE) != 0);
    }

    /**
     * True if the value is currently defaulted.
     */
    public final boolean isDefaultable() {
        check_element_dated();
        return ((_flags & FLAG_HASDEFAULT) != 0);
    }

    /**
     * True if the value is currently defaulted.
     */
    public final boolean isDefault() {
        check_dated();
        return ((_flags & FLAG_ISDEFAULT) != 0);
    }


    /**
     * Nils the value.
     */
    public final void setNil() {
        synchronized (monitor()) {
            set_prepare();

            // if we're not nillable, throw exception on setNil(true)
            if ((_flags & FLAG_NILLABLE) == 0 &&
                (_flags & FLAG_VALIDATE_ON_SET) != 0) {
                throw new XmlValueNotNillableException();
            }

            // the implementation should zero the value to reflect nil
            set_nil();

            // set the nil flag
            _flags |= FLAG_NIL;

            // ordinary commit except no clearing of nil flag
            if ((_flags & FLAG_STORE) != 0) {
                get_store().invalidate_text();
                _flags &= ~FLAGS_DATED;
                get_store().invalidate_nil();
            } else {
                _textsource = null;
            }
        }
    }

    /**
     * Used for situations where these flags must be passed on to
     * chained values. (See XmlAnySimpleType (allSimpleValue), union
     * implementations).
     */
    protected int elementFlags() {
        check_element_dated();
        return (_flags & FLAGS_ELEMENT);
    }

    /**
     * Used to make a free-standing xml simple value instance immutable.
     * This is a one-way street, and it is illegal to attempt to make a
     * value that is embedded in an xml document immutable.
     * <p>
     * Once a value is marked as immutable, it is illegal to call setters
     * of any kind.
     */
    public void setImmutable() {
        if ((_flags & (FLAG_IMMUTABLE | FLAG_STORE)) != 0) {
            throw new IllegalStateException();
        }

        _flags |= FLAG_IMMUTABLE;
    }

    /**
     * Is this instance an immutable value?
     */
    public boolean isImmutable() {
        return (_flags & FLAG_IMMUTABLE) != 0;
    }


    // TEXTUSER implementation

    /**
     * Called to initialize the TypeStore associated with this XmlObject
     * implementation. If not called, this is a free-floating value holder.
     * <p>
     * When a value is first attached, it is put in a completely invalidated
     * state.
     */
    public final void attach_store(TypeStore store) {
        _textsource = store;
        if ((_flags & FLAG_IMMUTABLE) != 0) {
            throw new IllegalStateException();
        }
        _flags |= FLAG_STORE | FLAG_VALUE_DATED | FLAG_NIL_DATED | FLAG_ELEMENT_DATED;

        if (store.is_attribute()) {
            _flags |= FLAG_ATTRIBUTE;
        }

        if (store.validate_on_set()) {
            _flags |= FLAG_VALIDATE_ON_SET;
        }
    }

    /**
     * Called by a TypeStore to indicate that the text has been
     * invalidated and should be fetched next time the value is
     * needed.
     */
    public final void invalidate_value() {
        assert ((_flags & FLAG_STORE) != 0);
        _flags |= FLAG_VALUE_DATED;
    }

    public final boolean uses_invalidate_value() {
        SchemaType type = schemaType();
        return type.isSimpleType() || type.getContentType() == SchemaType.SIMPLE_CONTENT;
    }

    /**
     * Called by a TypeStore to indicate that the xsi:nil attribute
     * on the containing element (and possibly the text) has been
     * invalidated and both should be consulted next time the value
     * is needed.
     */
    public final void invalidate_nilvalue() {
        assert ((_flags & FLAG_STORE) != 0);
        _flags |= FLAG_VALUE_DATED | FLAG_NIL_DATED;
    }

    /**
     * Called by a TypeStore to indicate that the element's default
     * value, nillability, fixedness, etc, may have changed by
     * virtue of the element order changing (and xsi:nil and the
     * text may have changed too); so the store should be consulted
     * next time any setter or getter is called.
     */
    public final void invalidate_element_order() {
        assert ((_flags & FLAG_STORE) != 0);
        _flags |= FLAG_VALUE_DATED | FLAG_NIL_DATED | FLAG_ELEMENT_DATED;
    }

    /**
     * Used by the ComplexTypeImpl subclass to get direct access
     * to the store.
     */
    public final TypeStore get_store() {
        assert ((_flags & FLAG_STORE) != 0);
        return (TypeStore) _textsource;
    }

    public final XmlLocale getXmlLocale() {
        return get_store().get_locale();
    }

    protected final boolean has_store() {
        return (_flags & FLAG_STORE) != 0;
    }

    /**
     * Called by a TypeStore to pull out the most reasonable
     * text value from us. This is done after we have invalidated
     * the store (typically when our value has been set).
     */
    public final String build_text(NamespaceManager nsm) {
        assert ((_flags & FLAG_STORE) != 0);
        assert ((_flags & FLAG_VALUE_DATED) == 0);
        if ((_flags & (FLAG_NIL | FLAG_ISDEFAULT)) != 0) {
            return "";
        }
        return compute_text(
            nsm == null ? has_store() ? get_store() : null : nsm);
    }

    /**
     * A store will call back on build_nil after we've called invalidate_nil
     * and it needs to know what the nil value is.
     */
    public boolean build_nil() {
        assert ((_flags & FLAG_STORE) != 0);
        assert ((_flags & FLAG_VALUE_DATED) == 0);
        return (_flags & FLAG_NIL) != 0;
    }

    /**
     * A store will call back on validate_now to force us to look at
     * the text if we're in an invalid state. We're allowed to throw
     * an exception if the text isn't valid for our type.
     */
    public void validate_now() {
        check_dated();
    }

    /**
     * A store calls back here in order to force a disconnect.
     * After this is done, the object should be considered invalid.
     * Any attempt to access or set a value should result in an
     * exception.
     * <p>
     * Note that this is how we handle deletions and xsi:type changes.
     */
    public void disconnect_store() {
        assert ((_flags & FLAG_STORE) != 0);
        _flags |= FLAGS_DATED | FLAG_ORPHANED;
        // do NOT null out _textsource, because we need it non-null for synchronization
    }

    /**
     * A typestore user can create a new TypeStoreUser instance for
     * a given element child name as long as you also pass the
     * qname contained by the xsi:type attribute, if any.
     * <p>
     * Note that we will ignore the xsiType if it turns out to be invalid.
     * <p>
     * Returns null if there is no strongly typed information for that
     * given element (which implies, recusively, no strongly typed information
     * downwards).
     */
    public TypeStoreUser create_element_user(QName eltName, QName xsiType) {
        return
            (TypeStoreUser)
                ((SchemaTypeImpl) schemaType()).createElementType(
                    eltName, xsiType, get_store().get_schematypeloader());

        /*
        SchemaTypeImpl stype = (SchemaTypeImpl)schemaType().getElementType(eltName, xsiType, get_store().get_schematypeloader());
        if (stype == null)
            return null;
        return (TypeStoreUser)stype.createUnattachedNode();
        */
    }

    /**
     * A typestore user can create a new TypeStoreUser instance for
     * a given attribute child, based on the attribute name.
     * <p>
     * Returns null if there is no strongly typed information for that
     * given attributes.
     */
    public TypeStoreUser create_attribute_user(QName attrName) {
        return (TypeStoreUser) ((SchemaTypeImpl) schemaType()).createAttributeType(attrName, get_store().get_schematypeloader());
    }

    public SchemaType get_schema_type() {
        return schemaType();
    }

    public SchemaType get_element_type(QName eltName, QName xsiType) {
        return schemaType().getElementType(
            eltName, xsiType, get_store().get_schematypeloader());
    }

    public SchemaType get_attribute_type(QName attrName) {
        return schemaType().getAttributeType(
            attrName, get_store().get_schematypeloader());
    }

    /**
     * Returns the default element text, if it's consistent. If it's
     * not consistent, returns null, and requires a visitor walk.
     * <p>
     * Also returns null if there is no default at all (although
     * that can also be discovered via get_elementflags without
     * doing a walk).
     */
    public String get_default_element_text(QName eltName) {
        assert (_isComplexContent());
        if (!_isComplexContent()) {
            throw new IllegalStateException();
        }

        SchemaProperty prop = schemaType().getElementProperty(eltName);
        if (prop == null) {
            return "";
        }
        return prop.getDefaultText();
    }

    /**
     * Returns the default attribute text for the attribute with
     * the given name, or null if no default.
     */
    public String get_default_attribute_text(QName attrName) {
        assert (_isComplexType());
        if (!_isComplexType()) {
            throw new IllegalStateException();
        }

        SchemaProperty prop = schemaType().getAttributeProperty(attrName);
        if (prop == null) {
            return "";
        }
        return prop.getDefaultText();
    }

    /**
     * Returns the elementflags, if they're consistent. If they're
     * not, returns -1, and requires a vistor walk.
     */
    public int get_elementflags(QName eltName) {
        if (!_isComplexContent()) {
            return 0;
        }

        SchemaProperty prop = schemaType().getElementProperty(eltName);
        if (prop == null) {
            return 0;
        }
        if (prop.hasDefault() == SchemaProperty.VARIABLE ||
            prop.hasFixed() == SchemaProperty.VARIABLE ||
            prop.hasNillable() == SchemaProperty.VARIABLE) {
            return -1;
        }
        return
            (prop.hasDefault() == SchemaProperty.NEVER ? 0 : TypeStore.HASDEFAULT) |
            (prop.hasFixed() == SchemaProperty.NEVER ? 0 : TypeStore.FIXED) |
            (prop.hasNillable() == SchemaProperty.NEVER ? 0 : TypeStore.NILLABLE);
    }

    /**
     * Returns the flags for the given attribute.
     */
    public int get_attributeflags(QName attrName) {
        if (!_isComplexType()) {
            return 0;
        }
        SchemaProperty prop = schemaType().getAttributeProperty(attrName);
        if (prop == null) {
            return 0;
        }
        return
            (prop.hasDefault() == SchemaProperty.NEVER ? 0 : TypeStore.HASDEFAULT) |
            (prop.hasFixed() == SchemaProperty.NEVER ? 0 : TypeStore.FIXED);
        // BUGBUG: todo: hook up required?
    }

    /**
     * Returns false if child elements are insensitive to order;
     * if it returns true, you're required to call invalidate_element_order
     * on children to the right of any child order rearrangement.
     */
    public boolean is_child_element_order_sensitive() {
        if (!_isComplexType()) {
            return false;
        }
        return schemaType().isOrderSensitive();
    }

    /**
     * Inserting a new element is always unambiguous except in one
     * situation: when adding an element after the last one with
     * that name (or the first one if there are none).
     * <p>
     * In that case, add the element at the first possible slot
     * BEFORE any element whose qname is contained in the QNameSet
     * given. (If the QNameSet is empty, that means add the new
     * element at the very end.)
     * <p>
     * If the returned QNameSet is null, treat it as if the QNameSet
     * contained all QNames, i.e., add the new element at the very
     * first position possible (adjacent to the last element of the
     * same name, or at the very first slot if it is the first elt
     * with that name).
     */
    public final QNameSet get_element_ending_delimiters(QName eltname) {
        SchemaProperty prop = schemaType().getElementProperty(eltname);
        if (prop == null) {
            return null;
        }
        return prop.getJavaSetterDelimiter();
    }

    /**
     * A typestore user can return a visitor that is used to compute
     * default text and elementflags for an arbitrary element.
     */
    public TypeStoreVisitor new_visitor() {
        if (!_isComplexContent()) {
            return null;
        }
        return new SchemaTypeVisitorImpl(schemaType().getContentModel());
    }

    public SchemaField get_attribute_field(QName attrName) {
        SchemaAttributeModel model = schemaType().getAttributeModel();
        if (model == null) {
            return null;
        }
        return model.getAttribute(attrName);
    }


    /**
     * Setting a string preserves any noncanonical literal
     * representation. This is done by storing the actual
     * string in the underlying store after checking it
     * against the primitive type for validity.
     */
    protected void set_String(String v) {
        if ((_flags & FLAG_IMMUTABLE) != 0) {
            throw new IllegalStateException();
        }

        boolean wasNilled = ((_flags & FLAG_NIL) != 0);

        // update the underlying value from the string
        String wscanon = apply_wscanon(v);
        update_from_wscanon_text(wscanon);

        // Now store the literal text immediately in the underlying
        if ((_flags & FLAG_STORE) != 0) {
            _flags &= ~FLAG_VALUE_DATED;
            if ((_flags & FLAG_SETTINGDEFAULT) == 0) {
                get_store().store_text(v);
            }
            if (wasNilled) {
                get_store().invalidate_nil();
            }
        } else {
            _textsource = v;
        }
    }

    /**
     * Update the value based on complex content.
     */
    protected void update_from_complex_content() {
        throw new XmlValueNotSupportedException("Complex content");
    }

    /**
     * Utility to update the value based on a string that
     * was passed either from the text store or from the user.
     * This function handles the cases where there is a default
     * that must be applied, and where the value must match
     * a fixed value.
     */
    private void update_from_wscanon_text(String v) {
        // Whitespace is default if this type treats this space as defaultable
        if ((_flags & FLAG_HASDEFAULT) != 0 && (_flags & FLAG_SETTINGDEFAULT) == 0) {
            // This isn't quite correct since the .equals("") test should be
            // done on the actual text, not the wscanon text
            if ((_flags & FLAG_ATTRIBUTE) == 0 && v.isEmpty()) {
                String def = get_store().compute_default_text();
                if (def == null) {
                    throw new XmlValueOutOfRangeException();
                }

                // protect against recursion with this flag
                _flags |= FLAG_SETTINGDEFAULT;
                try {
                    this.setStringValue(def);
                } finally {
                    _flags &= ~FLAG_SETTINGDEFAULT;
                }
                _flags &= ~FLAG_NIL;
                _flags |= FLAG_ISDEFAULT;
                return;
            }
        }
        // If we haven't returned yet, the default doesn't apply.

        // Ask underlying impl to parse ordinary non-default text
        set_text(v);
        _flags &= ~(FLAG_NIL | FLAG_ISDEFAULT);
    }

    /**
     * Types should return false if they don't treat the given
     * whitespace as a default value.
     */
    protected boolean is_defaultable_ws(String v) {
        return true;
    }

    /**
     * Returns the whitespace rule that will be applied before
     * building a string to pass to get_text().
     * <p>
     * Overridden by subclasses that don't need their text
     * for set_text canonicalized; perhaps they already implement
     * scanners that can deal with whitespace, and they know
     * they have no regex pattern restrictions.
     */
    protected int get_wscanon_rule() {
        return SchemaType.WS_COLLAPSE;
    }

    /**
     * Called to canonicalize whitespace before calling set_text.
     * <p>
     * Tries to avoid allocation when the string is already canonical, but
     * otherwise this is not particularly efficient. Hopefully the common
     * case is that we pass our wscanon rule to the store via fetch_text
     * and it's canonicalized before we even see it as a string.
     */
    private String apply_wscanon(String v) {
        return XmlWhitespace.collapse(v, get_wscanon_rule());
    }

    /**
     * Called before every set and get, to ensure that we have
     * a correct picture of whether we're nillable, fixed, or
     * if we have a default that can be applied.
     */
    private void check_element_dated() {
        if ((_flags & FLAG_ELEMENT_DATED) != 0 &&
            (_flags & FLAG_NOT_VARIABLE) == 0) {
            if ((_flags & FLAG_ORPHANED) != 0) {
                throw new XmlValueDisconnectedException();
            }

            int eltflags = get_store().compute_flags();
            // int eltflags = 0;
            _flags &= ~(FLAGS_ELEMENT | FLAG_ELEMENT_DATED);
            _flags |= eltflags;
        }
        if ((_flags & FLAG_NOT_VARIABLE) != 0) {
            _flags &= ~(FLAG_ELEMENT_DATED);
        }
    }

    /**
     * Describes the orphaned status of this object.
     */
    protected final boolean is_orphaned() {
        return (_flags & FLAG_ORPHANED) != 0;
    }

    /**
     * Called before every getter and setter on the strongly
     * typed classes to ensure that the object has not been
     * orphaned.
     */
    protected final void check_orphaned() {
        if (is_orphaned()) {
            throw new XmlValueDisconnectedException();
        }
    }

    /**
     * Called prior to every get operation, to ensure
     * that the value being read is valid. If the value
     * has been invalidated, it is re-read from the underlying
     * text store, and this may cause an out of range exception.
     * <p>
     * This method deals with nils, nillability, defaults, etc.
     */
    public final void check_dated() {
        if ((_flags & FLAGS_DATED) != 0) {
            if ((_flags & FLAG_ORPHANED) != 0) {
                throw new XmlValueDisconnectedException();
            }

            assert ((_flags & FLAG_STORE) != 0);

            check_element_dated();

            if ((_flags & FLAG_ELEMENT_DATED) != 0) {
                int eltflags = get_store().compute_flags();
                _flags &= ~(FLAGS_ELEMENT | FLAG_ELEMENT_DATED);
                _flags |= eltflags;
            }

            boolean nilled = false;

            if ((_flags & FLAG_NIL_DATED) != 0) {
                if (get_store().find_nil()) {
                    if ((_flags & FLAG_NILLABLE) == 0 &&
                        (_flags & FLAG_VALIDATE_ON_SET) != 0) {
                        throw new XmlValueOutOfRangeException(); // nil not allowed
                    }

                    // let the implementation know that we're nil now
                    set_nil();

                    _flags |= FLAG_NIL;
                    nilled = true;
                } else {
                    _flags &= ~FLAG_NIL;
                }
                _flags &= ~FLAG_NIL_DATED;
            }

            if (!nilled) {
                String text;

                if ((_flags & FLAG_COMPLEXCONTENT) != 0 || (text = get_wscanon_text()) == null) {
                    update_from_complex_content();
                } else {
                    NamespaceContext.push(new NamespaceContext(get_store()));
                    try {
                        update_from_wscanon_text(text);
                    } finally {
                        NamespaceContext.pop();
                    }
                }
            }

            _flags &= ~FLAG_VALUE_DATED;
        }
    }

    /**
     * Called before every set operation (except for the
     * special case of setting a string) to:
     * (1) get the nillable, fixed, etc flags
     * (2) throw an exception if it's fixed (not for strings)
     */
    private void set_prepare() {
        check_element_dated();
        if ((_flags & FLAG_IMMUTABLE) != 0) {
            throw new IllegalStateException();
        }
    }

    /**
     * Called after every set operation to invalidate
     * the attached raw text. Also, if we were dated,
     * we make a note that we're now current, since the
     * latest set beats the previous invalidate. Also,
     * if we were nil, we're no longer.
     */
    private void set_commit() {
        boolean wasNilled = ((_flags & FLAG_NIL) != 0);
        _flags &= ~(FLAG_NIL | FLAG_ISDEFAULT);

        if ((_flags & FLAG_STORE) != 0) {
            _flags &= ~(FLAGS_DATED);
            get_store().invalidate_text();
            if (wasNilled) {
                get_store().invalidate_nil();
            }
            _flags &= ~(FLAGS_DATED);
        } else {
            _textsource = null;
        }
    }

    /**
     * Grabs the undelying litral representation, applying the
     * implementation's wscanon rule.
     * Null if not simple content.
     */
    public final String get_wscanon_text() {
        if ((_flags & FLAG_STORE) == 0) {
            return apply_wscanon((String) _textsource);
        } else {
            return get_store().fetch_text(get_wscanon_rule());
        }
    }

    /**
     * This should set the value of the type from text,
     * or throw an XmlValueOutOfRangeException if it can't.
     */
    abstract protected void set_text(String text);

    /**
     * This should clear the value, and set it to whatever
     * is supposed to be returned when the value is nilled.
     */
    abstract protected void set_nil();

    /**
     * This should return the canonical string value of the primitive.
     * Only called when non-nil.
     */
    abstract protected String compute_text(NamespaceManager nsm);

    // SIMPLE VALUE ACCESSORS BELOW -------------------------------------------

    // numerics: fractional
    public float getFloatValue() {
        BigDecimal bd = getBigDecimalValue();
        return bd == null ? 0.0f : bd.floatValue();
    }

    public double getDoubleValue() {
        BigDecimal bd = getBigDecimalValue();
        return bd == null ? 0.0 : bd.doubleValue();
    }

    public BigDecimal getBigDecimalValue() {
        throw new XmlValueNotSupportedException(XmlErrorCodes.EXCEPTION_VALUE_NOT_SUPPORTED_S2J,
            new Object[]{getPrimitiveTypeName(), "numeric"});
    }

    // numerics: integral
    public BigInteger getBigIntegerValue() {
        BigDecimal bd = getBigDecimalValue();
        return bd == null ? null : MathUtil.toBigInteger(bd);
    }

    public byte getByteValue() {
        long l = getIntValue();
        if (l > Byte.MAX_VALUE) {
            throw new XmlValueOutOfRangeException();
        }
        if (l < Byte.MIN_VALUE) {
            throw new XmlValueOutOfRangeException();
        }
        return (byte) l;
    }

    public short getShortValue() {
        long l = getIntValue();
        if (l > Short.MAX_VALUE) {
            throw new XmlValueOutOfRangeException();
        }
        if (l < Short.MIN_VALUE) {
            throw new XmlValueOutOfRangeException();
        }
        return (short) l;
    }

    public int getIntValue() {
        long l = getLongValue();
        if (l > Integer.MAX_VALUE) {
            throw new XmlValueOutOfRangeException();
        }
        if (l < Integer.MIN_VALUE) {
            throw new XmlValueOutOfRangeException();
        }
        return Math.toIntExact(l);
    }

    private static final BigInteger _max = BigInteger.valueOf(Long.MAX_VALUE);
    private static final BigInteger _min = BigInteger.valueOf(Long.MIN_VALUE);

    public long getLongValue() {
        BigInteger b = getBigIntegerValue();
        if (b == null) {
            return 0L;
        }
        if (b.compareTo(_max) >= 0) {
            throw new XmlValueOutOfRangeException();
        }
        if (b.compareTo(_min) <= 0) {
            throw new XmlValueOutOfRangeException();
        }
        return b.longValue();
    }

    private static final XmlOptions _toStringOptions =
        buildInnerPrettyOptions();

    static XmlOptions buildInnerPrettyOptions() {
        XmlOptions options = new XmlOptions();
        options.setSaveInner();
        options.setSavePrettyPrint();
        options.setSaveAggressiveNamespaces();
        options.setUseDefaultNamespace();

        return options;
    }

    /*
    private static final void dumpStack ( String msg )
    {
        try
        {
            java.io.FileOutputStream fos = new java.io.FileOutputStream( "C:\\ee.txt", true );
            java.io.PrintStream ps = new java.io.PrintStream( fos );
            ps.println( "======================================" );
            ps.println( msg );
            new Exception().printStackTrace( ps );
            ps.close();
            fos.close();
        }
        catch ( Exception e )
        {
        }

    }
    */

    public final String toString() {
        synchronized (monitor()) {
            return ensureStore().xmlText(_toStringOptions);
        }
    }

    public String xmlText() {
        return xmlText(null);
    }

    public String xmlText(XmlOptions options) {
        try (XmlCursor cur = newCursorForce()) {
            return cur.xmlText(makeInnerOptions(options));
        }
    }

    // enums
    public StringEnumAbstractBase getEnumValue() {
        throw new XmlValueNotSupportedException(XmlErrorCodes.EXCEPTION_VALUE_NOT_SUPPORTED_S2J,
            new Object[]{getPrimitiveTypeName(), "enum"});
    }

    // various
    public String getStringValue() {
        if (isImmutable()) {
            if ((_flags & FLAG_NIL) != 0) {
                return null;
            }
            return compute_text(null);
        }
        // Since complex-content types don't have a "natural" string value, we
        // emit the deeply concatenated, tag-removed content of the tag.
        synchronized (monitor()) {
            if (_isComplexContent()) {
                return get_store().fetch_text(TypeStore.WS_PRESERVE);
            }

            check_dated();
            if ((_flags & FLAG_NIL) != 0) {
                return null;
            }
            return compute_text(has_store() ? get_store() : null);
        }
    }

    public byte[] getByteArrayValue() {
        throw new XmlValueNotSupportedException(XmlErrorCodes.EXCEPTION_VALUE_NOT_SUPPORTED_S2J,
            new Object[]{getPrimitiveTypeName(), "byte[]"});
    }

    public boolean getBooleanValue() {
        throw new XmlValueNotSupportedException(XmlErrorCodes.EXCEPTION_VALUE_NOT_SUPPORTED_S2J,
            new Object[]{getPrimitiveTypeName(), "boolean"});
    }

    public GDate getGDateValue() {
        throw new XmlValueNotSupportedException(XmlErrorCodes.EXCEPTION_VALUE_NOT_SUPPORTED_S2J,
            new Object[]{getPrimitiveTypeName(), "Date"});
    }

    public Date getDateValue() {
        throw new XmlValueNotSupportedException(XmlErrorCodes.EXCEPTION_VALUE_NOT_SUPPORTED_S2J,
            new Object[]{getPrimitiveTypeName(), "Date"});
    }

    public Calendar getCalendarValue() {
        throw new XmlValueNotSupportedException(XmlErrorCodes.EXCEPTION_VALUE_NOT_SUPPORTED_S2J,
            new Object[]{getPrimitiveTypeName(), "Calendar"});
    }

    public GDuration getGDurationValue() {
        throw new XmlValueNotSupportedException(XmlErrorCodes.EXCEPTION_VALUE_NOT_SUPPORTED_S2J,
            new Object[]{getPrimitiveTypeName(), "Duration"});
    }

    public QName getQNameValue() {
        throw new XmlValueNotSupportedException(XmlErrorCodes.EXCEPTION_VALUE_NOT_SUPPORTED_S2J,
            new Object[]{getPrimitiveTypeName(), "QName"});
    }

    public List<?> getListValue() {
        throw new XmlValueNotSupportedException(XmlErrorCodes.EXCEPTION_VALUE_NOT_SUPPORTED_S2J,
            new Object[]{getPrimitiveTypeName(), "List"});
    }

    public List<? extends XmlAnySimpleType> xgetListValue() {
        throw new XmlValueNotSupportedException(XmlErrorCodes.EXCEPTION_VALUE_NOT_SUPPORTED_S2J,
            new Object[]{getPrimitiveTypeName(), "List"});
    }

    public Object getObjectValue() {
        return java_value(this);
    }

    // set this value
    public final void setBooleanValue(boolean v) {
        synchronized (monitor()) {
            set_prepare();
            set_boolean(v);
            set_commit();
        }
    }

    public final void setByteValue(byte v) {
        synchronized (monitor()) {
            set_prepare();
            set_byte(v);
            set_commit();
        }
    }

    public final void setShortValue(short v) {
        synchronized (monitor()) {
            set_prepare();
            set_short(v);
            set_commit();
        }
    }

    public final void setIntValue(int v) {
        synchronized (monitor()) {
            set_prepare();
            set_int(v);
            set_commit();
        }
    }

    public final void setLongValue(long v) {
        synchronized (monitor()) {
            set_prepare();
            set_long(v);
            set_commit();
        }
    }

    public final void setFloatValue(float v) {
        synchronized (monitor()) {
            set_prepare();
            set_float(v);
            set_commit();
        }
    }

    public final void setDoubleValue(double v) {
        synchronized (monitor()) {
            set_prepare();
            set_double(v);
            set_commit();
        }
    }

    public final void setByteArrayValue(byte[] obj) {
        if (obj == null) {
            setNil();
        } else {
            synchronized (monitor()) {
                set_prepare();
                set_ByteArray(obj);
                set_commit();
            }
        }
    }

    public final void setEnumValue(StringEnumAbstractBase obj) {
        if (obj == null) {
            setNil();
        } else {
            synchronized (monitor()) {
                set_prepare();
                set_enum(obj);
                set_commit();
            }
        }
    }

    public final void setBigIntegerValue(BigInteger obj) {
        if (obj == null) {
            setNil();
        } else {
            synchronized (monitor()) {
                set_prepare();
                set_BigInteger(obj);
                set_commit();
            }
        }
    }

    public final void setBigDecimalValue(BigDecimal obj) {
        if (obj == null) {
            setNil();
        } else {
            synchronized (monitor()) {
                set_prepare();
                set_BigDecimal(obj);
                set_commit();
            }
        }
    }

    public final void setCalendarValue(Calendar obj) {
        if (obj == null) {
            setNil();
        } else {
            synchronized (monitor()) {
                set_prepare();
                set_Calendar(obj);
                set_commit();
            }
        }
    }

    public final void setDateValue(Date obj) {
        if (obj == null) {
            setNil();
        } else {
            synchronized (monitor()) {
                set_prepare();
                set_Date(obj);
                set_commit();
            }
        }
    }

    public final void setGDateValue(GDate obj) {
        if (obj == null) {
            setNil();
        } else {
            synchronized (monitor()) {
                set_prepare();
                set_GDate(obj);
                set_commit();
            }
        }
    }

    public final void setGDateValue(GDateSpecification obj) {
        if (obj == null) {
            setNil();
        } else {
            synchronized (monitor()) {
                set_prepare();
                set_GDate(obj);
                set_commit();
            }
        }
    }

    public final void setGDurationValue(GDuration obj) {
        if (obj == null) {
            setNil();
        } else {
            synchronized (monitor()) {
                set_prepare();
                set_GDuration(obj);
                set_commit();
            }
        }
    }

    public final void setGDurationValue(GDurationSpecification obj) {
        if (obj == null) {
            setNil();
        } else {
            synchronized (monitor()) {
                set_prepare();
                set_GDuration(obj);
                set_commit();
            }
        }
    }

    public final void setQNameValue(QName obj) {
        if (obj == null) {
            setNil();
        } else {
            synchronized (monitor()) {
                set_prepare();
                set_QName(obj);
                set_commit();
            }
        }
    }

    public final void setListValue(List<?> obj) {
        if (obj == null) {
            setNil();
        } else {
            synchronized (monitor()) {
                set_prepare();
                set_list(obj);
                set_commit();
            }
        }
    }

    public final void setStringValue(String obj) {
        if (obj == null) {
            setNil();
        } else {
            synchronized (monitor()) {
                set_prepare();
                set_String(obj); /* strings are special, so set_String does its own commit.*/
            }
        }
    }

    public void setObjectValue(Object o) {
        if (o == null) {
            setNil();
            return;
        }

        if (o instanceof XmlObject) {
            set((XmlObject) o);
        } else if (o instanceof String) {
            setStringValue((String) o);
        } else if (o instanceof StringEnumAbstractBase) {
            setEnumValue((StringEnumAbstractBase) o);
        } else if (o instanceof BigInteger) {
            setBigIntegerValue((BigInteger) o);
        } else if (o instanceof BigDecimal) {
            setBigDecimalValue((BigDecimal) o);
        } else if (o instanceof Byte) {
            setByteValue((Byte) o);
        } else if (o instanceof Short) {
            setShortValue((Short) o);
        } else if (o instanceof Integer) {
            setIntValue((Integer) o);
        } else if (o instanceof Long) {
            setLongValue((Long) o);
        } else if (o instanceof Boolean) {
            setBooleanValue((Boolean) o);
        } else if (o instanceof Float) {
            setFloatValue((Float) o);
        } else if (o instanceof Double) {
            setDoubleValue((Double) o);
        } else if (o instanceof Calendar) {
            setCalendarValue(((Calendar) o));
        } else if (o instanceof Date) {
            setDateValue((Date) o);
        } else if (o instanceof GDateSpecification) {
            setGDateValue((GDateSpecification) o);
        } else if (o instanceof GDurationSpecification) {
            setGDurationValue((GDurationSpecification) o);
        } else if (o instanceof QName) {
            setQNameValue((QName) o);
        } else if (o instanceof List) {
            setListValue((List<?>) o);
        } else if (o instanceof byte[]) {
            setByteArrayValue((byte[]) o);
        } else {
            throw
                new XmlValueNotSupportedException(
                    "Can't set union object of class : " +
                    o.getClass().getName());
        }
    }

    public final void set_newValue(XmlObject obj) {
        if (obj == null || obj.isNil()) {
            setNil();
            return;
        }

        primitive:
        if (obj instanceof XmlAnySimpleType) {
            XmlAnySimpleType v = (XmlAnySimpleType) obj;
            SchemaType instanceType = ((SimpleValue) v).instanceType();
            assert (instanceType != null) : "Nil case should have been handled already";

            // handle lists
            if (instanceType.getSimpleVariety() == SchemaType.LIST) {
                synchronized (monitor()) {
                    set_prepare();
                    set_list(((SimpleValue) v).xgetListValue());
                    set_commit();
                    return;
                }
            }

            // handle atomic types
            synchronized (monitor()) {
                assert (instanceType.getSimpleVariety() == SchemaType.ATOMIC);
                switch (instanceType.getPrimitiveType().getBuiltinTypeCode()) {
                    case SchemaType.BTC_BOOLEAN: {
                        boolean bool = ((SimpleValue) v).getBooleanValue();
                        set_prepare();
                        set_boolean(bool);
                        break;
                    }
                    case SchemaType.BTC_BASE_64_BINARY: {
                        byte[] byteArr = ((SimpleValue) v).getByteArrayValue();
                        set_prepare();
                        set_b64(byteArr);
                        break;
                    }
                    case SchemaType.BTC_HEX_BINARY: {
                        byte[] byteArr = ((SimpleValue) v).getByteArrayValue();
                        set_prepare();
                        set_hex(byteArr);
                        break;
                    }
                    case SchemaType.BTC_QNAME: {
                        QName name = ((SimpleValue) v).getQNameValue();
                        set_prepare();
                        set_QName(name);
                        break;
                    }
                    case SchemaType.BTC_FLOAT: {
                        float f = ((SimpleValue) v).getFloatValue();
                        set_prepare();
                        set_float(f);
                        break;
                    }
                    case SchemaType.BTC_DOUBLE: {
                        double d = ((SimpleValue) v).getDoubleValue();
                        set_prepare();
                        set_double(d);
                        break;
                    }
                    case SchemaType.BTC_DECIMAL: {
                        switch (instanceType.getDecimalSize()) {
                            case SchemaType.SIZE_BYTE: {
                                byte b = ((SimpleValue) v).getByteValue();
                                set_prepare();
                                set_byte(b);
                                break;
                            }
                            case SchemaType.SIZE_SHORT: {
                                short s = ((SimpleValue) v).getShortValue();
                                set_prepare();
                                set_short(s);
                                break;
                            }
                            case SchemaType.SIZE_INT: {
                                int i = ((SimpleValue) v).getIntValue();
                                set_prepare();
                                set_int(i);
                                break;
                            }
                            case SchemaType.SIZE_LONG: {
                                long l = ((SimpleValue) v).getLongValue();
                                set_prepare();
                                set_long(l);
                                break;
                            }
                            case SchemaType.SIZE_BIG_INTEGER: {
                                BigInteger bi = ((SimpleValue) v).getBigIntegerValue();
                                set_prepare();
                                set_BigInteger(bi);
                                break;
                            }
                            case SchemaType.SIZE_BIG_DECIMAL: {
                                BigDecimal bd = ((SimpleValue) v).getBigDecimalValue();
                                set_prepare();
                                set_BigDecimal(bd);
                                break;
                            }
                            default: {
                                assert (false) : "invalid numeric bit count";
                                // fallthrough
                            }
                        }
                        break;
                    }
                    case SchemaType.BTC_ANY_URI: {
                        String uri = v.getStringValue();
                        set_prepare();
                        set_text(uri);
                        break;
                    }
                    case SchemaType.BTC_NOTATION: {
                        String s = v.getStringValue();
                        set_prepare();
                        set_notation(s);
                        break;
                    }
                    case SchemaType.BTC_DURATION: {
                        GDuration gd = ((SimpleValue) v).getGDurationValue();
                        set_prepare();
                        set_GDuration(gd);
                        break;
                    }
                    case SchemaType.BTC_DATE_TIME:
                    case SchemaType.BTC_TIME:
                    case SchemaType.BTC_DATE:
                    case SchemaType.BTC_G_YEAR_MONTH:
                    case SchemaType.BTC_G_YEAR:
                    case SchemaType.BTC_G_MONTH_DAY:
                    case SchemaType.BTC_G_DAY:
                    case SchemaType.BTC_G_MONTH: {
                        GDate gd = ((SimpleValue) v).getGDateValue();
                        set_prepare();
                        set_GDate(gd);
                        break;
                    }
                    case SchemaType.BTC_STRING: {
                        String s = v.getStringValue();
                        set_prepare();
                        set_String(s);
                        break;
                    }
                    case SchemaType.BTC_ANY_SIMPLE: {
                        boolean pushed = false;
                        if (!v.isImmutable()) {
                            pushed = true;
                            NamespaceContext.push(new NamespaceContext(v));
                        }
                        try {
                            set_prepare();
                            set_xmlanysimple(v);
                        } finally {
                            if (pushed) {
                                NamespaceContext.pop();
                            }
                        }
                        break;
                    }
                    default:
                        assert (false) : "encountered nonprimitive type.";
                        // case SchemaType.BTC_ANY_SIMPLE:  This is handled below...
                        // but we eventually want to handle it with a treecopy, so
                        // eventually we should break here.
                        break primitive;
                }
                set_commit();
                return; // primitive node tree copy handled.
            }
        }

        throw new IllegalStateException("Complex type unexpected");
    }

    private TypeStoreUser setterHelper(XmlObjectBase src) {
        check_orphaned();

        src.check_orphaned();

        return
            get_store().copy_contents_from(src.get_store()).
                get_store().change_type(src.schemaType());
    }

    public final XmlObject set(XmlObject src) {
        if (isImmutable()) {
            throw new IllegalStateException("Cannot set the value of an immutable XmlObject");
        }

        XmlObjectBase obj = underlying(src);

        TypeStoreUser newObj = this;

        if (obj == null) {
            setNil();
            return this;
        }

        if (obj.isImmutable()) {
            setStringValue(obj.getStringValue());
        } else {
            boolean noSyncThis = preCheck();
            boolean noSyncObj = obj.preCheck();

            if (monitor() == obj.monitor())             // both are in the same locale
            {
                if (noSyncThis)                         // the locale is not sync
                {
                    newObj = setterHelper(obj);
                } else                                    // the locale is sync
                {
                    synchronized (monitor()) {
                        newObj = setterHelper(obj);
                    }
                }
            } else                                        // on different locale's
            {
                if (noSyncThis) {
                    if (noSyncObj)                      // both unsync
                    {
                        newObj = setterHelper(obj);
                    } else                                // only obj is sync
                    {
                        synchronized (obj.monitor()) {
                            newObj = setterHelper(obj);
                        }
                    }
                } else {
                    if (noSyncObj)                      // only this is sync
                    {
                        synchronized (monitor()) {
                            newObj = setterHelper(obj);
                        }
                    } else                                // both are sync can't avoid the global lock
                    {
                        boolean acquired = false;

                        try {
                            // about to grab two locks: don't deadlock ourselves
                            GlobalLock.acquire();
                            acquired = true;

                            synchronized (monitor()) {
                                synchronized (obj.monitor()) {
                                    GlobalLock.release();
                                    acquired = false;

                                    newObj = setterHelper(obj);
                                }
                            }
                        } catch (InterruptedException e) {
                            throw new XmlRuntimeException(e);
                        } finally {
                            if (acquired) {
                                GlobalLock.release();
                            }
                        }
                    }
                }
            }
        }

        return (XmlObject) newObj;
    }

    public final XmlObject generatedSetterHelperImpl(XmlObject src, QName propName, int index,
                                                     short kindSetterHelper) {

        XmlObjectBase srcObj = underlying(src);

        if (srcObj == null) {
            synchronized (monitor()) {
                XmlObjectBase target = getTargetForSetter(propName, index, kindSetterHelper);
                target.setNil();
                return target;
            }
        }

        if (srcObj.isImmutable()) {
            synchronized (monitor()) {
                XmlObjectBase target = getTargetForSetter(propName, index, kindSetterHelper);
                target.setStringValue(srcObj.getStringValue());
                return target;
            }
        }


        boolean noSyncThis = preCheck();
        boolean noSyncObj = srcObj.preCheck();

        if (monitor() == srcObj.monitor()) {
            // both are in the same locale
            if (noSyncThis) {
                // the locale is not sync
                return (XmlObject) objSetterHelper(srcObj, propName, index, kindSetterHelper);
            } else {
                // the locale is sync
                synchronized (monitor()) {
                    return (XmlObject) objSetterHelper(srcObj, propName, index, kindSetterHelper);
                }
            }
        }

        if (noSyncThis) {
            // on different locale's
            if (noSyncObj) {
                // both unsync
                return (XmlObject) objSetterHelper(srcObj, propName, index, kindSetterHelper);
            } else {
                // only obj is sync
                synchronized (srcObj.monitor()) {
                    return (XmlObject) objSetterHelper(srcObj, propName, index, kindSetterHelper);
                }
            }
        } else {
            if (noSyncObj) {
                // only this is sync
                synchronized (monitor()) {
                    return (XmlObject) objSetterHelper(srcObj, propName, index, kindSetterHelper);
                }
            } else {
                // both are sync can't avoid the global lock
                boolean acquired = false;

                try {
                    // about to grab two locks: don't deadlock ourselves
                    GlobalLock.acquire();
                    acquired = true;

                    synchronized (monitor()) {
                        synchronized (srcObj.monitor()) {
                            GlobalLock.release();
                            acquired = false;

                            return (XmlObject) objSetterHelper(srcObj, propName, index, kindSetterHelper);
                        }
                    }
                } catch (InterruptedException e) {
                    throw new XmlRuntimeException(e);
                } finally {
                    if (acquired) {
                        GlobalLock.release();
                    }
                }
            }
        }
    }

    private TypeStoreUser objSetterHelper(XmlObjectBase srcObj, QName propName, int index, short kindSetterHelper) {
        XmlObjectBase target = getTargetForSetter(propName, index, kindSetterHelper);

        target.check_orphaned();
        srcObj.check_orphaned();

        return target.get_store().copy_contents_from(srcObj.get_store()).
            get_store().change_type(srcObj.schemaType());
    }

    private XmlObjectBase getTargetForSetter(QName propName, int index, short kindSetterHelper) {
        switch (kindSetterHelper) {
            case KIND_SETTERHELPER_SINGLETON: {
                check_orphaned();
                XmlObjectBase target;
                target = (XmlObjectBase) get_store().find_element_user(propName, index);
                if (target == null) {
                    target = (XmlObjectBase) get_store().add_element_user(propName);
                }

                if (target.isImmutable()) {
                    throw new IllegalStateException("Cannot set the value of an immutable XmlObject");
                }

                return target;
            }

            case KIND_SETTERHELPER_ARRAYITEM: {
                check_orphaned();
                XmlObjectBase target;
                target = (XmlObjectBase) get_store().find_element_user(propName, index);
                if (target == null) {
                    throw new IndexOutOfBoundsException();
                }

                if (target.isImmutable()) {
                    throw new IllegalStateException("Cannot set the value of an immutable XmlObject");
                }

                return target;
            }

            default:
                throw new IllegalArgumentException("Unknown kindSetterHelper: " + kindSetterHelper);
        }
    }

    /**
     * Same as set() but unsynchronized.
     * Warning: Using this method in mutithreaded environment can cause invalid states.
     */
    public final XmlObject _set(XmlObject src) {
        if (isImmutable()) {
            throw new IllegalStateException("Cannot set the value of an immutable XmlObject");
        }

        XmlObjectBase obj = underlying(src);

        TypeStoreUser newObj = this;

        if (obj == null) {
            setNil();
            return this;
        }

        if (obj.isImmutable()) {
            setStringValue(obj.getStringValue());
        } else {
            check_orphaned();
            obj.check_orphaned();

            newObj = get_store().copy_contents_from(obj.get_store()).
                get_store().change_type(obj.schemaType());
        }
        return (XmlObject) newObj;
    }

    protected void set_list(List<?> list) {
        throw new XmlValueNotSupportedException(XmlErrorCodes.EXCEPTION_VALUE_NOT_SUPPORTED_J2S,
            new Object[]{"List", getPrimitiveTypeName()});
    }

    protected void set_boolean(boolean v) {
        throw new XmlValueNotSupportedException(XmlErrorCodes.EXCEPTION_VALUE_NOT_SUPPORTED_J2S,
            new Object[]{"boolean", getPrimitiveTypeName()});
    }

    protected void set_byte(byte v) {
        set_int(v);
    }

    protected void set_short(short v) {
        set_int(v);
    }

    protected void set_int(int v) {
        set_long(v);
    }

    protected void set_long(long v) {
        set_BigInteger(BigInteger.valueOf(v));
    }

    protected void set_char(char v) {
        set_String(Character.toString(v));
    }

    protected void set_float(float v) {
        set_BigDecimal(new BigDecimal(v));
    }

    protected void set_double(double v) {
        set_BigDecimal(new BigDecimal(v));
    }

    protected void set_enum(StringEnumAbstractBase e) {
        throw new XmlValueNotSupportedException(XmlErrorCodes.EXCEPTION_VALUE_NOT_SUPPORTED_J2S,
            new Object[]{"enum", getPrimitiveTypeName()});
    }

    protected void set_ByteArray(byte[] b) {
        throw new XmlValueNotSupportedException(XmlErrorCodes.EXCEPTION_VALUE_NOT_SUPPORTED_J2S,
            new Object[]{"byte[]", getPrimitiveTypeName()});
    }

    protected void set_b64(byte[] b) {
        set_ByteArray(b);
    }

    protected void set_hex(byte[] b) {
        set_ByteArray(b);
    }

    protected void set_BigInteger(BigInteger v) {
        set_BigDecimal(new BigDecimal(v));
    }

    protected void set_BigDecimal(BigDecimal v) {
        throw new XmlValueNotSupportedException(XmlErrorCodes.EXCEPTION_VALUE_NOT_SUPPORTED_J2S,
            new Object[]{"numeric", getPrimitiveTypeName()});
    }

    protected void set_Date(Date v) {
        throw new XmlValueNotSupportedException(XmlErrorCodes.EXCEPTION_VALUE_NOT_SUPPORTED_J2S,
            new Object[]{"Date", getPrimitiveTypeName()});
    }

    protected void set_Calendar(Calendar v) {
        throw new XmlValueNotSupportedException(XmlErrorCodes.EXCEPTION_VALUE_NOT_SUPPORTED_J2S,
            new Object[]{"Calendar", getPrimitiveTypeName()});
    }

    protected void set_GDate(GDateSpecification v) {
        throw new XmlValueNotSupportedException(XmlErrorCodes.EXCEPTION_VALUE_NOT_SUPPORTED_J2S,
            new Object[]{"Date", getPrimitiveTypeName()});
    }

    protected void set_GDuration(GDurationSpecification v) {
        throw new XmlValueNotSupportedException(XmlErrorCodes.EXCEPTION_VALUE_NOT_SUPPORTED_J2S,
            new Object[]{"Duration", getPrimitiveTypeName()});
    }

    protected void set_ComplexXml(XmlObject v) {
        throw new XmlValueNotSupportedException(XmlErrorCodes.EXCEPTION_VALUE_NOT_SUPPORTED_J2S,
            new Object[]{"complex content", getPrimitiveTypeName()});
    }

    protected void set_QName(QName v) {
        throw new XmlValueNotSupportedException(XmlErrorCodes.EXCEPTION_VALUE_NOT_SUPPORTED_J2S,
            new Object[]{"QName", getPrimitiveTypeName()});
    }

    protected void set_notation(String v) {
        throw new XmlValueNotSupportedException();
    }

    protected void set_xmlanysimple(XmlAnySimpleType v) {
        set_String(v.getStringValue());
    }

    private String getPrimitiveTypeName() {
        SchemaType type = schemaType();
        if (type.isNoType()) {
            return "unknown";
        }
        SchemaType t = type.getPrimitiveType();
        if (t == null) {
            return "complex";
        } else {
            return t.getName().getLocalPart();
        }
    }

    private boolean comparable_value_spaces(SchemaType t1, SchemaType t2) {
        assert (t1.getSimpleVariety() != SchemaType.UNION && t2.getSimpleVariety() != SchemaType.UNION);

        if (!t1.isSimpleType() && !t2.isSimpleType()) {
            return (t1.getContentType() == t2.getContentType());
        }

        if (!t1.isSimpleType() || !t2.isSimpleType()) {
            return false;
        }

        if (t1.getSimpleVariety() == SchemaType.LIST && t2.getSimpleVariety() == SchemaType.LIST) {
            return true;
        }

        if (t1.getSimpleVariety() == SchemaType.LIST || t2.getSimpleVariety() == SchemaType.LIST) {
            return false;
        }

        return (t1.getPrimitiveType().equals(t2.getPrimitiveType()));
    }

    private boolean valueEqualsImpl(XmlObject xmlobj) {
        check_dated();

        SchemaType typethis = instanceType();
        SchemaType typeother = ((SimpleValue) xmlobj).instanceType();

        if (typethis == null && typeother == null) // detects nil
        {
            return true;
        }

        if (typethis == null || typeother == null) {
            return false;
        }

        if (!comparable_value_spaces(typethis, typeother)) {
            return false;
        }

        if (xmlobj.schemaType().getSimpleVariety() == SchemaType.UNION) {
            return (underlying(xmlobj)).equal_to(this);
        }

        return equal_to(xmlobj);
    }

    public final boolean valueEquals(XmlObject xmlobj) {
        boolean acquired = false;
        try {
            if (isImmutable()) {
                if (xmlobj.isImmutable()) {
                    return valueEqualsImpl(xmlobj);
                } else {
                    synchronized (xmlobj.monitor()) {
                        return valueEqualsImpl(xmlobj);
                    }
                }
            } else {
                if (xmlobj.isImmutable() || monitor() == xmlobj.monitor()) {
                    synchronized (monitor()) {
                        return valueEqualsImpl(xmlobj);
                    }
                } else {
                    GlobalLock.acquire();
                    acquired = true;
                    synchronized (monitor()) {
                        synchronized (xmlobj.monitor()) {
                            GlobalLock.release();
                            acquired = false;
                            return valueEqualsImpl(xmlobj);
                        }
                    }
                }
            }

        } catch (InterruptedException e) {
            throw new XmlRuntimeException(e);
        } finally {
            if (acquired) {
                GlobalLock.release();
            }
        }
    }

    /**
     * Implements Comparable. This compareTo is inconsistent with
     * equals unless isImmutable() is true.
     */
    public final int compareTo(Object obj) {
        int result = compareValue((XmlObject) obj); // can throw ClassCast
        if (result == 2) {
            throw new ClassCastException();
        }
        return result;
    }

    /**
     * Allowed to return 2 for incomparable.
     */
    private int compareValueImpl(XmlObject xmlobj) {
        SchemaType type1, type2;

        try {
            type1 = instanceType();
            type2 = ((SimpleValue) xmlobj).instanceType();
        } catch (XmlValueOutOfRangeException e) {
            return 2;
        }

        if (type1 == null && type2 == null) {
            return 0;
        }
        if (type1 == null || type2 == null) {
            return 2;
        }

        if (!type1.isSimpleType() || type1.isURType()) {
            return 2;
        }

        if (!type2.isSimpleType() || type2.isURType()) {
            return 2;
        }

        type1 = type1.getPrimitiveType();
        type2 = type2.getPrimitiveType();

        // Different value spaces: different
        if (type1.getBuiltinTypeCode() != type2.getBuiltinTypeCode()) {
            return 2;
        }

        // now we'll depend on our impl class to do the work
        return compare_to(xmlobj);
    }

    public final int compareValue(XmlObject xmlobj) {
        if (xmlobj == null) {
            return 2;
        }

        boolean acquired = false;
        try {
            if (isImmutable()) {
                if (xmlobj.isImmutable()) {
                    return compareValueImpl(xmlobj);
                } else {
                    synchronized (xmlobj.monitor()) {
                        return compareValueImpl(xmlobj);
                    }
                }
            } else {
                if (xmlobj.isImmutable() || monitor() == xmlobj.monitor()) {
                    synchronized (monitor()) {
                        return compareValueImpl(xmlobj);
                    }
                } else {
                    GlobalLock.acquire();
                    acquired = true;
                    synchronized (monitor()) {
                        synchronized (xmlobj.monitor()) {
                            GlobalLock.release();
                            acquired = false;
                            return compareValueImpl(xmlobj);
                        }
                    }
                }
            }

        } catch (InterruptedException e) {
            throw new XmlRuntimeException(e);
        } finally {
            if (acquired) {
                GlobalLock.release();
            }
        }
    }

    /**
     * This implementation of compare_to is allowed to do two
     * unusual things:
     * (1) it can assume that the xmlobj passed has a primitive
     * type underlying the instance type that matches the
     * current instance, and that neither is nil.
     * (2) it is allowed to return 2 for "incomparable";
     * it should not throw an exception.
     */
    protected int compare_to(XmlObject xmlobj) {
        if (equal_to(xmlobj)) {
            return 0;
        }
        return 2;
    }

    protected abstract boolean equal_to(XmlObject xmlobj);

    protected abstract int value_hash_code();

    public int valueHashCode() {
        synchronized (monitor()) {
            return value_hash_code();
        }
    }


    public boolean isInstanceOf(SchemaType type) {
        SchemaType myType;

        if (type.getSimpleVariety() != SchemaType.UNION) {
            for (myType = instanceType(); myType != null; myType = myType.getBaseType()) {
                if (type == myType) {
                    return true;
                }
            }
        } else {
            Set<SchemaType> ctypes = new HashSet<>(Arrays.asList(type.getUnionConstituentTypes()));
            for (myType = instanceType(); myType != null; myType = myType.getBaseType()) {
                if (ctypes.contains(myType)) {
                    return true;
                }
            }
        }
        return false;
    }

    public final boolean equals(Object obj) {
        if (!isImmutable()) {
            return super.equals(obj);
        }

        if (!(obj instanceof XmlObject)) {
            return false;
        }

        XmlObject xmlobj = (XmlObject) obj;
        if (!xmlobj.isImmutable()) {
            return false;
        }

        return valueEquals(xmlobj);
    }

    public final int hashCode() {
        if (!isImmutable()) {
            return super.hashCode();
        }

        synchronized (monitor()) {
            if (isNil()) {
                return 0;
            }

            return value_hash_code();
        }
    }

    private static final XmlObject[] EMPTY_RESULT = new XmlObject[0];

    /**
     * Selects the contents of the children elements with the given name.
     */
    public XmlObject[] selectChildren(QName elementName) {
        try (XmlCursor xc = this.newCursor()) {
            if (!xc.isContainer()) {
                return EMPTY_RESULT;
            }

            List<XmlObject> result = new ArrayList<>();

            if (xc.toChild(elementName)) {
                // look for elements
                do {
                    result.add(xc.getObject());
                }
                while (xc.toNextSibling(elementName));
            }
            if (result.isEmpty()) {
                return EMPTY_RESULT;
            } else {
                return result.toArray(EMPTY_RESULT);
            }
        }
    }

    /**
     * Selects the contents of the children elements with the given name.
     */
    public XmlObject[] selectChildren(String elementUri, String elementLocalName) {
        return selectChildren(new QName(elementUri, elementLocalName));
    }

    /**
     * Selects the contents of the children elements that are contained in the elementNameSet.
     */
    public XmlObject[] selectChildren(QNameSet elementNameSet) {
        if (elementNameSet == null) {
            throw new IllegalArgumentException();
        }

        try (XmlCursor xc = this.newCursor()) {
            if (!xc.isContainer()) {
                return EMPTY_RESULT;
            }

            List<XmlObject> result = new ArrayList<>();

            if (xc.toFirstChild()) {
                // look for elements
                do {
                    assert xc.isContainer();
                    if (elementNameSet.contains(xc.getName())) {
                        result.add(xc.getObject());
                    }
                }
                while (xc.toNextSibling());
            }
            if (result.isEmpty()) {
                return EMPTY_RESULT;
            } else {
                return result.toArray(EMPTY_RESULT);
            }
        }
    }

    /**
     * Selects the content of the attribute with the given name.
     */
    public XmlObject selectAttribute(QName attributeName) {
        try (XmlCursor xc = this.newCursor()) {
            if (!xc.isContainer()) {
                return null;
            }

            if (xc.toFirstAttribute()) {
                //look for attributes
                do {
                    if (xc.getName().equals(attributeName)) {
                        return xc.getObject();
                    }
                }
                while (xc.toNextAttribute());
            }
            return null;
        }
    }

    /**
     * Selects the content of the attribute with the given name.
     */
    public XmlObject selectAttribute(String attributeUri, String attributeLocalName) {
        return selectAttribute(new QName(attributeUri, attributeLocalName));
    }

    /**
     * Selects the contents of the attributes that are contained in the elementNameSet.
     */
    public XmlObject[] selectAttributes(QNameSet attributeNameSet) {
        if (attributeNameSet == null) {
            throw new IllegalArgumentException();
        }

        try (XmlCursor xc = this.newCursor()) {
            if (!xc.isContainer()) {
                return EMPTY_RESULT;
            }

            List<XmlObject> result = new ArrayList<>();

            if (xc.toFirstAttribute()) {
                //look for attributes
                do {
                    if (attributeNameSet.contains(xc.getName())) {
                        result.add(xc.getObject());
                    }
                }
                while (xc.toNextAttribute());
            }

            if (result.isEmpty()) {
                return EMPTY_RESULT;
            } else {
                return result.toArray(EMPTY_RESULT);
            }
        }
    }

    /**
     * This method can writeReplace either an unwrapped XmlObjectBase
     * or an XBean proxy.  A "true" argument means unwrapped.
     * <p>
     * The serialization strategy for XmlObjects is this:
     * <p>
     * (1) Only the root XmlObject for a document actually gets
     * fully serialized; it is serialized as a SerializedRootObject,
     * which simply saves itself as XML text.
     * <p>
     * (2) Interior XmlObjects get serialized as a reference to the
     * root XmlObject for their document, plus an integer which
     * indicates the position of the XmlObject within the owner
     * document. This pair is stored as a SerializedInteriorObject.
     * <p>
     * Both objects can be maked as wrapped or unwrapped. If wrapped,
     * then the proxy is returned when deserializing; if unwrapped, then
     * the proxy is stripped when deserializing.
     */
    public Object writeReplace() {
        synchronized (monitor()) {
            if (isRootXmlObject()) {
                return new SerializedRootObject(this);
            }

            return new SerializedInteriorObject(this, getRootXmlObject());
        }
    }

    /**
     * True if the object is at the root of the document.
     */
    private boolean isRootXmlObject() {
        try (XmlCursor cur = newCursor()) {
            if (cur == null) {
                return false;
            }

            return !cur.toParent();
        }
    }

    /**
     * Gets the root XmlObject for this document.
     */
    private XmlObject getRootXmlObject() {
        try (XmlCursor cur = newCursor()) {
            if (cur == null) {
                return this;
            }
            cur.toStartDoc();
            return cur.getObject();
        }
    }

    /**
     * Serializable rewrite object that knows how to resolve
     * to an XmlObjectBase or a proxy for the root object of
     * a document.
     */
    private static class SerializedRootObject implements Serializable {
        private static final long serialVersionUID = 1;

        transient Class<? extends XmlObject> _xbeanClass;
        transient XmlObject _impl;

        private SerializedRootObject(XmlObject impl) {
            _xbeanClass = impl.schemaType().getJavaClass();
            _impl = impl;
        }

        private void writeObject(ObjectOutputStream out) throws IOException {
            out.writeObject(_xbeanClass);
            // the first short is written out for backwards compatibility
            // it will always be zero for objects written with
            // this code, but it used to be the first 2 bytes of the
            // writeUTF() method
            out.writeShort((short) 0);
            out.writeShort(MAJOR_VERSION_NUMBER);
            out.writeShort(MINOR_VERSION_NUMBER);
            // CR122401 - need to use writeObject instead of writeUTF
            // for xmlText as writeUTF has a length limitation of
            // 65535 bytes
            String xmlText = _impl.xmlText();
            out.writeObject(xmlText);
            out.writeBoolean(false);
        }

        @SuppressWarnings("unchecked")
        private void readObject(ObjectInputStream in) throws IOException {
            try {
                // read class object first - this is
                // first just for historical reasons - really
                // it would be better to have the version numbers
                // first
                _xbeanClass = (Class<? extends XmlObject>) in.readObject();

                int utfBytes = in.readUnsignedShort();

                // determine version numbers
                // if utfBytes is non-zero then we default to 0.0
                // otherwise expect major and minor version numbers
                // to be next entries in stream
                int majorVersionNum = 0;
                int minorVersionNum = 0;
                if (utfBytes == 0) {
                    majorVersionNum = in.readUnsignedShort();
                    minorVersionNum = in.readUnsignedShort();
                }

                String xmlText;
                switch (majorVersionNum) {
                    case 0: // original, unnumbered version
                        // minorVersionNum is always zero
                        xmlText = readObjectV0(in, utfBytes);
                        in.readBoolean(); // ignored
                        break;

                    case 1:
                        if (minorVersionNum == 1) {
                            xmlText = (String) in.readObject();
                            in.readBoolean(); // ignored
                        } else {
                            throw new IOException("Deserialization error: " +
                                                  "version number " + majorVersionNum + "." +
                                                  minorVersionNum + " not supported.");
                        }
                        break;

                    default:
                        throw new IOException("Deserialization error: " +
                                              "version number " + majorVersionNum + "." +
                                              minorVersionNum + " not supported.");
                }

                XmlOptions opts = new XmlOptions().setDocumentType(XmlBeans.typeForClass(_xbeanClass));
                _impl = XmlBeans.getContextTypeLoader().parse(xmlText, null, opts);
            } catch (Exception e) {
                throw new IOException(e.getMessage(), e);
            }
        }

        // this method is for reading the UTF-8 String that used to be
        // written out for a serialized XmlObject according to the
        // original format before this fix, i.e. it expects it
        // to have been written using the following algorithm:
        //
        // writeObject(Class object)
        // writeUTF(xmlText of object as String)
        // writeBoolean()
        //
        // this method is passed the original input stream positioned as though
        // it had just read the class object plus the next 2 bytes. Those 2
        // bytes are interpreted as an unsigned short saying how many more
        // bytes there are representing the bytes of the UTF-8-formatted String;
        // this value is passed in as the argument utfBytes
        private String readObjectV0(ObjectInputStream in, int utfBytes)
            throws IOException {
            // allow an extra 2 bytes up front for the unsigned short
            byte[] bArray = new byte[utfBytes + 2];

            // for format of these first 2 bytes see
            // Java API docs - DataOutputStream.writeShort()
            bArray[0] = (byte) (0xff & (utfBytes >> 8));
            bArray[1] = (byte) (0xff & utfBytes);

            // read the next numBytes bytes from the input stream
            // into the byte array starting at offset 2; this may
            // take multiple calls to read()
            int totalBytesRead = 0;
            int numRead;
            while (totalBytesRead < utfBytes) {
                numRead =
                    in.read(bArray, 2 + totalBytesRead, utfBytes - totalBytesRead);
                if (numRead == -1) // reached end of stream
                {
                    break;
                }

                totalBytesRead += numRead;
            }

            if (totalBytesRead != utfBytes) {
                throw new IOException("Error reading backwards compatible " +
                                      "XmlObject: number of bytes read (" + totalBytesRead +
                                      ") != number expected (" + utfBytes + ")");
            }

            // now set up a DataInputStream to read those
            // bytes as a UTF-8 String i.e. as though we'd never
            // read the first 2 bytes from the original stream
            LongUTFDataInputStream dis = null;
            String str;
            try {
                dis = new LongUTFDataInputStream(new ByteArrayInputStream(bArray));
                str = dis.readLongUTF();
            } finally {
                if (dis != null) {
                    dis.close();
                }
            }

            return str;
        }

        private Object readResolve() throws ObjectStreamException {
            return _impl;
        }
    }

    /**
     * Serializable rewrite object that knows how to resolve
     * to an XmlObjectBase or a proxy for an interior position
     * within a document.
     */
    private static class SerializedInteriorObject implements Serializable {
        private static final long serialVersionUID = 1;

        transient XmlObject _impl;
        transient XmlObject _root;

        private SerializedInteriorObject(XmlObject impl, XmlObject root) {
            _impl = impl;
            _root = root;
        }

        private void writeObject(ObjectOutputStream out) throws IOException {
            out.writeObject(_root);
            out.writeBoolean(false);
            out.writeInt(distanceToRoot());
        }

        private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
            _root = (XmlObject) in.readObject();
            in.readBoolean();
            _impl = objectAtDistance(in.readInt());
        }

        private Object readResolve() throws ObjectStreamException {
            return _impl;
        }

        private int distanceToRoot() {
            int count = 0;
            try (XmlCursor cur = _impl.newCursor()) {
                while (!cur.toPrevToken().isNone()) {
                    if (!cur.currentTokenType().isNamespace()) {
                        count += 1;
                        // System.out.println("Count: " + count + " " + cur.currentTokenType().toString() + " " + QName.pretty(cur.getName()));
                    }
                }
            }
            return count;
        }

        private XmlObject objectAtDistance(int count) {
            try (XmlCursor cur = _root.newCursor()) {
                while (count > 0) {
                    cur.toNextToken();
                    if (!cur.currentTokenType().isNamespace()) {
                        count -= 1;
                        // System.out.println("Count: " + count + " " + cur.currentTokenType().toString() + " " + QName.pretty(cur.getName()));
                    }
                }
                return cur.getObject();
            }
        }
    }

    protected static Object java_value(XmlObject obj) {
        if (obj.isNil()) {
            return null;
        }

        if (!(obj instanceof XmlAnySimpleType)) {
            return obj;
        }

        SchemaType instanceType = ((SimpleValue) obj).instanceType();
        assert (instanceType != null) : "Nil case should have been handled above";

        // handle lists
        if (instanceType.getSimpleVariety() == SchemaType.LIST) {
            return ((SimpleValue) obj).getListValue();
        }

        SimpleValue base = (SimpleValue) obj;

        switch (instanceType.getPrimitiveType().getBuiltinTypeCode()) {
            case SchemaType.BTC_BOOLEAN:
                return base.getBooleanValue() ? Boolean.TRUE : Boolean.FALSE;

            case SchemaType.BTC_BASE_64_BINARY:
            case SchemaType.BTC_HEX_BINARY:
                return base.getByteArrayValue();

            case SchemaType.BTC_QNAME:
                return base.getQNameValue();

            case SchemaType.BTC_FLOAT:
                return base.getFloatValue();

            case SchemaType.BTC_DOUBLE:
                return base.getDoubleValue();

            case SchemaType.BTC_DECIMAL: {
                switch (instanceType.getDecimalSize()) {
                    case SchemaType.SIZE_BYTE:
                        return base.getByteValue();

                    case SchemaType.SIZE_SHORT:
                        return base.getShortValue();

                    case SchemaType.SIZE_INT:
                        return base.getIntValue();

                    case SchemaType.SIZE_LONG:
                        return base.getLongValue();

                    case SchemaType.SIZE_BIG_INTEGER:
                        return base.getBigIntegerValue();

                    case SchemaType.SIZE_BIG_DECIMAL:
                        return base.getBigDecimalValue();
                    default:
                        assert (false) : "invalid numeric bit count";
                        // fallthrough
                }
            }
            case SchemaType.BTC_ANY_URI:
                return base.getStringValue();

            case SchemaType.BTC_DURATION:
                return base.getGDurationValue();

            case SchemaType.BTC_DATE_TIME:
            case SchemaType.BTC_TIME:
            case SchemaType.BTC_DATE:
            case SchemaType.BTC_G_YEAR_MONTH:
            case SchemaType.BTC_G_YEAR:
            case SchemaType.BTC_G_MONTH_DAY:
            case SchemaType.BTC_G_DAY:
            case SchemaType.BTC_G_MONTH:
                return base.getCalendarValue();
                case SchemaType.BTC_NOTATION:
            case SchemaType.BTC_STRING:
            case SchemaType.BTC_ANY_SIMPLE:
                // return base.getStringValue();
                return base.getStringValue();
            default:
                assert (false) : "encountered nonprimitive type.";
                // fallthrough

                // NB: for string enums we just do java.lang.String
                // when in the context of unions. It's easier on users.
        }
        return null;
    }

    /**
     * Called by code generated code to get the default attribute value
     * for a given attribute name, or null if none.
     */
    protected XmlAnySimpleType get_default_attribute_value(QName name) {
        SchemaType sType = schemaType();
        SchemaAttributeModel aModel = sType.getAttributeModel();
        if (aModel == null) {
            return null;
        }
        SchemaLocalAttribute sAttr = aModel.getAttribute(name);
        if (sAttr == null) {
            return null;
        }
        return sAttr.getDefaultValue();
    }

    private List<XmlObjectBase> getBaseList(QName elementName) {
        check_orphaned();
        List<XmlObjectBase> targetList = new java.util.ArrayList<>();
        get_store().find_all_element_users(elementName, targetList);
        return targetList;
    }

    private List<XmlObjectBase> getBaseList(QNameSet elementSet) {
        check_orphaned();
        List<XmlObjectBase> targetList = new java.util.ArrayList<>();
        get_store().find_all_element_users(elementSet, targetList);
        return targetList;
    }

    protected <T> T[] getObjectArray(QName elementName, Function<SimpleValue, T> fun, IntFunction<T[]> arrayCon) {
        synchronized (monitor()) {
            return getBaseList(elementName).stream().map(fun).toArray(arrayCon);
        }
    }

    protected <T> T[] getEnumArray(QName elementName, IntFunction<T[]> arrayCon) {
        synchronized (monitor()) {
            return getBaseList(elementName).stream().map(SimpleValue::getEnumValue).toArray(arrayCon);
        }
    }

    protected boolean[] getBooleanArray(QName elementName) {
        synchronized (monitor()) {
            List<XmlObjectBase> targetList = getBaseList(elementName);
            boolean[] result = new boolean[targetList.size()];
            for (int i = 0; i < result.length; i++) {
                result[i] = targetList.get(i).getBooleanValue();
            }
            return result;
        }
    }

    protected float[] getFloatArray(QName elementName) {
        synchronized (monitor()) {
            List<XmlObjectBase> targetList = getBaseList(elementName);
            float[] result = new float[targetList.size()];
            for (int i = 0; i < result.length; i++) {
                result[i] = targetList.get(i).getFloatValue();
            }
            return result;
        }
    }

    protected double[] getDoubleArray(QName elementName) {
        synchronized (monitor()) {
            List<XmlObjectBase> targetList = getBaseList(elementName);
            return targetList.stream()
                .map(org.apache.xmlbeans.SimpleValue.class::cast)
                .mapToDouble(org.apache.xmlbeans.SimpleValue::getDoubleValue)
                .toArray();
        }
    }

    protected byte[] getByteArray(QName elementName) {
        synchronized (monitor()) {
            List<XmlObjectBase> targetList = getBaseList(elementName);
            byte[] result = new byte[targetList.size()];
            for (int i = 0; i < result.length; i++) {
                result[i] = targetList.get(i).getByteValue();
            }
            return result;
        }
    }

    protected short[] getShortArray(QName elementName) {
        synchronized (monitor()) {
            List<XmlObjectBase> targetList = getBaseList(elementName);
            short[] result = new short[targetList.size()];
            for (int i = 0; i < result.length; i++) {
                result[i] = targetList.get(i).getShortValue();
            }
            return result;
        }
    }

    protected int[] getIntArray(QName elementName) {
        synchronized (monitor()) {
            List<XmlObjectBase> targetList = getBaseList(elementName);
            return targetList.stream()
                .map(org.apache.xmlbeans.SimpleValue.class::cast)
                .mapToInt(org.apache.xmlbeans.SimpleValue::getIntValue)
                .toArray();
        }
    }

    protected long[] getLongArray(QName elementName) {
        synchronized (monitor()) {
            List<XmlObjectBase> targetList = getBaseList(elementName);
            return targetList.stream()
                .map(org.apache.xmlbeans.SimpleValue.class::cast)
                .mapToLong(org.apache.xmlbeans.SimpleValue::getLongValue)
                .toArray();
        }
    }

    protected <T extends XmlObject> T[] getXmlObjectArray(QName elementName, T[] arrayCon) {
        synchronized (monitor()) {
            List<XmlObjectBase> list = getBaseList(elementName);
            try {
                return list.toArray(arrayCon);
            } catch (ArrayStoreException e) {
                return reportArrayStoreException(list, arrayCon, e);
            }
        }
    }

    protected <T extends XmlObject> T[] getXmlObjectArray(QNameSet elementSet, T[] arrayCon) {
        synchronized (monitor()) {
            List<XmlObjectBase> list = getBaseList(elementSet);
            try {
                return list.toArray(arrayCon);
            } catch (ArrayStoreException e) {
                return reportArrayStoreException(list, arrayCon, e);
            }
        }
    }

    private <T extends XmlObject> T[] reportArrayStoreException(List<XmlObjectBase> list, T[] arrayCon,
                                                                ArrayStoreException e) {
        if (list.isEmpty()) {
            throw e;
        }
        String elementClass = list.get(0).getClass().getName();
        Class<?> arrayClass = arrayCon.getClass().getComponentType();
        if (arrayClass == null) {
            throw e;
        } else {
            throw new IllegalStateException("The requested return type for the array (" + arrayClass.getName() +
                    ") is not compatible with the type of underlying elements (" + elementClass + ")", e);
        }
    }

    protected <T> T[] getObjectArray(QNameSet elementSet, Function<SimpleValue, T> fun, IntFunction<T[]> arrayCon) {
        synchronized (monitor()) {
            return getBaseList(elementSet).stream().map(fun).toArray(arrayCon);
        }
    }

    protected <T> T[] getEnumArray(QNameSet elementSet, IntFunction<T[]> arrayCon) {
        synchronized (monitor()) {
            return getBaseList(elementSet).stream().map(SimpleValue::getEnumValue).toArray(arrayCon);
        }
    }

    protected boolean[] getBooleanArray(QNameSet elementSet) {
        synchronized (monitor()) {
            List<XmlObjectBase> targetList = getBaseList(elementSet);
            boolean[] result = new boolean[targetList.size()];
            for (int i = 0; i < result.length; i++) {
                result[i] = targetList.get(i).getBooleanValue();
            }
            return result;
        }
    }

    protected float[] getFloatArray(QNameSet elementSet) {
        synchronized (monitor()) {
            List<XmlObjectBase> targetList = getBaseList(elementSet);
            float[] result = new float[targetList.size()];
            for (int i = 0; i < result.length; i++) {
                result[i] = targetList.get(i).getFloatValue();
            }
            return result;
        }
    }

    protected double[] getDoubleArray(QNameSet elementSet) {
        synchronized (monitor()) {
            List<XmlObjectBase> targetList = getBaseList(elementSet);
            return targetList.stream()
                .map(org.apache.xmlbeans.SimpleValue.class::cast)
                .mapToDouble(org.apache.xmlbeans.SimpleValue::getDoubleValue)
                .toArray();
        }
    }

    protected byte[] getByteArray(QNameSet elementSet) {
        synchronized (monitor()) {
            List<XmlObjectBase> targetList = getBaseList(elementSet);
            byte[] result = new byte[targetList.size()];
            for (int i = 0; i < result.length; i++) {
                result[i] = targetList.get(i).getByteValue();
            }
            return result;
        }
    }

    protected short[] getShortArray(QNameSet elementSet) {
        synchronized (monitor()) {
            List<XmlObjectBase> targetList = getBaseList(elementSet);
            short[] result = new short[targetList.size()];
            for (int i = 0; i < result.length; i++) {
                result[i] = targetList.get(i).getShortValue();
            }
            return result;
        }
    }

    protected int[] getIntArray(QNameSet elementSet) {
        synchronized (monitor()) {
            List<XmlObjectBase> targetList = getBaseList(elementSet);
            return targetList.stream()
                .map(org.apache.xmlbeans.SimpleValue.class::cast)
                .mapToInt(org.apache.xmlbeans.SimpleValue::getIntValue)
                .toArray();
        }
    }

    protected long[] getLongArray(QNameSet elementSet) {
        synchronized (monitor()) {
            List<XmlObjectBase> targetList = getBaseList(elementSet);
            return targetList.stream()
                .map(org.apache.xmlbeans.SimpleValue.class::cast)
                .mapToLong(org.apache.xmlbeans.SimpleValue::getLongValue)
                .toArray();
        }
    }

    protected <T extends XmlObject> T[] xgetArray(QName elementName, IntFunction<T[]> arrayCon) {
        synchronized (monitor()) {
            check_orphaned();
            java.util.List<T> targetList = new java.util.ArrayList<>();
            get_store().find_all_element_users(elementName, targetList);
            return targetList.stream().toArray(arrayCon);
        }
    }

    protected <T extends XmlObject> T[] xgetArray(QNameSet elementSet, IntFunction<T[]> arrayCon) {
        synchronized (monitor()) {
            check_orphaned();
            java.util.List<T> targetList = new java.util.ArrayList<>();
            get_store().find_all_element_users(elementSet, targetList);
            return targetList.stream().toArray(arrayCon);
        }
    }
}
