org.openide.filesystems.LocalFileSystem Maven / Gradle / Ivy
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.openide.filesystems;
import java.beans.PropertyVetoException;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FilterOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputValidation;
import java.io.OutputStream;
import java.io.SyncFailedException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Random;
import java.util.logging.Level;
import org.openide.util.NbBundle;
import org.openide.util.BaseUtilities;
/** Local filesystem. Provides access to files on local disk.
* For historical reasons many AbstractFileSystem.* methods are implemented
* as protected in this class. Do not call them! Subclasses might override
* them, or (better) use delegation.
*/
public class LocalFileSystem extends AbstractFileSystem {
/** generated Serialized Version UID */
private static final long serialVersionUID = -5355566113542272442L;
/** Controlls the LocalFileSystem's automatic refresh.
* If the refresh time interval is set from the System.property, than this value is used.
* Otherwise, the refresh time interval is set to 0 which means the refresh
* is disabled. */
private static final int REFRESH_TIME = Integer.getInteger(
"org.openide.filesystems.LocalFileSystem.REFRESH_TIME", 0
).intValue(); // NOI18N
private static final int SUCCESS = 0;
private static final int FAILURE = 1;
private static final int NOT_EXISTS = 3;
/** root file */
private File rootFile = new File("."); // NOI18N
/** is read only */
private boolean readOnly;
/** Constructor.
*/
public LocalFileSystem() {
Impl impl = new Impl(this);
info = impl;
change = impl;
DefaultAttributes a = new InnerAttrs(this, info, change, impl);
attr = a;
list = a;
setRefreshTime(REFRESH_TIME);
}
/* Human presentable name */
public String getDisplayName() {
return rootFile.getAbsolutePath();
}
@SuppressWarnings("deprecation") // need to set it for compat
private void _setSystemName(String s) throws PropertyVetoException {
setSystemName(s);
}
/** Set the root directory of the filesystem.
* @param r file to set root to
* @exception PropertyVetoException if the value if vetoed by someone else (usually
* by the {@link org.openide.filesystems.Repository Repository})
* @exception IOException if the root does not exists or some other error occured
*/
public synchronized void setRootDirectory(File r) throws PropertyVetoException, IOException {
if (!r.exists() || r.isFile()) {
throw new FSException(NbBundle.getMessage(LocalFileSystem.class, "EXC_RootNotExist", r.getAbsolutePath()));
}
String oldDisplayName = getDisplayName();
_setSystemName(computeSystemName(r));
rootFile = r;
firePropertyChange(PROP_ROOT, null, refreshRoot());
firePropertyChange(PROP_DISPLAY_NAME, oldDisplayName, getDisplayName());
}
/** Get the root directory of the filesystem.
* @return root directory
*/
public File getRootDirectory() {
return rootFile;
}
/** Set whether the filesystem should be read only.
* @param flag true
if it should
*/
public void setReadOnly(boolean flag) {
if (flag != readOnly) {
readOnly = flag;
firePropertyChange(
PROP_READ_ONLY, (!flag) ? Boolean.TRUE : Boolean.FALSE, flag ? Boolean.TRUE : Boolean.FALSE
);
}
}
/* Test whether filesystem is read only.
* @return if filesystem is read only
*/
public boolean isReadOnly() {
return readOnly;
}
/** Compute the system name of this filesystem for a given root directory.
*
* The default implementation simply returns the filename separated by slashes.
* @see FileSystem#setSystemName
* @param rootFile root directory for the filesystem
* @return system name for the filesystem
*/
protected String computeSystemName(File rootFile) {
String retVal = rootFile.getAbsolutePath().replace(File.separatorChar, '/');
return ((BaseUtilities.isWindows() || (BaseUtilities.getOperatingSystem() == BaseUtilities.OS_OS2))) ? retVal.toLowerCase()
: retVal;
}
//
// List
//
protected String[] children(String name) {
File f = getFile(name);
if (f.isDirectory()) {
return f.list();
} else {
return null;
}
}
//
// Change
//
protected void createFolder(String name) throws java.io.IOException {
File f = getFile(name);
if (name.equals("")) { // NOI18N
throw new FSException(NbBundle.getMessage(LocalFileSystem.class, "EXC_CannotCreateF", f.getName(), getDisplayName(), f.getAbsolutePath()));
}
if (f.exists()) {
throw new FSException(NbBundle.getMessage(LocalFileSystem.class, "EXC_FolderAlreadyExist", f.getName(), getDisplayName(), f.getAbsolutePath()));
}
boolean b = createRecursiveFolder(f);
if (!b) {
throw new FSException(NbBundle.getMessage(LocalFileSystem.class, "EXC_CannotCreateF", f.getName(), getDisplayName(), f.getAbsolutePath()));
}
}
/*
* @return true if RefreshAction should be enabled
*/
@Override
boolean isEnabledRefreshFolder() {
return true;
}
/** Creates new folder and all necessary subfolders
* @param f folder to create
* @return true
if the file exists when returning from this method
*/
private static boolean createRecursiveFolder(File f) {
if (f.exists()) {
return true;
}
if (!f.isAbsolute()) {
f = f.getAbsoluteFile();
}
String par = f.getParent();
if (par == null) {
return false;
}
if (!createRecursiveFolder(new File(par))) {
return false;
}
f.mkdir();
return f.exists();
}
protected void createData(String name) throws IOException {
File f = getFile(name);
boolean isError = true;
IOException creationException = null;
String annotationMsg = null;
try {
isError = f.createNewFile() ? false : true;
isError = isError ? true : (!f.exists());
if (isError) {
annotationMsg = NbBundle.getMessage(LocalFileSystem.class, "EXC_DataAlreadyExist", f.getName(), getDisplayName(), f.getAbsolutePath());
creationException = new SyncFailedException(annotationMsg);
}
} catch (IOException iex) {
isError = true;
creationException = iex;
annotationMsg = iex.getLocalizedMessage();
}
if (isError) {
LOG.log(Level.INFO, "Trying to create new file {0}.", f.getPath());
ExternalUtil.annotate(creationException, annotationMsg);
throw creationException;
}
}
protected void rename(String oldName, String newName)
throws IOException {
File of = getFile(oldName);
File nf = getFile(newName);
Random rndm = null;
for (int retry = 0;; retry++) {
// #7086 - (nf.exists() && !nf.equals(of)) instead of nf.exists() - fix for Win32
boolean existsNF = nf.exists();
boolean equalsOF = nf.equals(of);
if (BaseUtilities.isMac()) {
// File.equal on mac is not case insensitive (which it should be),
// so try harder
equalsOF = of.getCanonicalFile().equals(nf.getCanonicalFile());
}
Boolean rename = null;
if ((existsNF && !equalsOF) || !(rename = of.renameTo(nf))) {
final String msg = NbBundle.getMessage(LocalFileSystem.class, "EXC_CannotRename", oldName, getDisplayName(), newName, existsNF, rename);
if (retry > 10) {
throw new FSException(msg);
}
LOG.log(Level.WARNING, "Rename #{0} failed: {1}", new Object[]{retry, msg});
if (rndm == null) {
rndm = new Random();
}
int sleep = rndm.nextInt(100) + 1;
LOG.log(Level.INFO, "Sleeping for {0} ms", sleep);
try {
Thread.sleep(sleep);
} catch (InterruptedException ex) {
LOG.log(Level.FINE, null, ex);
}
continue;
}
return;
}
}
protected void delete(String name) throws IOException {
File file = getFile(name);
if (deleteFile(file) != SUCCESS) {
if (file.exists()) {
throw new FSException(NbBundle.getMessage(LocalFileSystem.class, "EXC_CannotDelete", name, getDisplayName(), file.getAbsolutePath()));
} else {
/** When file externaly deleted and fo.delete () is called before
periodical refresh */
FileObject thisFo = findResource(name);
if (thisFo != null) {
if (thisFo.getParent() != null) {
thisFo.getParent().refresh();
}
thisFo.refresh();
if (thisFo.isValid()) {
throw new FSException(NbBundle.getMessage(LocalFileSystem.class, "EXC_CannotDelete", name, getDisplayName(), file.getAbsolutePath()));
}
}
}
}
}
/** Method that recursivelly deletes all files in a folder.
* @return true if successful
*/
private static int deleteFile(File file) {
boolean ret = file.delete();
if (ret) {
return SUCCESS;
}
if (!file.exists()) {
return NOT_EXISTS;
}
if (file.isDirectory()) {
// first of all delete whole content
File[] arr = file.listFiles();
for (int i = 0; i < arr.length; i++) {
if (deleteFile(arr[i]) != SUCCESS) {
return FAILURE;
}
}
}
// delete the file itself
return (file.delete() ? SUCCESS : FAILURE);
}
//
// Info
//
protected java.util.Date lastModified(String name) {
return new java.util.Date(getFile(name).lastModified());
}
protected boolean folder(String name) {
return getFile(name).isDirectory();
}
protected boolean readOnly(String name) {
File f = getFile(name);
return !f.canWrite() && f.exists();
}
protected String mimeType(String name) {
return null;
}
protected long size(String name) {
return getFile(name).length();
}
// ===============================================================================
// This part of code could be used for monitoring of closing file streams.
/* public static java.util.HashMap openedIS = new java.util.HashMap();
public static java.util.HashMap openedOS = new java.util.HashMap();
static class DebugIS extends FileInputStream {
public DebugIS(File f) throws java.io.FileNotFoundException { super(f); }
public void close() throws IOException { openedIS.remove(this); super.close(); }
};
static class DebugOS extends FileOutputStream {
public DebugOS(File f) throws java.io.IOException { super(f); }
public void close() throws IOException { openedOS.remove(this); super.close(); }
};
public InputStream inputStream (String name) throws java.io.FileNotFoundException {
DebugIS is = new DebugIS(getFile(name));
openedIS.put(is, new Exception());
return is;
}
public OutputStream outputStream (String name) throws java.io.IOException {
DebugOS os = new DebugOS(getFile(name));
openedOS.put(os, new Exception());
return os;
}*/
// End of the debug part
// ============================================================================
// Begin of the original part
protected InputStream inputStream(String name) throws java.io.FileNotFoundException {
InputStream fis;
File file = null;
try {
fis = new BufferedInputStream(new FileInputStream(file = getFile(name)));
} catch (FileNotFoundException exc) {
if ((file == null) || !file.exists()) {
ExternalUtil.annotate(exc, NbBundle.getMessage(LocalFileSystem.class, "EXC_FileOutsideModified", getFile(name)));
}
throw exc;
}
return fis;
}
protected OutputStream outputStream(final String name) throws java.io.IOException {
File f = getFile(name);
if (!f.exists()) {
f.getParentFile().mkdirs();
}
OutputStream retVal = new BufferedOutputStream(new FileOutputStream(f));
// workaround for #42624
if (BaseUtilities.isMac()) {
retVal = getOutputStreamForMac42624(retVal, name);
}
return retVal;
}
private OutputStream getOutputStreamForMac42624(final OutputStream originalStream, final String name) {
final File f = getFile(name);
final long lModified = f.lastModified();
OutputStream retVal = new FilterOutputStream(originalStream) {
@Override
public void close() throws IOException {
super.close();
if ((f.length() == 0) && (f.lastModified() == lModified)) {
f.setLastModified(System.currentTimeMillis());
}
}
};
return retVal;
}
// End of the original part
// ============================================================================
protected void lock(String name) throws IOException {
File file = getFile(name);
if ((!file.canWrite() && file.exists()) || isReadOnly()) {
throw new FSException(NbBundle.getMessage(LocalFileSystem.class, "EXC_CannotLock", null, null, file.getAbsolutePath()));
}
}
protected void unlock(String name) {
}
protected void markUnimportant(String name) {
}
/**
* Creates path for given string name.
*
* @param name File name.
* @return The path.
*/
private Path getPath(String name) {
return Paths.get(rootFile.getPath(), name);
}
/** Creates file for given string name.
* @param name the name
* @return the file
*/
private File getFile(String name) {
// XXX should this be name.replace('/', File.separatorChar)? Cf. BT #4745638.
return new File(rootFile, name);
}
/**
* @param in the input stream to read from
* @exception IOException error during read
* @exception ClassNotFoundException when class not found
*/
private void readObject(java.io.ObjectInputStream in)
throws java.io.IOException, java.lang.ClassNotFoundException {
in.defaultReadObject();
in.registerValidation(
new ObjectInputValidation() {
public void validateObject() {
if (attr.getClass() == DefaultAttributes.class) {
Impl impl = new Impl(LocalFileSystem.this);
attr = new InnerAttrs(LocalFileSystem.this, impl, impl, impl);
}
}
}, 0
);
}
/** The implementation class that implements List, Info
* and Change interfaces and delegates all the methods
* to appropriate methods of LocalFileSystem.
*/
public static class Impl extends Object implements AbstractFileSystem.List,
AbstractFileSystem.Info, AbstractFileSystem.SymlinkInfo,
AbstractFileSystem.Change {
/** generated Serialized Version UID */
static final long serialVersionUID = -8432015909317698511L;
/** pointer to local filesystem */
private LocalFileSystem fs;
/** Pointer to local filesystem
* @param fs the filesystem this impl is connected to
*/
public Impl(LocalFileSystem fs) {
this.fs = fs;
}
/*
*
* Scans children for given name
*/
public String[] children(String name) {
return fs.children(name);
}
//
// Change
//
/*
* Creates new folder named name.
* @param name name of folder
* @throws IOException if operation fails
*/
public void createFolder(String name) throws java.io.IOException {
fs.createFolder(name);
}
/*
* Create new data file.
*
* @param name name of the file
*
* @return the new data file object
* @exception IOException if the file cannot be created (e.g. already exists)
*/
public void createData(String name) throws IOException {
fs.createData(name);
}
/*
* Renames a file.
*
* @param oldName old name of the file
* @param newName new name of the file
*/
public void rename(String oldName, String newName)
throws IOException {
fs.rename(oldName, newName);
}
/*
* Delete the file.
*
* @param name name of file
* @exception IOException if the file could not be deleted
*/
public void delete(String name) throws IOException {
fs.delete(name);
}
//
// Info
//
/*
*
* Get last modification time.
* @param name the file to test
* @return the date
*/
public java.util.Date lastModified(String name) {
return fs.lastModified(name);
}
/*
* Test if the file is folder or contains data.
* @param name name of the file
* @return true if the file is folder, false otherwise
*/
public boolean folder(String name) {
return fs.folder(name);
}
/*
* Test whether this file can be written to or not.
* @param name the file to test
* @return true
if file is read-only
*/
public boolean readOnly(String name) {
return fs.readOnly(name);
}
/*
* Get the MIME type of the file.
* Uses {@link FileUtil#getMIMEType}.
*
* @param name the file to test
* @return the MIME type textual representation, e.g. "text/plain"
*/
public String mimeType(String name) {
return fs.mimeType(name);
}
/*
* Get the size of the file.
*
* @param name the file to test
* @return the size of the file in bytes or zero if the file does not contain data (does not
* exist or is a folder).
*/
public long size(String name) {
return fs.size(name);
}
/*
* Get input stream.
*
* @param name the file to test
* @return an input stream to read the contents of this file
* @exception FileNotFoundException if the file does not exists or is invalid
*/
public InputStream inputStream(String name) throws java.io.FileNotFoundException {
return fs.inputStream(name);
}
/*
* Get output stream.
*
* @param name the file to test
* @return output stream to overwrite the contents of this file
* @exception IOException if an error occures (the file is invalid, etc.)
*/
public OutputStream outputStream(String name) throws java.io.IOException {
return fs.outputStream(name);
}
/*
* Does nothing to lock the file.
*
* @param name name of the file
*/
public void lock(String name) throws IOException {
fs.lock(name);
}
/*
* Does nothing to unlock the file.
*
* @param name name of the file
*/
public void unlock(String name) {
fs.unlock(name);
}
/*
* Does nothing to mark the file as unimportant.
*
* @param name the file to mark
*/
public void markUnimportant(String name) {
fs.markUnimportant(name);
}
@Override
public boolean isSymbolicLink(String name) throws IOException {
return Files.isSymbolicLink(fs.getPath(name));
}
@Override
public String readSymbolicLink(String name) throws IOException {
return Files.readSymbolicLink(fs.getPath(name)).toString();
}
@Override
public String getCanonicalName(String name) throws IOException {
return fs.getPath(name).toRealPath().toString();
}
}
/** This class adds new virtual attribute "java.io.File".
* Because of the fact that FileObjects of LocalFileSystem are convertable
* to java.io.File by means of attributes. */
private static class InnerAttrs extends DefaultAttributes {
static final long serialVersionUID = 1257351369229921993L;
LocalFileSystem lfs;
public InnerAttrs(
LocalFileSystem lfs, AbstractFileSystem.Info info, AbstractFileSystem.Change change,
AbstractFileSystem.List list
) {
super(info, change, list);
this.lfs = lfs;
}
@Override
public Object readAttribute(String name, String attrName) {
if (attrName.equals("java.io.File")) { // NOI18N
return FileUtil.normalizeFile(lfs.getFile(name));
}
return super.readAttribute(name, attrName);
}
}
}