2 * Licensed to the Apache Software Foundation (ASF) under one or more
\r
3 * contributor license agreements. See the NOTICE file distributed with
\r
4 * this work for additional information regarding copyright ownership.
\r
5 * The ASF licenses this file to You under the Apache License, Version 2.0
\r
6 * (the "License"); you may not use this file except in compliance with
\r
7 * the License. You may obtain a copy of the License at
\r
9 * http://www.apache.org/licenses/LICENSE-2.0
\r
11 * Unless required by applicable law or agreed to in writing, software
\r
12 * distributed under the License is distributed on an "AS IS" BASIS,
\r
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
\r
14 * See the License for the specific language governing permissions and
\r
15 * limitations under the License.
\r
19 package org.apache.tools.zip;
\r
21 import java.util.ArrayList;
\r
22 import java.util.LinkedHashMap;
\r
23 import java.util.List;
\r
24 import java.util.zip.ZipException;
\r
27 * Extension that adds better handling of extra fields and provides
\r
28 * access to the internal and external file attributes.
\r
30 * <p>The extra data is expected to follow the recommendation of
\r
31 * {@link http://www.pkware.com/documents/casestudies/APPNOTE.TXT
\r
34 * <li>the extra byte array consists of a sequence of extra fields</li>
\r
35 * <li>each extra fields starts by a two byte header id followed by
\r
36 * a two byte sequence holding the length of the remainder of
\r
40 * <p>Any extra data that cannot be parsed by the rules above will be
\r
41 * consumed as "unparseable" extra data and treated differently by the
\r
42 * methods of this class. Versions prior to Apache Commons Compress
\r
43 * 1.1 would have thrown an exception if any attempt was made to read
\r
44 * or write extra data not conforming to the recommendation.</p>
\r
47 @SuppressWarnings({"unchecked", "rawtypes"})
\r
48 public class ZipEntry extends java.util.zip.ZipEntry implements Cloneable {
\r
50 public static final int PLATFORM_UNIX = 3;
\r
51 public static final int PLATFORM_FAT = 0;
\r
52 private static final int SHORT_MASK = 0xFFFF;
\r
53 private static final int SHORT_SHIFT = 16;
\r
55 private int internalAttributes = 0;
\r
56 private int platform = PLATFORM_FAT;
\r
57 private long externalAttributes = 0;
\r
58 private LinkedHashMap/*<ZipShort, ZipExtraField>*/ extraFields = null;
\r
59 private UnparseableExtraFieldData unparseableExtra = null;
\r
60 private String name = null;
\r
63 * Creates a new zip entry with the specified name.
\r
64 * @param name the name of the entry
\r
67 public ZipEntry(String name) {
\r
72 * Creates a new zip entry with fields taken from the specified zip entry.
\r
73 * @param entry the entry to get fields from
\r
75 * @throws ZipException on error
\r
77 public ZipEntry(java.util.zip.ZipEntry entry) throws ZipException {
\r
79 byte[] extra = entry.getExtra();
\r
80 if (extra != null) {
\r
81 setExtraFields(ExtraFieldUtils.parse(extra, true,
\r
83 .UnparseableExtraField.READ));
\r
85 // initializes extra data to an empty byte array
\r
91 * Creates a new zip entry with fields taken from the specified zip entry.
\r
92 * @param entry the entry to get fields from
\r
93 * @throws ZipException on error
\r
96 public ZipEntry(ZipEntry entry) throws ZipException {
\r
97 this((java.util.zip.ZipEntry) entry);
\r
98 setInternalAttributes(entry.getInternalAttributes());
\r
99 setExternalAttributes(entry.getExternalAttributes());
\r
100 setExtraFields(entry.getExtraFields(true));
\r
106 protected ZipEntry() {
\r
112 * @return a cloned copy of this ZipEntry
\r
115 public Object clone() {
\r
116 ZipEntry e = (ZipEntry) super.clone();
\r
118 e.setInternalAttributes(getInternalAttributes());
\r
119 e.setExternalAttributes(getExternalAttributes());
\r
120 e.setExtraFields(getExtraFields(true));
\r
125 * Retrieves the internal file attributes.
\r
127 * @return the internal file attributes
\r
130 public int getInternalAttributes() {
\r
131 return internalAttributes;
\r
135 * Sets the internal file attributes.
\r
136 * @param value an <code>int</code> value
\r
139 public void setInternalAttributes(int value) {
\r
140 internalAttributes = value;
\r
144 * Retrieves the external file attributes.
\r
145 * @return the external file attributes
\r
148 public long getExternalAttributes() {
\r
149 return externalAttributes;
\r
153 * Sets the external file attributes.
\r
154 * @param value an <code>long</code> value
\r
157 public void setExternalAttributes(long value) {
\r
158 externalAttributes = value;
\r
162 * Sets Unix permissions in a way that is understood by Info-Zip's
\r
164 * @param mode an <code>int</code> value
\r
167 public void setUnixMode(int mode) {
\r
168 // CheckStyle:MagicNumberCheck OFF - no point
\r
169 setExternalAttributes((mode << SHORT_SHIFT)
\r
170 // MS-DOS read-only attribute
\r
171 | ((mode & 0200) == 0 ? 1 : 0)
\r
172 // MS-DOS directory flag
\r
173 | (isDirectory() ? 0x10 : 0));
\r
174 // CheckStyle:MagicNumberCheck ON
\r
175 platform = PLATFORM_UNIX;
\r
180 * @return the unix permissions
\r
183 public int getUnixMode() {
\r
184 return platform != PLATFORM_UNIX ? 0 :
\r
185 (int) ((getExternalAttributes() >> SHORT_SHIFT) & SHORT_MASK);
\r
189 * Platform specification to put into the "version made
\r
190 * by" part of the central file header.
\r
192 * @return PLATFORM_FAT unless {@link #setUnixMode setUnixMode}
\r
193 * has been called, in which case PLATORM_UNIX will be returned.
\r
197 public int getPlatform() {
\r
202 * Set the platform (UNIX or FAT).
\r
203 * @param platform an <code>int</code> value - 0 is FAT, 3 is UNIX
\r
206 protected void setPlatform(int platform) {
\r
207 this.platform = platform;
\r
211 * Replaces all currently attached extra fields with the new array.
\r
212 * @param fields an array of extra fields
\r
215 public void setExtraFields(ZipExtraField[] fields) {
\r
216 extraFields = new LinkedHashMap();
\r
217 for (int i = 0; i < fields.length; i++) {
\r
218 if (fields[i] instanceof UnparseableExtraFieldData) {
\r
219 unparseableExtra = (UnparseableExtraFieldData) fields[i];
\r
221 extraFields.put(fields[i].getHeaderId(), fields[i]);
\r
228 * Retrieves all extra fields that have been parsed successfully.
\r
229 * @return an array of the extra fields
\r
231 public ZipExtraField[] getExtraFields() {
\r
232 return getExtraFields(false);
\r
236 * Retrieves extra fields.
\r
237 * @param includeUnparseable whether to also return unparseable
\r
238 * extra fields as {@link UnparseableExtraFieldData} if such data
\r
240 * @return an array of the extra fields
\r
243 public ZipExtraField[] getExtraFields(boolean includeUnparseable) {
\r
244 if (extraFields == null) {
\r
245 return !includeUnparseable || unparseableExtra == null
\r
246 ? new ZipExtraField[0]
\r
247 : new ZipExtraField[] { unparseableExtra };
\r
249 List result = new ArrayList(extraFields.values());
\r
250 if (includeUnparseable && unparseableExtra != null) {
\r
251 result.add(unparseableExtra);
\r
253 return (ZipExtraField[]) result.toArray(new ZipExtraField[0]);
\r
257 * Adds an extra field - replacing an already present extra field
\r
258 * of the same type.
\r
260 * <p>If no extra field of the same type exists, the field will be
\r
261 * added as last field.</p>
\r
262 * @param ze an extra field
\r
265 public void addExtraField(ZipExtraField ze) {
\r
266 if (ze instanceof UnparseableExtraFieldData) {
\r
267 unparseableExtra = (UnparseableExtraFieldData) ze;
\r
269 if (extraFields == null) {
\r
270 extraFields = new LinkedHashMap();
\r
272 extraFields.put(ze.getHeaderId(), ze);
\r
278 * Adds an extra field - replacing an already present extra field
\r
279 * of the same type.
\r
281 * <p>The new extra field will be the first one.</p>
\r
282 * @param ze an extra field
\r
285 public void addAsFirstExtraField(ZipExtraField ze) {
\r
286 if (ze instanceof UnparseableExtraFieldData) {
\r
287 unparseableExtra = (UnparseableExtraFieldData) ze;
\r
289 LinkedHashMap copy = extraFields;
\r
290 extraFields = new LinkedHashMap();
\r
291 extraFields.put(ze.getHeaderId(), ze);
\r
292 if (copy != null) {
\r
293 copy.remove(ze.getHeaderId());
\r
294 extraFields.putAll(copy);
\r
301 * Remove an extra field.
\r
302 * @param type the type of extra field to remove
\r
305 public void removeExtraField(ZipShort type) {
\r
306 if (extraFields == null) {
\r
307 throw new java.util.NoSuchElementException();
\r
309 if (extraFields.remove(type) == null) {
\r
310 throw new java.util.NoSuchElementException();
\r
316 * Removes unparseable extra field data.
\r
318 public void removeUnparseableExtraFieldData() {
\r
319 if (unparseableExtra == null) {
\r
320 throw new java.util.NoSuchElementException();
\r
322 unparseableExtra = null;
\r
327 * Looks up an extra field by its header id.
\r
329 * @return null if no such field exists.
\r
331 public ZipExtraField getExtraField(ZipShort type) {
\r
332 if (extraFields != null) {
\r
333 return (ZipExtraField) extraFields.get(type);
\r
339 * Looks up extra field data that couldn't be parsed correctly.
\r
341 * @return null if no such field exists.
\r
343 public UnparseableExtraFieldData getUnparseableExtraFieldData() {
\r
344 return unparseableExtra;
\r
348 * Parses the given bytes as extra field data and consumes any
\r
349 * unparseable data as an {@link UnparseableExtraFieldData}
\r
351 * @param extra an array of bytes to be parsed into extra fields
\r
352 * @throws RuntimeException if the bytes cannot be parsed
\r
354 * @throws RuntimeException on error
\r
356 public void setExtra(byte[] extra) throws RuntimeException {
\r
358 ZipExtraField[] local =
\r
359 ExtraFieldUtils.parse(extra, true,
\r
360 ExtraFieldUtils.UnparseableExtraField.READ);
\r
361 mergeExtraFields(local, true);
\r
362 } catch (Exception e) {
\r
363 // actually this is not be possible as of Ant 1.8.1
\r
364 throw new RuntimeException("Error parsing extra fields for entry: "
\r
365 + getName() + " - " + e.getMessage(), e);
\r
370 * Unfortunately {@link java.util.zip.ZipOutputStream
\r
371 * java.util.zip.ZipOutputStream} seems to access the extra data
\r
372 * directly, so overriding getExtra doesn't help - we need to
\r
373 * modify super's data directly.
\r
377 protected void setExtra() {
\r
378 super.setExtra(ExtraFieldUtils.mergeLocalFileDataData(getExtraFields(true)));
\r
382 * Sets the central directory part of extra fields.
\r
384 public void setCentralDirectoryExtra(byte[] b) {
\r
386 ZipExtraField[] central =
\r
387 ExtraFieldUtils.parse(b, false,
\r
388 ExtraFieldUtils.UnparseableExtraField.READ);
\r
389 mergeExtraFields(central, false);
\r
390 } catch (Exception e) {
\r
391 throw new RuntimeException(e.getMessage(), e);
\r
396 * Retrieves the extra data for the local file data.
\r
397 * @return the extra data for local file
\r
400 public byte[] getLocalFileDataExtra() {
\r
401 byte[] extra = getExtra();
\r
402 return extra != null ? extra : new byte[0];
\r
406 * Retrieves the extra data for the central directory.
\r
407 * @return the central directory extra data
\r
410 public byte[] getCentralDirectoryExtra() {
\r
411 return ExtraFieldUtils.mergeCentralDirectoryData(getExtraFields(true));
\r
415 * Make this class work in JDK 1.1 like a 1.2 class.
\r
417 * <p>This either stores the size for later usage or invokes
\r
418 * setCompressedSize via reflection.</p>
\r
419 * @param size the size to use
\r
420 * @deprecated since 1.7.
\r
421 * Use setCompressedSize directly.
\r
424 public void setComprSize(long size) {
\r
425 setCompressedSize(size);
\r
429 * Get the name of the entry.
\r
430 * @return the entry name
\r
433 public String getName() {
\r
434 return name == null ? super.getName() : name;
\r
438 * Is this entry a directory?
\r
439 * @return true if the entry is a directory
\r
442 public boolean isDirectory() {
\r
443 return getName().endsWith("/");
\r
447 * Set the name of the entry.
\r
448 * @param name the name to use
\r
450 protected void setName(String name) {
\r
455 * Get the hashCode of the entry.
\r
456 * This uses the name as the hashcode.
\r
457 * @return a hashcode.
\r
460 public int hashCode() {
\r
461 // this method has severe consequences on performance. We cannot rely
\r
462 // on the super.hashCode() method since super.getName() always return
\r
463 // the empty string in the current implemention (there's no setter)
\r
464 // so it is basically draining the performance of a hashmap lookup
\r
465 return getName().hashCode();
\r
469 * The equality method. In this case, the implementation returns 'this == o'
\r
470 * which is basically the equals method of the Object class.
\r
471 * @param o the object to compare to
\r
472 * @return true if this object is the same as <code>o</code>
\r
475 public boolean equals(Object o) {
\r
476 return (this == o);
\r
480 * If there are no extra fields, use the given fields as new extra
\r
481 * data - otherwise merge the fields assuming the existing fields
\r
482 * and the new fields stem from different locations inside the
\r
484 * @param f the extra fields to merge
\r
485 * @param local whether the new fields originate from local data
\r
487 private void mergeExtraFields(ZipExtraField[] f, boolean local)
\r
488 throws ZipException {
\r
489 if (extraFields == null) {
\r
492 for (int i = 0; i < f.length; i++) {
\r
493 ZipExtraField existing;
\r
494 if (f[i] instanceof UnparseableExtraFieldData) {
\r
495 existing = unparseableExtra;
\r
497 existing = getExtraField(f[i].getHeaderId());
\r
499 if (existing == null) {
\r
500 addExtraField(f[i]);
\r
504 instanceof CentralDirectoryParsingZipExtraField)) {
\r
505 byte[] b = f[i].getLocalFileDataData();
\r
506 existing.parseFromLocalFileData(b, 0, b.length);
\r
508 byte[] b = f[i].getCentralDirectoryData();
\r
509 ((CentralDirectoryParsingZipExtraField) existing)
\r
510 .parseFromCentralDirectoryData(b, 0, b.length);
\r