/* 
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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 java.lang;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintStream;
import java.nio.channels.Channel;
import java.nio.channels.spi.SelectorProvider;
import java.security.Policy;
import java.util.Map;
import java.util.Properties;
import java.util.PropertyPermission;

import org.jikesrvm.VM;
import org.jikesrvm.CommandLineArgs;
import org.jikesrvm.classloader.RVMClass;
import org.jikesrvm.objectmodel.ObjectModel;
import org.jikesrvm.runtime.Time;

/**
 * Class System provides a standard place for programs to find system related
 * information. All System API is static.
 * 
 */
public final class System {

  // The standard input, output, and error streams.
  // Typically, these are connected to the shell which
  // ran the Java program.
  /**
   * Default input stream
   */
  public static final InputStream in;

  /**
   * Default output stream
   */
  public static final PrintStream out;

  /**
   * Default error output stream
   */
  public static final PrintStream err;

  // Get a ref to the Runtime instance for faster lookup
  private static final Runtime RUNTIME = Runtime.getRuntime();

  // The System Properties table
  private static Properties systemProperties;

  // The System default SecurityManager
  private static SecurityManager security;

  // Indicates whether the classes needed for
  // permission checks was initialized or not
  private static boolean security_initialized;

  // Initialize all the slots in System on first use.
  static {
    // Fill in the properties from the VM information.
    ensureProperties();
    // Set up standard in, out, and err.
    err = new String.ConsolePrintStream(new BufferedOutputStream(new FileOutputStream(
        FileDescriptor.err)));
    out = new String.ConsolePrintStream(new BufferedOutputStream(new FileOutputStream(
        FileDescriptor.out)));
    in = new BufferedInputStream(new FileInputStream(FileDescriptor.in));
  }

  /**
   * Sets the value of the static slot "in" in the receiver to the passed in
   * argument.
   * 
   * @param newIn the new value for in.
   */
  @SuppressWarnings("unused")
  public static void setIn(InputStream newIn) {
    SecurityManager secMgr = System.getSecurityManager();
    VMCommonLibrarySupport.setSystemStreamField("in", in);
  }

  /**
   * Sets the value of the static slot "out" in the receiver to the passed in
   * argument.
   * 
   * @param newOut the new value for out.
   */
  @SuppressWarnings("unused")
  public static void setOut(java.io.PrintStream newOut) {
    SecurityManager secMgr = System.getSecurityManager();
    VMCommonLibrarySupport.setSystemStreamField("out", newOut);
  }

  /**
   * Sets the value of the static slot "err" in the receiver to the passed in
   * argument.
   * 
   * @param newErr the new value for err.
   */
  @SuppressWarnings("unused")
  public static void setErr(java.io.PrintStream newErr) {
    SecurityManager secMgr = System.getSecurityManager();
    VMCommonLibrarySupport.setSystemStreamField("err", newErr);
  }

  /**
   * Prevents this class from being instantiated.
   */
  private System() {
  }

  /**
   * Copies the contents of <code>array1</code> starting at offset
   * <code>start1</code> into <code>array2</code> starting at offset
   * <code>start2</code> for <code>length</code> elements.
   * 
   * @param array1 the array to copy out of
   * @param start1 the starting index in array1
   * @param array2 the array to copy into
   * @param start2 the starting index in array2
   * @param length the number of elements in the array to copy
   */
  public static void arraycopy(Object array1, int start1, Object array2, int start2,
      int length) {
    VMCommonLibrarySupport.arraycopy(array1, start1, array2, start2, length);
  }
  /**
   * Answers the current time expressed as milliseconds since the time
   * 00:00:00 UTC on January 1, 1970.
   * 
   * @return the time in milliseconds.
   */
  public static long currentTimeMillis() {
    return Time.currentTimeMillis();
  }

