Reuse previously parsed entries for filtered JARs
Update JarFile to reuse the previously parsed entries when creating filtered jars. This saves needing to re-scan the underlying file to recreate a subset of entries. See gh-1119
This commit is contained in:
parent
e9aab1e90c
commit
88195292dd
|
|
@ -55,20 +55,26 @@ public final class JarEntryData {
|
||||||
|
|
||||||
public JarEntryData(JarFile source, byte[] header, InputStream inputStream)
|
public JarEntryData(JarFile source, byte[] header, InputStream inputStream)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
|
|
||||||
this.source = source;
|
this.source = source;
|
||||||
this.header = header;
|
this.header = header;
|
||||||
long nameLength = Bytes.littleEndianValue(header, 28, 2);
|
long nameLength = Bytes.littleEndianValue(header, 28, 2);
|
||||||
long extraLength = Bytes.littleEndianValue(header, 30, 2);
|
long extraLength = Bytes.littleEndianValue(header, 30, 2);
|
||||||
long commentLength = Bytes.littleEndianValue(header, 32, 2);
|
long commentLength = Bytes.littleEndianValue(header, 32, 2);
|
||||||
|
|
||||||
this.name = new AsciiBytes(Bytes.get(inputStream, nameLength));
|
this.name = new AsciiBytes(Bytes.get(inputStream, nameLength));
|
||||||
this.extra = Bytes.get(inputStream, extraLength);
|
this.extra = Bytes.get(inputStream, extraLength);
|
||||||
this.comment = new AsciiBytes(Bytes.get(inputStream, commentLength));
|
this.comment = new AsciiBytes(Bytes.get(inputStream, commentLength));
|
||||||
|
|
||||||
this.localHeaderOffset = Bytes.littleEndianValue(header, 42, 4);
|
this.localHeaderOffset = Bytes.littleEndianValue(header, 42, 4);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private JarEntryData(JarEntryData master, JarFile source, AsciiBytes name) {
|
||||||
|
this.header = master.header;
|
||||||
|
this.extra = master.extra;
|
||||||
|
this.comment = master.comment;
|
||||||
|
this.localHeaderOffset = master.localHeaderOffset;
|
||||||
|
this.source = source;
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
void setName(AsciiBytes name) {
|
void setName(AsciiBytes name) {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
}
|
}
|
||||||
|
|
@ -154,6 +160,10 @@ public final class JarEntryData {
|
||||||
return this.comment;
|
return this.comment;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
JarEntryData createFilteredCopy(JarFile jarFile, AsciiBytes name) {
|
||||||
|
return new JarEntryData(this, jarFile, name);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new {@link JarEntryData} instance from the specified input stream.
|
* Create a new {@link JarEntryData} instance from the specified input stream.
|
||||||
* @param source the source {@link JarFile}
|
* @param source the source {@link JarFile}
|
||||||
|
|
|
||||||
|
|
@ -68,18 +68,16 @@ public class JarFile extends java.util.jar.JarFile implements Iterable<JarEntryD
|
||||||
|
|
||||||
private final RandomAccessDataFile rootFile;
|
private final RandomAccessDataFile rootFile;
|
||||||
|
|
||||||
private final RandomAccessData data;
|
|
||||||
|
|
||||||
private final String name;
|
private final String name;
|
||||||
|
|
||||||
private final long size;
|
private final RandomAccessData data;
|
||||||
|
|
||||||
private boolean signed;
|
private final List<JarEntryData> entries;
|
||||||
|
|
||||||
private List<JarEntryData> entries;
|
|
||||||
|
|
||||||
private SoftReference<Map<AsciiBytes, JarEntryData>> entriesByName;
|
private SoftReference<Map<AsciiBytes, JarEntryData>> entriesByName;
|
||||||
|
|
||||||
|
private boolean signed;
|
||||||
|
|
||||||
private JarEntryData manifestEntry;
|
private JarEntryData manifestEntry;
|
||||||
|
|
||||||
private SoftReference<Manifest> manifest;
|
private SoftReference<Manifest> manifest;
|
||||||
|
|
@ -108,18 +106,25 @@ public class JarFile extends java.util.jar.JarFile implements Iterable<JarEntryD
|
||||||
* @param rootFile the root jar file
|
* @param rootFile the root jar file
|
||||||
* @param name the name of this file
|
* @param name the name of this file
|
||||||
* @param data the underlying data
|
* @param data the underlying data
|
||||||
* @param filters an optional set of jar entry filters
|
|
||||||
* @throws IOException
|
* @throws IOException
|
||||||
*/
|
*/
|
||||||
private JarFile(RandomAccessDataFile rootFile, String name, RandomAccessData data,
|
private JarFile(RandomAccessDataFile rootFile, String name, RandomAccessData data)
|
||||||
JarEntryFilter... filters) throws IOException {
|
throws IOException {
|
||||||
super(rootFile.getFile());
|
super(rootFile.getFile());
|
||||||
CentralDirectoryEndRecord endRecord = new CentralDirectoryEndRecord(data);
|
CentralDirectoryEndRecord endRecord = new CentralDirectoryEndRecord(data);
|
||||||
this.data = getArchiveData(endRecord, data);
|
|
||||||
this.rootFile = rootFile;
|
this.rootFile = rootFile;
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.size = data.getSize();
|
this.data = getArchiveData(endRecord, data);
|
||||||
loadJarEntries(endRecord, filters);
|
this.entries = loadJarEntries(endRecord);
|
||||||
|
}
|
||||||
|
|
||||||
|
private JarFile(RandomAccessDataFile rootFile, String name, RandomAccessData data,
|
||||||
|
List<JarEntryData> entries, JarEntryFilter... filters) throws IOException {
|
||||||
|
super(rootFile.getFile());
|
||||||
|
this.rootFile = rootFile;
|
||||||
|
this.name = name;
|
||||||
|
this.data = data;
|
||||||
|
this.entries = filterEntries(entries, filters);
|
||||||
}
|
}
|
||||||
|
|
||||||
private RandomAccessData getArchiveData(CentralDirectoryEndRecord endRecord,
|
private RandomAccessData getArchiveData(CentralDirectoryEndRecord endRecord,
|
||||||
|
|
@ -131,37 +136,49 @@ public class JarFile extends java.util.jar.JarFile implements Iterable<JarEntryD
|
||||||
return data.getSubsection(offset, data.getSize() - offset);
|
return data.getSubsection(offset, data.getSize() - offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void loadJarEntries(CentralDirectoryEndRecord endRecord,
|
private List<JarEntryData> loadJarEntries(CentralDirectoryEndRecord endRecord)
|
||||||
JarEntryFilter[] filters) throws IOException {
|
throws IOException {
|
||||||
RandomAccessData centralDirectory = endRecord.getCentralDirectory(this.data);
|
RandomAccessData centralDirectory = endRecord.getCentralDirectory(this.data);
|
||||||
int numberOfRecords = endRecord.getNumberOfRecords();
|
int numberOfRecords = endRecord.getNumberOfRecords();
|
||||||
this.entries = new ArrayList<JarEntryData>(numberOfRecords);
|
List<JarEntryData> entries = new ArrayList<JarEntryData>(numberOfRecords);
|
||||||
InputStream inputStream = centralDirectory.getInputStream(ResourceAccess.ONCE);
|
InputStream inputStream = centralDirectory.getInputStream(ResourceAccess.ONCE);
|
||||||
try {
|
try {
|
||||||
JarEntryData entry = JarEntryData.fromInputStream(this, inputStream);
|
JarEntryData entry = JarEntryData.fromInputStream(this, inputStream);
|
||||||
while (entry != null) {
|
while (entry != null) {
|
||||||
addJarEntry(entry, filters);
|
entries.add(entry);
|
||||||
|
processEntry(entry);
|
||||||
entry = JarEntryData.fromInputStream(this, inputStream);
|
entry = JarEntryData.fromInputStream(this, inputStream);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
finally {
|
finally {
|
||||||
inputStream.close();
|
inputStream.close();
|
||||||
}
|
}
|
||||||
|
return entries;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addJarEntry(JarEntryData entry, JarEntryFilter[] filters) {
|
private List<JarEntryData> filterEntries(List<JarEntryData> entries,
|
||||||
|
JarEntryFilter[] filters) {
|
||||||
|
List<JarEntryData> filteredEntries = new ArrayList<JarEntryData>(entries.size());
|
||||||
|
for (JarEntryData entry : entries) {
|
||||||
AsciiBytes name = entry.getName();
|
AsciiBytes name = entry.getName();
|
||||||
for (JarEntryFilter filter : filters) {
|
for (JarEntryFilter filter : filters) {
|
||||||
name = (filter == null || name == null ? name : filter.apply(name, entry));
|
name = (filter == null || name == null ? name : filter.apply(name, entry));
|
||||||
}
|
}
|
||||||
if (name != null) {
|
if (name != null) {
|
||||||
entry.setName(name);
|
JarEntryData filteredCopy = entry.createFilteredCopy(this, name);
|
||||||
this.entries.add(entry);
|
filteredEntries.add(filteredCopy);
|
||||||
|
processEntry(filteredCopy);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return filteredEntries;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void processEntry(JarEntryData entry) {
|
||||||
|
AsciiBytes name = entry.getName();
|
||||||
if (name.startsWith(META_INF)) {
|
if (name.startsWith(META_INF)) {
|
||||||
processMetaInfEntry(name, entry);
|
processMetaInfEntry(name, entry);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private void processMetaInfEntry(AsciiBytes name, JarEntryData entry) {
|
private void processMetaInfEntry(AsciiBytes name, JarEntryData entry) {
|
||||||
if (name.equals(MANIFEST_MF)) {
|
if (name.equals(MANIFEST_MF)) {
|
||||||
|
|
@ -322,8 +339,7 @@ public class JarFile extends java.util.jar.JarFile implements Iterable<JarEntryD
|
||||||
private JarFile getNestedJarFileFromDirectoryEntry(JarEntryData sourceEntry)
|
private JarFile getNestedJarFileFromDirectoryEntry(JarEntryData sourceEntry)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
final AsciiBytes sourceName = sourceEntry.getName();
|
final AsciiBytes sourceName = sourceEntry.getName();
|
||||||
JarEntryFilter[] filtersToUse = new JarEntryFilter[1];
|
JarEntryFilter filter = new JarEntryFilter() {
|
||||||
filtersToUse[0] = new JarEntryFilter() {
|
|
||||||
@Override
|
@Override
|
||||||
public AsciiBytes apply(AsciiBytes name, JarEntryData entryData) {
|
public AsciiBytes apply(AsciiBytes name, JarEntryData entryData) {
|
||||||
if (name.startsWith(sourceName) && !name.equals(sourceName)) {
|
if (name.startsWith(sourceName) && !name.equals(sourceName)) {
|
||||||
|
|
@ -334,7 +350,7 @@ public class JarFile extends java.util.jar.JarFile implements Iterable<JarEntryD
|
||||||
};
|
};
|
||||||
return new JarFile(this.rootFile, getName() + "!/"
|
return new JarFile(this.rootFile, getName() + "!/"
|
||||||
+ sourceEntry.getName().substring(0, sourceName.length() - 1), this.data,
|
+ sourceEntry.getName().substring(0, sourceName.length() - 1), this.data,
|
||||||
filtersToUse);
|
this.entries, filter);
|
||||||
}
|
}
|
||||||
|
|
||||||
private JarFile getNestedJarFileFromFileEntry(JarEntryData sourceEntry)
|
private JarFile getNestedJarFileFromFileEntry(JarEntryData sourceEntry)
|
||||||
|
|
@ -355,7 +371,7 @@ public class JarFile extends java.util.jar.JarFile implements Iterable<JarEntryD
|
||||||
*/
|
*/
|
||||||
public synchronized JarFile getFilteredJarFile(JarEntryFilter... filters)
|
public synchronized JarFile getFilteredJarFile(JarEntryFilter... filters)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
return new JarFile(this.rootFile, getName(), this.data, filters);
|
return new JarFile(this.rootFile, getName(), this.data, this.entries, filters);
|
||||||
}
|
}
|
||||||
|
|
||||||
private JarEntry getContainedEntry(ZipEntry zipEntry) throws IOException {
|
private JarEntry getContainedEntry(ZipEntry zipEntry) throws IOException {
|
||||||
|
|
@ -368,7 +384,7 @@ public class JarFile extends java.util.jar.JarFile implements Iterable<JarEntryD
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int size() {
|
public int size() {
|
||||||
return (int) this.size;
|
return (int) this.data.getSize();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue