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:
Phillip Webb 2014-06-20 08:54:04 -07:00
parent e9aab1e90c
commit 88195292dd
2 changed files with 60 additions and 34 deletions

View File

@ -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}

View File

@ -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