001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.commons.vfs2.provider;
018
019import java.io.InputStream;
020import java.io.OutputStream;
021import java.security.cert.Certificate;
022import java.util.HashSet;
023import java.util.Map;
024import java.util.Set;
025
026import org.apache.commons.vfs2.FileChangeEvent;
027import org.apache.commons.vfs2.FileContentInfo;
028import org.apache.commons.vfs2.FileListener;
029import org.apache.commons.vfs2.FileName;
030import org.apache.commons.vfs2.FileNotFolderException;
031import org.apache.commons.vfs2.FileObject;
032import org.apache.commons.vfs2.FileSystemException;
033import org.apache.commons.vfs2.FileType;
034import org.apache.commons.vfs2.RandomAccessContent;
035import org.apache.commons.vfs2.util.RandomAccessMode;
036import org.apache.commons.vfs2.util.WeakRefFileListener;
037
038/**
039 * A file backed by another file.
040 * <p>
041 * TODO - Extract subclass that overlays the children.
042 *
043 * @param <AFS> A subclass of AbstractFileSystem.
044 */
045public class DelegateFileObject<AFS extends AbstractFileSystem> extends AbstractFileObject<AFS>
046        implements FileListener {
047    private FileObject file;
048    private final Set<String> children = new HashSet<>();
049    private boolean ignoreEvent;
050
051    public DelegateFileObject(final AbstractFileName name, final AFS fileSystem, final FileObject file)
052            throws FileSystemException {
053        super(name, fileSystem);
054        this.file = file;
055        if (file != null) {
056            WeakRefFileListener.installListener(file, this);
057        }
058    }
059
060    /**
061     * Get access to the delegated file.
062     *
063     * @return The FileObject.
064     * @since 2.0
065     */
066    public FileObject getDelegateFile() {
067        return file;
068    }
069
070    /**
071     * Adds a child to this file.
072     *
073     * @param baseName The base FileName.
074     * @param type The FileType.
075     * @throws Exception if an error occurs.
076     */
077    public void attachChild(final FileName baseName, final FileType type) throws Exception {
078        final FileType oldType = doGetType();
079        if (children.add(baseName.getBaseName())) {
080            childrenChanged(baseName, type);
081        }
082        maybeTypeChanged(oldType);
083    }
084
085    /**
086     * Attaches or detaches the target file.
087     *
088     * @param file The FileObject.
089     * @throws Exception if an error occurs.
090     */
091    public void setFile(final FileObject file) throws Exception {
092        final FileType oldType = doGetType();
093
094        if (file != null) {
095            WeakRefFileListener.installListener(file, this);
096        }
097        this.file = file;
098        maybeTypeChanged(oldType);
099    }
100
101    /**
102     * Checks whether the file's type has changed, and fires the appropriate events.
103     *
104     * @param oldType The old FileType.
105     * @throws Exception if an error occurs.
106     */
107    private void maybeTypeChanged(final FileType oldType) throws Exception {
108        final FileType newType = doGetType();
109        if (oldType == FileType.IMAGINARY && newType != FileType.IMAGINARY) {
110            handleCreate(newType);
111        } else if (oldType != FileType.IMAGINARY && newType == FileType.IMAGINARY) {
112            handleDelete();
113        }
114    }
115
116    /**
117     * Determines the type of the file, returns null if the file does not exist.
118     */
119    @Override
120    protected FileType doGetType() throws FileSystemException {
121        if (file != null) {
122            return file.getType();
123        } else if (children.size() > 0) {
124            return FileType.FOLDER;
125        } else {
126            return FileType.IMAGINARY;
127        }
128    }
129
130    /**
131     * Determines if this file can be read.
132     */
133    @Override
134    protected boolean doIsReadable() throws FileSystemException {
135        if (file != null) {
136            return file.isReadable();
137        }
138        return true;
139    }
140
141    /**
142     * Determines if this file can be written to.
143     */
144    @Override
145    protected boolean doIsWriteable() throws FileSystemException {
146        if (file != null) {
147            return file.isWriteable();
148        }
149        return false;
150    }
151
152    /**
153     * Determines if this file is executable.
154     */
155    @Override
156    protected boolean doIsExecutable() throws FileSystemException {
157        if (file != null) {
158            return file.isExecutable();
159        }
160        return false;
161    }
162
163    /**
164     * Determines if this file is hidden.
165     */
166    @Override
167    protected boolean doIsHidden() throws FileSystemException {
168        if (file != null) {
169            return file.isHidden();
170        }
171        return false;
172    }
173
174    /**
175     * Lists the children of the file.
176     */
177    @Override
178    protected String[] doListChildren() throws Exception {
179        if (file != null) {
180            final FileObject[] children;
181
182            try {
183                children = file.getChildren();
184            }
185            // VFS-210
186            catch (final FileNotFolderException e) {
187                throw new FileNotFolderException(getName(), e);
188            }
189
190            final String[] childNames = new String[children.length];
191            for (int i = 0; i < children.length; i++) {
192                childNames[i] = children[i].getName().getBaseName();
193            }
194            return childNames;
195        }
196        return children.toArray(new String[children.size()]);
197    }
198
199    /**
200     * Creates this file as a folder.
201     */
202    @Override
203    protected void doCreateFolder() throws Exception {
204        ignoreEvent = true;
205        try {
206            file.createFolder();
207        } finally {
208            ignoreEvent = false;
209        }
210    }
211
212    /**
213     * Deletes the file.
214     */
215    @Override
216    protected void doDelete() throws Exception {
217        ignoreEvent = true;
218        try {
219            file.delete();
220        } finally {
221            ignoreEvent = false;
222        }
223    }
224
225    /**
226     * Returns the size of the file content (in bytes). Is only called if {@link #doGetType} returns
227     * {@link FileType#FILE}.
228     */
229    @Override
230    protected long doGetContentSize() throws Exception {
231        return file.getContent().getSize();
232    }
233
234    /**
235     * Returns the attributes of this file.
236     */
237    @Override
238    protected Map<String, Object> doGetAttributes() throws Exception {
239        return file.getContent().getAttributes();
240    }
241
242    /**
243     * Sets an attribute of this file.
244     */
245    @Override
246    protected void doSetAttribute(final String atttrName, final Object value) throws Exception {
247        file.getContent().setAttribute(atttrName, value);
248    }
249
250    /**
251     * Returns the certificates of this file.
252     */
253    @Override
254    protected Certificate[] doGetCertificates() throws Exception {
255        return file.getContent().getCertificates();
256    }
257
258    /**
259     * Returns the last-modified time of this file.
260     */
261    @Override
262    protected long doGetLastModifiedTime() throws Exception {
263        return file.getContent().getLastModifiedTime();
264    }
265
266    /**
267     * Sets the last-modified time of this file.
268     *
269     * @since 2.0
270     */
271    @Override
272    protected boolean doSetLastModifiedTime(final long modtime) throws Exception {
273        file.getContent().setLastModifiedTime(modtime);
274        return true;
275    }
276
277    /**
278     * Creates an input stream to read the file content from.
279     */
280    @Override
281    protected InputStream doGetInputStream() throws Exception {
282        return file.getContent().getInputStream();
283    }
284
285    /**
286     * Creates an output stream to write the file content to.
287     */
288    @Override
289    protected OutputStream doGetOutputStream(final boolean bAppend) throws Exception {
290        return file.getContent().getOutputStream(bAppend);
291    }
292
293    /**
294     * Called when a file is created.
295     *
296     * @param event The FileChangeEvent.
297     * @throws Exception if an error occurs.
298     */
299    @Override
300    public void fileCreated(final FileChangeEvent event) throws Exception {
301        if (event.getFile() != file) {
302            return;
303        }
304        if (!ignoreEvent) {
305            handleCreate(file.getType());
306        }
307    }
308
309    /**
310     * Called when a file is deleted.
311     *
312     * @param event The FileChangeEvent.
313     * @throws Exception if an error occurs.
314     */
315    @Override
316    public void fileDeleted(final FileChangeEvent event) throws Exception {
317        if (event.getFile() != file) {
318            return;
319        }
320        if (!ignoreEvent) {
321            handleDelete();
322        }
323    }
324
325    /**
326     * Called when a file is changed.
327     * <p>
328     * This will only happen if you monitor the file using {@link org.apache.commons.vfs2.FileMonitor}.
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.getFile() != 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}