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