  /**
   * <p>
   * Returns the most precise time measurement in nanoseconds that's
   * available.
   * </p>
   * 
   * @return The current time in nanoseconds.
   */
  public static long nanoTime() {
    return Time.nanoTime();
  }

  private static final int InitLocale = 0;

  private static final int PlatformEncoding = 1;

  private static final int FileEncoding = 2;

  private static final int OSEncoding = 3;

  /**
   * If systemProperties is unset, then create a new one based on the values
   * provided by the virtual machine.
   */
  private static void ensureProperties() {
    systemProperties = new Properties();

    String platformEncoding = null;
    String fileEncoding, osEncoding = null;
    String definedFileEncoding = getEncoding(FileEncoding);
    String definedOSEncoding = getEncoding(OSEncoding);
    if (definedFileEncoding != null) {
      fileEncoding = definedFileEncoding;
      // if file.encoding is defined, and os.encoding is not, use the
      // detected
      // platform encoding for os.encoding
      if (definedOSEncoding == null) {
        platformEncoding = getEncoding(PlatformEncoding);
        osEncoding = platformEncoding;
      } else {
        getEncoding(InitLocale);
      }
    } else {
      platformEncoding = getEncoding(PlatformEncoding);
      fileEncoding = platformEncoding;
    }
    // if os.encoding is not defined, file.encoding will be used
    if (osEncoding == null) {
      osEncoding = definedOSEncoding;
    }
    if (osEncoding != null) {
      systemProperties.put("os.encoding", osEncoding);
    }

    systemProperties.put("file.encoding", fileEncoding);

    systemProperties.put("java.version", "1.5 subset");
    systemProperties.put("java.specification.version", "1.5");

    systemProperties.put("java.specification.vendor", "Sun Microsystems Inc.");
    systemProperties.put("java.specification.name", "Java Platform API Specification");

    systemProperties.put("com.ibm.oti.configuration", "clear");
    systemProperties.put("com.ibm.oti.configuration.dir", "jclClear");

    String[] list = CommandLineArgs.getEnvironmentArgs();
    for (int i = 0; i < list.length; i ++) {
      if (list[i] == null) {
        continue;
      }
      int index = list[i].indexOf('=');
      String key = list[i].substring(0, index);
      String value = list[i].substring(index+1);
      if (key == null || key.length() == 0) {
        continue;
      }
      systemProperties.put(key, value);
    }

    String consoleEncoding = (String) systemProperties.get("console.encoding");
    if (consoleEncoding == null) {
      if (platformEncoding == null) {
        platformEncoding = getEncoding(PlatformEncoding);
      }
      consoleEncoding = platformEncoding;
      systemProperties.put("console.encoding", consoleEncoding);
    }
  }

  /**
   * Causes the virtual machine to stop running, and the program to exit. If
   * runFinalizersOnExit(true) has been invoked, then all finalizers will be
   * run first.
   * 
   * @param code the return code.
   * 
   * @throws SecurityException if the running thread is not allowed to cause
   *         the vm to exit.
   * 
   * @see SecurityManager#checkExit
   */
  public static void exit(int code) {
    RUNTIME.exit(code);
  }

  /**
   * Indicate to the virtual machine that it would be a good time to collect
   * available memory. Note that, this is a hint only.
   */
  public static void gc() {
    RUNTIME.gc();
  }

  /**
   * Returns an environment variable.
   * 
   * @param var the name of the environment variable
   * @return the value of the specified environment variable
   */
  public static String getenv(String var) {
    if (var == null) {
      throw new NullPointerException();
    }
    SecurityManager secMgr = System.getSecurityManager();
    if (secMgr != null) {
      secMgr.checkPermission(new RuntimePermission("getenv." + var));
    }
    return VMCommonLibrarySupport.getenv(var);
  }

  /**
   * <p>
   * Returns all environment variables.
   * </p>
   * 
   * @return A Map of all environment variables.
   */
  public static Map<String, String> getenv() {
    SecurityManager secMgr = System.getSecurityManager();
    if (secMgr != null) {
      secMgr.checkPermission(new RuntimePermission("getenv.*"));
    }
    throw new Error();
  }

