/* * Copyright 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 com.github.atdi.gboot.loader.jar; import java.io.IOException; import java.io.InputStream; import java.lang.ref.SoftReference; import java.util.Arrays; import java.util.zip.ZipEntry; import com.github.atdi.gboot.loader.data.RandomAccessData; import com.github.atdi.gboot.loader.util.AsciiBytes; /** * Holds the underlying data of a {@link com.github.atdi.gboot.loader.jar.GBootJarEntry}, allowing creation to be deferred until * the entry is actually needed. */ public class GBootJarEntryData { private static final long LOCAL_FILE_HEADER_SIZE = 30; private static final AsciiBytes SLASH = new AsciiBytes("/"); private final GBootJarFile source; private final byte[] header; private AsciiBytes name; private final byte[] extra; private final AsciiBytes comment; private final long localHeaderOffset; private RandomAccessData data; private SoftReference<GBootJarEntry> entry; GBootJarFile nestedJar; public GBootJarEntryData(GBootJarFile source, byte[] header, InputStream inputStream) throws IOException { this.source = source; this.header = Arrays.copyOf(header, header.length); 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 GBootJarEntryData(GBootJarEntryData master, GBootJarFile 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) { this.name = name; } GBootJarFile 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 com.github.atdi.gboot.loader.jar.GBootJarFile#getInputStream(java.util.zip.ZipEntry)}. * @throws java.io.IOException */ 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; } GBootJarEntry asJarEntry() { GBootJarEntry entry = (this.entry == null ? null : this.entry.get()); if (entry == null) { entry = new GBootJarEntry(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<GBootJarEntry>(entry); } return entry; } public AsciiBytes getName() { return this.name; } public boolean isDirectory() { return this.name.endsWith(SLASH); } public int getMethod() { return (int) Bytes.littleEndianValue(this.header, 10, 2); } public long getTime() { return Bytes.littleEndianValue(this.header, 12, 4); } 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 Arrays.copyOf(this.extra, this.extra.length); } public AsciiBytes getComment() { return this.comment; } GBootJarEntryData createFilteredCopy(GBootJarFile jarFile, AsciiBytes name) { return new GBootJarEntryData(this, jarFile, name); } /** * Create a new {@link com.github.atdi.gboot.loader.jar.GBootJarEntryData} instance from the specified input stream. * @param source the source {@link com.github.atdi.gboot.loader.jar.GBootJarFile} * @param inputStream the input stream to load data from * @return a {@link com.github.atdi.gboot.loader.jar.GBootJarEntryData} or {@code null} * @throws java.io.IOException */ static GBootJarEntryData fromInputStream(GBootJarFile source, InputStream inputStream) throws IOException { byte[] header = new byte[46]; if (!Bytes.fill(inputStream, header)) { return null; } return new GBootJarEntryData(source, header, inputStream); } }