Skip to content
Snippets Groups Projects
InputSource.java 14.40 KiB
/*****************************************************************************
 *                        Web3d.org Copyright (c) 2001 - 2006
 *                               Java Source
 *
 * This source is licensed under the GNU LGPL v2.1
 * Please read http://www.gnu.org/copyleft/lgpl.html for more information
 *
 * This software comes with the standard NO WARRANTY disclaimer for any
 * purpose. Use it at your own risk. If there's a problem you get to fix it.
 *
 ****************************************************************************/
package org.web3d.vrml.sav;

// External imports
import java.io.File;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.IOException;
import java.io.Reader;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.security.AccessController;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.util.zip.GZIPInputStream;
import java.net.URLDecoder;

import org.ietf.uri.ResourceConnection;
import org.ietf.uri.URIUtils;
import org.ietf.uri.URL;
import org.ietf.uri.event.ProgressListener;

// Local imports
import org.web3d.vrml.util.URLChecker;

import org.xj3d.io.ReadProgressListener;
import org.xj3d.io.ReportableInputStream;
import org.xj3d.io.ReportableInputStreamReader;
import org.xj3d.io.ReportableReader;

/**
 * Representation of an input stream of bytes to the reader.
 * <p>
 *
 * @author Justin Couch
 * @version $Revision: 1.19 $
 */
public class InputSource {

    /** The encoding of the underlying stream. */
    private String encoding;

    /** The URL that represents the base of the passed file */
    private String baseURL;

    /** The fully qualified URL to the resource */
    private String realURL;

    /** The inputstream supplying bytes to us */
    private InputStream stream;

    /** The Reader representing the supply of characters to us */
    private Reader reader;

    /** The read progress listener */
    private ReadProgressListener readProgressListener;

    /** The progress listener */
    private ProgressListener progressListener;
    /** The size in bytes to issue update events */
    private int updateSize;

    /** The content type of the source */
    private String contentType;

    /**
     * Create an input source representation of the given URI string. This
     * may use the given string as a fully qualified URI that needs resolving.
     *
     * @param uri The URI to open
     * @throws MalformedURLException if uri can not be resolved as a URL complete with proper scheme
     */
    public InputSource(String uri) throws MalformedURLException {
        this(new File(uri));
    }

    /**
     * Create an input source representing the given file. It does not check
     * for the file existing or a directory on creation. This will be done at
     * the point stream is requested
     *
     * @param file the file to be used as the source
     * @throws MalformedURLException if the file uri can not be resolved as a URL complete with proper scheme
     */
    public InputSource(File file) throws MalformedURLException {
        this(new URL(file.getPath())); // <- This must be an org.ietf.uri.URL not a java.net.URL
    }

    /**
     * Create an input source representing the given URL. It does not open the
     * URL until the stream is requested.
     *
     * @param url The URL to use
     */
    public InputSource(java.net.URL url) {
        this(new URL(url));
    }

    /**
     * Create an input source representing the given URL. It does not open the
     * URL until the stream is requested.
     *
     * @param url The org.ietf.uri.URL to use
     */
    public InputSource(URL url) {
        realURL = url.toExternalForm();
        // debug
//        System.out.println("InputSource: " + url.getClass() + ":"); // paths can be long for console
//        System.out.println("  " + realURL);
        extractBaseUrl();
    }

    /**
     * Create an input source from the input stream and the defined base URL.
     * The base is required because it is not possible to determine this from
     * the stream.
     *
     * @param urlBase The name of the base URL for this stream
     * @param is The underlying stream to use
     */
    public InputSource(String urlBase, InputStream is) {
        this(urlBase, is, null);
    }

    /**
     * Create an input source from the input stream and the defined base URL
     * with the option of providing a known full URL. The base is required
     * because it is not possible to determine this from the stream. If the
     * real URL is not known, then set a value of null.
     *
     * @param urlBase The name of the base URL for this stream
     * @param is The underlying stream to use
     * @param fullUrl The fully qualified URL string
     */
    public InputSource(String urlBase, InputStream is, String fullUrl) {
        baseURL = urlBase;
        stream = is;
        realURL = fullUrl;
    }

    /**
     * Create an input source from the reader and the defined base URL. The
     * base is required because it is not possible to determine this from
     * the stream.
     *
     * @param urlBase The name of the base URL for this stream
     * @param rdr The underlying reader to use
     */
    public InputSource(String urlBase, Reader rdr) {
        this(urlBase, rdr, null);
    }

    /**
     * Create an input source from the reader and the defined base URL with
     * with the option of providing a known full URL. The base is required
     * because it is not possible to determine this from the stream. If the
     * real URL is not known, then set a value of null.
     *
     * @param urlBase The name of the base URL for this stream
     * @param rdr The underlying Reader to use
     * @param fullUrl The fully qualified URL string
     */
    public InputSource(String urlBase, Reader rdr, String fullUrl) {
        baseURL = urlBase;
        reader = rdr;
        realURL = fullUrl;
    }

    private void extractBaseUrl() {

        realURL = URLChecker.prependFileScheme(realURL);

        try {
            String[] stripped_file = URIUtils.stripFile(URLDecoder.decode(realURL, "UTF-8"));

            String path = stripped_file[0];
//            System.out.println("[InputSource] extractBaseUrl():"); // paths can be long for console
//            System.out.println("  " + path);

            // Would this really work? What if the uri is a URN?
            int index = path.lastIndexOf("/");
            baseURL = path.substring(0, index + 1);
        } catch(UnsupportedEncodingException uee) {
            uee.printStackTrace(System.err);
        }
    }

