/* * Sun Public License Notice * * The contents of this file are subject to the Sun Public License * Version 1.0 (the "License"). You may not use this file except in * compliance with the License. A copy of the License is available at * http://www.sun.com/ * * The Original Code is NetBeans. The Initial Developer of the Original * Code is Sun Microsystems, Inc. Portions Copyright 1997-2003 Sun * Microsystems, Inc. All Rights Reserved. */ package org.openide.filesystems; import java.beans.*; import java.io.*; import java.util.*; import org.openide.util.NbBundle; import org.openide.util.io.NbMarshalledObject; /** This singleton object contains all {@link FileSystem}s in the IDE. * It corresponds to the <b>Filesystems</b> tab in the Explorer, or more precisely * to <b>Filesystems Settings</b> in Project Settings. * <P> * At any given time, no two filesystems in the pool may share the same {@link FileSystem#getSystemName name} * (unless all but one are {@link FileSystem#isValid invalid}). * * <p>Use {@link #getDefault} to retrieve the default instance. * * <p>Note that you may construct additional instances of the Repository if you * wish. The serialization replacer of this class specifically deals with the * default instance as stored by the top manager; however you may safely call * the {@link #readExternal} and {@link #writeExternal} methods directly to implement * persistence of a non-default instance. * * @author Jaroslav Tulach, Petr Hamernik */ public class Repository extends Object implements java.io.Serializable { /** list of filesystems (FileSystem) */ private ArrayList fileSystems; private transient ArrayList fileSystemsClone; /** the system filesystem */ private FileSystem system; /** hashtable that maps system names to FileSystems */ private Hashtable names; private transient FCLSupport fclSupport; // [PENDING] access to this hashtable is apparently not propertly synched // should use e.g. Collections.synchronizedSet, or just synch methods using it /** hashtable for listeners on changes in the filesystem. * Its elements are of type (RepositoryListener, RepositoryListener) */ private Hashtable listeners = new Hashtable (); /** vetoable listener on systemName property of filesystem */ private VetoableChangeListener vetoListener = new VetoableChangeListener () { /** @param ev event with changes */ public void vetoableChange (PropertyChangeEvent ev) throws PropertyVetoException { if (ev.getPropertyName ().equals ("systemName")) { final String ov = (String)ev.getOldValue (); final String nv = (String)ev.getNewValue (); if (names.get (nv) != null) { throw new PropertyVetoException ("system name already exists", ev) { // NOI18N public String getLocalizedMessage () { return NbBundle.getMessage (Repository.class, "EXC_duplicate_system_name", ov, nv); } }; } } } }; /** property listener on systemName property of filesystem */ private PropertyChangeListener propListener = new PropertyChangeListener () { /** @param ev event with changes */ public void propertyChange (PropertyChangeEvent ev) { if (ev.getPropertyName ().equals ("systemName")) { // assign the property to new name String ov = (String)ev.getOldValue (); String nv = (String)ev.getNewValue (); FileSystem fs = (FileSystem)ev.getSource (); if (fs.isValid ()) { // when a filesystem is valid then it is attached to a name names.remove (ov); } // register name of the filesystem names.put (nv, fs); // the filesystem becomes valid fs.setValid (true); } } }; static final long serialVersionUID =-6344768369160069704L; /** Creates new instance of filesystem pool and * registers it as the default one. Also registers the default filesystem. * * @param def the default filesystem */ public Repository (FileSystem def) { this.system = def; init (); } /** Access method to get default instance of repository in the system. * The instance is either taken as a result of * <CODE>org.openide.util.Lookup.getDefault ().lookup (Repository.class)</CODE> * or (if the lookup query returns null) a default instance is created. * * @return default repository for the system */ public static Repository getDefault () { return ExternalUtil.getRepository (); } /** Initialazes the pool. */ private void init () { // empties the pool fileSystems = new ArrayList (); names = new Hashtable (); addFileSystem (system); } /** Gets the default filesystem of the IDE. * @return the default filesystem */ public final FileSystem getDefaultFileSystem () { return system; } /** Adds new filesystem to the pool. * <em>Note</em> that a filesystem cannot be assigned to more than one file * system pool at one time (though currently there is only one pool anyway). * At any given time, no two filesystems in the pool may share the same {@link FileSystem#getSystemName name} * (unless all but one are {@link FileSystem#isValid invalid}). To be sure, that * filesystem was really added in Repository, then test that <code>FileSystem</code> * is valid. * @param fs filesystem to add */ public final void addFileSystem (FileSystem fs) { boolean fireIt = false; synchronized (this) { // if the filesystem is not assigned yet if (!fs.assigned && !fileSystems.contains(fs)) { // new filesystem fs.setRepository (this); fileSystems.add(fs); fileSystemsClone = (ArrayList)fileSystems.clone(); String systemName = fs.getSystemName (); boolean isReg = names.get (systemName) == null; if (isReg && !systemName.equals ("")) { // NOI18N // filesystem with the same name is not there => then it is valid names.put (systemName, fs); fs.setValid (true); } else { // there is another filesystem with the same name => it is invalid fs.setValid (false); } // mark the filesystem as being assigned fs.assigned = true; // mark as a listener on changes in the filesystem fs.addPropertyChangeListener (propListener); fs.addVetoableChangeListener (vetoListener); // notify filesystem itself that it has been added fs.addNotify(); // fire info about new filesystem fireIt = true; } } // postponed firing after synchronized block to prevent deadlock if (fireIt) fireFileSystem (fs, true); } /** Removes a filesystem from the pool. * @param fs filesystem to remove */ public final void removeFileSystem (FileSystem fs) { boolean fireIt = false; synchronized (this) { if (fs.isDefault()) return; if (fireIt = fileSystems.remove(fs)) { fs.setRepository (null); fileSystemsClone = (ArrayList)fileSystems.clone(); // the filesystem realy was here if (fs.isValid ()) { // if the filesystem is valid then is in names hashtable names.remove (fs.getSystemName ()); fs.setValid (false); } // in all cases remove it from listeners fs.removePropertyChangeListener (propListener); fs.removeVetoableChangeListener (vetoListener); // notify filesystem itself that it has been removed fs.removeNotify(); } // unassign the filesystem fs.assigned = false; } // postponed firing after synchronized block to prevent deadlock if (fireIt) fireFileSystem (fs, false); } /** Reorders {@link FileSystem}s by given permutation. * For example, if there are three filesystems, <code>new int[] {2, 0, 1}</code> cycles the filesystems forwards. * @param perm an array of integers * @throws IllegalArgumentException if the array is not a permutation, or is not the same length as the current number of filesystems in the pool */ public final void reorder(int[] perm) { synchronized (this) { if (perm == null) { throw new IllegalArgumentException ("null permutation"); // NOI18N } else if (perm.length != fileSystems.size ()) { throw new IllegalArgumentException ("permutation is wrong size: " + perm.length + " elements but should be " + fileSystems.size ()); // NOI18N } else if (! isPermutation (perm)) { StringBuffer message = new StringBuffer ("permutation is not really a permutation:"); // NOI18N for (int i = 0; i < perm.length; i++) { message.append (' '); message.append (perm[i]); } throw new IllegalArgumentException (message.toString ()); } ArrayList newList = new ArrayList (fileSystems.size ()); int len = perm.length; for (int i = 0; i < len; i++) { newList.add (fileSystems.get (perm[i])); } fileSystems = newList; fileSystemsClone = (ArrayList)fileSystems.clone(); } fireFileSystemReordered(perm); } /** @return true if the parameter describes a permutation */ private static boolean isPermutation(int[] perm) { final int len = perm.length; boolean[] bool = new boolean[len]; try { for (int i = 0; i < len; i++) { if (bool[perm[i]]) return false; else bool[perm[i]] = true; } return true; } catch (IndexOutOfBoundsException e) { return false; } } /** Returns enumeration of all filesystems. * @return enumeration of type {@link FileSystem} */ public final Enumeration getFileSystems () { ArrayList tempFileSystems = fileSystemsClone; return java.util.Collections.enumeration (tempFileSystems); } /** Returns enumeration of all filesystems. * @return enumeration of type {@link FileSystem} */ public final Enumeration fileSystems () { return getFileSystems (); } /** Returns a sorted array of filesystems. */ public final FileSystem[] toArray() { ArrayList tempFileSystems = fileSystemsClone; FileSystem[] fss = new FileSystem[tempFileSystems.size()]; tempFileSystems.toArray(fss); return fss; } /** Finds filesystem when only its system name is known. * @param systemName {@link FileSystem#getSystemName name} of the filesystem * @return the filesystem or <CODE>null</CODE> if there is no such * filesystem */ public final FileSystem findFileSystem (String systemName) { FileSystem fs = (FileSystem)names.get (systemName); return fs; } /** Saves pool to stream by saving all filesystems. * The default (system) filesystem, or any persistent filesystems, are skipped. * * @param oos object output stream * @exception IOException if an error occures * @deprecated Unused. */ public final synchronized void writeExternal (ObjectOutput oos) throws IOException { Iterator iter = fileSystems.iterator(); while (iter.hasNext()) { FileSystem fs = (FileSystem)iter.next(); if (!fs.isDefault () && !fs.isPersistent ()) { oos.writeObject (new NbMarshalledObject (fs)); } } oos.writeObject (null); } /** Reads object from stream. * Reads all filesystems. Persistent and system filesystems are untouched; all others are removed and possibly reread. * @param ois object input stream * @exception IOException if an error occures * @exception ClassNotFoundException if read class is not found * @deprecated Unused. */ public final synchronized void readExternal (ObjectInput ois) throws IOException, ClassNotFoundException { ArrayList temp = new ArrayList(10); for (;;) { Object obj = ois.readObject (); if (obj == null) { // all system has been read in break; } FileSystem fs; if (obj instanceof FileSystem) { fs = (FileSystem)obj; } else { try { NbMarshalledObject mar = (NbMarshalledObject)obj; fs = (FileSystem)mar.get (); } catch (IOException ex) { ExternalUtil.exception (ex); fs = null; } catch (ClassNotFoundException ex) { ExternalUtil.exception (ex); fs = null; } } if (fs != null) { // add the new filesystem temp.add(fs); } } Enumeration ee = getFileSystems(); FileSystem fs; while (ee.hasMoreElements()) { fs = (FileSystem) ee.nextElement(); if (!fs.isPersistent ()) { removeFileSystem (fs); } } // in init assigned is checked and we force 'system' to be added again system.assigned = false; init (); // all is successfuly read for (Iterator iter = temp.iterator(); iter.hasNext();) addFileSystem ((FileSystem) iter.next()); } /** Finds file when its name is provided. It scans in the list of * filesystems and asks them for the specified file by a call to * {@link FileSystem#find find}. The first object that is found is returned or <CODE>null</CODE> * if none of the filesystems contain such a file. * * @param aPackage package name where each package is separated by a dot * @param name name of the file (without dots) or <CODE>null</CODE> if * one wants to obtain the name of a package and not a file in it * @param ext extension of the file or <CODE>null</CODE> if one needs * a package and not a file name * * @return {@link FileObject} that represents file with given name or * <CODE>null</CODE> if the file does not exist * @deprecated Please use the <a href="@JAVA/API@/org/netbeans/api/java/classpath/api.html">ClassPath API</a> instead. */ public final FileObject find (String aPackage, String name, String ext) { Enumeration en = getFileSystems (); while (en.hasMoreElements ()) { FileSystem fs = (FileSystem)en.nextElement (); FileObject fo = fs.find (aPackage, name, ext); if (fo != null) { // object found return fo; } } return null; } /** Searches for the given resource among all filesystems. * <p><em>Note: Do not use this method for finding classes! * It is a wrong usage.</em> * @see FileSystem#findResource * @param name a name of the resource * @return file object or <code>null</code> if the resource can not be found */ public final FileObject findResource(String name) { Enumeration en = getFileSystems (); while (en.hasMoreElements ()) { FileSystem fs = (FileSystem)en.nextElement (); FileObject fo = fs.findResource(name); if (fo != null) { // object found return fo; } } return null; } /** Searches for the given resource among all filesystems, returning all matches. * <p><em>Note: Do not use this method for finding classes!. * It is a wrong usage.</em> * @param name name of the resource * @return enumeration of {@link FileObject}s */ public final Enumeration findAllResources(String name) { Vector v = new Vector(8); Enumeration en = getFileSystems (); while (en.hasMoreElements ()) { FileSystem fs = (FileSystem)en.nextElement (); FileObject fo = fs.findResource(name); if (fo != null) { v.addElement(fo); } } return v.elements(); } /** Finds all files among all filesystems matching a given name, returning all matches. * All filesystems are queried with {@link FileSystem#find}. * * @param aPackage package name where each package is separated by a dot * @param name name of the file (without dots) or <CODE>null</CODE> if * one wants to obtain the name of a package and not a file in it * @param ext extension of the file or <CODE>null</CODE> if one needs * a package and not a file name * * @return enumeration of {@link FileObject}s * @deprecated Please use the <a href="@JAVA/API@/org/netbeans/api/java/classpath/api.html">ClassPath API</a> instead. */ public final Enumeration findAll (String aPackage, String name, String ext) { Enumeration en = getFileSystems (); Vector ret = new Vector(); while (en.hasMoreElements ()) { FileSystem fs = (FileSystem)en.nextElement (); FileObject fo = fs.find (aPackage, name, ext); if (fo != null) { ret.addElement(fo); } } return ret.elements(); } /** Fire info about changes in the filesystem pool. * @param fs filesystem * @param add <CODE>true</CODE> if the filesystem is added, * <CODE>false</CODE> if it is removed */ private void fireFileSystem (FileSystem fs, boolean add) { Enumeration en = ((Hashtable)listeners.clone ()).elements (); RepositoryEvent ev = new RepositoryEvent (this, fs, add); while (en.hasMoreElements ()) { RepositoryListener list = (RepositoryListener)en.nextElement (); if (add) { list.fileSystemAdded (ev); } else { list.fileSystemRemoved (ev); } } } /** Fires info about reodering * @param perm */ private void fireFileSystemReordered(int[] perm) { Enumeration en = ((Hashtable)listeners.clone ()).elements (); RepositoryReorderedEvent ev = new RepositoryReorderedEvent(this, perm); while (en.hasMoreElements ()) { RepositoryListener list = (RepositoryListener)en.nextElement (); list.fileSystemPoolReordered(ev); } } /** Adds new listener. * @param list the listener */ public final void addRepositoryListener (RepositoryListener list) { listeners.put (list, list); } /** Removes listener. * @param list the listener */ public final void removeRepositoryListener (RepositoryListener list) { listeners.remove (list); } /** Writes the object to the stream. */ private Object writeReplace () { return new Replacer (); } final FCLSupport getFCLSupport() { synchronized (FCLSupport.class) { if (fclSupport == null) fclSupport = new FCLSupport (); } return fclSupport; } /** Add new listener to this object. * @param fcl the listener * @since 2.8 */ public final void addFileChangeListener(FileChangeListener fcl) { getFCLSupport ().addFileChangeListener(fcl); } /** Remove listener from this object. * @param fcl the listener * @since 2.8 */ public final void removeFileChangeListener(FileChangeListener fcl) { getFCLSupport ().removeFileChangeListener(fcl); } private static class Replacer implements java.io.Serializable { /** serial version UID */ static final long serialVersionUID=-3814531276726840241L; Replacer() {} private void writeObject (ObjectOutputStream oos) throws IOException { ExternalUtil.getRepository ().writeExternal (oos); } private void readObject (ObjectInputStream ois) throws IOException, ClassNotFoundException { ExternalUtil.getRepository ().readExternal (ois); } /** @return the default pool */ public Object readResolve () { return ExternalUtil.getRepository (); } } }