/*
 * Copyright (c) 2004, Oracle and/or its affiliates. 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.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

package sun.jvmstat.perfdata.monitor.protocol.local;

import java.io.File;
import java.io.FilenameFilter;

/**
 * Class to provide translations from the local Vm Identifier
 * name space into the file system name space and vice-versa.
 * <p>
 * Provides a factory for creating a File object to the backing
 * store file for instrumentation shared memory region for a JVM
 * identified by its Local Java Virtual Machine Identifier, or
 * <em>lvmid</em>.
 *
 * @author Brian Doherty
 * @since 1.5
 * @see java.io.File
 */
public class PerfDataFile {
    private PerfDataFile() { };

    /**
     * The name of the of the system dependent temporary directory
     */
    public static final String tmpDirName;

    /**
     * The file name prefix for PerfData shared memory files.
     * <p>
     * This prefix must be kept in sync with the prefix used by the JVM.
     */
    public static final String dirNamePrefix = "hsperfdata_";

    /**
     * The directory name pattern for the user directories.
     */
    public static final String userDirNamePattern = "hsperfdata_\\S*";

    /**
     * The file name pattern for PerfData shared memory files.
     * <p>
     * This pattern must be kept in synch with the file name pattern
     * used by the 1.4.2 and later HotSpot JVM.
     */
    public static final String fileNamePattern = "^[0-9]+$";

    /**
     * The file name pattern for 1.4.1 PerfData shared memory files.
     * <p>
     * This pattern must be kept in synch with the file name pattern
     * used by the 1.4.1 HotSpot JVM.
     */
    public static final String tmpFileNamePattern =
            "^hsperfdata_[0-9]+(_[1-2]+)?$";


    /**
     * Get a File object for the instrumentation backing store file
     * for the JVM identified by the given local Vm Identifier.
     * <p>
     * This method looks for the most up to date backing store file for
     * the given <tt>lvmid</tt>. It will search all the user specific
     * directories in the temporary directory for the host operating
     * system, which may be influenced by platform specific environment
     * variables.
     *
     * @param lvmid  the local Java Virtual Machine Identifier for the target
     * @return File - a File object to the backing store file for the named
     *                shared memory region of the target JVM.
     * @see java.io.File
     * @see #getTempDirectory()
     */
    public static File getFile(int lvmid) {
        if (lvmid == 0) {
            /*
             * lvmid == 0 is used to indicate the current Java Virtual Machine.
             * If the SDK provided an API to get a unique Java Virtual Machine
             * identifier, then a filename could be constructed with that
             * identifier. In absence of such an api, return null.
             */
            return null;
        }

        /*
         * iterate over all files in all directories in tmpDirName that
         * match the file name patterns.
         */
        File tmpDir = new File(tmpDirName);
        String[] files = tmpDir.list(new FilenameFilter() {
            public boolean accept(File dir, String name) {
                if (!name.startsWith(dirNamePrefix)) {
                    return false;
                }
                File candidate = new File(dir, name);
                return ((candidate.isDirectory() || candidate.isFile())
                        && candidate.canRead());
            }
        });

        long newestTime = 0;
        File newest = null;

        for (int i = 0; i < files.length; i++) {
            File f = new File(tmpDirName + files[i]);
            File candidate = null;

            if (f.exists() && f.isDirectory()) {
                /*
                 * found a directory matching the name patterns. This
                 * is a 1.4.2 hsperfdata_<user> directory. Check for
                 * file named <lvmid> in that directory
                 */
                String name = Integer.toString(lvmid);
                candidate = new File(f.getName(), name);

            } else if (f.exists() && f.isFile()) {
                /*
                 * found a file matching the name patterns. This
                 * is a 1.4.1 hsperfdata_<lvmid> file.
                 */
                candidate = f;

            } else {
                // unexpected - let conditional below filter this one out
                candidate = f;
            }

            if (candidate.exists() && candidate.isFile()
                    && candidate.canRead()) {
                long modTime = candidate.lastModified();
                if (modTime >= newestTime) {
                    newestTime = modTime;
                    newest = candidate;
                }
            }
        }
        return newest;
    }

