/*
 * Copyright (c) 1997, 2022 Oracle and/or its affiliates. All rights reserved.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Distribution License v. 1.0, which is available at
 * http://www.eclipse.org/org/documents/edl-v10.php.
 *
 * SPDX-License-Identifier: BSD-3-Clause
 */

package com.sun.tools.xjc.reader.xmlschema;

import java.io.StringWriter;
import java.math.BigInteger;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Stack;

import jakarta.activation.MimeTypeParseException;
import jakarta.xml.bind.DatatypeConverter;

import com.sun.codemodel.JJavaName;
import org.glassfish.jaxb.core.v2.WellKnownNamespace;
import com.sun.tools.xjc.ErrorReceiver;
import com.sun.tools.xjc.model.CBuiltinLeafInfo;
import com.sun.tools.xjc.model.CClassInfo;
import com.sun.tools.xjc.model.CClassInfoParent;
import com.sun.tools.xjc.model.CClassRef;
import com.sun.tools.xjc.model.CEnumConstant;
import com.sun.tools.xjc.model.CEnumLeafInfo;
import com.sun.tools.xjc.model.CNonElement;
import com.sun.tools.xjc.model.Model;
import com.sun.tools.xjc.model.TypeUse;
import com.sun.tools.xjc.model.TypeUseFactory;
import com.sun.tools.xjc.reader.Const;
import com.sun.tools.xjc.reader.Ring;
import com.sun.tools.xjc.reader.xmlschema.bindinfo.BIConversion;
import com.sun.tools.xjc.reader.xmlschema.bindinfo.BIEnum;
import com.sun.tools.xjc.reader.xmlschema.bindinfo.BIEnumMember;
import com.sun.tools.xjc.reader.xmlschema.bindinfo.BIProperty;
import com.sun.tools.xjc.reader.xmlschema.bindinfo.BindInfo;
import com.sun.tools.xjc.reader.xmlschema.bindinfo.EnumMemberMode;
import com.sun.tools.xjc.util.MimeTypeRange;

import static org.glassfish.jaxb.core.v2.WellKnownNamespace.XML_MIME_URI;

import org.glassfish.jaxb.core.v2.runtime.SwaRefAdapterMarker;
import com.sun.xml.xsom.XSAttributeDecl;
import com.sun.xml.xsom.XSComplexType;
import com.sun.xml.xsom.XSComponent;
import com.sun.xml.xsom.XSElementDecl;
import com.sun.xml.xsom.XSFacet;
import com.sun.xml.xsom.XSListSimpleType;
import com.sun.xml.xsom.XSRestrictionSimpleType;
import com.sun.xml.xsom.XSSimpleType;
import com.sun.xml.xsom.XSUnionSimpleType;
import com.sun.xml.xsom.XSVariety;
import com.sun.xml.xsom.impl.util.SchemaWriter;
import com.sun.xml.xsom.visitor.XSSimpleTypeFunction;
import com.sun.xml.xsom.visitor.XSVisitor;

import org.xml.sax.Locator;

import javax.xml.XMLConstants;

/**
 * Builds {@link TypeUse} from simple types.
 *
 * <p>
 * This code consists of two main portions. The {@link #compose(XSSimpleType)} method
 * and {@link #composer} forms an outer cycle, which gradually ascends the type
 * inheritance chain until it finds the suitable binding. When it does this
 * {@link #initiatingType} is set to the type which started binding, so that we can refer
 * to the actual constraint facets and such that are applicable on the type.
 *
 * <p>
 * For each intermediate type in the chain, the {@link #find(XSSimpleType)} method
 * is used to find the binding on that type, sine the outer loop is doing the ascending,
 * this method only sees if the current type has some binding available.
 *
 * <p>
 * There is at least one ugly code that you need to aware of
 * when you are modifying the code. See the documentation
 * about <a href="package.html#stref_cust">
 * "simple type customization at the point of reference."</a>
 *
 *
 * @author
 *     Kohsuke Kawaguchi (kohsuke.kawaguchi@sun.com)
 */
public final class SimpleTypeBuilder extends BindingComponent {

    protected final BGMBuilder builder = Ring.get(BGMBuilder.class);

