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.local;
018
019import java.io.File;
020import java.io.FileInputStream;
021import java.io.IOException;
022import java.io.InputStream;
023import java.io.OutputStream;
024import java.nio.file.Files;
025
026import org.apache.commons.io.file.PathUtils;
027import org.apache.commons.vfs2.FileObject;
028import org.apache.commons.vfs2.FileSystemException;
029import org.apache.commons.vfs2.FileType;
030import org.apache.commons.vfs2.RandomAccessContent;
031import org.apache.commons.vfs2.provider.AbstractFileName;
032import org.apache.commons.vfs2.provider.AbstractFileObject;
033import org.apache.commons.vfs2.provider.UriParser;
034import org.apache.commons.vfs2.util.FileObjectUtils;
035import org.apache.commons.vfs2.util.RandomAccessMode;
036
037/**
038 * A file object implementation which uses direct file access.
039 */
040public class LocalFile extends AbstractFileObject<LocalFileSystem> {
041
042    private final String rootFile;
043
044    private File file;
045
046    /**
047     * Creates a non-root file.
048     *
049     * @param fileSystem the file system this file belongs to.
050     * @param rootFile the root file for the file system.
051     * @param name the file name on this file system.
052     */
053    protected LocalFile(final LocalFileSystem fileSystem, final String rootFile, final AbstractFileName name) {
054        super(name, fileSystem);
055        this.rootFile = rootFile;
056    }
057
058    /**
059     * Attaches this file object to its file resource.
060     */
061    @Override
062    protected void doAttach() throws Exception {
063        if (file == null) {
064            // Remove the "file:///"
065            // LocalFileName localFileName = (LocalFileName) getName();
066            final String fileName = rootFile + getName().getPathDecoded();
067            // fileName = UriParser.decode(fileName);
068            file = new File(fileName);
069        }
070    }
071
072    /**
073     * Creates this folder.
074     */
075    @Override
076    protected void doCreateFolder() throws Exception {
077        if (!file.mkdirs()) {
078            throw new FileSystemException("vfs.provider.local/create-folder.error", file);
079        }
080    }
081
082    /**
083     * Deletes this file, and all children.
084     */
085    @Override
086    protected void doDelete() throws Exception {
087        if (!file.delete()) {
088            throw new FileSystemException("vfs.provider.local/delete-file.error", file);
089        }
090    }
091
092    /**
093     * Returns the size of the file content (in bytes).
094     */
095    @Override
096    protected long doGetContentSize() throws Exception {
097        return file.length();
098    }
099
100    /**
101     * Creates an input stream to read the file contents.
102     */
103    @Override
104    protected InputStream doGetInputStream(final int bufferSize) throws Exception {
105        return new FileInputStream(file);
106    }
107
108    /**
109     * Gets the last modified time of this file.
110     */
111    @Override
112    protected long doGetLastModifiedTime() throws FileSystemException {
113        // Workaround OpenJDK 8 and 9 bug JDK-8177809
114        // https://bugs.openjdk.java.net/browse/JDK-8177809
115        try {
116            return Files.getLastModifiedTime(file.toPath()).toMillis();
117        } catch (final IOException e) {
118            throw new FileSystemException("vfs.provider/get-last-modified.error", file, e);
119        }
120    }
121
122    /**
123     * Creates an output stream to write the file content to.
124     */
125    @Override
126    protected OutputStream doGetOutputStream(final boolean append) throws IOException {
127        return PathUtils.newOutputStream(file.toPath(), append);
128    }
129
130    @Override
131    protected RandomAccessContent doGetRandomAccessContent(final RandomAccessMode mode) throws Exception {
132        return new LocalFileRandomAccessContent(file, mode);
133    }
134
135    /**
136     * Returns the file's type.
137     */
138    @Override
139    protected FileType doGetType() {
140        if (!file.exists()) {
141            return FileType.IMAGINARY;
142        }
143        if (file.isDirectory()) {
144            return FileType.FOLDER;
145        }
146        return FileType.FILE;
147    }
148
149    /**
150     * Determines if this file is hidden.
151     */
152    @Override
153    protected boolean doIsExecutable() {
154        return file.canExecute();
155    }
156
157    /**
158     * Determines if this file is hidden.
159     */
160    @Override
161    protected boolean doIsHidden() {
162        return file.isHidden();
163    }
164
165    /**
166     * Determines if this file can be read.
167     */
168    @Override
169    protected boolean doIsReadable() throws FileSystemException {
170        return file.canRead();
171    }
172
173    @Override
174    protected boolean doIsSameFile(final FileObject destFile) throws FileSystemException {
175        if (!FileObjectUtils.isInstanceOf(destFile, LocalFile.class)) {
176            return false;
177        }
178
179        final LocalFile destLocalFile = (LocalFile) FileObjectUtils.getAbstractFileObject(destFile);
180        if (!exists() || !destLocalFile.exists()) {
181            return false;
182        }
183
184        try {
185            return file.getCanonicalPath().equals(destLocalFile.file.getCanonicalPath());
186        } catch (final IOException e) {
187            throw new FileSystemException(e);
188        }
189    }
190
191    /**
192     * Determines if this file is a symbolic link.
193     *
194     * @since 2.4
195     */
196    @Override
197    protected boolean doIsSymbolicLink() throws FileSystemException {
198        return Files.isSymbolicLink(file.toPath());
199    }
200
201    /**
202     * Determines if this file can be written to.
203     */
204    @Override
205    protected boolean doIsWriteable() throws FileSystemException {
206        return file.canWrite();
207    }
208
209    /**
210     * Returns the children of the file.
211     */
212    @Override
213    protected String[] doListChildren() throws Exception {
214        return UriParser.encode(file.list());
215    }
216
217    /**
218     * rename this file
219     */
220    @Override
221    protected void doRename(final FileObject newFile) throws Exception {
222        final LocalFile newLocalFile = (LocalFile) FileObjectUtils.getAbstractFileObject(newFile);
223
224        if (!file.renameTo(newLocalFile.getLocalFile())) {
225            throw new FileSystemException("vfs.provider.local/rename-file.error", file.toString(), newFile.toString());
226        }
227    }
228
229    @Override
230    protected boolean doSetExecutable(final boolean executable, final boolean ownerOnly) throws Exception {
231        return file.setExecutable(executable, ownerOnly);
232    }
233
234    /**
235     * Sets the last modified time of this file.
236     *
237     * @since 2.0
238     */
239    @Override
240    protected boolean doSetLastModifiedTime(final long modtime) throws FileSystemException {
241        return file.setLastModified(modtime);
242    }
243
244    @Override
245    protected boolean doSetReadable(final boolean readable, final boolean ownerOnly) throws Exception {
246        return file.setReadable(readable, ownerOnly);
247    }
248
249    @Override
250    protected boolean doSetWritable(final boolean writable, final boolean ownerOnly) throws Exception {
251        return file.setWritable(writable, ownerOnly);
252    }
253
254    /**
255     * Gets the local file that this file object represents.
256     *
257     * @return the local file that this file object represents.
258     */
259    protected File getLocalFile() {
260        return file;
261    }
262
263    /**
264     * Returns the URI of the file.
265     *
266     * @return The URI of the file.
267     */
268    @Override
269    public String toString() {
270        try {
271            // VFS-325: URI may contain percent-encoded values as part of file name, so decode
272            // those characters before returning
273            return UriParser.decode(getName().getURI());
274        } catch (final FileSystemException e) {
275            return getName().getURI();
276        }
277    }
278}