changeset 7502:ccd0aceb1190

8003255: (profiles) Update JAR file specification to support profiles Reviewed-by: dholmes, mchung, ksrini
author alanb
date Mon, 21 Jan 2013 23:20:42 -0500
parents 7096f51288ab
children c024147205f6
files src/share/classes/java/net/URLClassLoader.java src/share/classes/java/util/jar/Attributes.java src/share/classes/java/util/jar/UnsupportedProfileException.java src/share/classes/sun/launcher/LauncherHelper.java src/share/classes/sun/launcher/resources/launcher.properties src/share/classes/sun/misc/URLClassPath.java src/share/classes/sun/tools/jar/Main.java src/share/classes/sun/tools/jar/resources/jar.properties test/java/net/URLClassLoader/profiles/Basic.java test/java/net/URLClassLoader/profiles/Lib.java test/java/net/URLClassLoader/profiles/basic.sh test/tools/jar/AddAndUpdateProfile.java test/tools/launcher/profiles/Basic.java test/tools/launcher/profiles/Logging.java test/tools/launcher/profiles/Main.java
diffstat 15 files changed, 801 insertions(+), 9 deletions(-) [+]
line wrap: on
line diff
--- a/src/share/classes/java/net/URLClassLoader.java	Mon Jan 21 23:17:58 2013 -0500
+++ b/src/share/classes/java/net/URLClassLoader.java	Mon Jan 21 23:20:42 2013 -0500
@@ -57,6 +57,12 @@
  * <p>
  * The classes that are loaded are by default granted permission only to
  * access the URLs specified when the URLClassLoader was created.
+ * <p>
+ * Where a JAR file contains the {@link Name#PROFILE Profile} attribute
+ * then its value is the name of the Java SE profile that the library
+ * minimally requires. If this runtime does not support the profile then
+ * it causes {@link java.util.jar.UnsupportedProfileException} to be
+ * thrown at some unspecified time.
  *
  * @author  David Connelly
  * @since   1.2
--- a/src/share/classes/java/util/jar/Attributes.java	Mon Jan 21 23:17:58 2013 -0500
+++ b/src/share/classes/java/util/jar/Attributes.java	Mon Jan 21 23:20:42 2013 -0500
@@ -565,6 +565,15 @@
         public static final Name MAIN_CLASS = new Name("Main-Class");
 
         /**
+         * {@code Name} object for {@code Profile} manifest attribute used by
+         * applications or libraries packaged as JAR files to indicate the
+         * minimum profile required to execute the application.
+         * @since 1.8
+         * @see UnsupportedProfileException
+         */
+        public static final Name PROFILE = new Name("Profile");
+
+        /**
          * <code>Name</code> object for <code>Sealed</code> manifest attribute
          * used for sealing.
          * @see <a href="../../../../technotes/guides/extensions/spec.html#sealing">
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/share/classes/java/util/jar/UnsupportedProfileException.java	Mon Jan 21 23:20:42 2013 -0500
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package java.util.jar;
+
+/**
+ * Thrown to indicate an attempt to access a JAR file with a {@link
+ * Attributes.Name#PROFILE Profile} attribute that names a profile that
+ * is not supported by this runtime.
+ *
+ * @since   1.8
+ */
+public class UnsupportedProfileException extends RuntimeException {
+    private static final long serialVersionUID = -1834773870678792406L;
+
+    /**
+     * Constructs an {@code UnsupportedProfileException} with no detail
+     * message.
+     */
+    public UnsupportedProfileException() {
+    }
+
+    /**
+     * Constructs an {@code UnsupportedProfileException} with the
+     * specified detail message.
+     *
+     * @param message the detail message
+     */
+    public UnsupportedProfileException(String message) {
+        super(message);
+    }
+}
--- a/src/share/classes/sun/launcher/LauncherHelper.java	Mon Jan 21 23:17:58 2013 -0500
+++ b/src/share/classes/sun/launcher/LauncherHelper.java	Mon Jan 21 23:20:42 2013 -0500
@@ -65,10 +65,14 @@
 import java.util.jar.Attributes;
 import java.util.jar.JarFile;
 import java.util.jar.Manifest;
+import sun.misc.Version;
+import sun.misc.URLClassPath;
 
 public enum LauncherHelper {
     INSTANCE;
     private static final String MAIN_CLASS = "Main-Class";
+    private static final String PROFILE    = "Profile";
+
     private static StringBuilder outBuf = new StringBuilder();
 
     private static final String INDENT = "    ";
@@ -418,6 +422,28 @@
                     new Attributes.Name(FXHelper.JAVAFX_APPLICATION_MARKER))) {
                 return FXHelper.class.getName();
             }
+
+            /*
+             * If this is not a full JRE then the Profile attribute must be
+             * present with the Main-Class attribute so as to indicate the minimum
+             * profile required. Note that we need to suppress checking of the Profile
+             * attribute after we detect an error. This is because the abort may
+             * need to lookup resources and this may involve opening additional JAR
+             * files that would result in errors that suppress the main error.
+             */
+            String profile = mainAttrs.getValue(PROFILE);
+            if (profile == null) {
+                if (!Version.isFullJre()) {
+                    URLClassPath.suppressProfileCheckForLauncher();
+                    abort(null, "java.launcher.jar.error4", jarname);
+                }
+            } else {
+                if (!Version.supportsProfile(profile)) {
+                    URLClassPath.suppressProfileCheckForLauncher();
+                    abort(null, "java.launcher.jar.error5", profile, jarname);
+                }
+            }
+
             return mainValue.trim();
         } catch (IOException ioe) {
             abort(ioe, "java.launcher.jar.error1", jarname);
--- a/src/share/classes/sun/launcher/resources/launcher.properties	Mon Jan 21 23:17:58 2013 -0500
+++ b/src/share/classes/sun/launcher/resources/launcher.properties	Mon Jan 21 23:20:42 2013 -0500
@@ -139,6 +139,8 @@
     Error: An unexpected error occurred while trying to open file {0}
 java.launcher.jar.error2=manifest not found in {0}
 java.launcher.jar.error3=no main manifest attribute, in {0}
+java.launcher.jar.error4=no Profile manifest attribute in {0}
+java.launcher.jar.error5=Profile {0} required by {1} not supported by this runtime
 java.launcher.init.error=initialization error
 java.launcher.javafx.error1=\
     Error: The JavaFX launchApplication method has the wrong signature, it\n\
--- a/src/share/classes/sun/misc/URLClassPath.java	Mon Jan 21 23:17:58 2013 -0500
+++ b/src/share/classes/sun/misc/URLClassPath.java	Mon Jan 21 23:20:42 2013 -0500
@@ -35,6 +35,7 @@
 import java.util.jar.Manifest;
 import java.util.jar.Attributes;
 import java.util.jar.Attributes.Name;
+import java.util.jar.UnsupportedProfileException;
 import java.net.JarURLConnection;
 import java.net.MalformedURLException;
 import java.net.URL;
@@ -64,6 +65,12 @@
     final static String JAVA_VERSION;
     private static final boolean DEBUG;
 
+    /**
+     * Used by launcher to indicate that checking of the JAR file "Profile"
+     * attribute has been suppressed.
+     */
+    private static boolean profileCheckSuppressedByLauncher;
+
     static {
         JAVA_VERSION = java.security.AccessController.doPrivileged(
             new sun.security.action.GetPropertyAction("java.version"));
@@ -581,6 +588,15 @@
         }
     }
 
+    /**
+     * Used by the launcher to suppress further checking of the JAR file Profile
+     * attribute (necessary when the launcher is aborting as the abort involves
+     * a resource lookup that may involve opening additional JAR files)
+     */
+    public static void suppressProfileCheckForLauncher() {
+        profileCheckSuppressedByLauncher = true;
+    }
+
     /*
      * Inner class used to represent a Loader of resources from a JAR URL.
      */
@@ -788,6 +804,28 @@
             return false;
         }
 
