![JAR search and dependency download from the Maven repository](/logo.png)
io.joshworks.snappy.loader.jar.JarEntryData Maven / Gradle / Ivy
/*
* Copyright 2012-2015 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 io.joshworks.snappy.loader.jar;
import io.joshworks.snappy.loader.data.RandomAccessData;
import io.joshworks.snappy.loader.util.AsciiBytes;
import java.io.IOException;
import java.io.InputStream;
import java.lang.ref.SoftReference;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.zip.ZipEntry;
/**
* Holds the underlying data of a {@link JarEntry}, allowing creation to be deferred until
* the entry is actually needed.
*
* @author Phillip Webb
* @author Andy Wilkinson
*/
public final class JarEntryData {
private static final long LOCAL_FILE_HEADER_SIZE = 30;
private static final AsciiBytes SLASH = new AsciiBytes("/");
private final JarFile source;
private final byte[] header;
private final byte[] extra;
private final AsciiBytes comment;
private final long localHeaderOffset;
JarFile nestedJar;
private AsciiBytes name;
private RandomAccessData data;
private SoftReference entry;
public JarEntryData(JarFile source, byte[] header, InputStream inputStream)
throws IOException {
this.source = source;
this.header = header;
long nameLength = Bytes.littleEndianValue(header, 28, 2);
long extraLength = Bytes.littleEndianValue(header, 30, 2);
long commentLength = Bytes.littleEndianValue(header, 32, 2);
this.name = new AsciiBytes(Bytes.get(inputStream, nameLength));
this.extra = Bytes.get(inputStream, extraLength);
this.comment = new AsciiBytes(Bytes.get(inputStream, commentLength));
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;
}
/**
* Create a new {@link JarEntryData} instance from the specified input stream.
*
* @param source the source {@link JarFile}
* @param inputStream the input stream to load data from
* @return a {@link JarEntryData} or {@code null}
* @throws IOException in case of I/O errors
*/
static JarEntryData fromInputStream(JarFile source, InputStream inputStream)
throws IOException {
byte[] header = new byte[46];
if (!Bytes.fill(inputStream, header)) {
return null;
}
return new JarEntryData(source, header, inputStream);
}
JarFile getSource() {
return this.source;
}
InputStream getInputStream() throws IOException {
InputStream inputStream = getData().getInputStream(RandomAccessData.ResourceAccess.PER_READ);
if (getMethod() == ZipEntry.DEFLATED) {
inputStream = new ZipInflaterInputStream(inputStream, getSize());
}
return inputStream;
}
/**
* Return the underlying {@link RandomAccessData} for this entry. Generally this
* method should not be called directly and instead data should be accessed via
* {@link JarFile#getInputStream(ZipEntry)}.
*
* @return the data
* @throws IOException if the data cannot be read
*/
public RandomAccessData getData() throws IOException {
if (this.data == null) {
// aspectjrt-1.7.4.jar has a different ext bytes length in the
// local directory to the central directory. We need to re-read
// here to skip them
byte[] localHeader = Bytes.get(this.source.getData()
.getSubsection(this.localHeaderOffset, LOCAL_FILE_HEADER_SIZE));
long nameLength = Bytes.littleEndianValue(localHeader, 26, 2);
long extraLength = Bytes.littleEndianValue(localHeader, 28, 2);
this.data = this.source.getData().getSubsection(this.localHeaderOffset
+ LOCAL_FILE_HEADER_SIZE + nameLength + extraLength,
getCompressedSize());
}
return this.data;
}
JarEntry asJarEntry() {
JarEntry entry = (this.entry == null ? null : this.entry.get());
if (entry == null) {
entry = new JarEntry(this);
entry.setCompressedSize(getCompressedSize());
entry.setMethod(getMethod());
entry.setCrc(getCrc());
entry.setSize(getSize());
entry.setExtra(getExtra());
entry.setComment(getComment().toString());
entry.setSize(getSize());
entry.setTime(getTime());
this.entry = new SoftReference(entry);
}
return entry;
}
public AsciiBytes getName() {
return this.name;
}
void setName(AsciiBytes name) {
this.name = name;
}
public boolean isDirectory() {
return this.name.endsWith(SLASH);
}
public int getMethod() {
return (int) Bytes.littleEndianValue(this.header, 10, 2);
}
public long getTime() {
long date = Bytes.littleEndianValue(this.header, 14, 2);
long time = Bytes.littleEndianValue(this.header, 12, 2);
return decodeMsDosFormatDateTime(date, time).getTimeInMillis();
}
/**
* 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.
*/
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);
}
public long getCrc() {
return Bytes.littleEndianValue(this.header, 16, 4);
}
public int getCompressedSize() {
return (int) Bytes.littleEndianValue(this.header, 20, 4);
}
public int getSize() {
return (int) Bytes.littleEndianValue(this.header, 24, 4);
}
public byte[] getExtra() {
return this.extra;
}
public AsciiBytes getComment() {
return this.comment;
}
JarEntryData createFilteredCopy(JarFile jarFile, AsciiBytes name) {
return new JarEntryData(this, jarFile, name);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy