001: /*
002: * VFSManager.java - Main class of virtual filesystem
003: * :tabSize=8:indentSize=8:noTabs=false:
004: * :folding=explicit:collapseFolds=1:
005: *
006: * Copyright (C) 2000, 2005 Slava Pestov
007: *
008: * This program is free software; you can redistribute it and/or
009: * modify it under the terms of the GNU General Public License
010: * as published by the Free Software Foundation; either version 2
011: * of the License, or any later version.
012: *
013: * This program is distributed in the hope that it will be useful,
014: * but WITHOUT ANY WARRANTY; without even the implied warranty of
015: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
016: * GNU General Public License for more details.
017: *
018: * You should have received a copy of the GNU General Public License
019: * along with this program; if not, write to the Free Software
020: * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
021: */
022:
023: package org.gjt.sp.jedit.io;
024:
025: //{{{ Imports
026: import javax.swing.JOptionPane;
027: import javax.swing.SwingUtilities;
028: import java.awt.Component;
029: import java.awt.Frame;
030: import java.io.IOException;
031: import java.util.*;
032:
033: import org.gjt.sp.jedit.gui.ErrorListDialog;
034: import org.gjt.sp.jedit.msg.VFSUpdate;
035: import org.gjt.sp.jedit.*;
036: import org.gjt.sp.util.Log;
037: import org.gjt.sp.util.WorkThreadPool;
038: import org.gjt.sp.util.StandardUtilities;
039:
040: //}}}
041:
042: /**
043: * jEdit's virtual filesystem allows it to transparently edit files
044: * stored elsewhere than the local filesystem, for example on an FTP
045: * site. See the {@link VFS} class for implementation details.<p>
046: *
047: * Note that most of the jEdit API is not thread-safe, so special care
048: * must be taken when making jEdit API calls. Also, it is not safe to
049: * call <code>SwingUtilities.invokeAndWait()</code> from a work request;
050: * it can cause a deadlock if the given runnable then later calls
051: * {@link #waitForRequests()}.
052: *
053: * @author Slava Pestov
054: * @version $Id: VFSManager.java 8286 2006-12-30 12:11:59Z kpouer $
055: */
056: public class VFSManager {
057: /**
058: * The service type. See {@link org.gjt.sp.jedit.ServiceManager}.
059: * @since jEdit 4.2pre1
060: */
061: public static final String SERVICE = "org.gjt.sp.jedit.io.VFS";
062:
063: //{{{ init() method
064: /**
065: * Do not call.
066: */
067: public static void init() {
068: int count = jEdit.getIntegerProperty("ioThreadCount", 4);
069: ioThreadPool = new WorkThreadPool("jEdit I/O", count);
070: JARClassLoader classLoader = new JARClassLoader();
071: for (int i = 0; i < ioThreadPool.getThreadCount(); i++) {
072: ioThreadPool.getThread(i)
073: .setContextClassLoader(classLoader);
074: }
075: } //}}}
076:
077: //{{{ start() method
078: /**
079: * Do not call.
080: */
081: public static void start() {
082: ioThreadPool.start();
083: } //}}}
084:
085: //{{{ VFS methods
086:
087: //{{{ getFileVFS() method
088: /**
089: * Returns the local filesystem VFS.
090: * @since jEdit 2.5pre1
091: */
092: public static VFS getFileVFS() {
093: return fileVFS;
094: } //}}}
095:
096: //{{{ getUrlVFS() method
097: /**
098: * Returns the URL VFS.
099: * @since jEdit 2.5pre1
100: */
101: public static VFS getUrlVFS() {
102: return urlVFS;
103: } //}}}
104:
105: //{{{ getVFSByName() method
106: /**
107: * @deprecated Use <code>getVFSForProtocol()</code> instead.
108: */
109: public static VFS getVFSByName(String name) {
110: // in new api, protocol always equals name
111: VFS vfs = (VFS) ServiceManager.getService(SERVICE, name);
112: if (vfs == null)
113: return vfsHash.get(name);
114: else
115: return vfs;
116: } //}}}
117:
118: //{{{ getVFSForProtocol() method
119: /**
120: * Returns the VFS for the specified protocol.
121: * @param protocol The protocol
122: * @since jEdit 2.5pre1
123: */
124: public static VFS getVFSForProtocol(String protocol) {
125: if (protocol.equals("file"))
126: return fileVFS;
127: else {
128: VFS vfs = (VFS) ServiceManager
129: .getService(SERVICE, protocol);
130: if (vfs == null)
131: vfs = protocolHash.get(protocol);
132:
133: if (vfs != null)
134: return vfs;
135: else
136: return urlVFS;
137: }
138: } //}}}
139:
140: //{{{ getVFSForPath() method
141: /**
142: * Returns the VFS for the specified path.
143: * @param path The path
144: * @since jEdit 2.6pre4
145: */
146: public static VFS getVFSForPath(String path) {
147: if (MiscUtilities.isURL(path))
148: return getVFSForProtocol(MiscUtilities
149: .getProtocolOfURL(path));
150: else
151: return fileVFS;
152: } //}}}
153:
154: //{{{ registerVFS() method
155: /**
156: * @deprecated Write a <code>services.xml</code> file instead;
157: * see {@link org.gjt.sp.jedit.ServiceManager}.
158: */
159: public static void registerVFS(String protocol, VFS vfs) {
160: Log.log(Log.DEBUG, VFSManager.class, "Registered "
161: + vfs.getName() + " filesystem for " + protocol
162: + " protocol");
163: vfsHash.put(vfs.getName(), vfs);
164: protocolHash.put(protocol, vfs);
165: } //}}}
166:
167: //{{{ getFilesystems() method
168: /**
169: * @deprecated Use <code>getVFSs()</code> instead.
170: */
171: public static Enumeration<VFS> getFilesystems() {
172: return vfsHash.elements();
173: } //}}}
174:
175: //{{{ getVFSs() method
176: /**
177: * Returns a list of all registered filesystems.
178: * @since jEdit 4.2pre1
179: */
180: public static String[] getVFSs() {
181: // the sooner ppl move to the new api, the less we'll need
182: // crap like this
183: List<String> returnValue = new LinkedList<String>();
184: String[] newAPI = ServiceManager.getServiceNames(SERVICE);
185: if (newAPI != null) {
186: for (int i = 0; i < newAPI.length; i++) {
187: returnValue.add(newAPI[i]);
188: }
189: }
190: Enumeration<String> oldAPI = vfsHash.keys();
191: while (oldAPI.hasMoreElements())
192: returnValue.add(oldAPI.nextElement());
193: return returnValue.toArray(new String[returnValue.size()]);
194: } //}}}
195:
196: //}}}
197:
198: //{{{ I/O request methods
199:
200: //{{{ getIOThreadPool() method
201: /**
202: * Returns the I/O thread pool.
203: */
204: public static WorkThreadPool getIOThreadPool() {
205: return ioThreadPool;
206: } //}}}
207:
208: //{{{ waitForRequests() method
209: /**
210: * Returns when all pending requests are complete.
211: * @since jEdit 2.5pre1
212: */
213: public static void waitForRequests() {
214: ioThreadPool.waitForRequests();
215: } //}}}
216:
217: //{{{ errorOccurred() method
218: /**
219: * Returns if the last request caused an error.
220: */
221: public static boolean errorOccurred() {
222: return error;
223: } //}}}
224:
225: //{{{ getRequestCount() method
226: /**
227: * Returns the number of pending I/O requests.
228: */
229: public static int getRequestCount() {
230: return ioThreadPool.getRequestCount();
231: } //}}}
232:
233: //{{{ runInAWTThread() method
234: /**
235: * Executes the specified runnable in the AWT thread once all
236: * pending I/O requests are complete.
237: * @since jEdit 2.5pre1
238: */
239: public static void runInAWTThread(Runnable run) {
240: ioThreadPool.addWorkRequest(run, true);
241: } //}}}
242:
243: //{{{ runInWorkThread() method
244: /**
245: * Executes the specified runnable in one of the I/O threads.
246: * @since jEdit 2.6pre2
247: */
248: public static void runInWorkThread(Runnable run) {
249: ioThreadPool.addWorkRequest(run, false);
250: } //}}}
251:
252: //}}}
253:
254: //{{{ error() method
255: /**
256: * Handle an I/O error.
257: * @since jEdit 4.3pre3
258: */
259: public static void error(IOException e, String path, Component comp) {
260: Log.log(Log.ERROR, VFSManager.class, e);
261: VFSManager.error(comp, path, "ioerror", new String[] { e
262: .toString() });
263: } //}}}
264:
265: //{{{ error() method
266: /**
267: * @deprecated Call the other <code>error()</code> method instead.
268: */
269: public static void error(final Component comp, final String error,
270: final Object[] args) {
271: // if we are already in the AWT thread, take a shortcut
272: if (SwingUtilities.isEventDispatchThread()) {
273: GUIUtilities.error(comp, error, args);
274: return;
275: }
276:
277: // the 'error' chicanery ensures that stuff like:
278: // VFSManager.waitForRequests()
279: // if(VFSManager.errorOccurred())
280: // ...
281: // will work (because the below runnable will only be
282: // executed in the next event)
283: VFSManager.error = true;
284:
285: runInAWTThread(new Runnable() {
286: public void run() {
287: VFSManager.error = false;
288:
289: if (comp == null || !comp.isShowing())
290: GUIUtilities.error(null, error, args);
291: else
292: GUIUtilities.error(comp, error, args);
293: }
294: });
295: } //}}}
296:
297: //{{{ error() method
298: /**
299: * Reports an I/O error.
300: *
301: * @param comp The component
302: * @param path The path name that caused the error
303: * @param messageProp The error message property name
304: * @param args Positional parameters
305: * @since jEdit 4.0pre3
306: */
307: public static void error(Component comp, final String path,
308: String messageProp, Object[] args) {
309: final Frame frame = JOptionPane.getFrameForComponent(comp);
310:
311: synchronized (errorLock) {
312: error = true;
313:
314: errors.add(new ErrorListDialog.ErrorEntry(path,
315: messageProp, args));
316:
317: if (errors.size() == 1) {
318:
319: VFSManager.runInAWTThread(new Runnable() {
320: public void run() {
321: String caption = jEdit.getProperty(
322: "ioerror.caption"
323: + (errors.size() == 1 ? "-1"
324: : ""),
325: new Integer[] { Integer.valueOf(errors
326: .size()) });
327: new ErrorListDialog(frame.isShowing() ? frame
328: : jEdit.getFirstView(), jEdit
329: .getProperty("ioerror.title"), caption,
330: errors, false);
331: errors.clear();
332: error = false;
333: }
334: });
335: }
336: }
337: } //}}}
338:
339: //{{{ sendVFSUpdate() method
340: /**
341: * Sends a VFS update message.
342: * @param vfs The VFS
343: * @param path The path that changed
344: * @param parent True if an update should be sent for the path's
345: * parent too
346: * @since jEdit 2.6pre4
347: */
348: public static void sendVFSUpdate(VFS vfs, String path,
349: boolean parent) {
350: if (parent) {
351: sendVFSUpdate(vfs, vfs.getParentOfPath(path), false);
352: sendVFSUpdate(vfs, path, false);
353: } else {
354: // have to do this hack until VFSPath class is written
355: if (path.length() != 1
356: && (path.endsWith("/") || path
357: .endsWith(java.io.File.separator)))
358: path = path.substring(0, path.length() - 1);
359:
360: synchronized (vfsUpdateLock) {
361: for (int i = 0; i < vfsUpdates.size(); i++) {
362: VFSUpdate msg = vfsUpdates.get(i);
363: if (msg.getPath().equals(path)) {
364: // don't send two updates
365: // for the same path
366: return;
367: }
368: }
369:
370: vfsUpdates.add(new VFSUpdate(path));
371:
372: if (vfsUpdates.size() == 1) {
373: // we were the first to add an update;
374: // add update sending runnable to AWT
375: // thread
376: VFSManager
377: .runInAWTThread(new SendVFSUpdatesSafely());
378: }
379: }
380: }
381: } //}}}
382:
383: //{{{ SendVFSUpdatesSafely class
384: static class SendVFSUpdatesSafely implements Runnable {
385: public void run() {
386: synchronized (vfsUpdateLock) {
387: // the vfs browser has what you might call
388: // a design flaw, it doesn't update properly
389: // unless the vfs update for a parent arrives
390: // before any updates for the children. sorting
391: // the list alphanumerically guarantees this.
392: Collections.sort(vfsUpdates,
393: new StandardUtilities.StringCompare());
394: for (int i = 0; i < vfsUpdates.size(); i++) {
395: EditBus.send(vfsUpdates.get(i));
396: }
397:
398: vfsUpdates.clear();
399: }
400: }
401: } //}}}
402:
403: //{{{ Private members
404:
405: //{{{ Static variables
406: private static WorkThreadPool ioThreadPool;
407: private static VFS fileVFS;
408: private static VFS urlVFS;
409: private static final Hashtable<String, VFS> vfsHash;
410: private static final Map<String, VFS> protocolHash;
411: private static boolean error;
412: private static final Object errorLock = new Object();
413: private static final Vector<ErrorListDialog.ErrorEntry> errors;
414: private static final Object vfsUpdateLock = new Object();
415: private static final List<VFSUpdate> vfsUpdates;
416: //}}}
417:
418: //{{{ Class initializer
419: static {
420: errors = new Vector<ErrorListDialog.ErrorEntry>();
421: fileVFS = new FileVFS();
422: urlVFS = new UrlVFS();
423: vfsHash = new Hashtable<String, VFS>();
424: protocolHash = new Hashtable<String, VFS>();
425: vfsUpdates = new ArrayList<VFSUpdate>(10);
426: } //}}}
427:
428: private VFSManager() {
429: }
430: //}}}
431: }
|