/*
 * Copyright 2005-2006 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.
 */
package com.sun.tools.attach;

import com.sun.tools.attach.spi.AttachProvider;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;

/**
 * <p>
 * A Java virtual machine.
 * </p>
 * <p> A {@code VirtualMachine} represents a Java virtual machine to which this
 * Java virtual machine has attached. The Java virtual machine to which it is
 * attached is sometimes called the <i>target virtual machine</i>, or <i>target VM</i>.
 * An application (typically a tool such as a managemet console or profiler) uses a
 * VirtualMachine to load an agent into the target VM. For example, a profiler tool
 * written in the Java Language might attach to a running application and load its
 * profiler agent to profile the running application. </p>
 * <p> A VirtualMachine is obtained by invoking the {@link #attach(String) attach} method
 * with an identifier that identifies the target virtual machine. The identifier is
 * implementation-dependent but is typically the process identifier (or pid) in
 * environments where each Java virtual machine runs in its own operating system process.
 * Alternatively, a {@code VirtualMachine} instance is obtained by invoking the
 * {@link #attach(VirtualMachineDescriptor) attach} method with a {@link
 * com.sun.tools.attach.VirtualMachineDescriptor VirtualMachineDescriptor} obtained
 * from the list of virtual machine descriptors returned by the {@link #list list} method.
 * Once a reference to a virtual machine is obtained, the {@link #loadAgent loadAgent},
 * {@link #loadAgentLibrary loadAgentLibrary}, and {@link #loadAgentPath loadAgentPath}
 * methods are used to load agents into target virtual machine. The {@link
 * #loadAgent loadAgent} method is used to load agents that are written in the Java
 * Language and deployed in a {@link java.util.jar.JarFile JAR file}. (See
 * {@link java.lang.instrument} for a detailed description on how these agents
 * are loaded and started). The {@link #loadAgentLibrary loadAgentLibrary} and
 * {@link #loadAgentPath loadAgentPath} methods are used to load agents that
 * are deployed in a dynamic library and make use of the <a
 * href="../../../../../../../../technotes/guides/jvmti/index.html">JVM Tools
 * Interface</a>. </p>
 * <p> In addition to loading agents a VirtualMachine provides read access to the
 * {@link java.lang.System#getProperties() system properties} in the target VM.
 * This can be useful in some environments where properties such as
 * {@code java.home}, {@code os.name}, or {@code os.arch} are
 * used to construct the path to agent that will be loaded into the target VM.
 * <p/>
 * <p> The following example demonstrates how VirtualMachine may be used:</p>
 * <pre>
 *      // attach to target VM
 *      VirtualMachine vm = VirtualMachine.attach("2177");
 *      // get system properties in target VM
 *      Properties props = vm.getSystemProperties();
 *      // construct path to management agent
 *      String home = props.getProperty("java.home");
 *      String agent = home + File.separator + "lib" + File.separator
 *          + "management-agent.jar";
 *      // load agent into target VM
 *      vm.loadAgent(agent, "com.sun.management.jmxremote.port=5000");
 *      // detach
 *      vm.detach();
 * </pre>
 * <p> In this example we attach to a Java virtual machine that is identified by
 * the process identifier {@code 2177}. The system properties from the target
 * VM are then used to construct the path to a <i>management agent</i> which is then
 * loaded into the target VM. Once loaded the client detaches from the target VM. </p>
 * <p> A VirtualMachine is safe for use by multiple concurrent threads. </p>
 *
 * @since 1.6
 */
public abstract class VirtualMachine
{
   private final AttachProvider provider;
   private final String id;
   private volatile int hash;        // 0 => not computed

   /**
    * Initializes a new instance of this class.
    *
    * @param provider The attach provider creating this class.
    * @param id       The abstract identifier that identifies the Java virtual machine.
    */
   protected VirtualMachine(AttachProvider provider, String id)
   {
      this.provider = provider;
      this.id = id;
   }

