/*
 * (C) Copyright IBM Corp. 1997-1998  All rights reserved.
 *
 * US Government Users Restricted Rights Use, duplication or
 * disclosure restricted by GSA ADP Schedule Contract with IBM Corp.
 *
 * The program is provided "as is" without any warranty express or
 * implied, including the warranty of non-infringement and the implied
 * warranties of merchantibility and fitness for a particular purpose.
 * IBM will not be liable for any damages suffered by you as a result
 * of using the Program. In no event will IBM be liable for any
 * special, indirect or consequential damages or lost profits even if
 * IBM has been advised of the possibility of their occurrence. IBM
 * will not be liable for any third party claims against you.
 */

package com.ibm.xml.parser;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.CharArrayReader;
import java.io.CharArrayWriter;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;

/**
 * The Entity class provides support for XML entities.
 * Entities are useful in:
 * <ul compact>
 * <li>Representing non-standard characters within an XML document (Example: an e-acute symbol)
 * <li>Holding sections or chapters from a large XML document
 * <li>Organizing DTDs into logical sub-units
 * <li>Representing shorthand notation for frequently used phrases
 * <li>Holding markup common to multiple XML documents
 * </ul>
 * <p>Internal entities have a <var>value</var> that is directly given in the entity declaration.
 * For example:
 * <p><center><code>&lt;!ENTITY IBM &quot;International Business Machines&quot;&gt;</code></center>
 * <p>External entities refer to declarations to a storage unit by means of a SYSTEM or
 * PUBLIC identifier.  For example:
 * <p><center><code>&lt;!ENTITY IBM SYSTEM &quot;ibm.xml&quot;&gt;</code></center>
 * <p>..associates the name <code>IBM</code> with the URL <code>&quot;ibm.xml&quot;</code>.
 * XML4J must read the file referenced in order to find out the content of that entity.
 * <p>As you might expect, external Entities contain <var>External IDs</var> in order to provide
 * access to the resource.  External entities can can be either text or binary resources.
 * Text resources are XML-encoded resources, and are illustrated by this example:
 * <p><center><code>&lt;!ENTITY IBM SYSTEM &quot;ibm.xml&quot;&gt;</code></center>
 * <p>Binary entities are anything that is not XML-encoded.  Binary entities always include
 * a notation (<var>ndata</var>) which describes the type of resource.  For example:
 * <p><center><code>&lt;!ENTITY IBMLogo SYSTEM &quot;ibm.jpg&quot; NDATA JPEG&gt;</code></center>
 * <!--p>Only internal Entities can be <var>parameter</var> Entities; this special
 * type of entity is only used within an internal DTD.-->
 *
 * @version 1.1
 * @author TAMURA Kent &lt;kent@trl.ibm.co.jp&gt;
 * @see com.ibm.xml.parser.ExternalID
 * @see com.ibm.xml.parser.Child
 */
public class Entity extends Child {

        String      name        =   null;
        boolean     isParameter =   false;
        String      value       =   null;   // Always <var>null</var> for external Entities
        ExternalID  externalID  =   null;   // Always <var>null</var> for internal Entities
        String      ndata       =   null;   // Always <var>null</var> for internal Entities
        
        byte[]      rawByteStream   =   null;
        String      encoding        =   null;   // for rawByteStream
        char[]      rawCharStream   =   null;

    /**
     * Constructor for internal Entities.
     * @param   name        Name of this Entity.
     * @param   value       The XML-encoded value that was directly assigned to the Entity.
     * @param   isParameter =true if a parameter Entity; otherwise =false.
     */
    public Entity(String name, String value, boolean isParameter) {
        this.name           = name;
        this.value          = value;
        this.isParameter    = isParameter;
    }

    /**
     * Constructor for external Entities.
     * @param   name        Name of the Entity.
     * @param   externalID  The reference(s) to the external entity to retrieve. 
     * @param   isParameter =true if a parameter Entity; otherwise =false.
     * @param   ndata       The notation associated with the binary Entity, or <var>null</var> if
     *                      the Entity is a text Entity.
     * @see com.ibm.xml.parser.ExternalID
     */
    public Entity(String name, ExternalID externalID, boolean isParameter, String ndata) {
        this.name           = name;
        this.externalID     = externalID;
        this.isParameter    = isParameter;
        this.ndata          = ndata;
    }