+        /**
+         * If the Profile attribute is present then this method checks that the runtime
+         * supports that profile.
+         *
+         * ## Add a fast path like Class-Path to avoid reading the manifest when the attribute
+         *    is not present.
+         */
+        void checkProfileAttribute() throws IOException {
+            Manifest man = jar.getManifest();
+            if (man != null) {
+                Attributes attr = man.getMainAttributes();
+                if (attr != null) {
+                    String value = attr.getValue(Name.PROFILE);
+                    if (value != null && !Version.supportsProfile(value)) {
+                        String prefix = Version.profileName().length() > 0 ?
+                            "This runtime implements " + Version.profileName() + ", " : "";
+                        throw new UnsupportedProfileException(prefix + csu + " requires " + value);
+                    }
+                }
+            }
+        }
+
         /*
          * Returns the URL for a resource with the specified name
          */
@@ -957,6 +995,12 @@
 
             ensureOpen();
             parseExtensionsDependencies();
+
+            // check Profile attribute if present
+            if (!profileCheckSuppressedByLauncher) {
+                checkProfileAttribute();
+            }
+
             if (SharedSecrets.javaUtilJarAccess().jarFileHasClassPathAttribute(jar)) { // Only get manifest when necessary
                 Manifest man = jar.getManifest();
                 if (man != null) {
--- a/src/share/classes/sun/tools/jar/Main.java	Mon Jan 21 23:17:58 2013 -0500
+++ b/src/share/classes/sun/tools/jar/Main.java	Mon Jan 21 23:20:42 2013 -0500
@@ -47,7 +47,7 @@
 class Main {
     String program;
     PrintStream out, err;
-    String fname, mname, ename;
+    String fname, mname, ename, pname;
     String zname = "";
     String[] files;
     String rootjar = null;
@@ -78,6 +78,9 @@
     static final String MANIFEST_DIR = "META-INF/";
     static final String VERSION = "1.0";
 
+    // valid values for Profile attribute
+    private static final String[] PROFILES = { "compact1", "compact2", "compact3" };
+
     private static ResourceBundle rsrc;
 
     /**
@@ -184,6 +187,14 @@
                     if (ename != null) {
                         addMainClass(manifest, ename);
                     }
+                    if (pname != null) {
+                        if (!addProfileName(manifest, pname)) {
+                            if (in != null) {
+                                in.close();
+                            }
+                            return false;
+                        }
+                    }
                 }
                 OutputStream out;
                 if (fname != null) {
@@ -230,7 +241,7 @@
                 if (manifest != null) {
                     manifest.close();
                 }
-                if (fname != null) {
+                if (ok && fname != null) {
                     // on Win32, we need this delete
                     inputFile.delete();
                     if (!tmpFile.renameTo(inputFile)) {
@@ -361,6 +372,9 @@
                 case 'e':
                      ename = args[count++];
                      break;
+                case 'p':
+                     pname = args[count++];
+                     break;
                 default:
                     error(formatMsg("error.illegal.option",
                                 String.valueOf(flags.charAt(i))));
@@ -410,7 +424,7 @@
             usageError();
             return false;
         } else if (uflag) {
-            if ((mname != null) || (ename != null)) {
+            if ((mname != null) || (ename != null) || (pname != null)) {
                 /* just want to update the manifest */
                 return true;
             } else {
@@ -544,7 +558,7 @@
                 || (Mflag && isManifestEntry)) {
                 continue;
             } else if (isManifestEntry && ((newManifest != null) ||
-                        (ename != null))) {
+                        (ename != null) || (pname != null))) {
                 foundManifest = true;
                 if (newManifest != null) {
                     // Don't read from the newManifest InputStream, as we
@@ -563,7 +577,9 @@
                 if (newManifest != null) {
                     old.read(newManifest);
                 }
-                updateManifest(old, zos);
+                if (!updateManifest(old, zos)) {
+                    return false;
+                }
             } else {
                 if (!entryMap.containsKey(name)) { // copy the old stuff
                     // do our own compression
@@ -596,10 +612,14 @@
                 Manifest m = new Manifest(newManifest);
                 updateOk = !isAmbiguousMainClass(m);
                 if (updateOk) {
-                    updateManifest(m, zos);
+                    if (!updateManifest(m, zos)) {
+                        updateOk = false;
+                    }
                 }
-            } else if (ename != null) {
-                updateManifest(new Manifest(), zos);
+            } else if (ename != null || pname != null) {
+                if (!updateManifest(new Manifest(), zos)) {
+                    updateOk = false;
+                }
             }
         }
         zis.close();
@@ -623,7 +643,7 @@
         zos.closeEntry();
     }
 
-    private void updateManifest(Manifest m, ZipOutputStream zos)
+    private boolean updateManifest(Manifest m, ZipOutputStream zos)
         throws IOException
     {
         addVersion(m);
@@ -631,6 +651,11 @@
         if (ename != null) {
             addMainClass(m, ename);
         }
+        if (pname != null) {
+            if (!addProfileName(m, pname)) {
+                return false;
+            }
+        }
         ZipEntry e = new ZipEntry(MANIFEST_NAME);
         e.setTime(System.currentTimeMillis());
         if (flag0) {
@@ -641,6 +666,7 @@
         if (vflag) {
             output(getMsg("out.update.manifest"));
         }
+        return true;
     }
 
 
@@ -687,6 +713,28 @@
         global.put(Attributes.Name.MAIN_CLASS, mainApp);
     }
 
+    private boolean addProfileName(Manifest m, String profile) {
+        // check profile name
+        boolean found = false;
+        int i = 0;
+        while (i < PROFILES.length) {
+            if (profile.equals(PROFILES[i])) {
+                found = true;
+                break;
+            }
+            i++;
+        }
+        if (!found) {
+            error(formatMsg("error.bad.pvalue", profile));
+            return false;
+        }
+
+        // overrides any existing Profile attribute
+        Attributes global = m.getMainAttributes();
+        global.put(Attributes.Name.PROFILE, profile);
+        return true;
+    }
+
     private boolean isAmbiguousMainClass(Manifest m) {
         if (ename != null) {
             Attributes global = m.getMainAttributes();
--- a/src/share/classes/sun/tools/jar/resources/jar.properties	Mon Jan 21 23:17:58 2013 -0500
+++ b/src/share/classes/sun/tools/jar/resources/jar.properties	Mon Jan 21 23:20:42 2013 -0500
@@ -36,6 +36,8 @@
 error.bad.eflag=\
 	'e' flag and manifest with the 'Main-Class' attribute cannot be specified \n\
 	 together!
+error.bad.pvalue=\
+        bad value for 'Profile' attribute: {0}
 error.nosuch.fileordir=\
         {0} : no such file or directory
 error.write.file=\
@@ -77,6 +79,7 @@
 \ \   -m  include manifest information from specified manifest file\n\
 \ \   -e  specify application entry point for stand-alone application \n\
 \ \       bundled into an executable jar file\n\
+\ \   -p  specify profile name\n\
 \ \   -0  store only; use no ZIP compression\n\
 \ \   -M  do not create a manifest file for the entries\n\
 \ \   -i  generate index information for the specified jar files\n\
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/java/net/URLClassLoader/profiles/Basic.java	Mon Jan 21 23:20:42 2013 -0500
@@ -0,0 +1,95 @@
+/*
+ * Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import java.net.*;
+import java.io.File;
+import java.util.jar.*;
+
+/**
+ * Attempts to load classes or resources from a JAR file. The load should succeed
+ * if the runtime supports the profile indicated by the Profile attribute, fail
+ * with UnsupportedProfileException otherwise.
+ */
+
+public class Basic {
+
+    static int indexOf(String profile) {
+        if (profile == null || "compact1".equals(profile)) return 1;
+        if ("compact2".equals(profile)) return 2;
+        if ("compact3".equals(profile)) return 3;
+        if ("".equals(profile)) return 4;
+        return Integer.MAX_VALUE;  // unknown profile name
+    }
+
+    public static void main(String[] args) throws Exception {
+        if (args.length < 2)
+            throw new RuntimeException("Usage: java <jarfile> <classname>");
+        String jar = args[0];
+        String cn = args[1];
+
+        File lib = new File(jar);
+        URL url = lib.toURI().toURL();
+        URL urls[] = { url };
+
+        // ## replace this if there is a standard way to determine the profile
+        String thisProfile = sun.misc.Version.profileName();
+
+        String jarProfile = null;
+        try (JarFile jf = new JarFile(lib)) {
+            Manifest manifest = jf.getManifest();
+            if (manifest != null) {
+                Attributes mainAttrs = manifest.getMainAttributes();
+                if (mainAttrs != null) {
+                    jarProfile = mainAttrs.getValue(Attributes.Name.PROFILE);
+                }
+            }
+        }
+
+        boolean shouldFail = indexOf(thisProfile) < indexOf(jarProfile);
+
+        try (URLClassLoader cl = new URLClassLoader(urls)) {
+            System.out.format("Loading %s from %s ...%n", cn, jar);
+            Class<?> c = Class.forName(cn, true, cl);
+            System.out.println(c);
+            if (shouldFail)
+                throw new RuntimeException("UnsupportedProfileException expected");
+        } catch (UnsupportedProfileException x) {
+            if (!shouldFail)
+                throw x;
+            System.out.println("UnsupportedProfileException thrown as expected");
+        }
+
+        try (URLClassLoader cl = new URLClassLoader(urls)) {
+            System.out.format("Loading resource from %s ...%n", jar);
+            URL r = cl.findResource("META-INF/MANIFEST.MF");
+            System.out.println(r);
+            if (shouldFail)
+                throw new RuntimeException("UnsupportedProfileException expected");
+        } catch (UnsupportedProfileException x) {
+            if (!shouldFail)
+                throw x;
+            System.out.println("UnsupportedProfileException thrown as expected");
+        }
+    }
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/java/net/URLClassLoader/profiles/Lib.java	Mon Jan 21 23:20:42 2013 -0500
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package lib;
+
+public class Lib {
+    private Lib() { }
+
+    public static void doSomething() { }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/java/net/URLClassLoader/profiles/basic.sh	Mon Jan 21 23:20:42 2013 -0500
@@ -0,0 +1,55 @@
+#
+# Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved.
+# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+#
+# This code is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License version 2 only, as
+# published by the Free Software Foundation.
+#
+# This code is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+# version 2 for more details (a copy is included in the LICENSE file that
+# accompanied this code).
+#
+# You should have received a copy of the GNU General Public License version
+# 2 along with this work; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+# Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+# or visit www.oracle.com if you need additional information or have any
+# questions.
+#
+
+# @test
+# @bug 8003255
+# @compile -XDignore.symbol.file Basic.java Lib.java
+# @summary Test that UnsupportedProfileException thrown when attempting to
+#     load classes or resources from a JAR file with the Profile attribute
+# @run shell basic.sh
+
+if [ -z "$TESTJAVA" ]; then
+  if [ $# -lt 1 ]; then exit 1; fi
+  TESTJAVA=$1; shift
+  COMPILEJAVA=$TESTJAVA
+  TESTSRC=`pwd`
+  TESTCLASSES=`pwd`
+fi
+
+echo "Creating GoodLib.jar ..."
+echo "Profile: compact3" > good.mf
+$COMPILEJAVA/bin/jar cvfm GoodLib.jar good.mf -C $TESTCLASSES lib
+
+echo "Create BadLib.jar ..."
+echo "Profile: badname" > bad.mf
+$COMPILEJAVA/bin/jar cvfm BadLib.jar bad.mf -C $TESTCLASSES lib
+
+# remove classes so that they aren't on the classpath
+rm -rf $TESTCLASSES/lib
+
+echo "Test with GoodLib.jar ..."
+$TESTJAVA/bin/java -cp $TESTCLASSES Basic GoodLib.jar lib.Lib
+
+echo "Test with BadLib.jar ..."
+$TESTJAVA/bin/java -cp $TESTCLASSES Basic BadLib.jar lib.Lib
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/tools/jar/AddAndUpdateProfile.java	Mon Jan 21 23:20:42 2013 -0500
@@ -0,0 +1,129 @@
+/*
+ * Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/* @test
+ * @bug 8003255
+ * @compile -XDignore.symbol.file AddAndUpdateProfile.java
+ * @run main AddAndUpdateProfile
+ * @summary Basic test of jar tool "p" option to add or update the Profile
+ *    attribute in the main manifest of a JAR file
+ */
+
+import java.util.jar.*;
+import static java.util.jar.Attributes.Name.*;
+import java.nio.file.*;
+import java.io.IOException;
+
+import sun.tools.jar.Main;
+
+public class AddAndUpdateProfile {
+    static boolean doJar(String... args) {
+        System.out.print("jar");
+        for (String arg: args)
+            System.out.print(" " + arg);
+        System.out.println("");
+
+        Main jartool = new Main(System.out, System.err, "jar");
+        return jartool.run(args);
+    }
+
+    static void jar(String... args) {
+        if (!doJar(args))
+            throw new RuntimeException("jar command failed");
+    }
+
+    static void jarExpectingFail(String... args) {
+        if (doJar(args))
+            throw new RuntimeException("jar command not expected to succeed");
+    }
+
+    static void checkMainAttribute(String jarfile, Attributes.Name name,
+                                   String expectedValue)
+        throws IOException
+    {
+        try (JarFile jf = new JarFile(jarfile)) {
+            Manifest mf = jf.getManifest();
+            if (mf == null && expectedValue != null)
+                throw new RuntimeException("Manifest not found");
+            if (mf != null) {
+                String actual = mf.getMainAttributes().getValue(name);
+                if (actual != null) {
+                    if (!actual.equals(expectedValue))
+                        throw new RuntimeException("Profile attribute has unexpected value");
+                } else {
+                    if (expectedValue != null)
+                        throw new RuntimeException("Profile attribute should not be present");
+                }
+            }
+        }
+    }
+
+    public static void main(String[] args) throws Exception {
+        Path entry = Files.createFile(Paths.get("xfoo"));
+        String jarfile = "xFoo.jar";
+        try {
+
+            // create JAR file with Profile attribute
+            jar("cfp", jarfile, "compact1", entry.toString());
+            checkMainAttribute(jarfile, PROFILE, "compact1");
+
+            // attempt to create JAR file with Profile attribute and bad value
+            jarExpectingFail("cfp", jarfile, "garbage", entry.toString());
+            jarExpectingFail("cfp", jarfile, "Compact1", entry.toString());
+            jarExpectingFail("cfp", jarfile, "COMPACT1", entry.toString());
+
+            // update value of Profile attribute
+            jar("ufp", jarfile, "compact2");
+            checkMainAttribute(jarfile, PROFILE, "compact2");
+
+            // attempt to update value of Profile attribute to bad value
+            // (update should not change the JAR file)
+            jarExpectingFail("ufp", jarfile, "garbage");
+            checkMainAttribute(jarfile, PROFILE, "compact2");
+            jarExpectingFail("ufp", jarfile, "COMPACT1");
+            checkMainAttribute(jarfile, PROFILE, "compact2");
+
+            // create JAR file with both a Main-Class and Profile attribute
+            jar("cfep", jarfile, "Foo", "compact1", entry.toString());
+            checkMainAttribute(jarfile, MAIN_CLASS, "Foo");
+            checkMainAttribute(jarfile, PROFILE, "compact1");
+
+            // update value of Profile attribute
+            jar("ufp", jarfile, "compact2");
+            checkMainAttribute(jarfile, PROFILE, "compact2");
+
+            // create JAR file without Profile attribute
+            jar("cf", jarfile, entry.toString());
+            checkMainAttribute(jarfile, PROFILE, null);
+
+            // update value of Profile attribute
+            jar("ufp", jarfile, "compact3");
+            checkMainAttribute(jarfile, PROFILE, "compact3");
+
+        } finally {
+            Files.deleteIfExists(Paths.get(jarfile));
+            Files.delete(entry);
+        }
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/tools/launcher/profiles/Basic.java	Mon Jan 21 23:20:42 2013 -0500
@@ -0,0 +1,231 @@
+/*
+ * Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/**
+ * @test
+ * @bug 8003255
+ * @compile -XDignore.symbol.file Basic.java Main.java Logging.java
+ * @run main Basic
+ * @summary Test the launcher checks the Profile attribute of executable JAR
+ *     files. Also checks that libraries that specify the Profile attribute
+ *     are not loaded if the runtime does not support the required profile.
+ */
+
+import java.io.*;
+import java.util.jar.*;
+import static java.util.jar.JarFile.MANIFEST_NAME;
+import java.util.zip.*;
+import java.nio.file.*;
+import java.nio.file.attribute.BasicFileAttributes;
+
+public class Basic {
+
+    static final String MANIFEST_DIR = "META-INF/";
+
+    static final String JAVA_HOME = System.getProperty("java.home");
+    static final String OS_NAME = System.getProperty("os.name");
+    static final String OS_ARCH = System.getProperty("os.arch");
+
+    static final String JAVA_CMD =
+            OS_NAME.startsWith("Windows") ? "java.exe" : "java";
+
+    static final boolean NEED_D64 =
+            OS_NAME.equals("SunOS") &&
+            (OS_ARCH.equals("sparcv9") || OS_ARCH.equals("amd64"));
+
+    /**
+     * Creates a JAR file with the given attributes and the given entries.
+     * Class files are assumed to be in ${test.classes}. Note that this this
+     * method cannot use the "jar" tool as it may not be present in the image.
+     */
+    static void createJarFile(String jarfile,
+                              String mainAttributes,
+                              String... entries)
+        throws IOException
+    {
+        // create Manifest
+        Manifest manifest = new Manifest();
+        Attributes jarAttrs = manifest.getMainAttributes();
+        jarAttrs.put(Attributes.Name.MANIFEST_VERSION, "1.0");
+        if (mainAttributes.length() > 0) {
+            for (String attr: mainAttributes.split(",")) {
+                String[] s = attr.split("=");
+                jarAttrs.put(new Attributes.Name(s[0]), s[1]);
+            }
+        }
+
+        try (OutputStream out = Files.newOutputStream(Paths.get(jarfile));
+             ZipOutputStream zos = new JarOutputStream(out))
+        {
+            // add manifest directory and manifest file
+            ZipEntry e = new JarEntry(MANIFEST_DIR);
+            e.setTime(System.currentTimeMillis());
+            e.setSize(0);
+            e.setCrc(0);
+            zos.putNextEntry(e);
+            e = new ZipEntry(MANIFEST_NAME);
+            e.setTime(System.currentTimeMillis());
+            zos.putNextEntry(e);
+            manifest.write(zos);
+            zos.closeEntry();
+
+            // entries in JAR file
+            for (String entry: entries) {
+                e = new JarEntry(entry);
+                Path path;
+                if (entry.endsWith(".class")) {
+                    path = Paths.get(System.getProperty("test.classes"), entry);
+                } else {
+                    path = Paths.get(entry);
+                }
+                BasicFileAttributes attrs =
+                    Files.readAttributes(path, BasicFileAttributes.class);
+                e.setTime(attrs.lastModifiedTime().toMillis());
+                if (attrs.size() == 0) {
+                    e.setMethod(ZipEntry.STORED);
+                    e.setSize(0);
+                    e.setCrc(0);
+                }
+                zos.putNextEntry(e);
+                if (attrs.isRegularFile())
+                    Files.copy(path, zos);
+                zos.closeEntry();
+            }
+        }
+    }
+
+    /**
+     * Execute the given executable JAR file with the given arguments. This
+     * method blocks until the launched VM terminates. Any output or error
+     * message from the launched VM are printed to System.out. Returns the
+     * exit value.
+     */
+    static int exec(String jf, String... args) throws IOException {
+        StringBuilder sb = new StringBuilder();
+        sb.append(Paths.get(JAVA_HOME, "bin", JAVA_CMD).toString());
+        if (NEED_D64)
+            sb.append(" -d64");
+        sb.append(" -jar ");
+        sb.append(Paths.get(jf).toAbsolutePath());
+        for (String arg: args) {
+            sb.append(' ');
+            sb.append(arg);
+        }
+        String[] cmd = sb.toString().split(" ");
+        ProcessBuilder pb = new ProcessBuilder(cmd);
+        pb.redirectErrorStream(true);
+        Process p = pb.start();
+        BufferedReader reader =
+            new BufferedReader(new InputStreamReader(p.getInputStream()));
+        String line;
+        while ((line = reader.readLine()) != null) {
+            System.out.println(line);
+        }
+        try {
+            return p.waitFor();
+        } catch (InterruptedException e) {
+            throw new RuntimeException("Should not happen");
+        }
+    }
+
+    static void checkRun(String jf, String... args) throws IOException {
+        if (exec(jf) != 0)
+            throw new RuntimeException(jf + " failed!!!");
+    }
+
+    static void checkRunFail(String jf, String... args) throws IOException {
+        if (exec(jf) == 0)
+            throw new RuntimeException(jf + " did not fail!!!");
+        System.out.println("Failed as expected");
+    }
+
+    public static void main(String[] args) throws IOException {
+        // ## replace this if there is a standard way to determine the profile
+        String profile = sun.misc.Version.profileName();
+
+        int thisProfile = 4;
+        if ("compact1".equals(profile)) thisProfile = 1;
+        if ("compact2".equals(profile)) thisProfile = 2;
+        if ("compact3".equals(profile)) thisProfile = 3;
+
+        // "library" JAR file used by the test
+        createJarFile("Logging.jar", "", "Logging.class");
+
+        // Executable JAR file without the Profile attribute
+        if (thisProfile <= 3) {
+            createJarFile("Main.jar",
+                          "Main-Class=Main,Class-Path=Logging.jar",
+                          "Main.class");
+            checkRunFail("Main.jar");
+        }
+
+        // Executable JAR file with Profile attribute, Library JAR file without
+        for (int p=1; p<=3; p++) {
+            String attrs = "Main-Class=Main,Class-Path=Logging.jar" +
+                 ",Profile=compact" + p;
+            createJarFile("Main.jar", attrs,  "Main.class");
+            if (p <= thisProfile) {
+                checkRun("Main.jar");
+            } else {
+                checkRunFail("Main.jar");
+            }
+        }
+
+        // Executable JAR file with Profile attribute that has invalid profile
+        // name, including incorrect case.
+        createJarFile("Main.jar",
+                      "Main-Class=Main,Class-Path=Logging.jar,Profile=BadName",
+                      "Main.class");
+        checkRunFail("Main.jar");
+
+        createJarFile("Main.jar",
+                      "Main-Class=Main,Class-Path=Logging.jar,Profile=Compact1",
+                      "Main.class");
+        checkRunFail("Main.jar");
+
+        // Executable JAR file and Librrary JAR file with Profile attribute
+        createJarFile("Main.jar",
+                      "Main-Class=Main,Class-Path=Logging.jar,Profile=compact1",
+                      "Main.class");
+        for (int p=1; p<=3; p++) {
+            String attrs = "Profile=compact" + p;
+            createJarFile("Logging.jar", attrs, "Logging.class");
+            if (p <= thisProfile) {
+                checkRun("Main.jar");
+            } else {
+                checkRunFail("Main.jar");
+            }
+        }
+
+        // Executable JAR file and Library JAR with Profile attribute, value
+        // of Profile not recognized
+        createJarFile("Logging.jar", "Profile=BadName", "Logging.class");
+        createJarFile("Main.jar",
+                      "Main-Class=Main,Class-Path=Logging.jar,Profile=compact1",
+                      "Main.class");
+        checkRunFail("Main.jar");
+
+        System.out.println("TEST PASSED.");
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/tools/launcher/profiles/Logging.java	Mon Jan 21 23:20:42 2013 -0500
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+public class Logging {
+    private Logging() { }
+
+    public static void log(String msg) {
+        System.out.println(msg);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/tools/launcher/profiles/Main.java	Mon Jan 21 23:20:42 2013 -0500
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+public class Main {
+    private Main() { }
+
+    public static void main(String[] args) {
+        Logging.log("main running");
+    }
+}