package org.xmlet.xsdparser.xsdelements.xsdrestrictions;

import org.xmlet.xsdparser.core.XsdParserCore;
import org.xmlet.xsdparser.xsdelements.XsdAnnotatedElements;
import org.xmlet.xsdparser.xsdelements.visitors.XsdAnnotatedElementsVisitor;

import javax.validation.constraints.NotNull;
import java.util.Map;

/**
 * This class serves as a base to every different restriction that has its restricting parameter defined as an {@link String}.
 * Classes like {@link XsdPattern} or {@link XsdEnumeration} should extend this class.
 */
public class XsdStringRestrictions extends XsdAnnotatedElements{

    private XsdAnnotatedElementsVisitor visitor = new XsdAnnotatedElementsVisitor(this);

    /**
     * The value of associated with a given restriction. This field has different meanings depending on the concrete
     * restriction, e.g. if the concrete class is {@link XsdEnumeration} this field means that the attribute which
     * has the restriction can only have the value that is specified in this field.
     */
    private String value;

    XsdStringRestrictions(@NotNull XsdParserCore parser, @NotNull Map<String, String> elementFieldsMapParam) {
        super(parser, elementFieldsMapParam);

        value = attributesMap.getOrDefault(VALUE_TAG, value);
    }

    @Override
    public XsdAnnotatedElementsVisitor getVisitor() {
        return visitor;
    }

    /**
     * Compares two different objects of this type.
     * @param o1 The first object.
     * @param o2 The object to compare.
     * @return True if the value of both classes is different, False if the value is equal.
     */
    public static boolean hasDifferentValue(XsdStringRestrictions o1, XsdStringRestrictions o2) {
        if (o1 == null && o2 == null) {
            return false;
        }

        String o1Value = null;
        String o2Value;

        if (o1 != null) {
            o1Value = o1.getValue();
        }

        if (o2 != null) {
            o2Value = o2.getValue();
            return o2Value.equals(o1Value);
        }

        return false;
    }

    public String getValue() {
        return value;
    }
}


package org.xmlet.xsdparser.xsdelements.visitors;

import org.xmlet.xsdparser.xsdelements.XsdAnnotatedElements;
import org.xmlet.xsdparser.xsdelements.XsdAttribute;
import org.xmlet.xsdparser.xsdelements.XsdAttributeGroup;
import org.xmlet.xsdparser.xsdelements.elementswrapper.ConcreteElement;
import org.xmlet.xsdparser.xsdelements.elementswrapper.NamedConcreteElement;
import org.xmlet.xsdparser.xsdelements.elementswrapper.ReferenceBase;
import org.xmlet.xsdparser.xsdelements.elementswrapper.UnsolvedReference;

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;

/**
 * Represents the restrictions of the all elements that can contain {@link XsdAttribute} and {@link XsdAttributeGroup}.
 * This visitor contains the {@link AttributesVisitor#attributes} and {@link AttributesVisitor#attributeGroups} that
 * belong to the owner and apart from receiving the attributes in the visit method this visitor also performs the
 * {@link AttributesVisitor#replaceUnsolvedAttributes} method, which is a method that was shared by all the types that
 * contained a list of {@link XsdAttribute} and {@link XsdAttributeGroup} objects.
 * Can also have xsd:annotation children as per inheritance of {@link XsdAnnotatedElementsVisitor}.
 */
public abstract class AttributesVisitor extends XsdAnnotatedElementsVisitor {

    /**
     * The list of {@link XsdAttributeGroup} instances received by this visitor, wrapped in a {@link ReferenceBase} object.
     */
    private List<ReferenceBase> attributeGroups = new ArrayList<>();

    /**
     * The list of {@link XsdAttribute} instances received by this visitor, wrapped in a {@link ReferenceBase} object.
     */
    private List<ReferenceBase> attributes = new ArrayList<>();

    AttributesVisitor(XsdAnnotatedElements owner){
        super(owner);
    }

    @Override
    public void visit(XsdAttribute attribute) {
        super.visit(attribute);

        attributes.add(ReferenceBase.createFromXsd(attribute));
    }

    @Override
    public void visit(XsdAttributeGroup attributeGroup) {
        super.visit(attributeGroup);

        attributeGroups.add(ReferenceBase.createFromXsd(attributeGroup));
    }

    public void setAttributes(List<ReferenceBase> attributes) {
        this.attributes = attributes;
    }

    public void setAttributeGroups(List<ReferenceBase> attributeGroups) {
        this.attributeGroups = attributeGroups;
    }

    /**
     * @return All the wrapped {@link XsdAttribute} objects received by this visitor.
     */
    public List<ReferenceBase> getAttributes() {
        return attributes;
    }

    /**
     * @return All the wrapped {@link XsdAttributeGroup} objects received by this visitor.
     */
    public List<ReferenceBase> getAttributeGroups() {
        return attributeGroups;
    }

    /**
     * @return All the {@link XsdAttribute} objects that are fully resolved by this visitor. The {@link XsdAttribute}
     * objects wrapped in {@link UnsolvedReference} objects are not returned.
     */
    public Stream<XsdAttribute> getXsdAttributes() {
        return attributes.stream()
                .filter(attribute -> attribute instanceof ConcreteElement)
                .filter(attribute -> attribute.getElement() instanceof  XsdAttribute)
                .map(attribute -> (XsdAttribute)attribute.getElement());
    }

    /**
     * @return All the {@link XsdAttributeGroup} objects that are fully resolved by this visitor. The
     * {@link XsdAttributeGroup} objects wrapped in {@link UnsolvedReference} objects are not returned.
     */
    public Stream<XsdAttributeGroup> getXsdAttributeGroup() {
        return attributeGroups.stream()
                .filter(attributeGroup -> attributeGroup instanceof ConcreteElement)
                .map(attributeGroup -> (XsdAttributeGroup) attributeGroup.getElement());
    }

    /**
     * Tries to match the received {@link NamedConcreteElement} object, with any of the elements present either in
     * {@link AttributesVisitor#attributeGroups} or {@link AttributesVisitor#attributes}. If a match occurs this method
     * performs all the required actions to fully exchange the {@link UnsolvedReference} object with the element parameter.
     * @param element The resolved element that will be match with the contents of this visitor in order to assert if
     *                there is anything to replace.
     */
    public void replaceUnsolvedAttributes(NamedConcreteElement element){
        if (element.getElement() instanceof XsdAttributeGroup){
            attributeGroups.stream()
                    .filter(attributeGroup -> attributeGroup instanceof UnsolvedReference && ((UnsolvedReference) attributeGroup).getRef().equals(element.getName()))
                    .findFirst().ifPresent(referenceBase -> {
                attributeGroups.remove(referenceBase);
                attributeGroups.add(element);
                attributes.addAll(element.getElement().getElements());

                element.getElement().setParent(getOwner());
            });
        }

        if (element.getElement() instanceof XsdAttribute ){
            attributes.stream()
                    .filter(attribute -> attribute instanceof UnsolvedReference && ((UnsolvedReference) attribute).getRef().equals(element.getName()))
                    .findFirst().ifPresent(referenceBase -> {
                attributes.remove(referenceBase);
                attributes.add(element);
                element.getElement().setParent(getOwner());
            });
        }
    }
}


package org.xmlet.xsdparser.xsdelements;

import org.xmlet.xsdparser.core.XsdParserCore;
import org.xmlet.xsdparser.xsdelements.elementswrapper.ConcreteElement;
import org.xmlet.xsdparser.xsdelements.elementswrapper.NamedConcreteElement;
import org.xmlet.xsdparser.xsdelements.elementswrapper.ReferenceBase;
import org.xmlet.xsdparser.xsdelements.elementswrapper.UnsolvedReference;

import javax.validation.constraints.NotNull;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Stream;

/**
 * A class that serves as a base class to three classes that share similarities, {@link XsdAll}, {@link XsdChoice} and
 * {@link XsdSequence}. Those three classes share {@link XsdMultipleElements#elements} which is a list of
 * {@link XsdAbstractElement} objects contained in each of these types. The types of the instances present in the
 * {@link XsdMultipleElements#elements} list depends on the concrete type, {@link XsdAll}, {@link XsdChoice} or
 * {@link XsdSequence}.
 */
public abstract class XsdMultipleElements extends XsdAnnotatedElements {

    /**
     * A list of elements that are contained in the concrete implementation of the {@link XsdMultipleElements} instance.
     */
    private List<ReferenceBase> elements = new ArrayList<>();

    XsdMultipleElements(@NotNull XsdParserCore parser, @NotNull Map<String, String> elementFieldsMapParam) {
        super(parser, elementFieldsMapParam);
    }

    /**
     * Replaces possible {@link UnsolvedReference} objects in the {@link XsdMultipleElements#elements} if any of their
     * {@link UnsolvedReference#ref} field matches the {@link NamedConcreteElement#name} field.
     * @param elementWrapper A {@link NamedConcreteElement} with a name that will replace an {@link UnsolvedReference}
     *                       object, if a match between the {@link NamedConcreteElement#name} attribute and the
     *                       {@link UnsolvedReference#ref} attribute.
     */
    @Override
    public void replaceUnsolvedElements(NamedConcreteElement elementWrapper) {
        if (elementWrapper.getElement() instanceof XsdElement){
            super.replaceUnsolvedElements(elementWrapper);
        }

        if (elementWrapper.getElement() instanceof XsdGroup){
            elements.add(elementWrapper);

            this.elements.removeIf(element ->
               element instanceof UnsolvedReference && ((UnsolvedReference) element).getRef().equals(elementWrapper.getName())
            );
        }
    }

    /**
     * @return All the elements received in the parsing process.
     */
    @Override
    public List<ReferenceBase> getElements(){
        return elements;
    }

    /**
     * @return The elements that are fully resolved. The {@link UnsolvedReference} objects aren't returned.
     */
    @Override
    public Stream<XsdAbstractElement> getXsdElements() {
        return elements.stream()
                .filter(element -> element instanceof ConcreteElement)
                .map(ReferenceBase::getElement);
    }

    public void addElement(XsdAbstractElement element){
        this.elements.add(ReferenceBase.createFromXsd(element));
    }

    /**
     * @param element The element containing the child to return.
     * @return The childElement as a {@link XsdAll} object or null if childElement isn't a {@link XsdAll} instance.
     */
    @SuppressWarnings("unused")
    public static XsdAll getChildAsdAll(XsdMultipleElements element) {
        return element instanceof XsdAll ? (XsdAll) element : null;
    }

    /**
     * @param element The element containing the child to return.
     * @return The childElement as a {@link XsdChoice} object or null if childElement isn't a {@link XsdChoice} instance.
     */
    @SuppressWarnings("unused")
    public static XsdChoice getChildAsChoice(XsdMultipleElements element) {
        return element instanceof XsdChoice ? (XsdChoice) element : null;
    }

    /**
     * @param element The element containing the child to return.
     * @return The childElement as a {@link XsdSequence} object or null if childElement isn't a {@link XsdSequence} instance.
     */
    @SuppressWarnings("unused")
    public static XsdSequence getChildAsSequence(XsdMultipleElements element) {
        return element instanceof XsdSequence ? (XsdSequence) element : null;
    }
}


package org.xmlet.xsdparser.core;

import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.xml.sax.SAXException;
import org.xmlet.xsdparser.xsdelements.XsdSchema;
import org.xmlet.xsdparser.xsdelements.exceptions.ParsingException;

import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.logging.Level;
import java.util.logging.Logger;

public class XsdParserJar extends XsdParserCore {

    private static ClassLoader classLoader;

