/*
 * Copyright 1999 Sun Microsystems, Inc.  All Rights Reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Sun designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Sun in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
 * CA 95054 USA or visit www.sun.com if you need additional information or
 * have any questions.
 */
/*
 * Licensed Materials - Property of IBM
 * 5639-D57 (C) COPYRIGHT International Business Machines Corp. 1997,1998
 * RMI-IIOP v1.0
 *
 */

package com.sun.tools.corba.se.idl.som.cff;

import java.lang.Exception;
import java.lang.String;
import java.lang.System;
import java.io.BufferedInputStream;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.io.IOException;
import java.util.Locale;
import java.util.NoSuchElementException;
import java.util.Properties;
import java.util.StringTokenizer;
import java.util.zip.*;

/**
 * FileLocator is an abstract class (one that cannot be instantiated) that
 * provides class methods for finding files in the directories or zip
 * archives that make up the CLASSPATH.
 *
 * @author      Larry K. Raper
 */
public abstract class FileLocator extends Object {

    /* Class variables */


    static final Properties pp = System.getProperties ();
    static final String classPath = pp.getProperty ("java.class.path", ".");
    static final String pathSeparator = pp.getProperty ("path.separator", ";");

    /* Instance variables */

    /* [None, no instances of this class are ever instantiated.] */

    /**
     * locateClassFile returns a DataInputStream with mark/reset
     * capability that can be used to read the requested class file.  The
     * CLASSPATH is used to locate the class.
     *
     * @param classFileName The name of the class to locate.  The class name
     * should be given in fully-qualified form, for example:
     * <pre>
     *     java.lang.Object
     *     java.io.DataInputStream
     * </pre>
     *
     * @exception java.io.FileNotFoundException The requested class file
     * could not be found.
     * @exception java.io.IOException The requested class file
     * could not be opened.
     */
    public static DataInputStream locateClassFile (String classFileName)
        throws FileNotFoundException, IOException {

        boolean notFound = true;
        StringTokenizer st;
        String path = "";
        String pathNameForm;
        File cf = null;
        NamedDataInputStream result;

        st = new StringTokenizer (classPath, pathSeparator, false);
        pathNameForm = classFileName.replace ('.', File.separatorChar) +
            ".class";

        while (st.hasMoreTokens () && notFound) {

            try {path = st.nextToken ();}
                catch (NoSuchElementException nse) {break;}
            int pLen = path.length ();
            String pathLast4 = pLen > 3 ? path.substring (pLen - 4) : "";
            if (pathLast4.equalsIgnoreCase (".zip") ||
                pathLast4.equalsIgnoreCase (".jar")) {

                try {

                    result = locateInZipFile (path, classFileName, true, true);
                    if (result == null)
                        continue;
                    return (DataInputStream) result;

                } catch (ZipException zfe) {
                    continue;
                } catch (IOException ioe) {
                    continue;
                }

            } else {
                try {cf = new File (path + File.separator + pathNameForm);
                } catch (NullPointerException npe) { continue; }
                if ((cf != null) && cf.exists ())
                    notFound = false;
            }
        }

        if (notFound) {

            /* Make one last attempt to find the file in the current
             * directory
             */

            int lastdot = classFileName.lastIndexOf ('.');
            String simpleName =
                (lastdot >= 0) ? classFileName.substring (lastdot+1) :
                classFileName;

            result = new NamedDataInputStream (new BufferedInputStream (
               new FileInputStream (simpleName + ".class")),
                   simpleName + ".class", false);
            return (DataInputStream) result;
        }

        result = new NamedDataInputStream (new BufferedInputStream (
            new FileInputStream (cf)), path + File.separator + pathNameForm,
                false);
        return (DataInputStream) result;

    }

    /**
     * locateLocaleSpecificFileInClassPath returns a DataInputStream that
     * can be used to read the requested file, but the name of the file is
     * determined using information from the current locale and the supplied
     * file name (which is treated as a "base" name, and is supplemented with
     * country and language related suffixes, obtained from the current
     * locale).  The CLASSPATH is used to locate the file.
     *
     * @param fileName The name of the file to locate.  The file name
     * may be qualified with a partial path name, using '/' as the separator
     * character or using separator characters appropriate for the host file
     * system, in which case each directory or zip file in the CLASSPATH will
     * be used as a base for finding the fully-qualified file.
     * Here is an example of how the supplied fileName is used as a base
     * for locating a locale-specific file:
     *
     * <pre>
     *     Supplied fileName: a/b/c/x.y,  current locale: US English
     *
     *                     Look first for: a/b/c/x_en_US.y
     *     (if that fails) Look next for:  a/b/c/x_en.y
     *     (if that fails) Look last for:  a/b/c/x.y
     *
     *     All elements of the class path are searched for each name,
     *     before the next possible name is tried.
     * </pre>
     *
     * @exception java.io.FileNotFoundException The requested class file
     * could not be found.
     * @exception java.io.IOException The requested class file
     * could not be opened.
     */
    public static DataInputStream locateLocaleSpecificFileInClassPath (
        String fileName) throws FileNotFoundException, IOException {

        String localeSuffix = "_" + Locale.getDefault ().toString ();
        int lastSlash = fileName.lastIndexOf ('/');
        int lastDot   = fileName.lastIndexOf ('.');
        String fnFront, fnEnd;
        DataInputStream result = null;
        boolean lastAttempt = false;

        if ((lastDot > 0) && (lastDot > lastSlash)) {
            fnFront = fileName.substring (0, lastDot);
            fnEnd   = fileName.substring (lastDot);
        } else {
            fnFront = fileName;
            fnEnd   = "";
        }

        while (true) {
            if (lastAttempt)
                result = locateFileInClassPath (fileName);
            else try {
                result = locateFileInClassPath (fnFront + localeSuffix + fnEnd);
            } catch (Exception e) { /* ignore */ }
            if ((result != null) || lastAttempt)
                break;
            int lastUnderbar = localeSuffix.lastIndexOf ('_');
            if (lastUnderbar > 0)
                localeSuffix = localeSuffix.substring (0, lastUnderbar);
            else
                lastAttempt = true;
        }
        return result;

    }

