summaryrefslogtreecommitdiff
path: root/src/main/java/de/waldheinz/fs/fat/ClusterChain.java
blob: e296092875a16a9013957bba4b688190d7f5b021 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
/*
 * Copyright (C) 2009,2010 Matthias Treydte <mt@waldheinz.de>
 *
 * This library is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as published
 * by the Free Software Foundation; either version 2.1 of the License, or
 * (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
 * License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this library; If not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */

package de.waldheinz.fs.fat;

import de.waldheinz.fs.AbstractFsObject;
import de.waldheinz.fs.BlockDevice;
import java.io.EOFException;
import java.io.IOException;
import java.nio.ByteBuffer;

/**
 * A chain of clusters as stored in a {@link Fat}.
 *
 * @author Matthias Treydte &lt;waldheinz at gmail.com&gt;
 */
final class ClusterChain extends AbstractFsObject {
    protected final Fat fat;
    private final BlockDevice device;
    private final int clusterSize;
    protected final long dataOffset;
    
    private long startCluster;
    
    /**
     * Creates a new {@code ClusterChain} that contains no clusters.
     *
     * @param fat the {@code Fat} that holds the new chain
     * @param readOnly if the chain should be created read-only
     */
    public ClusterChain(Fat fat, boolean readOnly) {
        this(fat, 0, readOnly);
    }
    
    public ClusterChain(Fat fat, long startCluster, boolean readOnly) {
        super(readOnly);
        
        this.fat = fat;
        
        if (startCluster != 0) {
            this.fat.testCluster(startCluster);
            
            if (this.fat.isFreeCluster(startCluster))
                throw new IllegalArgumentException(
                    "cluster " + startCluster + " is free");
        }
        
        this.device = fat.getDevice();
        this.dataOffset = FatUtils.getFilesOffset(fat.getBootSector());
        this.startCluster = startCluster;
        this.clusterSize = fat.getBootSector().getBytesPerCluster();
    }

    public int getClusterSize() {
        return clusterSize;
    }
    
    public Fat getFat() {
        return fat;
    }

    public BlockDevice getDevice() {
        return device;
    }

    /**
     * Returns the first cluster of this chain.
     *
     * @return the chain's first cluster, which may be 0 if this chain does
     *      not contain any clusters
     */
    public long getStartCluster() {
        return startCluster;
    }
    
    /**
     * Calculates the device offset (0-based) for the given cluster and offset
     * within the cluster.
     * 
     * @param cluster
     * @param clusterOffset
     * @return long
     * @throws FileSystemException
     */
    private long getDevOffset(long cluster, int clusterOffset) {
        return dataOffset + clusterOffset +
                ((cluster - Fat.FIRST_CLUSTER) * clusterSize);
    }

    /**
     * Returns the size this {@code ClusterChain} occupies on the device.
     *
     * @return the size this chain occupies on the device in bytes
     */
    public long getLengthOnDisk() {
        if (getStartCluster() == 0) return 0;
        
        return getChainLength() * clusterSize;
    }
    
    /**
     * Sets the length of this {@code ClusterChain} in bytes. Because a
     * {@code ClusterChain} can only contain full clusters, the new size
     * will always be a multiple of the cluster size.
     *
     * @param size the desired number of bytes the can be stored in
     *      this {@code ClusterChain}
     * @return the true number of bytes this {@code ClusterChain} can contain
     * @throws IOException on error setting the new size
     * @see #setChainLength(int) 
     */
    public long setSize(long size) throws IOException {
        final long nrClusters = ((size + clusterSize - 1) / clusterSize);
        if (nrClusters > Integer.MAX_VALUE)
            throw new IOException("too many clusters");

        setChainLength((int) nrClusters);
        
        return clusterSize * nrClusters;
    }

    /**
     * Determines the length of this {@code ClusterChain} in clusters.
     *
     * @return the length of this chain
     */
    public int getChainLength() {
        if (getStartCluster() == 0) return 0;
        
        final long[] chain = getFat().getChain(getStartCluster());
        return chain.length;
    }