    private final Model model = Ring.get(Model.class);

    /**
     * The component that is refering to the simple type
     * which we are building. This is ugly but necessary
     * to support the customization of simple types at
     * its point of reference. See my comment at the header
     * of this class for details.
     *
     * UGLY: Implemented as a Stack of XSComponent to fix a bug
     */
    public final Stack<XSComponent> refererStack = new Stack<>();

    /**
     * Records what xmime:expectedContentTypes annotations we honored and processed,
     * so that we can later check if the user had these annotations in the places
     * where we didn't anticipate them.
     */
    private final Set<XSComponent> acknowledgedXmimeContentTypes = new HashSet<>();

    /**
     * The type that was originally passed to this {@link SimpleTypeBuilder#build(XSSimpleType)}.
     * Never null.
     */
    private XSSimpleType initiatingType;

    /** {@link TypeUse}s for the built-in types. Read-only. */
    public static final Map<String,TypeUse> builtinConversions;

    /**
     * Default constructor.
     */
    public SimpleTypeBuilder() {}

    /**
     * Entry point from outside. Builds a BGM type expression
     * from a simple type schema component.
     *
     * @param type
     *      the simple type to be bound.
     */
    public TypeUse build( XSSimpleType type ) {
        XSSimpleType oldi = initiatingType;
        this.initiatingType = type;

        TypeUse e = checkRefererCustomization(type);
        if(e==null)
            e = compose(type);

        initiatingType = oldi;

        return e;
    }

    /**
     * A version of the {@link #build(XSSimpleType)} method
     * used to bind the definition of a class generated from
     * the given simple type.
     */
    public TypeUse buildDef( XSSimpleType type ) {
        XSSimpleType oldi = initiatingType;
        this.initiatingType = type;

        TypeUse e = type.apply(composer);

        initiatingType = oldi;

        return e;
    }


    /**
     * Returns a javaType customization specified to the referer, if present.
     * @return can be null.
     */
    private BIConversion getRefererCustomization() {
        BindInfo info = builder.getBindInfo(getReferer());
        BIProperty prop = info.get(BIProperty.class);
        if(prop==null)  return null;
        return prop.getConv();
    }

    public XSComponent getReferer() {
        return refererStack.peek();
    }

    /**
     * Checks if the referer has a conversion customization or not.
     * If it does, use it to bind this simple type. Otherwise
     * return null;
     */
    private TypeUse checkRefererCustomization( XSSimpleType type ) {

        // assertion check. referer must be set properly
        // before the build method is called.
        // since the handling of the simple type point-of-reference
        // customization is very error prone, it deserves a strict
        // assertion check.
        // UGLY CODE WARNING
        XSComponent top = getReferer();

        if( top instanceof XSElementDecl ) {
            // if the parent is element type, its content type must be us.
            XSElementDecl eref = (XSElementDecl)top;
            assert eref.getType()==type;

            // for elements, you can't use <property>,
            // so we allow javaType to appear directly.
            BindInfo info = builder.getBindInfo(top);
            BIConversion conv = info.get(BIConversion.class);
            if(conv!=null) {
                conv.markAsAcknowledged();
                // the conversion is given.
                return conv.getTypeUse(type);
            }
            detectJavaTypeCustomization();
        } else
        if( top instanceof XSAttributeDecl ) {
            XSAttributeDecl aref = (XSAttributeDecl)top;
            assert aref.getType()==type;
            detectJavaTypeCustomization();
        } else
        if( top instanceof XSComplexType ) {
            XSComplexType tref = (XSComplexType)top;
            assert tref.getBaseType()==type || tref.getContentType()==type;
            detectJavaTypeCustomization();
        } else
        if( top == type ) {
            // this means the simple type is built by itself and
            // not because it's referenced by something.
        } else
            // unexpected referer type.
            assert false;

        // now we are certain that the referer is OK.
        // see if it has a conversion customization.
        BIConversion conv = getRefererCustomization();
        if(conv!=null) {
            conv.markAsAcknowledged();
            // the conversion is given.
            return conv.getTypeUse(type);
        } else
            // not found
            return null;
    }