   /**
    * <p>
    * Return a list of Java virtual machines.
    * </p>
    * <p>
    * This method returns a list of Java {@link com.sun.tools.attach.VirtualMachineDescriptor}
    * elements.  The list is an aggregation of the virtual machine descriptor lists obtained by
    * invoking the {@link com.sun.tools.attach.spi.AttachProvider#listVirtualMachines
    * listVirtualMachines} method of all installed {@link com.sun.tools.attach.spi.AttachProvider
    * attach providers}.  If there are no Java virtual machines known to any provider then an empty
    * list is returned.
    * </p>
    * @return The list of virtual machine descriptors.
    */
   public static List<VirtualMachineDescriptor> list()
   {
      List<VirtualMachineDescriptor> l = new ArrayList<VirtualMachineDescriptor>();
      List<AttachProvider> providers = AttachProvider.providers();

      for (AttachProvider provider : providers) {
         l.addAll(provider.listVirtualMachines());
      }

      return l;
   }

   /**
    * <p>
    * Attaches to a Java virtual machine.
    * </p>
    * <p>
    * This method obtains the list of attach providers by invoking the
    * {@link com.sun.tools.attach.spi.AttachProvider#providers() AttachProvider.providers()} method.
    * It then iterates overs the list and invokes each provider's {@link
    * com.sun.tools.attach.spi.AttachProvider#attachVirtualMachine(java.lang.String)
    * attachVirtualMachine} method in turn. If a provider successfully
    * attaches then the iteration terminates, and the VirtualMachine created
    * by the provider that successfully attached is returned by this method.
    * If the {@code attachVirtualMachine} method of all providers throws
    * {@link com.sun.tools.attach.AttachNotSupportedException AttachNotSupportedException}
    * then this method also throws {@code AttachNotSupportedException}.
    * This means that {@code AttachNotSupportedException} is thrown when
    * the identifier provided to this method is invalid, or the identifier
    * corresponds to a Java virtual machine that does not exist, or none
    * of the providers can attach to it. This exception is also thrown if
    * {@link com.sun.tools.attach.spi.AttachProvider#providers()
    * AttachProvider.providers()} returns an empty list.
    * </p>
    *
    * @param id The abstract identifier that identifies the Java virtual machine.
    * @return A VirtualMachine representing the target VM.
    * @throws SecurityException           If a security manager has been installed and it denies
    *                                     {@link com.sun.tools.attach.AttachPermission AttachPermission}
    *                                     <tt>("attachVirtualMachine")</tt>, or another permission
    *                                     required by the implementation.
    * @throws IOException                 If an I/O error occurs
    */
   public static VirtualMachine attach(String id) throws AttachNotSupportedException, IOException
   {
      List<AttachProvider> providers = AttachProvider.providers();
      AttachNotSupportedException lastExc = null;

      for (AttachProvider provider : providers) {
         try {
            return provider.attachVirtualMachine(id);
         }
         catch (AttachNotSupportedException x) {
            lastExc = x;
         }
      }

      throw lastExc;
   }

   /**
    * Attaches to a Java virtual machine.
    * <p/>
    * This method first invokes the {@link com.sun.tools.attach.VirtualMachineDescriptor#provider()
    * provider()} method of the given virtual machine descriptor to obtain the attach provider.
    * It then invokes the attach provider's {@link
    * com.sun.tools.attach.spi.AttachProvider#attachVirtualMachine(VirtualMachineDescriptor)
    * attachVirtualMachine} to attach to the target VM.
    *
    * @param   vmd The virtual machine descriptor.
    *
    * @return A VirtualMachine representing the target VM.
    *
    * @throws SecurityException
    *          If a security manager has been installed and it denies
    *          {@link com.sun.tools.attach.AttachPermission AttachPermission}
    *          <tt>("attachVirtualMachine")</tt>, or another permission
    *          required by the implementation.
    *
    * @throws AttachNotSupportedException
    *          If the attach provider's {@code attachVirtualmachine}
    *          throws {@code AttachNotSupportedException}.
    *
    * @throws IOException If an I/O error occurs
    *
    * @throws NullPointerException If {@code vmd} is {@code null}.
    */
   public static VirtualMachine attach(VirtualMachineDescriptor vmd)
      throws AttachNotSupportedException, IOException
   {
      return vmd.provider().attachVirtualMachine(vmd);
   }

