blob: 392d9471b7076a7fce58668a0a058502cf26e9ca [file] [log] [blame]
// Copyright 2015 The Bazel Authors. All rights reserved.
//
// 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.google.devtools.build.zip;
import java.io.IOException;
import java.io.InputStream;
import java.util.Iterator;
import java.util.LinkedHashMap;
/**
* A list of {@link ExtraData} records to be associated with a {@link ZipFileEntry}. Supports
* creating the list directly from a byte array and modifying the list without reallocating the
* underlying buffer.
*/
public class ExtraDataList {
public static final short ZIP64 = 0x0001;
public static final short EXTENDED_TIMESTAMP = 0x5455;
// Some documentation says that this is actually 0x7855, but zip files do not seem to corroborate
// this
public static final short INFOZIP_UNIX_NEW = 0x7875;
private final LinkedHashMap<Short, ExtraData> entries;
/**
* Create a new empty extra data list.
*
* <p><em>NOTE:</em> entries in a list created this way will be backed by their own storage.
*/
public ExtraDataList() {
entries = new LinkedHashMap<>();
}
public ExtraDataList(ExtraDataList other) {
this.entries = new LinkedHashMap<>();
this.entries.putAll(other.entries);
}
/**
* Creates an extra data list from the given extra data records.
*
* <p><em>NOTE:</em> entries in a list created this way will be backed by their own storage.
*
* @param extra the extra data records
*/
public ExtraDataList(ExtraData... extra) {
this();
for (ExtraData e : extra) {
add(e);
}
}
/**
* Creates an extra data list from the entries contained in the given array.
*
* <p><em>NOTE:</em> entries in a list created this way will be backed by the buffer. No defensive
* copying is performed.
*
* @param buffer the array containing sequential extra data entries
*/
public ExtraDataList(byte[] buffer) {
if (buffer.length > 0xffff) {
throw new IllegalArgumentException("invalid extra field length");
}
entries = new LinkedHashMap<>();
int index = 0;
while (index < buffer.length) {
ExtraData extra = new ExtraData(buffer, index);
entries.put(extra.getId(), extra);
index += extra.getLength();
}
}
/**
* Returns the extra data record with the specified id, or null if it does not exist.
*/
public ExtraData get(short id) {
return entries.get(id);
}
/**
* Removes and returns the extra data record with the specified id if it exists.
*
* <p><em>NOTE:</em> does not modify the underlying storage, only marks the record as removed.
*/
public ExtraData remove(short id) {
return entries.remove(id);
}
/**
* Returns if the list contains an extra data record with the specified id.
*/
public boolean contains(short id) {
return entries.containsKey(id);
}
/**
* Adds a new entry to the end of the list.
*
* @throws IllegalArgumentException if adding the entry will make the list too long for the ZIP
* format
*/
public void add(ExtraData entry) {
if (getLength() + entry.getLength() > 0xffff) {
throw new IllegalArgumentException("adding entry will make the extra field be too long");
}
entries.put(entry.getId(), entry);
}
/**
* Returns the overall length of the list in bytes.
*/
public int getLength() {
int length = 0;
for (ExtraData e : entries.values()) {
length += e.getLength();
}
return length;
}
/**
* Creates and returns a byte array of the extra data list.
*/
public byte[] getBytes() {
byte[] extra = new byte[getLength()];
try {
getByteStream().read(extra);
} catch (IOException impossible) {
throw new AssertionError(impossible);
}
return extra;
}
/**
* Returns an input stream for reading the extra data list entries.
*/
public InputStream getByteStream() {
return new InputStream() {
private final Iterator<ExtraData> itr = entries.values().iterator();
private ExtraData entry;
private int index;
@Override
public int read() {
if (entry == null) {
if (itr.hasNext()) {
entry = itr.next();
index = 0;
} else {
return -1;
}
}
byte val = entry.getByte(index++);
if (index >= entry.getLength()) {
entry = null;
}
return val & 0xff;
}
};
}
}