    /**
     * Detect "javaType" customizations placed directly on simple types, rather
     * than being enclosed by "property" and "baseType" customizations (see
     * sec 6.8.1 of the spec).
     *
     * Report an error if any exist.
     */
    private void detectJavaTypeCustomization() {
        BindInfo info = builder.getBindInfo(getReferer());
        BIConversion conv = info.get(BIConversion.class);

        if( conv != null ) {
            // ack this conversion to prevent further error messages
            conv.markAsAcknowledged();

            // report the error
            getErrorReporter().error( conv.getLocation(),
                    Messages.ERR_UNNESTED_JAVATYPE_CUSTOMIZATION_ON_SIMPLETYPE );
        }
    }

    /**
     * Recursively decend the type inheritance chain to find a binding.
     */
    TypeUse compose( XSSimpleType t ) {
        TypeUse e = find(t);
        if(e!=null)     return e;
        return t.apply(composer);
    }

    public final XSSimpleTypeFunction<TypeUse> composer = new XSSimpleTypeFunction<>() {

        @Override
        public TypeUse listSimpleType(XSListSimpleType type) {
            // bind item type individually and then compose them into a list
            // facets on the list shouldn't be taken account when binding item types,
            // so weed to call build(), not compose().
            XSSimpleType itemType = type.getItemType();
            refererStack.push(itemType);
            TypeUse tu = TypeUseFactory.makeCollection(build(type.getItemType()));
            refererStack.pop();
            return tu;
        }

        @Override
        public TypeUse unionSimpleType(XSUnionSimpleType type) {
            boolean isCollection = false;
            for (int i = 0; i < type.getMemberSize(); i++)
                if (type.getMember(i).getVariety() == XSVariety.LIST || type.getMember(i).getVariety() == XSVariety.UNION) {
                    isCollection = true;
                    break;
                }

            TypeUse r = CBuiltinLeafInfo.STRING;
            if (isCollection)
                r = TypeUseFactory.makeCollection(r);
            return r;
        }

        @Override
        public TypeUse restrictionSimpleType(XSRestrictionSimpleType type) {
            // just process the base type.
            return compose(type.getSimpleBaseType());
        }
    };


    /**
     * Checks if there's any binding available on the given type.
     *
     * @return
     *      null if not (which causes the {@link #compose(XSSimpleType)} method
     *      to do ascending.
     */
    private TypeUse find( XSSimpleType type ) {
        TypeUse r;
        boolean noAutoEnum = false;

        // check for user specified conversion
        BindInfo info = builder.getBindInfo(type);
        BIConversion conv = info.get(BIConversion.class);

        if( conv!=null ) {
            // a conversion was found
            conv.markAsAcknowledged();
            return conv.getTypeUse(type);
        }

        // look for enum customization, which is another user specified conversion
        BIEnum en = info.get(BIEnum.class);
        if( en!=null ) {
            en.markAsAcknowledged();

            if(!en.isMapped()) {
                noAutoEnum = true;
            } else {
                // if an enum customization is specified, make sure
                // the type is OK
                if( !canBeMappedToTypeSafeEnum(type) ) {
                    getErrorReporter().error( en.getLocation(),
                        Messages.ERR_CANNOT_BE_TYPE_SAFE_ENUM );
                    getErrorReporter().error( type.getLocator(),
                        Messages.ERR_CANNOT_BE_TYPE_SAFE_ENUM_LOCATION );
                    // recover by ignoring this customization
                    return null;
                }

                // reference?
                if(en.ref!=null) {
                    if(!JJavaName.isFullyQualifiedClassName(en.ref)) {
                        Ring.get(ErrorReceiver.class).error( en.getLocation(),
                            Messages.format(Messages.ERR_INCORRECT_CLASS_NAME, en.ref) );
                        // recover by ignoring @ref
                        return null;
                    }

                    return new CClassRef(model, type, en, info.toCustomizationList() );
                }

                // list and union cannot be mapped to a type-safe enum,
                // so in this stage we can safely cast it to XSRestrictionSimpleType
                return bindToTypeSafeEnum( (XSRestrictionSimpleType)type,
                        en.className, en.javadoc, en.members,
                        getEnumMemberMode().getModeWithEnum(),
                        en.getLocation() );
            }
        }


        // if the type is built in, look for the default binding
        if(XMLConstants.W3C_XML_SCHEMA_NS_URI.equals(type.getTargetNamespace())) {
            String name = type.getName();
            if(name!=null) {
                r = lookupBuiltin(name);
                if(r!=null)
                    return r;
            }
        }

        // also check for swaRef
        if(type.getTargetNamespace().equals(WellKnownNamespace.SWA_URI)) {
            String name = type.getName();
            if(name!=null && name.equals("swaRef"))
                return CBuiltinLeafInfo.STRING.makeAdapted(SwaRefAdapterMarker.class,false);
        }


        // see if this type should be mapped to a type-safe enumeration by default.
        // if so, built a EnumXDucer from it and return it.
        if(type.isRestriction() && !noAutoEnum) {
            XSRestrictionSimpleType rst = type.asRestriction();
            if(shouldBeMappedToTypeSafeEnumByDefault(rst)) {
                r = bindToTypeSafeEnum(rst,null,null, Collections.emptyMap(),
                            getEnumMemberMode(),null);
                if(r!=null)
                    return r;
            }
        }

        return (CNonElement)getClassSelector()._bindToClass(type,null,false);
    }