  /**
   * <p>
   * Returns the inherited channel from the system-wide provider.
   * </p>
   * 
   * @return A {@link Channel} or <code>null</code>.
   * @throws IOException
   * @see SelectorProvider
   * @see SelectorProvider#inheritedChannel()
   */
  public static Channel inheritedChannel() throws IOException {
    return SelectorProvider.provider().inheritedChannel();
  }

  /**
   * Answers the system properties. Note that this is not a copy, so that
   * changes made to the returned Properties object will be reflected in
   * subsequent calls to getProperty and getProperties.
   * <p>
   * Security managers should restrict access to this API if possible.
   * 
   * @return the system properties
   */
  public static Properties getProperties() {
    SecurityManager secMgr = System.getSecurityManager();
    if (secMgr != null) {
      secMgr.checkPropertiesAccess();
    }
    return systemProperties;
  }

  /**
   * Answers the system properties without any security checks. This is used
   * for access from within java.lang.
   * 
   * @return the system properties
   */
  static Properties internalGetProperties() {
    return systemProperties;
  }

  /**
   * Answers the value of a particular system property. Answers null if no
   * such property exists,
   * <p>
   * The properties currently provided by the virtual machine are:
   * 
   * <pre>
   *        java.vendor.url
   *        java.class.path
   *        user.home
   *        java.class.version
   *        os.version
   *        java.vendor
   *        user.dir
   *        user.timezone
   *        path.separator
   *        os.name
   *        os.arch
   *        line.separator
   *        file.separator
   *        user.name
   *        java.version
   *        java.home
   * </pre>
   * 
   * @param prop the system property to look up
   * @return the value of the specified system property, or null if the
   *         property doesn't exist
   */
  public static String getProperty(String prop) {
    return getProperty(prop, null);
  }

  /**
   * Answers the value of a particular system property. If no such property is
   * found, answers the defaultValue.
   * 
   * @param prop the system property to look up
   * @param defaultValue return value if system property is not found
   * @return the value of the specified system property, or defaultValue if
   *         the property doesn't exist
   */
  public static String getProperty(String prop, String defaultValue) {
    if (prop.length() == 0) {
      throw new IllegalArgumentException();
    }
    SecurityManager secMgr = System.getSecurityManager();
    if (secMgr != null) {
      secMgr.checkPropertyAccess(prop);
    }
    return systemProperties.getProperty(prop, defaultValue);
  }

  /**
   * Sets the value of a particular system property.
   * 
   * @param prop the system property to change
   * @param value the value to associate with prop
   * @return the old value of the property, or null
   */
  public static String setProperty(String prop, String value) {
    if (prop.length() == 0) {
      throw new IllegalArgumentException();
    }
    SecurityManager secMgr = System.getSecurityManager();
    if (secMgr != null) {
      secMgr.checkPermission(new PropertyPermission(prop, "write"));
    }
    return (String) systemProperties.setProperty(prop, value);
  }

  /**
   * <p>
   * Removes the system property for the specified key.
   * </p>
   * 
   * <p>
   * Please see the Java SE API documentation for further
   * information on this method.
   * <p>
   * 
   * @param key the system property to be removed.
   * @return previous value or null if no value existed
   * 
   * @throws NullPointerException if the <code>key</code> argument is
   *         <code>null</code>.
   * @throws IllegalArgumentException if the <code>key</code> argument is
   *         empty.
   * @throws SecurityException if a security manager exists and write access
   *         to the specified property is not allowed.
   * @since 1.5
   */
  public static String clearProperty(String key) {
    if (key == null) {
      throw new NullPointerException();
    }
    if (key.length() == 0) {
      throw new IllegalArgumentException();
    }

    SecurityManager secMgr = System.getSecurityManager();
    if (secMgr != null) {
      secMgr.checkPermission(new PropertyPermission(key, "write"));
    }
    return (String) systemProperties.remove(key);
  }