    /**
     * Sets the length of this cluster chain in clusters.
     *
     * @param nrClusters the new number of clusters this chain should contain,
     *      must be {@code >= 0}
     * @throws IOException on error updating the chain length
     * @see #setSize(long) 
     */
    public void setChainLength(int nrClusters) throws IOException {
        if (nrClusters < 0) throw new IllegalArgumentException(
                "negative cluster count"); //NOI18N
                
        if ((this.startCluster == 0) && (nrClusters == 0)) {
            /* nothing to do */
        } else if ((this.startCluster == 0) && (nrClusters > 0)) {
            final long[] chain = fat.allocNew(nrClusters);
            this.startCluster = chain[0];
        } else {
            final long[] chain = fat.getChain(startCluster);
            
            if (nrClusters != chain.length) {
                if (nrClusters > chain.length) {
                    /* grow the chain */
                    int count = nrClusters - chain.length;
                    
                    while (count > 0) {
                        fat.allocAppend(getStartCluster());
                        count--;
                    }
                } else {
                    /* shrink the chain */
                    if (nrClusters > 0) {
                        fat.setEof(chain[nrClusters - 1]);
                        for (int i = nrClusters; i < chain.length; i++) {
                            fat.setFree(chain[i]);
                        }
                    } else {
                        for (int i=0; i < chain.length; i++) {
                            fat.setFree(chain[i]);
                        }
                        
                        this.startCluster = 0;
                    }
                }
            }
        }
    }
    
    public void readData(long offset, ByteBuffer dest)
            throws IOException {

        int len = dest.remaining();

        if ((startCluster == 0 && len > 0)) throw new EOFException();
        
        final long[] chain = getFat().getChain(startCluster);
        final BlockDevice dev = getDevice();

        int chainIdx = (int) (offset / clusterSize);
        if (offset % clusterSize != 0) {
            int clusOfs = (int) (offset % clusterSize);
            int size = Math.min(len,
                    (int) (clusterSize - (offset % clusterSize) - 1));
            dest.limit(dest.position() + size);

            dev.read(getDevOffset(chain[chainIdx], clusOfs), dest);
            
            offset += size;
            len -= size;
            chainIdx++;
        }

        while (len > 0) {
            int size = Math.min(clusterSize, len);
            dest.limit(dest.position() + size);

            dev.read(getDevOffset(chain[chainIdx], 0), dest);

            len -= size;
            chainIdx++;
        }
    }
    
    /**
     * Writes data to this cluster chain, possibly growing the chain so it
     * can store the additional data. When this method returns without throwing
     * an exception, the buffer's {@link ByteBuffer#position() position} will
     * equal it's {@link ByteBuffer#limit() limit}, and the limit will not
     * have changed. This is not guaranteed if writing fails.
     *
     * @param offset the offset where to write the first byte from the buffer
     * @param srcBuf the buffer to write to this {@code ClusterChain}
     * @throws IOException on write error
     */
    public void writeData(long offset, ByteBuffer srcBuf) throws IOException {
        
        int len = srcBuf.remaining();

        if (len == 0) return;

        final long minSize = offset + len;
        if (getLengthOnDisk() < minSize) {
            setSize(minSize);
        }
        
        final long[] chain = fat.getChain(getStartCluster());

        int chainIdx = (int) (offset / clusterSize);
        if (offset % clusterSize != 0) {
            int clusOfs = (int) (offset % clusterSize);
            int size = Math.min(len,
                    (int) (clusterSize - (offset % clusterSize)));
            srcBuf.limit(srcBuf.position() + size);
            
            device.write(getDevOffset(chain[chainIdx], clusOfs), srcBuf);
            
            offset += size;
            len -= size;
            chainIdx++;
        }
        
        while (len > 0) {
            int size = Math.min(clusterSize, len);
            srcBuf.limit(srcBuf.position() + size);

            device.write(getDevOffset(chain[chainIdx], 0), srcBuf);

            len -= size;
            chainIdx++;
        }
        
    }

    @Override
    public boolean equals(Object obj) {
        if (obj == null) return false;
        if (!(obj instanceof ClusterChain)) return false;
        
        final ClusterChain other = (ClusterChain) obj;
        
        if (this.fat != other.fat &&
                (this.fat == null || !this.fat.equals(other.fat))) {

            return false;
        }
        
        if (this.startCluster != other.startCluster) {
            return false;
        }

        return true;
    }

    @Override
    public int hashCode() {
        int hash = 3;
        hash = 79 * hash +
                (this.fat != null ? this.fat.hashCode() : 0);
        hash = 79 * hash +
                (int) (this.startCluster ^ (this.startCluster >>> 32));
        return hash;
    }
    
}