/*   Copyright 2004 The Apache Software Foundation
 *
 *   Licensed under the Apache License, Version 2.0 (the "License");
 *   you may not use this file except in compliance with the License.
 *   You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *   Unless required by applicable law or agreed to in writing, software
 *   distributed under the License is distributed on an "AS IS" BASIS,
 *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *   See the License for the specific language governing permissions and
 *  limitations under the License.
 */

package org.apache.xmlbeans.samples.enumeration;

import org.apache.xmlbeans.XmlError;
import org.apache.xmlbeans.XmlException;
import org.apache.xmlbeans.XmlObject;
import org.apache.xmlbeans.XmlOptions;
import org.apache.xmlbeans.samples.enumeration.schemaenum.easypo.LineItem;
import org.apache.xmlbeans.samples.enumeration.schemaenum.easypo.PurchaseOrderDocument;
import org.apache.xmlbeans.samples.enumeration.schemaenum.pricesummary.ItemType;
import org.apache.xmlbeans.samples.enumeration.schemaenum.pricesummary.PriceSummaryDocument;
import org.apache.xmlbeans.samples.enumeration.schemaenum.pricesummary.PriceType;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;

/**
 * This sample illustrates how you can access XML values that are
 * defined in schema as enumerations. When a schema containing
 * enumerations is compiled, the generated Java types represent the
 * schema enumerations with Java enumerations. You can access these through
 * their constants and corresponding int values.
 * <p/>
 * The schemas used by this sample are defined in PriceSummary.xsd and
 * EasyPO.xsd.
 */
public class SchemaEnum
{
    /**
     * Receives an PO XML instance and uses its data to create an XML
     * document based another schema, and which summarizes the items
     * in the PO by price.
     *
     * @param args An array containing one argument: the path to an XML instance
     *             conforming to the schema in EasyPO.xsd.
     */
    public static void main(String[] args)
    {
        // Create an instance of this class to work with.
        SchemaEnum thisSample = new SchemaEnum();

        // Create an instance of a type based on the received XML's schema
        // and use it to print what the sample received.
        PurchaseOrderDocument poDoc = thisSample.parseXml(args[0]);
        System.out.println("Received XML: \n\n" + poDoc.toString());

        // Print the summarized items in XML based on a different schema.
        PriceSummaryDocument summaryDoc = thisSample.summarizeItems(poDoc);
        System.out.println("Summarized items: \n\n" + summaryDoc.toString());

        // Print a simple non-XML list of items by threshold.
        String sortedItems = thisSample.sortByThreshold(summaryDoc);
        System.out.println("Sorted items: \n" + sortedItems);

        // Validate the result.
        System.out.println("New XML is valid: " +
                thisSample.validateXml(summaryDoc));
    }

    /**
     * <p>This method uses values in the incoming XML to construct
     * a new XML document of a different schema. PriceSummary.xsd, the schema
     * for the new document, defines XML enumerations for a price
     * threshold attribute. Items whose price is between $10 and $20 receive
     * a threshold value of "Between10And20Dollars"; items above 20 get a threshold
     * value of "Above20Dollars".</p>
     * <p/>
     * <p>This method loops through the purchase order items, creating a summary
     * document that specifies their threshold value.</p>
     * <p/>
     * <p>You can verify this method's work by comparing the resulting XML with
     * the XML in PriceSummary.xml. You can also use this method's return value
     * to test the sortByThreshold method.</p>
     */
    public PriceSummaryDocument summarizeItems(PurchaseOrderDocument poDoc)
    {
        PurchaseOrderDocument.PurchaseOrder po = poDoc.getPurchaseOrder();

        // Create a new instance of the PriceSummary schema. This is the document
        // the code creates, extracting values from the purchase order.
        PriceSummaryDocument summaryDoc = PriceSummaryDocument.Factory.newInstance();
        PriceSummaryDocument.PriceSummary summary = summaryDoc.addNewPriceSummary();

        // Create <price> elements to hold <item> elements according to their
        // price threshold.
        PriceType priceZero = summary.addNewPrice();
        PriceType priceTen = summary.addNewPrice();
        PriceType priceTwenty = summary.addNewPrice();

        // Set the threshold attribute value for the two new elements.
        priceZero.setThreshold(PriceType.Threshold.BELOW_10_DOLLARS);
        priceTen.setThreshold(PriceType.Threshold.BETWEEN_10_AND_20_DOLLARS);
        priceTwenty.setThreshold(PriceType.Threshold.ABOVE_20_DOLLARS);

        // Loop through the purchase order <line-item> elements. If their
        // <price> child element is between 10.00 and 20.00, add the <line-item>
        // to the <price> element whose threshold is 10.00. For those over 20.00,
        // add them to the <price> element whose threshold is 20.00.

        // There don't happen to be any under 10.00, but handle this case anyway.
        LineItem[] items = po.getLineItemArray();
        for (int i = 0; i < items.length; i++)
        {
            LineItem item = items[i];

            if (item.getPrice() < 10.00)
            {

                ItemType newItem = priceZero.addNewItem();
                newItem.setTitle(item.getDescription());
                newItem.xsetQuantity(item.xgetQuantity());
                newItem.setAmount(item.getPrice());

            } else if (item.getPrice() >= 10.00 && item.getPrice() < 20.00)
            {

                ItemType newItem = priceTen.addNewItem();
                newItem.setTitle(item.getDescription());
                newItem.xsetQuantity(item.xgetQuantity());
                newItem.setAmount(item.getPrice());

            } else if (item.getPrice() >= 20.00)
            {

                ItemType newItem = priceTwenty.addNewItem();
                newItem.setTitle(item.getDescription());
                newItem.xsetQuantity(item.xgetQuantity());
                newItem.setAmount(item.getPrice());
            }
        }
        return summaryDoc;
    }