   /**
    * Detach from the virtual machine.
    * <p/>
    * After detaching from the virtual machine, any further attempt to invoke
    * operations on that virtual machine will cause an {@link java.io.IOException
    * IOException} to be thrown. If an operation (such as {@link #loadAgent
    * loadAgent} for example) is in progress when this method is invoked then
    * the behaviour is implementation dependent. In other words, it is
    * implementation specific if the operation completes or throws <tt>IOException</tt>.
    * <p/>
    * If already detached from the virtual machine then invoking this method has no effect.
    *
    * @throws IOException If an I/O error occurs
    */
   public abstract void detach() throws IOException;

   /**
    * Returns the provider that created this virtual machine.
    */
   public final AttachProvider provider()
   {
      return provider;
   }

   /**
    * Returns the identifier for this Java virtual machine.
    */
   public final String id()
   {
      return id;
   }

   /**
    * Loads an agent library.
    * <p/>
    * A <a href="../../../../../../../../technotes/guides/jvmti/index.html">JVM
    * TI</a> client is called an <i>agent</i>. It is developed in a native language.
    * A JVM TI agent is deployed in a platform specific manner but it is typically the
    * platform equivalent of a dynamic library. This method causes the given agent
    * library to be loaded into the target VM (if not already loaded).
    * It then causes the target VM to invoke the {@code Agent_OnAttach} function
    * as specified in the
    * <a href="../../../../../../../../technotes/guides/jvmti/index.html"> JVM Tools
    * Interface</a> specification. Note that the {@code Agent_OnAttach}
    * function is invoked even if the agent library was loaded prior to invoking
    * this method.
    * <p/>
    * The agent library provided is the name of the agent library. It is interpreted
    * in the target virtual machine in an implementation-dependent manner. Typically an
    * implementation will expand the library name into an operating system specific file
    * name. For example, on UNIX systems, the name <tt>foo</tt> might be expanded to
    * <tt>libfoo.so</tt>, and located using the search path specified by the
    * <tt>LD_LIBRARY_PATH</tt> environment variable.
    * <p/>
    * If the {@code Agent_OnAttach} function in the agent library returns
    * an error then an {@link com.sun.tools.attach.AgentInitializationException} is
    * thrown. The return value from the {@code Agent_OnAttach} can then be
    * obtained by invoking the {@link
    * com.sun.tools.attach.AgentInitializationException#returnValue() returnValue}
    * method on the exception.
    *
    * @param agentLibrary The name of the agent library.
    * @param options      The options to provide to the {@code Agent_OnAttach}
    *                     function (can be {@code null}).
    * @throws AgentLoadException           If the agent library does not exist, or cannot be loaded for
    *                                      another reason.
    * @throws AgentInitializationException If the {@code Agent_OnAttach} function returns an error
    * @throws IOException                  If an I/O error occurs
    * @throws NullPointerException         If {@code agentLibrary} is {@code null}.
    * @see com.sun.tools.attach.AgentInitializationException#returnValue()
    */
   public abstract void loadAgentLibrary(String agentLibrary, String options)
      throws AgentLoadException, AgentInitializationException, IOException;

   /**
    * Loads an agent library.
    * <p/>
    * This convenience method works as if by invoking:
    * <p/>
    * <blockquote><tt>
    * {@link #loadAgentLibrary(String, String) loadAgentLibrary}(agentLibrary,&nbsp;null);
    * </tt></blockquote>
    *
    * @param agentLibrary The name of the agent library.
    * @throws AgentLoadException           If the agent library does not exist, or cannot be loaded for
    *                                      another reason.
    * @throws AgentInitializationException If the {@code Agent_OnAttach} function returns an error
    * @throws IOException                  If an I/O error occurs
    * @throws NullPointerException         If {@code agentLibrary} is {@code null}.
    */
   public void loadAgentLibrary(String agentLibrary)
      throws AgentLoadException, AgentInitializationException, IOException
   {
      loadAgentLibrary(agentLibrary, null);
   }

