blob: 3affe35730b2fec0824cf3ef3a561333ccf8f1b5 [file] [log] [blame]
/**************************************************************
*
* 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.
*
*************************************************************/
package org.openoffice.xmerge.converter.xml;
import java.util.List;
import java.util.ListIterator;
import java.util.LinkedList;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;
import java.util.zip.ZipEntry;
import java.util.zip.CRC32;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.IOException;
import java.io.ByteArrayOutputStream;
import org.openoffice.xmerge.util.Debug;
/**
* Class used by {@link
* org.openoffice.xmerge.converter.xml.OfficeDocument
* OfficeDocument} to handle reading and writing
* from a ZIP file, as well as storing ZIP entries.
*
* @author Herbie Ong
*/
class OfficeZip {
/** File name of the XML file in a zipped document. */
private final static String CONTENTXML = "content.xml";
private final static String STYLEXML = "styles.xml";
private final static String METAXML = "meta.xml";
private final static String SETTINGSXML = "settings.xml";
private final static String MANIFESTXML = "META-INF/manifest.xml";
private final static int BUFFERSIZE = 1024;
private List entryList = null;
private int contentIndex = -1;
private int styleIndex = -1;
private int metaIndex = -1;
private int settingsIndex = -1;
private int manifestIndex = -1;
/** Default constructor. */
OfficeZip() {
entryList = new LinkedList();
}
/**
* <p>Read each zip entry in the <code>InputStream</code> object
* and store in entryList both the <code>ZipEntry</code> object
* as well as the bits of each entry. Call this method before
* calling the <code>getContentXMLBytes</code> method or the
* <code>getStyleXMLBytes</code> method.</p>
*
* <p>Keep track of the CONTENTXML and STYLEXML using
* contentIndex and styleIndex, respectively.</p>
*
* @param is <code>InputStream</code> object to read.
*
* @throws IOException If any I/O error occurs.
*/
void read(InputStream is) throws IOException {
ZipInputStream zis = new ZipInputStream(is);
ZipEntry ze = null;
int i = -1;
while ((ze = zis.getNextEntry()) != null) {
String name = ze.getName();
Debug.log(Debug.TRACE, "reading entry: " + name);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
int len = 0;
byte buffer[] = new byte[BUFFERSIZE];
while ((len = zis.read(buffer)) > 0) {
baos.write(buffer, 0, len);
}
byte bytes[] = baos.toByteArray();
Entry entry = new Entry(ze,bytes);
entryList.add(entry);
i++;
if (name.equalsIgnoreCase(CONTENTXML)) {
contentIndex = i;
}
else if (name.equalsIgnoreCase(STYLEXML)) {
styleIndex = i;
}
else if (name.equalsIgnoreCase(METAXML)) {
metaIndex = i;
}
else if (name.equalsIgnoreCase(SETTINGSXML)) {
settingsIndex = i;
}
else if (name.equalsIgnoreCase(MANIFESTXML)) {
manifestIndex = i;
}
}
zis.close();
}
/**
* This method returns the CONTENTXML file in a
* <code>byte</code> array. It returns null if there is no
* CONTENTXML in this zip file.
*
* @return CONTENTXML in a <code>byte</code> array.
*/
byte[] getContentXMLBytes() {
return getEntryBytes(contentIndex);
}
/**
* This method returns the STYLEXML file in a
* <code>byte</code> array. It returns null if there is
* no STYLEXML in this zip file.
*
* @return STYLEXML in a <code>byte</code> array.
*/
byte[] getStyleXMLBytes() {
return getEntryBytes(styleIndex);
}
/**
* This method returns the METAXML file in a
* <code>byte</code> array. It returns null if there is
* no METAXML in this zip file.
*
* @return METAXML in a <code>byte</code> array.
*/
byte[] getMetaXMLBytes() {
return getEntryBytes(metaIndex);
}
/**
* This method returns the SETTINGSXML file in a
* <code>byte</code> array. It returns null if there is
* no SETTINGSXML in this zip file.
*
* @return SETTINGSXML in a <code>byte</code> array.
*/
byte[] getSettingsXMLBytes() {
return getEntryBytes(settingsIndex);
}
/**
* This method returns the MANIFESTXML file in a <code>byte</code> array.
* It returns null if there is no MANIFESTXML in this zip file.
*
* @return MANIFESTXML in a <code>byte</code> array.
*/
byte[] getManifestXMLBytes() {
return getEntryBytes(manifestIndex);
}
/**
* This method returns the bytes corresponding to the entry named in the
* parameter.
*
* @param name The name of the entry in the Zip file to retrieve.
*
* @return The data for the named entry in a <code>byte</code> array or
* <code>null</code> if no entry is found.
*/
byte[] getNamedBytes(String name) {
// The list is not sorted, and sorting it for a binary search would
// invalidate the indices stored for the main files.
// Could improve performance by caching the name and index when
// iterating through the ZipFile in read().
for (int i = 0; i < entryList.size(); i++) {
Entry e = (Entry)entryList.get(i);
if (e.zipEntry.getName().equals(name)) {
return getEntryBytes(i);
}
}
return null;
}
/**
* This method sets the bytes for the named entry. It searches for a
* matching entry in the LinkedList. If no entry is found, a new one is
* created.
*
* Writing of data is defferred to setEntryBytes().
*
* @param name The name of the entry to search for.
* @param bytes The new data to write.
*/
void setNamedBytes(String name, byte[] bytes) {
for (int i = 0; i < entryList.size(); i++) {
Entry e = (Entry)entryList.get(i);
if (e.zipEntry.getName().equals(name)) {
setEntryBytes(i, bytes, name);
return;
}
}
// If we're here, no entry was found. Call setEntryBytes with an index
// of -1 to insert a new entry.
setEntryBytes(-1, bytes, name);
}
/**
* Used by the <code>getContentXMLBytes</code> method and the
* <code>getStyleXMLBytes</code> method to return the
* <code>byte</code> array from the corresponding
* <code>entry</code> in the <code>entryList</code>.
*
* @param index Index of <code>Entry</code> object in
* <code>entryList</code>.
*
* @return <code>byte</code> array associated in that
* <code>Entry</code> object or null, if there is
* not such <code>Entry</code>.
*/
private byte[] getEntryBytes(int index) {
byte[] bytes = null;
if (index > -1) {
Entry entry = (Entry) entryList.get(index);
bytes = entry.bytes;
}
return bytes;
}
/**
* Set or replace the <code>byte</code> array for the
* CONTENTXML file.
*
* @param bytes <code>byte</code> array for the
* CONTENTXML file.
*/
void setContentXMLBytes(byte bytes[]) {
contentIndex = setEntryBytes(contentIndex, bytes, CONTENTXML);
}
/**
* Set or replace the <code>byte</code> array for the
* STYLEXML file.
*
* @param bytes <code>byte</code> array for the
* STYLEXML file.
*/
void setStyleXMLBytes(byte bytes[]) {
styleIndex = setEntryBytes(styleIndex, bytes, STYLEXML);
}
/**
* Set or replace the <code>byte</code> array for the
* METAXML file.
*
* @param bytes <code>byte</code> array for the
* METAXML file.
*/
void setMetaXMLBytes(byte bytes[]) {
metaIndex = setEntryBytes(metaIndex, bytes, METAXML);
}
/**
* Set or replace the <code>byte</code> array for the
* SETTINGSXML file.
*
* @param bytes <code>byte</code> array for the
* SETTINGSXML file.
*/
void setSettingsXMLBytes(byte bytes[]) {
settingsIndex = setEntryBytes(settingsIndex, bytes, SETTINGSXML);
}
/**
* Set or replace the <code>byte</code> array for the MANIFESTXML file.
*
* @param bytes <code>byte</code> array for the MANIFESTXML file.
*/
void setManifestXMLBytes(byte bytes[]) {
manifestIndex = setEntryBytes(manifestIndex, bytes, MANIFESTXML);
}
/**
* <p>Used by the <code>setContentXMLBytes</code> method and
* the <code>setStyleXMLBytes</code> to either replace an
* existing <code>Entry</code>, or create a new entry in
* <code>entryList</code>.</p>
*
* <p>If there is an <code>Entry</code> object within
* entryList that corresponds to the index, replace the
* <code>ZipEntry</code> info.</p>
*
* @param index Index of <code>Entry</code> to modify.
* @param bytes <code>Entry</code> value.
* @param name Name of <code>Entry</code>.
*
* @return Index of value added or modified in entryList
*/
private int setEntryBytes(int index, byte bytes[], String name) {
if (index > -1) {
// replace existing entry in entryList
Entry entry = (Entry) entryList.get(index);
name = entry.zipEntry.getName();
int method = entry.zipEntry.getMethod();
ZipEntry ze = createZipEntry(name, bytes, method);
entry.zipEntry = ze;
entry.bytes= bytes;
} else {
// add a new entry into entryList
ZipEntry ze = createZipEntry(name, bytes, ZipEntry.DEFLATED);
Entry entry = new Entry(ze, bytes);
entryList.add(entry);
index = entryList.size() - 1;
}
return index;
}
/**
* Write out the ZIP entries into the <code>OutputStream</code>
* object.
*
* @param os <code>OutputStream</code> object to write ZIP.
*
* @throws IOException If any ZIP I/O error occurs.
*/
void write(OutputStream os) throws IOException {
Debug.log(Debug.TRACE, "Writing out the following entries into zip.");
ZipOutputStream zos = new ZipOutputStream(os);
ListIterator iterator = entryList.listIterator();
while (iterator.hasNext()) {
Entry entry = (Entry) iterator.next();
ZipEntry ze = entry.zipEntry;
String name = ze.getName();
Debug.log(Debug.TRACE, "... " + name);
zos.putNextEntry(ze);
zos.write(entry.bytes);
}
zos.close();
}
/**
* Creates a <code>ZipEntry</code> object based on the given params.
*
* @param name Name for the <code>ZipEntry</code>.
* @param bytes <code>byte</code> array for <code>ZipEntry</code>.
* @param method ZIP method to be used for <code>ZipEntry</code>.
*
* @return A <code>ZipEntry</code> object.
*/
private ZipEntry createZipEntry(String name, byte bytes[], int method) {
ZipEntry ze = new ZipEntry(name);
ze.setMethod(method);
ze.setSize(bytes.length);
CRC32 crc = new CRC32();
crc.reset();
crc.update(bytes);
ze.setCrc(crc.getValue());
ze.setTime(System.currentTimeMillis());
return ze;
}
/**
* This inner class is used as a data structure for holding
* a <code>ZipEntry</code> info and its corresponding bytes.
* These are stored in entryList.
*/
private class Entry {
ZipEntry zipEntry = null;
byte bytes[] = null;
Entry(ZipEntry zipEntry, byte bytes[]) {
this.zipEntry = zipEntry;
this.bytes = bytes;
}
}
}