# HG changeset patch # User alanb # Date 1358828442 18000 # Node ID 793a36de151da6495116f87708a9364cecb7c8ea # Parent 8ca785029fe23719e8a1b8a9c8248ad74bc6c6b6 8003255: (profiles) Update JAR file specification to support profiles Reviewed-by: dholmes, mchung, ksrini diff -r 8ca785029fe2 -r 793a36de151d jdk/src/share/classes/java/net/URLClassLoader.java --- 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 @@ *

* The classes that are loaded are by default granted permission only to * access the URLs specified when the URLClassLoader was created. + *

+ * 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 diff -r 8ca785029fe2 -r 793a36de151d jdk/src/share/classes/java/util/jar/Attributes.java --- 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"); + + /** * Name object for Sealed manifest attribute * used for sealing. * @see diff -r 8ca785029fe2 -r 793a36de151d jdk/src/share/classes/java/util/jar/UnsupportedProfileException.java --- /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); + } +} diff -r 8ca785029fe2 -r 793a36de151d jdk/src/share/classes/sun/launcher/LauncherHelper.java --- 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); diff -r 8ca785029fe2 -r 793a36de151d jdk/src/share/classes/sun/launcher/resources/launcher.properties --- 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\ diff -r 8ca785029fe2 -r 793a36de151d jdk/src/share/classes/sun/misc/URLClassPath.java --- 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) { diff -r 8ca785029fe2 -r 793a36de151d jdk/src/share/classes/sun/tools/jar/Main.java --- 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(); diff -r 8ca785029fe2 -r 793a36de151d jdk/src/share/classes/sun/tools/jar/resources/jar.properties --- 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\ diff -r 8ca785029fe2 -r 793a36de151d jdk/test/java/net/URLClassLoader/profiles/Basic.java --- /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 "); + 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"); + } + } +} + diff -r 8ca785029fe2 -r 793a36de151d jdk/test/java/net/URLClassLoader/profiles/Lib.java --- /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() { } +} diff -r 8ca785029fe2 -r 793a36de151d jdk/test/java/net/URLClassLoader/profiles/basic.sh --- /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 + diff -r 8ca785029fe2 -r 793a36de151d jdk/test/tools/jar/AddAndUpdateProfile.java --- /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); + } + } + +} diff -r 8ca785029fe2 -r 793a36de151d jdk/test/tools/launcher/profiles/Basic.java --- /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."); + } + +} diff -r 8ca785029fe2 -r 793a36de151d jdk/test/tools/launcher/profiles/Logging.java --- /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); + } +} diff -r 8ca785029fe2 -r 793a36de151d jdk/test/tools/launcher/profiles/Main.java --- /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"); + } +}