8003255: (profiles) Update JAR file specification to support profiles
Reviewed-by: dholmes, mchung, ksrini
--- a/jdk/src/share/classes/java/net/URLClassLoader.java Mon Jan 21 23:17:58 2013 -0500
+++ b/jdk/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/jdk/src/share/classes/java/util/jar/Attributes.java Mon Jan 21 23:17:58 2013 -0500
+++ b/jdk/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/jdk/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/jdk/src/share/classes/sun/launcher/LauncherHelper.java Mon Jan 21 23:17:58 2013 -0500
+++ b/jdk/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/jdk/src/share/classes/sun/launcher/resources/launcher.properties Mon Jan 21 23:17:58 2013 -0500
+++ b/jdk/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/jdk/src/share/classes/sun/misc/URLClassPath.java Mon Jan 21 23:17:58 2013 -0500
+++ b/jdk/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/jdk/src/share/classes/sun/tools/jar/Main.java Mon Jan 21 23:17:58 2013 -0500
+++ b/jdk/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/jdk/src/share/classes/sun/tools/jar/resources/jar.properties Mon Jan 21 23:17:58 2013 -0500
+++ b/jdk/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/jdk/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/jdk/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/jdk/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/jdk/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/jdk/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/jdk/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/jdk/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");
+ }
+}