OSDN Git Service

Add new source files
[armadillo/armadillo1.git] / src / jp / sfjp / armadillo / archive / tar / TarFile.java
1 package jp.sfjp.armadillo.archive.tar;
2
3 import java.io.*;
4 import java.nio.channels.*;
5 import jp.sfjp.armadillo.archive.*;
6
7 public final class TarFile extends ArchiveFile { //implements ArchiveCreator, ArchiveExtractor {
8
9     private File afile;
10     private RandomAccessFile raf;
11     private TarHeader header;
12     private TarEntry ongoingEntry;
13     private final byte[] buffer;
14
15     public TarFile(File afile) {
16         if (afile.isDirectory())
17             throw new IllegalArgumentException("not a file: " + afile.getPath());
18         this.afile = afile;
19         this.header = new TarHeader();
20         this.buffer = new byte[TarHeader.BLOCK_SIZE];
21     }
22
23     @Override
24     public void open() throws IOException {
25         if (raf != null)
26             throw new IOException("the file has been already opened");
27         if (!afile.exists())
28             afile.createNewFile();
29         this.raf = new RandomAccessFile(afile, "rw");
30         this.opened = true;
31     }
32
33     @Override
34     public void reset() throws IOException {
35         ongoingEntry = null;
36         currentPosition = 0L;
37         raf.seek(0L);
38     }
39
40     @Override
41     public ArchiveEntry nextEntry() throws IOException {
42         ensureOpen();
43         if (ongoingEntry == null)
44             currentPosition = 0L;
45         else {
46             final long size = ongoingEntry.size;
47             final long totalLength = TarHeader.BLOCK_SIZE + size + TarHeader.getSkipSize(size);
48             assert totalLength % TarHeader.BLOCK_SIZE == 0;
49             currentPosition += totalLength;
50         }
51         raf.seek(currentPosition);
52         ongoingEntry = readCurrentEntry();
53         raf.seek(currentPosition + TarHeader.BLOCK_SIZE);
54         return ongoingEntry;
55     }
56
57     @Override
58     public boolean seek(ArchiveEntry entry) throws IOException {
59         ensureOpen();
60         reset();
61         while (true) {
62             ArchiveEntry nextEntry = nextEntry();
63             if (nextEntry == null)
64                 break;
65             if (nextEntry.getName().equals(entry.getName()))
66                 return true;
67         }
68         reset();
69         return false;
70     }
71
72     public void seekEndOfLastEntry() throws IOException {
73         ensureOpen();
74         reset();
75         while (true)
76             if (nextEntry() == null)
77                 break;
78     }
79
80     @Override
81     public void addEntry(ArchiveEntry entry, InputStream is, long length) throws IOException {
82         final long blockCount = calculateBlockCount(length);
83         FileChannel fc = raf.getChannel();
84         seekEndOfLastEntry();
85         raf.seek(currentPosition);
86         insertEmptyBlock(raf.getFilePointer(), blockCount + 1);
87         TarEntry newEntry = toTarEntry(entry);
88         header.write(Channels.newOutputStream(fc), newEntry);
89         if (blockCount > 0) {
90             final long p = currentPosition + TarHeader.BLOCK_SIZE;
91             long written = fc.transferFrom(Channels.newChannel(is), p, length);
92             assert written == length;
93         }
94         raf.seek(currentPosition);
95         ongoingEntry = newEntry;
96     }
97
98     @Override
99     public void updateEntry(ArchiveEntry entry, InputStream is, long length) throws IOException {
100         final int blockSize = TarHeader.BLOCK_SIZE;
101         if (!seek(entry))
102             throw new TarException("entry " + entry + " not found");
103         raf.seek(currentPosition);
104         header.write(Channels.newOutputStream(raf.getChannel()), toTarEntry(entry));
105         if (length > 0) {
106             final long oldCount = calculateBlockCount(ongoingEntry.size);
107             final long newCount = calculateBlockCount(length);
108             assert newCount > 0;
109             final long p = currentPosition + blockSize;
110             if (newCount < oldCount)
111                 truncateBlock(p, oldCount - newCount);
112             if (newCount <= oldCount) {
113                 raf.seek(p + (newCount - 1) * blockSize);
114                 raf.write(buffer);
115             }
116             else
117                 insertEmptyBlock(p, newCount - oldCount);
118             raf.getChannel().transferFrom(Channels.newChannel(is), p, length);
119         }
120         raf.seek(currentPosition);
121         ongoingEntry = TarArchiveCreator.toTarEntry(entry);
122     }
123
124     @Override
125     public void removeEntry(ArchiveEntry entry) throws IOException {
126         if (!seek(entry))
127             throw new TarException("entry " + entry + " not found");
128         raf.seek(currentPosition);
129         assert ongoingEntry != null;
130         final long size = ongoingEntry.size;
131         if (size == 0)
132             truncate(currentPosition, TarHeader.BLOCK_SIZE);
133         else {
134             final long totalLength = TarHeader.BLOCK_SIZE + size + TarHeader.getSkipSize(size);
135             assert totalLength >= 0 && totalLength <= Integer.MAX_VALUE;
136             assert totalLength % TarHeader.BLOCK_SIZE == 0;
137             truncate(currentPosition, totalLength);
138         }
139     }
140
141     static TarEntry toTarEntry(ArchiveEntry entry) {
142         if (entry instanceof TarEntry)
143             return (TarEntry)entry;
144         TarEntry newEntry = new TarEntry(entry.getName());
145         entry.copyTo(newEntry);
146         return newEntry;
147     }
148
149     @Override
150     public ArchiveEntry newEntry(String name) {
151         return new TarEntry(name);
152     }
153
154     public void insertEmptyBlock(long offset, long blockCount) throws IOException {
155         final int blockSize = TarHeader.BLOCK_SIZE;
156         final long insertLength = blockCount * blockSize;
157         raf.seek(raf.length() + insertLength - 1);
158         raf.write(0);
159         final long x = raf.getFilePointer();
160         long p2 = x - blockSize; // last block
161         long p1 = p2 - insertLength;
162         assert p1 > 0 && p2 > 0;
163         byte[] buffer = this.buffer;
164         for (int i = 0; i < blockCount && p1 >= offset; i++) {
165             raf.seek(p1);
166             raf.readFully(buffer);
167             raf.seek(p2);
168             raf.write(buffer);
169             p1 -= blockSize;
170             p2 -= blockSize;
171         }
172         raf.seek(currentPosition = offset);
173     }
174
175     void truncateBlock(long offset, long blockCount) throws IOException {
176         truncate(offset, blockCount * TarHeader.BLOCK_SIZE);
177     }
178
179     void truncate(final long offset, final long length) throws IOException {
180         final int blockSize = TarHeader.BLOCK_SIZE;
181         long p2 = offset;
182         long p1 = p2 + length;
183         final long restLength = raf.length() - p1;
184         assert restLength >= blockSize && restLength % blockSize == 0;
185         byte[] bytes = new byte[blockSize];
186         final long blockCount = calculateBlockCount(restLength);
187         for (int i = 0; i < blockCount; i++) {
188             raf.seek(p1);
189             raf.readFully(bytes);
190             raf.seek(p2);
191             raf.write(bytes);
192             p2 += blockSize;
193             p1 += blockSize;
194         }
195         raf.setLength(raf.length() - length);
196         reset();
197     }
198
199     TarEntry readCurrentEntry() throws IOException {
200         return header.read(Channels.newInputStream(raf.getChannel()));
201     }
202
203     static long calculateBlockCount(long size) {
204         if (size == 0L)
205             return 0L;
206         final long r = size / TarHeader.BLOCK_SIZE;
207         final long mod = size - r * TarHeader.BLOCK_SIZE;
208         return r + (mod > 0 ? 1 : 0);
209     }
210
211     @Override
212     public void close() throws IOException {
213         try {
214             raf.close();
215         }
216         finally {
217             super.close();
218             afile = null;
219             header = null;
220         }
221     }
222
223 }