View Javadoc
1   /*
2    * Copyright (C) 2005-2015 Schlichtherle IT Services.
3    * All rights reserved. Use is subject to license terms.
4    */
5   package net.java.truevfs.kernel.spec;
6   
7   import edu.umd.cs.findbugs.annotations.CreatesObligation;
8   import java.io.*;
9   import java.nio.charset.*;
10  import javax.annotation.*;
11  import javax.annotation.concurrent.Immutable;
12  import net.java.truecommons.cio.*;
13  import net.java.truecommons.cio.Entry.Type;
14  import static net.java.truecommons.cio.Entry.Type.DIRECTORY;
15  import net.java.truecommons.shed.BitField;
16  import static net.java.truecommons.shed.Paths.cutTrailingSeparators;
17  import static net.java.truevfs.kernel.spec.FsNodeName.*;
18  
19  /**
20   * An abstract factory for components required for accessing archive files.
21   * Implementations of this abstract base class are used to access specific
22   * archive formats like ZIP, TAR et al.
23   * <p>
24   * Subclasses should be immutable.
25   *
26   * @param  <E> the type of the archive entries.
27   * @author Christian Schlichtherle
28   */
29  @Immutable
30  public abstract class FsArchiveDriver<E extends FsArchiveEntry>
31  extends FsDriver {
32  
33      /**
34       * {@inheritDoc}
35       *
36       * @return The implementation in the class {@link FsArchiveDriver}
37       *         unconditionally returns {@code true}.
38       */
39      @Override
40      public final boolean isArchiveDriver() { return true; }
41  
42      /**
43       * {@inheritDoc}
44       * <p>
45       * The implementation in the class {@link FsArchiveDriver} forwards the
46       * call to the given file system manager.
47       */
48      @Override
49      public final FsController newController(
50              FsManager context,
51              FsModel model,
52              @Nonnull FsController parent) {
53          assert parent.getModel().equals(model.getParent());
54          return context.newController(this, model, parent);
55      }
56  
57      /**
58       * Decorates the given file system controller.
59       *
60       * @param  controller the file system controller to decorate.
61       * @return The implementation in the class {@link FsArchiveDriver}
62       *         unconditionally returns {@code controller}.
63       */
64      public FsController decorate(FsController controller) { return controller; }
65  
66      /**
67       * Returns the character set to use for encoding character based meta data
68       * such as entry names or file comments to binary data when writing
69       * an archive file.
70       * Depending on the archive file format, this may get used for decoding
71       * binary data when reading an archive file, too.
72       * <p>
73       * This is an immutable property - multiple calls must return the same
74       * object.
75       *
76       * @return The character set to use for encoding character based meta data
77       *         such as entry names or file comments to binary data when writing
78       *         an archive file.
79       */
80      public abstract Charset getCharset();
81  
82      /**
83       * Ensures that the given entry name can get encoded by this driver's
84       * {@linkplain #getCharset() character set}.
85       *
86       * @param  name an entry name.
87       * @throws CharConversionException If the entry name contains characters
88       *         which cannot get encoded.
89       */
90      public final void checkEncodable(final String name)
91      throws CharConversionException {
92          CharsetEncoder enc = encoder.get();
93          if (null == enc) {
94              enc = getCharset().newEncoder();
95              encoder.set(enc);
96          }
97          if (!enc.canEncode(name))
98              throw new CharConversionException(name +
99                      " (entry name is not encodable with " + getCharset() + ")");
100     }
101 
102     private final ThreadLocal<CharsetEncoder> encoder = new ThreadLocal<>();
103 
104     /**
105      * Returns {@code true} if and only if the archive files produced by this
106      * archive driver may contain redundant archive entry contents.
107      * If the return value is {@code true}, then an archive file may contain
108      * redundant archive entry contents, but only the last contents written
109      * should get used when reading the archive file.
110      * <p>
111      * This is an immutable property - multiple calls must return the same
112      * value.
113      * <p>
114      * The default value of this property is {@code false} as defined by the
115      * implementation in the class {@link FsArchiveDriver}.
116      *
117      * @return {@code true} if and only if the archive files produced by this
118      *         archive driver may contain redundant archive entry contents.
119      */
120     public boolean getRedundantContentSupport() { return false; }
121 
122     /**
123      * Returns {@code true} if and only if the archive files produced by this
124      * archive driver may contain redundant archive entry meta data.
125      * If the return value is {@code true}, then an archive file may contain
126      * redundant archive entry meta data, but only the last meta data written
127      * should get used when reading the archive file.
128      * This usually implies the existence of a central directory in the
129      * resulting archive file.
130      * <p>
131      * This is an immutable property - multiple calls must return the same
132      * value.
133      * <p>
134      * The default value of this property is {@code false} as defined by the
135      * implementation in the class {@link FsArchiveDriver}.
136      *
137      * @return {@code true} if and only if the archive files produced by this
138      *         archive driver may contain redundant archive entry meta data.
139      */
140     public boolean getRedundantMetaDataSupport() { return false; }
141 
142     /**
143      * Returns the pool for allocating temporary I/O buffers.
144      * <p>
145      * This is an immutable property - multiple calls must return the same
146      * object.
147      *
148      * @return The pool to use for allocating temporary I/O buffers.
149      */
150     public abstract IoBufferPool getPool();
151 
152     /**
153      * This method gets called by an archive controller in order to create a
154      * new input service for its target archive file.
155      * <p>
156      * The implementation in {@link FsArchiveDriver} simply forwards the call
157      * to {@link #source}
158      * and {@link #newInput(FsModel, FsInputSocketSource)}.
159      *
160      * @param  model the file system model for the target archive file.
161      * @param  options the options for accessing the target archive file in the
162      *         parent file system.
163      * @param  controller the controller for the parent file system with the
164      *         target archive file.
165      * @param  name the node name of the target archive file in the parent
166      *         file system.
167      * @return A new input service for reading the target archive file.
168      *         Note that this service does <em>not</em> need to be thread-safe!
169      * @throws IOException on any I/O error.
170      *         If the file system node for the given model exists in the
171      *         parent file system and is <em>not</em> a {@link Type#SPECIAL}
172      *         type, then this exception is deemed to indicate a
173      *         <em>persistent false positive</em> archive file and gets cached
174      *         until the file system controller for the given model gets
175      *         {@linkplain FsController#sync(BitField) synced} again.
176      *         Otherwise, this exception is deemed to indicate a
177      *         <em>transient false positive</em> archive file and does not
178      *         get cached.
179      */
180     @CreatesObligation
181     public InputService<E> newInput(
182             FsModel model,
183             BitField<FsAccessOption> options,
184             FsController controller,
185             FsNodeName name)
186     throws IOException {
187         return newInput(model, source(options, controller, name));
188     }
189 
190     /**
191      * Creates a new input service for reading archive entries for the given
192      * {@code model} from the target archive file referenced by {@code source}.
193      *
194      * @param  model the file system model.
195      * @param  source the source for reading the target archive file.
196      * @return A new input service.
197      *         Note that this service does <em>not</em> need to be thread-safe!
198      * @throws IOException on any I/O error.
199      * @see    #newInput(FsModel, BitField, FsController, FsNodeName)
200      */
201     @CreatesObligation
202     protected abstract InputService<E> newInput(
203             FsModel model,
204             FsInputSocketSource source)
205     throws IOException;
206 
207     /**
208      * This method gets called by an archive controller in order to create a
209      * new output service for its target archive file.
210      * <p>
211      * The implementation in {@link FsArchiveDriver} simply forwards the call
212      * to {@link #sink}
213      * and {@link #newOutput(FsModel, FsOutputSocketSink, InputService)}.
214      *
215      * @param  model the file system model for the target archive file.
216      * @param  options the options for accessing the target archive file in the
217      *         parent file system.
218      * @param  controller the controller for the parent file system with the
219      *         target archive file.
220      * @param  name the node name of the target archive file in the parent
221      *         file system.
222      * @param  input the nullable {@link InputService} for the target archive
223      *         file.
224      *         If not {@code null}, then the target archive file is going to
225      *         get updated.
226      *         This parameter is guaranteed to be the product of this driver's
227      *         factory method
228      *         {@link #newInput(FsModel, BitField, FsController, FsNodeName)}.
229      * @return A new output service for writing the target archive file.
230      *         Note that this service does <em>not</em> need to be thread-safe!
231      * @throws IOException on any I/O error.
232      */
233     @CreatesObligation
234     public OutputService<E> newOutput(
235             FsModel model,
236             BitField<FsAccessOption> options,
237             FsController controller,
238             FsNodeName name,
239             @CheckForNull @WillNotClose InputService<E> input)
240     throws IOException {
241         return newOutput(model, sink(options, controller, name), input);
242     }
243 
244     /**
245      * Creates a new input service for writing archive entries for the given
246      * {@code model} to the target archive file referenced by {@code sink}.
247      *
248      * @param  model the file system model.
249      * @param  sink the sink for writing the target archive file.
250      * @param  input the nullable {@link InputService} for the target archive
251      *         file.
252      *         If not {@code null}, then the target archive file is going to
253      *         get updated.
254      *         This parameter is guaranteed to be the product of this driver's
255      *         factory method
256      *         {@link #newInput(FsModel, BitField, FsController, FsNodeName)}.
257      * @return A new output service for writing the target archive file.
258      *         Note that this service does <em>not</em> need to be thread-safe!
259      * @throws IOException on any I/O error.
260      * @see    #newOutput(FsModel, BitField, FsController, FsNodeName, InputService)
261      */
262     @CreatesObligation
263     protected abstract OutputService<E> newOutput(
264             FsModel model,
265             FsOutputSocketSink sink,
266             @CheckForNull @WillNotClose InputService<E> input)
267     throws IOException;
268 
269     /**
270      * Called to prepare reading an archive file artifact of this driver from
271      * {@code name} in {@code controller} using {@code options}.
272      * <p>
273      * This method is overridable to enable modifying the given options
274      * before forwarding the call to the given controller.
275      * The implementation in the class {@link FsArchiveDriver} simply forwards
276      * the call to the given controller with the given options unaltered.
277      *
278      * @param  options the options for accessing the file system node.
279      * @param  controller the controller to use for reading an artifact of this
280      *         driver.
281      * @param  name the node name.
282      * @return A source for reading an artifact of this driver.
283      * @see    #newInput(FsModel, BitField, FsController, FsNodeName)
284      */
285     protected FsInputSocketSource source(
286             BitField<FsAccessOption> options,
287             FsController controller,
288             FsNodeName name) {
289         return new FsInputSocketSource(options, controller.input(options, name));
290     }
291 
292     /**
293      * Called to prepare writing an archive file artifact of this driver to
294      * the node {@code name} in {@code controller} using {@code options} and
295      * the nullable {@code template}.
296      * <p>
297      * This method is overridable to enable modifying the given options
298      * before forwarding the call to the given controller.
299      * The implementation in the class {@link FsArchiveDriver} simply forwards
300      * the call to the given controller with the given options unaltered.
301      *
302      * @param  options the options for accessing the file system node.
303      * @param  controller the controller to use for writing an artifact of this
304      *         driver.
305      * @param  name the node name.
306      * @return A sink for writing an artifact of this driver.
307      * @see    #newOutput(FsModel, BitField, FsController, FsNodeName, InputService)
308      */
309     protected FsOutputSocketSink sink(
310             BitField<FsAccessOption> options,
311             FsController controller,
312             FsNodeName name) {
313         return new FsOutputSocketSink(options,
314                 controller.output(options, name, null));
315     }
316 
317     /**
318      * Equivalent to {@link #newEntry(BitField, String, Entry.Type, Entry)
319      * newEntry(FsAccessOptions.NONE, name, type, template)}.
320      *
321      * @param  name the entry name.
322      * @param  type the entry type.
323      * @param  template if not {@code null}, then the new entry shall inherit
324      *         as much properties from this entry as possible - with the
325      *         exception of its name and type.
326      * @return A new entry for the given name.
327      */
328     public final E newEntry(String name, Type type, @CheckForNull Entry template) {
329         return newEntry(FsAccessOptions.NONE, name, type, template);
330     }
331 
332     /**
333      * Returns a new entry for the given name.
334      * The implementation may change this name in order to form a valid
335      * {@link Entry#getName() entry name} for their particular requirements.
336      *
337      * @param  options when called from {@link FsController#make}, this is its
338      *         {@code options} parameter, otherwise it's typically an empty set.
339      * @param  name the entry name.
340      * @param  type the entry type.
341      * @param  template if not {@code null}, then the new entry shall inherit
342      *         as much properties from this entry as possible - with the
343      *         exception of its name and type.
344      * @return A new entry for the given name.
345      * @see    #newEntry(String, Entry.Type, Entry)
346      */
347     public abstract E newEntry(
348             BitField<FsAccessOption> options,
349             String name,
350             Type type,
351             @CheckForNull Entry template);
352 
353     /**
354      * Normalizes the given entry name so that it forms a valid entry name for
355      * ZIP and TAR files by ensuring that the returned entry name ends with the
356      * separator character {@code '/'} if and only if {@code type} is
357      * {@code DIRECTORY}.
358      *
359      * @param  name an entry name.
360      * @param  type an entry type.
361      * @return The normalized entry name.
362      */
363     public static String normalize(final String name, final Type type) {
364         return DIRECTORY == type
365                 ? name.endsWith(SEPARATOR) ? name : name + SEPARATOR_CHAR
366                 : cutTrailingSeparators(name, SEPARATOR_CHAR);
367     }
368 
369     /**
370      * Returns a string representation of this object for debugging and logging
371      * purposes.
372      */
373     @Override
374     public String toString() {
375         return String.format("%s@%x[archiveDriver=%b, charset=%s, redundantContentSupport=%b, redundantMetaDataSupport=%b, pool=%s]",
376                 getClass().getName(),
377                 hashCode(),
378                 isArchiveDriver(),
379                 getCharset(),
380                 getRedundantContentSupport(),
381                 getRedundantMetaDataSupport(),
382                 getPool());
383     }
384 }