    private static Set<XSRestrictionSimpleType> reportedEnumMemberSizeWarnings;

    /**
     * Returns true if a type-safe enum should be created from
     * the given simple type by default without an explicit {@code <jaxb:enum>} customization.
     */
    private boolean shouldBeMappedToTypeSafeEnumByDefault( XSRestrictionSimpleType type ) {

        // if not, there will be a problem wrt the class name of this type safe enum type.
        if( type.isLocal() )    return false;

        // if redefined, we should map the new definition, not the old one.
        if( type.getRedefinedBy()!=null )   return false;

        List<XSFacet> facets = type.getDeclaredFacets(XSFacet.FACET_ENUMERATION);
        if( facets.isEmpty() )
            // if the type itself doesn't have the enumeration facet,
            // it won't be mapped to a type-safe enum.
            return false;

        if(facets.size() > builder.getGlobalBinding().getDefaultEnumMemberSizeCap()) {
            // if there are too many facets, it's not very useful
            // produce warning when simple type is not mapped to enum
            // see issue https://jaxb.dev.java.net/issues/show_bug.cgi?id=711

            if(reportedEnumMemberSizeWarnings == null)
                reportedEnumMemberSizeWarnings = new HashSet<>();

            if(!reportedEnumMemberSizeWarnings.contains(type)) {
                getErrorReporter().warning(type.getLocator(), Messages.WARN_ENUM_MEMBER_SIZE_CAP,
                        type.getName(), facets.size(), builder.getGlobalBinding().getDefaultEnumMemberSizeCap());

                reportedEnumMemberSizeWarnings.add(type);
            }

            return false;
        }

        if( !canBeMappedToTypeSafeEnum(type) )
            // we simply can't map this to an enumeration
            return false;

        // check for collisions among constant names. if a collision will happen,
        // don't try to bind it to an enum.

        // return true only when this type is derived from one of the "enum base type".
        for( XSSimpleType t = type; t!=null; t=t.getSimpleBaseType() )
            if( t.isGlobal() && builder.getGlobalBinding().canBeMappedToTypeSafeEnum(t) )
                return true;

        return false;
    }


    private static final Set<String> builtinTypeSafeEnumCapableTypes;

    static {

        // see a bullet of 6.5.1 of the spec.
        String[] typeNames = new String[] {
            "string", "boolean", "float", "decimal", "double", "anyURI"
        };
        Set<String> s = new HashSet<>(Arrays.asList(typeNames));

        builtinTypeSafeEnumCapableTypes = Collections.unmodifiableSet(s);
    }


