diff --git a/spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/archive/JarFileArchive.java b/spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/archive/JarFileArchive.java index d5b0bb9e34b..77dee2bc5fb 100755 --- a/spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/archive/JarFileArchive.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/archive/JarFileArchive.java @@ -32,7 +32,6 @@ import java.util.UUID; import java.util.jar.JarEntry; import java.util.jar.Manifest; -import org.springframework.boot.loader.data.RandomAccessData.ResourceAccess; import org.springframework.boot.loader.jar.JarFile; /** @@ -145,8 +144,7 @@ public class JarFileArchive implements Archive { } private void unpack(JarEntry entry, File file) throws IOException { - try (InputStream inputStream = this.jarFile.getInputStream(entry, - ResourceAccess.ONCE); + try (InputStream inputStream = this.jarFile.getInputStream(entry); OutputStream outputStream = new FileOutputStream(file)) { byte[] buffer = new byte[BUFFER_SIZE]; int bytesRead; diff --git a/spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/data/ByteArrayRandomAccessData.java b/spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/data/ByteArrayRandomAccessData.java deleted file mode 100644 index 4f7bcabae9f..00000000000 --- a/spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/data/ByteArrayRandomAccessData.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright 2012-2017 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.loader.data; - -import java.io.ByteArrayInputStream; -import java.io.InputStream; - -/** - * {@link RandomAccessData} implementation backed by a byte array. - * - * @author Phillip Webb - */ -public class ByteArrayRandomAccessData implements RandomAccessData { - - private final byte[] bytes; - - private final long offset; - - private final long length; - - public ByteArrayRandomAccessData(byte[] bytes) { - this(bytes, 0, (bytes == null ? 0 : bytes.length)); - } - - public ByteArrayRandomAccessData(byte[] bytes, long offset, long length) { - this.bytes = (bytes == null ? new byte[0] : bytes); - this.offset = offset; - this.length = length; - } - - @Override - public InputStream getInputStream(ResourceAccess access) { - return new ByteArrayInputStream(this.bytes, (int) this.offset, (int) this.length); - } - - @Override - public RandomAccessData getSubsection(long offset, long length) { - return new ByteArrayRandomAccessData(this.bytes, this.offset + offset, length); - } - - @Override - public long getSize() { - return this.length; - } - -} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/data/RandomAccessData.java b/spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/data/RandomAccessData.java index e6d1e445466..5db8a23b90a 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/data/RandomAccessData.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/data/RandomAccessData.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2017 the original author or authors. + * Copyright 2012-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -30,11 +30,10 @@ public interface RandomAccessData { /** * Returns an {@link InputStream} that can be used to read the underlying data. The * caller is responsible close the underlying stream. - * @param access hint indicating how the underlying data should be accessed * @return a new input stream that can be used to read the underlying data. * @throws IOException if the stream cannot be opened */ - InputStream getInputStream(ResourceAccess access) throws IOException; + InputStream getInputStream() throws IOException; /** * Returns a new {@link RandomAccessData} for a specific subsection of this data. @@ -45,27 +44,25 @@ public interface RandomAccessData { RandomAccessData getSubsection(long offset, long length); /** - * Returns the size of the data. - * @return the size + * Reads all the data and returns it as a byte array. + * @return the data + * @throws IOException if the data cannot be read */ - long getSize(); + byte[] read() throws IOException; /** - * Lock modes for accessing the underlying resource. + * Reads the {@code length} bytes of data starting at the given {@code offset}. + * @param offset the offset from which data should be read + * @param length the number of bytes to be read + * @return the data + * @throws IOException if the data cannot be read */ - enum ResourceAccess { - - /** - * Obtain access to the underlying resource once and keep it until the stream is - * closed. - */ - ONCE, - - /** - * Obtain access to the underlying resource on each read, releasing it when done. - */ - PER_READ + byte[] read(long offset, long length) throws IOException; - } + /** + * Returns the size of the data. + * @return the size + */ + long getSize(); } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/data/RandomAccessDataFile.java b/spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/data/RandomAccessDataFile.java index 6b9ba1db2fd..597f9cd549c 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/data/RandomAccessDataFile.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/data/RandomAccessDataFile.java @@ -17,25 +17,22 @@ package org.springframework.boot.loader.data; import java.io.File; +import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.RandomAccessFile; -import java.util.Queue; -import java.util.concurrent.ConcurrentLinkedQueue; -import java.util.concurrent.Semaphore; /** * {@link RandomAccessData} implementation backed by a {@link RandomAccessFile}. * * @author Phillip Webb + * @author Andy Wilkinson */ public class RandomAccessDataFile implements RandomAccessData { - private static final int DEFAULT_CONCURRENT_READS = 4; - private final File file; - private final FilePool filePool; + private final RandomAccessFile randomAccessFile; private final long offset; @@ -45,30 +42,19 @@ public class RandomAccessDataFile implements RandomAccessData { * Create a new {@link RandomAccessDataFile} backed by the specified file. * @param file the underlying file * @throws IllegalArgumentException if the file is null or does not exist - * @see #RandomAccessDataFile(File, int) */ public RandomAccessDataFile(File file) { - this(file, DEFAULT_CONCURRENT_READS); - } - - /** - * Create a new {@link RandomAccessDataFile} backed by the specified file. - * @param file the underlying file - * @param concurrentReads the maximum number of concurrent reads allowed on the - * underlying file before blocking - * @throws IllegalArgumentException if the file is null or does not exist - * @see #RandomAccessDataFile(File) - */ - public RandomAccessDataFile(File file, int concurrentReads) { if (file == null) { throw new IllegalArgumentException("File must not be null"); } - if (!file.exists()) { + try { + this.randomAccessFile = new RandomAccessFile(file, "r"); + } + catch (FileNotFoundException ex) { throw new IllegalArgumentException( String.format("File %s must exist", file.getAbsolutePath())); } this.file = file; - this.filePool = new FilePool(file, concurrentReads); this.offset = 0L; this.length = file.length(); } @@ -76,15 +62,16 @@ public class RandomAccessDataFile implements RandomAccessData { /** * Private constructor used to create a {@link #getSubsection(long, long) subsection}. * @param file the underlying file - * @param pool the underlying pool + * @param randomAccessFile the random access file from which data is read * @param offset the offset of the section * @param length the length of the section */ - private RandomAccessDataFile(File file, FilePool pool, long offset, long length) { + private RandomAccessDataFile(File file, RandomAccessFile randomAccessFile, + long offset, long length) { this.file = file; - this.filePool = pool; this.offset = offset; this.length = length; + this.randomAccessFile = randomAccessFile; } /** @@ -96,8 +83,8 @@ public class RandomAccessDataFile implements RandomAccessData { } @Override - public InputStream getInputStream(ResourceAccess access) throws IOException { - return new DataInputStream(access); + public InputStream getInputStream() throws IOException { + return new DataInputStream(this.randomAccessFile); } @Override @@ -105,8 +92,23 @@ public class RandomAccessDataFile implements RandomAccessData { if (offset < 0 || length < 0 || offset + length > this.length) { throw new IndexOutOfBoundsException(); } - return new RandomAccessDataFile(this.file, this.filePool, this.offset + offset, - length); + return new RandomAccessDataFile(this.file, this.randomAccessFile, + this.offset + offset, length); + } + + @Override + public byte[] read() throws IOException { + return read(0, this.length); + } + + @Override + public byte[] read(long offset, long length) throws IOException { + byte[] bytes = new byte[(int) length]; + synchronized (this.randomAccessFile) { + this.randomAccessFile.seek(this.offset + offset); + this.randomAccessFile.read(bytes, 0, (int) length); + } + return bytes; } @Override @@ -115,7 +117,7 @@ public class RandomAccessDataFile implements RandomAccessData { } public void close() throws IOException { - this.filePool.close(); + } /** @@ -128,11 +130,8 @@ public class RandomAccessDataFile implements RandomAccessData { private int position; - DataInputStream(ResourceAccess access) throws IOException { - if (access == ResourceAccess.ONCE) { - this.file = new RandomAccessFile(RandomAccessDataFile.this.file, "r"); - this.file.seek(RandomAccessDataFile.this.offset); - } + DataInputStream(RandomAccessFile file) throws IOException { + this.file = file; } @Override @@ -170,24 +169,15 @@ public class RandomAccessDataFile implements RandomAccessData { if (cappedLen <= 0) { return -1; } - RandomAccessFile file = this.file; - try { - if (file == null) { - file = RandomAccessDataFile.this.filePool.acquire(); - file.seek(RandomAccessDataFile.this.offset + this.position); - } + synchronized (this.file) { + this.file.seek(RandomAccessDataFile.this.offset + this.position); if (b == null) { - int rtn = file.read(); + int rtn = this.file.read(); moveOn(rtn == -1 ? 0 : 1); return rtn; } else { - return (int) moveOn(file.read(b, off, cappedLen)); - } - } - finally { - if (this.file == null && file != null) { - RandomAccessDataFile.this.filePool.release(file); + return (int) moveOn(this.file.read(b, off, cappedLen)); } } } @@ -197,13 +187,6 @@ public class RandomAccessDataFile implements RandomAccessData { return (n <= 0 ? 0 : moveOn(cap(n))); } - @Override - public void close() throws IOException { - if (this.file != null) { - this.file.close(); - } - } - /** * Cap the specified value such that it cannot exceed the number of bytes * remaining. @@ -226,55 +209,4 @@ public class RandomAccessDataFile implements RandomAccessData { } - /** - * Manage a pool that can be used to perform concurrent reads on the underlying - * {@link RandomAccessFile}. - */ - static class FilePool { - - private final File file; - - private final int size; - - private final Semaphore available; - - private final Queue files; - - FilePool(File file, int size) { - this.file = file; - this.size = size; - this.available = new Semaphore(size); - this.files = new ConcurrentLinkedQueue<>(); - } - - public RandomAccessFile acquire() throws IOException { - this.available.acquireUninterruptibly(); - RandomAccessFile file = this.files.poll(); - if (file != null) { - return file; - } - return new RandomAccessFile(this.file, "r"); - } - - public void release(RandomAccessFile file) { - this.files.add(file); - this.available.release(); - } - - public void close() throws IOException { - this.available.acquireUninterruptibly(this.size); - try { - RandomAccessFile pooledFile = this.files.poll(); - while (pooledFile != null) { - pooledFile.close(); - pooledFile = this.files.poll(); - } - } - finally { - this.available.release(this.size); - } - } - - } - } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/jar/AsciiBytes.java b/spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/jar/AsciiBytes.java index d828c2aacc7..dcf4da07f4f 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/jar/AsciiBytes.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/jar/AsciiBytes.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2017 the original author or authors. + * Copyright 2012-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,6 +27,8 @@ import java.nio.charset.StandardCharsets; */ final class AsciiBytes { + private static final String EMPTY_STRING = ""; + private static final int[] EXCESS = { 0x0, 0x1080, 0x96, 0x1c82080 }; private final byte[] bytes; @@ -123,8 +125,13 @@ final class AsciiBytes { @Override public String toString() { if (this.string == null) { - this.string = new String(this.bytes, this.offset, this.length, - StandardCharsets.UTF_8); + if (this.length == 0) { + this.string = EMPTY_STRING; + } + else { + this.string = new String(this.bytes, this.offset, this.length, + StandardCharsets.UTF_8); + } } return this.string; } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/jar/Bytes.java b/spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/jar/Bytes.java index 946211d74bd..4344c843ea9 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/jar/Bytes.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/jar/Bytes.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2017 the original author or authors. + * Copyright 2012-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,12 +16,6 @@ package org.springframework.boot.loader.jar; -import java.io.IOException; -import java.io.InputStream; - -import org.springframework.boot.loader.data.RandomAccessData; -import org.springframework.boot.loader.data.RandomAccessData.ResourceAccess; - /** * Utilities for dealing with bytes from ZIP files. * @@ -29,45 +23,9 @@ import org.springframework.boot.loader.data.RandomAccessData.ResourceAccess; */ final class Bytes { - private static final byte[] EMPTY_BYTES = new byte[] {}; - private Bytes() { } - public static byte[] get(RandomAccessData data) throws IOException { - try (InputStream inputStream = data.getInputStream(ResourceAccess.ONCE)) { - return get(inputStream, data.getSize()); - } - } - - public static byte[] get(InputStream inputStream, long length) throws IOException { - if (length == 0) { - return EMPTY_BYTES; - } - byte[] bytes = new byte[(int) length]; - if (!fill(inputStream, bytes)) { - throw new IOException("Unable to read bytes"); - } - return bytes; - } - - public static boolean fill(InputStream inputStream, byte[] bytes) throws IOException { - return fill(inputStream, bytes, 0, bytes.length); - } - - private static boolean fill(InputStream inputStream, byte[] bytes, int offset, - int length) throws IOException { - while (length > 0) { - int read = inputStream.read(bytes, offset, length); - if (read == -1) { - return false; - } - offset += read; - length = -read; - } - return true; - } - public static long littleEndianValue(byte[] bytes, int offset, int length) { long value = 0; for (int i = length - 1; i >= 0; i--) { diff --git a/spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/jar/CentralDirectoryEndRecord.java b/spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/jar/CentralDirectoryEndRecord.java index d30140d5793..00918f03479 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/jar/CentralDirectoryEndRecord.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/jar/CentralDirectoryEndRecord.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2017 the original author or authors. + * Copyright 2012-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -74,7 +74,7 @@ class CentralDirectoryEndRecord { private byte[] createBlockFromEndOfData(RandomAccessData data, int size) throws IOException { int length = (int) Math.min(data.getSize(), size); - return Bytes.get(data.getSubsection(data.getSize() - length, length)); + return data.read(data.getSize() - length, length); } private boolean isValid() { diff --git a/spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/jar/CentralDirectoryFileHeader.java b/spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/jar/CentralDirectoryFileHeader.java index 8bb61a66b0e..1f3d06be1b4 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/jar/CentralDirectoryFileHeader.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/jar/CentralDirectoryFileHeader.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2017 the original author or authors. + * Copyright 2012-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,8 +17,8 @@ package org.springframework.boot.loader.jar; import java.io.IOException; -import java.util.Calendar; -import java.util.GregorianCalendar; +import java.time.LocalDateTime; +import java.time.ZoneId; import org.springframework.boot.loader.data.RandomAccessData; @@ -75,8 +75,8 @@ final class CentralDirectoryFileHeader implements FileHeader { // Load variable part dataOffset += 46; if (variableData != null) { - data = Bytes.get(variableData.getSubsection(variableOffset + 46, - nameLength + extraLength + commentLength)); + data = variableData.read(variableOffset + 46, + nameLength + extraLength + commentLength); dataOffset = 0; } this.name = new AsciiBytes(data, dataOffset, (int) nameLength); @@ -115,27 +115,24 @@ final class CentralDirectoryFileHeader implements FileHeader { } public long getTime() { - long date = Bytes.littleEndianValue(this.header, this.headerOffset + 14, 2); - long time = Bytes.littleEndianValue(this.header, this.headerOffset + 12, 2); - return decodeMsDosFormatDateTime(date, time).getTimeInMillis(); + long datetime = Bytes.littleEndianValue(this.header, this.headerOffset + 12, 4); + return decodeMsDosFormatDateTime(datetime); } /** * Decode MS-DOS Date Time details. See * mindprod.com/jgloss/zip.html for * more details of the format. - * @param date the date part - * @param time the time part - * @return a {@link Calendar} containing the decoded date. + * @param datetime the date and time + * @return the date and time as milliseconds since the epoch */ - private Calendar decodeMsDosFormatDateTime(long date, long time) { - int year = (int) ((date >> 9) & 0x7F) + 1980; - int month = (int) ((date >> 5) & 0xF) - 1; - int day = (int) (date & 0x1F); - int hours = (int) ((time >> 11) & 0x1F); - int minutes = (int) ((time >> 5) & 0x3F); - int seconds = (int) ((time << 1) & 0x3E); - return new GregorianCalendar(year, month, day, hours, minutes, seconds); + private long decodeMsDosFormatDateTime(long datetime) { + LocalDateTime localDateTime = LocalDateTime.of( + (int) (((datetime >> 25) & 0x7f) + 1980), (int) ((datetime >> 21) & 0x0f), + (int) ((datetime >> 16) & 0x1f), (int) ((datetime >> 11) & 0x1f), + (int) ((datetime >> 5) & 0x3f), (int) ((datetime << 1) & 0x3e)); + return localDateTime.toEpochSecond( + ZoneId.systemDefault().getRules().getOffset(localDateTime)) * 1000; } public long getCrc() { @@ -176,7 +173,7 @@ final class CentralDirectoryFileHeader implements FileHeader { public static CentralDirectoryFileHeader fromRandomAccessData(RandomAccessData data, int offset, JarEntryFilter filter) throws IOException { CentralDirectoryFileHeader fileHeader = new CentralDirectoryFileHeader(); - byte[] bytes = Bytes.get(data.getSubsection(offset, 46)); + byte[] bytes = data.read(offset, 46); fileHeader.load(bytes, 0, data, offset, filter); return fileHeader; } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/jar/CentralDirectoryParser.java b/spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/jar/CentralDirectoryParser.java index 4cbb54c5e56..a29f53b2933 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/jar/CentralDirectoryParser.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/jar/CentralDirectoryParser.java @@ -26,6 +26,7 @@ import org.springframework.boot.loader.data.RandomAccessData; * Parses the central directory from a JAR file. * * @author Phillip Webb + * @author Andy Wilkinson * @see CentralDirectoryVisitor */ class CentralDirectoryParser { @@ -61,7 +62,7 @@ class CentralDirectoryParser { private void parseEntries(CentralDirectoryEndRecord endRecord, RandomAccessData centralDirectoryData) throws IOException { - byte[] bytes = Bytes.get(centralDirectoryData); + byte[] bytes = centralDirectoryData.read(0, centralDirectoryData.getSize()); CentralDirectoryFileHeader fileHeader = new CentralDirectoryFileHeader(); int dataOffset = 0; for (int i = 0; i < endRecord.getNumberOfRecords(); i++) { diff --git a/spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/jar/JarEntry.java b/spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/jar/JarEntry.java index 945bac8fd04..b5c76e907ec 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/jar/JarEntry.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/jar/JarEntry.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2017 the original author or authors. + * Copyright 2012-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -28,6 +28,7 @@ import java.util.jar.Manifest; * Extended variant of {@link java.util.jar.JarEntry} returned by {@link JarFile}s. * * @author Phillip Webb + * @author Andy Wilkinson */ class JarEntry extends java.util.jar.JarEntry implements FileHeader { @@ -49,11 +50,10 @@ class JarEntry extends java.util.jar.JarEntry implements FileHeader { setCompressedSize(header.getCompressedSize()); setMethod(header.getMethod()); setCrc(header.getCrc()); - setSize(header.getSize()); - setExtra(header.getExtra()); setComment(header.getComment().toString()); setSize(header.getSize()); setTime(header.getTime()); + setExtra(header.getExtra()); } AsciiBytes getAsciiBytesName() { diff --git a/spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/jar/JarFile.java b/spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/jar/JarFile.java index 80e5a0b75fb..dbf9051e470 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/jar/JarFile.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/jar/JarFile.java @@ -31,7 +31,6 @@ import java.util.jar.Manifest; import java.util.zip.ZipEntry; import org.springframework.boot.loader.data.RandomAccessData; -import org.springframework.boot.loader.data.RandomAccessData.ResourceAccess; import org.springframework.boot.loader.data.RandomAccessDataFile; /** @@ -45,6 +44,7 @@ import org.springframework.boot.loader.data.RandomAccessDataFile; * * * @author Phillip Webb + * @author Andy Wilkinson */ public class JarFile extends java.util.jar.JarFile { @@ -162,8 +162,7 @@ public class JarFile extends java.util.jar.JarFile { } } else { - try (InputStream inputStream = getInputStream(MANIFEST_NAME, - ResourceAccess.ONCE)) { + try (InputStream inputStream = getInputStream(MANIFEST_NAME)) { if (inputStream == null) { return null; } @@ -213,19 +212,14 @@ public class JarFile extends java.util.jar.JarFile { @Override public synchronized InputStream getInputStream(ZipEntry ze) throws IOException { - return getInputStream(ze, ResourceAccess.PER_READ); - } - - public InputStream getInputStream(ZipEntry ze, ResourceAccess access) - throws IOException { if (ze instanceof JarEntry) { - return this.entries.getInputStream((JarEntry) ze, access); + return this.entries.getInputStream((JarEntry) ze); } - return getInputStream(ze == null ? null : ze.getName(), access); + return getInputStream(ze == null ? null : ze.getName()); } - InputStream getInputStream(String name, ResourceAccess access) throws IOException { - return this.entries.getInputStream(name, access); + InputStream getInputStream(String name) throws IOException { + return this.entries.getInputStream(name); } /** @@ -333,7 +327,7 @@ public class JarFile extends java.util.jar.JarFile { // happening that often. try { try (JarInputStream inputStream = new JarInputStream( - getData().getInputStream(ResourceAccess.ONCE))) { + getData().getInputStream())) { java.util.jar.JarEntry certEntry = inputStream.getNextJarEntry(); while (certEntry != null) { inputStream.closeEntry(); diff --git a/spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/jar/JarFileEntries.java b/spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/jar/JarFileEntries.java index 5f14ac91efc..dc24840afe4 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/jar/JarFileEntries.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/jar/JarFileEntries.java @@ -27,7 +27,6 @@ import java.util.NoSuchElementException; import java.util.zip.ZipEntry; import org.springframework.boot.loader.data.RandomAccessData; -import org.springframework.boot.loader.data.RandomAccessData.ResourceAccess; /** * Provides access to entries from a {@link JarFile}. In order to reduce memory @@ -41,6 +40,7 @@ import org.springframework.boot.loader.data.RandomAccessData.ResourceAccess; * which should consume about 122K. * * @author Phillip Webb + * @author Andy Wilkinson */ class JarFileEntries implements CentralDirectoryVisitor, Iterable { @@ -177,18 +177,16 @@ class JarFileEntries implements CentralDirectoryVisitor, Iterable { return getEntry(name, JarEntry.class, true); } - public InputStream getInputStream(String name, ResourceAccess access) - throws IOException { + public InputStream getInputStream(String name) throws IOException { FileHeader entry = getEntry(name, FileHeader.class, false); - return getInputStream(entry, access); + return getInputStream(entry); } - public InputStream getInputStream(FileHeader entry, ResourceAccess access) - throws IOException { + public InputStream getInputStream(FileHeader entry) throws IOException { if (entry == null) { return null; } - InputStream inputStream = getEntryData(entry).getInputStream(access); + InputStream inputStream = getEntryData(entry).getInputStream(); if (entry.getMethod() == ZipEntry.DEFLATED) { inputStream = new ZipInflaterInputStream(inputStream, (int) entry.getSize()); } @@ -208,8 +206,8 @@ class JarFileEntries implements CentralDirectoryVisitor, Iterable { // local directory to the central directory. We need to re-read // here to skip them RandomAccessData data = this.jarFile.getData(); - byte[] localHeader = Bytes.get( - data.getSubsection(entry.getLocalHeaderOffset(), LOCAL_FILE_HEADER_SIZE)); + byte[] localHeader = data.read(entry.getLocalHeaderOffset(), + LOCAL_FILE_HEADER_SIZE); long nameLength = Bytes.littleEndianValue(localHeader, 26, 2); long extraLength = Bytes.littleEndianValue(localHeader, 28, 2); return data.getSubsection(entry.getLocalHeaderOffset() + LOCAL_FILE_HEADER_SIZE diff --git a/spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/jar/JarURLConnection.java b/spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/jar/JarURLConnection.java index c1ef506b5eb..719457ee4ce 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/jar/JarURLConnection.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/jar/JarURLConnection.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2017 the original author or authors. + * Copyright 2012-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -29,8 +29,6 @@ import java.net.URLEncoder; import java.net.URLStreamHandler; import java.security.Permission; -import org.springframework.boot.loader.data.RandomAccessData.ResourceAccess; - /** * {@link java.net.JarURLConnection} used to support {@link JarFile#getUrl()}. * @@ -170,7 +168,7 @@ final class JarURLConnection extends java.net.JarURLConnection { } connect(); InputStream inputStream = (this.jarEntryName.isEmpty() - ? this.jarFile.getData().getInputStream(ResourceAccess.ONCE) + ? this.jarFile.getData().getInputStream() : this.jarFile.getInputStream(this.jarEntry)); if (inputStream == null) { throwFileNotFound(this.jarEntryName, this.jarFile); diff --git a/spring-boot-project/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/data/ByteArrayRandomAccessDataTests.java b/spring-boot-project/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/data/ByteArrayRandomAccessDataTests.java deleted file mode 100644 index 2f19ccef3bc..00000000000 --- a/spring-boot-project/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/data/ByteArrayRandomAccessDataTests.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright 2012-2017 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.loader.data; - -import java.io.InputStream; - -import org.junit.Test; - -import org.springframework.boot.loader.data.RandomAccessData.ResourceAccess; -import org.springframework.util.FileCopyUtils; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Tests for {@link ByteArrayRandomAccessData}. - * - * @author Phillip Webb - */ -public class ByteArrayRandomAccessDataTests { - - @Test - public void testGetInputStream() throws Exception { - byte[] bytes = new byte[] { 0, 1, 2, 3, 4, 5 }; - RandomAccessData data = new ByteArrayRandomAccessData(bytes); - InputStream inputStream = data.getInputStream(ResourceAccess.PER_READ); - assertThat(FileCopyUtils.copyToByteArray(inputStream)).isEqualTo(bytes); - assertThat(data.getSize()).isEqualTo(bytes.length); - } - - @Test - public void testGetSubsection() throws Exception { - byte[] bytes = new byte[] { 0, 1, 2, 3, 4, 5 }; - RandomAccessData data = new ByteArrayRandomAccessData(bytes); - data = data.getSubsection(1, 4).getSubsection(1, 2); - InputStream inputStream = data.getInputStream(ResourceAccess.PER_READ); - assertThat(FileCopyUtils.copyToByteArray(inputStream)) - .isEqualTo(new byte[] { 2, 3 }); - assertThat(data.getSize()).isEqualTo(2L); - } - -} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/data/RandomAccessDataFileTests.java b/spring-boot-project/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/data/RandomAccessDataFileTests.java index c7d20001ba6..972bfa066b2 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/data/RandomAccessDataFileTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/data/RandomAccessDataFileTests.java @@ -18,14 +18,10 @@ package org.springframework.boot.loader.data; import java.io.File; import java.io.FileOutputStream; -import java.io.IOException; import java.io.InputStream; -import java.io.RandomAccessFile; -import java.lang.reflect.Field; import java.util.ArrayList; import java.util.Arrays; import java.util.List; -import java.util.Queue; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; @@ -36,23 +32,14 @@ import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; import org.junit.rules.TemporaryFolder; -import org.mockito.Mockito; - -import org.springframework.boot.loader.data.RandomAccessData.ResourceAccess; -import org.springframework.boot.loader.data.RandomAccessDataFile.FilePool; -import org.springframework.test.util.ReflectionTestUtils; import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.Assert.fail; -import static org.mockito.ArgumentMatchers.anyLong; -import static org.mockito.BDDMockito.willAnswer; -import static org.mockito.BDDMockito.willThrow; -import static org.mockito.Mockito.spy; /** * Tests for {@link RandomAccessDataFile}. * * @author Phillip Webb + * @author Andy Wilkinson */ public class RandomAccessDataFileTests { @@ -84,7 +71,7 @@ public class RandomAccessDataFileTests { outputStream.write(BYTES); outputStream.close(); this.file = new RandomAccessDataFile(this.tempFile); - this.inputStream = this.file.getInputStream(ResourceAccess.PER_READ); + this.inputStream = this.file.getInputStream(); } @After @@ -109,22 +96,6 @@ public class RandomAccessDataFileTests { new RandomAccessDataFile(file); } - @Test - public void fileNotNullWithConcurrentReads() { - this.thrown.expect(IllegalArgumentException.class); - this.thrown.expectMessage("File must not be null"); - new RandomAccessDataFile(null, 1); - } - - @Test - public void fileExistsWithConcurrentReads() { - File file = new File("/does/not/exist"); - this.thrown.expect(IllegalArgumentException.class); - this.thrown.expectMessage( - String.format("File %s must exist", file.getAbsolutePath())); - new RandomAccessDataFile(file, 1); - } - @Test public void inputStreamRead() throws Exception { for (int i = 0; i <= 255; i++) { @@ -224,8 +195,7 @@ public class RandomAccessDataFileTests { @Test public void subsectionZeroLength() throws Exception { RandomAccessData subsection = this.file.getSubsection(0, 0); - assertThat(subsection.getInputStream(ResourceAccess.PER_READ).read()) - .isEqualTo(-1); + assertThat(subsection.getInputStream().read()).isEqualTo(-1); } @Test @@ -245,14 +215,13 @@ public class RandomAccessDataFileTests { @Test public void subsection() throws Exception { RandomAccessData subsection = this.file.getSubsection(1, 1); - assertThat(subsection.getInputStream(ResourceAccess.PER_READ).read()) - .isEqualTo(1); + assertThat(subsection.getInputStream().read()).isEqualTo(1); } @Test public void inputStreamReadPastSubsection() throws Exception { RandomAccessData subsection = this.file.getSubsection(1, 2); - InputStream inputStream = subsection.getInputStream(ResourceAccess.PER_READ); + InputStream inputStream = subsection.getInputStream(); assertThat(inputStream.read()).isEqualTo(1); assertThat(inputStream.read()).isEqualTo(2); assertThat(inputStream.read()).isEqualTo(-1); @@ -261,7 +230,7 @@ public class RandomAccessDataFileTests { @Test public void inputStreamReadBytesPastSubsection() throws Exception { RandomAccessData subsection = this.file.getSubsection(1, 2); - InputStream inputStream = subsection.getInputStream(ResourceAccess.PER_READ); + InputStream inputStream = subsection.getInputStream(); byte[] b = new byte[3]; int amountRead = inputStream.read(b); assertThat(b).isEqualTo(new byte[] { 1, 2, 0 }); @@ -271,7 +240,7 @@ public class RandomAccessDataFileTests { @Test public void inputStreamSkipPastSubsection() throws Exception { RandomAccessData subsection = this.file.getSubsection(1, 2); - InputStream inputStream = subsection.getInputStream(ResourceAccess.PER_READ); + InputStream inputStream = subsection.getInputStream(); assertThat(inputStream.skip(3)).isEqualTo(2L); assertThat(inputStream.read()).isEqualTo(-1); } @@ -293,7 +262,7 @@ public class RandomAccessDataFileTests { for (int i = 0; i < 100; i++) { results.add(executorService.submit(() -> { InputStream subsectionInputStream = RandomAccessDataFileTests.this.file - .getSubsection(0, 256).getInputStream(ResourceAccess.PER_READ); + .getSubsection(0, 256).getInputStream(); byte[] b = new byte[256]; subsectionInputStream.read(b); return Arrays.equals(b, BYTES); @@ -304,45 +273,4 @@ public class RandomAccessDataFileTests { } } - @Test - public void close() throws Exception { - this.file.getInputStream(ResourceAccess.PER_READ).read(); - this.file.close(); - Field filePoolField = RandomAccessDataFile.class.getDeclaredField("filePool"); - filePoolField.setAccessible(true); - Object filePool = filePoolField.get(this.file); - Field filesField = filePool.getClass().getDeclaredField("files"); - filesField.setAccessible(true); - Queue queue = (Queue) filesField.get(filePool); - assertThat(queue).isEmpty(); - } - - @Test - public void seekFailuresDoNotPreventSubsequentReads() throws Exception { - FilePool filePool = (FilePool) ReflectionTestUtils.getField(this.file, - "filePool"); - FilePool spiedPool = spy(filePool); - ReflectionTestUtils.setField(this.file, "filePool", spiedPool); - willAnswer((invocation) -> { - RandomAccessFile originalFile = (RandomAccessFile) invocation - .callRealMethod(); - if (Mockito.mockingDetails(originalFile).isSpy()) { - return originalFile; - } - RandomAccessFile spiedFile = spy(originalFile); - willThrow(new IOException("Seek failed")).given(spiedFile).seek(anyLong()); - return spiedFile; - }).given(spiedPool).acquire(); - - for (int i = 0; i < 5; i++) { - try { - this.file.getInputStream(ResourceAccess.PER_READ).read(); - fail("Read should fail due to exception from seek"); - } - catch (IOException ex) { - - } - } - } - } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/jar/JarFileTests.java b/spring-boot-project/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/jar/JarFileTests.java index 4aed20f8026..0f1af0aac92 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/jar/JarFileTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/jar/JarFileTests.java @@ -186,7 +186,7 @@ public class JarFileTests { @Test public void close() throws Exception { RandomAccessDataFile randomAccessDataFile = spy( - new RandomAccessDataFile(this.rootJarFile, 1)); + new RandomAccessDataFile(this.rootJarFile)); JarFile jarFile = new JarFile(randomAccessDataFile); jarFile.close(); verify(randomAccessDataFile).close();