    /**
     * Adds the jar file represented the jarPath to the classpath and proceeds by parsing the file present in the
     * previous jar with the path filePath.
     * @param jarPath The path to the jar file.
     * @param filePath The filePath of the XSD file to parse. Relative to the Jar structure.
     */
    public XsdParserJar(String jarPath, String filePath){
        setClassLoader(jarPath);

        parseJarFile(classLoader.getResourceAsStream(filePath));

        int index = 0;

        while (schemaLocations.size() > index){
            String schemaLocation = schemaLocations.get(index);
            parseJarFile(classLoader.getResourceAsStream(schemaLocation));
            ++index;
        }

        resolveRefs();
    }

    /**
     * Parses the XSD file represented by the received InputStream.
     * @param inputStream The inputStream of the XSD file.
     */
    private void parseJarFile(InputStream inputStream) {
        //https://www.mkyong.com/java/how-to-read-xml-file-in-java-dom-parser/
        try {
            Node schemaNode = getSchemaNode(inputStream);

            if (isXsdSchema(schemaNode)){
                XsdSchema.parse(this, schemaNode);
            } else {
                throw new ParsingException("The top level element of a XSD file should be the xsd:schema node.");
            }
        } catch (SAXException | IOException | ParserConfigurationException e) {
            Logger.getAnonymousLogger().log(Level.SEVERE, "Exception while parsing.", e);
        }
    }

    private Node getSchemaNode(InputStream inputStream) throws ParserConfigurationException, IOException, SAXException {
        Document doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(inputStream);

        doc.getDocumentElement().normalize();

        return doc.getFirstChild();
    }

    /**
     * Creates a new class loader, replacing the current one, having another path added to the classpath. The new
     * path is the path to the jar received in this class constructor.
     * @param jarPath The path of the jar file.
     */
    private void setClassLoader(String jarPath) {
        if (!jarPath.endsWith(".jar")){
            throw new ParsingException("The jarPath received doesn't represent a jar file.");
        }

        ClassLoader originalCl = Thread.currentThread().getContextClassLoader();

        URL url = originalCl.getResource(jarPath);

        if (url == null){
            try {
                url = new URL("file:/" + jarPath);
            } catch (MalformedURLException e) {
                throw new ParsingException("Invalid jar name.");
            }
        }

        // Create class loader using given codebase
        // Use prevCl as parent to maintain current visibility
        ClassLoader urlCl = URLClassLoader.newInstance(new URL[]{url}, originalCl);

        Thread.currentThread().setContextClassLoader(urlCl);

        classLoader = urlCl;
    }
}


package org.xmlet.xsdparser.xsdelements;

import org.w3c.dom.Node;
import org.xmlet.xsdparser.core.XsdParserCore;
import org.xmlet.xsdparser.xsdelements.elementswrapper.ReferenceBase;
import org.xmlet.xsdparser.xsdelements.exceptions.VisitorNotFoundException;
import org.xmlet.xsdparser.xsdelements.visitors.XsdAbstractElementVisitor;

import javax.validation.constraints.NotNull;
import java.util.Map;

/**
 * This class serves as a base to both {@link XsdAppInfo} and {@link XsdDocumentation} since they share similarities.
 */
public abstract class XsdAnnotationChildren extends XsdAbstractElement {

    /**
     * An URI that specifies a source for the application information.
     */
    private String source;

    /**
     * The textual content of the current element, either {@link XsdAppInfo} or {@link XsdDocumentation}.
     */
    private String content;

    XsdAnnotationChildren(@NotNull XsdParserCore parser, @NotNull Map<String, String> attributesMap) {
        super(parser, attributesMap);
        this.source = attributesMap.getOrDefault(SOURCE_TAG, source);
    }

    /**
     * @return Always returns a {@link VisitorNotFoundException} since the descendants of this class shouldn't be
     * visited since they aren't allowed to have children.
     */
    @Override
    public XsdAbstractElementVisitor getVisitor() {
        throw new VisitorNotFoundException("AppInfo/Documentation can't have children.");
    }

    public String getSource() {
        return source;
    }

    public String getContent() {
        return content;
    }

    /**
     * This method is used to parse {@link XsdAnnotationChildren} instances.
     * @param node The node containing the information to parse.
     * @param annotationChildren An instance of {@link XsdAnnotationChildren} (either {@link XsdAppInfo} or
     *                           {@link XsdDocumentation}).
     * @return The annotationChildren wrapped in the correct {@link ReferenceBase} wrapper.
     */
    static ReferenceBase xsdAnnotationChildrenParse(Node node, XsdAnnotationChildren annotationChildren){
        annotationChildren.content = xsdRawContentParse(node);

        return ReferenceBase.createFromXsd(annotationChildren);
    }

}


package org.xmlet.xsdparser.core;

import org.w3c.dom.Node;
import org.xmlet.xsdparser.core.utils.UnsolvedReferenceItem;
import org.xmlet.xsdparser.xsdelements.*;
import org.xmlet.xsdparser.xsdelements.elementswrapper.NamedConcreteElement;
import org.xmlet.xsdparser.xsdelements.elementswrapper.ReferenceBase;
import org.xmlet.xsdparser.xsdelements.elementswrapper.UnsolvedReference;
import org.xmlet.xsdparser.xsdelements.xsdrestrictions.*;

import java.util.*;
import java.util.function.BiFunction;
import java.util.stream.Stream;

import static java.util.stream.Collectors.groupingBy;

public abstract class XsdParserCore {

    /**
     * A {@link Map} object that contains a parse function to each {@link XsdAbstractElement} concrete
     * type supported by this mapper, this way based on the concrete {@link XsdAbstractElement} tag the according parse
     * method can be invoked.
     */
    static final Map<String, BiFunction<XsdParserCore, Node, ReferenceBase>> parseMappers;

    /**
     * A {@link Map} object that contains the all the XSD types and their respective types in the Java
     * language.
     */
    private static final Map<String, String> xsdTypesToJava;

    /**
     * A {@link List} which contains all the top elements parsed by this class.
     */
    private List<ReferenceBase> parseElements = new ArrayList<>();

    /**
     * A {@link List} of {@link UnsolvedReference} elements that weren't solved. This list is consulted after all the
     * elements are parsed in order to find if there is any suitable parsed element to replace the unsolved element.
     */
    private List<UnsolvedReference> unsolvedElements = new ArrayList<>();

    /**
     * A {@link List} containing all the elements that even after parsing all the elements on the file, don't have a
     * suitable object to replace the reference. This list can be consulted after the parsing process to assert if there
     * is any missing information in the XSD file.
     */
    private List<UnsolvedReferenceItem> parserUnsolvedElementsMap = new ArrayList<>();

    /**
     * A {@link List} containing the paths of files that were present in either {@link XsdInclude} or {@link XsdImport}
     * objects that are present in the original or subsequent files. These paths are stored to be parsed as well, the
     * parsing process only ends when all the files present in this {@link List} are parsed.
     */
    List<String> schemaLocations = new ArrayList<>();