   /**
    * Load a native agent library by full pathname.
    * <p/>
    * A <a href="../../../../../../../../technotes/guides/jvmti/index.html">JVM TI</a> client is
    * called an <i>agent</i>. It is developed in a native language.
    * A JVM TI agent is deployed in a platform specific manner but it is typically the
    * platform equivalent of a dynamic library. This method causes the given agent
    * library to be loaded into the target VM (if not already loaded).
    * It then causes the target VM to invoke the {@code Agent_OnAttach} function
    * as specified in the
    * <a href="../../../../../../../../technotes/guides/jvmti/index.html"> JVM Tools
    * Interface</a> specification. Note that the {@code Agent_OnAttach}
    * function is invoked even if the agent library was loaded prior to invoking
    * this method.
    * <p/>
    * The agent library provided is the absolute path from which to load the
    * agent library. Unlike {@link #loadAgentLibrary loadAgentLibrary}, the library name
    * is not expanded in the target virtual machine.
    * <p/>
    * If the {@code Agent_OnAttach} function in the agent library returns
    * an error then an {@link com.sun.tools.attach.AgentInitializationException} is
    * thrown. The return value from the {@code Agent_OnAttach} can then be
    * obtained by invoking the {@link
    * com.sun.tools.attach.AgentInitializationException#returnValue() returnValue}
    * method on the exception.
    *
    * @param agentPath The full path of the agent library.
    * @param options   The options to provide to the {@code Agent_OnAttach}
    *                  function (can be {@code null}).
    * @throws AgentLoadException           If the agent library does not exist, or cannot be loaded for
    *                                      another reason.
    * @throws AgentInitializationException If the {@code Agent_OnAttach} function returns an error
    * @throws IOException                  If an I/O error occurs
    * @throws NullPointerException         If {@code agentPath} is {@code null}.
    * @see com.sun.tools.attach.AgentInitializationException#returnValue()
    */
   public abstract void loadAgentPath(String agentPath, String options)
      throws AgentLoadException, AgentInitializationException, IOException;

   /**
    * Load a native agent library by full pathname.
    * <p/>
    * This convenience method works as if by invoking:
    * <p/>
    * <blockquote><tt>
    * {@link #loadAgentPath(String, String) loadAgentPath}(agentLibrary,&nbsp;null);
    * </tt></blockquote>
    *
    * @param agentPath The full path to the agent library.
    * @throws AgentLoadException           If the agent library does not exist, or cannot be loaded for
    *                                      another reason.
    * @throws AgentInitializationException If the {@code Agent_OnAttach} function returns an error
    * @throws IOException                  If an I/O error occurs
    * @throws NullPointerException         If {@code agentPath} is {@code null}.
    */
   public void loadAgentPath(String agentPath)
      throws AgentLoadException, AgentInitializationException, IOException
   {
      loadAgentPath(agentPath, null);
   }


   /**
    * Loads an agent.
    * <p/>
    * <p> The agent provided to this method is a path name to a JAR file on the file
    * system of the target virtual machine. This path is passed to the target virtual
    * machine where it is interpreted. The target virtual machine attempts to start
    * the agent as specified by the {@link java.lang.instrument} specification.
    * That is, the specified JAR file is added to the system class path (of the target
    * virtual machine), and the {@code agentmain} method of the agent class, specified
    * by the {@code Agent-Class} attribute in the JAR manifest, is invoked. This
    * method completes when the {@code agentmain} method completes.
    *
    * @param agent   Path to the JAR file containing the agent.
    * @param options The options to provide to the agent's {@code agentmain}
    *                method (can be {@code null}).
    * @throws AgentLoadException           If the agent does not exist, or cannot be started in the manner
    *                                      specified in the {@link java.lang.instrument} specification.
    * @throws AgentInitializationException If the {@code agentmain} throws an exception
    * @throws IOException                  If an I/O error occurs
    * @throws NullPointerException         If {@code agent} is {@code null}.
    */
   public abstract void loadAgent(String agent, String options)
      throws AgentLoadException, AgentInitializationException, IOException;