    /**
     * Return the File object for the backing store file for the specified Java
     * Virtual Machine.
     * <p>
     * This method looks for the most up to date backing store file for
     * the JVM identified by the given user name and lvmid. The directory
     * searched is the temporary directory for the host operating system,
     * which may be influenced by environment variables.
     *
     * @param user   the user name
     * @param lvmid  the local Java Virtual Machine Identifier for the target
     * @return File - a File object to the backing store file for the named
     *                shared memory region of the target JVM.
     * @see java.io.File
     * @see #getTempDirectory()
     */
    public static File getFile(String user, int lvmid) {
        if (lvmid == 0) {
            /*
             * lvmid == 0 is used to indicate the current Java Virtual Machine.
             * If the SDK provided an API to get a unique Java Virtual Machine
             * identifier, then a filename could be constructed with that
             * identifier. In absence of such an api, return null.
             */
            return null;
        }

        // first try for 1.4.2 and later JVMs
        String basename = getTempDirectory(user) + Integer.toString(lvmid);
        File f = new File(basename);

        if (f.exists() && f.isFile() && f.canRead()) {
            return f;
        }

        // No hit on 1.4.2 JVMs, try 1.4.1 files
        long newestTime = 0;
        File newest = null;
        for (int i = 0; i < 2; i++) {
            if (i == 0) {
                basename = getTempDirectory() + Integer.toString(lvmid);
            } else {
                basename = getTempDirectory() + Integer.toString(lvmid)
                           + Integer.toString(i);
            }

            f = new File(basename);

            if (f.exists() && f.isFile() && f.canRead()) {
                long modTime = f.lastModified();
                if (modTime >= newestTime) {
                    newestTime = modTime;
                    newest = f;
                }
            }
        }
        return newest;
    }

    /**
     * Method to extract a local Java Virtual Machine Identifier from the
     * file name of the given File object.
     *
     * @param file A File object representing the name of a
     *             shared memory region for a target JVM
     * @return int - the local Java Virtual Machine Identifier for the target
     *               associated with the file
     * @throws java.lang.IllegalArgumentException Thrown if the file name
     *               does not conform to the expected pattern
     */
    public static int getLocalVmId(File file) {
        int lvmid = 0;

        try {
            // try 1.4.2 and later format first
            return Integer.parseInt(file.getName());
        } catch (NumberFormatException e) { }

        // now try the 1.4.1 format
        String name = file.getName();
        if (name.startsWith(dirNamePrefix)) {
            int first = name.indexOf('_');
            int last = name.lastIndexOf('_');
            try {
                if (first == last) {
                    return Integer.parseInt(name.substring(first + 1));
                } else {
                    return Integer.parseInt(name.substring(first + 1, last));
                }
            } catch (NumberFormatException e) { }
        }
        throw new IllegalArgumentException("file name does not match pattern");
    }

    /**
     * Return the name of the temporary directory being searched for
     * HotSpot PerfData backing store files.
     * <p>
     * This method generally returns the value of the java.io.tmpdir
     * property. However, on some platforms it may return a different
     * directory, as the JVM implementation may store the PerfData backing
     * store files in a different directory for performance reasons.
     *
     * @return String - the name of the temporary directory.
     */
    public static String getTempDirectory() {
        return tmpDirName;
    }

    /**
     * Return the name of the temporary directory to be searched
     * for HotSpot PerfData backing store files for a given user.
     * <p>
     * This method generally returns the name of a subdirectory of
     * the directory indicated in the java.io.tmpdir property. However,
     * on some platforms it may return a different directory, as the
     * JVM implementation may store the PerfData backing store files
     * in a different directory for performance reasons.
     *
     * @return String - the name of the temporary directory.
     */
    public static String getTempDirectory(String user) {
        return tmpDirName + dirNamePrefix + user + File.separator;
    }

    /*
     * this static initializer would not be necessary if the
     * Solaris java.io.tmpdir property were set to /tmp by default
     */
    static {
        /*
         * Why is java.io.tmpdir on Solaris set to "/var/tmp/" when the
         * HotSpot JVM os:get_temp_path() method returns "/tmp/"
         *
         * Why do Solaris and Windows return a string with a trailing
         * file separator character where as Linix does not? (this change
         * seems to have occurred sometime during hopper beta)
         */
        String tmpdir = System.getProperty("java.io.tmpdir");

        if (tmpdir.compareTo("/var/tmp/") == 0) {
             /*
              * shared memory files are created in /tmp. Interestingly,
              * java.io.tmpdir is set to "/var/tmp/" on Solaris and Linux,
              * but os::get_temp_directory() is set to "/tmp/" on these
              * platforms. the java.io.logging packages also makes reference
              * to java.io.tmpdir.
              */
             tmpdir = "/tmp/";
        }

        /*
         * Assure that the string returned has a trailing File.separator
         * character. This check was added because the Linux implementation
         * changed such that the java.io.tmpdir string no longer terminates
         * with a File.separator character.
         */
        if (tmpdir.lastIndexOf(File.separator) != (tmpdir.length()-1)) {
            tmpdir = tmpdir + File.separator;
        }
        tmpDirName = tmpdir;
    }
}