    static {
        parseMappers = new HashMap<>();
        xsdTypesToJava = new HashMap<>();

        parseMappers.put(XsdAll.XSD_TAG, XsdAll::parse);
        parseMappers.put(XsdAll.XS_TAG, XsdAll::parse);
        parseMappers.put(XsdAttribute.XSD_TAG, XsdAttribute::parse);
        parseMappers.put(XsdAttribute.XS_TAG, XsdAttribute::parse);
        parseMappers.put(XsdAttributeGroup.XSD_TAG, XsdAttributeGroup::parse);
        parseMappers.put(XsdAttributeGroup.XS_TAG, XsdAttributeGroup::parse);
        parseMappers.put(XsdChoice.XSD_TAG, XsdChoice::parse);
        parseMappers.put(XsdChoice.XS_TAG, XsdChoice::parse);
        parseMappers.put(XsdComplexType.XSD_TAG, XsdComplexType::parse);
        parseMappers.put(XsdComplexType.XS_TAG, XsdComplexType::parse);
        parseMappers.put(XsdElement.XSD_TAG, XsdElement::parse);
        parseMappers.put(XsdElement.XS_TAG, XsdElement::parse);
        parseMappers.put(XsdGroup.XSD_TAG, XsdGroup::parse);
        parseMappers.put(XsdGroup.XS_TAG, XsdGroup::parse);
        parseMappers.put(XsdInclude.XSD_TAG, XsdInclude::parse);
        parseMappers.put(XsdInclude.XS_TAG, XsdInclude::parse);
        parseMappers.put(XsdImport.XSD_TAG, XsdImport::parse);
        parseMappers.put(XsdImport.XS_TAG, XsdImport::parse);
        parseMappers.put(XsdSequence.XSD_TAG, XsdSequence::parse);
        parseMappers.put(XsdSequence.XS_TAG, XsdSequence::parse);
        parseMappers.put(XsdSimpleType.XSD_TAG, XsdSimpleType::parse);
        parseMappers.put(XsdSimpleType.XS_TAG, XsdSimpleType::parse);
        parseMappers.put(XsdList.XSD_TAG, XsdList::parse);
        parseMappers.put(XsdList.XS_TAG, XsdList::parse);
        parseMappers.put(XsdRestriction.XSD_TAG, XsdRestriction::parse);
        parseMappers.put(XsdRestriction.XS_TAG, XsdRestriction::parse);
        parseMappers.put(XsdUnion.XSD_TAG, XsdUnion::parse);
        parseMappers.put(XsdUnion.XS_TAG, XsdUnion::parse);

        parseMappers.put(XsdAnnotation.XSD_TAG, XsdAnnotation::parse);
        parseMappers.put(XsdAnnotation.XS_TAG, XsdAnnotation::parse);
        parseMappers.put(XsdAppInfo.XSD_TAG, XsdAppInfo::parse);
        parseMappers.put(XsdAppInfo.XS_TAG, XsdAppInfo::parse);
        parseMappers.put(XsdComplexContent.XSD_TAG, XsdComplexContent::parse);
        parseMappers.put(XsdComplexContent.XS_TAG, XsdComplexContent::parse);
        parseMappers.put(XsdDocumentation.XSD_TAG, XsdDocumentation::parse);
        parseMappers.put(XsdDocumentation.XS_TAG, XsdDocumentation::parse);
        parseMappers.put(XsdExtension.XSD_TAG, XsdExtension::parse);
        parseMappers.put(XsdExtension.XS_TAG, XsdExtension::parse);
        parseMappers.put(XsdSimpleContent.XSD_TAG, XsdSimpleContent::parse);
        parseMappers.put(XsdSimpleContent.XS_TAG, XsdSimpleContent::parse);

        parseMappers.put(XsdEnumeration.XSD_TAG, XsdEnumeration::parse);
        parseMappers.put(XsdEnumeration.XS_TAG, XsdEnumeration::parse);
        parseMappers.put(XsdFractionDigits.XSD_TAG, XsdFractionDigits::parse);
        parseMappers.put(XsdFractionDigits.XS_TAG, XsdFractionDigits::parse);
        parseMappers.put(XsdLength.XSD_TAG, XsdLength::parse);
        parseMappers.put(XsdLength.XS_TAG, XsdLength::parse);
        parseMappers.put(XsdMaxExclusive.XSD_TAG, XsdMaxExclusive::parse);
        parseMappers.put(XsdMaxExclusive.XS_TAG, XsdMaxExclusive::parse);
        parseMappers.put(XsdMaxInclusive.XSD_TAG, XsdMaxInclusive::parse);
        parseMappers.put(XsdMaxInclusive.XS_TAG, XsdMaxInclusive::parse);
        parseMappers.put(XsdMaxLength.XSD_TAG, XsdMaxLength::parse);
        parseMappers.put(XsdMaxLength.XS_TAG, XsdMaxLength::parse);
        parseMappers.put(XsdMinExclusive.XSD_TAG, XsdMinExclusive::parse);
        parseMappers.put(XsdMinExclusive.XS_TAG, XsdMinExclusive::parse);
        parseMappers.put(XsdMinInclusive.XSD_TAG, XsdMinInclusive::parse);
        parseMappers.put(XsdMinInclusive.XS_TAG, XsdMinInclusive::parse);
        parseMappers.put(XsdMinLength.XSD_TAG, XsdMinLength::parse);
        parseMappers.put(XsdMinLength.XS_TAG, XsdMinLength::parse);
        parseMappers.put(XsdPattern.XSD_TAG, XsdPattern::parse);
        parseMappers.put(XsdPattern.XS_TAG, XsdPattern::parse);
        parseMappers.put(XsdTotalDigits.XSD_TAG, XsdTotalDigits::parse);
        parseMappers.put(XsdTotalDigits.XS_TAG, XsdTotalDigits::parse);
        parseMappers.put(XsdWhiteSpace.XSD_TAG, XsdWhiteSpace::parse);
        parseMappers.put(XsdWhiteSpace.XS_TAG, XsdWhiteSpace::parse);

        String string = "String";
        String xmlGregorianCalendar = "XMLGregorianCalendar";
        String duration = "Duration";
        String bigInteger = "BigInteger";
        String integer = "Integer";
        String shortString = "Short";
        String qName = "QName";
        String longString = "Long";
        String byteString = "Byte";

        xsdTypesToJava.put("xsd:anyURI", string);
        xsdTypesToJava.put("xs:anyURI", string);
        xsdTypesToJava.put("xsd:boolean", "Boolean");
        xsdTypesToJava.put("xs:boolean", "Boolean");
        xsdTypesToJava.put("xsd:date", xmlGregorianCalendar);
        xsdTypesToJava.put("xs:date", xmlGregorianCalendar);
        xsdTypesToJava.put("xsd:dateTime", xmlGregorianCalendar);
        xsdTypesToJava.put("xs:dateTime", xmlGregorianCalendar);
        xsdTypesToJava.put("xsd:time", xmlGregorianCalendar);
        xsdTypesToJava.put("xs:time", xmlGregorianCalendar);
        xsdTypesToJava.put("xsd:duration", duration);
        xsdTypesToJava.put("xs:duration", duration);
        xsdTypesToJava.put("xsd:dayTimeDuration", duration);
        xsdTypesToJava.put("xs:dayTimeDuration", duration);
        xsdTypesToJava.put("xsd:yearMonthDuration", duration);
        xsdTypesToJava.put("xs:yearMonthDuration", duration);
        xsdTypesToJava.put("xsd:gDay", xmlGregorianCalendar);
        xsdTypesToJava.put("xs:gDay", xmlGregorianCalendar);
        xsdTypesToJava.put("xsd:gMonth", xmlGregorianCalendar);
        xsdTypesToJava.put("xs:gMonth", xmlGregorianCalendar);
        xsdTypesToJava.put("xsd:gMonthDay", xmlGregorianCalendar);
        xsdTypesToJava.put("xs:gMonthDay", xmlGregorianCalendar);
        xsdTypesToJava.put("xsd:gYear", xmlGregorianCalendar);
        xsdTypesToJava.put("xs:gYear", xmlGregorianCalendar);
        xsdTypesToJava.put("xsd:gYearMonth", xmlGregorianCalendar);
        xsdTypesToJava.put("xs:gYearMonth", xmlGregorianCalendar);
        xsdTypesToJava.put("xsd:decimal", "BigDecimal");
        xsdTypesToJava.put("xs:decimal", "BigDecimal");
        xsdTypesToJava.put("xsd:integer", bigInteger);
        xsdTypesToJava.put("xs:integer", bigInteger);
        xsdTypesToJava.put("xsd:nonPositiveInteger", bigInteger);
        xsdTypesToJava.put("xs:nonPositiveInteger", bigInteger);
        xsdTypesToJava.put("xsd:negativeInteger", bigInteger);
        xsdTypesToJava.put("xs:negativeInteger", bigInteger);
        xsdTypesToJava.put("xsd:long", longString);
        xsdTypesToJava.put("xs:long", longString);
        xsdTypesToJava.put("xsd:int", integer);
        xsdTypesToJava.put("xs:int", integer);
        xsdTypesToJava.put("xsd:short", shortString);
        xsdTypesToJava.put("xs:short", shortString);
        xsdTypesToJava.put("xsd:byte", byteString);
        xsdTypesToJava.put("xs:byte", byteString);
        xsdTypesToJava.put("xsd:nonNegativeInteger", bigInteger);
        xsdTypesToJava.put("xs:nonNegativeInteger", bigInteger);
        xsdTypesToJava.put("xsd:unsignedLong", bigInteger);
        xsdTypesToJava.put("xs:unsignedLong", bigInteger);
        xsdTypesToJava.put("xsd:unsignedInt", longString);
        xsdTypesToJava.put("xs:unsignedInt", longString);
        xsdTypesToJava.put("xsd:unsignedShort", integer);
        xsdTypesToJava.put("xs:unsignedShort", integer);
        xsdTypesToJava.put("xsd:unsignedByte", shortString);
        xsdTypesToJava.put("xs:unsignedByte", shortString);
        xsdTypesToJava.put("xsd:positiveInteger", bigInteger);
        xsdTypesToJava.put("xs:positiveInteger", bigInteger);
        xsdTypesToJava.put("xsd:double", "Double");
        xsdTypesToJava.put("xs:double", "Double");
        xsdTypesToJava.put("xsd:float", "Float");
        xsdTypesToJava.put("xs:float", "Float");
        xsdTypesToJava.put("xsd:QName", qName);
        xsdTypesToJava.put("xs:QName", qName);
        xsdTypesToJava.put("xsd:NOTATION", qName);
        xsdTypesToJava.put("xs:NOTATION", qName);
        xsdTypesToJava.put("xsd:string", string);
        xsdTypesToJava.put("xs:string", string);
        xsdTypesToJava.put("xsd:normalizedString", string);
        xsdTypesToJava.put("xs:normalizedString", string);
        xsdTypesToJava.put("xsd:token", string);
        xsdTypesToJava.put("xs:token", string);
        xsdTypesToJava.put("xsd:language", string);
        xsdTypesToJava.put("xs:language", string);
        xsdTypesToJava.put("xsd:NMTOKEN", string);
        xsdTypesToJava.put("xs:NMTOKEN", string);
        xsdTypesToJava.put("xsd:Name", string);
        xsdTypesToJava.put("xs:Name", string);
        xsdTypesToJava.put("xsd:NCName", string);
        xsdTypesToJava.put("xs:NCName", string);
        xsdTypesToJava.put("xsd:ID", string);
        xsdTypesToJava.put("xs:ID", string);
        xsdTypesToJava.put("xsd:IDREF", string);
        xsdTypesToJava.put("xs:IDREF", string);
        xsdTypesToJava.put("xsd:ENTITY", string);
        xsdTypesToJava.put("xs:ENTITY", string);
        xsdTypesToJava.put("xsd:untypedAtomic", string);
        xsdTypesToJava.put("xs:untypedAtomic", string);
    }

    /**
     * Verifies if a given {@link Node} object, i.e. {@code node} is a xsd:schema node.
     * @param node The node to verify.
     * @return True if the node is a xsd:schema or xs:schema. False otherwise.
     */
    boolean isXsdSchema(Node node){
        String schemaNodeName = node.getNodeName();

        return schemaNodeName.equals(XsdSchema.XSD_TAG) || schemaNodeName.equals(XsdSchema.XS_TAG);
    }

    /**
     * This method resolves all the remaining {@link UnsolvedReference} objects present after all the elements are parsed.
     * It starts by iterating all {@link XsdParser#parseElements} and inserting all the parsed elements with a name
     * attribute in the concreteElementsMap variable. After that it iterates on the {@link XsdParser#unsolvedElements}
     * list in order to find if any of the unsolvedReferences can be solved by replacing the unsolvedElement by its
     * matching {@link NamedConcreteElement} object, present in the concreteElementsMap. The {@link UnsolvedReference}
     * objects matches a {@link NamedConcreteElement} object by having its ref attribute with the same value as the
     * name attribute of the {@link NamedConcreteElement}.
     */
    void resolveRefs() {
        Map<String, List<NamedConcreteElement>> concreteElementsMap =
                parseElements.stream()
                        .filter(concreteElement -> concreteElement instanceof NamedConcreteElement)
                        .map(concreteElement -> (NamedConcreteElement) concreteElement)
                        .collect(groupingBy(NamedConcreteElement::getName));

        unsolvedElements.forEach(unsolvedElement -> replaceUnsolvedReference(concreteElementsMap, unsolvedElement));
    }

    /**
     * Replaces a single {@link UnsolvedReference} object, with the respective {@link NamedConcreteElement} object. If
     * there isn't a {@link NamedConcreteElement} object to replace the {@link UnsolvedReference} object, information
     * is stored informing the user of this Project of the occurrence.
     * @param concreteElementsMap The map containing all named concreteElements.
     * @param unsolvedReference The unsolved reference to solve.
     */
    private void replaceUnsolvedReference(Map<String, List<NamedConcreteElement>> concreteElementsMap, UnsolvedReference unsolvedReference) {
        List<NamedConcreteElement> concreteElements = concreteElementsMap.get(unsolvedReference.getRef());

        if (concreteElements != null){
            Map<String, String> oldElementAttributes = unsolvedReference.getElement().getAttributesMap();

            for (NamedConcreteElement concreteElement : concreteElements) {
                NamedConcreteElement substitutionElementWrapper;

                if (!unsolvedReference.isTypeRef()){
                    XsdNamedElements substitutionElement = concreteElement.getElement().clone(oldElementAttributes);

                    substitutionElementWrapper = (NamedConcreteElement) ReferenceBase.createFromXsd(substitutionElement);
                } else {
                    substitutionElementWrapper = concreteElement;
                }

                unsolvedReference.getParent().replaceUnsolvedElements(substitutionElementWrapper);
            }
        } else {
            storeUnsolvedItem(unsolvedReference);
        }
    }

    /**
     * Saves an occurrence of an element which couldn't be resolved in the {@link XsdParser#replaceUnsolvedReference}
     * method, which can be accessed at the end of the parsing process in order to verify if were there were any
     * references that couldn't be solved.
     * @param unsolvedReference The unsolved reference which couldn't be resolved.
     */
    private void storeUnsolvedItem(UnsolvedReference unsolvedReference) {
        if (parserUnsolvedElementsMap.isEmpty()){
            parserUnsolvedElementsMap.add(new UnsolvedReferenceItem(unsolvedReference));
        } else {
            Optional<UnsolvedReferenceItem> innerEntry =
                    parserUnsolvedElementsMap.stream()
                            .filter(unsolvedReferenceObj ->
                                    unsolvedReferenceObj.getUnsolvedReference()
                                            .getRef()
                                            .equals(unsolvedReference.getRef()))
                            .findFirst();

            if (innerEntry.isPresent()){
                innerEntry.ifPresent(entry -> entry.getParents().add(unsolvedReference.getParent()));
            } else {
                parserUnsolvedElementsMap.add(new UnsolvedReferenceItem(unsolvedReference));
            }
        }
    }

    /**
     * @return The {@link List} of {@link UnsolvedReferenceItem} that represent all the objects with a reference that couldn't
     * be solved.
     */
    public List<UnsolvedReferenceItem> getUnsolvedReferences(){
        return parserUnsolvedElementsMap;
    }

    /**
     * @return A list of all the top level parsed xsd:elements by this class. It doesn't return any other elements apart
     * from xsd:elements. To access the whole element tree use {@link XsdParser#getResultXsdSchemas()}
     */
    public Stream<XsdElement> getResultXsdElements(){
        List<XsdElement> elements = new ArrayList<>();

        getResultXsdSchemas().forEach(schema -> schema.getChildrenElements().forEach(elements::add));

        return elements.stream();
    }

