| /* |
| * 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.netbeans.modules.editor.mimelookup.impl; |
| |
| import java.beans.PropertyChangeEvent; |
| import java.beans.PropertyChangeListener; |
| import java.beans.PropertyChangeSupport; |
| import java.util.ArrayList; |
| import java.util.List; |
| import java.util.logging.Level; |
| import java.util.logging.Logger; |
| import org.openide.filesystems.FileAttributeEvent; |
| import org.openide.filesystems.FileChangeListener; |
| import org.openide.filesystems.FileEvent; |
| import org.openide.filesystems.FileObject; |
| import org.openide.filesystems.FileRenameEvent; |
| import org.openide.filesystems.FileStateInvalidException; |
| import org.openide.filesystems.FileSystem; |
| import org.openide.filesystems.FileUtil; |
| import org.openide.filesystems.MultiFileSystem; |
| import org.openide.util.Exceptions; |
| |
| /** |
| * |
| * @author vita, Jesse Glick |
| */ |
| public final class CompoundFolderChildren implements FileChangeListener { |
| |
| public static final String PROP_CHILDREN = "FolderChildren.PROP_CHILDREN"; //NOI18N |
| |
| private static final String HIDDEN_ATTR_NAME = "hidden"; //NOI18N |
| |
| private static final Logger LOG = Logger.getLogger(CompoundFolderChildren.class.getName()); |
| |
| private final String LOCK = new String("CompoundFolderChildren.LOCK"); //NOI18N |
| private final List<String> prefixes; |
| private final boolean includeSubfolders; |
| private List<FileObject> children; |
| private FileObject mergedLayers; // just hold a strong ref so listeners remain active |
| private final FileChangeListener weakFCL = FileUtil.weakFileChangeListener(this, null); |
| |
| private PropertyChangeSupport pcs = new PropertyChangeSupport(this); |
| |
| public CompoundFolderChildren(String [] paths) { |
| this(paths, true); |
| } |
| |
| public CompoundFolderChildren(String [] paths, boolean includeSubfolders) { |
| prefixes = new ArrayList<String>(); |
| for (String path : paths) { |
| prefixes.add(path.endsWith("/") ? path : path + "/"); // NOI18N |
| } |
| this.includeSubfolders = includeSubfolders; |
| rebuild(); |
| } |
| |
| public List<FileObject> getChildren() { |
| synchronized (LOCK) { |
| return children; |
| } |
| } |
| |
| public void addPropertyChangeListener(PropertyChangeListener l) { |
| pcs.addPropertyChangeListener(l); |
| } |
| |
| public void removePropertyChangeListener(PropertyChangeListener l) { |
| pcs.removePropertyChangeListener(l); |
| } |
| |
| private void rebuild() { |
| PropertyChangeEvent event = null; |
| synchronized (LOCK) { |
| List<FileObject> folders = new ArrayList<FileObject>(prefixes.size()); |
| List<FileSystem> layers = new ArrayList<FileSystem>(prefixes.size()); |
| for (final String prefix : prefixes) { |
| // use system-wide configuration, ignore execution-local specifics |
| FileObject layer = FileUtil.getSystemConfigFile(prefix); |
| if (layer != null && layer.isFolder()) { |
| folders.add(layer); |
| try { |
| layers.add(new MultiFileSystem(new FileSystem[]{layer.getFileSystem()}) { |
| |
| @Override |
| protected FileObject findResourceOn(FileSystem fs, String res) { |
| return fs.findResource(prefix + res); |
| } |
| }); |
| } catch (FileStateInvalidException ex) { |
| Exceptions.printStackTrace(ex); |
| } |
| } else { |
| // Listen to nearest enclosing parent, in case it is created. |
| // XXX would be simpler to use FileChangeSupport but that is in ant/project for now. |
| String parentPath = prefix; |
| while (true) { |
| assert parentPath.length() > 0; |
| parentPath = parentPath.substring(0, Math.max(0, parentPath.lastIndexOf('/'))); |
| FileObject parent = FileUtil.getConfigFile(parentPath); |
| if (parent != null) { |
| parent.removeFileChangeListener(weakFCL); |
| parent.addFileChangeListener(weakFCL); |
| break; |
| } |
| } |
| } |
| } |
| mergedLayers = new MultiFileSystem(layers.toArray(new FileSystem[layers.size()])).getRoot(); |
| mergedLayers.addFileChangeListener(this); // need not be weak since only we hold this FS |
| List<FileObject> unsorted = new ArrayList<FileObject>(); |
| for (FileObject f : mergedLayers.getChildren()) { |
| if ((includeSubfolders || f.isData()) && !Boolean.TRUE.equals(f.getAttribute(HIDDEN_ATTR_NAME))) { |
| f.addFileChangeListener(this); |
| unsorted.add(f); |
| } |
| } |
| List<FileObject> sorted = new ArrayList<FileObject>(unsorted.size()); |
| for (FileObject merged : FileUtil.getOrder(unsorted, true)) { |
| String name = merged.getNameExt(); |
| FileObject original = null; |
| for (FileObject folder : folders) { |
| original = folder.getFileObject(name); |
| if (original != null) { |
| break; |
| } |
| } |
| assert original != null : "Should have equivalent to " + name + " among " + folders; |
| sorted.add(original); |
| } |
| if (children != null && !sorted.equals(children)) { |
| event = new PropertyChangeEvent(this, PROP_CHILDREN, children, sorted); |
| } |
| children = sorted; |
| |
| if (LOG.isLoggable(Level.FINE)) { |
| rebuildCnt++; |
| LOG.log(Level.FINE, "{0} rebuilt {1} times", new Object [] { this, rebuildCnt }); |
| } |
| } |
| if (event != null) { |
| pcs.firePropertyChange(event); |
| } |
| } |
| |
| public void fileFolderCreated(FileEvent fe) { |
| fe.runWhenDeliveryOver(rebuildRunnable); |
| } |
| |
| public void fileDataCreated(FileEvent fe) { |
| fe.runWhenDeliveryOver(rebuildRunnable); |
| } |
| |
| public void fileChanged(FileEvent fe) { |
| fe.runWhenDeliveryOver(propChangeRunnable); |
| } |
| |
| public void fileDeleted(FileEvent fe) { |
| fe.runWhenDeliveryOver(rebuildRunnable); |
| } |
| |
| public void fileRenamed(FileRenameEvent fe) { |
| fe.runWhenDeliveryOver(rebuildRunnable); |
| } |
| |
| public void fileAttributeChanged(FileAttributeEvent fe) { |
| // if (FileUtil.affectsOrder(fe)) { |
| // if (!filterEvents(fe)) { |
| // rebuild(); |
| // } else { |
| // rebuildTask.schedule(DELAY); |
| // } |
| // } |
| fe.runWhenDeliveryOver(rebuildRunnable); |
| } |
| |
| private final Runnable rebuildRunnable = new Runnable() { |
| public void run() { |
| rebuild(); |
| } |
| }; |
| |
| private final Runnable propChangeRunnable = new Runnable() { |
| public void run() { |
| pcs.firePropertyChange(PROP_CHILDREN, null, null); |
| } |
| }; |
| |
| // // ignoring loads of fileAttributeChanged events fired when installing plugins |
| // // see issue #161201 and #168536 |
| // private static final int DELAY = 1000; // milliseconds |
| // private final RequestProcessor.Task rebuildTask = RequestProcessor.getDefault().create(new Runnable() { |
| // public void run() { |
| // synchronized (times) { |
| // times.clear(); |
| // } |
| // rebuild(); |
| // } |
| // }); |
| // private final Map<String, Long> times = new HashMap<String, Long>(); |
| private long rebuildCnt = 0; |
| // private boolean filterEvents(FileEvent event) { |
| // // filter out duplicate events |
| // synchronized (times) { |
| // Long timestamp = times.get(event.getFile().getPath()); |
| // times.put(event.getFile().getPath(), event.getTime()); |
| // boolean filterOut = timestamp != null && Math.abs(event.getTime() - timestamp) < DELAY; |
| // if (LOG.isLoggable(Level.FINER)) { |
| // if (filterOut) { |
| // LOG.fine("Filtering out filesystem event: [" + printEvent(event) + "]"); //NOI18N |
| // } else { |
| // LOG.fine("Processing filesystem event: [" + printEvent(event) + "]"); //NOI18N |
| // } |
| // } |
| // return filterOut; |
| // |
| // } |
| // } |
| // |
| // private static String printEvent(FileEvent event) { |
| // return event.getClass().getSimpleName() + "@" + Integer.toHexString(System.identityHashCode(event)) //NOI18N |
| // + ", ts=" + event.getTime() //NOI18N |
| // + ", path=" + event.getFile().getPath(); //NOI18N |
| // } |
| } |