/*
* Copyright ( c ) 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 .
*
* 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 org.testng.annotations.*;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.FileSystem;
import java.nio.file.*;
import java.security.SecureRandom;
import java.util.Arrays;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.function .Consumer;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import static java.lang.Boolean .TRUE ;
import static java.lang.String.format;
import static java.util.stream.Collectors.joining;
import static org.testng.Assert .*;
/**
* @ test
* @ bug 8230870
* @ summary Test ZIP Filesystem behavior with ~ 64 k entries
* @ modules jdk . zipfs
* @ run testng LargeEntriesTest
*/
public class LargeEntriesTest {
private static final Path HERE = Path.of("." );
/**
* Number of ZIP entries which results in the use of ZIP64
*/
private static final int ZIP64_ENTRIES = 65535 ;
/**
* Classes and MANIFEST attribute used for invoking Main via java - jar
*/
private static final String MANIFEST_MAIN_CLASS = "LargeEntriesTest$Main" ;
private static final String MAIN_CLASS = "LargeEntriesTest$Main.class" ;
private static final String THIS_CLASS = "LargeEntriesTest.class" ;
/**
* Number of entries included in the JAR file including META - INF ,
* MANIFEST . MF , and the classes associated with this test
*/
private static final int ADDITIONAL_JAR_ENTRIES = 4 ;
/**
* Value used for creating the required entries in a ZIP or JAR file
*/
private static final String ZIP_FILE_VALUE = "US Open 2019" ;
private static final byte [] ZIP_FILE_ENTRY =
ZIP_FILE_VALUE.getBytes(StandardCharsets.UTF_8);
/**
* Location of the classes to be added to the JAR file
*/
static final Path TEST_CLASSES = Paths.get(System.getProperty("test.classes" , "." ));
private static final SecureRandom random = new SecureRandom();
/**
* Fields used for timing runs
*/
private static int testNumberRunning;
private static long runningTestTime;
private static long startTestRunTime;
private static final double NANOS_IN_SECOND = 1 _000 _000 _000 .0 ;
@BeforeTest(enabled = false )
public void beforeTest() {
startTestRunTime = System.nanoTime();
}
@AfterTest(enabled = false )
public void afterTest() {
long endTestRunTime = System.nanoTime();
long duration = endTestRunTime - startTestRunTime;
System.out.printf("#### Completed test run, total running time: %.4f in seconds%n" ,
duration / NANOS_IN_SECOND);
}
@BeforeMethod(enabled = false )
public static void beforeMethod() {
runningTestTime = System.nanoTime();
System.out.printf("**** Starting test number: %s%n" , testNumberRunning);
}
@AfterMethod(enabled = false )
public void afterMethod() {
long endRunningTestTime = System.nanoTime();
long duration = endRunningTestTime - runningTestTime;
System.out.printf("**** Completed test number: %s, Time: %.4f%n" ,
testNumberRunning, duration / NANOS_IN_SECOND);
testNumberRunning++;
}
/**
* Validate that you can create a ZIP file with and without compression
* and that the ZIP file is created using ZIP64 if there are 65535 or
* more entries .
*
* @ param env Properties used for creating the ZIP Filesystem
* @ param compression Indicates whether the files are DEFLATED ( default )
* or STORED
* @ throws Exception If an error occurs during the creation , verification or
* deletion of the ZIP file
*/
@Test(dataProvider = "zipfsMap" , enabled = true )
public void testZip(Map<String, String> env, int compression) throws Exception {
System.out.printf("ZIP FS Map = %s, Compression mode= %s%n " ,
formatMap(env), compression);
for (int entries = ZIP64_ENTRIES - 1 ; entries < ZIP64_ENTRIES + 2 ; entries++) {
Path zipfile = generatePath(HERE, "test" , ".zip" );
Files.deleteIfExists(zipfile);
createZipFile(zipfile, env, entries);
verify(zipfile, compression, entries,
isTrue(env, "forceZIP64End" ), 0 );
Files.deleteIfExists(zipfile);
}
}
/**
* Validate that when the forceZIP64End property is set to true ,
* that ZIP64 is used .
*
* @ param env Properties used for creating the ZIP Filesystem
* @ param compression Indicates whether the files are DEFLATED ( default )
* or STORED
* @ throws Exception If an error occurs during the creation , verification or
* deletion of the ZIP file
*/
@Test(dataProvider = "zip64Map" , enabled = true )
public void testForceZIP64End(Map<String, String> env, int compression) throws Exception {
System.out.printf("ZIP FS Map = %s, Compression mode= %s%n " ,
formatMap(env), compression);
// Generate a ZIP file path
Path zipfile = generatePath(HERE, "test" , ".zip" );
Files.deleteIfExists(zipfile);
createZipFile(zipfile, env, 1 );
verify(zipfile, compression, 1 , isTrue(env, "forceZIP64End" ), 0 );
Files.deleteIfExists(zipfile);
}
/**
* Validate that you can create a JAR file with and without compression
* and that the JAR file is created using ZIP64 if there are 65535 or
* more entries .
*
* @ param env Properties used for creating the ZIP Filesystem
* @ param compression Indicates whether the files are DEFLATED ( default )
* or STORED
* @ throws Exception If an error occurs during the creation , verification or
* deletion of the JAR file
*/
@Test(dataProvider = "zipfsMap" , enabled = true )
public void testJar(Map<String, String> env, int compression) throws Exception {
for (int entries = ZIP64_ENTRIES - 1 ; entries < ZIP64_ENTRIES + 2 ; entries++) {
Path jar = generatePath(HERE, "test" , ".jar" );
Files.deleteIfExists(jar);
createJarFile(jar, env, entries);
// Now run the Main-Class specified the Manifest
runJar(jar.getFileName().toString()).assertSuccess()
.validate(r -> assertTrue(r.output.matches("\\AMain\\Z" )));
verify(jar, compression, entries, isTrue(env, "forceZIP64End" ),
ADDITIONAL_JAR_ENTRIES);
Files.deleteIfExists(jar);
}
}
/**
* Create a ZIP File System using the specified properties and a ZIP file
* with the specified number of entries
*
* @ param zipFile Path to the ZIP File to create
* @ param env Properties used for creating the ZIP Filesystem
* @ param entries Number of entries to add to the ZIP File
* @ throws IOException If an error occurs while creating the ZIP file
*/
private void createZipFile(Path zipFile, Map<String, String> env,
int entries) throws IOException {
System.out.printf("Creating file = %s%n" , zipFile);
try (FileSystem zipfs =
FileSystems.newFileSystem(zipFile, env)) {
for (int i = 0 ; i < entries; i++) {
Files.writeString(zipfs.getPath("Entry-" + i), ZIP_FILE_VALUE);
}
}
}
/**
* Create a ZIP File System using the specified properties and a JAR file
* with the specified number of entries
*
* @ param zipFile Path to the JAR File to create
* @ param env Properties used for creating the ZIP Filesystem
* @ param entries Number of entries to add to the JAR File
* @ throws IOException If an error occurs while creating the JAR file
*/
private void createJarFile(Path zipFile, Map<String, String> env,
int entries) throws IOException {
System.out.printf("Creating file = %s%n" , zipFile);
String jdkVendor = System.getProperty("java.vendor" );
String jdkVersion = System.getProperty("java.version" );
String manifest = "Manifest-Version: 1.0"
+ System.lineSeparator()
+ "Main-Class: " + MANIFEST_MAIN_CLASS
+ System.lineSeparator()
+ "Created-By: " + jdkVersion + " (" + jdkVendor + ")" ;
try (FileSystem zipfs =
FileSystems.newFileSystem(zipFile, env);
InputStream in = new ByteArrayInputStream(manifest.getBytes())) {
// Get ZIP FS path to META-INF/MANIFEST.MF
Path metadir = zipfs.getPath("/" , "META-INF" );
Path manifestFile = metadir.resolve("MANIFEST.MF" );
// Create META-INF directory if it does not already exist and
// add the MANIFEST.MF file
if (!Files.exists(metadir))
Files.createDirectory(zipfs.getPath("/" , "META-INF" ));
Files.copy(in, manifestFile);
// Add the needed test classes
Path target = zipfs.getPath("/" );
Files.copy(TEST_CLASSES.resolve(MAIN_CLASS),
target.resolve(MAIN_CLASS));
Files.copy(TEST_CLASSES.resolve(THIS_CLASS),
target.resolve(THIS_CLASS));
// Add the remaining entries that are required
for (int i = ADDITIONAL_JAR_ENTRIES; i < entries; i++) {
Files.writeString(zipfs.getPath("Entry-" + i), ZIP_FILE_VALUE);
}
}
}
/*
* DataProvider used to validate that you can create a ZIP file with and
* without compression .
*/
@DataProvider(name = "zipfsMap" )
private Object[][] zipfsMap() {
return new Object[][]{
{Map.of("create" , "true" ), ZipEntry.DEFLATED},
{Map.of("create" , "true" , "noCompression" , "true" ),
ZipEntry.STORED},
{Map.of("create" , "true" , "noCompression" , "false" ),
ZipEntry.DEFLATED}
};
}
/*
* DataProvider used to validate that you can create a ZIP file with / without
* ZIP64 format extensions
*/
@DataProvider(name = "zip64Map" )
private Object[][] zip64Map() {
return new Object[][]{
{Map.of("create" , "true" , "forceZIP64End" , "true" ),
ZipEntry.DEFLATED},
{Map.of("create" , "true" , "noCompression" , "true" ,
"forceZIP64End" , "true" ), ZipEntry.STORED},
{Map.of("create" , "true" , "noCompression" , "false" ,
"forceZIP64End" , "false" ), ZipEntry.DEFLATED},
{Map.of("create" , "true" , "noCompression" , "true" ,
"forceZIP64End" , "false" ), ZipEntry.STORED}
};
}
/**
* Verify that the given path is a ZIP file containing the
* expected entries .
*
* @ param zipfile ZIP file to be validated
* @ param method Expected Compression method : STORED or DEFLATED
* @ param entries Number of expected entries
* @ param isZip64Forced true if ZIP64 use is being forced ; false otherwise
* @ param start Starting number for verifying entries
* @ throws Exception If an error occurs while examining the ZIP file
*/
private static void verify(Path zipfile, int method, int entries,
boolean isZip64Forced, int start) throws Exception {
// check entries with ZIP API
try (ZipFile zf = new ZipFile(zipfile.toFile())) {
// check entry count
assertEquals(entries, zf.size());
// check compression method and content of each entry
for (int i = start; i < entries; i++) {
ZipEntry ze = zf.getEntry("Entry-" + i);
assertNotNull(ze);
assertEquals(method, ze.getMethod());
try (InputStream is = zf.getInputStream(ze)) {
byte [] bytes = is.readAllBytes();
assertTrue(Arrays.equals(bytes, ZIP_FILE_ENTRY));
}
}
}
// check entries with FileSystem API
try (FileSystem fs = FileSystems.newFileSystem(zipfile)) {
// check entry count
Path top = fs.getPath("/" );
long count = Files.find(top, Integer.MAX_VALUE, (path, attrs) ->
attrs.isRegularFile() || (attrs.isDirectory() &&
path.getFileName() != null &&
path.getFileName().toString().equals("META-INF" )))
.count();
assertEquals(entries, count);
// check content of each entry
for (int i = start; i < entries; i++) {
Path file = fs.getPath("Entry-" + i);
byte [] bytes = Files.readAllBytes(file);
assertTrue(Arrays.equals(bytes, ZIP_FILE_ENTRY));
}
}
// Check for a ZIP64 End of Central Directory Locator
boolean foundZip64 = usesZip64(zipfile.toFile());
// Is ZIP64 required?
boolean requireZip64 = entries >= ZIP64_ENTRIES || isZip64Forced;
System.out.printf(" isZip64Forced = %s, foundZip64= %s, requireZip64= %s%n" ,
isZip64Forced, foundZip64, requireZip64);
assertEquals(requireZip64, foundZip64);
}
/**
* Determine if the specified property name = true / " true "
*
* @ param env ZIP Filesystem Map
* @ param name property to validate
* @ return true if the property value is set to true / " true " ; false otherwise
*/
private static boolean isTrue(Map<String, ?> env, String name) {
return "true" .equals(env.get(name)) || TRUE .equals(env.get(name));
}
/**
* Check to see if the ZIP64 End of Central Directory Locator has been found
*
* @ param b byte array to check for the locator in
* @ param n starting offset for the search
* @ return true if the Zip64 End of Central Directory Locator is found ; false
* otherwise
*/
private static boolean end64SigAt(byte [] b, int n) {
return b[n] == 'P' & b[n + 1 ] == 'K' & b[n + 2 ] == 6 & b[n + 3 ] == 6 ;
}
/**
* Utility method that checks the ZIP file for the use of the ZIP64
* End of Central Directory Locator
*
* @ param zipFile ZIP file to check
* @ return true if the ZIP64 End of Central Directory Locator is found ; false
* otherwise
* * @ throws Exception If an error occurs while traversing the file
*/
private static boolean usesZip64(File zipFile) throws Exception {
try (RandomAccessFile raf = new RandomAccessFile(zipFile, "r" )) {
byte [] buf = new byte [4096 ];
long seeklen = raf.length() - buf.length;
if (seeklen < 0 )
seeklen = 0 ;
raf.seek(seeklen);
raf.read(buf);
for (int i = 0 ; i < buf.length - 4 ; i++) {
// Is there a ZIP64 End of Central Directory Locator?
if (end64SigAt(buf, i)) {
return true ;
}
}
}
return false ;
}
/**
* Generate a temporary file Path
*
* @ param dir Directory used to create the path
* @ param prefix The prefix string used to create the path
* @ param suffix The suffix string used to create the path
* @ return Path that was generated
*/
private static Path generatePath(Path dir, String prefix, String suffix) {
long n = random.nextLong();
String s = prefix + Long .toUnsignedString(n) + suffix;
Path name = dir.getFileSystem().getPath(s);
// the generated name should be a simple file name
if (name.getParent() != null )
throw new IllegalArgumentException("Invalid prefix or suffix" );
return dir.resolve(name);
}
/**
* Utility method to return a formatted String of the key : value entries for
* a Map
*
* @ param env Map to format
* @ return Formatted string of the Map entries
*/
private static String formatMap(Map<String, String> env) {
return env.entrySet().stream()
.map(e -> format("(%s:%s)" , e.getKey(), e.getValue()))
.collect(joining(", " ));
}
/**
* Validates that a jar created using ZIP FS can be used by the java
* tool to run a program specified in the Main - Class Manifest attribute
*
* @ param jarFile Name of the JAR file to specify to the - jar option
* @ return A Result object representing the return code and output from the
* program that was invoked
*/
private static Result runJar(String jarFile) {
String javaHome = System.getProperty("java.home" );
String java = Paths.get(javaHome, "bin" , "java" ).toString();
String[] cmd = {java, "-jar" , jarFile};
String output;
ProcessBuilder pb = new ProcessBuilder(cmd);
Process p;
try {
p = pb.start();
output = toString(p.getInputStream(), p.getErrorStream());
p.waitFor();
} catch (IOException | InterruptedException e) {
throw new RuntimeException(
format("Error invoking: '%s', Exception= %s" , pb.command(), e));
}
return new Result(p.exitValue(), output);
}
/**
* Utility method to combine the output and error streams for the Process
* started by ProcessBuilder
*
* @ param is Process Outputstream
* @ param is2 Process ErrorStream
* @ return String representing the combination of the OutputStream & ErrorStream
* @ throws IOException If an error occurs while combining the streams
*/
private static String toString(InputStream is, InputStream is2) throws IOException {
try (ByteArrayOutputStream dst = new ByteArrayOutputStream();
InputStream concatenated = new SequenceInputStream(is, is2)) {
concatenated.transferTo(dst);
return new String(dst.toByteArray(), StandardCharsets.UTF_8);
}
}
/**
* Wrapper class used to verify the results from a ProcessBuilder invocation
*/
private static class Result {
final int ec; // Return code for command that was executed
final String output; // Output from the command that was executed
/**
* Constructor
*
* @ param ec Return code from the ProcessBuilder invocation
* @ param output ProcessBuilder output to be validated
*/
private Result(int ec, String output) {
this .ec = ec;
this .output = output;
}
/**
* Validate that the command that was executed completed successfully
*
* @ return This Result object
*/
Result assertSuccess() {
assertEquals(ec, 0 , format("Expected ec 0, received: %s, output [%s]" , ec, output));
return this ;
}
/**
* Validate that the expected result is received
*
* @ param r The operation to perform
* @ return This Result object
*/
Result validate(Consumer<Result> r) {
r.accept(this );
return this ;
}
}
/**
* Trivial class used to validate that a JAR created using ZIP FS
* can be successfully executed
*/
public static class Main {
public static void main(String[] args) {
System.out.print("Main" );
}
}
}
Messung V0.5 in Prozent C=91 H=75 G=83
¤ Dauer der Verarbeitung: 0.13 Sekunden
(vorverarbeitet am 2026-06-10)
¤
*© Formatika GbR, Deutschland