    /**
     * @return A {@link List} of all the {@link XsdSchema} elements parsed by this class. You can use the {@link XsdSchema}
     * instances to navigate through the whole element tree.
     */
    public Stream<XsdSchema> getResultXsdSchemas(){
        return parseElements
                .stream()
                .filter(element -> element.getElement() instanceof XsdSchema)
                .map(element -> (XsdSchema) element.getElement());
    }

    /**
     * Adds an {@link UnsolvedReference} object to the {@link XsdParser#unsolvedElements} list which should be solved
     * at a later time in the parsing process.
     * @param unsolvedReference The unsolvedReference to add to the unsolvedElements list.
     */
    public void addUnsolvedReference(UnsolvedReference unsolvedReference){
        unsolvedElements.add(unsolvedReference);
    }

    /**
     * Adds a new file to the parsing queue. This new file appears by having xsd:import or xsd:include tags in the
     * original file to parse.
     * @param schemaLocation A new file path of another XSD file to parse.
     */
    public void addFileToParse(String schemaLocation) {
        if (!schemaLocations.contains(schemaLocation) && schemaLocation.endsWith(".xsd")){
            schemaLocations.add(schemaLocation);
        }
    }

    public static Map<String, String> getXsdTypesToJava() {
        return xsdTypesToJava;
    }

    public static Map<String, BiFunction<XsdParserCore, Node, ReferenceBase>> getParseMappers() {
        return parseMappers;
    }

    public void addParsedElement(ReferenceBase wrappedElement) {
        parseElements.add(wrappedElement);
    }

}


package org.xmlet.xsdparser.xsdelements;

import org.w3c.dom.Node;
import org.xmlet.xsdparser.core.XsdParserCore;
import org.xmlet.xsdparser.xsdelements.elementswrapper.ReferenceBase;
import org.xmlet.xsdparser.xsdelements.elementswrapper.UnsolvedReference;
import org.xmlet.xsdparser.xsdelements.exceptions.ParsingException;
import org.xmlet.xsdparser.xsdelements.visitors.XsdAbstractElementVisitor;
import org.xmlet.xsdparser.xsdelements.visitors.XsdAnnotatedElementsVisitor;
import org.xmlet.xsdparser.xsdelements.visitors.XsdGroupVisitor;