    /**
     * Returns true if the given simple type can be mapped to a
     * type-safe enum class.
     *
     * <p>
     * JAXB spec places a restrictrion as to what type can be
     * mapped to a type-safe enum. This method enforces this
     * constraint.
     */
    public static boolean canBeMappedToTypeSafeEnum( XSSimpleType type ) {
        do {
            if( XMLConstants.W3C_XML_SCHEMA_NS_URI.equals(type.getTargetNamespace()) ) {
                // type must be derived from one of these types
                String localName = type.getName();
                if( localName!=null ) {
                    if( localName.equals("anySimpleType") )
                        return false;   // catch all case
                    if( localName.equals("ID") || localName.equals("IDREF") )
                        return false;   // not ID/IDREF

                    // other allowed list
                    if( builtinTypeSafeEnumCapableTypes.contains(localName) )
                        return true;
                }
            }

            type = type.getSimpleBaseType();
        } while( type!=null );

        return false;
    }



    /**
     * Builds a type-safe enum conversion from a simple type
     * with enumeration facets.
     *
     * @param className
     *      The class name of the type-safe enum. Or null to
     *      create a default name.
     * @param javadoc
     *      Additional javadoc that will be added at the beginning of the
     *      class, or null if none is necessary.
     * @param members
     *      A map from enumeration values (as String) to BIEnumMember objects.
     *      if some of the value names need to be overrided.
     *      Cannot be null, but the map may not contain entries
     *      for all enumeration values.
     * @param loc
     *      The source location where the above customizations are
     *      specified, or null if none is available.
     */
    private TypeUse bindToTypeSafeEnum( XSRestrictionSimpleType type,
                                        String className, String javadoc, Map<String,BIEnumMember> members,
                                        EnumMemberMode mode, Locator loc ) {

        if( loc==null )  // use the location of the simple type as the default
            loc = type.getLocator();

        if( className==null ) {
            // infer the class name. For this to be possible,
            // the simple type must be a global one.
            if( !type.isGlobal() ) {
                getErrorReporter().error( loc, Messages.ERR_NO_ENUM_NAME_AVAILABLE );
                // recover by returning a meaningless conversion
                return CBuiltinLeafInfo.STRING;
            }
            className = type.getName();
        }

        // we apply name conversion in any case
        className = builder.deriveName(className,type);

        {// compute Javadoc
            StringWriter out = new StringWriter();
            SchemaWriter sw = new SchemaWriter(out);
            type.visit((XSVisitor)sw);

            if(javadoc!=null)   javadoc += "\n\n";
            else                javadoc = "";

            javadoc += Messages.format( Messages.JAVADOC_HEADING, type.getName() )
                +"\n<pre>{@code\n"+out.getBuffer()+"}</pre>";

        }

        // build base type
        refererStack.push(type.getSimpleBaseType());
        TypeUse use = build(type.getSimpleBaseType());
        refererStack.pop();

        if(use.isCollection())
            return null;    // can't bind a list to enum constant

        CNonElement baseDt = use.getInfo();   // for now just ignore that case

        if(baseDt instanceof CClassInfo)
            return null;    // can't bind to an enum if the base is a class, since we don't have the value constrctor

        // if the member names collide, re-generate numbered constant names.
        XSFacet[] errorRef = new XSFacet[1];
        List<CEnumConstant> memberList = buildCEnumConstants(type, false, members, errorRef);
        if(memberList==null || checkMemberNameCollision(memberList)!=null) {
            switch(mode) {
            case SKIP:
                // abort
                return null;
            case ERROR:
                // error
                if(memberList==null) {
                    getErrorReporter().error( errorRef[0].getLocator(),
                        Messages.ERR_CANNOT_GENERATE_ENUM_NAME,
                        errorRef[0].getValue() );
                } else {
                    CEnumConstant[] collision = checkMemberNameCollision(memberList);
                    getErrorReporter().error( collision[0].getLocator(),
                        Messages.ERR_ENUM_MEMBER_NAME_COLLISION,
                        collision[0].getName() );
                    getErrorReporter().error( collision[1].getLocator(),
                        Messages.ERR_ENUM_MEMBER_NAME_COLLISION_RELATED );
                }
                return null;    // recover from error
            case GENERATE:
                // generate
                memberList = buildCEnumConstants(type,true,members,null);
                break;
            }
        }
        if(memberList.isEmpty()) {
            getErrorReporter().error( loc, Messages.ERR_NO_ENUM_FACET );
            return null;
        }

        // use the name of the simple type as the name of the class.
        CClassInfoParent scope;
        if(type.isGlobal())
            scope = new CClassInfoParent.Package(getClassSelector().getPackage(type.getTargetNamespace()));
        else
            scope = getClassSelector().getClassScope();
        CEnumLeafInfo xducer = new CEnumLeafInfo( model, BGMBuilder.getName(type), scope,
            className, baseDt, memberList, type,
            builder.getBindInfo(type).toCustomizationList(), loc );
        xducer.javadoc = javadoc;

        BIConversion conv = new BIConversion.Static( type.getLocator(),xducer);
        conv.markAsAcknowledged();

        // attach this new conversion object to this simple type
        // so that successive look up will use the same object.
        builder.getOrCreateBindInfo(type).addDecl(conv);

        return conv.getTypeUse(type);
    }

