/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.
*/
/*
* Imported by CG 20090322 based on Apache Harmony ("enhanced") revision 757150.
* Modified to not use java.nio (because not in OSGi RFC 26). For this I've used
* the Mika code, hence the write method is still copyright Punch-/k/.
*/
package java.util.jar;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
/**
* The Manifest class is used to obtain attribute information for a JarFile and
* its entries.
*/
public class Manifest implements Cloneable {
static final int LINE_LENGTH_LIMIT = 72;
private static final byte[] LINE_SEPARATOR = new byte[] { '\r', '\n' };
private static final byte[] VALUE_SEPARATOR = new byte[] { ':', ' ' };
private static final Attributes.Name NAME_ATTRIBUTE = new Attributes.Name(
"Name"); //$NON-NLS-1$
private Attributes mainAttributes = new Attributes();
private HashMap entries = new HashMap();
static class Chunk {
int start;
int end;
Chunk(int start, int end) {
this.start = start;
this.end = end;
}
}
private HashMap chunks;
/**
** Manifest bytes are used for delayed entry parsing.
**/
private InitManifest im;
/**
* The end of the main attributes section in the manifest is needed in
* verification.
*/
private int mainEnd;
/**
* Constructs a new Manifest instance.
*/
public Manifest() {
super();
}
/**
* Constructs a new Manifest instance using the attributes obtained from is.
*
* @param is
* InputStream to parse for attributes
*
* @throws IOException
* if an IO error occurs while creating this Manifest
*
*/
public Manifest(InputStream is) throws IOException {
super();
read(is);
}
/**
* Constructs a new Manifest instance. The new instance will have the same
* attributes as those found in the parameter Manifest.
*
* @param man
* Manifest instance to obtain attributes from
*/
public Manifest(Manifest man) {
mainAttributes = (Attributes) man.mainAttributes.clone();
entries = (HashMap) ((HashMap) man.getEntries()).clone();
}
Manifest(InputStream is, boolean readChunks) throws IOException {
if (readChunks) {
chunks = new HashMap();
}
read(is);
}
/**
* Resets the both the mainAttributes as well as the entry Attributes
* associated with this Manifest.
*/
public void clear() {
entries.clear();
mainAttributes.clear();
}
/**
* Returns the Attributes associated with the parameter entry name
*
* @param name
* The name of the entry to obtain Attributes for.
* @return The Attributes for the entry or null if the entry does not exist.
*/
public Attributes getAttributes(String name) {
return (Attributes)getEntries().get(name);
}
/**
* Returns a Map containing the Attributes for each entry in the Manifest.
*
* @return A Map of entry attributes
*/
public Map getEntries() {
initEntries();
return entries;
}
private void initEntries() {
if (im == null) {
return;
}
/*
try {
im.initEntries(entries, chunks);
} catch (IOException ioe) {
throw new RuntimeException(ioe);
}
im = null;
*/
}
/**
* Returns the main Attributes of the JarFile.
*
* @return Main Attributes associated with the source JarFile
*/
public Attributes getMainAttributes() {
return mainAttributes;
}
/**
* Creates a copy of this Manifest. The returned Manifest will equal the
* Manifest from which it was cloned.
*
* @return A copy of the receiver.
*/
public Object clone() {
return new Manifest(this);
}
/**
** Constructs a new Manifest instance obtaining Attribute information from
** the parameter InputStream.
**
** @param is
** The InputStream to read from
** @throws IOException
** If an error occurs reading the Manifest.
**/
public void read(InputStream is) throws IOException {
byte[] buf = readFully(is);
if (buf.length == 0) {
return;
}
// a workaround for HARMONY-5662
// replace EOF and NUL with another new line
// which does not trigger an error
byte b = buf[buf.length - 1];
if (0 == b || 26 == b) {
buf[buf.length - 1] = '\n';
}
// Attributes.Name.MANIFEST_VERSION is not used for
// the second parameter for RI compatibility
im = new InitManifest(buf, mainAttributes, null);
mainEnd = im.getPos();
// FIXME
im.initEntries(entries, chunks);
im = null;
}
/*
* Helper to read the entire contents of the manifest from the
* given input stream. Usually we can do this in a single read
* but we need to account for 'infinite' streams, by ensuring we
* have a line feed within a reasonable number of characters.
*/
private byte[] readFully(InputStream is) throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] buffer = new byte[8192];
while (true) {
int count = is.read(buffer);
if (count == -1) {
// TODO: Do we need to copy this, or can we live with junk at the end?
return baos.toByteArray();
}
baos.write(buffer, 0, count);
if (!containsLine(buffer, count)) {
throw new IOException("no newline in first 8192 bytes");
}
}
}
/*
* Check to see if the buffer contains a newline or carriage
* return character within the first 'length' bytes. Used to
* check the validity of the manifest input stream.
*/
private boolean containsLine(byte[] buffer, int length) {
for (int i = 0; i < length; i++) {
if (buffer[i] == 0x0A || buffer[i] == 0x0D) {
return true;
}
}
return false;
}
/**************************************************************************
* Parts copyright (c) 2001 by Punch Telematix. All rights reserved. *
* Parts copyright (c) 2008 by Chris Gray, /k/ Embedded Java Solutions. *
* All rights reserved. *
* *
* Redistribution and use in source and binary forms, with or without *
* modification, are permitted provided that the following conditions *
* are met: *
* 1. Redistributions of source code must retain the above copyright *
* notice, this list of conditions and the following disclaimer. *
* 2. Redistributions in binary form must reproduce the above copyright *
* notice, this list of conditions and the following disclaimer in the *
* documentation and/or other materials provided with the distribution. *
* 3. Neither the name of Punch Telematix or of /k/ Embedded Java Solutions*
* nor the names of other contributors may be used to endorse or promote*
* products derived from this software without specific prior written *
* permission. *
* *
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED *
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF *
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. *
* IN NO EVENT SHALL PUNCH TELEMATIX, /K/ EMBEDDED JAVA SOLUTIONS OR OTHER *
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, *
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, *
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR *
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF *
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING *
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS *
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *
**************************************************************************/
/**
* Writes out the attribute information of the receiver to the specified
* OutputStream
*
* @param os
* The OutputStream to write to.
*
* @throws IOException
* If an error occurs writing the Manifest
*/
public void write(OutputStream os) throws IOException {
writeAttributes(os, mainAttributes);
Iterator it = entries.entrySet().iterator();
Map.Entry me;
StringBuffer buf;
while (it.hasNext()) {
me = (Map.Entry) it.next();
buf = new StringBuffer("\r\n");
buf.append("Name: ");
buf.append((String)me.getKey());
os.write(LINE_SEPARATOR);
writeBuffer(os,buf);
writeAttributes(os, (Attributes)me.getValue());
}
}
private void writeAttributes(OutputStream os, Attributes at) throws IOException {
StringBuffer buf;
Iterator it = at.entrySet().iterator();
Map.Entry me;
while (it.hasNext()) {
me = (Map.Entry) it.next();
buf = new StringBuffer(((Attributes.Name)me.getKey()).toString());
buf.append(": ");
buf.append((String)me.getValue());
writeBuffer(os,buf);
}
}
private void writeBuffer(OutputStream os, StringBuffer buf) throws IOException {
int len = buf.length();
int offset = 0;
byte [] bytes = new String(buf).getBytes("UTF8");
while(len > 0) {
if ( len > LINE_LENGTH_LIMIT ) {
os.write(bytes,offset,LINE_LENGTH_LIMIT);
os.write(LINE_SEPARATOR);
os.write(' ');
len -= LINE_LENGTH_LIMIT;
offset += LINE_LENGTH_LIMIT;
}
else {
os.write(bytes,offset,len);
os.write(LINE_SEPARATOR);
break;
}
}
}
/**
* Returns the hashCode for this instance.
*
* @return This Manifest's hashCode
*/
public int hashCode() {
return mainAttributes.hashCode() ^ getEntries().hashCode();
}
/**
* Determines if the receiver is equal to the parameter Object. Two
* Manifests are equal if they have identical main Attributes as well as
* identical entry Attributes.
*
* @param o
* The Object to compare against.
* @return <code>true</code> if the manifests are equal,
* <code>false</code> otherwise
*/
public boolean equals(Object o) {
if (o == null) {
return false;
}
if (o.getClass() != this.getClass()) {
return false;
}
if (!mainAttributes.equals(((Manifest) o).mainAttributes)) {
return false;
}
return getEntries().equals(((Manifest) o).getEntries());
}
Chunk getChunk(String name) {
return (Chunk)chunks.get(name);
}
void removeChunks() {
chunks = null;
}
int getMainAttributesEnd() {
return mainEnd;
}
}