import javax.validation.constraints.NotNull;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * A class representing the xsd:complexType element. Extends {@link XsdNamedElements} because it's one of the
 * {@link XsdAbstractElement} concrete classes that can have a {@link XsdNamedElements#name} attribute.
 *
 * @see xsd:group description and usage at w3c
 */
public class XsdGroup extends XsdNamedElements {

    public static final String XSD_TAG = "xsd:group";
    public static final String XS_TAG = "xs:group";

    /**
     * {@link XsdGroupVisitor} instance which restricts his children to {@link XsdAll}, {@link XsdChoice} or
     * {@link XsdSequence} instances.
     * Can also have {@link XsdAnnotation} children as per inheritance of {@link XsdAnnotatedElementsVisitor}.
     */
    private XsdGroupVisitor visitor = new XsdGroupVisitor(this);

    /**
     * The child element of the {@link XsdGroup} instance. It can be a {@link XsdAll}, {@link XsdChoice} or a
     * {@link XsdSequence} instance.
     */
    private XsdMultipleElements childElement;

    /**
     * Specifies the minimum number of times this element can occur in the parent element. The value can be any
     * number bigger or equal to 0. Default value is 1. This attribute cannot be used if the parent element is the
     * XsdSchema element.
     */
    private Integer minOccurs;

    /**
     * Specifies the maximum number of times this element can occur in the parent element. The value can be any
     * number bigger or equal to 0, or if you want to set no limit on the maximum number, use the value "unbounded".
     * Default value is 1. This attribute cannot be used if the parent element is the XsdSchema element.
     */
    private String maxOccurs;

    private XsdGroup(@NotNull XsdParserCore parser, @NotNull Map<String, String> attributesMap) {
        super(parser, attributesMap);

        this.minOccurs = AttributeValidations.validateNonNegativeInteger(XSD_TAG, MIN_OCCURS_TAG, attributesMap.getOrDefault(MIN_OCCURS_TAG, "1"));
        this.maxOccurs = AttributeValidations.maxOccursValidation(XSD_TAG, attributesMap.getOrDefault(MAX_OCCURS_TAG, "1"));
    }

    private XsdGroup(XsdAbstractElement parent, @NotNull XsdParserCore parser, @NotNull Map<String, String> attributesMap) {
        this(parser, attributesMap);
        setParent(parent);
    }

    /**
     * Runs verifications on each concrete element to ensure that the XSD schema rules are verified.
     */
    @Override
    public void validateSchemaRules() {
        super.validateSchemaRules();

        rule2();
        rule3();
    }

    /**
     * Asserts if the current object has the name attribute when not being a direct child of the XsdSchema element, which is
     * not allowed, throwing an exception in that case.
     */
    private void rule2() {
        if (!(parent instanceof XsdSchema) && name != null){
            throw new ParsingException(XSD_TAG + " element: The " + NAME_TAG + " should only be used when the parent of the " + XSD_TAG + " is the " + XsdSchema.XSD_TAG + " element." );
        }
    }

    /**
     * Asserts if the current has no value for its name attribute while being a direct child of the top level XsdSchema element,
     * which is required. Throws an exception if no name is present.
     */
    private void rule3() {
        if (parent instanceof XsdSchema && name == null){
            throw new ParsingException(XSD_TAG + " element: The " + NAME_TAG + " should is required the parent of the " + XSD_TAG + " is the " + XsdSchema.XSD_TAG + " element." );
        }
    }

    @Override
    public void accept(XsdAbstractElementVisitor visitorParam) {
        super.accept(visitorParam);
        visitorParam.visit(this);
    }

    @Override
    public XsdGroupVisitor getVisitor() {
        return visitor;
    }

    /**
     * @return A list with the child element of the {@link XsdGroup} instance.
     */
    @Override
    public List<ReferenceBase> getElements() {
        List<ReferenceBase> list = new ArrayList<>();

        if (childElement != null){
            list.add(ReferenceBase.createFromXsd(childElement));
        }

        return list;
    }

    /**
     * Performs a copy of the current object for replacing purposes. The cloned objects are used to replace
     * {@link UnsolvedReference} objects in the reference solving process.
     * @param placeHolderAttributes The additional attributes to add to the clone.
     * @return A copy of the object from which is called upon.
     */
    @Override
    public XsdNamedElements clone(@NotNull Map<String, String> placeHolderAttributes) {
        placeHolderAttributes.putAll(attributesMap);
        placeHolderAttributes.remove(REF_TAG);

        XsdGroup elementCopy = new XsdGroup(this.parent, this.parser, placeHolderAttributes);

        if (childElement != null){
            elementCopy.setChildElement(this.childElement);
        }

        return elementCopy;
    }

    public void setChildElement(XsdMultipleElements childElement) {
        this.childElement = childElement;
        childElement.getElements().forEach(childElementObj -> childElementObj.getElement().setParent(childElement));
        this.childElement.setParent(this);
    }

    @SuppressWarnings("unused")
    public XsdMultipleElements getChildElement() {
        return childElement;
    }

    /**
     * @return The childElement as a {@link XsdAll} object or null if childElement isn't a {@link XsdAll} instance.
     */
    @SuppressWarnings("unused")
    public XsdAll getChildAsAll() {
        return XsdMultipleElements.getChildAsdAll(childElement);
    }

    /**
     * @return The childElement as a {@link XsdChoice} object or null if childElement isn't a {@link XsdChoice} instance.
     */
    @SuppressWarnings("unused")
    public XsdChoice getChildAsChoice() {
        return XsdMultipleElements.getChildAsChoice(childElement);
    }

    /**
     * @return The childElement as a {@link XsdSequence} object or null if childElement isn't a {@link XsdSequence} instance.
     */
    @SuppressWarnings("unused")
    public XsdSequence getChildAsSequence() {
        return XsdMultipleElements.getChildAsSequence(childElement);
    }

    public static ReferenceBase parse(@NotNull XsdParserCore parser, Node node){
        return xsdParseSkeleton(node, new XsdGroup(parser, convertNodeMap(node.getAttributes())));
    }

    public Integer getMinOccurs() {
        return minOccurs;
    }

    public String getMaxOccurs() {
        return maxOccurs;
    }

}


package org.xmlet.xsdparser.xsdelements.xsdrestrictions;

import org.xmlet.xsdparser.core.XsdParserCore;
import org.xmlet.xsdparser.xsdelements.AttributeValidations;
import org.xmlet.xsdparser.xsdelements.XsdAnnotatedElements;
import org.xmlet.xsdparser.xsdelements.visitors.XsdAnnotatedElementsVisitor;

import javax.validation.constraints.NotNull;
import java.util.Map;

/**
 * This class serves as a base to every different restriction that has its restricting parameter defined as a
 * {@link Double}. Classes like {@link XsdMaxInclusive} or {@link XsdMinInclusive} should derive from this type.
 */
public abstract class XsdDoubleRestrictions extends XsdAnnotatedElements {

    private XsdAnnotatedElementsVisitor visitor = new XsdAnnotatedElementsVisitor(this);

    /**
     * Indicates if the value is fixed.
     */
    private boolean fixed;

    /**
     * The value of associated with a given restriction. This field has different meanings depending on the concrete
     * restriction, e.g. if the concrete class is {@link XsdMaxInclusive} this field means that the attribute which
     * has the restriction can only have a value that doesn't exceed the current value of the value field.
     */
    private double value;

    XsdDoubleRestrictions(@NotNull XsdParserCore parser, @NotNull Map<String, String> elementFieldsMapParam, String restrictionName) {
        super(parser, elementFieldsMapParam);

        fixed = AttributeValidations.validateBoolean(attributesMap.getOrDefault(FIXED_TAG, "false"));
        value = AttributeValidations.validateRequiredDouble(restrictionName, VALUE_TAG, attributesMap.get(VALUE_TAG));
    }

    @Override
    public XsdAnnotatedElementsVisitor getVisitor() {
        return visitor;
    }

    /**
     * Compares two different objects of this type.
     * @param o1 The first object.
     * @param o2 The object to compare.
     * @return True if the value of both classes is different, False if the value is equal.
     */
    public static boolean hasDifferentValue(XsdDoubleRestrictions o1, XsdDoubleRestrictions o2) {
        if (o1 == null && o2 == null) {
            return false;
        }

        double o1Value = Double.MAX_VALUE;
        double o2Value;

        if (o1 != null) {
            o1Value = o1.getValue();
        }

        if (o2 != null) {
            o2Value = o2.getValue();
            return o2Value == o1Value;
        }

        return false;
    }

    public double getValue() {
        return value;
    }

    public boolean isFixed() {
        return fixed;
    }
}


package org.xmlet.xsdparser.xsdelements;

import org.w3c.dom.Node;
import org.xmlet.xsdparser.core.XsdParserCore;
import org.xmlet.xsdparser.xsdelements.elementswrapper.ReferenceBase;
import org.xmlet.xsdparser.xsdelements.elementswrapper.UnsolvedReference;
import org.xmlet.xsdparser.xsdelements.enums.SimpleTypeFinalEnum;
import org.xmlet.xsdparser.xsdelements.exceptions.ParsingException;
import org.xmlet.xsdparser.xsdelements.visitors.XsdAbstractElementVisitor;
import org.xmlet.xsdparser.xsdelements.visitors.XsdAnnotatedElementsVisitor;
import org.xmlet.xsdparser.xsdelements.visitors.XsdSimpleTypeVisitor;
import org.xmlet.xsdparser.xsdelements.xsdrestrictions.*;

import javax.validation.constraints.NotNull;
import java.security.InvalidParameterException;
import java.util.*;

import static org.xmlet.xsdparser.xsdelements.xsdrestrictions.XsdDoubleRestrictions.hasDifferentValue;
import static org.xmlet.xsdparser.xsdelements.xsdrestrictions.XsdIntegerRestrictions.hasDifferentValue;
import static org.xmlet.xsdparser.xsdelements.xsdrestrictions.XsdStringRestrictions.hasDifferentValue;
import static org.xmlet.xsdparser.xsdelements.xsdrestrictions.XsdWhiteSpace.hasDifferentValue;

/**
 * A class representing the xsd:simpleType element.
 *
 * @see xsd:simpleType description and usage at W3C
 */
public class XsdSimpleType extends XsdNamedElements {

    public static final String XSD_TAG = "xsd:simpleType";
    public static final String XS_TAG = "xs:simpleType";

    /**
     * {@link XsdSimpleTypeVisitor} instance which restricts its children to {@link XsdList}, {@link XsdUnion} or
     * {@link XsdRestriction} instances. Can also have {@link XsdAnnotation} as children as per inheritance of
     * {@link XsdAnnotatedElementsVisitor}.
     */
    private XsdSimpleTypeVisitor visitor = new XsdSimpleTypeVisitor(this);

    /**
     * A {@link XsdRestriction} instance that is present in the {@link XsdSimpleType} instance.
     */
    private XsdRestriction restriction;

    /**
     * A {@link XsdUnion} instance that is present in the {@link XsdSimpleType} instance.
     */
    private XsdUnion union;

    /**
     * A {@link XsdList} instance that is present in the {@link XsdSimpleType} instance.
     */
    private XsdList list;

    /**
     * Prevents other elements to derive depending on its value.
     */
    private SimpleTypeFinalEnum finalObj;

    private XsdSimpleType(@NotNull XsdParserCore parser, @NotNull Map<String, String> attributesMap) {
        super(parser, attributesMap);

        String finalDefault = AttributeValidations.getFinalDefaultValue(parent);

        this.finalObj = AttributeValidations.belongsToEnum(SimpleTypeFinalEnum.ALL, attributesMap.getOrDefault(FINAL_TAG, finalDefault));
    }

    private XsdSimpleType(XsdAbstractElement parent, XsdParserCore parser, @NotNull Map<String, String> elementFieldsMapParam) {
        this(parser, elementFieldsMapParam);
        setParent(parent);
    }

    /**
     * Runs verifications on each concrete element to ensure that the XSD schema rules are verified.
     */
    @Override
    public void validateSchemaRules() {
        super.validateSchemaRules();

        rule2();
        rule3();
    }

    /**
     * Asserts that the current object has the required name attribute when not being a direct child of the XsdSchema element.
     * Throws an exception if the required attribute is not present.
     */
    private void rule2() {
        if (!(parent instanceof XsdSchema) && name != null){
            throw new ParsingException(XSD_TAG + " element: The " + NAME_TAG + " should only be used when the parent of the " + XSD_TAG + " is the " + XsdSchema.XSD_TAG + " element." );
        }
    }

    /**
     * Asserts if the current has no value for its name attribute while being a direct child of the top level XsdSchema element,
     * which is required. Throws an exception if no name is present.
     */
    private void rule3() {
        if (parent instanceof XsdSchema && name == null){
            throw new ParsingException(XSD_TAG + " element: The " + NAME_TAG + " should is required the parent of the " + XSD_TAG + " is the " + XsdSchema.XSD_TAG + " element." );
        }
    }

    @Override
    public XsdSimpleTypeVisitor getVisitor() {
        return visitor;
    }

    @Override
    public void accept(XsdAbstractElementVisitor visitorParam) {
        super.accept(visitorParam);
        visitorParam.visit(this);
    }

    /**
     * Performs a copy of the current object for replacing purposes. The cloned objects are used to replace
     * {@link UnsolvedReference} objects in the reference solving process.
     * @param placeHolderAttributes The additional attributes to add to the clone.
     * @return A copy of the object from which is called upon.
     */
    @Override
    public XsdSimpleType clone(@NotNull Map<String, String> placeHolderAttributes) {
        placeHolderAttributes.putAll(attributesMap);
        placeHolderAttributes.remove(REF_TAG);

        XsdSimpleType copy = new XsdSimpleType(this.parent, this.parser, placeHolderAttributes);

        copy.union = this.union;
        copy.list = this.list;
        copy.restriction = this.restriction;

        return copy;
    }

    public static ReferenceBase parse(@NotNull XsdParserCore parser, Node node){
        return xsdParseSkeleton(node, new XsdSimpleType(parser, convertNodeMap(node.getAttributes())));
    }

    public XsdRestriction getRestriction() {
        return restriction;
    }

    public XsdUnion getUnion() {
        return union;
    }

    public XsdList getList() {
        if (this.list == null && this.union != null){
            Optional<XsdSimpleType> simpleType = union.getUnionElements().stream().filter(xsdSimpleType -> xsdSimpleType.list != null).findFirst();

            if (simpleType.isPresent()){
                return simpleType.get().list;
            }
        }

        return this.list;
    }

    /**
     * This method obtains all the restrictions for the current {@link XsdSimpleType} element. It also joins multiple
     * restrictions with the same base attribute in the same {@link XsdRestriction} object, if a overlap doesn't occur.
     * In case of restriction overlap an exception is thrown because the information on the xsd file is contradictory.
     * @return A list of restrictions.
     */
    public List<XsdRestriction> getAllRestrictions() {
        Map<String, XsdRestriction> restrictions = new HashMap<>();
        Map<String, String> xsdBuiltinTypes = XsdParserCore.getXsdTypesToJava();

        if (restriction != null){
            restrictions.put(xsdBuiltinTypes.get(restriction.getBase()), restriction);
        }

        if (union != null){
            union.getUnionElements().forEach(unionMember -> {
                XsdRestriction unionMemberRestriction = unionMember.getRestriction();

                if (unionMemberRestriction != null){
                    XsdRestriction existingRestriction = restrictions.getOrDefault(xsdBuiltinTypes.get(unionMemberRestriction.getBase()), null);

                    if (existingRestriction != null){
                        if (existsRestrictionOverlap(existingRestriction, unionMemberRestriction)){
                            throw new InvalidParameterException("The xsd file is invalid because has contradictory restrictions.");
                        }

                        updateExistingRestriction(existingRestriction, unionMemberRestriction);
                    } else {
                        restrictions.put(xsdBuiltinTypes.get(unionMemberRestriction.getBase()), unionMemberRestriction);
                    }
                }
            });
        }

        return new ArrayList<>(restrictions.values());
    }

    /**
     * Joins two distinct {@link XsdRestriction} instances. This method assumes that the information of both
     * {@link XsdRestriction} objects don't have overlapping or contradictory information.
     * @param existing The existing restriction.
     * @param newRestriction The new restriction.
     */
    private void updateExistingRestriction(XsdRestriction existing, XsdRestriction newRestriction) {
        XsdPattern pattern = newRestriction.getPattern();
        XsdMaxExclusive maxExclusive = newRestriction.getMaxExclusive();
        XsdMaxInclusive maxInclusive = newRestriction.getMaxInclusive();
        XsdMaxLength maxLength = newRestriction.getMaxLength();
        XsdMinExclusive minExclusive = newRestriction.getMinExclusive();
        XsdMinInclusive minInclusive = newRestriction.getMinInclusive();
        XsdMinLength minLength = newRestriction.getMinLength();
        XsdLength length = newRestriction.getLength();
        XsdFractionDigits fractionDigits = newRestriction.getFractionDigits();
        XsdTotalDigits totalDigits = newRestriction.getTotalDigits();
        XsdWhiteSpace whiteSpace = newRestriction.getWhiteSpace();

        if (pattern != null){
            existing.setPattern(pattern);
        }

        if (maxExclusive != null){
            existing.setMaxExclusive(maxExclusive);
        }

        if (maxInclusive != null){
            existing.setMaxInclusive(maxInclusive);
        }

        if (maxLength != null){
            existing.setMaxLength(maxLength);
        }

        if (minExclusive != null){
            existing.setMinExclusive(minExclusive);
        }

        if (minInclusive != null){
            existing.setMinInclusive(minInclusive);
        }

        if (minLength != null){
            existing.setMinLength(minLength);
        }

        if (length != null){
            existing.setLength(length);
        }

        if (fractionDigits != null){
            existing.setFractionDigits(fractionDigits);
        }

        if (totalDigits != null){
            existing.setTotalDigits(totalDigits);
        }

        if (whiteSpace != null){
            existing.setWhiteSpace(whiteSpace);
        }

        updateExistingRestrictionEnumerations(existing, newRestriction);
    }

    /**
     * Updates the existing {@link XsdRestriction} with the restrictions of the new {@link XsdRestriction} instance.
     * @param existing The existing {@link XsdRestriction} instance.
     * @param newRestriction The new {@link XsdRestriction} instance.
     */
    private void updateExistingRestrictionEnumerations(XsdRestriction existing, XsdRestriction newRestriction) {
        List<XsdEnumeration> existingEnumeration = existing.getEnumeration();
        List<XsdEnumeration> newRestrictionEnumeration = newRestriction.getEnumeration();

        if (existingEnumeration == null){
            existing.setEnumeration(newRestrictionEnumeration);
        } else {
            if (newRestrictionEnumeration != null){
                for (XsdEnumeration enumerationElem : newRestrictionEnumeration){
                    if (existingEnumeration.stream().noneMatch(existingEnumerationElem -> existingEnumerationElem.getValue().equals(enumerationElem.getValue()))){
                        existingEnumeration.add(enumerationElem);
                    }
                }
            }
        }
    }

    /**
     * Checks for any restriction overlap between two different {@link XsdRestriction} instances.
     * @param existing The existing restriction.
     * @param newRestriction The second restriction found.
     * @return True if an overlap between the restrictions occur, false if it doesn't occur.
     */
    private boolean existsRestrictionOverlap(XsdRestriction existing, XsdRestriction newRestriction) {
        return hasDifferentValue(existing.getPattern(), newRestriction.getPattern()) ||
               hasDifferentValue(existing.getWhiteSpace(), newRestriction.getWhiteSpace()) ||
               hasDifferentValue(existing.getTotalDigits(), newRestriction.getTotalDigits()) ||
               hasDifferentValue(existing.getFractionDigits(), newRestriction.getFractionDigits()) ||
               hasDifferentValue(existing.getMaxExclusive(), newRestriction.getMaxExclusive()) ||
               hasDifferentValue(existing.getMaxInclusive(), newRestriction.getMaxInclusive()) ||
               hasDifferentValue(existing.getMaxLength(), newRestriction.getMaxLength()) ||
               hasDifferentValue(existing.getMinExclusive(), newRestriction.getMinExclusive()) ||
               hasDifferentValue(existing.getMinInclusive(), newRestriction.getMinInclusive()) ||
               hasDifferentValue(existing.getMinLength(), newRestriction.getMinLength()) ||
               hasDifferentValue(existing.getLength(), newRestriction.getLength());
    }

    public void setList(XsdList list) {
        this.list = list;
    }

    public void setUnion(XsdUnion union) {
        this.union = union;
    }

    public void setRestriction(XsdRestriction restriction) {
        this.restriction = restriction;
    }

    @SuppressWarnings("unused")
    public String getFinalObj() {
        return finalObj.getValue();
    }
}


package org.xmlet.xsdparser.xsdelements;

import org.w3c.dom.Node;
import org.xmlet.xsdparser.core.XsdParser;
import org.xmlet.xsdparser.core.XsdParserCore;
import org.xmlet.xsdparser.xsdelements.elementswrapper.ConcreteElement;
import org.xmlet.xsdparser.xsdelements.elementswrapper.NamedConcreteElement;
import org.xmlet.xsdparser.xsdelements.elementswrapper.ReferenceBase;
import org.xmlet.xsdparser.xsdelements.elementswrapper.UnsolvedReference;
import org.xmlet.xsdparser.xsdelements.enums.FormEnum;
import org.xmlet.xsdparser.xsdelements.enums.UsageEnum;
import org.xmlet.xsdparser.xsdelements.exceptions.ParsingException;
import org.xmlet.xsdparser.xsdelements.visitors.XsdAbstractElementVisitor;
import org.xmlet.xsdparser.xsdelements.visitors.XsdAnnotatedElementsVisitor;
import org.xmlet.xsdparser.xsdelements.visitors.XsdAttributeVisitor;

import javax.validation.constraints.NotNull;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * A class representing the xsd:attribute element. It can have a ref attribute and therefore extends from
 * {@link XsdNamedElements}, which serves as a base to every element type that can have a ref attribute.
 * For more information check {@link XsdNamedElements}.
 *
 * @see xsd:attribute element description and usage at w3c
 */
public class XsdAttribute extends XsdNamedElements {

    public static final String XSD_TAG = "xsd:attribute";
    public static final String XS_TAG = "xs:attribute";

    /**
     * {@link XsdAttributeVisitor} instance which restricts the its children to {@link XsdSimpleType}.
     * Can also have xsd:annotation as children as per inheritance of {@link XsdAnnotatedElementsVisitor}.
     */
    private XsdAttributeVisitor visitor = new XsdAttributeVisitor(this);

    /**
     * A {@link XsdSimpleType} instance wrapped in a {@link ReferenceBase} object which indicate any restrictions
     * that may be present in the current {@link XsdAttribute} instance.
     */
    private ReferenceBase simpleType;

    /**
     * A default value for the current {@link XsdAttribute} instance. This value and {@link XsdAttribute#fixed}
     * shouldn't be present at the same time.
     */
    private String defaultElement;

    /**
     * Specifies a fixed value for the current {@link XsdAttribute} instance. This value and
     * {@link XsdAttribute#defaultElement} shouldn't be present at the same time.
     */
    private String fixed;

    /**
     * Specifies either a built-in data type for the current {@link XsdAttribute} instance or serves as a reference to a
     * {@link XsdSimpleType} instance. In the case of being used as a reference to a {@link XsdSimpleType} instance
     * its value is used to create an {@link UnsolvedReference} using its value as ref to be resolved later in the
     * parsing process.
     */
    private String type;

    /**
     * Specifies if the current {@link XsdAttribute} attribute is "qualified" or "unqualified".
     */
    private FormEnum form;

    /**
     * Specifies how this {@link XsdAttribute} should be used. The possible values are: required, prohibited, optional.
     */
    private UsageEnum use;

    private XsdAttribute(@NotNull XsdParserCore parser, @NotNull Map<String, String> attributesMap) {
        super(parser, attributesMap);

        String formDefaultValue = getFormDefaultValue(parent);

        this.defaultElement = attributesMap.getOrDefault(DEFAULT_ELEMENT_TAG, defaultElement);
        this.fixed = attributesMap.getOrDefault(FIXED_TAG, fixed);
        this.type = attributesMap.getOrDefault(TYPE_TAG, type);
        this.form = AttributeValidations.belongsToEnum(FormEnum.QUALIFIED, attributesMap.getOrDefault(FORM_TAG, formDefaultValue));
        this.use = AttributeValidations.belongsToEnum(UsageEnum.OPTIONAL, attributesMap.getOrDefault(USE_TAG, UsageEnum.OPTIONAL.getValue()));

        if (type != null && !XsdParser.getXsdTypesToJava().containsKey(type)){
            this.simpleType = new UnsolvedReference(type, new XsdAttribute(this, parser, new HashMap<>()));
            parser.addUnsolvedReference((UnsolvedReference) this.simpleType);
        }
    }

    private XsdAttribute(XsdAbstractElement parent, @NotNull XsdParserCore parser, @NotNull Map<String, String> attributesMap) {
        this(parser, attributesMap);
        setParent(parent);
    }

    private static String getFormDefaultValue(XsdAbstractElement parent) {
        if (parent == null) return null;

        if (parent instanceof XsdElement){
            return ((XsdElement) parent).getForm();
        }

        if (parent instanceof XsdSchema){
            return ((XsdSchema) parent).getAttributeFormDefault();
        }

        return getFormDefaultValue(parent.getParent());
    }

    /**
     * Runs verifications on each concrete element to ensure that the XSD schema rules are verified.
     */
    @Override
    public void validateSchemaRules() {
        super.validateSchemaRules();

        rule2();
        rule3();
    }

    /**
     * Asserts if the current object has a ref attribute at the same time as either a simpleType as children, a form attribute or a type attribute.
     * Throws an exception in that case.
     */
    private void rule3() {
        if (attributesMap.containsKey(REF_TAG) && (simpleType != null || form != null || type != null)){
            throw new ParsingException(XSD_TAG + " element: If " + REF_TAG + " attribute is present, simpleType element, form attribute and type attribute cannot be present at the same time.");
        }
    }

    /**
     * Asserts if the current object has the fixed and default attributes at the same time, which isn't allowed, throwing
     * an exception in that case.
     */
    private void rule2() {
        if (fixed != null && defaultElement != null){
            throw new ParsingException(XSD_TAG + " element: " + FIXED_TAG + " and " + DEFAULT_ELEMENT_TAG + " attributes are not allowed at the same time.");
        }
    }

    @Override
    public void accept(XsdAbstractElementVisitor visitorParam) {
        super.accept(visitorParam);
        visitorParam.visit(this);
    }

    @Override
    public XsdAttributeVisitor getVisitor() {
        return visitor;
    }

    /**
     * Performs a copy of the current object for replacing purposes. The cloned objects are used to replace
     * {@link UnsolvedReference} objects in the reference solving process.
     * @param placeHolderAttributes The additional attributes to add to the clone.
     * @return A copy of the object from which is called upon.
     */
    @Override
    public XsdAttribute clone(@NotNull Map<String, String> placeHolderAttributes) {
        placeHolderAttributes.putAll(attributesMap);
        placeHolderAttributes.remove(TYPE_TAG);
        placeHolderAttributes.remove(REF_TAG);

        XsdAttribute copy = new XsdAttribute(this.parent, this.parser, placeHolderAttributes);

        copy.type = this.type;
        copy.simpleType = this.simpleType;

        return copy;
    }

    /**
     * Receives a {@link NamedConcreteElement} that should be the one requested earlier.
     *  * In the {@link XsdAttribute} constructor:
     *      this.simpleType = new UnsolvedReference(type, placeHolder);
     *      XsdParser.getInstance().addUnsolvedReference((UnsolvedReference) this.simpleType);
     * This implies that the object being received is the object that is being referred with the {@link XsdAttribute#type}
     * String.
     * @param elementWrapper The object that should be wrapping the requested {@link XsdSimpleType} object.
     */
    @Override
    public void replaceUnsolvedElements(NamedConcreteElement elementWrapper) {
        super.replaceUnsolvedElements(elementWrapper);

        XsdAbstractElement element = elementWrapper.getElement();

        if (element instanceof XsdSimpleType && simpleType != null && type.equals(elementWrapper.getName())){
            this.simpleType = elementWrapper;
        }
    }

    public void setSimpleType(ReferenceBase simpleType) {
        this.simpleType = simpleType;
    }

    public XsdSimpleType getXsdSimpleType(){
        return simpleType instanceof ConcreteElement ? (XsdSimpleType) simpleType.getElement() : null;
    }

    public String getType() {
        return type;
    }

    public String getUse() {
        return use.getValue();
    }

    public String getForm() {
        return form.getValue();
    }

    public String getFixed() {
        return fixed;
    }

    @SuppressWarnings("unused")
    public List<XsdRestriction> getAllRestrictions(){
        XsdSimpleType simpleTypeObj = getXsdSimpleType();

        if (simpleTypeObj != null){
            return simpleTypeObj.getAllRestrictions();
        }

        return Collections.emptyList();
    }

    public static ReferenceBase parse(@NotNull XsdParserCore parser, Node node) {
        return xsdParseSkeleton(node, new XsdAttribute(parser, convertNodeMap(node.getAttributes())));
    }

}


package org.xmlet.xsdparser.core;

import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.xml.sax.SAXException;
import org.xmlet.xsdparser.xsdelements.XsdSchema;
import org.xmlet.xsdparser.xsdelements.exceptions.ParsingException;

import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * {@link XsdParser} in the core class of the XsdParser project. It functions as a one shot class, receiving the name
 * of the file to parse in its constructor and storing the parse results in its multiple fields, which can be consulted
 * after the instance is created.
 */
public class XsdParser extends XsdParserCore{

    /**
     * The XsdParser constructor will parse the XSD file with the {@code filepath} and will also parse all the subsequent
     * XSD files with their path present in xsd:import and xsd:include tags. After parsing all the XSD files present it
     * resolves the references existent in the XSD language, represented by the ref attribute. When this method finishes
     * the parse results and remaining unsolved references are accessible by the {@link XsdParser#getResultXsdSchemas()},
     * {@link XsdParser#getResultXsdElements()} and {@link XsdParser#getUnsolvedReferences()}.
     * @param filePath States the path of the XSD file to be parsed.
     */
    public XsdParser(String filePath){
        schemaLocations.add(filePath);
        int index = 0;

        while (schemaLocations.size() > index){
            String schemaLocation = schemaLocations.get(index);
            parseFile(schemaLocation);
            ++index;
        }

        resolveRefs();
    }

    /**
     * Parses a XSD file and all its containing XSD elements. This code iterates on the nodes and parses the supported
     * ones. The supported types are all the XSD types that have their tag present in the {@link XsdParser#parseMappers}
     * field.
     * @param filePath The path to the XSD file.
     */
    private void parseFile(String filePath) {
        //https://www.mkyong.com/java/how-to-read-xml-file-in-java-dom-parser/
        try {
            if (!new File(filePath).exists()){
                throw new FileNotFoundException();
            }

            Node schemaNode = getSchemaNode(filePath);

            if (isXsdSchema(schemaNode)){
                XsdSchema.parse(this, schemaNode);
            } else {
                throw new ParsingException("The top level element of a XSD file should be the xsd:schema node.");
            }
        } catch (SAXException | IOException | ParserConfigurationException e) {
            Logger.getAnonymousLogger().log(Level.SEVERE, "Exception while parsing.", e);
        }
    }

    /**
     * This function uses DOM to obtain a list of nodes from a XSD file.
     * @param filePath The path to the XSD file.
     * @throws IOException If the file parsing throws {@link IOException}.
     * @throws SAXException if the file parsing throws {@link SAXException}.
     * @throws ParserConfigurationException If the {@link DocumentBuilderFactory#newDocumentBuilder()} throws
     *      {@link ParserConfigurationException}.
     * @return A list of nodes that represent the node tree of the XSD file with the path received.
     */
    private Node getSchemaNode(String filePath) throws IOException, SAXException, ParserConfigurationException {
        Document doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(filePath);

        doc.getDocumentElement().normalize();

        return doc.getFirstChild();
    }
}


package org.xmlet.xsdparser.xsdelements;

import org.w3c.dom.Node;
import org.xmlet.xsdparser.core.XsdParserCore;
import org.xmlet.xsdparser.xsdelements.elementswrapper.ConcreteElement;
import org.xmlet.xsdparser.xsdelements.elementswrapper.NamedConcreteElement;
import org.xmlet.xsdparser.xsdelements.elementswrapper.ReferenceBase;
import org.xmlet.xsdparser.xsdelements.elementswrapper.UnsolvedReference;
import org.xmlet.xsdparser.xsdelements.enums.BlockEnum;
import org.xmlet.xsdparser.xsdelements.enums.FinalEnum;
import org.xmlet.xsdparser.xsdelements.enums.FormEnum;
import org.xmlet.xsdparser.xsdelements.exceptions.ParsingException;
import org.xmlet.xsdparser.xsdelements.visitors.XsdAbstractElementVisitor;
import org.xmlet.xsdparser.xsdelements.visitors.XsdAnnotatedElementsVisitor;
import org.xmlet.xsdparser.xsdelements.visitors.XsdElementVisitor;

import javax.validation.constraints.NotNull;
import java.util.HashMap;
import java.util.Map;

/**
 * A class representing the xsd:element element. Extends {@link XsdNamedElements} because it's one of the
 * {@link XsdAbstractElement} concrete classes that can have a {@link XsdNamedElements#name} attribute.
 *
 * @see xsd:element description and usage at w3c
 */
public class XsdElement extends XsdNamedElements {

    public static final String XSD_TAG = "xsd:element";
    public static final String XS_TAG = "xs:element";

    /**
     * {@link XsdElementVisitor} which restricts its children to {@link XsdComplexType} and {@link XsdSimpleType}
     * instances.
     * Can also have {@link XsdAnnotation} as children as per inheritance of {@link XsdAnnotatedElementsVisitor}.
     */
    private XsdElementVisitor visitor = new XsdElementVisitor(this);

    /**
     * The {@link XsdComplexType} instance wrapped in a {@link ReferenceBase} object.
     */
    private ReferenceBase complexType;

    /**
     * The {@link XsdSimpleType} instance wrapped in a {@link ReferenceBase} object.
     */
    private ReferenceBase simpleType;

    /**
     * The type of the current element.
     * Either specified a built in data type or is a reference to a existent {@link XsdComplexType} or a
     * {@link XsdSimpleType} instances.
     */
    private ReferenceBase type;

    /**
     * Specifies the name of an element that can be substituted with this element. Only should be present if this
     * {@link XsdElement} is a top level element, i.e. his parent is a XsdSchema element.
     */
    private ReferenceBase substitutionGroup;

    /**
     * Specifies a default value for the element. It's only available if the type contents are text only type defined by
     * a simpleType.
     */
    private String defaultObj;

    /**
     * Specifies a fixed value for the element. It's only available if the type contents are text only type defined by
     * a simpleType.
     */
    private String fixed;

    /**
     * Specifies if the current {@link XsdElement} attribute is "qualified" or "unqualified".
     */
    private FormEnum form;

    /**
     * Specifies if the this {@link XsdElement} support a null value.
     */
    private boolean nillable;

    /**
     * Specifies whether the element can be used in an instance document.
     */
    private boolean abstractObj;

    /**
     * Prevents an element with a specified type of derivation from being used in place of this {@link XsdElement} element.
     * Possible values are:
        * extension - prevents elements derived by extension;
        * restriction - prevents elements derived by restriction;
        * substitution - prevents elements derived by substitution;
        * #all - all of the above.
     */
    private BlockEnum block;

    /**
     * Prevents other elements to derive depending on its value. This attribute cannot be present unless this
     * {@link XsdElement} is a top level element, i.e. his parent is a XsdSchema element.
         * extension - prevents elements derived by extension;
         * restriction - prevents elements derived by restriction;
         * #all - all of the above.
     */
    private FinalEnum finalObj;

    /**
     * Specifies the minimum number of times this element can occur in the parent element. The value can be any
     * number bigger or equal to 0. Default value is 1. This attribute cannot be used if the parent element is the
     * XsdSchema element.
     */
    private Integer minOccurs;

    /**
     * Specifies the maximum number of times this element can occur in the parent element. The value can be any
     * number bigger or equal to 0, or if you want to set no limit on the maximum number, use the value "unbounded".
     * Default value is 1. This attribute cannot be used if the parent element is the XsdSchema element.
     */
    private String maxOccurs;

    public XsdElement(@NotNull XsdParserCore parser, @NotNull Map<String, String> attributesMap) {
        super(parser, attributesMap);

        String typeString = attributesMap.get(TYPE_TAG);

        if (typeString != null){
            if (XsdParserCore.getXsdTypesToJava().containsKey(typeString)){
                HashMap<String, String> attributes = new HashMap<>();
                attributes.put(NAME_TAG, typeString);
                this.type = ReferenceBase.createFromXsd(new XsdComplexType(this, this.parser, attributes));
            } else {
                this.type = new UnsolvedReference(typeString, new XsdElement(this, this.parser, new HashMap<>()));
                parser.addUnsolvedReference((UnsolvedReference) this.type);
            }
        }

        String formDefault = AttributeValidations.getFormDefaultValue(parent);
        String blockDefault = AttributeValidations.getBlockDefaultValue(parent);
        String finalDefault = AttributeValidations.getFinalDefaultValue(parent);
        String substitutionGroup = attributesMap.getOrDefault(SUBSTITUTION_GROUP_TAG, null);

        if (substitutionGroup != null){
            this.substitutionGroup = new UnsolvedReference(substitutionGroup, new XsdElement(this, this.parser, new HashMap<>()));
            parser.addUnsolvedReference((UnsolvedReference) this.substitutionGroup);
        }

        this.defaultObj = attributesMap.getOrDefault(DEFAULT_TAG, defaultObj);
        this.fixed = attributesMap.getOrDefault(FIXED_TAG, fixed);
        this.form = AttributeValidations.belongsToEnum(FormEnum.QUALIFIED, attributesMap.getOrDefault(FORM_TAG, formDefault));
        this.nillable = AttributeValidations.validateBoolean(attributesMap.getOrDefault(NILLABLE_TAG, "false"));
        this.abstractObj = AttributeValidations.validateBoolean(attributesMap.getOrDefault(ABSTRACT_TAG, "false"));
        this.block = AttributeValidations.belongsToEnum(BlockEnum.ALL, attributesMap.getOrDefault(BLOCK_TAG, blockDefault));
        this.finalObj = AttributeValidations.belongsToEnum(FinalEnum.ALL, attributesMap.getOrDefault(FINAL_TAG, finalDefault));
        this.minOccurs = AttributeValidations.validateNonNegativeInteger(XSD_TAG, MIN_OCCURS_TAG, attributesMap.getOrDefault(MIN_OCCURS_TAG, "1"));
        this.maxOccurs = AttributeValidations.maxOccursValidation(XSD_TAG, attributesMap.getOrDefault(MAX_OCCURS_TAG, "1"));
    }

    public XsdElement(XsdAbstractElement parent, @NotNull XsdParserCore parser, @NotNull Map<String, String> elementFieldsMapParam) {
        this(parser, elementFieldsMapParam);
        setParent(parent);
    }

    /**
     * Runs verifications on each concrete element to ensure that the XSD schema rules are verified.
     */
    @Override
    public void validateSchemaRules() {
        super.validateSchemaRules();

        rule2();
        rule3();
        rule4();
        rule5();
        rule6();
        rule7();
    }

    private static String xsdElementIsXsdSchema = XSD_TAG + " is a " + XsdSchema.XSD_TAG + " element.";

    /**
     * Asserts if the current object has a form attribute while being a direct child of the top level XsdSchema element,
     * which isn't allowed, throwing an exception in that case.
     */
    private void rule7() {
        if (parent instanceof XsdSchema && attributesMap.containsKey(FORM_TAG)){
            throw new ParsingException(XSD_TAG + " element: The " + FORM_TAG + " attribute can only be present when the parent of the " + xsdElementIsXsdSchema);
        }
    }

    private void rule6() {
        //fixed 	Optional. Specifies a fixed value for the element (can only be used if the element's content is a simple type or text only)
    }

    private void rule5() {
        // default 	Optional. Specifies a default value for the element (can only be used if the element's content is a simple type or text only)
    }

    /**
     * Asserts if the current object isn't a direct child of the top level XsdSchema and has a value for the substitutionGroup,
     * which isn't allowed, throwing an exception in that case.
     */
    private void rule4() {
        if (!(parent instanceof XsdSchema) && substitutionGroup != null){
            throw new ParsingException(XSD_TAG + " element: The " + SUBSTITUTION_GROUP_TAG + " attribute can only be present when the parent of the " + xsdElementIsXsdSchema);
        }
    }

    /**
     * Asserts if the current object has a ref attribute while being a direct child of the top level XsdSchema element, which isn't allowed,
     * throwing an exception in that case.
     */
    private void rule3() {
        if (parent instanceof XsdSchema && attributesMap.containsKey(REF_TAG)){
            throw new ParsingException(XSD_TAG + " element: The " + REF_TAG + " attribute cannot be present when the parent of the " + xsdElementIsXsdSchema);
        }
    }

    /**
     * Asserts if the current object is a direct child of the top level XsdSchema element and doesn't have a name, which isn't allowed,
     * throwing an exception in that case.
     */
    private void rule2() {
        if (parent instanceof XsdSchema && name == null){
            throw new ParsingException(XSD_TAG + " element: The " + NAME_TAG + " attribute is required when the parent of the " + xsdElementIsXsdSchema);
        }
    }

    @Override
    public void accept(XsdAbstractElementVisitor visitorParam) {
        super.accept(visitorParam);
        visitorParam.visit(this);
    }

    @Override
    public XsdElementVisitor getVisitor() {
        return visitor;
    }

    /**
     * Performs a copy of the current object for replacing purposes. The cloned objects are used to replace
     * {@link UnsolvedReference} objects in the reference solving process.
     * @param placeHolderAttributes The additional attributes to add to the clone.
     * @return A copy of the object from which is called upon.
     */
    @Override
    public XsdElement clone(@NotNull Map<String, String> placeHolderAttributes) {
        placeHolderAttributes.putAll(attributesMap);
        placeHolderAttributes.remove(TYPE_TAG);
        placeHolderAttributes.remove(REF_TAG);

        XsdElement elementCopy = new XsdElement(this.parent, this.parser, placeHolderAttributes);

        elementCopy.simpleType = this.simpleType;
        elementCopy.complexType = this.complexType;
        elementCopy.type = this.type;

        return elementCopy;
    }

    /**
     * This method aims to replace the previously created {@link UnsolvedReference} in case that the type of the
     * current {@link XsdElement} instance is not a built-in type.
     * @param element A concrete element with a name that will replace the {@link UnsolvedReference} object created in the
     *                {@link XsdElement} constructor. The {@link UnsolvedReference} is only replaced if there
     *                is a match between the {@link UnsolvedReference#ref} and the {@link NamedConcreteElement#name}.
     */
    @Override
    public void replaceUnsolvedElements(NamedConcreteElement element) {
        super.replaceUnsolvedElements(element);

        XsdNamedElements elem = element.getElement();

        boolean isComplexOrSimpleType = elem instanceof XsdComplexType || elem instanceof XsdSimpleType;

        if (this.type instanceof UnsolvedReference && isComplexOrSimpleType && ((UnsolvedReference) this.type).getRef().equals(element.getName())){
            this.type = element;
            elem.setParent(this);
        }

        if (this.substitutionGroup instanceof UnsolvedReference && elem instanceof XsdElement && ((UnsolvedReference) this.substitutionGroup).getRef().equals(element.getName())){
            XsdElement xsdElement = (XsdElement) elem;

            this.type = xsdElement.type;
            this.simpleType = xsdElement.simpleType;
            this.complexType = xsdElement.complexType;
            this.substitutionGroup = element;
            elem.setParent(this);
        }
    }

    public XsdComplexType getXsdComplexType() {
        return complexType == null ? getXsdType() : (XsdComplexType) complexType.getElement();
    }

    public XsdSimpleType getXsdSimpleType(){
        return simpleType instanceof ConcreteElement ? (XsdSimpleType) simpleType.getElement() : null;
    }

    private XsdComplexType getXsdType(){
        if (type instanceof ConcreteElement){
            return (XsdComplexType) type.getElement();
        }

        return null;
    }

    public static ReferenceBase parse(@NotNull XsdParserCore parser, Node node){
        return xsdParseSkeleton(node, new XsdElement(parser, convertNodeMap(node.getAttributes())));
    }

    public String getFinal() {
        return finalObj.getValue();
    }

    @SuppressWarnings("unused")
    public boolean isNillable() {
        return nillable;
    }

    @SuppressWarnings("unused")
    public Integer getMinOccurs() {
        return minOccurs;
    }

    @SuppressWarnings("unused")
    public String getMaxOccurs() {
        return maxOccurs;
    }

    @SuppressWarnings("unused")
    public boolean isAbstractObj() {
        return abstractObj;
    }

    public void setComplexType(ReferenceBase complexType) {
        this.complexType = complexType;
    }

    public void setSimpleType(ReferenceBase simpleType) {
        this.simpleType = simpleType;
    }

    @SuppressWarnings("unused")
    public String getBlock() {
        return block.getValue();
    }

    @SuppressWarnings("unused")
    public String getForm() {
        return form.getValue();
    }

    public String getType(){
        if (this.type != null && this.type instanceof NamedConcreteElement){
            return ((NamedConcreteElement) this.type).getName();
        }

        return attributesMap.getOrDefault(TYPE_TAG, null);
    }

    public ReferenceBase getSubstitutionGroup() {
        return substitutionGroup;
    }
}


package org.xmlet.xsdparser.xsdelements;

import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.xmlet.xsdparser.core.XsdParser;
import org.xmlet.xsdparser.core.XsdParserCore;
import org.xmlet.xsdparser.xsdelements.elementswrapper.ConcreteElement;
import org.xmlet.xsdparser.xsdelements.elementswrapper.NamedConcreteElement;
import org.xmlet.xsdparser.xsdelements.elementswrapper.ReferenceBase;
import org.xmlet.xsdparser.xsdelements.elementswrapper.UnsolvedReference;
import org.xmlet.xsdparser.xsdelements.visitors.XsdAbstractElementVisitor;

import javax.validation.constraints.NotNull;
import java.util.*;
import java.util.function.BiFunction;
import java.util.stream.Stream;

/**
 * This class serves as a base to every element class, i.e. {@link XsdElement}, {@link XsdAttribute}, etc.
 */
public abstract class XsdAbstractElement {

    /**
     * A {@link Map} object containing the keys/values of the attributes that belong to the concrete element instance.
     */
    protected Map<String, String> attributesMap = new HashMap<>();

    static final String ATTRIBUTE_FORM_DEFAULT = "attribtueFormDefault";
    static final String ELEMENT_FORM_DEFAULT = "elementFormDefault";
    static final String BLOCK_DEFAULT = "blockDefault";
    static final String FINAL_DEFAULT = "finalDefault";
    static final String TARGET_NAMESPACE = "targetNamespace";
    static final String VERSION = "version";
    static final String XMLNS = "xmlns";

    static final String ID_TAG = "id";
    public static final String NAME_TAG = "name";
    static final String ABSTRACT_TAG = "abstract";
    static final String DEFAULT_ELEMENT_TAG = "defaultElement";
    protected static final String FIXED_TAG = "fixed";
    static final String TYPE_TAG = "type";
    static final String MIXED_TAG = "mixed";
    static final String BLOCK_TAG = "block";
    static final String FINAL_TAG = "final";
    static final String USE_TAG = "use";
    static final String SUBSTITUTION_GROUP_TAG = "substitutionGroup";
    static final String DEFAULT_TAG = "default";
    static final String FORM_TAG = "form";
    static final String NILLABLE_TAG = "nillable";
    static final String MIN_OCCURS_TAG = "minOccurs";
    static final String MAX_OCCURS_TAG = "maxOccurs";
    static final String ITEM_TYPE_TAG = "itemType";
    static final String BASE_TAG = "base";
    static final String SOURCE_TAG = "source";
    static final String XML_LANG_TAG = "xml:lang";
    static final String MEMBER_TYPES_TAG = "memberTypes";
    static final String SCHEMA_LOCATION = "schemaLocation";
    static final String NAMESPACE = "namespace";
    public static final String REF_TAG = "ref";
    protected static final String VALUE_TAG = "value";

    /**
     * The instance which contains the present element.
     */
    XsdAbstractElement parent;

    /**
     * The {@link XsdParserCore} instance that parsed this element.
     */
    XsdParserCore parser;

    protected XsdAbstractElement(@NotNull XsdParserCore parser, @NotNull Map<String, String> attributesMap){
        this.parser = parser;
        this.attributesMap = attributesMap;
    }

    public Map<String, String> getAttributesMap() {
        return attributesMap;
    }

    /**
     * Obtains the visitor of a concrete {@link XsdAbstractElement} instance.
     * @return The concrete visitor instance.
     */
    public abstract XsdAbstractElementVisitor getVisitor();

    /**
     * Runs verifications on each concrete element to ensure that the XSD schema rules are verified.
     */
    public void validateSchemaRules(){ }

    /**
     * Base method for all accept methods. It serves as a way to guarantee that every accept call assigns the parent
     * field.
     * @param xsdAbstractElementVisitor The visitor that is visiting the current instance.
     */
    public void accept(XsdAbstractElementVisitor xsdAbstractElementVisitor){
        this.setParent(xsdAbstractElementVisitor.getOwner());
    }

    public List<ReferenceBase> getElements(){
        return Collections.emptyList();
    }

    /**
     * @return All the {@link ConcreteElement} objects present in the concrete implementation of the
     * {@link XsdAbstractElement} class. It doesn't return the {@link UnsolvedReference} objects.
     */
    public Stream<XsdAbstractElement> getXsdElements(){
        List<ReferenceBase> elements = getElements();

        if (elements == null){
            return new ArrayList<XsdAbstractElement>().stream();
        }

        return elements.stream().filter(element -> element instanceof ConcreteElement).map(ReferenceBase::getElement);
    }

    /**
     * The base code for parsing any {@link XsdAbstractElement}. All the concrete implementations of this class should
     * call this method in order to parse its children.
     * @param node The node from where the element will be parsed.
     * @param element The concrete element that will be populated and returned.
     * @return A wrapper object that contains the parsed XSD object.
     */
    static ReferenceBase xsdParseSkeleton(Node node, XsdAbstractElement element){
        XsdParserCore parser = element.getParser();
        Node child = node.getFirstChild();

        while (child != null) {
            if (child.getNodeType() == Node.ELEMENT_NODE) {
                String nodeName = child.getNodeName();

                BiFunction<XsdParserCore, Node, ReferenceBase> parserFunction = XsdParserCore.getParseMappers().get(nodeName);

                if (parserFunction != null){
                    XsdAbstractElement childElement = parserFunction.apply(parser, child).getElement();

                    childElement.accept(element.getVisitor());

                    childElement.validateSchemaRules();
                }
            }

            child = child.getNextSibling();
        }

        ReferenceBase wrappedElement = ReferenceBase.createFromXsd(element);

        parser.addParsedElement(wrappedElement);

        return wrappedElement;
    }

    public XsdParserCore getParser() {
        return parser;
    }

    /**
     * Converts a {@link NamedNodeMap} to a {@link Map} object. This is meant to simplify the manipulation of the
     * information.
     * @param nodeMap The {@link NamedNodeMap} to convert to a {@link Map} object.
     * @return The {@link Map} object that was generated by the conversion.
     */
    protected static Map<String, String> convertNodeMap(NamedNodeMap nodeMap){
        HashMap<String, String> attributesMapped = new HashMap<>();

        for (int i = 0; i < nodeMap.getLength(); i++) {
            Node node = nodeMap.item(i);
            attributesMapped.put(node.getNodeName(), node.getNodeValue());
        }

        return attributesMapped;
    }

    /**
     * This method iterates on the current element children and replaces any {@link UnsolvedReference} object that has a
     * ref attribute that matches the receiving {@link NamedConcreteElement} name attribute.
     * @param element A fully parsed element with a name that will replace an {@link UnsolvedReference} object, if a
     *                match between the {@link NamedConcreteElement} name attribute and the {@link UnsolvedReference}
     *                ref attribute.
     */
    public void replaceUnsolvedElements(NamedConcreteElement element){
        List<ReferenceBase> elements = this.getElements();

        if (elements != null){
            elements.stream()
                .filter(referenceBase -> referenceBase instanceof UnsolvedReference)
                .map(referenceBase -> (UnsolvedReference) referenceBase)
                .filter(unsolvedReference -> unsolvedReference.getRef().equals(element.getName()))
                .findFirst()
                .ifPresent(oldElement -> elements.set(elements.indexOf(oldElement), element));
        }
    }

    /**
     * @return The parent of the current {@link XsdAbstractElement} object.
     */
    public XsdAbstractElement getParent() {
        return parent;
    }

    public void setParent(XsdAbstractElement parent) {
        this.parent = parent;
    }

    /**
     * In special cases such as {@link XsdAppInfo} and {@link XsdDocumentation} the contents are a simple text node,
     * in which case this function is more suited than using the {@link XsdAbstractElement#xsdParseSkeleton} since
     * those types of elements can't have children nodes.
     * @param node The {@link Node} containing either a {@link XsdAppInfo} or {@link XsdDocumentation}.
     * @return The textual value contained in the {@link Node} parameter.
     */
    static String xsdRawContentParse(Node node) {
        Node child = node.getFirstChild();
        StringBuilder stringBuilder = new StringBuilder();

        while (child != null) {
            if (child.getNodeType() == Node.TEXT_NODE) {
                stringBuilder.append(child.getTextContent());
            }

            child = child.getNextSibling();
        }

        return stringBuilder.toString();
    }
}


package org.xmlet.xsdparser.xsdelements.xsdrestrictions;

import org.xmlet.xsdparser.core.XsdParserCore;
import org.xmlet.xsdparser.xsdelements.AttributeValidations;
import org.xmlet.xsdparser.xsdelements.XsdAnnotatedElements;
import org.xmlet.xsdparser.xsdelements.visitors.XsdAnnotatedElementsVisitor;

import javax.validation.constraints.NotNull;
import java.util.Map;

/**
 * This class serves as a base to every different restriction that has its restricting parameter defined as an {@link Integer}.
 * Classes like {@link XsdMaxLength} or {@link XsdLength} should extend this class.
 * i.e. xsd:maxLength or xsd:length.
 */
public class XsdIntegerRestrictions extends XsdAnnotatedElements {

    private XsdAnnotatedElementsVisitor visitor = new XsdAnnotatedElementsVisitor(this);

    /**
     * Indicates if the value is fixed.
     */
    private boolean fixed;

    /**
     * The value of associated with a given restriction. This field has different meanings depending on the concrete
     * restriction, e.g. if the concrete class is {@link XsdLength} this field means that the attribute which
     * has the restriction can only have the length specified in this field..
     */
    protected int value;

    XsdIntegerRestrictions(@NotNull XsdParserCore parser, @NotNull Map<String, String> elementFieldsMapParam) {
        super(parser, elementFieldsMapParam);

        fixed = AttributeValidations.validateBoolean(attributesMap.getOrDefault(FIXED_TAG, "false"));
    }

    @Override
    public XsdAnnotatedElementsVisitor getVisitor() {
        return visitor;
    }

    /**
     * Compares two different objects of this type.
     * @param o1 The first object.
     * @param o2 The object to compare.
     * @return True if the value of both classes is different, False if the value is equal.
     */
    public static boolean hasDifferentValue(XsdIntegerRestrictions o1, XsdIntegerRestrictions o2) {
        if (o1 == null && o2 == null) {
            return false;
        }

        int o1Value = Integer.MAX_VALUE;
        int o2Value;

        if (o1 != null) {
            o1Value = o1.getValue();
        }

        if (o2 != null) {
            o2Value = o2.getValue();
            return o2Value == o1Value;
        }

        return false;
    }

    public int getValue() {
        return value;
    }

    public boolean isFixed() {
        return fixed;
    }
}