    /**
     *
     * @param errorRef
     *      if constant names couldn't be generated, return a reference to that enum facet.
     * @return
     *      null if unable to generate names for some of the constants.
     */
    private List<CEnumConstant> buildCEnumConstants(XSRestrictionSimpleType type, boolean needsToGenerateMemberName, Map<String, BIEnumMember> members, XSFacet[] errorRef) {
        List<CEnumConstant> memberList = new ArrayList<>();
        int idx=1;
        Set<String> enums = new HashSet<>(); // to avoid duplicates. See issue #366

        for( XSFacet facet : type.getDeclaredFacets(XSFacet.FACET_ENUMERATION)) {
            String name=null;
            String mdoc=builder.getBindInfo(facet).getDocumentation();

            if(!enums.add(facet.getValue().value))
                continue;   // ignore the 2nd occasion

            if( needsToGenerateMemberName ) {
                // generate names for all member names.
                // this will even override names specified by the user. that's crazy.
                name = "VALUE_"+(idx++);
            } else {
                String facetValue = facet.getValue().value;
                BIEnumMember mem = members.get(facetValue);
                if( mem==null )
                    // look at the one attached to the facet object
                    mem = builder.getBindInfo(facet).get(BIEnumMember.class);

                if (mem!=null) {
                    name = mem.name;
                    if (mdoc == null) {
                        mdoc = mem.javadoc;
                    }
                }

                if(name==null) {
                    StringBuilder sb = new StringBuilder();
                    for( int i=0; i<facetValue.length(); i++) {
                        char ch = facetValue.charAt(i);
                        if(Character.isJavaIdentifierPart(ch))
                            sb.append(ch);
                        else
                            sb.append('_');
                    }
                    name = model.getNameConverter().toConstantName(sb.toString());
                }
            }

            if(!JJavaName.isJavaIdentifier(name)) {
                if(errorRef!=null)  errorRef[0] = facet;
                return null;    // unable to generate a name
            }

            memberList.add(new CEnumConstant(name,mdoc,facet.getValue().value,facet,builder.getBindInfo(facet).toCustomizationList(),facet.getLocator()));
        }
        return memberList;
    }

    /**
     * Returns non-null if {@link CEnumConstant}s have name collisions among them.
     *
     * @return
     *      if there's a collision, return two {@link CEnumConstant}s that collided.
     *      otherwise return null.
     */
    private CEnumConstant[] checkMemberNameCollision( List<CEnumConstant> memberList ) {
        Map<String,CEnumConstant> names = new HashMap<>();
        for (CEnumConstant c : memberList) {
            CEnumConstant old = names.put(c.getName(),c);
            if(old!=null)
                // collision detected
                return new CEnumConstant[]{old,c};
        }
        return null;
    }



    private EnumMemberMode getEnumMemberMode() {
        return builder.getGlobalBinding().getEnumMemberMode();
    }