    /**
     * locateFileInClassPath returns a DataInputStream that can be used
     * to read the requested file.  The CLASSPATH is used to locate the file.
     *
     * @param fileName The name of the file to locate.  The file name
     * may be qualified with a partial path name, using '/' as the separator
     * character or using separator characters appropriate for the host file
     * system, in which case each directory or zip file in the CLASSPATH will
     * be used as a base for finding the fully-qualified file.
     *
     * @exception java.io.FileNotFoundException The requested class file
     * could not be found.
     * @exception java.io.IOException The requested class file
     * could not be opened.
     */
    public static DataInputStream locateFileInClassPath (String fileName)
        throws FileNotFoundException, IOException {

        boolean notFound = true;
        StringTokenizer st;
        String path = "";
        File cf = null;
        NamedDataInputStream result;

        String zipEntryName = File.separatorChar == '/' ? fileName :
            fileName.replace (File.separatorChar, '/');

        String localFileName = File.separatorChar == '/' ? fileName :
            fileName.replace ('/', File.separatorChar);

        st = new StringTokenizer (classPath, pathSeparator, false);

        while (st.hasMoreTokens () && notFound) {

            try {path = st.nextToken ();}
                catch (NoSuchElementException nse) {break;}
            int pLen = path.length ();
            String pathLast4 = pLen > 3 ? path.substring (pLen - 4) : "";
            if (pathLast4.equalsIgnoreCase (".zip") ||
                pathLast4.equalsIgnoreCase (".jar")) {

                try {

                    result = locateInZipFile (path, zipEntryName, false, false);
                    if (result == null)
                        continue;
                    return (DataInputStream) result;

                } catch (ZipException zfe) {
                    continue;
                } catch (IOException ioe) {
                    continue;
                }

            } else {
                try {cf = new File (path + File.separator + localFileName);
                } catch (NullPointerException npe) { continue; }
                if ((cf != null) && cf.exists ())
                    notFound = false;
            }
        }

        if (notFound) {

            /* Make one last attempt to find the file in the current
             * directory
             */

            int lastpart = localFileName.lastIndexOf (File.separator);
            String simpleName =
                (lastpart >= 0) ? localFileName.substring (lastpart+1) :
                localFileName;

            result = new NamedDataInputStream (new BufferedInputStream (
               new FileInputStream (simpleName)), simpleName, false);
            return (DataInputStream) result;
        }

        result = new NamedDataInputStream (new BufferedInputStream (
            new FileInputStream (cf)), path + File.separator + localFileName,
                false);
        return (DataInputStream) result;

    }

    /**
     * Returns the fully qualified file name associated with the passed
     * DataInputStream <i>if the DataInputStream was created using one
     * of the static locate methods supplied with this class</i>, otherwise
     * returns a zero length string.
     */
    public static String getFileNameFromStream (DataInputStream ds) {

        if (ds instanceof NamedDataInputStream)
            return ((NamedDataInputStream) ds).fullyQualifiedFileName;
        return "";

    }

    /**
     * Returns an indication of whether the passed DataInputStream is
     * associated with a member of a zip file <i>if the DataInputStream was
     * created using one of the static locate methods supplied with this
     * class</i>, otherwise returns false.
     */
    public static boolean isZipFileAssociatedWithStream (DataInputStream ds) {

        if (ds instanceof NamedDataInputStream)
            return ((NamedDataInputStream) ds).inZipFile;
        return false;

    }

    private static NamedDataInputStream locateInZipFile (String zipFileName,
        String fileName, boolean wantClass, boolean buffered)
        throws ZipException, IOException {

        ZipFile zf;
        ZipEntry ze;
        zf = new ZipFile (zipFileName);

        if (zf == null)
            return null;
        String zeName = wantClass ?
            fileName.replace ('.', '/') + ".class" :
            fileName;

        //  This code works with JDK 1.0 level SUN zip classes
        //

        //  ze = zf.get (zeName);
        //  if (ze == null)
        //      return null;
        //  return new NamedDataInputStream (
        //      new BufferedInputStream (new ZipInputStream (ze)),
        //          zipFileName + '(' +zeName + ')', true);

        //  This code works with JDK 1.0.2 and JDK 1.1 level SUN zip classes
        //

            ze = zf.getEntry (zeName);
            if (ze == null) {
                zf.close(); // D55355, D56419
                zf = null;
                return null;
            }
            InputStream istream = zf.getInputStream(ze);
            if (buffered)
                istream = new BufferedInputStream(istream);
            return new NamedDataInputStream (istream,
                    zipFileName + '(' + zeName + ')', true);

    }

}

/**
 * This class is used to associate a filename with a DataInputStream
 * The host platform's file naming conventions are assumed for the filename.
 *
 * @author      Larry K. Raper
 *
 */
/* default access */ class NamedDataInputStream extends DataInputStream {

    /* Instance variables */

    /**
     * The name of the file associated with the DataInputStream.
     */
    public String fullyQualifiedFileName;

    /**
     * Indicates whether or not the file is contained in a .zip file.
     */
    public boolean inZipFile;

    /* Constructors */

    protected NamedDataInputStream (InputStream in, String fullyQualifiedName,
        boolean inZipFile) {

        super (in);
        this.fullyQualifiedFileName = fullyQualifiedName;
        this.inZipFile = inZipFile;

    }

}
