OSDN Git Service

Add new source files
[armadillo/armadillo1.git] / src / jp / sfjp / armadillo / archive / zip / ZipFile.java
1 package jp.sfjp.armadillo.archive.zip;
2
3 import java.io.*;
4 import java.nio.channels.*;
5 import java.util.*;
6 import java.util.zip.*;
7 import jp.sfjp.armadillo.archive.*;
8 import jp.sfjp.armadillo.archive.tar.*;
9 import jp.sfjp.armadillo.io.*;
10
11 public final class ZipFile extends ArchiveFile {
12
13     private File afile;
14     private ZipHeader header;
15     private RandomAccessFile raf;
16     private ZipEntry ongoingEntry;
17     private byte[] buffer;
18     private ZipEndEntry endEntry;
19
20     public ZipFile(File afile) {
21         this.afile = afile;
22         this.header = new ZipHeader();
23     }
24
25     public ZipFile(File afile, boolean withOpen) throws IOException {
26         this(afile);
27         open();
28     }
29
30     @Override
31     public void open() throws IOException {
32         if (raf != null)
33             throw new IOException("the file has been already opened");
34         if (!afile.exists())
35             afile.createNewFile();
36         this.raf = new RandomAccessFile(afile, "rw");
37         this.opened = true;
38     }
39
40     @Override
41     public void reset() throws IOException {
42         if (raf.length() == 0)
43             endEntry = new ZipEndEntry();
44         else if ((endEntry = readEndHeader()) == null)
45             throw new IllegalStateException("failed to read END header");
46         ongoingEntry = null;
47         currentPosition = endEntry.offsetStartCEN;
48         raf.seek(currentPosition);
49     }
50
51     @Override
52     public ArchiveEntry nextEntry() throws IOException {
53         ensureOpen();
54         if (ongoingEntry == null)
55             reset();
56         else {
57             long cenLength = 0L;
58             cenLength += ZipHeader.LENGTH_CEN;
59             cenLength += ongoingEntry.getNameAsBytes().length;
60             cenLength += ongoingEntry.extlen;
61             currentPosition += cenLength;
62         }
63         raf.seek(currentPosition);
64         ongoingEntry = header.readCEN(Channels.newInputStream(raf.getChannel()));
65         return (ongoingEntry == null) ? ArchiveEntry.NULL : ongoingEntry;
66     }
67
68     @Override
69     public ArchiveEntry newEntry(String name) {
70         return new ZipEntry(name);
71     }
72
73     @Override
74     public void addEntry(ArchiveEntry entry, InputStream is, long length) throws IOException {
75         reset();
76         List<ZipEntry> entries = getEntries();
77         ZipEntry newEntry = new ZipEntry(entry.getName());
78         newEntry.copyFrom(entry);
79         // overwrite new entry, CEN and END
80         raf.seek(endEntry.offsetStartCEN);
81         ZipOutputStream zos = new ZipOutputStream(Channels.newOutputStream(raf.getChannel()));
82         zos.putNextEntry(newEntry);
83         if (length > 0) {
84             final long written = IOUtilities.transfer(is, zos, length);
85             assert written == length;
86             newEntry.setCompressedSize(written);
87             newEntry.setSize(length);
88         }
89         zos.closeEntry();
90         zos.flush();
91         final long p = raf.getFilePointer();
92         raf.seek(endEntry.offsetStartCEN);
93         newEntry.flags &= 0xFFF7;
94         zos.putNextEntry(newEntry);
95         newEntry.reloff = endEntry.offsetStartCEN;
96         entries.add(newEntry);
97         endEntry.countCENs = (short)entries.size();
98         raf.seek(p);
99         writeEnd(entries);
100     }
101
102     @Override
103     public void updateEntry(ArchiveEntry entry, InputStream is, long length) throws IOException {
104         super.updateEntry(entry, is, length);
105     }
106
107     @Override
108     public void removeEntry(ArchiveEntry entry) throws IOException {
109         if (!seek(entry))
110             throw new TarException("entry " + entry + " not found");
111         assert ongoingEntry != null;
112         ZipEntry target = this.ongoingEntry;
113         ZipEndEntry endEntry = this.endEntry;
114         final long offset = target.reloff;
115         List<ZipEntry> entries = getEntries(); // reset offset
116         raf.seek(offset);
117         ZipInputStream zis = new ZipInputStream(Channels.newInputStream(raf.getChannel()));
118         ZipEntry locEntry = zis.getNextEntry();
119         zis.skip(locEntry.uncompsize);
120         zis.closeEntry();
121         final long totalLength = getOffset() - offset;
122         endEntry.offsetStartCEN -= totalLength;
123         truncate(offset, totalLength);
124         for (ZipEntry zipEntry : entries)
125             if (zipEntry.equalsName(target)) {
126                 entries.remove(zipEntry);
127                 break;
128             }
129         for (ZipEntry zipEntry : entries)
130             if (zipEntry.reloff > offset)
131                 zipEntry.reloff -= totalLength;
132         raf.seek(endEntry.offsetStartCEN);
133         this.endEntry = endEntry;
134         writeEnd(entries);
135     }
136
137     void writeEnd(List<ZipEntry> entries) throws IOException {
138         ZipEndEntry endEntry = ZipEndEntry.create(getOffset(), entries);
139         OutputStream os = Channels.newOutputStream(raf.getChannel());
140         for (ZipEntry entry : entries)
141             header.writeCEN(os, entry);
142         header.writeEND(os, endEntry);
143         if (raf.length() > raf.getFilePointer())
144             raf.setLength(raf.getFilePointer());
145     }
146
147     public void insertEmptySpace(long offset, long blockCount) throws IOException {
148         final int blockSize = 8192;
149         final long insertLength = blockCount * blockSize;
150         raf.seek(raf.length() + insertLength - 1);
151         raf.write(0);
152         final long x = raf.getFilePointer();
153         long p2 = x - blockSize; // last block
154         long p1 = p2 - insertLength;
155         assert p1 > 0 && p2 > 0;
156         byte[] buffer = this.buffer;
157         for (int i = 0; i < blockCount && p1 >= offset; i++) {
158             raf.seek(p1);
159             raf.readFully(buffer);
160             raf.seek(p2);
161             raf.write(buffer);
162             p1 -= blockSize;
163             p2 -= blockSize;
164         }
165         raf.seek(currentPosition = offset);
166     }
167
168     @Override
169     public long extract(OutputStream os) throws IOException {
170         raf.seek(ongoingEntry.reloff);
171         InputStream is = Channels.newInputStream(raf.getChannel());
172         ZipEntry entry = header.read(is);
173         assert entry != null;
174         entry.compsize = ongoingEntry.compsize;
175         entry.uncompsize = ongoingEntry.uncompsize;
176         entry.flags &= (8 ^ 0xFFFF);
177         ZipInputStream zis = new ZipInputStream(is);
178         zis.openEntry(entry);
179         return IOUtilities.transfer(zis, os, ongoingEntry.uncompsize);
180     }
181
182     int getOffset() throws IOException {
183         final long offset = raf.getFilePointer();
184         if (offset > Integer.MAX_VALUE)
185             throw new ZipException("size not supported: " + offset);
186         return (int)offset;
187     }
188
189     List<ZipEntry> getEntries() {
190         List<ZipEntry> entries = new ArrayList<ZipEntry>();
191         for (ArchiveEntry entry : this)
192             entries.add((ZipEntry)entry);
193         return entries;
194     }
195
196     ZipEndEntry readEndHeader() throws IOException {
197         final int commentlen = 0;
198         raf.seek(raf.length() - 22 - commentlen);
199         return header.readEND(Channels.newInputStream(raf.getChannel()));
200     }
201
202     void truncate(final long offset, final long length) throws IOException {
203         final int blockSize = 8192;
204         long p2 = offset;
205         long p1 = p2 + length;
206         byte[] bytes = new byte[blockSize];
207         while (true) {
208             raf.seek(p1);
209             final int r = raf.read(bytes);
210             if (r <= 0)
211                 break;
212             raf.seek(p2);
213             raf.write(bytes, 0, r);
214             p2 += r;
215             p1 += r;
216         }
217         raf.setLength(raf.length() - length);
218         reset();
219     }
220
221     @Override
222     public void close() throws IOException {
223         try {
224             if (raf != null)
225                 raf.close();
226         }
227         finally {
228             super.close();
229             afile = null;
230             header = null;
231         }
232     }
233
234 }