    private TypeUse lookupBuiltin( String typeLocalName ) {
        if(typeLocalName.equals("integer") || typeLocalName.equals("long")) {
            /*
                attempt an optimization so that we can
                improve the binding for types like this:

                <simpleType>
                  <restriciton baseType="integer">
                    <maxInclusive value="100" />
                  </
                </

                ... to int, not BigInteger.
            */

            BigInteger xe = readFacet(XSFacet.FACET_MAXEXCLUSIVE,-1);
            BigInteger xi = readFacet(XSFacet.FACET_MAXINCLUSIVE,0);
            BigInteger max = min(xe,xi);    // most restrictive one takes precedence

            if(max!=null) {
                BigInteger ne = readFacet(XSFacet.FACET_MINEXCLUSIVE,+1);
                BigInteger ni = readFacet(XSFacet.FACET_MININCLUSIVE,0);
                BigInteger min = max(ne,ni);

                if(min!=null) {
                    if(min.compareTo(INT_MIN )>=0 && max.compareTo(INT_MAX )<=0)
                        typeLocalName = "int";
                    else
                    if(min.compareTo(LONG_MIN)>=0 && max.compareTo(LONG_MAX)<=0)
                        typeLocalName = "long";
                }
            }
        } else
        if(typeLocalName.equals("boolean") && isRestrictedTo0And1()) {
            // this is seen in the SOAP schema and too common to ignore
            return CBuiltinLeafInfo.BOOLEAN_ZERO_OR_ONE;
        } else
        if(typeLocalName.equals("base64Binary")) {
            return lookupBinaryTypeBinding();
        } else
        if(typeLocalName.equals("anySimpleType")) {
            if(getReferer() instanceof XSAttributeDecl || getReferer() instanceof XSSimpleType)
                return CBuiltinLeafInfo.STRING;
            else
                return CBuiltinLeafInfo.ANYTYPE;
        }
        return builtinConversions.get(typeLocalName);
    }

    /**
     * Decides the way xs:base64Binary binds.
     *
     * This method checks the expected media type.
     */
    private TypeUse lookupBinaryTypeBinding() {
        XSComponent referer = getReferer();
        String emt = referer.getForeignAttribute(XML_MIME_URI, Const.EXPECTED_CONTENT_TYPES);
        if(emt!=null) {
            acknowledgedXmimeContentTypes.add(referer);
            try {
                // see http://www.xml.com/lpt/a/2004/07/21/dive.html
                List<MimeTypeRange> types = MimeTypeRange.parseRanges(emt);
                MimeTypeRange mt = MimeTypeRange.merge(types);

                // see spec table I-1 in appendix I section 2.1.1 for bindings
                if(mt.majorType.equalsIgnoreCase("image"))
                    return CBuiltinLeafInfo.IMAGE.makeMimeTyped(mt.toMimeType());

                if(( mt.majorType.equalsIgnoreCase("application") || mt.majorType.equalsIgnoreCase("text"))
                        && isXml(mt.subType))
                    return CBuiltinLeafInfo.XML_SOURCE.makeMimeTyped(mt.toMimeType());

                if((mt.majorType.equalsIgnoreCase("text") && (mt.subType.equalsIgnoreCase("plain")) )) {
                    return CBuiltinLeafInfo.STRING.makeMimeTyped(mt.toMimeType());
                }

                return CBuiltinLeafInfo.DATA_HANDLER.makeMimeTyped(mt.toMimeType());
            } catch (ParseException | MimeTypeParseException e) {
                getErrorReporter().error( referer.getLocator(),
                    Messages.format(Messages.ERR_ILLEGAL_EXPECTED_MIME_TYPE,emt, e.getMessage()) );
                // recover by using the default
            }
        }
        // default
        return CBuiltinLeafInfo.BASE64_BYTE_ARRAY;
    }

    public boolean isAcknowledgedXmimeContentTypes(XSComponent c) {
        return acknowledgedXmimeContentTypes.contains(c);
    }

    /**
     * Returns true if the specified sub-type is an XML type.
     */
    private boolean isXml(String subType) {
        return subType.equals("xml") || subType.endsWith("+xml");
    }

    /**
     * Returns true if the {@link #initiatingType} is restricted
     * to '0' and '1'. This logic is not complete, but it at least
     * finds the such definition in SOAP @mustUnderstand.
     */
    private boolean isRestrictedTo0And1() {
        XSFacet pattern = initiatingType.getFacet(XSFacet.FACET_PATTERN);
        if(pattern!=null) {
            String v = pattern.getValue().value;
            if(v.equals("0|1") || v.equals("1|0") || v.equals("\\d"))
                return true;
        }
        XSFacet enumf = initiatingType.getFacet(XSFacet.FACET_ENUMERATION);
        if(enumf!=null) {
            String v = enumf.getValue().value;
            return v.equals("0") || v.equals("1");
        }
        return false;
    }