  /**
   * Return the requested encoding. 0 - initialize locale 1 - detected
   * platform encoding 2 - command line defined file.encoding 3 - command line
   * defined os.encoding
   */
  private static String getEncoding(int type) {
    return "ISO-8859-1"; // TODO
  }

  /**
   * Answers the active security manager.
   * 
   * @return the system security manager object.
   */
  public static SecurityManager getSecurityManager() {
    return security;
  }

  /**
   * Answers an integer hash code for the parameter. The hash code returned is
   * the same one that would be returned by java.lang.Object.hashCode(),
   * whether or not the object's class has overridden hashCode(). The hash
   * code for null is 0.
   * 
   * @param anObject the object
   * @return the hash code for the object
   * 
   * @see java.lang.Object#hashCode
   */
  public static int identityHashCode(Object anObject) {
    return anObject == null ? 0 : ObjectModel.getObjectHashCode(anObject);
  }

  /**
   * Loads the specified file as a dynamic library.
   * 
   * @param pathName the path of the file to be loaded
   */
  public static void load(String pathName) {
    Runtime.getRuntime().load0(
        pathName,
        RVMClass.getClassLoaderFromStackFrame(1),
        true);
  }

  /**
   * Loads and links the library specified by the argument.
   * 
   * @param libName the name of the library to load
   * 
   * @throws UnsatisfiedLinkError if the library could not be loaded
   * @throws SecurityException if the library was not allowed to be loaded
   */
  public static void loadLibrary(String libName) {
    Runtime.getRuntime().loadLibrary0(
        libName,
        RVMClass.getClassLoaderFromStackFrame(1),
        true);
  }

  /**
   * Provides a hint to the virtual machine that it would be useful to attempt
   * to perform any outstanding object finalizations.
   */
  public static void runFinalization() {
    RUNTIME.runFinalization();
  }

  /**
   * Ensure that, when the virtual machine is about to exit, all objects are
   * finalized. Note that all finalization which occurs when the system is
   * exiting is performed after all running threads have been terminated.
   * 
   * @param flag
   *            true means finalize all on exit.
   * 
   * @deprecated This method is unsafe.
   */
  @SuppressWarnings("deprecation")
  @Deprecated
  public static void runFinalizersOnExit(boolean flag) {
    Runtime.runFinalizersOnExit(flag);
  }

  /**
   * Answers the system properties. Note that the object which is passed in
   * not copied, so that subsequent changes made to the object will be
   * reflected in calls to getProperty and getProperties.
   * <p>
   * Security managers should restrict access to this API if possible.
   * 
   * @param p
   *            the property to set
   */
  public static void setProperties(Properties p) {
    SecurityManager secMgr = System.getSecurityManager();
    if (secMgr != null) {
      secMgr.checkPropertiesAccess();
    }
    if (p == null) {
      ensureProperties();
    } else {
      systemProperties = p;
    }
  }

  /**
   * Sets the active security manager. Note that once the security manager has
   * been set, it can not be changed. Attempts to do so will cause a security
   * exception.
   * 
   * @param s
   *            the new security manager
   * 
   * @throws SecurityException
   *             if the security manager has already been set.
   */
  public static void setSecurityManager(final SecurityManager s) {
    if (!security_initialized) {
      try {
        // Preload and initialize Policy implementation classes
        // otherwise we could go recursive
        Policy.getPolicy();
      } catch (Exception e) {
      }
      security_initialized = true;
    }

    security = s;
  }

  /**
   * Answers the platform specific file name format for the shared library
   * named by the argument.
   * 
   * @param userLibName
   *            the name of the library to look up.
   * @return the platform specific filename for the library
   */
  public static String mapLibraryName(String userLibName) {
    return VMCommonLibrarySupport.mapLibraryName(userLibName);
  }
}
