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