    /**
     * <p>This method loops through a price summary XML document to
     * create a string that lists the items grouped by threshold.
     * Unlike the summarizeItems method, which creates a new XML
     * document that contains an attribute whose value is enumerated,
     * this method retrieves values from an enumeration.</p>
     * <p/>
     * <p>This method illustrates how you can use the int value corresponding
     * to enumerations to specify them in Java switch statements.</p>
     */
    public String sortByThreshold(PriceSummaryDocument summaryDoc)
    {
        // Extract the summary element from the incoming XML, then use it
        // to extract an array of the price elements.
        PriceSummaryDocument.PriceSummary summary = summaryDoc.getPriceSummary();
        PriceType[] priceElements = summary.getPriceArray();

        StringBuilder responseBuffer = new StringBuilder();

        // Create string buffers to hold the sorted results of the values
        // retrieved.
        StringBuilder zeroBuffer = new StringBuilder("\nItems under 10 dollars: \n");
        StringBuilder tenBuffer = new StringBuilder("\nItems between 10 and 20 dollars: \n");
        StringBuilder twentyBuffer = new StringBuilder("\nItems more than 20 dollars: \n");

        // Loop through the price elements, extracting the array of <item> child
        // elements in each.
        for (int i = 0; i < priceElements.length; i++)
        {
            ItemType[] itemElements = priceElements[i].getItemArray();

            // Loop through the <item> elements, discovering which threshold
            // the item belongs to, then using the element's <title> value
            // in in a sorted list.
            for (int j = 0; j < itemElements.length; j++)
            {
                ItemType item = itemElements[j];

                // For each <item> element, find out the int value of its <price>
                // parent element's threshold attribute value. Append the item's
                // title to the appropriate string buffer.
                switch (priceElements[i].getThreshold().intValue())
                {

                    case PriceType.Threshold.INT_BELOW_10_DOLLARS:
                        zeroBuffer.append(" " + item.getTitle() + "\n");
                        break;

                    case PriceType.Threshold.INT_BETWEEN_10_AND_20_DOLLARS:
                        tenBuffer.append(" " + item.getTitle() + "\n");
                        break;

                    case PriceType.Threshold.INT_ABOVE_20_DOLLARS:
                        twentyBuffer.append(" " + item.getTitle() + "\n");
                        break;

                    default:
                        System.out.println("Yo! Something unexpected happened!");
                        break;
                }
            }
        }
        responseBuffer.append(tenBuffer);
        responseBuffer.append(twentyBuffer);
        return responseBuffer.toString();
    }

    /**
     * <p>Validates the XML, printing error messages when the XML is invalid. Note
     * that this method will properly validate any instance of a compiled schema
     * type because all of these types extend XmlObject.</p>
     * <p/>
     * <p>Note that in actual practice, you'll probably want to use an assertion
     * when validating if you want to ensure that your code doesn't pass along
     * invalid XML. This sample prints the generated XML whether or not it's
     * valid so that you can see the result in both cases.</p>
     *
     * @param xml The XML to validate.
     * @return <code>true</code> if the XML is valid; otherwise, <code>false</code>
     */
    public boolean validateXml(XmlObject xml)
    {
        boolean isXmlValid = false;

        // A collection instance to hold validation error messages.
        ArrayList validationMessages = new ArrayList();

        // Validate the XML, collecting messages.
        isXmlValid = xml.validate(new XmlOptions().setErrorListener(validationMessages));

        if (!isXmlValid)
        {
            System.out.println("Invalid XML: ");
            for (int i = 0; i < validationMessages.size(); i++)
            {
                XmlError error = (XmlError) validationMessages.get(i);
                System.out.println(error.getMessage());
                System.out.println(error.getObjectLocation());
            }
        }
        return isXmlValid;
    }

    /**
     * <p>Creates a File from the XML path provided in main arguments, then
     * parses the file's contents into a type generated from schema.</p>
     * <p/>
     * <p>Note that this work might have been done in main. Isolating it here
     * makes the code separately available from outside this class.</p>
     *
     * @param xmlFilePath A path to XML based on the schema in inventory.xsd.
     * @return An instance of a generated schema type that contains the parsed
     *         XML.
     */
    public PurchaseOrderDocument parseXml(String xmlFilePath)
    {
        File poFile = new File(xmlFilePath);
        PurchaseOrderDocument poDoc = null;
        try
        {
            poDoc = PurchaseOrderDocument.Factory.parse(poFile);
        } catch (XmlException e)
        {
            e.printStackTrace();
        } catch (IOException e)
        {
            e.printStackTrace();
        }
        return poDoc;
    }
}