    /**
     * Get the encoding, binary or string of the underlying stream. The
     * encoding is that of the character stream of the file rather than the
     * VRML encoding statement in the file header eg UTF8. This will not be
     * available until the stream has been opened.
     *
     * @return The encoding string
     */
    public String getEncoding() {
        return encoding;
    }
    /**
     * Get a stream of the characters from the source. The encoding is the
     * same as that given by the getEncoding() method. If the underlying
     * stream has not been opened, then this will force it to open.
     *
     * @return The reader representing the underlying stream
     * @throws IOException An error opening the stream
     */
    public Reader getCharacterStream() throws IOException {
        Reader ret_val = null;

        if(reader == null) {
            if(stream == null) {

                URL url;
                try {
                    url = new URL(realURL);

                    final ResourceConnection conn = url.getResource();

                    if (progressListener != null)
                        conn.addProgressListener(progressListener);

                    AccessController.doPrivileged((PrivilegedExceptionAction<Object>) () -> {
                        conn.connect();
                        return null;
                    });

                    stream = conn.getInputStream();
                    contentType = conn.getContentType();
                    encoding = conn.getContentEncoding();

                    // If it is a gzip stream, then wrap the ordinary stream with
                    // something that can decode it.
                    if (encoding != null && encoding.equals("x-gzip")) {
                        stream = new GZIPInputStream(stream);
                    }

                    if (readProgressListener != null) {
                        ReportableInputStreamReader ris =
                            new ReportableInputStreamReader(false,
                                                            updateSize,
                                                            readProgressListener,
                                                            stream);
                        ret_val = ris;

                    } else {
                        ret_val = new InputStreamReader(stream);
                    }


                } catch(MalformedURLException mue) {
                    throw new IOException("Unable to locate file");
                } catch(PrivilegedActionException pae) {
                    throw (IOException)pae.getException();
                }
            } else {
                if (readProgressListener != null) {
                    ret_val = new ReportableInputStreamReader(false,
                                                              updateSize,
                                                              readProgressListener,
                                                              stream);

                } else {
                    ret_val = new InputStreamReader(stream);
                }
            }

            reader = ret_val;
        } else {
            if (!(reader instanceof ReportableReader) && readProgressListener != null) {
                ret_val = new ReportableReader(false,
                                               updateSize,
                                               readProgressListener,
                                               reader);

            } else {
                ret_val = reader;
            }
        }

        return ret_val;
    }

    /**
     * Get a stream of raw bytes from the source. If the underlying stream
     * has not been opened, this will force it to open. The current
     * implementation barfs if the user supplied a raw stream.
     *
     * @return The stream representing the underlying bytes
     * @throws IOException An error opening the stream
     */
    public InputStream getByteStream() throws IOException {
        if((reader != null) && (stream == null))
            throw new IOException("Raw reader provided. Can't make stream");

        if(stream == null) {
            URL url;

            try {
                url = new URL(realURL);

                final ResourceConnection conn = url.getResource();

                if (progressListener != null)
                    conn.addProgressListener(progressListener);

                AccessController.doPrivileged((PrivilegedExceptionAction<Object>) () -> {
                    conn.connect();
                    return null;
                });

                stream = conn.getInputStream();
                contentType = conn.getContentType();
                encoding = conn.getContentEncoding();

                if (encoding != null && encoding.equals("x-gzip")) {
                    stream = new GZIPInputStream(stream);
                }

                if (readProgressListener != null) {
                    ReportableInputStream ris =
                        new ReportableInputStream(false,
                                                  updateSize,
                                                  readProgressListener,
                                                  stream);
                    return ris;
                }
            } catch(MalformedURLException mue) {
                throw new IOException("Unable to locate file");
            } catch(PrivilegedActionException pae) {
                throw (IOException)pae.getException();
            }
        } else {
            if (!(stream instanceof ReportableInputStream) && readProgressListener != null) {
                stream = new ReportableInputStream(false,
                                                   updateSize,
                                                   readProgressListener,
                                                   stream);
            }
        }

        return stream;
    }

    /**
     * Get the base URL of this stream. This is used by code that may need to
     * resolve other relative URIs in the stream.
     *
     * @return A string representing the base URL of this connection
     */
    public String getBaseURL() {
        return baseURL;
    }

    /**
     * Get the fully qualified URL string to the source. If this is not known,
     * then null is returned and the user should use the base URL.
     *
     * @return A string representing the full URL of this connection or null
     */
    public String getURL() {
        return realURL;
    }

    /**
     * Close the underlying stream used by the source.
     *
     * @throws IOException An error closing the stream
     */
    public void close() throws IOException {
        if(stream != null)
            stream.close();

        if(reader != null)
            reader.close();
    }

    /**
     * Set the read progress listener.  If set it will be notified of read
     * progress.  This listener is used to expose the stream state to the URI library.
     *
     * @param listener The progress listener.
     * @param updateSize The number of bytes before issuing an update message.
     */
    public void setReadProgressListener(ReadProgressListener listener,
                                        int updateSize) {
        readProgressListener = listener;
        this.updateSize = updateSize;
    }

    /**
     * Set the progress listener.  This will notify the listener on changes
     * in download state and progress.
     *
     * @param listener The progress listener.
     */
    public void setProgressListener(ProgressListener listener) {
        progressListener = listener;
    }

    /**
     * Set the content type of this source.
     * @param type the type to set
     */
    public void setContentType(String type) {
        contentType = type;
    }

    /**
     * Get the content type of this source.
     * @return the content type
     */
    public String getContentType() {
        return contentType;
    }
}