    /**
     * Clone this Entity using the appropriate factory.  
     * <p>This method is defined by Child.
     * @return          Cloned Entity.
     * @see com.ibm.xml.parser.Child#clone
     */
    public synchronized Object clone() {
        checkFactory();
        Entity e = factory.createEntity(this.name, this.value, this.isParameter);
        e.setFactory(getFactory());
        e.externalID = this.externalID;
        e.ndata = this.ndata;
        e.rawByteStream = this.rawByteStream;
        e.rawCharStream = this.rawCharStream;
        e.encoding = this.encoding;
        return e;
    }
    
    /**
     * Returns that this object is an Entity Node. 
     * <p>This method is defined by DOM.
     * @return          Entity Node indicator.
     */
    public int getNodeType() {
        return Child.ENTITY;
    }
    
    /**
     * Returns the name associated with this Entity.
     * @return          Name of this entity.
     */
    public String getName() {
        return this.name;
    }
    
    /**
     * Returns whether this Entity is a parameter Entity.
     * @return          =true if an internal parameter Entity; otherwise =false.
     */
    public boolean isParameter() {
        return this.isParameter;
    }
    
    /**
     * Returns the value of this Entity.
     * @return              The XML-encoded value that was directly assigned to the internal Entity;
     *                      otherwise, <var>null</var>.
     */
    public String getValue() {
        return this.value;
    }
    
    /**
     * Returns the external ID of this Entity.
     * @return              The reference(s) to the external entity to retrieve; otherwise,
     *                      <var>null</var>. 
     * @see com.ibm.xml.parser.ExternalID
     */
    public ExternalID getExternalID() {
        return this.externalID;
    }

    /**
     * Returns whether this entity value is external.
     * @return              =true if entity is external; otherwise, =false. 
     * @see com.ibm.xml.parser.ExternalID
     */
    public boolean isExternal() {
        return this.externalID != null;
    }
    
    /**
     * Returns the notation associated with this Entity.
     * @return              The notation associated with the external binary Entity, otherwise,
     *                      <var>null</var>.
     */
    public String getNDATAType() {
        return this.ndata;
    }

    /**
     * Returns whether there is a  notation associated with this entity value.
     * @return              =true if the external binary entity contains a notation; otherwise, =false. 
     */
    public boolean isNDATA() {
        return this.ndata != null;
    }

    void load(Parser pa) throws IOException {
        if (getExternalID() != null && (null == this.rawByteStream || null == this.rawCharStream)) {
            try {
                Source src = pa.streamProducer.getInputStream(this.name, this.externalID.getPubidLiteral(),
                                                   this.externalID.getSystemLiteral());
                int ch;
                if (null != src.getInputStream()) {
                    ByteArrayOutputStream baos = new ByteArrayOutputStream();
                    InputStream is = src.getInputStream();
                    while (0 <= (ch = is.read()))
                        baos.write(ch);
                    is.close();
                    baos.close();
                    this.rawByteStream = baos.toByteArray();
                    this.encoding = src.getEncoding();
                } else {
                    CharArrayWriter caw = new CharArrayWriter();
                    Reader rr = src.getReader();
                    while (0 <= (ch = rr.read()))
                        caw.write(ch);
                    rr.close();
                    caw.close();
                    this.rawCharStream = caw.toCharArray();
                }
            } catch (IOException e) {
                this.rawByteStream = new byte[0];
                this.rawCharStream = new char[0];
                throw e;
            }
        }
    }

    Source getInputStream() {
        return null != this.rawByteStream
            ? new Source(new ByteArrayInputStream(this.rawByteStream), this.encoding)
            : new Source(new CharArrayReader(this.rawCharStream));
    }

    void setValue(String s) {
        this.value = s;
    }

    Token createToken(Parser p, Token tok) throws IOException {
        Token toks;
        if (getExternalID() != null) {
            try {
                load(p);
            } catch (FileNotFoundException e) {
                p.format1(tok, "E_IO0", getExternalID().getSystemLiteral());
            }
            toks = new Token(p, getExternalID().getSystemLiteral(), getInputStream());
        } else {
            toks = new Token(p, getValue());
            toks.getReading().setNext(tok.getReading());
            toks.setFollow(false);
        }
        return toks;
    }
}