    private BigInteger readFacet(String facetName,int offset) {
        XSFacet me = initiatingType.getFacet(facetName);
        if(me==null)
            return null;
        BigInteger bi = DatatypeConverter.parseInteger(me.getValue().value);
        if(offset!=0)
            bi = bi.add(BigInteger.valueOf(offset));
        return bi;
    }

    private BigInteger min(BigInteger a, BigInteger b) {
        if(a==null) return b;
        if(b==null) return a;
        return a.min(b);
    }

    private BigInteger max(BigInteger a, BigInteger b) {
        if(a==null) return b;
        if(b==null) return a;
        return a.max(b);
    }

    private static final BigInteger LONG_MIN = BigInteger.valueOf(Long.MIN_VALUE);
    private static final BigInteger LONG_MAX = BigInteger.valueOf(Long.MAX_VALUE);
    private static final BigInteger INT_MIN = BigInteger.valueOf(Integer.MIN_VALUE);
    private static final BigInteger INT_MAX = BigInteger.valueOf(Integer.MAX_VALUE);

    static {
        // list of datatypes which have built-in conversions.
        // note that although xs:token and xs:normalizedString are not
        // specified in the spec, they need to be here because they
        // have different whitespace normalization semantics.
        Map<String,TypeUse> m = new HashMap<>();

        // TODO: this is so dumb
        m.put("string",         CBuiltinLeafInfo.STRING);
        m.put("anyURI",         CBuiltinLeafInfo.STRING);
        m.put("boolean",        CBuiltinLeafInfo.BOOLEAN);
        // we'll also look at the expected media type, so don't just add this to the map
        // m.put("base64Binary",   CBuiltinLeafInfo.BASE64_BYTE_ARRAY);
        m.put("hexBinary",      CBuiltinLeafInfo.HEXBIN_BYTE_ARRAY);
        m.put("float",          CBuiltinLeafInfo.FLOAT);
        m.put("decimal",        CBuiltinLeafInfo.BIG_DECIMAL);
        m.put("integer",        CBuiltinLeafInfo.BIG_INTEGER);
        m.put("long",           CBuiltinLeafInfo.LONG);
        m.put("unsignedInt",    CBuiltinLeafInfo.LONG);
        m.put("int",            CBuiltinLeafInfo.INT);
        m.put("unsignedShort",  CBuiltinLeafInfo.INT);
        m.put("short",          CBuiltinLeafInfo.SHORT);
        m.put("unsignedByte",   CBuiltinLeafInfo.SHORT);
        m.put("byte",           CBuiltinLeafInfo.BYTE);
        m.put("double",         CBuiltinLeafInfo.DOUBLE);
        m.put("QName",          CBuiltinLeafInfo.QNAME);
        m.put("NOTATION",       CBuiltinLeafInfo.QNAME);
        m.put("dateTime",       CBuiltinLeafInfo.CALENDAR);
        m.put("date",           CBuiltinLeafInfo.CALENDAR);
        m.put("time",           CBuiltinLeafInfo.CALENDAR);
        m.put("gYearMonth",     CBuiltinLeafInfo.CALENDAR);
        m.put("gYear",          CBuiltinLeafInfo.CALENDAR);
        m.put("gMonthDay",      CBuiltinLeafInfo.CALENDAR);
        m.put("gDay",           CBuiltinLeafInfo.CALENDAR);
        m.put("gMonth",         CBuiltinLeafInfo.CALENDAR);
        m.put("duration",       CBuiltinLeafInfo.DURATION);
        m.put("token",          CBuiltinLeafInfo.TOKEN);
        m.put("normalizedString",CBuiltinLeafInfo.NORMALIZED_STRING);
        m.put("ID",             CBuiltinLeafInfo.ID);
        m.put("IDREF",          CBuiltinLeafInfo.IDREF);

        builtinConversions = Collections.unmodifiableMap(m);
        // TODO: handling dateTime, time, and date type
//        String[] names = {
//            "date", "dateTime", "time", "hexBinary" };
    }
}
