The classes {@link java.lang.invoke.MethodHandle MethodHandle} and
* {@link java.lang.invoke.VarHandle VarHandle} contain
* signature polymorphic methods
* which can be linked regardless of their type descriptor.
@@ -190,7 +190,7 @@
* invoked with just the parameter types of static arguments, thereby supporting a wider
* range of methods compatible with the static arguments (such as methods that don't declare
* or require the lookup, name, and type meta-data parameters).
- * For example, for dynamically-computed call site, a the first argument
+ *
For example, for dynamically-computed call site, the first argument
* could be {@code Object} instead of {@code MethodHandles.Lookup}, and the return type
* could also be {@code Object} instead of {@code CallSite}.
* (Note that the types and number of the stacked arguments limit
diff --git a/src/java.base/share/classes/java/text/CompactNumberFormat.java b/src/java.base/share/classes/java/text/CompactNumberFormat.java
index a55f097a8a7..e9f1c0bd909 100644
--- a/src/java.base/share/classes/java/text/CompactNumberFormat.java
+++ b/src/java.base/share/classes/java/text/CompactNumberFormat.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2018, 2020, 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
@@ -206,7 +206,7 @@ import java.util.stream.Stream;
* {@link java.math.RoundingMode} for formatting. By default, it uses
* {@link java.math.RoundingMode#HALF_EVEN RoundingMode.HALF_EVEN}.
*
- * @see CompactNumberFormat.Style
+ * @see NumberFormat.Style
* @see NumberFormat
* @see DecimalFormat
* @since 12
diff --git a/src/java.base/share/classes/java/util/zip/ZipCoder.java b/src/java.base/share/classes/java/util/zip/ZipCoder.java
index 204f786c4b5..2e74d0f062e 100644
--- a/src/java.base/share/classes/java/util/zip/ZipCoder.java
+++ b/src/java.base/share/classes/java/util/zip/ZipCoder.java
@@ -32,6 +32,7 @@ import java.nio.charset.CharsetDecoder;
import java.nio.charset.CharsetEncoder;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.CodingErrorAction;
+import java.util.Arrays;
import sun.nio.cs.UTF_8;
@@ -43,51 +44,13 @@ class ZipCoder {
private static final jdk.internal.access.JavaLangAccess JLA =
jdk.internal.access.SharedSecrets.getJavaLangAccess();
- static final class UTF8ZipCoder extends ZipCoder {
-
- // Encoding/decoding is stateless, so make it singleton.
- static final ZipCoder INSTANCE = new UTF8ZipCoder(UTF_8.INSTANCE);
-
- private UTF8ZipCoder(Charset utf8) {
- super(utf8);
- }
-
- @Override
- boolean isUTF8() {
- return true;
- }
-
- @Override
- String toString(byte[] ba, int off, int length) {
- return JLA.newStringUTF8NoRepl(ba, off, length);
- }
-
- @Override
- byte[] getBytes(String s) {
- return JLA.getBytesUTF8NoRepl(s);
- }
-
- @Override
- int hashN(byte[] a, int off, int len) {
- // Performance optimization: when UTF8-encoded, ZipFile.getEntryPos
- // assume that the hash of a name remains unchanged when appending a
- // trailing '/', which allows lookups to avoid rehashing
- int end = off + len;
- if (len > 0 && a[end - 1] == '/') {
- end--;
- }
-
- int h = 1;
- for (int i = off; i < end; i++) {
- h = 31 * h + a[i];
- }
- return h;
- }
- }
+ // Encoding/decoding is stateless, so make it singleton.
+ static final UTF8ZipCoder UTF8 = new UTF8ZipCoder(UTF_8.INSTANCE);
public static ZipCoder get(Charset charset) {
- if (charset == UTF_8.INSTANCE)
- return UTF8ZipCoder.INSTANCE;
+ if (charset == UTF_8.INSTANCE) {
+ return UTF8;
+ }
return new ZipCoder(charset);
}
@@ -123,40 +86,74 @@ class ZipCoder {
}
}
- // assume invoked only if "this" is not utf8
- byte[] getBytesUTF8(String s) {
- return UTF8ZipCoder.INSTANCE.getBytes(s);
- }
-
String toStringUTF8(byte[] ba, int len) {
- return UTF8ZipCoder.INSTANCE.toString(ba, 0, len);
- }
-
- String toStringUTF8(byte[] ba, int off, int len) {
- return UTF8ZipCoder.INSTANCE.toString(ba, off, len);
+ return UTF8.toString(ba, 0, len);
}
boolean isUTF8() {
return false;
}
- int hashN(byte[] a, int off, int len) {
- int h = 1;
- while (len-- > 0) {
- h = 31 * h + a[off++];
+ // Hash code functions for ZipFile entry names. We generate the hash as-if
+ // we first decoded the byte sequence to a String, then appended '/' if no
+ // trailing slash was found, then called String.hashCode(). This
+ // normalization ensures we can simplify and speed up lookups.
+ int normalizedHash(byte[] a, int off, int len) {
+ if (len == 0) {
+ return 0;
+ }
+ return normalizedHashDecode(0, a, off, off + len);
+ }
+
+ // Matching normalized hash code function for Strings
+ static int normalizedHash(String name) {
+ int hsh = name.hashCode();
+ int len = name.length();
+ if (len > 0 && name.charAt(len - 1) != '/') {
+ hsh = hsh * 31 + '/';
+ }
+ return hsh;
+ }
+
+ boolean hasTrailingSlash(byte[] a, int end) {
+ byte[] slashBytes = slashBytes();
+ return end >= slashBytes.length &&
+ Arrays.mismatch(a, end - slashBytes.length, end, slashBytes, 0, slashBytes.length) == -1;
+ }
+
+ // Implements normalizedHash by decoding byte[] to char[] and then computing
+ // the hash. This is a slow-path used for non-UTF8 charsets and also when
+ // aborting the ASCII fast-path in the UTF8 implementation, so {@code h}
+ // might be a partially calculated hash code
+ int normalizedHashDecode(int h, byte[] a, int off, int end) {
+ try {
+ // cb will be a newly allocated CharBuffer with pos == 0,
+ // arrayOffset == 0, backed by an array.
+ CharBuffer cb = decoder().decode(ByteBuffer.wrap(a, off, end - off));
+ int limit = cb.limit();
+ char[] decoded = cb.array();
+ for (int i = 0; i < limit; i++) {
+ h = 31 * h + decoded[i];
+ }
+ if (limit > 0 && decoded[limit - 1] != '/') {
+ h = 31 * h + '/';
+ }
+ } catch (CharacterCodingException cce) {
+ // Ignore - return the hash code generated so far.
}
return h;
}
- private Charset cs;
- private CharsetDecoder dec;
+ private byte[] slashBytes;
+ private final Charset cs;
+ protected CharsetDecoder dec;
private CharsetEncoder enc;
private ZipCoder(Charset cs) {
this.cs = cs;
}
- private CharsetDecoder decoder() {
+ protected CharsetDecoder decoder() {
if (dec == null) {
dec = cs.newDecoder()
.onMalformedInput(CodingErrorAction.REPORT)
@@ -173,4 +170,73 @@ class ZipCoder {
}
return enc;
}
+
+ // This method produces an array with the bytes that will correspond to a
+ // trailing '/' in the chosen character encoding.
+ //
+ // While in most charsets a trailing slash will be encoded as the byte
+ // value of '/', this does not hold in the general case. E.g., in charsets
+ // such as UTF-16 and UTF-32 it will be represented by a sequence of 2 or 4
+ // bytes, respectively.
+ private byte[] slashBytes() {
+ if (slashBytes == null) {
+ // Take into account charsets that produce a BOM, e.g., UTF-16
+ byte[] slash = "/".getBytes(cs);
+ byte[] doubleSlash = "//".getBytes(cs);
+ slashBytes = Arrays.copyOfRange(doubleSlash, slash.length, doubleSlash.length);
+ }
+ return slashBytes;
+ }
+
+ static final class UTF8ZipCoder extends ZipCoder {
+
+ private UTF8ZipCoder(Charset utf8) {
+ super(utf8);
+ }
+
+ @Override
+ boolean isUTF8() {
+ return true;
+ }
+
+ @Override
+ String toString(byte[] ba, int off, int length) {
+ return JLA.newStringUTF8NoRepl(ba, off, length);
+ }
+
+ @Override
+ byte[] getBytes(String s) {
+ return JLA.getBytesUTF8NoRepl(s);
+ }
+
+ @Override
+ int normalizedHash(byte[] a, int off, int len) {
+ if (len == 0) {
+ return 0;
+ }
+
+ int end = off + len;
+ int h = 0;
+ while (off < end) {
+ byte b = a[off];
+ if (b < 0) {
+ // Non-ASCII, fall back to decoder loop
+ return normalizedHashDecode(h, a, off, end);
+ } else {
+ h = 31 * h + b;
+ off++;
+ }
+ }
+
+ if (a[end - 1] != '/') {
+ h = 31 * h + '/';
+ }
+ return h;
+ }
+
+ @Override
+ boolean hasTrailingSlash(byte[] a, int end) {
+ return end > 0 && a[end - 1] == '/';
+ }
+ }
}
diff --git a/src/java.base/share/classes/java/util/zip/ZipFile.java b/src/java.base/share/classes/java/util/zip/ZipFile.java
index a15407a1626..bb05e19454d 100644
--- a/src/java.base/share/classes/java/util/zip/ZipFile.java
+++ b/src/java.base/share/classes/java/util/zip/ZipFile.java
@@ -92,7 +92,6 @@ public class ZipFile implements ZipConstants, Closeable {
private final String name; // zip file name
private volatile boolean closeRequested;
- private final @Stable ZipCoder zc;
// The "resource" used by this zip file that needs to be
// cleaned after use.
@@ -232,11 +231,10 @@ public class ZipFile implements ZipConstants, Closeable {
}
Objects.requireNonNull(charset, "charset");
- this.zc = ZipCoder.get(charset);
this.name = name;
long t0 = System.nanoTime();
- this.res = new CleanableResource(this, file, mode);
+ this.res = new CleanableResource(this, ZipCoder.get(charset), file, mode);
PerfCounter.getZipFileOpenTime().addElapsedTimeFrom(t0);
PerfCounter.getZipFileCount().increment();
@@ -307,7 +305,7 @@ public class ZipFile implements ZipConstants, Closeable {
if (res.zsrc.comment == null) {
return null;
}
- return zc.toString(res.zsrc.comment);
+ return res.zsrc.zc.toString(res.zsrc.comment);
}
}
@@ -338,18 +336,9 @@ public class ZipFile implements ZipConstants, Closeable {
ZipEntry entry = null;
synchronized (this) {
ensureOpen();
- byte[] bname = zc.getBytes(name);
- int pos = res.zsrc.getEntryPos(bname, true);
+ int pos = res.zsrc.getEntryPos(name, true);
if (pos != -1) {
- entry = getZipEntry(name, bname, pos, func);
- } else if (!zc.isUTF8() && !name.isEmpty() && !name.endsWith("/")) {
- // non-UTF-8 charsets need to lookup again with added slash
- name = name + '/';
- bname = zc.getBytes(name);
- pos = res.zsrc.getEntryPos(bname, false);
- if (pos != -1) {
- entry = getZipEntry(name, bname, pos, func);
- }
+ entry = getZipEntry(name, pos, func);
}
}
return entry;
@@ -371,7 +360,7 @@ public class ZipFile implements ZipConstants, Closeable {
*/
public InputStream getInputStream(ZipEntry entry) throws IOException {
Objects.requireNonNull(entry, "entry");
- int pos = -1;
+ int pos;
ZipFileInputStream in;
Source zsrc = res.zsrc;
Set istreams = res.istreams;
@@ -379,10 +368,8 @@ public class ZipFile implements ZipConstants, Closeable {
ensureOpen();
if (Objects.equals(lastEntryName, entry.name)) {
pos = lastEntryPos;
- } else if (!zc.isUTF8() && (entry.flag & USE_UTF8) != 0) {
- pos = zsrc.getEntryPos(zc.getBytesUTF8(entry.name), false);
} else {
- pos = zsrc.getEntryPos(zc.getBytes(entry.name), false);
+ pos = zsrc.getEntryPos(entry.name, false);
}
if (pos == -1) {
return null;
@@ -528,7 +515,7 @@ public class ZipFile implements ZipConstants, Closeable {
throw new NoSuchElementException();
}
// each "entry" has 3 ints in table entries
- return (T)getZipEntry(null, null, res.zsrc.getEntryPos(i++ * 3), gen);
+ return (T)getZipEntry(null, res.zsrc.getEntryPos(i++ * 3), gen);
}
}
@@ -600,18 +587,15 @@ public class ZipFile implements ZipConstants, Closeable {
synchronized (this) {
ensureOpen();
return StreamSupport.stream(new EntrySpliterator<>(0, res.zsrc.total,
- pos -> getZipEntry(null, null, pos, ZipEntry::new)), false);
+ pos -> getZipEntry(null, pos, ZipEntry::new)), false);
}
}
private String getEntryName(int pos) {
byte[] cen = res.zsrc.cen;
int nlen = CENNAM(cen, pos);
- if (!zc.isUTF8() && (CENFLG(cen, pos) & USE_UTF8) != 0) {
- return zc.toStringUTF8(cen, pos + CENHDR, nlen);
- } else {
- return zc.toString(cen, pos + CENHDR, nlen);
- }
+ ZipCoder zc = res.zsrc.zipCoderForPos(pos);
+ return zc.toString(cen, pos + CENHDR, nlen);
}
/*
@@ -647,34 +631,37 @@ public class ZipFile implements ZipConstants, Closeable {
synchronized (this) {
ensureOpen();
return StreamSupport.stream(new EntrySpliterator<>(0, res.zsrc.total,
- pos -> (JarEntry)getZipEntry(null, null, pos, func)), false);
+ pos -> (JarEntry)getZipEntry(null, pos, func)), false);
}
}
private String lastEntryName;
private int lastEntryPos;
- /* Checks ensureOpen() before invoke this method */
- private ZipEntry getZipEntry(String name, byte[] bname, int pos,
+ /* Check ensureOpen() before invoking this method */
+ private ZipEntry getZipEntry(String name, int pos,
Function func) {
byte[] cen = res.zsrc.cen;
int nlen = CENNAM(cen, pos);
int elen = CENEXT(cen, pos);
int clen = CENCOM(cen, pos);
- int flag = CENFLG(cen, pos);
- if (name == null || bname.length != nlen) {
- // to use the entry name stored in cen, if the passed in name is
- // (1) null, invoked from iterator, or
- // (2) not equal to the name stored, a slash is appended during
- // getEntryPos() search.
- if (!zc.isUTF8() && (flag & USE_UTF8) != 0) {
- name = zc.toStringUTF8(cen, pos + CENHDR, nlen);
- } else {
- name = zc.toString(cen, pos + CENHDR, nlen);
+
+ ZipCoder zc = res.zsrc.zipCoderForPos(pos);
+ if (name != null) {
+ // only need to check for mismatch of trailing slash
+ if (nlen > 0 &&
+ !name.isEmpty() &&
+ zc.hasTrailingSlash(cen, pos + CENHDR + nlen) &&
+ !name.endsWith("/"))
+ {
+ name += '/';
}
+ } else {
+ // invoked from iterator, use the entry name stored in cen
+ name = zc.toString(cen, pos + CENHDR, nlen);
}
ZipEntry e = func.apply(name); //ZipEntry e = new ZipEntry(name);
- e.flag = flag;
+ e.flag = CENFLG(cen, pos);
e.xdostime = CENTIM(cen, pos);
e.crc = CENCRC(cen, pos);
e.size = CENLEN(cen, pos);
@@ -686,11 +673,7 @@ public class ZipFile implements ZipConstants, Closeable {
}
if (clen != 0) {
int start = pos + CENHDR + nlen + elen;
- if (!zc.isUTF8() && (flag & USE_UTF8) != 0) {
- e.comment = zc.toStringUTF8(cen, start, clen);
- } else {
- e.comment = zc.toString(cen, start, clen);
- }
+ e.comment = zc.toString(cen, start, clen);
}
lastEntryName = e.name;
lastEntryPos = pos;
@@ -721,11 +704,11 @@ public class ZipFile implements ZipConstants, Closeable {
Source zsrc;
- CleanableResource(ZipFile zf, File file, int mode) throws IOException {
+ CleanableResource(ZipFile zf, ZipCoder zc, File file, int mode) throws IOException {
this.cleanable = CleanerFactory.cleaner().register(zf, this);
this.istreams = Collections.newSetFromMap(new WeakHashMap<>());
this.inflaterCache = new ArrayDeque<>();
- this.zsrc = Source.get(file, (mode & OPEN_DELETE) != 0, zf.zc);
+ this.zsrc = Source.get(file, (mode & OPEN_DELETE) != 0, zc);
}
void clean() {
@@ -1113,7 +1096,7 @@ public class ZipFile implements ZipConstants, Closeable {
private static final int[] EMPTY_META_VERSIONS = new int[0];
private final Key key; // the key in files
- private final ZipCoder zc; // zip coder used to decode/encode
+ private final @Stable ZipCoder zc; // zip coder used to decode/encode
private int refs = 1;
@@ -1412,8 +1395,6 @@ public class ZipFile implements ZipConstants, Closeable {
private void initCEN(int knownTotal) throws IOException {
// Prefer locals for better performance during startup
byte[] cen;
- ZipCoder zc = this.zc;
-
if (knownTotal == -1) {
End end = findEND();
if (end.endpos == 0) {
@@ -1488,7 +1469,7 @@ public class ZipFile implements ZipConstants, Closeable {
if (entryPos + nlen > limit)
zerror("invalid CEN header (bad header size)");
// Record the CEN offset and the name hash in our hash cell.
- hash = zc.hashN(cen, entryPos, nlen);
+ hash = zipCoderForPos(pos).normalizedHash(cen, entryPos, nlen);
hsh = (hash & 0x7fffffff) % tablelen;
next = table[hsh];
table[hsh] = idx;
@@ -1544,11 +1525,12 @@ public class ZipFile implements ZipConstants, Closeable {
* Returns the {@code pos} of the zip cen entry corresponding to the
* specified entry name, or -1 if not found.
*/
- private int getEntryPos(byte[] name, boolean addSlash) {
+ private int getEntryPos(String name, boolean addSlash) {
if (total == 0) {
return -1;
}
- int hsh = zc.hashN(name, 0, name.length);
+
+ int hsh = ZipCoder.normalizedHash(name);
int idx = table[(hsh & 0x7fffffff) % tablelen];
// Search down the target hash chain for a entry whose
@@ -1557,31 +1539,25 @@ public class ZipFile implements ZipConstants, Closeable {
if (getEntryHash(idx) == hsh) {
// The CEN name must match the specfied one
int pos = getEntryPos(idx);
- byte[] cen = this.cen;
- final int nlen = CENNAM(cen, pos);
- int nameoff = pos + CENHDR;
- // If addSlash is true and we're using the UTF-8 zip coder,
- // we'll directly test for name+/ in addition to name,
- // unless name is the empty string or already ends with a
- // slash
- if (name.length == nlen ||
- (addSlash &&
- zc.isUTF8() &&
- name.length > 0 &&
- name.length + 1 == nlen &&
- cen[nameoff + nlen - 1] == '/' &&
- name[name.length - 1] != '/')) {
- boolean matched = true;
- for (int i = 0; i < name.length; i++) {
- if (name[i] != cen[nameoff++]) {
- matched = false;
- break;
- }
- }
- if (matched) {
- return pos;
+ try {
+ ZipCoder zc = zipCoderForPos(pos);
+ String entry = zc.toString(cen, pos + CENHDR, CENNAM(cen, pos));
+
+ // If addSlash is true we'll test for name+/ in addition to
+ // name, unless name is the empty string or already ends with a
+ // slash
+ int entryLen = entry.length();
+ int nameLen = name.length();
+ if ((entryLen == nameLen && entry.equals(name)) ||
+ (addSlash &&
+ nameLen + 1 == entryLen &&
+ entry.startsWith(name) &&
+ entry.charAt(entryLen - 1) == '/')) {
+ return pos;
}
+ } catch (IllegalArgumentException iae) {
+ // Ignore
}
}
idx = getEntryNext(idx);
@@ -1589,6 +1565,16 @@ public class ZipFile implements ZipConstants, Closeable {
return -1;
}
+ private ZipCoder zipCoderForPos(int pos) {
+ if (zc.isUTF8()) {
+ return zc;
+ }
+ if ((CENFLG(cen, pos) & USE_UTF8) != 0) {
+ return ZipCoder.UTF8;
+ }
+ return zc;
+ }
+
/**
* Returns true if the bytes represent a non-directory name
* beginning with "META-INF/", disregarding ASCII case.
diff --git a/src/java.base/share/classes/jdk/internal/loader/ClassLoaders.java b/src/java.base/share/classes/jdk/internal/loader/ClassLoaders.java
index 27cd71359f7..f87d24f8dd5 100644
--- a/src/java.base/share/classes/jdk/internal/loader/ClassLoaders.java
+++ b/src/java.base/share/classes/jdk/internal/loader/ClassLoaders.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2015, 2019, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2015, 2020, 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
@@ -132,16 +132,6 @@ public class ClassLoaders {
PlatformClassLoader(BootClassLoader parent) {
super("platform", parent, null);
}
-
- /**
- * Called by the VM to support define package for AppCDS.
- *
- * Shared classes are returned in ClassLoader::findLoadedClass
- * that bypass the defineClass call.
- */
- private Package definePackage(String pn, Module module) {
- return JLA.definePackage(this, pn, module);
- }
}
/**
@@ -194,16 +184,6 @@ public class ClassLoaders {
ucp.addFile(path);
}
- /**
- * Called by the VM to support define package for AppCDS
- *
- * Shared classes are returned in ClassLoader::findLoadedClass
- * that bypass the defineClass call.
- */
- private Package definePackage(String pn, Module module) {
- return JLA.definePackage(this, pn, module);
- }
-
/**
* Called by the VM to support define package for AppCDS
*/
diff --git a/src/java.base/share/classes/jdk/internal/module/ModuleHashes.java b/src/java.base/share/classes/jdk/internal/module/ModuleHashes.java
index efc3d102491..5e6dcb367f1 100644
--- a/src/java.base/share/classes/jdk/internal/module/ModuleHashes.java
+++ b/src/java.base/share/classes/jdk/internal/module/ModuleHashes.java
@@ -26,18 +26,21 @@
package jdk.internal.module;
import java.io.IOException;
+import java.io.InputStream;
import java.io.UncheckedIOException;
-import java.nio.ByteBuffer;
-import java.nio.channels.FileChannel;
-import java.nio.file.Path;
+import java.lang.module.ModuleReader;
+import java.lang.module.ModuleReference;
+import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
+import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
-import java.util.TreeMap;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
+import java.util.TreeMap;
+import java.util.function.Supplier;
/**
* The result of hashing the contents of a number of module artifacts.
@@ -61,8 +64,8 @@ public final class ModuleHashes {
* @param algorithm the algorithm used to create the hashes
* @param nameToHash the map of module name to hash value
*/
- public ModuleHashes(String algorithm, Map nameToHash) {
- this.algorithm = algorithm;
+ ModuleHashes(String algorithm, Map nameToHash) {
+ this.algorithm = Objects.requireNonNull(algorithm);
this.nameToHash = Collections.unmodifiableMap(nameToHash);
}
@@ -96,54 +99,125 @@ public final class ModuleHashes {
}
/**
- * Computes the hash for the given file with the given message digest
- * algorithm.
+ * Computes a hash from the names and content of a module.
*
+ * @param reader the module reader to access the module content
+ * @param algorithm the name of the message digest algorithm to use
+ * @return the hash
+ * @throws IllegalArgumentException if digest algorithm is not supported
* @throws UncheckedIOException if an I/O error occurs
- * @throws RuntimeException if the algorithm is not available
*/
- public static byte[] computeHash(Path file, String algorithm) {
+ private static byte[] computeHash(ModuleReader reader, String algorithm) {
+ MessageDigest md;
try {
- MessageDigest md = MessageDigest.getInstance(algorithm);
-
- // Ideally we would just mmap the file but this consumes too much
- // memory when jlink is running concurrently on very large jmods
- try (FileChannel fc = FileChannel.open(file)) {
- ByteBuffer bb = ByteBuffer.allocate(32*1024);
- while (fc.read(bb) > 0) {
- bb.flip();
- md.update(bb);
- assert bb.remaining() == 0;
- bb.clear();
- }
- }
-
- return md.digest();
+ md = MessageDigest.getInstance(algorithm);
} catch (NoSuchAlgorithmException e) {
- throw new RuntimeException(e);
+ throw new IllegalArgumentException(e);
+ }
+ try {
+ byte[] buf = new byte[32*1024];
+ reader.list().sorted().forEach(rn -> {
+ md.update(rn.getBytes(StandardCharsets.UTF_8));
+ try (InputStream in = reader.open(rn).orElseThrow()) {
+ int n;
+ while ((n = in.read(buf)) > 0) {
+ md.update(buf, 0, n);
+ }
+ } catch (IOException ioe) {
+ throw new UncheckedIOException(ioe);
+ }
+ });
+ } catch (IOException ioe) {
+ throw new UncheckedIOException(ioe);
+ }
+ return md.digest();
+ }
+
+ /**
+ * Computes a hash from the names and content of a module.
+ *
+ * @param supplier supplies the module reader to access the module content
+ * @param algorithm the name of the message digest algorithm to use
+ * @return the hash
+ * @throws IllegalArgumentException if digest algorithm is not supported
+ * @throws UncheckedIOException if an I/O error occurs
+ */
+ static byte[] computeHash(Supplier supplier, String algorithm) {
+ try (ModuleReader reader = supplier.get()) {
+ return computeHash(reader, algorithm);
} catch (IOException ioe) {
throw new UncheckedIOException(ioe);
}
}
/**
- * Computes the hash for every entry in the given map, returning a
- * {@code ModuleHashes} to encapsulate the result. The map key is
- * the entry name, typically the module name. The map value is the file
- * path to the entry (module artifact).
+ * Computes the hash from the names and content of a set of modules. Returns
+ * a {@code ModuleHashes} to encapsulate the result.
*
+ * @param mrefs the set of modules
+ * @param algorithm the name of the message digest algorithm to use
* @return ModuleHashes that encapsulates the hashes
+ * @throws IllegalArgumentException if digest algorithm is not supported
+ * @throws UncheckedIOException if an I/O error occurs
*/
- public static ModuleHashes generate(Map map, String algorithm) {
+ static ModuleHashes generate(Set mrefs, String algorithm) {
Map nameToHash = new TreeMap<>();
- for (Map.Entry entry: map.entrySet()) {
- String name = entry.getKey();
- Path path = entry.getValue();
- nameToHash.put(name, computeHash(path, algorithm));
+ for (ModuleReference mref : mrefs) {
+ try (ModuleReader reader = mref.open()) {
+ byte[] hash = computeHash(reader, algorithm);
+ nameToHash.put(mref.descriptor().name(), hash);
+ } catch (IOException ioe) {
+ throw new UncheckedIOException(ioe);
+ }
}
return new ModuleHashes(algorithm, nameToHash);
}
+ @Override
+ public int hashCode() {
+ int h = algorithm.hashCode();
+ for (Map.Entry e : nameToHash.entrySet()) {
+ h = h * 31 + e.getKey().hashCode();
+ h = h * 31 + Arrays.hashCode(e.getValue());
+ }
+ return h;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof ModuleHashes))
+ return false;
+ ModuleHashes other = (ModuleHashes) obj;
+ if (!algorithm.equals(other.algorithm)
+ || nameToHash.size() != other.nameToHash.size())
+ return false;
+ for (Map.Entry e : nameToHash.entrySet()) {
+ String name = e.getKey();
+ byte[] hash = e.getValue();
+ if (!Arrays.equals(hash, other.nameToHash.get(name)))
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder(algorithm);
+ sb.append(" ");
+ nameToHash.entrySet()
+ .stream()
+ .sorted(Map.Entry.comparingByKey())
+ .forEach(e -> {
+ sb.append(e.getKey());
+ sb.append("=");
+ byte[] ba = e.getValue();
+ for (byte b : ba) {
+ sb.append(String.format("%02x", b & 0xff));
+ }
+ });
+ return sb.toString();
+ }
+
/**
* This is used by jdk.internal.module.SystemModules class
* generated at link time.
diff --git a/src/java.base/share/classes/jdk/internal/module/ModuleHashesBuilder.java b/src/java.base/share/classes/jdk/internal/module/ModuleHashesBuilder.java
index 9b6f497fff9..cebca6fb360 100644
--- a/src/java.base/share/classes/jdk/internal/module/ModuleHashesBuilder.java
+++ b/src/java.base/share/classes/jdk/internal/module/ModuleHashesBuilder.java
@@ -27,9 +27,8 @@ package jdk.internal.module;
import java.io.PrintStream;
import java.lang.module.Configuration;
+import java.lang.module.ModuleReference;
import java.lang.module.ResolvedModule;
-import java.net.URI;
-import java.nio.file.Path;
import java.util.ArrayDeque;
import java.util.Collections;
import java.util.Deque;
@@ -39,7 +38,6 @@ import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.function.Consumer;
-import java.util.function.Function;
import java.util.stream.Stream;
import static java.util.stream.Collectors.*;
@@ -114,27 +112,17 @@ public class ModuleHashesBuilder {
mods.addAll(ns);
if (!ns.isEmpty()) {
- Map moduleToPath = ns.stream()
- .collect(toMap(Function.identity(), this::moduleToPath));
- hashes.put(mn, ModuleHashes.generate(moduleToPath, "SHA-256"));
+ Set mrefs = ns.stream()
+ .map(name -> configuration.findModule(name)
+ .orElseThrow(InternalError::new))
+ .map(ResolvedModule::reference)
+ .collect(toSet());
+ hashes.put(mn, ModuleHashes.generate(mrefs, "SHA-256"));
}
});
return hashes;
}
- private Path moduleToPath(String name) {
- ResolvedModule rm = configuration.findModule(name).orElseThrow(
- () -> new InternalError("Selected module " + name + " not on module path"));
-
- URI uri = rm.reference().location().get();
- Path path = Path.of(uri);
- String fn = path.getFileName().toString();
- if (!fn.endsWith(".jar") && !fn.endsWith(".jmod")) {
- throw new UnsupportedOperationException(path + " is not a modular JAR or jmod file");
- }
- return path;
- }
-
/*
* Utility class
*/
diff --git a/src/java.base/share/classes/jdk/internal/module/ModuleInfoExtender.java b/src/java.base/share/classes/jdk/internal/module/ModuleInfoExtender.java
index 7262dbbea16..7015194169b 100644
--- a/src/java.base/share/classes/jdk/internal/module/ModuleInfoExtender.java
+++ b/src/java.base/share/classes/jdk/internal/module/ModuleInfoExtender.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2014, 2018, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2014, 2020, 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
@@ -167,7 +167,9 @@ public final class ModuleInfoExtender {
// ModulePackages attribute
if (packages != null) {
- packages.forEach(pn -> mv.visitPackage(pn.replace('.', '/')));
+ packages.stream()
+ .sorted()
+ .forEach(pn -> mv.visitPackage(pn.replace('.', '/')));
}
return new ModuleVisitor(Opcodes.ASM7, mv) {
diff --git a/src/java.base/share/classes/jdk/internal/module/ModuleReferences.java b/src/java.base/share/classes/jdk/internal/module/ModuleReferences.java
index 92243c6a26e..3e30e8ee53d 100644
--- a/src/java.base/share/classes/jdk/internal/module/ModuleReferences.java
+++ b/src/java.base/share/classes/jdk/internal/module/ModuleReferences.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2015, 2016, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2015, 2020, 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
@@ -93,7 +93,7 @@ class ModuleReferences {
Path file) {
URI uri = file.toUri();
Supplier supplier = () -> new JarModuleReader(file, uri);
- HashSupplier hasher = (a) -> ModuleHashes.computeHash(file, a);
+ HashSupplier hasher = (a) -> ModuleHashes.computeHash(supplier, a);
return newModule(attrs, uri, supplier, patcher, hasher);
}
@@ -103,7 +103,7 @@ class ModuleReferences {
static ModuleReference newJModModule(ModuleInfo.Attributes attrs, Path file) {
URI uri = file.toUri();
Supplier supplier = () -> new JModModuleReader(file, uri);
- HashSupplier hasher = (a) -> ModuleHashes.computeHash(file, a);
+ HashSupplier hasher = (a) -> ModuleHashes.computeHash(supplier, a);
return newModule(attrs, uri, supplier, null, hasher);
}
diff --git a/src/java.base/share/classes/sun/security/ssl/SSLSocketImpl.java b/src/java.base/share/classes/sun/security/ssl/SSLSocketImpl.java
index c7caed8b82e..f2b9a228d8b 100644
--- a/src/java.base/share/classes/sun/security/ssl/SSLSocketImpl.java
+++ b/src/java.base/share/classes/sun/security/ssl/SSLSocketImpl.java
@@ -619,6 +619,15 @@ public final class SSLSocketImpl
}
}
+ // Deliver the user_canceled alert and the close notify alert.
+ closeNotify(useUserCanceled);
+
+ if (!isInputShutdown()) {
+ bruteForceCloseInput(hasCloseReceipt);
+ }
+ }
+
+ void closeNotify(boolean useUserCanceled) throws IOException {
// Need a lock here so that the user_canceled alert and the
// close_notify alert can be delivered together.
int linger = getSoLinger();
@@ -633,7 +642,7 @@ public final class SSLSocketImpl
conContext.outputRecord.recordLock.tryLock(
linger, TimeUnit.SECONDS)) {
try {
- handleClosedNotifyAlert(useUserCanceled);
+ deliverClosedNotify(useUserCanceled);
} finally {
conContext.outputRecord.recordLock.unlock();
}
@@ -687,18 +696,14 @@ public final class SSLSocketImpl
} else {
conContext.outputRecord.recordLock.lock();
try {
- handleClosedNotifyAlert(useUserCanceled);
+ deliverClosedNotify(useUserCanceled);
} finally {
conContext.outputRecord.recordLock.unlock();
}
}
-
- if (!isInputShutdown()) {
- bruteForceCloseInput(hasCloseReceipt);
- }
}
- private void handleClosedNotifyAlert(
+ private void deliverClosedNotify(
boolean useUserCanceled) throws IOException {
try {
// send a user_canceled alert if needed.
diff --git a/src/java.base/share/classes/sun/security/ssl/TransportContext.java b/src/java.base/share/classes/sun/security/ssl/TransportContext.java
index 78bc99014ea..dc63b663ce6 100644
--- a/src/java.base/share/classes/sun/security/ssl/TransportContext.java
+++ b/src/java.base/share/classes/sun/security/ssl/TransportContext.java
@@ -238,7 +238,7 @@ final class TransportContext implements ConnectionContext {
(handshakeContext instanceof PostHandshakeContext);
}
- // Note: close_notify is delivered as a warning alert.
+ // Note: Don't use this method for close_nofity, use closeNotify() instead.
void warning(Alert alert) {
// For initial handshaking, don't send a warning alert message to peer
// if handshaker has not started.
@@ -254,6 +254,33 @@ final class TransportContext implements ConnectionContext {
}
}
+ // Note: close_notify is delivered as a warning alert.
+ void closeNotify(boolean isUserCanceled) throws IOException {
+ // Socket transport is special because of the SO_LINGER impact.
+ if (transport instanceof SSLSocketImpl) {
+ ((SSLSocketImpl)transport).closeNotify(isUserCanceled);
+ } else {
+ // Need a lock here so that the user_canceled alert and the
+ // close_notify alert can be delivered together.
+ outputRecord.recordLock.lock();
+ try {
+ try {
+ // send a user_canceled alert if needed.
+ if (isUserCanceled) {
+ warning(Alert.USER_CANCELED);
+ }
+
+ // send a close_notify alert
+ warning(Alert.CLOSE_NOTIFY);
+ } finally {
+ outputRecord.close();
+ }
+ } finally {
+ outputRecord.recordLock.unlock();
+ }
+ }
+ }
+
SSLException fatal(Alert alert,
String diagnostic) throws SSLException {
return fatal(alert, diagnostic, null);
@@ -501,17 +528,7 @@ final class TransportContext implements ConnectionContext {
}
if (needCloseNotify) {
- outputRecord.recordLock.lock();
- try {
- try {
- // send a close_notify alert
- warning(Alert.CLOSE_NOTIFY);
- } finally {
- outputRecord.close();
- }
- } finally {
- outputRecord.recordLock.unlock();
- }
+ closeNotify(false);
}
}
}
@@ -547,24 +564,7 @@ final class TransportContext implements ConnectionContext {
useUserCanceled = true;
}
- // Need a lock here so that the user_canceled alert and the
- // close_notify alert can be delivered together.
- outputRecord.recordLock.lock();
- try {
- try {
- // send a user_canceled alert if needed.
- if (useUserCanceled) {
- warning(Alert.USER_CANCELED);
- }
-
- // send a close_notify alert
- warning(Alert.CLOSE_NOTIFY);
- } finally {
- outputRecord.close();
- }
- } finally {
- outputRecord.recordLock.unlock();
- }
+ closeNotify(useUserCanceled);
}
// Note; HandshakeStatus.FINISHED status is retrieved in other places.
diff --git a/src/java.base/share/classes/sun/util/resources/TimeZoneNames.java b/src/java.base/share/classes/sun/util/resources/TimeZoneNames.java
index bd4e2c87c3a..298269012e7 100644
--- a/src/java.base/share/classes/sun/util/resources/TimeZoneNames.java
+++ b/src/java.base/share/classes/sun/util/resources/TimeZoneNames.java
@@ -272,6 +272,9 @@ public final class TimeZoneNames extends TimeZoneNamesBundle {
String VICTORIA[] = new String[] {"Australian Eastern Standard Time (Victoria)", "AEST",
"Australian Eastern Daylight Time (Victoria)", "AEDT",
"Australian Eastern Time (Victoria)", "AET"};
+ String WGT[] = new String[] {"Western Greenland Time", "WGT",
+ "Western Greenland Summer Time", "WGST",
+ "Western Greenland Time", "WGT"};
String UTC[] = new String[] {"Coordinated Universal Time", "UTC",
"Coordinated Universal Time", "UTC",
"Coordinated Universal Time", "UTC"};
@@ -432,7 +435,7 @@ public final class TimeZoneNames extends TimeZoneNamesBundle {
{"America/Cuiaba", AMT},
{"America/Curacao", AST},
{"America/Danmarkshavn", GMT},
- {"America/Dawson", PST},
+ {"America/Dawson", MST},
{"America/Dawson_Creek", MST},
{"America/Detroit", EST},
{"America/Dominica", AST},
@@ -444,9 +447,7 @@ public final class TimeZoneNames extends TimeZoneNamesBundle {
{"America/Fort_Wayne", EST},
{"America/Fortaleza", BRT},
{"America/Glace_Bay", AST},
- {"America/Godthab", new String[] {"Western Greenland Time", "WGT",
- "Western Greenland Summer Time", "WGST",
- "Western Greenland Time", "WGT"}},
+ {"America/Godthab", WGT},
{"America/Goose_Bay", AST},
{"America/Grand_Turk", EST},
{"America/Grenada", AST},
@@ -514,6 +515,7 @@ public final class TimeZoneNames extends TimeZoneNamesBundle {
{"America/North_Dakota/Beulah", CST},
{"America/North_Dakota/Center", CST},
{"America/North_Dakota/New_Salem", CST},
+ {"America/Nuuk", WGT},
{"America/Ojinaga", MST},
{"America/Panama", EST},
{"America/Pangnirtung", EST},
@@ -556,7 +558,7 @@ public final class TimeZoneNames extends TimeZoneNamesBundle {
{"America/Tortola", AST},
{"America/Vancouver", PST},
{"America/Virgin", AST},
- {"America/Whitehorse", PST},
+ {"America/Whitehorse", MST},
{"America/Winnipeg", CST},
{"America/Yakutat", AKST},
{"America/Yellowknife", MST},
@@ -798,7 +800,7 @@ public final class TimeZoneNames extends TimeZoneNamesBundle {
{"Canada/Mountain", MST},
{"Canada/Newfoundland", NST},
{"Canada/Pacific", PST},
- {"Canada/Yukon", PST},
+ {"Canada/Yukon", MST},
{"Canada/Saskatchewan", CST},
{"CAT", CAT},
{"CET", CET},
diff --git a/src/java.base/share/man/java.1 b/src/java.base/share/man/java.1
index e3ccec2093e..a78d5a3d580 100644
--- a/src/java.base/share/man/java.1
+++ b/src/java.base/share/man/java.1
@@ -1,5 +1,5 @@
.\"t
-.\" Copyright (c) 1994, 2019, Oracle and/or its affiliates. All rights reserved.
+.\" Copyright (c) 1994, 2020, 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
@@ -1810,7 +1810,7 @@ in the information collected.
.B \f[CB]settings=\f[R]\f[I]path\f[R]
Specifies the path and name of the event settings file (of type JFC).
By default, the \f[CB]default.jfc\f[R] file is used, which is located in
-\f[CB]JRE_HOME/lib/jfr\f[R].
+\f[CB]JAVA_HOME/lib/jfr\f[R].
This default settings file collects a predefined set of information with
low overhead, so it has minimal impact on performance and can be used
with recordings that run continuously.
diff --git a/src/java.base/share/native/libjli/java.c b/src/java.base/share/native/libjli/java.c
index dacd2525059..38ddbc03248 100644
--- a/src/java.base/share/native/libjli/java.c
+++ b/src/java.base/share/native/libjli/java.c
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 1995, 2019, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1995, 2020, 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
diff --git a/src/java.base/unix/native/libjli/java_md_solinux.c b/src/java.base/unix/native/libjli/java_md_solinux.c
index d06c8ee3552..4d666b426e3 100644
--- a/src/java.base/unix/native/libjli/java_md_solinux.c
+++ b/src/java.base/unix/native/libjli/java_md_solinux.c
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 1998, 2019, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1998, 2020, 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
diff --git a/src/java.base/unix/native/libjli/java_md_solinux.h b/src/java.base/unix/native/libjli/java_md_solinux.h
index 72fcc225b85..16b0edf8b48 100644
--- a/src/java.base/unix/native/libjli/java_md_solinux.h
+++ b/src/java.base/unix/native/libjli/java_md_solinux.h
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2013, 2020, 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
diff --git a/src/java.net.http/share/classes/jdk/internal/net/http/ResponseContent.java b/src/java.net.http/share/classes/jdk/internal/net/http/ResponseContent.java
index c48245cc0c3..de1e6742505 100644
--- a/src/java.net.http/share/classes/jdk/internal/net/http/ResponseContent.java
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/ResponseContent.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2015, 2020, 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
@@ -125,6 +125,7 @@ class ResponseContent {
static enum ChunkState {READING_LENGTH, READING_DATA, DONE}
+ static final int MAX_CHUNK_HEADER_SIZE = 2050;
class ChunkedBodyParser implements BodyParser {
final ByteBuffer READMORE = Utils.EMPTY_BYTEBUFFER;
final Consumer onComplete;
@@ -136,6 +137,8 @@ class ResponseContent {
volatile int chunklen = -1; // number of bytes in chunk
volatile int bytesremaining; // number of bytes in chunk left to be read incl CRLF
volatile boolean cr = false; // tryReadChunkLength has found CR
+ volatile int chunkext = 0; // number of bytes already read in the chunk extension
+ volatile int digits = 0; // number of chunkLength bytes already read
volatile int bytesToConsume; // number of bytes that still need to be consumed before proceeding
volatile ChunkState state = ChunkState.READING_LENGTH; // current state
volatile AbstractSubscription sub;
@@ -147,6 +150,26 @@ class ResponseContent {
return dbgTag;
}
+ // best effort - we're assuming UTF-8 text and breaks at character boundaries
+ // for this debug output. Not called.
+ private void debugBuffer(ByteBuffer b) {
+ if (!debug.on()) return;
+ ByteBuffer printable = b.asReadOnlyBuffer();
+ byte[] bytes = new byte[printable.limit() - printable.position()];
+ printable.get(bytes, 0, bytes.length);
+ String msg = "============== accepted ==================\n";
+ try {
+ var str = new String(bytes, "UTF-8");
+ msg += str;
+ } catch (Exception x) {
+ msg += x;
+ x.printStackTrace();
+ }
+ msg += "\n==========================================\n";
+ debug.log(msg);
+
+ }
+
@Override
public void onSubscribe(AbstractSubscription sub) {
if (debug.on())
@@ -158,7 +181,6 @@ class ResponseContent {
public String currentStateMessage() {
return format("chunked transfer encoding, state: %s", state);
}
-
@Override
public void accept(ByteBuffer b) {
if (closedExceptionally != null) {
@@ -166,6 +188,7 @@ class ResponseContent {
debug.log("already closed: " + closedExceptionally);
return;
}
+ // debugBuffer(b);
boolean completed = false;
try {
List out = new ArrayList<>();
@@ -221,6 +244,9 @@ class ResponseContent {
private int tryReadChunkLen(ByteBuffer chunkbuf) throws IOException {
assert state == ChunkState.READING_LENGTH;
while (chunkbuf.hasRemaining()) {
+ if (chunkext + digits >= MAX_CHUNK_HEADER_SIZE) {
+ throw new IOException("Chunk header size too long: " + (chunkext + digits));
+ }
int c = chunkbuf.get();
if (cr) {
if (c == LF) {
@@ -231,9 +257,34 @@ class ResponseContent {
}
if (c == CR) {
cr = true;
+ if (digits == 0 && debug.on()) {
+ debug.log("tryReadChunkLen: invalid chunk header? No digits in chunkLen?");
+ }
+ } else if (cr == false && chunkext > 0) {
+ // we have seen a non digit character after the chunk length.
+ // skip anything until CR is found.
+ chunkext++;
+ if (debug.on()) {
+ debug.log("tryReadChunkLen: More extraneous character after chunk length: " + c);
+ }
} else {
int digit = toDigit(c);
- partialChunklen = partialChunklen * 16 + digit;
+ if (digit < 0) {
+ if (digits > 0) {
+ // first non-digit character after chunk length.
+ // skip anything until CR is found.
+ chunkext++;
+ if (debug.on()) {
+ debug.log("tryReadChunkLen: Extraneous character after chunk length: " + c);
+ }
+ } else {
+ // there should be at list one digit in chunk length
+ throw new IOException("Illegal character in chunk size: " + c);
+ }
+ } else {
+ digits++;
+ partialChunklen = partialChunklen * 16 + digit;
+ }
}
}
return -1;
@@ -286,6 +337,7 @@ class ResponseContent {
+ " (remaining in buffer:"+chunk.remaining()+")");
int clen = chunklen = tryReadChunkLen(chunk);
if (clen == -1) return READMORE;
+ digits = chunkext = 0;
if (debug.on()) debug.log("Got chunk len %d", clen);
cr = false; partialChunklen = 0;
unfulfilled = bytesremaining = clen;
@@ -354,6 +406,7 @@ class ResponseContent {
chunklen = -1;
partialChunklen = 0;
cr = false;
+ digits = chunkext = 0;
state = ChunkState.READING_LENGTH;
if (debug.on()) debug.log("Ready to read next chunk");
}
@@ -395,7 +448,7 @@ class ResponseContent {
if (b >= 0x61 && b <= 0x66) {
return b - 0x61 + 10;
}
- throw new IOException("Invalid chunk header byte " + b);
+ return -1;
}
}
diff --git a/src/java.net.http/share/classes/jdk/internal/net/http/Stream.java b/src/java.net.http/share/classes/jdk/internal/net/http/Stream.java
index b6d5bb93fa6..b65294a8367 100644
--- a/src/java.net.http/share/classes/jdk/internal/net/http/Stream.java
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/Stream.java
@@ -389,10 +389,10 @@ class Stream extends ExchangeImpl {
if (hframe.endHeaders()) {
Log.logTrace("handling response (streamid={0})", streamid);
handleResponse();
- if (hframe.getFlag(HeaderFrame.END_STREAM)) {
- if (debug.on()) debug.log("handling END_STREAM: %d", streamid);
- receiveDataFrame(new DataFrame(streamid, DataFrame.END_STREAM, List.of()));
- }
+ }
+ if (hframe.getFlag(HeaderFrame.END_STREAM)) {
+ if (debug.on()) debug.log("handling END_STREAM: %d", streamid);
+ receiveDataFrame(new DataFrame(streamid, DataFrame.END_STREAM, List.of()));
}
} else if (frame instanceof DataFrame) {
receiveDataFrame((DataFrame)frame);
diff --git a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/oops/ConstMethod.java b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/oops/ConstMethod.java
index 2854f8693ed..b9e3b016132 100644
--- a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/oops/ConstMethod.java
+++ b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/oops/ConstMethod.java
@@ -194,6 +194,17 @@ public class ConstMethod extends Metadata {
// bytecode accessors
+ /** See if address is in the Method's bytecodes */
+ public boolean isAddressInMethod(Address bcp) {
+ Address bytecodeStart = getAddress().addOffsetTo(bytecodeOffset);
+ Address bytecodeEnd = bytecodeStart.addOffsetTo(getCodeSize() - 1);
+ if (bcp.greaterThanOrEqual(bytecodeStart) && bcp.lessThanOrEqual(bytecodeEnd)) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
/** Get a bytecode or breakpoint at the given bci */
public int getBytecodeOrBPAt(int bci) {
return getAddress().getJByteAt(bytecodeOffset + bci) & 0xFF;
@@ -296,7 +307,8 @@ public class ConstMethod extends Metadata {
}
if (Assert.ASSERTS_ENABLED) {
- Assert.that(bci == 0 || 0 <= bci && bci < getCodeSize(), "illegal bci");
+ Assert.that(0 <= bci && bci < getCodeSize(),
+ "illegal bci(" + bci + ") codeSize(" + getCodeSize() + ")");
}
int bestBCI = 0;
int bestLine = -1;
diff --git a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/runtime/ThreadStackTrace.java b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/runtime/ThreadStackTrace.java
index a599a171a57..65ba5c29ebe 100644
--- a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/runtime/ThreadStackTrace.java
+++ b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/runtime/ThreadStackTrace.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2017, 2020, 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
@@ -47,7 +47,7 @@ public class ThreadStackTrace {
public void dumpStack(int maxDepth) {
if (!thread.isJavaThread()) {
- System.out.println("dumpStack: not java Thread returning");
+ System.out.println("dumpStack: not java Thread.");
return;
}
try {
diff --git a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/runtime/bsd_amd64/BsdAMD64JavaThreadPDAccess.java b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/runtime/bsd_amd64/BsdAMD64JavaThreadPDAccess.java
index 5c8d9d68dc4..3f7d923a564 100644
--- a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/runtime/bsd_amd64/BsdAMD64JavaThreadPDAccess.java
+++ b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/runtime/bsd_amd64/BsdAMD64JavaThreadPDAccess.java
@@ -103,6 +103,10 @@ public class BsdAMD64JavaThreadPDAccess implements JavaThreadPDAccess {
}
if (guesser.getPC() == null) {
return new X86Frame(guesser.getSP(), guesser.getFP());
+ } else if (VM.getVM().getInterpreter().contains(guesser.getPC())) {
+ // pass the value of R13 which contains the bcp for the top level frame
+ Address bcp = context.getRegisterAsAddress(AMD64ThreadContext.R13);
+ return new X86Frame(guesser.getSP(), guesser.getFP(), guesser.getPC(), null, bcp);
} else {
return new X86Frame(guesser.getSP(), guesser.getFP(), guesser.getPC());
}
diff --git a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/runtime/win32_amd64/Win32AMD64JavaThreadPDAccess.java b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/runtime/win32_amd64/Win32AMD64JavaThreadPDAccess.java
index bc5869fb67a..f6975d836c1 100644
--- a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/runtime/win32_amd64/Win32AMD64JavaThreadPDAccess.java
+++ b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/runtime/win32_amd64/Win32AMD64JavaThreadPDAccess.java
@@ -107,6 +107,10 @@ public class Win32AMD64JavaThreadPDAccess implements JavaThreadPDAccess {
}
if (guesser.getPC() == null) {
return new X86Frame(guesser.getSP(), guesser.getFP());
+ } else if (VM.getVM().getInterpreter().contains(guesser.getPC())) {
+ // pass the value of R13 which contains the bcp for the top level frame
+ Address bcp = context.getRegisterAsAddress(AMD64ThreadContext.R13);
+ return new X86Frame(guesser.getSP(), guesser.getFP(), guesser.getPC(), null, bcp);
} else {
return new X86Frame(guesser.getSP(), guesser.getFP(), guesser.getPC());
}
diff --git a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/runtime/x86/X86Frame.java b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/runtime/x86/X86Frame.java
index 511bade768a..2eabb4b4c11 100644
--- a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/runtime/x86/X86Frame.java
+++ b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/runtime/x86/X86Frame.java
@@ -444,17 +444,23 @@ public class X86Frame extends Frame {
// FIXME: this is not atomic with respect to GC and is unsuitable
// for use in a non-debugging, or reflective, system. Need to
// figure out how to express this.
- Address bcp = addressOfInterpreterFrameBCX().getAddressAt(0);
-
- // If we are in the top level frame then the bcp may have been set for us. If so then let it
- // take priority. If we are in a top level interpreter frame, the bcp is live in R13 (on x86)
- // and not saved in the BCX stack slot.
- if (live_bcp != null) {
- bcp = live_bcp;
- }
Address methodHandle = addressOfInterpreterFrameMethod().getAddressAt(0);
Method method = (Method)Metadata.instantiateWrapperFor(methodHandle);
+ Address bcp = addressOfInterpreterFrameBCX().getAddressAt(0);
+
+ // If we are in the top level frame then the bcp may have been set for us. If so then let it
+ // take priority. If we are in a top level interpreter frame, the bcp is live in R13 (on x86_64)
+ // and not saved in the BCX stack slot.
+ if (live_bcp != null) {
+ // Only use live_bcp if it points within the Method's bytecodes. Sometimes R13 is used
+ // for scratch purposes and is not a valid BCP. If it is not valid, then we stick with
+ // the bcp stored in the frame, which R13 should have been flushed to.
+ if (method.getConstMethod().isAddressInMethod(live_bcp)) {
+ bcp = live_bcp;
+ }
+ }
+
return bcpToBci(bcp, method);
}
diff --git a/src/jdk.incubator.jpackage/macosx/classes/jdk/incubator/jpackage/internal/MacAppImageBuilder.java b/src/jdk.incubator.jpackage/macosx/classes/jdk/incubator/jpackage/internal/MacAppImageBuilder.java
index d5f7538e8ea..f8f7311bcb7 100644
--- a/src/jdk.incubator.jpackage/macosx/classes/jdk/incubator/jpackage/internal/MacAppImageBuilder.java
+++ b/src/jdk.incubator.jpackage/macosx/classes/jdk/incubator/jpackage/internal/MacAppImageBuilder.java
@@ -839,7 +839,6 @@ public class MacAppImageBuilder extends AbstractAppImageBuilder {
args.addAll(Arrays.asList("codesign",
"--timestamp",
"--options", "runtime",
- "--deep",
"--force",
"-s", signingIdentity, // sign with this key
"--prefix", identifierPrefix,
@@ -884,7 +883,6 @@ public class MacAppImageBuilder extends AbstractAppImageBuilder {
args.addAll(Arrays.asList("codesign",
"--timestamp",
"--options", "runtime",
- "--deep",
"--force",
"-s", signingIdentity,
"-vvvv"));
diff --git a/src/jdk.incubator.jpackage/share/classes/jdk/incubator/jpackage/internal/DottedVersion.java b/src/jdk.incubator.jpackage/share/classes/jdk/incubator/jpackage/internal/DottedVersion.java
index 8ef77f3cb4a..13da1350a9d 100644
--- a/src/jdk.incubator.jpackage/share/classes/jdk/incubator/jpackage/internal/DottedVersion.java
+++ b/src/jdk.incubator.jpackage/share/classes/jdk/incubator/jpackage/internal/DottedVersion.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2019, 2020, 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
@@ -137,6 +137,10 @@ class DottedVersion implements Comparable {
return value;
}
+ int[] getComponents() {
+ return components;
+ }
+
final private int[] components;
final private String value;
final private boolean greedy;
diff --git a/src/jdk.incubator.jpackage/share/classes/jdk/incubator/jpackage/internal/OverridableResource.java b/src/jdk.incubator.jpackage/share/classes/jdk/incubator/jpackage/internal/OverridableResource.java
index 9ce3354f10e..edfb6a9608c 100644
--- a/src/jdk.incubator.jpackage/share/classes/jdk/incubator/jpackage/internal/OverridableResource.java
+++ b/src/jdk.incubator.jpackage/share/classes/jdk/incubator/jpackage/internal/OverridableResource.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2019, 2020, 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
@@ -24,18 +24,28 @@
*/
package jdk.incubator.jpackage.internal;
-import java.io.*;
+import java.io.BufferedReader;
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.text.MessageFormat;
-import java.util.*;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static jdk.incubator.jpackage.internal.StandardBundlerParam.RESOURCE_DIR;
import jdk.incubator.jpackage.internal.resources.ResourceLocator;
+
/**
* Resource file that may have the default value supplied by jpackage. It can be
* overridden by a file from resource directory set with {@code --resource-dir}
@@ -134,13 +144,39 @@ final class OverridableResource {
return setExternal(toPath(v));
}
- Source saveToFile(Path dest) throws IOException {
- for (var source: sources) {
- if (source.getValue().apply(dest)) {
- return source.getKey();
- }
+ Source saveToStream(OutputStream dest) throws IOException {
+ if (dest == null) {
+ return sendToConsumer(null);
}
- return null;
+ return sendToConsumer(new ResourceConsumer() {
+ @Override
+ public Path publicName() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void consume(InputStream in) throws IOException {
+ in.transferTo(dest);
+ }
+ });
+ }
+
+ Source saveToFile(Path dest) throws IOException {
+ if (dest == null) {
+ return sendToConsumer(null);
+ }
+ return sendToConsumer(new ResourceConsumer() {
+ @Override
+ public Path publicName() {
+ return dest.getFileName();
+ }
+
+ @Override
+ public void consume(InputStream in) throws IOException {
+ Files.createDirectories(dest.getParent());
+ Files.copy(in, dest, StandardCopyOption.REPLACE_EXISTING);
+ }
+ });
}
Source saveToFile(File dest) throws IOException {
@@ -157,6 +193,15 @@ final class OverridableResource {
RESOURCE_DIR.fetchFrom(params));
}
+ private Source sendToConsumer(ResourceConsumer consumer) throws IOException {
+ for (var source: sources) {
+ if (source.getValue().apply(consumer)) {
+ return source.getKey();
+ }
+ }
+ return null;
+ }
+
private String getPrintableCategory() {
if (category != null) {
return String.format("[%s]", category);
@@ -164,7 +209,7 @@ final class OverridableResource {
return "";
}
- private boolean useExternal(Path dest) throws IOException {
+ private boolean useExternal(ResourceConsumer dest) throws IOException {
boolean used = externalPath != null && Files.exists(externalPath);
if (used && dest != null) {
Log.verbose(MessageFormat.format(I18N.getString(
@@ -179,7 +224,7 @@ final class OverridableResource {
return used;
}
- private boolean useResourceDir(Path dest) throws IOException {
+ private boolean useResourceDir(ResourceConsumer dest) throws IOException {
boolean used = false;
if (dest == null && publicName == null) {
@@ -187,7 +232,7 @@ final class OverridableResource {
}
final Path resourceName = Optional.ofNullable(publicName).orElseGet(
- () -> dest.getFileName());
+ () -> dest.publicName());
if (resourceDir != null) {
final Path customResource = resourceDir.resolve(resourceName);
@@ -213,14 +258,14 @@ final class OverridableResource {
return used;
}
- private boolean useDefault(Path dest) throws IOException {
+ private boolean useDefault(ResourceConsumer dest) throws IOException {
boolean used = defaultName != null;
if (used && dest != null) {
final Path resourceName = Optional
.ofNullable(logPublicName)
.orElse(Optional
.ofNullable(publicName)
- .orElseGet(() -> dest.getFileName()));
+ .orElseGet(() -> dest.publicName()));
Log.verbose(MessageFormat.format(
I18N.getString("message.using-default-resource"),
defaultName, getPrintableCategory(), resourceName));
@@ -232,7 +277,7 @@ final class OverridableResource {
return used;
}
- private static List substitute(Stream lines,
+ private static Stream substitute(Stream lines,
Map substitutionData) {
return lines.map(line -> {
String result = line;
@@ -241,7 +286,7 @@ final class OverridableResource {
entry.getValue()).orElse(""));
}
return result;
- }).collect(Collectors.toList());
+ });
}
private static Path toPath(File v) {
@@ -251,17 +296,20 @@ final class OverridableResource {
return null;
}
- private void processResourceStream(InputStream rawResource, Path dest)
- throws IOException {
+ private void processResourceStream(InputStream rawResource,
+ ResourceConsumer dest) throws IOException {
if (substitutionData == null) {
- Files.createDirectories(dest.getParent());
- Files.copy(rawResource, dest, StandardCopyOption.REPLACE_EXISTING);
+ dest.consume(rawResource);
} else {
// Utf8 in and out
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(rawResource, StandardCharsets.UTF_8))) {
- Files.createDirectories(dest.getParent());
- Files.write(dest, substitute(reader.lines(), substitutionData));
+ String data = substitute(reader.lines(), substitutionData).collect(
+ Collectors.joining("\n"));
+ try (InputStream in = new ByteArrayInputStream(data.getBytes(
+ StandardCharsets.UTF_8))) {
+ dest.consume(in);
+ }
}
}
}
@@ -292,7 +340,12 @@ final class OverridableResource {
private List> sources;
@FunctionalInterface
- static interface SourceHandler {
- public boolean apply(Path dest) throws IOException;
+ private static interface SourceHandler {
+ public boolean apply(ResourceConsumer dest) throws IOException;
+ }
+
+ private static interface ResourceConsumer {
+ public Path publicName();
+ public void consume(InputStream in) throws IOException;
}
}
diff --git a/src/jdk.incubator.jpackage/windows/classes/jdk/incubator/jpackage/internal/ExecutableRebrander.java b/src/jdk.incubator.jpackage/windows/classes/jdk/incubator/jpackage/internal/ExecutableRebrander.java
new file mode 100644
index 00000000000..b9628bf1eac
--- /dev/null
+++ b/src/jdk.incubator.jpackage/windows/classes/jdk/incubator/jpackage/internal/ExecutableRebrander.java
@@ -0,0 +1,240 @@
+/*
+ * Copyright (c) 2020, 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 jdk.incubator.jpackage.internal;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Path;
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Properties;
+import java.util.ResourceBundle;
+import static jdk.incubator.jpackage.internal.OverridableResource.createResource;
+import static jdk.incubator.jpackage.internal.StandardBundlerParam.APP_NAME;
+import static jdk.incubator.jpackage.internal.StandardBundlerParam.COPYRIGHT;
+import static jdk.incubator.jpackage.internal.StandardBundlerParam.DESCRIPTION;
+import static jdk.incubator.jpackage.internal.StandardBundlerParam.ICON;
+import static jdk.incubator.jpackage.internal.StandardBundlerParam.TEMP_ROOT;
+import static jdk.incubator.jpackage.internal.StandardBundlerParam.VENDOR;
+import static jdk.incubator.jpackage.internal.StandardBundlerParam.VERSION;
+
+
+final class ExecutableRebrander {
+ private static final ResourceBundle I18N = ResourceBundle.getBundle(
+ "jdk.incubator.jpackage.internal.resources.WinResources");
+
+ private static final String LAUNCHER_PROPERTIES_TEMPLATE =
+ "WinLauncher.template";
+
+ private static final String INSTALLER_PROPERTIES_TEMPLATE =
+ "WinInstaller.template";
+
+ private static final String INSTALLER_PROPERTIES_RESOURE_DIR_ID =
+ "WinInstaller.properties";
+
+
+ void rebrandInstaller(Map params, Path target)
+ throws IOException {
+ if (!target.isAbsolute()) {
+ rebrandInstaller(params, target.toAbsolutePath());
+ return;
+ }
+ rebrandExecutable(params, target, (resourceLock) -> {
+ rebrandProperties(resourceLock, createResource(
+ INSTALLER_PROPERTIES_TEMPLATE, params).setPublicName(
+ INSTALLER_PROPERTIES_RESOURE_DIR_ID),
+ createSubstituteData(params), target);
+ });
+ }
+
+ void rebrandLauncher(Map params, Path icon,
+ Path target) throws IOException {
+ if (!target.isAbsolute() || (icon != null && !icon.isAbsolute())) {
+ Path absIcon = null;
+ if (icon != null) {
+ absIcon = icon.toAbsolutePath();
+ }
+ rebrandLauncher(params, absIcon, target.toAbsolutePath());
+ return;
+ }
+ rebrandExecutable(params, target, (resourceLock) -> {
+ rebrandProperties(resourceLock, createResource(
+ LAUNCHER_PROPERTIES_TEMPLATE, params).setPublicName(
+ APP_NAME.fetchFrom(params) + ".properties"),
+ createSubstituteData(params), target);
+
+ if (icon != null) {
+ iconSwap(resourceLock, icon.toString());
+ }
+ });
+ }
+
+ ExecutableRebrander addAction(UpdateResourceAction action) {
+ if (extraActions == null) {
+ extraActions = new ArrayList<>();
+ }
+ extraActions.add(action);
+ return this;
+ }
+
+ private void rebrandExecutable(Map params,
+ Path target, UpdateResourceAction action) throws IOException {
+ try {
+ String tempDirectory = TEMP_ROOT.fetchFrom(params).getAbsolutePath();
+ if (WindowsDefender.isThereAPotentialWindowsDefenderIssue(
+ tempDirectory)) {
+ Log.verbose(MessageFormat.format(I18N.getString(
+ "message.potential.windows.defender.issue"),
+ tempDirectory));
+ }
+
+ target.toFile().setWritable(true, true);
+
+ long resourceLock = lockResource(target.toString());
+ if (resourceLock == 0) {
+ throw new RuntimeException(MessageFormat.format(
+ I18N.getString("error.lock-resource"), target));
+ }
+
+ try {
+ action.editResource(resourceLock);
+ if (extraActions != null) {
+ for (UpdateResourceAction extraAction: extraActions) {
+ extraAction.editResource(resourceLock);
+ }
+ }
+ } finally {
+ if (resourceLock != 0) {
+ unlockResource(resourceLock);
+ }
+ }
+ } finally {
+ target.toFile().setReadOnly();
+ }
+ }
+
+ @FunctionalInterface
+ static interface UpdateResourceAction {
+ public void editResource(long resourceLock) throws IOException;
+ }
+
+ private static String getFixedFileVersion(String value) {
+ int[] versionComponents = DottedVersion.greedy(value).getComponents();
+ int addComponentsCount = 4 - versionComponents.length;
+ if (addComponentsCount > 0) {
+ StringBuilder sb = new StringBuilder(value);
+ do {
+ sb.append('.');
+ sb.append(0);
+ } while (--addComponentsCount > 0);
+ return sb.toString();
+ }
+ return value;
+ }
+
+ private Map createSubstituteData(
+ Map params) {
+ Map data = new HashMap<>();
+
+ String fixedFileVersion = getFixedFileVersion(VERSION.fetchFrom(params));
+
+ // mapping Java parameters in strings for version resource
+ validateValueAndPut(data, "COMPANY_NAME", VENDOR, params);
+ validateValueAndPut(data, "FILE_DESCRIPTION", DESCRIPTION, params);
+ validateValueAndPut(data, "FILE_VERSION", VERSION, params);
+ validateValueAndPut(data, "LEGAL_COPYRIGHT", COPYRIGHT, params);
+ validateValueAndPut(data, "PRODUCT_NAME", APP_NAME, params);
+ data.put("FIXEDFILEINFO_FILE_VERSION", fixedFileVersion);
+
+ return data;
+ }
+
+ private void rebrandProperties(long resourceLock, OverridableResource properties,
+ Map data, Path target) throws IOException {
+
+ String targetExecutableName = target.getFileName().toString();
+ data.put("INTERNAL_NAME", targetExecutableName);
+ data.put("ORIGINAL_FILENAME", targetExecutableName);
+
+ ByteArrayOutputStream buffer = new ByteArrayOutputStream();
+ properties
+ .setSubstitutionData(data)
+ .setCategory(I18N.getString("resource.executable-properties-template"))
+ .saveToStream(buffer);
+
+ final List propList = new ArrayList<>();
+ try (Reader reader = new InputStreamReader(new ByteArrayInputStream(
+ buffer.toByteArray()), StandardCharsets.UTF_8)) {
+ final Properties configProp = new Properties();
+ configProp.load(reader);
+ configProp.forEach((k, v) -> {
+ propList.add(k.toString());
+ propList.add(v.toString());
+ });
+ }
+
+ if (versionSwap(resourceLock, propList.toArray(String[]::new)) != 0) {
+ throw new RuntimeException(MessageFormat.format(
+ I18N.getString("error.version-swap"), target));
+ }
+ }
+
+ private static void validateValueAndPut(
+ Map data, String key,
+ BundlerParamInfo param,
+ Map params) {
+ String value = param.fetchFrom(params);
+ if (value.contains("\r") || value.contains("\n")) {
+ Log.error("Configuration Parameter " + param.getID()
+ + " contains multiple lines of text, ignore it");
+ data.put(key, "");
+ return;
+ }
+ data.put(key, value);
+ }
+
+ private List extraActions;
+
+ static {
+ System.loadLibrary("jpackage");
+ }
+
+ private static native long lockResource(String executable);
+
+ private static native void unlockResource(long resourceLock);
+
+ private static native int iconSwap(long resourceLock, String iconTarget);
+
+ private static native int versionSwap(long resourceLock, String[] executableProperties);
+}
diff --git a/src/jdk.incubator.jpackage/windows/classes/jdk/incubator/jpackage/internal/WinExeBundler.java b/src/jdk.incubator.jpackage/windows/classes/jdk/incubator/jpackage/internal/WinExeBundler.java
index 6130b0c3c5e..3723e119c33 100644
--- a/src/jdk.incubator.jpackage/windows/classes/jdk/incubator/jpackage/internal/WinExeBundler.java
+++ b/src/jdk.incubator.jpackage/windows/classes/jdk/incubator/jpackage/internal/WinExeBundler.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2017, 2019, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2017, 2020, 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
@@ -64,7 +64,7 @@ public class WinExeBundler extends AbstractBundler {
@Override
public String getName() {
- return getString("exe.bundler.name");
+ return I18N.getString("exe.bundler.name");
}
@Override
@@ -117,48 +117,45 @@ public class WinExeBundler extends AbstractBundler {
.setEnvironmentVariable("JpMsiFile", msi.getAbsolutePath().toString())
.run(params);
- return buildEXE(msi, outdir);
+ return buildEXE(params, msi, outdir);
} catch (IOException ex) {
Log.verbose(ex);
throw new PackagerException(ex);
}
}
- private File buildEXE(File msi, File outdir)
- throws IOException {
+ private File buildEXE(Map params, File msi,
+ File outdir) throws IOException {
Log.verbose(MessageFormat.format(
- getString("message.outputting-to-location"),
+ I18N.getString("message.outputting-to-location"),
outdir.getAbsolutePath()));
// Copy template msi wrapper next to msi file
- String exePath = msi.getAbsolutePath();
- exePath = exePath.substring(0, exePath.lastIndexOf('.')) + ".exe";
+ final Path exePath = IOUtils.replaceSuffix(msi.toPath(), ".exe");
try (InputStream is = OverridableResource.readDefault(EXE_WRAPPER_NAME)) {
- Files.copy(is, Path.of(exePath));
+ Files.copy(is, exePath);
}
- // Embed msi in msi wrapper exe.
- embedMSI(exePath, msi.getAbsolutePath());
+
+ new ExecutableRebrander().addAction((resourceLock) -> {
+ // Embed msi in msi wrapper exe.
+ embedMSI(resourceLock, msi.getAbsolutePath());
+ }).rebrandInstaller(params, exePath);
Path dstExePath = Paths.get(outdir.getAbsolutePath(),
- Path.of(exePath).getFileName().toString());
+ exePath.getFileName().toString());
Files.deleteIfExists(dstExePath);
- Files.copy(Path.of(exePath), dstExePath);
+ Files.copy(exePath, dstExePath);
Log.verbose(MessageFormat.format(
- getString("message.output-location"),
+ I18N.getString("message.output-location"),
outdir.getAbsolutePath()));
return dstExePath.toFile();
}
- private static String getString(String key)
- throws MissingResourceException {
- return I18N.getString(key);
- }
-
private final WinMsiBundler msiBundler = new WinMsiBundler();
- private static native int embedMSI(String exePath, String msiPath);
+ private static native int embedMSI(long resourceLock, String msiPath);
}
diff --git a/src/jdk.incubator.jpackage/windows/classes/jdk/incubator/jpackage/internal/WindowsAppImageBuilder.java b/src/jdk.incubator.jpackage/windows/classes/jdk/incubator/jpackage/internal/WindowsAppImageBuilder.java
index 6634ac3e81d..48a991efd47 100644
--- a/src/jdk.incubator.jpackage/windows/classes/jdk/incubator/jpackage/internal/WindowsAppImageBuilder.java
+++ b/src/jdk.incubator.jpackage/windows/classes/jdk/incubator/jpackage/internal/WindowsAppImageBuilder.java
@@ -26,49 +26,28 @@
package jdk.incubator.jpackage.internal;
import java.io.File;
-import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
-import java.io.OutputStream;
-import java.io.OutputStreamWriter;
-import java.io.UncheckedIOException;
-import java.io.Writer;
-import java.io.BufferedWriter;
-import java.io.FileWriter;
-import java.nio.charset.StandardCharsets;
+import java.io.Reader;
import java.nio.file.Files;
import java.nio.file.Path;
-import java.nio.file.StandardCopyOption;
-import java.nio.file.attribute.PosixFilePermission;
import java.text.MessageFormat;
+import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
+import java.util.Properties;
import java.util.ResourceBundle;
-import java.util.Set;
-import java.util.concurrent.atomic.AtomicReference;
-import java.util.regex.Pattern;
-import java.util.stream.Stream;
import static jdk.incubator.jpackage.internal.OverridableResource.createResource;
import static jdk.incubator.jpackage.internal.StandardBundlerParam.*;
public class WindowsAppImageBuilder extends AbstractAppImageBuilder {
-
- static {
- System.loadLibrary("jpackage");
- }
-
private static final ResourceBundle I18N = ResourceBundle.getBundle(
"jdk.incubator.jpackage.internal.resources.WinResources");
- private final static String LIBRARY_NAME = "applauncher.dll";
-
- private final static String TEMPLATE_APP_ICON ="java48.ico";
-
- private static final String EXECUTABLE_PROPERTIES_TEMPLATE =
- "WinLauncher.template";
+ private static final String TEMPLATE_APP_ICON ="java48.ico";
private final Path root;
private final Path appDir;
@@ -77,13 +56,6 @@ public class WindowsAppImageBuilder extends AbstractAppImageBuilder {
private final Path mdir;
private final Path binDir;
- public static final BundlerParamInfo REBRAND_EXECUTABLE =
- new WindowsBundlerParam<>(
- "win.launcher.rebrand",
- Boolean.class,
- params -> Boolean.TRUE,
- (s, p) -> Boolean.valueOf(s));
-
public static final BundlerParamInfo ICON_ICO =
new StandardBundlerParam<>(
"icon.ico",
@@ -150,16 +122,6 @@ public class WindowsAppImageBuilder extends AbstractAppImageBuilder {
return "app/" + APP_NAME.fetchFrom(params) +".cfg";
}
- private File getConfig_ExecutableProperties(
- Map params) {
- return new File(getConfigRoot(params),
- APP_NAME.fetchFrom(params) + ".properties");
- }
-
- File getConfigRoot(Map params) {
- return CONFIG_ROOT.fetchFrom(params);
- }
-
@Override
public Path getAppDir() {
return appDir;
@@ -200,41 +162,6 @@ public class WindowsAppImageBuilder extends AbstractAppImageBuilder {
public void prepareJreFiles(Map params)
throws IOException {}
- private void validateValueAndPut(
- Map data, String key,
- BundlerParamInfo param,
- Map params) {
- String value = param.fetchFrom(params);
- if (value.contains("\r") || value.contains("\n")) {
- Log.error("Configuration Parameter " + param.getID()
- + " contains multiple lines of text, ignore it");
- data.put(key, "");
- return;
- }
- data.put(key, value);
- }
-
- protected void prepareExecutableProperties(
- Map params) throws IOException {
-
- Map data = new HashMap<>();
-
- // mapping Java parameters in strings for version resource
- validateValueAndPut(data, "COMPANY_NAME", VENDOR, params);
- validateValueAndPut(data, "FILE_DESCRIPTION", DESCRIPTION, params);
- validateValueAndPut(data, "FILE_VERSION", VERSION, params);
- data.put("INTERNAL_NAME", getLauncherName(params));
- validateValueAndPut(data, "LEGAL_COPYRIGHT", COPYRIGHT, params);
- data.put("ORIGINAL_FILENAME", getLauncherName(params));
- validateValueAndPut(data, "PRODUCT_NAME", APP_NAME, params);
- validateValueAndPut(data, "PRODUCT_VERSION", VERSION, params);
-
- createResource(EXECUTABLE_PROPERTIES_TEMPLATE, params)
- .setCategory(I18N.getString("resource.executable-properties-template"))
- .setSubstitutionData(data)
- .saveToFile(getConfig_ExecutableProperties(params));
- }
-
private void createLauncherForEntryPoint(Map params,
Map mainParams) throws IOException {
@@ -251,8 +178,6 @@ public class WindowsAppImageBuilder extends AbstractAppImageBuilder {
writeCfgFile(params, root.resolve(
getLauncherCfgName(params)).toFile());
- prepareExecutableProperties(params);
-
// Copy executable to bin folder
Path executableFile = binDir.resolve(getLauncherName(params));
@@ -261,47 +186,11 @@ public class WindowsAppImageBuilder extends AbstractAppImageBuilder {
writeEntry(is_launcher, executableFile);
}
- File launcher = executableFile.toFile();
- launcher.setWritable(true, true);
+ // Update branding of launcher executable
+ new ExecutableRebrander().rebrandLauncher(params, iconTarget, executableFile);
- // Update branding of EXE file
- if (REBRAND_EXECUTABLE.fetchFrom(params)) {
- try {
- String tempDirectory = WindowsDefender.getUserTempDirectory();
- if (Arguments.CLIOptions.context().userProvidedBuildRoot) {
- tempDirectory =
- TEMP_ROOT.fetchFrom(params).getAbsolutePath();
- }
- if (WindowsDefender.isThereAPotentialWindowsDefenderIssue(
- tempDirectory)) {
- Log.verbose(MessageFormat.format(I18N.getString(
- "message.potential.windows.defender.issue"),
- tempDirectory));
- }
-
- launcher.setWritable(true);
-
- if (iconTarget != null) {
- iconSwap(iconTarget.toAbsolutePath().toString(),
- launcher.getAbsolutePath());
- }
-
- File executableProperties =
- getConfig_ExecutableProperties(params);
-
- if (executableProperties.exists()) {
- if (versionSwap(executableProperties.getAbsolutePath(),
- launcher.getAbsolutePath()) != 0) {
- throw new RuntimeException(MessageFormat.format(
- I18N.getString("error.version-swap"),
- executableProperties.getAbsolutePath()));
- }
- }
- } finally {
- executableFile.toFile().setExecutable(true);
- executableFile.toFile().setReadOnly();
- }
- }
+ executableFile.toFile().setExecutable(true);
+ executableFile.toFile().setReadOnly();
}
private void copyApplication(Map params)
@@ -321,10 +210,4 @@ public class WindowsAppImageBuilder extends AbstractAppImageBuilder {
}
}
}
-
- private static native int iconSwap(String iconTarget, String launcher);
-
- private static native int versionSwap(String executableProperties,
- String launcher);
-
}
diff --git a/src/jdk.incubator.jpackage/windows/classes/jdk/incubator/jpackage/internal/resources/WinInstaller.template b/src/jdk.incubator.jpackage/windows/classes/jdk/incubator/jpackage/internal/resources/WinInstaller.template
new file mode 100644
index 00000000000..57ac0401660
--- /dev/null
+++ b/src/jdk.incubator.jpackage/windows/classes/jdk/incubator/jpackage/internal/resources/WinInstaller.template
@@ -0,0 +1,10 @@
+FIXEDFILEINFO_FileVersion=FIXEDFILEINFO_FILE_VERSION
+FIXEDFILEINFO_ProductVersion=FIXEDFILEINFO_FILE_VERSION
+CompanyName=COMPANY_NAME
+FileDescription=Installer of FILE_DESCRIPTION
+FileVersion=FILE_VERSION
+InternalName=INTERNAL_NAME
+LegalCopyright=LEGAL_COPYRIGHT
+OriginalFilename=ORIGINAL_FILENAME
+ProductName=PRODUCT_NAME Installer
+ProductVersion=FILE_VERSION
diff --git a/src/jdk.incubator.jpackage/windows/classes/jdk/incubator/jpackage/internal/resources/WinLauncher.template b/src/jdk.incubator.jpackage/windows/classes/jdk/incubator/jpackage/internal/resources/WinLauncher.template
index d17a31662d0..2c5d227a000 100644
--- a/src/jdk.incubator.jpackage/windows/classes/jdk/incubator/jpackage/internal/resources/WinLauncher.template
+++ b/src/jdk.incubator.jpackage/windows/classes/jdk/incubator/jpackage/internal/resources/WinLauncher.template
@@ -1,3 +1,5 @@
+FIXEDFILEINFO_FileVersion=FIXEDFILEINFO_FILE_VERSION
+FIXEDFILEINFO_ProductVersion=FIXEDFILEINFO_FILE_VERSION
CompanyName=COMPANY_NAME
FileDescription=FILE_DESCRIPTION
FileVersion=FILE_VERSION
@@ -5,4 +7,4 @@ InternalName=INTERNAL_NAME
LegalCopyright=LEGAL_COPYRIGHT
OriginalFilename=ORIGINAL_FILENAME
ProductName=PRODUCT_NAME
-ProductVersion=PRODUCT_VERSION
+ProductVersion=FILE_VERSION
diff --git a/src/jdk.incubator.jpackage/windows/native/libjpackage/ByteBuffer.cpp b/src/jdk.incubator.jpackage/windows/native/libjpackage/ByteBuffer.cpp
deleted file mode 100644
index b6dc39dd2b2..00000000000
--- a/src/jdk.incubator.jpackage/windows/native/libjpackage/ByteBuffer.cpp
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * Copyright (c) 2015, 2019, 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.
- */
-
-#include "ByteBuffer.h"
-
-#include
-
-ByteBuffer::ByteBuffer() {
- buffer.reserve(1024);
-}
-
-ByteBuffer::~ByteBuffer() {
-}
-
-LPBYTE ByteBuffer::getPtr() {
- return &buffer[0];
-}
-
-size_t ByteBuffer::getPos() {
- return buffer.size();
-}
-
-void ByteBuffer::AppendString(wstring str) {
- size_t len = (str.size() + 1) * sizeof (WCHAR);
- AppendBytes((BYTE*) str.c_str(), len);
-}
-
-void ByteBuffer::AppendWORD(WORD word) {
- AppendBytes((BYTE*) & word, sizeof (WORD));
-}
-
-void ByteBuffer::Align(size_t bytesNumber) {
- size_t pos = getPos();
- if (pos % bytesNumber) {
- DWORD dwNull = 0;
- size_t len = bytesNumber - pos % bytesNumber;
- AppendBytes((BYTE*) & dwNull, len);
- }
-}
-
-void ByteBuffer::AppendBytes(BYTE* ptr, size_t len) {
- buffer.insert(buffer.end(), ptr, ptr + len);
-}
-
-void ByteBuffer::ReplaceWORD(size_t offset, WORD word) {
- ReplaceBytes(offset, (BYTE*) & word, sizeof (WORD));
-}
-
-void ByteBuffer::ReplaceBytes(size_t offset, BYTE* ptr, size_t len) {
- for (size_t i = 0; i < len; i++) {
- buffer[offset + i] = *(ptr + i);
- }
-}
diff --git a/src/jdk.incubator.jpackage/windows/native/libjpackage/IconSwap.cpp b/src/jdk.incubator.jpackage/windows/native/libjpackage/IconSwap.cpp
index a09ba7ef94f..b7d604fc659 100644
--- a/src/jdk.incubator.jpackage/windows/native/libjpackage/IconSwap.cpp
+++ b/src/jdk.incubator.jpackage/windows/native/libjpackage/IconSwap.cpp
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2012, 2019, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2012, 2020, 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
@@ -96,7 +96,7 @@ void PrintError() {
// Note: We do not check here that iconTarget is valid icon.
// Java code will already do this for us.
-bool ChangeIcon(wstring iconTarget, wstring launcher) {
+bool ChangeIcon(HANDLE update, const wstring& iconTarget) {
WORD language = MAKELANGID(LANG_ENGLISH, SUBLANG_DEFAULT);
HANDLE icon = CreateFile(iconTarget.c_str(), GENERIC_READ, 0, NULL,
@@ -154,15 +154,6 @@ bool ChangeIcon(wstring iconTarget, wstring launcher) {
}
// Store images in .EXE
- HANDLE update = BeginUpdateResource(launcher.c_str(), FALSE);
- if (update == NULL) {
- free(lpid);
- free(lpgid);
- CloseHandle(icon);
- PrintError();
- return false;
- }
-
for (int i = 0; i < lpid->idCount; i++) {
LPBYTE lpBuffer = (LPBYTE) malloc(lpid->idEntries[i].dwBytesInRes);
SetFilePointer(icon, lpid->idEntries[i].dwImageOffset,
@@ -195,10 +186,5 @@ bool ChangeIcon(wstring iconTarget, wstring launcher) {
free(lpgid);
- if (EndUpdateResource(update, FALSE) == FALSE) {
- PrintError();
- return false;
- }
-
return true;
}
diff --git a/src/jdk.incubator.jpackage/windows/native/libjpackage/IconSwap.h b/src/jdk.incubator.jpackage/windows/native/libjpackage/IconSwap.h
index 75390273bb1..d653e0e2070 100644
--- a/src/jdk.incubator.jpackage/windows/native/libjpackage/IconSwap.h
+++ b/src/jdk.incubator.jpackage/windows/native/libjpackage/IconSwap.h
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2016, 2019, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2016, 2020, 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
@@ -28,9 +28,7 @@
#include
-using namespace std;
-
-bool ChangeIcon(wstring iconTarget, wstring launcher);
+bool ChangeIcon(HANDLE update, const std::wstring& iconTarget);
#endif // ICONSWAP_H
diff --git a/src/jdk.incubator.jpackage/windows/native/libjpackage/JniUtils.cpp b/src/jdk.incubator.jpackage/windows/native/libjpackage/JniUtils.cpp
new file mode 100644
index 00000000000..0549417f47d
--- /dev/null
+++ b/src/jdk.incubator.jpackage/windows/native/libjpackage/JniUtils.cpp
@@ -0,0 +1,83 @@
+/*
+ * Copyright (c) 2019, 2020, 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.
+ */
+
+#include "JniUtils.h"
+#include "ErrorHandling.h"
+#include "Toolbox.h"
+
+
+namespace jni {
+
+void JniObjWithEnv::LocalRefDeleter::operator()(pointer v) {
+ if (v.env && v.obj) {
+ v.env->DeleteLocalRef(v.obj);
+ }
+}
+
+
+#ifdef TSTRINGS_WITH_WCHAR
+std::wstring toUnicodeString(JNIEnv *env, jstring val) {
+ const jchar* chars = env->GetStringChars(val, 0);
+ if (!chars) {
+ JP_THROW("GetStringChars() failed");
+ }
+
+ const auto releaseStringChars =
+ runAtEndOfScope([env, val, chars]() -> void {
+ env->ReleaseStringChars(val, chars);
+ });
+
+ const jsize len = env->GetStringLength(val);
+
+ return std::wstring(reinterpret_cast(chars), len);
+}
+
+
+jstring toJString(JNIEnv *env, const std::wstring& val) {
+ jstring result = env->NewString(
+ reinterpret_cast(val.c_str()), jsize(val.size()));
+ if (!result) {
+ JP_THROW("NewString() failed");
+ }
+ return result;
+}
+#endif
+
+
+tstring_array toUnicodeStringArray(JNIEnv *env, jobjectArray val) {
+ tstring_array result;
+
+ const jsize len = env->GetArrayLength(val);
+ for (int i = 0; i < len; ++i) {
+ LocalRef localRef(JniObjWithEnv(env,
+ env->GetObjectArrayElement(val, i)));
+ result.push_back(toUnicodeString(env,
+ static_cast(localRef.get().obj)));
+ }
+
+ return result;
+}
+
+} // namespace jni
diff --git a/src/jdk.incubator.jpackage/windows/native/libjpackage/VersionInfoSwap.h b/src/jdk.incubator.jpackage/windows/native/libjpackage/JniUtils.h
similarity index 54%
rename from src/jdk.incubator.jpackage/windows/native/libjpackage/VersionInfoSwap.h
rename to src/jdk.incubator.jpackage/windows/native/libjpackage/JniUtils.h
index 27ad633a542..8f5f38293da 100644
--- a/src/jdk.incubator.jpackage/windows/native/libjpackage/VersionInfoSwap.h
+++ b/src/jdk.incubator.jpackage/windows/native/libjpackage/JniUtils.h
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2015, 2019, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2019, 2020, 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
@@ -23,31 +23,49 @@
* questions.
*/
-#ifndef VERSIONINFOSWAP_H
-#define VERSIONINFOSWAP_H
+#ifndef JNIUTILS_H
+#define JNIUTILS_H
-#include "ByteBuffer.h"
-#include