/*
* VFSManager.java - Main class of virtual filesystem
* :tabSize=4:indentSize=4:noTabs=false:
* :folding=explicit:collapseFolds=1:
*
* Copyright (C) 2000, 2005 Slava Pestov
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
package org.gjt.sp.jedit.io;
//{{{ Imports
import javax.swing.JOptionPane;
import java.awt.Component;
import java.awt.EventQueue;
import java.awt.Frame;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.util.*;
import org.gjt.sp.jedit.bufferio.IoTask;
import org.gjt.sp.jedit.gui.ErrorListDialog;
import org.gjt.sp.jedit.msg.VFSUpdate;
import org.gjt.sp.jedit.*;
import org.gjt.sp.util.Log;
import org.gjt.sp.util.Task;
import org.gjt.sp.util.TaskManager;
import org.gjt.sp.util.ThreadUtilities;
import org.gjt.sp.util.AwtRunnableQueue;
import org.gjt.sp.util.StandardUtilities;
//}}}
/**
* jEdit's virtual filesystem allows it to transparently edit files
* stored elsewhere than the local filesystem, for example on an FTP
* site. See the {@link VFS} class for implementation details.<p>
*
* Note that most of the jEdit API is not thread-safe, so special care
* must be taken when making jEdit API calls. Also, it is not safe to
* call <code>SwingUtilities.invokeAndWait()</code> from a work request;
* it can cause a deadlock if the given runnable then later calls
* {@link #waitForRequests()}.
*
* @author Slava Pestov
* @version $Id: VFSManager.java 23861 2015-02-12 04:54:35Z vanza $
*/
public class VFSManager
{
/**
* The service type. See {@link org.gjt.sp.jedit.ServiceManager}.
* @since jEdit 4.2pre1
*/
public static final String SERVICE = "org.gjt.sp.jedit.io.VFS";
//{{{ init() method
/**
* Do not call.
*/
public static void init()
{
} //}}}
//{{{ start() method
/**
* Do not call.
*/
public static void start()
{
AwtRunnableQueue.INSTANCE.start();
} //}}}
//{{{ VFS methods
//{{{ getFileVFS() method
/**
* Returns the local filesystem VFS.
* @since jEdit 2.5pre1
*/
public static VFS getFileVFS()
{
return fileVFS;
} //}}}
//{{{ getUrlVFS() method
/**
* Returns the URL VFS.
* @since jEdit 2.5pre1
*/
public static VFS getUrlVFS()
{
return urlVFS;
} //}}}
//{{{ getVFSForProtocol() method
/**
* Returns the VFS for the specified protocol.
* @param protocol The protocol
* @since jEdit 2.5pre1
*/
public static VFS getVFSForProtocol(String protocol)
{
if(protocol.equals("file"))
return fileVFS;
else
{
VFS vfs = (VFS)ServiceManager.getService(SERVICE,protocol);
if(vfs != null)
return vfs;
else
return urlVFS;
}
} //}}}
//{{{ getVFSForPath() method
/**
* Returns the VFS for the specified path.
* @param path The path
* @since jEdit 2.6pre4
*/
public static VFS getVFSForPath(String path)
{
if(MiscUtilities.isURL(path))
return getVFSForProtocol(MiscUtilities.getProtocolOfURL(path));
else
return fileVFS;
} //}}}
//{{{ getVFSs() method
/**
* Returns a list of all registered filesystems.
* @since jEdit 4.2pre1
*/
public static String[] getVFSs()
{
// the sooner ppl move to the new api, the less we'll need
// crap like this
List<String> returnValue = new LinkedList<String>();
String[] newAPI = ServiceManager.getServiceNames(SERVICE);
if(newAPI != null)
Collections.addAll(returnValue, newAPI);
return returnValue.toArray(new String[returnValue.size()]);
} //}}}
//}}}
//{{{ I/O request methods
//{{{ waitForRequests() method
/**
* Returns when all pending requests are complete.
* Must be called in the Event Dispatch Thread
* @since jEdit 2.5pre1
*/
public static void waitForRequests()
{
if(!EventQueue.isDispatchThread())
throw new IllegalStateException();
TaskManager.instance.waitForIoTasks();
} //}}}
//{{{ errorOccurred() method
/**
* Returns if the last request caused an error.
*/
public static boolean errorOccurred()
{
return error;
} //}}}
//{{{ getRequestCount() method
/**
* Returns the number of pending I/O requests.
*/
public static int getRequestCount()
{
return TaskManager.instance.countIoTasks();
} //}}}
//{{{ runInAWTThread() method
/**
* Executes the specified runnable in the AWT thread once all
* pending I/O requests are complete. Only in one case the
* <code>Runnable</code> is executed directly: when the current thread
* is EDT and there are no I/O requests active or queued
* at the moment of call.
* @since jEdit 2.5pre1
* @deprecated Using that method, when you run a task in AWT Thread,
* it will wait for all background task causing some unwanted delays.
* If you need calling a task after a background work, please add your
* runnable to the EDT thread yourself at the end of the background task
* @see org.gjt.sp.util.ThreadUtilities#runInDispatchThread(Runnable)
* @see org.gjt.sp.util.ThreadUtilities#runInDispatchThreadAndWait(Runnable)
*/
@Deprecated
public static void runInAWTThread(Runnable run)
{
AwtRunnableQueue.INSTANCE.runAfterIoTasks(run);
} //}}}
//{{{ runInWorkThread() method
/**
* Executes the specified runnable in one of the I/O threads.
* @since jEdit 2.6pre2
* @deprecated You should not use this method, this threadpool
* links the AWT Threads and Work threads.
* @see org.gjt.sp.util.ThreadUtilities#runInBackground(org.gjt.sp.util.Task)
* @see org.gjt.sp.util.ThreadUtilities#runInBackground(Runnable)
*/
@Deprecated
public static void runInWorkThread(Task run)
{
if(!(run instanceof IoTask))
throw new IllegalArgumentException();
ThreadUtilities.runInBackground(run);
} //}}}
//}}}
//{{{ error() method
/**
* Handle an I/O error.
* @since jEdit 4.3pre3
*/
public static void error(IOException e, String path, Component comp)
{
Log.log(Log.ERROR,VFSManager.class,e);
VFSManager.error(comp,path,"ioerror",new String[] { e.toString() });
} //}}}
//{{{ error() method
/**
* Reports an I/O error with default urgency, <code>Log.ERROR</code>
*
* @param comp The component
* @param path The path name that caused the error
* @param messageProp The error message property name
* @param args Positional parameters
* @since jEdit 4.0pre3
*/
public static void error(Component comp,
final String path,
String messageProp,
Object[] args)
{
error(comp,path,messageProp,args,Log.ERROR);
}
/**
* Reports an I/O error.
*
* @param comp The component
* @param path The path name that caused the error
* @param messageProp The error message property name
* @param args Positional parameters
* @param urgency Logging urgency (level)
* @since jEdit 5.0pre1
*/
public static void error(final Component comp,
final String path,
final String messageProp,
final Object[] args,
final int urgency)
{
Runnable r = new Runnable()
{
@Override
public void run()
{
final Frame frame =
JOptionPane.getFrameForComponent(comp);
synchronized(errorLock)
{
error = true;
errors.add(new ErrorListDialog.ErrorEntry(
path,messageProp,args,urgency));
if(errors.size() == 1)
{
if (!errorDisplayerActive)
{
ThreadUtilities.runInBackground(
new ErrorDisplayer(frame));
}
}
}
}
};
ThreadUtilities.runInDispatchThreadAndWait(r);
} //}}}
//{{{ sendVFSUpdate() method
/**
* Sends a VFS update message.
* @param vfs The VFS
* @param path The path that changed
* @param parent True if an update should be sent for the path's
* parent too
* @since jEdit 2.6pre4
*/
public static void sendVFSUpdate(VFS vfs, String path, boolean parent)
{
if(parent)
{
sendVFSUpdate(vfs,vfs.getParentOfPath(path),false);
sendVFSUpdate(vfs,path,false);
}
else
{
// have to do this hack until VFSPath class is written
if(path.length() != 1 && (path.endsWith("/")
|| path.endsWith(java.io.File.separator)))
path = path.substring(0,path.length() - 1);
synchronized(vfsUpdateLock)
{
for (VFSUpdate msg : vfsUpdates)
{
if (msg.getPath().equals(path))
{
// don't send two updates
// for the same path
return;
}
}
vfsUpdates.add(new VFSUpdate(path));
if(vfsUpdates.size() == 1)
{
// we were the first to add an update;
// add update sending runnable to AWT
// thread
AwtRunnableQueue.INSTANCE.runAfterIoTasks(new SendVFSUpdatesSafely());
}
}
}
} //}}}
//{{{ SendVFSUpdatesSafely class
static class SendVFSUpdatesSafely implements Runnable
{
public void run()
{
synchronized(vfsUpdateLock)
{
// the vfs browser has what you might call
// a design flaw, it doesn't update properly
// unless the vfs update for a parent arrives
// before any updates for the children. sorting
// the list alphanumerically guarantees this.
Collections.sort(vfsUpdates,
new StandardUtilities.StringCompare<VFSUpdate>()
);
for (VFSUpdate vfsUpdate : vfsUpdates)
EditBus.send(vfsUpdate);
vfsUpdates.clear();
}
}
} //}}}
//{{{ Private members
//{{{ Static variables
private static VFS fileVFS;
private static VFS urlVFS;
private static boolean error;
private static final Object errorLock = new Object();
private static final Vector<ErrorListDialog.ErrorEntry> errors;
private static final Object vfsUpdateLock = new Object();
private static final List<VFSUpdate> vfsUpdates;
// An indicator of whether ErrorDisplayer is active
// Should be accessed with synchronized(errorLock)
private static boolean errorDisplayerActive = false;
//}}}
//{{{ Class initializer
static
{
errors = new Vector<ErrorListDialog.ErrorEntry>();
fileVFS = new FileVFS();
urlVFS = new UrlVFS();
vfsUpdates = new ArrayList<VFSUpdate>(10);
} //}}}
//{{{ ErrorDisplayer class
private static class ErrorDisplayer implements Runnable
{
private Frame frame;
public ErrorDisplayer(Frame frame)
{
this.frame = frame;
}
private void showDialog(final Frame frame,
final Vector<ErrorListDialog.ErrorEntry> errors)
{
try
{
EventQueue.invokeAndWait(new Runnable() {
public void run()
{
String caption = jEdit.getProperty(
"ioerror.caption" + (errors.size() == 1
? "-1" : ""),new Integer[] {
Integer.valueOf(errors.size())});
new ErrorListDialog(
frame.isShowing()
? frame
: jEdit.getFirstView(),
jEdit.getProperty("ioerror.title"),
caption,errors,false);
}
});
}
catch (InterruptedException ie)
{
// preserve interruption flag, but don't stop
Thread.currentThread().interrupt();
}
catch (InvocationTargetException ite)
{
Log.log(Log.ERROR, ErrorDisplayer.class, ite);
}
}
public void run()
{
synchronized(errorLock)
{
// 2 threads might have been spawn simultaneously
if (errorDisplayerActive)
return;
errorDisplayerActive = true;
}
// The loop breaks only when errors.size() == 0
while (true)
{
synchronized(errorLock)
{
if (errors.size() == 0) {
errorDisplayerActive = false;
break;
}
}
// We know that there are errors, but let's wait a bit.
// Maybe there are more accumulating?
// We'll stay here until they stop coming out.
int errCount1 = -1, errCount2 = 0;
while (errCount1 != errCount2) //{{{
{
// errors is a Vector and Vectors are synchronized
errCount1 = errors.size();
try
{
Thread.sleep(200);
}
catch(InterruptedException ie)
{
// We don't stop, we have to display errors.
// However the flag must be preserved.
Thread.currentThread().interrupt();
// But since someone breaks us, let's exit
// this waiting loop.
break;
}
errCount2 = errors.size();
} //}}}
// For a while new errors didn't appear.
// Let's display those which we already have.
// While the dialog will be displayed,
// there may arrive the next, so we stay in
// the loop.
Vector<ErrorListDialog.ErrorEntry> errorsCopy;
synchronized(errorLock)
{
errorsCopy = new Vector<ErrorListDialog.ErrorEntry>(errors);
errors.clear();
error = false;
}
showDialog(frame, errorsCopy);
}
}
} //}}}
private VFSManager() {}
//}}}
}