diff agent/src/share/classes/sun/jvm/hotspot/jdi/ConnectorImpl.java @ 0:a61af66fc99e jdk7-b24

Initial load
author duke
date Sat, 01 Dec 2007 00:00:00 +0000
parents
children c18cbe5936b8
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/agent/src/share/classes/sun/jvm/hotspot/jdi/ConnectorImpl.java	Sat Dec 01 00:00:00 2007 +0000
@@ -0,0 +1,631 @@
+/*
+ * Copyright 2002-2004 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.
+ *
+ * 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 sun.jvm.hotspot.jdi;
+
+import com.sun.jdi.connect.*;
+import com.sun.jdi.InternalException;
+
+import java.io.*;
+import java.lang.ref.*;
+import java.lang.reflect.*;
+import java.util.*;
+
+abstract class ConnectorImpl implements Connector {
+    Map defaultArguments = new LinkedHashMap();
+
+    // Used by BooleanArgument
+    static String trueString = null;
+    static String falseString;
+
+
+    /**  This is not public in VirtualMachineManagerImpl
+    ThreadGroup mainGroupForJDI() {
+        return ((VirtualMachineManagerImpl)manager).mainGroupForJDI();
+    }
+    ***/
+
+    // multiple debuggee support for SA/JDI
+    private static List freeVMClasses; // List<SoftReference<Class>>
+    private static ClassLoader myLoader;
+    // debug mode for SA/JDI connectors
+    static final protected boolean DEBUG;
+    static {
+        myLoader = ConnectorImpl.class.getClassLoader();
+        freeVMClasses = new ArrayList(0);
+        DEBUG = System.getProperty("sun.jvm.hotspot.jdi.ConnectorImpl.DEBUG") != null;
+    }
+
+    // add a new free VirtualMachineImpl class
+    private static synchronized void addFreeVMImplClass(Class clazz) {
+        if (DEBUG) {
+            System.out.println("adding free VirtualMachineImpl class");
+        }
+        freeVMClasses.add(new SoftReference(clazz));
+    }
+
+    // returns null if we don't have anything free
+    private static synchronized Class getFreeVMImplClass() {
+        while (!freeVMClasses.isEmpty()) {
+              SoftReference ref = (SoftReference) freeVMClasses.remove(0);
+              Object o = ref.get();
+              if (o != null) {
+                  if (DEBUG) {
+                      System.out.println("re-using loaded VirtualMachineImpl");
+                  }
+                  return (Class) o;
+              }
+        }
+        return null;
+    }
+
+    private static Class getVMImplClassFrom(ClassLoader cl)
+                               throws ClassNotFoundException {
+        return Class.forName("sun.jvm.hotspot.jdi.VirtualMachineImpl", true, cl);
+    }
+
+    /* SA has not been designed to support multiple debuggee VMs
+     * at-a-time.  But, JDI supports multiple debuggee VMs.  We
+     * support multiple debuggee VMs in SA/JDI, by creating a new
+     * class loader instance (refer to comment in SAJDIClassLoader
+     * for details). But, to avoid excessive class loading (and
+     * thereby resulting in larger footprint), we re-use 'dispose'd
+     * VirtualMachineImpl classes.
+     */
+    protected static Class loadVirtualMachineImplClass()
+                               throws ClassNotFoundException {
+        Class vmImplClass = getFreeVMImplClass();
+        if (vmImplClass == null) {
+            ClassLoader cl = new SAJDIClassLoader(myLoader);
+            vmImplClass = getVMImplClassFrom(cl);
+        }
+        return vmImplClass;
+    }
+
+    /* We look for System property sun.jvm.hotspot.jdi.<vm version>.
+     * This property should have the value of JDK HOME directory for
+     * the given <vm version>.
+     */
+    private static String getSAClassPathForVM(String vmVersion) {
+        final String prefix = "sun.jvm.hotspot.jdi.";
+        // look for exact match of VM version
+        String jvmHome = System.getProperty(prefix + vmVersion);
+        if (DEBUG) {
+            System.out.println("looking for System property " + prefix + vmVersion);
+        }
+
+        if (jvmHome == null) {
+            // omit chars after first '-' in VM version and try
+            // for example, in '1.5.0-b55' we take '1.5.0'
+            int index = vmVersion.indexOf('-');
+            if (index != -1) {
+                vmVersion = vmVersion.substring(0, index);
+                if (DEBUG) {
+                    System.out.println("looking for System property " + prefix + vmVersion);
+                }
+                jvmHome = System.getProperty(prefix + vmVersion);
+            }
+
+            if (jvmHome == null) {
+                // System property is not set
+                if (DEBUG) {
+                    System.out.println("can't locate JDK home for " + vmVersion);
+                }
+                return null;
+            }
+        }
+
+        if (DEBUG) {
+            System.out.println("JDK home for " + vmVersion + " is " + jvmHome);
+        }
+
+        // sa-jdi is in $JDK_HOME/lib directory
+        StringBuffer buf = new StringBuffer();
+        buf.append(jvmHome);
+        buf.append(File.separatorChar);
+        buf.append("lib");
+        buf.append(File.separatorChar);
+        buf.append("sa-jdi.jar");
+        return buf.toString();
+    }
+
+    /* This method loads VirtualMachineImpl class by a ClassLoader
+     * configured with sa-jdi.jar path of given 'vmVersion'. This is
+     * used for cross VM version debugging. Refer to comments in
+     * SAJDIClassLoader as well.
+     */
+    protected static Class loadVirtualMachineImplClass(String vmVersion)
+            throws ClassNotFoundException {
+        if (DEBUG) {
+            System.out.println("attemping to load sa-jdi.jar for version " + vmVersion);
+        }
+        String classPath = getSAClassPathForVM(vmVersion);
+        if (classPath != null) {
+            ClassLoader cl = new SAJDIClassLoader(myLoader, classPath);
+            return getVMImplClassFrom(cl);
+        } else {
+            return null;
+        }
+    }
+
+    /* Is the given throwable an instanceof VMVersionMismatchException?
+     * Note that we can't do instanceof check because the exception
+     * class might have been loaded by a different class loader.
+     */
+    private static boolean isVMVersionMismatch(Throwable throwable) {
+        String className = throwable.getClass().getName();
+        return className.equals("sun.jvm.hotspot.runtime.VMVersionMismatchException");
+    }
+
+    /* gets target VM version from the given VMVersionMismatchException.
+     * Note that we need to reflectively call the method because of we may
+     * have got this from different classloader's namespace */
+    private static String getVMVersion(Throwable throwable)
+        throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
+        // assert isVMVersionMismatch(throwable), "not a VMVersionMismatch"
+        Class expClass = throwable.getClass();
+        Method targetVersionMethod = expClass.getMethod("getTargetVersion", new Class[0]);
+        return (String) targetVersionMethod.invoke(throwable, null);
+    }
+
+    /** If the causal chain has a sun.jvm.hotspot.runtime.VMVersionMismatchException,
+        attempt to load VirtualMachineImpl class for target VM version. */
+    protected static Class handleVMVersionMismatch(InvocationTargetException ite) {
+        Throwable cause = ite.getCause();
+        if (DEBUG) {
+            System.out.println("checking for version mismatch...");
+        }
+        while (cause != null) {
+            try {
+                if (isVMVersionMismatch(cause)) {
+                    if (DEBUG) {
+                        System.out.println("Triggering cross VM version support...");
+                    }
+                    return loadVirtualMachineImplClass(getVMVersion(cause));
+                }
+            } catch (Exception exp) {
+                if (DEBUG) {
+                    System.out.println("failed to load VirtualMachineImpl class");
+                    exp.printStackTrace();
+                }
+                return null;
+            }
+            cause = cause.getCause();
+        }
+        return null;
+    }
+
+    protected void checkNativeLink(SecurityManager sm, String os) {
+        if (os.equals("SunOS") || os.equals("Linux")) {
+            // link "saproc" - SA native library on SunOS and Linux?
+            sm.checkLink("saproc");
+        } else if (os.startsWith("Windows")) {
+            // link "sawindbg" - SA native library on Windows.
+            sm.checkLink("sawindbg");
+        } else {
+           throw new RuntimeException(os + " is not yet supported");
+        }
+    }
+
+    // we set an observer to detect VirtualMachineImpl.dispose call
+    // and on dispose we add corresponding VirtualMachineImpl.class to
+    // free VirtualMachimeImpl Class list.
+    protected static void setVMDisposeObserver(final Object vm) {
+        try {
+            Method setDisposeObserverMethod = vm.getClass().getDeclaredMethod("setDisposeObserver",
+                                                         new Class[] { java.util.Observer.class });
+            setDisposeObserverMethod.setAccessible(true);
+            setDisposeObserverMethod.invoke(vm,
+                                         new Object[] {
+                                             new Observer() {
+                                                 public void update(Observable o, Object data) {
+                                                     if (DEBUG) {
+                                                         System.out.println("got VM.dispose notification");
+                                                     }
+                                                     addFreeVMImplClass(vm.getClass());
+                                                 }
+                                             }
+                                         });
+        } catch (Exception exp) {
+            if (DEBUG) {
+               System.out.println("setVMDisposeObserver() got an exception:");
+               exp.printStackTrace();
+            }
+        }
+    }
+
+    public Map defaultArguments() {
+        Map defaults = new LinkedHashMap();
+        Collection values = defaultArguments.values();
+
+        Iterator iter = values.iterator();
+        while (iter.hasNext()) {
+            ArgumentImpl argument = (ArgumentImpl)iter.next();
+            defaults.put(argument.name(), argument.clone());
+        }
+        return defaults;
+    }
+
+    void addStringArgument(String name, String label, String description,
+                           String defaultValue, boolean mustSpecify) {
+        defaultArguments.put(name,
+                             new StringArgumentImpl(name, label,
+                                                    description,
+                                                    defaultValue,
+                                                    mustSpecify));
+    }
+
+    void addBooleanArgument(String name, String label, String description,
+                            boolean defaultValue, boolean mustSpecify) {
+        defaultArguments.put(name,
+                             new BooleanArgumentImpl(name, label,
+                                                     description,
+                                                     defaultValue,
+                                                     mustSpecify));
+    }
+
+    void addIntegerArgument(String name, String label, String description,
+                            String defaultValue, boolean mustSpecify,
+                            int min, int max) {
+        defaultArguments.put(name,
+                             new IntegerArgumentImpl(name, label,
+                                                     description,
+                                                     defaultValue,
+                                                     mustSpecify,
+                                                     min, max));
+    }
+
+    void addSelectedArgument(String name, String label, String description,
+                             String defaultValue, boolean mustSpecify,
+                             List list) {
+        defaultArguments.put(name,
+                             new SelectedArgumentImpl(name, label,
+                                                      description,
+                                                      defaultValue,
+                                                      mustSpecify, list));
+    }
+
+    ArgumentImpl argument(String name, Map arguments)
+                throws IllegalConnectorArgumentsException {
+
+        ArgumentImpl argument = (ArgumentImpl)arguments.get(name);
+        if (argument == null) {
+            throw new IllegalConnectorArgumentsException(
+                         "Argument missing", name);
+        }
+        String value = argument.value();
+        if (value == null || value.length() == 0) {
+            if (argument.mustSpecify()) {
+            throw new IllegalConnectorArgumentsException(
+                         "Argument unspecified", name);
+            }
+        } else if(!argument.isValid(value)) {
+            throw new IllegalConnectorArgumentsException(
+                         "Argument invalid", name);
+        }
+
+        return argument;
+    }
+
+    String getString(String key) {
+        //fixme jjh; needs i18n
+        // this is not public return ((VirtualMachineManagerImpl)manager).getString(key);
+        return key;
+    }
+
+    public String toString() {
+        String string = name() + " (defaults: ";
+        Iterator iter = defaultArguments().values().iterator();
+        boolean first = true;
+        while (iter.hasNext()) {
+            ArgumentImpl argument = (ArgumentImpl)iter.next();
+            if (!first) {
+                string += ", ";
+            }
+            string += argument.toString();
+            first = false;
+        }
+        return string  + ")";
+    }
+
+    abstract class ArgumentImpl implements Connector.Argument, Cloneable, Serializable {
+        private String name;
+        private String label;
+        private String description;
+        private String value;
+        private boolean mustSpecify;
+
+        ArgumentImpl(String name, String label, String description,
+                     String value,
+                     boolean mustSpecify) {
+            this.name = name;
+            this.label = label;
+            this.description = description;
+            this.value = value;
+            this.mustSpecify = mustSpecify;
+        }
+
+        public abstract boolean isValid(String value);
+
+        public String name() {
+            return name;
+        }
+
+        public String label() {
+            return label;
+        }
+
+        public String description() {
+            return description;
+        }
+
+        public String value() {
+            return value;
+        }
+
+        public void setValue(String value) {
+            if (value == null) {
+                throw new NullPointerException("Can't set null value");
+            }
+            this.value = value;
+        }
+
+        public boolean mustSpecify() {
+            return mustSpecify;
+        }
+
+        public boolean equals(Object obj) {
+            if ((obj != null) && (obj instanceof Connector.Argument)) {
+                Connector.Argument other = (Connector.Argument)obj;
+                return (name().equals(other.name())) &&
+                       (description().equals(other.description())) &&
+                       (mustSpecify() == other.mustSpecify()) &&
+                       (value().equals(other.value()));
+            } else {
+                return false;
+            }
+        }
+
+        public int hashCode() {
+            return description().hashCode();
+        }
+
+        public Object clone() {
+            try {
+                return super.clone();
+            } catch (CloneNotSupportedException e) {
+                // Object should always support clone
+                throw (InternalException) new InternalException().initCause(e);
+            }
+        }
+
+        public String toString() {
+            return name() + "=" + value();
+        }
+    }
+
+    class BooleanArgumentImpl extends ConnectorImpl.ArgumentImpl
+                              implements Connector.BooleanArgument {
+
+        BooleanArgumentImpl(String name, String label, String description,
+                            boolean value,
+                            boolean mustSpecify) {
+            super(name, label, description, null, mustSpecify);
+            if(trueString == null) {
+                trueString = getString("true");
+                falseString = getString("false");
+            }
+            setValue(value);
+        }
+
+        /**
+         * Sets the value of the argument.
+         */
+        public void setValue(boolean value) {
+            setValue(stringValueOf(value));
+        }
+
+        /**
+         * Performs basic sanity check of argument.
+         * @return <code>true</code> if value is a string
+         * representation of a boolean value.
+         * @see #stringValueOf(boolean)
+         */
+        public boolean isValid(String value) {
+            return value.equals(trueString) || value.equals(falseString);
+        }
+
+        /**
+         * Return the string representation of the <code>value</code>
+         * parameter.
+         * Does not set or examine the value or the argument.
+         * @return the localized String representation of the
+         * boolean value.
+         */
+        public String stringValueOf(boolean value) {
+            return value? trueString : falseString;
+        }
+
+        /**
+         * Return the value of the argument as a boolean.  Since
+         * the argument may not have been set or may have an invalid
+         * value {@link #isValid(String)} should be called on
+         * {@link #value()} to check its validity.  If it is invalid
+         * the boolean returned by this method is undefined.
+         * @return the value of the argument as a boolean.
+         */
+        public boolean booleanValue() {
+            return value().equals(trueString);
+        }
+    }
+
+    class IntegerArgumentImpl extends ConnectorImpl.ArgumentImpl
+                              implements Connector.IntegerArgument {
+
+        private final int min;
+        private final int max;
+
+        IntegerArgumentImpl(String name, String label, String description,
+                            String value,
+                            boolean mustSpecify, int min, int max) {
+            super(name, label, description, value, mustSpecify);
+            this.min = min;
+            this.max = max;
+        }
+
+        /**
+         * Sets the value of the argument.
+         * The value should be checked with {@link #isValid(int)}
+         * before setting it; invalid values will throw an exception
+         * when the connection is established - for example,
+         * on {@link LaunchingConnector#launch}
+         */
+        public void setValue(int value) {
+            setValue(stringValueOf(value));
+        }
+
+        /**
+         * Performs basic sanity check of argument.
+         * @return <code>true</code> if value represents an int that is
+         * <code>{@link #min()} &lt;= value &lt;= {@link #max()}</code>
+         */
+        public boolean isValid(String value) {
+            if (value == null) {
+                return false;
+            }
+            try {
+                return isValid(Integer.decode(value).intValue());
+            } catch(NumberFormatException exc) {
+                return false;
+            }
+        }
+
+        /**
+         * Performs basic sanity check of argument.
+         * @return <code>true</code> if
+         * <code>{@link #min()} &lt;= value  &lt;= {@link #max()}</code>
+         */
+        public boolean isValid(int value) {
+            return min <= value && value <= max;
+        }
+
+        /**
+         * Return the string representation of the <code>value</code>
+         * parameter.
+         * Does not set or examine the value or the argument.
+         * @return the String representation of the
+         * int value.
+         */
+        public String stringValueOf(int value) {
+            // *** Should this be internationalized????
+            // *** Even Brian Beck was unsure if an Arabic programmer
+            // *** would expect port numbers in Arabic numerals,
+            // *** so punt for now.
+            return ""+value;
+        }
+
+        /**
+         * Return the value of the argument as a int.  Since
+         * the argument may not have been set or may have an invalid
+         * value {@link #isValid(String)} should be called on
+         * {@link #value()} to check its validity.  If it is invalid
+         * the int returned by this method is undefined.
+         * @return the value of the argument as a int.
+         */
+        public int intValue() {
+            if (value() == null) {
+                return 0;
+            }
+            try {
+                return Integer.decode(value()).intValue();
+            } catch(NumberFormatException exc) {
+                return 0;
+            }
+        }
+
+        /**
+         * The upper bound for the value.
+         * @return the maximum allowed value for this argument.
+         */
+        public int max() {
+            return max;
+        }
+
+        /**
+         * The lower bound for the value.
+         * @return the minimum allowed value for this argument.
+         */
+        public int min() {
+            return min;
+        }
+    }
+
+    class StringArgumentImpl extends ConnectorImpl.ArgumentImpl
+                              implements Connector.StringArgument {
+
+        StringArgumentImpl(String name, String label, String description,
+                           String value,
+                           boolean mustSpecify) {
+            super(name, label, description, value, mustSpecify);
+        }
+
+        /**
+         * Performs basic sanity check of argument.
+         * @return <code>true</code> always
+         */
+        public boolean isValid(String value) {
+            return true;
+        }
+    }
+
+    class SelectedArgumentImpl extends ConnectorImpl.ArgumentImpl
+                              implements Connector.SelectedArgument {
+
+        private final List choices;
+
+        SelectedArgumentImpl(String name, String label, String description,
+                             String value,
+                             boolean mustSpecify, List choices) {
+            super(name, label, description, value, mustSpecify);
+            this.choices = Collections.unmodifiableList(
+                                           new ArrayList(choices));
+        }
+
+        /**
+         * Return the possible values for the argument
+         * @return {@link List} of {@link String}
+         */
+        public List choices() {
+            return choices;
+        }
+
+        /**
+         * Performs basic sanity check of argument.
+         * @return <code>true</code> if value is one of {@link #choices()}.
+         */
+        public boolean isValid(String value) {
+            return choices.contains(value);
+        }
+    }
+}