   /**
    * Loads an agent.
    * <p/>
    * This convenience method works as if by invoking:
    * <p/>
    * <blockquote><tt>
    * {@link #loadAgent(String, String) loadAgent}(agent,&nbsp;null);
    * </tt></blockquote>
    *
    * @param agent Path to the JAR file containing the agent.
    * @throws AgentLoadException           If the agent does not exist, or cannot be started in the manner
    *                                      specified in the {@link java.lang.instrument} specification.
    * @throws AgentInitializationException If the {@code agentmain} throws an exception
    * @throws IOException                  If an I/O error occurs
    * @throws NullPointerException         If {@code agent} is {@code null}.
    */
   public void loadAgent(String agent)
      throws AgentLoadException, AgentInitializationException, IOException
   {
      loadAgent(agent, null);
   }

   /**
    * Returns the current system properties in the target virtual machine.
    * <p/>
    * This method returns the system properties in the target virtual
    * machine. Properties whose key or value is not a <tt>String</tt> are
    * omitted. The method is approximately equivalent to the invocation of the
    * method {@link java.lang.System#getProperties System.getProperties}
    * in the target virtual machine except that properties with a key or
    * value that is not a <tt>String</tt> are not included.
    * <p/>
    * This method is typically used to decide which agent to load into
    * the target virtual machine with {@link #loadAgent loadAgent}, or
    * {@link #loadAgentLibrary loadAgentLibrary}. For example, the
    * {@code java.home} or {@code user.dir} properties might be
    * use to create the path to the agent library or JAR file.
    *
    * @return The system properties
    * @throws IOException If an I/O error occurs
    * @see java.lang.System#getProperties
    * @see #loadAgentLibrary
    * @see #loadAgent
    */
   public abstract Properties getSystemProperties() throws IOException;

   /**
    * Returns the current <i>agent properties</i> in the target virtual
    * machine.
    * <p/>
    * The target virtual machine can maintain a list of properties on
    * behalf of agents. The manner in which this is done, the names of the
    * properties, and the types of values that are allowed, is implementation
    * specific. Agent properties are typically used to store communication
    * end-points and other agent configuration details. For example, a debugger
    * agent might create an agent property for its transport address.
    * <p/>
    * This method returns the agent properties whose key and value is a
    * <tt>String</tt>. Properties whose key or value is not a <tt>String</tt>
    * are omitted. If there are no agent properties maintained in the target
    * virtual machine then an empty property list is returned.
    *
    * @return The agent properties
    * @throws IOException If an I/O error occurs
    */
   public abstract Properties getAgentProperties() throws IOException;

   /**
    * Returns a hash-code value for this VirtualMachine. The hash
    * code is based upon the VirtualMachine's components, and satisfies
    * the general contract of the Object.hashCode method.
    *
    * @return A hash-code value for this virtual machine
    */
   @Override
   public int hashCode()
   {
      if (hash != 0) {
         return hash;
      }

      hash = provider.hashCode() * 127 + id.hashCode();
      return hash;
   }

   /**
    * Tests this VirtualMachine for equality with another object.
    * <p/>
    * <p> If the given object is not a VirtualMachine then this
    * method returns <tt>false</tt>. For two VirtualMachines to
    * be considered equal requires that they both reference the same
    * provider, and their {@link VirtualMachineDescriptor#id() identifiers} are equal. </p>
    * <p/>
    * <p> This method satisfies the general contract of the  method. </p>
    *
    * @param ob The object to which this object is to be compared
    * @return <tt>true</tt> if, and only if, the given object is
    *         a VirtualMachine that is equal to this
    *         VirtualMachine.
    */
   @Override
   public boolean equals(Object ob)
   {
      if (ob == this) {
         return true;
      }

      if (!(ob instanceof VirtualMachine)) {
         return false;
      }

      VirtualMachine other = (VirtualMachine) ob;

      return other.provider() == provider() && other.id().equals(id());
   }

   /**
    * Returns the string representation of the {@code VirtualMachine}.
    */
   @Override
   public String toString()
   {
      return provider.toString() + ": " + id;
   }
}
