View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.vfs2.provider;
18  
19  import java.io.InputStream;
20  import java.io.OutputStream;
21  import java.security.cert.Certificate;
22  import java.util.HashSet;
23  import java.util.Map;
24  import java.util.Set;
25  import java.util.stream.Stream;
26  
27  import org.apache.commons.lang3.ArrayUtils;
28  import org.apache.commons.vfs2.FileChangeEvent;
29  import org.apache.commons.vfs2.FileContentInfo;
30  import org.apache.commons.vfs2.FileListener;
31  import org.apache.commons.vfs2.FileName;
32  import org.apache.commons.vfs2.FileNotFolderException;
33  import org.apache.commons.vfs2.FileObject;
34  import org.apache.commons.vfs2.FileSystemException;
35  import org.apache.commons.vfs2.FileType;
36  import org.apache.commons.vfs2.RandomAccessContent;
37  import org.apache.commons.vfs2.util.RandomAccessMode;
38  import org.apache.commons.vfs2.util.WeakRefFileListener;
39  
40  /**
41   * A file backed by another file.
42   * <p>
43   * TODO - Extract subclass that overlays the children.
44   * </p>
45   *
46   * @param <AFS> A subclass of AbstractFileSystem.
47   */
48  public class DelegateFileObject<AFS extends AbstractFileSystem> extends AbstractFileObject<AFS>
49          implements FileListener {
50      private FileObject file;
51      private final Set<String> children = new HashSet<>();
52      private boolean ignoreEvent;
53  
54      public DelegateFileObject(final AbstractFileName name, final AFS fileSystem, final FileObject file)
55              throws FileSystemException {
56          super(name, fileSystem);
57          this.file = file;
58          if (file != null) {
59              WeakRefFileListener.installListener(file, this);
60          }
61      }
62  
63      /**
64       * Get access to the delegated file.
65       *
66       * @return The FileObject.
67       * @since 2.0
68       */
69      public FileObject getDelegateFile() {
70          return file;
71      }
72  
73      /**
74       * Adds a child to this file.
75       *
76       * @param baseName The base FileName.
77       * @param type The FileType.
78       * @throws Exception if an error occurs.
79       */
80      public void attachChild(final FileName baseName, final FileType type) throws Exception {
81          final FileType oldType = doGetType();
82          if (children.add(baseName.getBaseName())) {
83              childrenChanged(baseName, type);
84          }
85          maybeTypeChanged(oldType);
86      }
87  
88      /**
89       * Attaches or detaches the target file.
90       *
91       * @param file The FileObject.
92       * @throws Exception if an error occurs.
93       */
94      public void setFile(final FileObject file) throws Exception {
95          final FileType oldType = doGetType();
96  
97          if (file != null) {
98              WeakRefFileListener.installListener(file, this);
99          }
100         this.file = file;
101         maybeTypeChanged(oldType);
102     }
103 
104     /**
105      * Checks whether the file's type has changed, and fires the appropriate events.
106      *
107      * @param oldType The old FileType.
108      * @throws Exception if an error occurs.
109      */
110     private void maybeTypeChanged(final FileType oldType) throws Exception {
111         final FileType newType = doGetType();
112         if (oldType == FileType.IMAGINARY && newType != FileType.IMAGINARY) {
113             handleCreate(newType);
114         } else if (oldType != FileType.IMAGINARY && newType == FileType.IMAGINARY) {
115             handleDelete();
116         }
117     }
118 
119     /**
120      * Determines the type of the file, returns null if the file does not exist.
121      */
122     @Override
123     protected FileType doGetType() throws FileSystemException {
124         if (file != null) {
125             return file.getType();
126         }
127         if (children.isEmpty()) {
128             return FileType.IMAGINARY;
129         }
130         return FileType.FOLDER;
131     }
132 
133     /**
134      * Determines if this file can be read.
135      */
136     @Override
137     protected boolean doIsReadable() throws FileSystemException {
138         if (file != null) {
139             return file.isReadable();
140         }
141         return true;
142     }
143 
144     /**
145      * Determines if this file can be written to.
146      */
147     @Override
148     protected boolean doIsWriteable() throws FileSystemException {
149         if (file != null) {
150             return file.isWriteable();
151         }
152         return false;
153     }
154 
155     /**
156      * Determines if this file is executable.
157      */
158     @Override
159     protected boolean doIsExecutable() throws FileSystemException {
160         if (file != null) {
161             return file.isExecutable();
162         }
163         return false;
164     }
165 
166     /**
167      * Determines if this file is hidden.
168      */
169     @Override
170     protected boolean doIsHidden() throws FileSystemException {
171         if (file != null) {
172             return file.isHidden();
173         }
174         return false;
175     }
176 
177     /**
178      * Lists the children of the file.
179      */
180     @Override
181     protected String[] doListChildren() throws Exception {
182         if (file != null) {
183             final FileObject[] children;
184 
185             try {
186                 children = file.getChildren();
187             }
188             // VFS-210
189             catch (final FileNotFolderException e) {
190                 throw new FileNotFolderException(getName(), e);
191             }
192 
193             return Stream.of(children).map(child -> child.getName().getBaseName()).toArray(String[]::new);
194         }
195         return children.toArray(ArrayUtils.EMPTY_STRING_ARRAY);
196     }
197 
198     /**
199      * Creates this file as a folder.
200      */
201     @Override
202     protected void doCreateFolder() throws Exception {
203         ignoreEvent = true;
204         try {
205             file.createFolder();
206         } finally {
207             ignoreEvent = false;
208         }
209     }
210 
211     /**
212      * Deletes the file.
213      */
214     @Override
215     protected void doDelete() throws Exception {
216         ignoreEvent = true;
217         try {
218             file.delete();
219         } finally {
220             ignoreEvent = false;
221         }
222     }
223 
224     /**
225      * Returns the size of the file content (in bytes). Is only called if {@link #doGetType} returns
226      * {@link FileType#FILE}.
227      */
228     @Override
229     protected long doGetContentSize() throws Exception {
230         return file.getContent().getSize();
231     }
232 
233     /**
234      * Returns the attributes of this file.
235      */
236     @Override
237     protected Map<String, Object> doGetAttributes() throws Exception {
238         return file.getContent().getAttributes();
239     }
240 
241     /**
242      * Sets an attribute of this file.
243      */
244     @Override
245     protected void doSetAttribute(final String atttrName, final Object value) throws Exception {
246         file.getContent().setAttribute(atttrName, value);
247     }
248 
249     /**
250      * Returns the certificates of this file.
251      */
252     @Override
253     protected Certificate[] doGetCertificates() throws Exception {
254         return file.getContent().getCertificates();
255     }
256 
257     /**
258      * Returns the last-modified time of this file.
259      */
260     @Override
261     protected long doGetLastModifiedTime() throws Exception {
262         return file.getContent().getLastModifiedTime();
263     }
264 
265     /**
266      * Sets the last-modified time of this file.
267      *
268      * @since 2.0
269      */
270     @Override
271     protected boolean doSetLastModifiedTime(final long modtime) throws Exception {
272         file.getContent().setLastModifiedTime(modtime);
273         return true;
274     }
275 
276     /**
277      * Creates an input stream to read the file content from.
278      */
279     @Override
280     protected InputStream doGetInputStream(final int bufferSize) throws Exception {
281         return file.getContent().getInputStream(bufferSize);
282     }
283 
284     /**
285      * Creates an output stream to write the file content to.
286      */
287     @Override
288     protected OutputStream doGetOutputStream(final boolean bAppend) throws Exception {
289         return file.getContent().getOutputStream(bAppend);
290     }
291 
292     /**
293      * Called when a file is created.
294      *
295      * @param event The FileChangeEvent.
296      * @throws Exception if an error occurs.
297      */
298     @Override
299     public void fileCreated(final FileChangeEvent event) throws Exception {
300         if (event.getFileObject() != file) {
301             return;
302         }
303         if (!ignoreEvent) {
304             handleCreate(file.getType());
305         }
306     }
307 
308     /**
309      * Called when a file is deleted.
310      *
311      * @param event The FileChangeEvent.
312      * @throws Exception if an error occurs.
313      */
314     @Override
315     public void fileDeleted(final FileChangeEvent event) throws Exception {
316         if (event.getFileObject() != file) {
317             return;
318         }
319         if (!ignoreEvent) {
320             handleDelete();
321         }
322     }
323 
324     /**
325      * Called when a file is changed.
326      * <p>
327      * This will only happen if you monitor the file using {@link org.apache.commons.vfs2.FileMonitor}.
328      * </p>
329      *
330      * @param event The FileChangeEvent.
331      * @throws Exception if an error occurs.
332      */
333     @Override
334     public void fileChanged(final FileChangeEvent event) throws Exception {
335         if (event.getFileObject() != file) {
336             return;
337         }
338         if (!ignoreEvent) {
339             handleChanged();
340         }
341     }
342 
343     /**
344      * Close the delegated file.
345      *
346      * @throws FileSystemException if an error occurs.
347      */
348     @Override
349     public void close() throws FileSystemException {
350         super.close();
351 
352         if (file != null) {
353             file.close();
354         }
355     }
356 
357     /**
358      * Refresh file information.
359      *
360      * @throws FileSystemException if an error occurs.
361      * @since 2.0
362      */
363     @Override
364     public void refresh() throws FileSystemException {
365         super.refresh();
366         if (file != null) {
367             file.refresh();
368         }
369     }
370 
371     /**
372      * Return file content info.
373      *
374      * @return the file content info of the delegee.
375      * @throws Exception Any thrown Exception is wrapped in FileSystemException.
376      * @since 2.0
377      */
378     protected FileContentInfo doGetContentInfo() throws Exception {
379         return file.getContent().getContentInfo();
380     }
381 
382     /**
383      * Renames the file.
384      *
385      * @param newFile the new location/name.
386      * @throws Exception Any thrown Exception is wrapped in FileSystemException.
387      * @since 2.0
388      */
389     @Override
390     protected void doRename(final FileObject newFile) throws Exception {
391         file.moveTo(((DelegateFileObject) newFile).file);
392     }
393 
394     /**
395      * Removes an attribute of this file.
396      *
397      * @since 2.0
398      */
399     @Override
400     protected void doRemoveAttribute(final String atttrName) throws Exception {
401         file.getContent().removeAttribute(atttrName);
402     }
403 
404     /**
405      * Creates access to the file for random i/o.
406      *
407      * @since 2.0
408      */
409     @Override
410     protected RandomAccessContent doGetRandomAccessContent(final RandomAccessMode mode) throws Exception {
411         return file.getContent().getRandomAccessContent(mode);
412     }
413 }