1 /*
   2  * Copyright (c) 2005, 2006, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package com.sun.tools.attach;
  27 
  28 import com.sun.tools.attach.spi.AttachProvider;
  29 import java.util.ArrayList;
  30 import java.util.List;
  31 import java.util.Properties;
  32 import java.io.IOException;
  33 
  34 
  35 /**
  36  * A Java virtual machine.
  37  *
  38  * <p> A <code>VirtualMachine</code> represents a Java virtual machine to which this
  39  * Java virtual machine has attached. The Java virtual machine to which it is
  40  * attached is sometimes called the <i>target virtual machine</i>, or <i>target VM</i>.
  41  * An application (typically a tool such as a managemet console or profiler) uses a
  42  * VirtualMachine to load an agent into the target VM. For example, a profiler tool
  43  * written in the Java Language might attach to a running application and load its
  44  * profiler agent to profile the running application. </p>
  45  *
  46  * <p> A VirtualMachine is obtained by invoking the {@link #attach(String) attach} method
  47  * with an identifier that identifies the target virtual machine. The identifier is
  48  * implementation-dependent but is typically the process identifier (or pid) in
  49  * environments where each Java virtual machine runs in its own operating system process.
  50  * Alternatively, a <code>VirtualMachine</code> instance is obtained by invoking the
  51  * {@link #attach(VirtualMachineDescriptor) attach} method with a {@link
  52  * com.sun.tools.attach.VirtualMachineDescriptor VirtualMachineDescriptor} obtained
  53  * from the list of virtual machine descriptors returned by the {@link #list list} method.
  54  * Once a reference to a virtual machine is obtained, the {@link #loadAgent loadAgent},
  55  * {@link #loadAgentLibrary loadAgentLibrary}, and {@link #loadAgentPath loadAgentPath}
  56  * methods are used to load agents into target virtual machine. The {@link
  57  * #loadAgent loadAgent} method is used to load agents that are written in the Java
  58  * Language and deployed in a {@link java.util.jar.JarFile JAR file}. (See
  59  * {@link java.lang.instrument} for a detailed description on how these agents
  60  * are loaded and started). The {@link #loadAgentLibrary loadAgentLibrary} and
  61  * {@link #loadAgentPath loadAgentPath} methods are used to load agents that
  62  * are deployed in a dynamic library and make use of the <a
  63  * href="../../../../../../../../technotes/guides/jvmti/index.html">JVM Tools
  64  * Interface</a>. </p>
  65  *
  66  * <p> In addition to loading agents a VirtualMachine provides read access to the
  67  * {@link java.lang.System#getProperties() system properties} in the target VM.
  68  * This can be useful in some environments where properties such as
  69  * <code>java.home</code>, <code>os.name</code>, or <code>os.arch</code> are
  70  * used to construct the path to agent that will be loaded into the target VM.
  71  *
  72  * <p> The following example demonstrates how VirtualMachine may be used:</p>
  73  *
  74  * <pre>
  75  *
  76  *      // attach to target VM
  77  *      VirtualMachine vm = VirtualMachine.attach("2177");
  78  *
  79  *      // get system properties in target VM
  80  *      Properties props = vm.getSystemProperties();
  81  *
  82  *      // construct path to management agent
  83  *      String home = props.getProperty("java.home");
  84  *      String agent = home + File.separator + "lib" + File.separator
  85  *          + "management-agent.jar";
  86  *
  87  *      // load agent into target VM
  88  *      vm.loadAgent(agent, "com.sun.management.jmxremote.port=5000");
  89  *
  90  *      // detach
  91  *      vm.detach();
  92  *
  93  * </pre>
  94  *
  95  * <p> In this example we attach to a Java virtual machine that is identified by
  96  * the process identifier <code>2177</code>. The system properties from the target
  97  * VM are then used to construct the path to a <i>management agent</i> which is then
  98  * loaded into the target VM. Once loaded the client detaches from the target VM. </p>
  99  *
 100  * <p> A VirtualMachine is safe for use by multiple concurrent threads. </p>
 101  *
 102  * @since 1.6
 103  */
 104 
 105 @jdk.Supported
 106 public abstract class VirtualMachine {
 107     private AttachProvider provider;
 108     private String id;
 109     private volatile int hash;        // 0 => not computed
 110 
 111     /**
 112      * Initializes a new instance of this class.
 113      *
 114      * @param   provider
 115      *          The attach provider creating this class.
 116      * @param   id
 117      *          The abstract identifier that identifies the Java virtual machine.
 118      *
 119      * @throws  NullPointerException
 120      *          If <code>provider</code> or <code>id</code> is <code>null</code>.
 121      */
 122     protected VirtualMachine(AttachProvider provider, String id) {
 123         if (provider == null) {
 124             throw new NullPointerException("provider cannot be null");
 125         }
 126         if (id == null) {
 127             throw new NullPointerException("id cannot be null");
 128         }
 129         this.provider = provider;
 130         this.id = id;
 131     }
 132 
 133     /**
 134      * Return a list of Java virtual machines.
 135      *
 136      * <p> This method returns a list of Java {@link
 137      * com.sun.tools.attach.VirtualMachineDescriptor} elements.
 138      * The list is an aggregation of the virtual machine
 139      * descriptor lists obtained by invoking the {@link
 140      * com.sun.tools.attach.spi.AttachProvider#listVirtualMachines
 141      * listVirtualMachines} method of all installed
 142      * {@link com.sun.tools.attach.spi.AttachProvider attach providers}.
 143      * If there are no Java virtual machines known to any provider
 144      * then an empty list is returned.
 145      *
 146      * @return  The list of virtual machine descriptors.
 147      */
 148     public static List<VirtualMachineDescriptor> list() {
 149         ArrayList<VirtualMachineDescriptor> l =
 150             new ArrayList<VirtualMachineDescriptor>();
 151         List<AttachProvider> providers = AttachProvider.providers();
 152         for (AttachProvider provider: providers) {
 153             l.addAll(provider.listVirtualMachines());
 154         }
 155         return l;
 156     }
 157 
 158     /**
 159      * Attaches to a Java virtual machine.
 160      *
 161      * <p> This method obtains the list of attach providers by invoking the
 162      * {@link com.sun.tools.attach.spi.AttachProvider#providers()
 163      * AttachProvider.providers()} method. It then iterates overs the list
 164      * and invokes each provider's {@link
 165      * com.sun.tools.attach.spi.AttachProvider#attachVirtualMachine(java.lang.String)
 166      * attachVirtualMachine} method in turn. If a provider successfully
 167      * attaches then the iteration terminates, and the VirtualMachine created
 168      * by the provider that successfully attached is returned by this method.
 169      * If the <code>attachVirtualMachine</code> method of all providers throws
 170      * {@link com.sun.tools.attach.AttachNotSupportedException AttachNotSupportedException}
 171      * then this method also throws <code>AttachNotSupportedException</code>.
 172      * This means that <code>AttachNotSupportedException</code> is thrown when
 173      * the identifier provided to this method is invalid, or the identifier
 174      * corresponds to a Java virtual machine that does not exist, or none
 175      * of the providers can attach to it. This exception is also thrown if
 176      * {@link com.sun.tools.attach.spi.AttachProvider#providers()
 177      * AttachProvider.providers()} returns an empty list. </p>
 178      *
 179      * @param   id
 180      *          The abstract identifier that identifies the Java virtual machine.
 181      *
 182      * @return  A VirtualMachine representing the target VM.
 183      *
 184      * @throws  SecurityException
 185      *          If a security manager has been installed and it denies
 186      *          {@link com.sun.tools.attach.AttachPermission AttachPermission}
 187      *          <tt>("attachVirtualMachine")</tt>, or another permission
 188      *          required by the implementation.
 189      *
 190      * @throws  AttachNotSupportedException
 191      *          If the <code>attachVirtualmachine</code> method of all installed
 192      *          providers throws <code>AttachNotSupportedException</code>, or
 193      *          there aren't any providers installed.
 194      *
 195      * @throws  IOException
 196      *          If an I/O error occurs
 197      *
 198      * @throws  NullPointerException
 199      *          If <code>id</code> is <code>null</code>.
 200      */
 201     public static VirtualMachine attach(String id)
 202         throws AttachNotSupportedException, IOException
 203     {
 204         if (id == null) {
 205             throw new NullPointerException("id cannot be null");
 206         }
 207         List<AttachProvider> providers = AttachProvider.providers();
 208         if (providers.size() == 0) {
 209             throw new AttachNotSupportedException("no providers installed");
 210         }
 211         AttachNotSupportedException lastExc = null;
 212         for (AttachProvider provider: providers) {
 213             try {
 214                 return provider.attachVirtualMachine(id);
 215             } catch (AttachNotSupportedException x) {
 216                 lastExc = x;
 217             }
 218         }
 219         throw lastExc;
 220     }
 221 
 222     /**
 223      * Attaches to a Java virtual machine.
 224      *
 225      * <p> This method first invokes the {@link
 226      * com.sun.tools.attach.VirtualMachineDescriptor#provider() provider()} method
 227      * of the given virtual machine descriptor to obtain the attach provider. It
 228      * then invokes the attach provider's {@link
 229      * com.sun.tools.attach.spi.AttachProvider#attachVirtualMachine(VirtualMachineDescriptor)
 230      * attachVirtualMachine} to attach to the target VM.
 231      *
 232      * @param   vmd
 233      *          The virtual machine descriptor.
 234      *
 235      * @return  A VirtualMachine representing the target VM.
 236      *
 237      * @throws  SecurityException
 238      *          If a security manager has been installed and it denies
 239      *          {@link com.sun.tools.attach.AttachPermission AttachPermission}
 240      *          <tt>("attachVirtualMachine")</tt>, or another permission
 241      *          required by the implementation.
 242      *
 243      * @throws  AttachNotSupportedException
 244      *          If the attach provider's <code>attachVirtualmachine</code>
 245      *          throws <code>AttachNotSupportedException</code>.
 246      *
 247      * @throws  IOException
 248      *          If an I/O error occurs
 249      *
 250      * @throws  NullPointerException
 251      *          If <code>vmd</code> is <code>null</code>.
 252      */
 253     public static VirtualMachine attach(VirtualMachineDescriptor vmd)
 254         throws AttachNotSupportedException, IOException
 255     {
 256         return vmd.provider().attachVirtualMachine(vmd);
 257     }
 258 
 259     /**
 260      * Detach from the virtual machine.
 261      *
 262      * <p> After detaching from the virtual machine, any further attempt to invoke
 263      * operations on that virtual machine will cause an {@link java.io.IOException
 264      * IOException} to be thrown. If an operation (such as {@link #loadAgent
 265      * loadAgent} for example) is in progress when this method is invoked then
 266      * the behaviour is implementation dependent. In other words, it is
 267      * implementation specific if the operation completes or throws
 268      * <tt>IOException</tt>.
 269      *
 270      * <p> If already detached from the virtual machine then invoking this
 271      * method has no effect. </p>
 272      *
 273      * @throws  IOException
 274      *          If an I/O error occurs
 275      */
 276     public abstract void detach() throws IOException;
 277 
 278     /**
 279      * Returns the provider that created this virtual machine.
 280      *
 281      * @return  The provider that created this virtual machine.
 282      */
 283     public final AttachProvider provider() {
 284         return provider;
 285     }
 286 
 287     /**
 288      * Returns the identifier for this Java virtual machine.
 289      *
 290      * @return  The identifier for this Java virtual machine.
 291      */
 292     public final String id() {
 293         return id;
 294     }
 295 
 296     /**
 297      * Loads an agent library.
 298      *
 299      * <p> A <a href="../../../../../../../../technotes/guides/jvmti/index.html">JVM
 300      * TI</a> client is called an <i>agent</i>. It is developed in a native language.
 301      * A JVM TI agent is deployed in a platform specific manner but it is typically the
 302      * platform equivalent of a dynamic library. This method causes the given agent
 303      * library to be loaded into the target VM (if not already loaded).
 304      * It then causes the target VM to invoke the <code>Agent_OnAttach</code> function
 305      * as specified in the
 306      * <a href="../../../../../../../../technotes/guides/jvmti/index.html"> JVM Tools
 307      * Interface</a> specification. Note that the <code>Agent_OnAttach</code>
 308      * function is invoked even if the agent library was loaded prior to invoking
 309      * this method.
 310      *
 311      * <p> The agent library provided is the name of the agent library. It is interpreted
 312      * in the target virtual machine in an implementation-dependent manner. Typically an
 313      * implementation will expand the library name into an operating system specific file
 314      * name. For example, on UNIX systems, the name <tt>foo</tt> might be expanded to
 315      * <tt>libfoo.so</tt>, and located using the search path specified by the
 316      * <tt>LD_LIBRARY_PATH</tt> environment variable.</p>
 317      *
 318      * <p> If the <code>Agent_OnAttach</code> function in the agent library returns
 319      * an error then an {@link com.sun.tools.attach.AgentInitializationException} is
 320      * thrown. The return value from the <code>Agent_OnAttach</code> can then be
 321      * obtained by invoking the {@link
 322      * com.sun.tools.attach.AgentInitializationException#returnValue() returnValue}
 323      * method on the exception. </p>
 324      *
 325      * @param   agentLibrary
 326      *          The name of the agent library.
 327      *
 328      * @param   options
 329      *          The options to provide to the <code>Agent_OnAttach</code>
 330      *          function (can be <code>null</code>).
 331      *
 332      * @throws  AgentLoadException
 333      *          If the agent library does not exist, or cannot be loaded for
 334      *          another reason.
 335      *
 336      * @throws  AgentInitializationException
 337      *          If the <code>Agent_OnAttach</code> function returns an error
 338      *
 339      * @throws  IOException
 340      *          If an I/O error occurs
 341      *
 342      * @throws  NullPointerException
 343      *          If <code>agentLibrary</code> is <code>null</code>.
 344      *
 345      * @see     com.sun.tools.attach.AgentInitializationException#returnValue()
 346      */
 347     public abstract void loadAgentLibrary(String agentLibrary, String options)
 348         throws AgentLoadException, AgentInitializationException, IOException;
 349 
 350     /**
 351      * Loads an agent library.
 352      *
 353      * <p> This convenience method works as if by invoking:
 354      *
 355      * <blockquote><tt>
 356      * {@link #loadAgentLibrary(String, String) loadAgentLibrary}(agentLibrary,&nbsp;null);
 357      * </tt></blockquote>
 358      *
 359      * @param   agentLibrary
 360      *          The name of the agent library.
 361      *
 362      * @throws  AgentLoadException
 363      *          If the agent library does not exist, or cannot be loaded for
 364      *          another reason.
 365      *
 366      * @throws  AgentInitializationException
 367      *          If the <code>Agent_OnAttach</code> function returns an error
 368      *
 369      * @throws  IOException
 370      *          If an I/O error occurs
 371      *
 372      * @throws  NullPointerException
 373      *          If <code>agentLibrary</code> is <code>null</code>.
 374      */
 375     public void loadAgentLibrary(String agentLibrary)
 376         throws AgentLoadException, AgentInitializationException, IOException
 377     {
 378         loadAgentLibrary(agentLibrary, null);
 379     }
 380 
 381     /**
 382      * Load a native agent library by full pathname.
 383      *
 384      * <p> A <a href="../../../../../../../../technotes/guides/jvmti/index.html">JVM
 385      * TI</a> client is called an <i>agent</i>. It is developed in a native language.
 386      * A JVM TI agent is deployed in a platform specific manner but it is typically the
 387      * platform equivalent of a dynamic library. This method causes the given agent
 388      * library to be loaded into the target VM (if not already loaded).
 389      * It then causes the target VM to invoke the <code>Agent_OnAttach</code> function
 390      * as specified in the
 391      * <a href="../../../../../../../../technotes/guides/jvmti/index.html"> JVM Tools
 392      * Interface</a> specification. Note that the <code>Agent_OnAttach</code>
 393      * function is invoked even if the agent library was loaded prior to invoking
 394      * this method.
 395      *
 396      * <p> The agent library provided is the absolute path from which to load the
 397      * agent library. Unlike {@link #loadAgentLibrary loadAgentLibrary}, the library name
 398      * is not expanded in the target virtual machine. </p>
 399      *
 400      * <p> If the <code>Agent_OnAttach</code> function in the agent library returns
 401      * an error then an {@link com.sun.tools.attach.AgentInitializationException} is
 402      * thrown. The return value from the <code>Agent_OnAttach</code> can then be
 403      * obtained by invoking the {@link
 404      * com.sun.tools.attach.AgentInitializationException#returnValue() returnValue}
 405      * method on the exception. </p>
 406      *
 407      * @param   agentPath
 408      *          The full path of the agent library.
 409      *
 410      * @param   options
 411      *          The options to provide to the <code>Agent_OnAttach</code>
 412      *          function (can be <code>null</code>).
 413      *
 414      * @throws  AgentLoadException
 415      *          If the agent library does not exist, or cannot be loaded for
 416      *          another reason.
 417      *
 418      * @throws  AgentInitializationException
 419      *          If the <code>Agent_OnAttach</code> function returns an error
 420      *
 421      * @throws  IOException
 422      *          If an I/O error occurs
 423      *
 424      * @throws  NullPointerException
 425      *          If <code>agentPath</code> is <code>null</code>.
 426      *
 427      * @see     com.sun.tools.attach.AgentInitializationException#returnValue()
 428      */
 429     public abstract void loadAgentPath(String agentPath, String options)
 430         throws AgentLoadException, AgentInitializationException, IOException;
 431 
 432     /**
 433      * Load a native agent library by full pathname.
 434      *
 435      * <p> This convenience method works as if by invoking:
 436      *
 437      * <blockquote><tt>
 438      * {@link #loadAgentPath(String, String) loadAgentPath}(agentLibrary,&nbsp;null);
 439      * </tt></blockquote>
 440      *
 441      * @param   agentPath
 442      *          The full path to the agent library.
 443      *
 444      * @throws  AgentLoadException
 445      *          If the agent library does not exist, or cannot be loaded for
 446      *          another reason.
 447      *
 448      * @throws  AgentInitializationException
 449      *          If the <code>Agent_OnAttach</code> function returns an error
 450      *
 451      * @throws  IOException
 452      *          If an I/O error occurs
 453      *
 454      * @throws  NullPointerException
 455      *          If <code>agentPath</code> is <code>null</code>.
 456      */
 457     public void loadAgentPath(String agentPath)
 458        throws AgentLoadException, AgentInitializationException, IOException
 459     {
 460         loadAgentPath(agentPath, null);
 461     }
 462 
 463 
 464    /**
 465      * Loads an agent.
 466      *
 467      * <p> The agent provided to this method is a path name to a JAR file on the file
 468      * system of the target virtual machine. This path is passed to the target virtual
 469      * machine where it is interpreted. The target virtual machine attempts to start
 470      * the agent as specified by the {@link java.lang.instrument} specification.
 471      * That is, the specified JAR file is added to the system class path (of the target
 472      * virtual machine), and the <code>agentmain</code> method of the agent class, specified
 473      * by the <code>Agent-Class</code> attribute in the JAR manifest, is invoked. This
 474      * method completes when the <code>agentmain</code> method completes.
 475      *
 476      * @param   agent
 477      *          Path to the JAR file containing the agent.
 478      *
 479      * @param   options
 480      *          The options to provide to the agent's <code>agentmain</code>
 481      *          method (can be <code>null</code>).
 482      *
 483      * @throws  AgentLoadException
 484      *          If the agent does not exist, or cannot be started in the manner
 485      *          specified in the {@link java.lang.instrument} specification.
 486      *
 487      * @throws  AgentInitializationException
 488      *          If the <code>agentmain</code> throws an exception
 489      *
 490      * @throws  IOException
 491      *          If an I/O error occurs
 492      *
 493      * @throws  NullPointerException
 494      *          If <code>agent</code> is <code>null</code>.
 495      */
 496     public abstract void loadAgent(String agent, String options)
 497         throws AgentLoadException, AgentInitializationException, IOException;
 498 
 499     /**
 500      * Loads an agent.
 501      *
 502      * <p> This convenience method works as if by invoking:
 503      *
 504      * <blockquote><tt>
 505      * {@link #loadAgent(String, String) loadAgent}(agent,&nbsp;null);
 506      * </tt></blockquote>
 507      *
 508      * @param   agent
 509      *          Path to the JAR file containing the agent.
 510      *
 511      * @throws  AgentLoadException
 512      *          If the agent does not exist, or cannot be started in the manner
 513      *          specified in the {@link java.lang.instrument} specification.
 514      *
 515      * @throws  AgentInitializationException
 516      *          If the <code>agentmain</code> throws an exception
 517      *
 518      * @throws  IOException
 519      *          If an I/O error occurs
 520      *
 521      * @throws  NullPointerException
 522      *          If <code>agent</code> is <code>null</code>.
 523      */
 524     public void loadAgent(String agent)
 525         throws AgentLoadException, AgentInitializationException, IOException
 526     {
 527         loadAgent(agent, null);
 528     }
 529 
 530     /**
 531      * Returns the current system properties in the target virtual machine.
 532      *
 533      * <p> This method returns the system properties in the target virtual
 534      * machine. Properties whose key or value is not a <tt>String</tt> are
 535      * omitted. The method is approximately equivalent to the invocation of the
 536      * method {@link java.lang.System#getProperties System.getProperties}
 537      * in the target virtual machine except that properties with a key or
 538      * value that is not a <tt>String</tt> are not included.
 539      *
 540      * <p> This method is typically used to decide which agent to load into
 541      * the target virtual machine with {@link #loadAgent loadAgent}, or
 542      * {@link #loadAgentLibrary loadAgentLibrary}. For example, the
 543      * <code>java.home</code> or <code>user.dir</code> properties might be
 544      * use to create the path to the agent library or JAR file.
 545      *
 546      * @return  The system properties
 547      *
 548      * @throws  IOException
 549      *          If an I/O error occurs
 550      *
 551      * @see     java.lang.System#getProperties
 552      * @see     #loadAgentLibrary
 553      * @see     #loadAgent
 554      */
 555     public abstract Properties getSystemProperties() throws IOException;
 556 
 557     /**
 558      * Returns the current <i>agent properties</i> in the target virtual
 559      * machine.
 560      *
 561      * <p> The target virtual machine can maintain a list of properties on
 562      * behalf of agents. The manner in which this is done, the names of the
 563      * properties, and the types of values that are allowed, is implementation
 564      * specific. Agent properties are typically used to store communication
 565      * end-points and other agent configuration details. For example, a debugger
 566      * agent might create an agent property for its transport address.
 567      *
 568      * <p> This method returns the agent properties whose key and value is a
 569      * <tt>String</tt>. Properties whose key or value is not a <tt>String</tt>
 570      * are omitted. If there are no agent properties maintained in the target
 571      * virtual machine then an empty property list is returned.
 572      *
 573      * @return       The agent properties
 574      *
 575      * @throws       IOException
 576      *               If an I/O error occurs
 577      */
 578     public abstract Properties getAgentProperties() throws IOException;
 579 
 580     /**
 581      * Returns a hash-code value for this VirtualMachine. The hash
 582      * code is based upon the VirtualMachine's components, and satifies
 583      * the general contract of the {@link java.lang.Object#hashCode()
 584      * Object.hashCode} method.
 585      *
 586      * @return  A hash-code value for this virtual machine
 587      */
 588     public int hashCode() {
 589         if (hash != 0) {
 590             return hash;
 591         }
 592         hash = provider.hashCode() * 127 + id.hashCode();
 593         return hash;
 594     }
 595 
 596     /**
 597      * Tests this VirtualMachine for equality with another object.
 598      *
 599      * <p> If the given object is not a VirtualMachine then this
 600      * method returns <tt>false</tt>. For two VirtualMachines to
 601      * be considered equal requires that they both reference the same
 602      * provider, and their {@link VirtualMachineDescriptor#id() identifiers} are equal. </p>
 603      *
 604      * <p> This method satisfies the general contract of the {@link
 605      * java.lang.Object#equals(Object) Object.equals} method. </p>
 606      *
 607      * @param   ob   The object to which this object is to be compared
 608      *
 609      * @return  <tt>true</tt> if, and only if, the given object is
 610      *                a VirtualMachine that is equal to this
 611      *                VirtualMachine.
 612      */
 613     public boolean equals(Object ob) {
 614         if (ob == this)
 615             return true;
 616         if (!(ob instanceof VirtualMachine))
 617             return false;
 618         VirtualMachine other = (VirtualMachine)ob;
 619         if (other.provider() != this.provider()) {
 620             return false;
 621         }
 622         if (!other.id().equals(this.id())) {
 623             return false;
 624         }
 625         return true;
 626     }
 627 
 628     /**
 629      * Returns the string representation of the <code>VirtualMachine</code>.
 630      */
 631     public String toString() {
 632         return provider.toString() + ": " + id;
 633     }
 634 }