org.netbeans.modules.openfile.RecentFiles Maven / Gradle / Ivy
The newest version!
/*
* 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.openfile;
import java.beans.BeanInfo;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.prefs.BackingStoreException;
import java.util.prefs.Preferences;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import org.netbeans.modules.openfile.RecentFiles.HistoryItem;
import org.openide.filesystems.FileObject;
import org.openide.filesystems.FileUtil;
import org.openide.loaders.DataObject;
import org.openide.loaders.DataObjectNotFoundException;
import org.openide.modules.OnStop;
import org.openide.util.ImageUtilities;
import org.openide.util.NbPreferences;
import org.openide.util.RequestProcessor;
import org.openide.windows.CloneableTopComponent;
import org.openide.windows.TopComponent;
import org.openide.windows.WindowManager;
/**
* Manages prioritized set of recently closed files.
*
* @author Dafe Simonek
*/
public final class RecentFiles {
static final String PROPERTY_RECENT_FILES = "RecentFiles";
/** List of recently closed files */
private static List history = new ArrayList();
/** Request processor */
private static RequestProcessor RP = new RequestProcessor(RecentFiles.class);
/** Preferences node for storing history info */
private static Preferences prefs;
private static final Object HISTORY_LOCK = new Object();
/** Name of preferences node where we persist history */
private static final String PREFS_NODE = "RecentFilesHistory"; //NOI18N
/** Prefix of property for recent file URL*/
private static final String PROP_URL_PREFIX = "RecentFilesURL."; //NOI18N
/** Prefix of property for recent file icon bytes*/
private static final String PROP_ICON_PREFIX = "RecentFilesIcon."; //NOI18N
/** Boundary for items count in history */
static final int MAX_HISTORY_ITEMS = 15;
private static PropertyChangeListener windowRegistryListener;
private static final Logger LOG = Logger.getLogger(
RecentFiles.class.getName());
private static final String RECENT_FILE_KEY = "nb.recent.file.path"; // NOI18N
private static final PropertyChangeSupport PCH_SUPPORT = new PropertyChangeSupport(PROPERTY_RECENT_FILES);
private RecentFiles() {
}
public static void addPropertyChangeListener(PropertyChangeListener l) {
PCH_SUPPORT.addPropertyChangeListener(l);
}
public static void removePropertyChangeListener(PropertyChangeListener l) {
PCH_SUPPORT.removePropertyChangeListener(l);
}
/** Starts to listen for recently closed files */
public static void init() {
WindowManager.getDefault().invokeWhenUIReady(new Runnable() {
@Override
public void run() {
List loaded = load();
synchronized (HISTORY_LOCK) {
history.addAll(0, loaded);
PCH_SUPPORT.firePropertyChange(PROPERTY_RECENT_FILES, null, null);
if (windowRegistryListener == null) {
windowRegistryListener = new WindowRegistryL();
TopComponent.getRegistry().addPropertyChangeListener(
windowRegistryListener);
}
}
}
});
}
/** Returns read-only list of recently closed files */
public static List getRecentFiles() {
synchronized (HISTORY_LOCK) {
checkHistory();
return Collections.unmodifiableList(history);
}
}
private static volatile boolean historyProbablyValid;
/**
* True if there are probably some recently closed files.
* Note: will still be true if all of them are in fact invalid,
* but this is much faster than calling {@link #getRecentFiles}.
*/
public static boolean hasRecentFiles() {
if (!historyProbablyValid) {
synchronized (HISTORY_LOCK) {
checkHistory();
return !history.isEmpty();
}
}
return historyProbablyValid;
}
/** Loads list of recent files stored in previous system sessions.
* @return list of stored recent files
*/
static List load() {
String[] keys;
Preferences _prefs = getPrefs();
try {
keys = _prefs.keys();
} catch (BackingStoreException ex) {
Logger.getLogger(RecentFiles.class.getName()).
log(Level.FINE, ex.getMessage(), ex);
return Collections.emptyList();
}
List result = new ArrayList();
for (String curKey : keys) {
if (curKey.startsWith(PROP_ICON_PREFIX)) {
continue;
}
String value = _prefs.get(curKey, null);
if (value != null) {
try {
int id = Integer.parseInt(curKey.substring(PROP_URL_PREFIX.length()));
HistoryItem hItem = new HistoryItem(id, value,
_prefs.getByteArray(PROP_ICON_PREFIX + id, null));
int ind = result.indexOf(hItem);
if (ind == -1) {
result.add(hItem);
} else {
_prefs.remove(PROP_URL_PREFIX +
Math.max(result.get(ind).id, id));
result.get(ind).id = Math.min(result.get(ind).id, id);
}
} catch (Exception ex) {
Logger.getLogger(RecentFiles.class.getName()).
log(Level.FINE, ex.getMessage(), ex);
_prefs.remove(curKey);
}
} else {
//clear the recent files history file from the old,
// not known and broken keys
_prefs.remove(curKey);
}
}
Collections.sort(result);
store(result);
return result;
}
static void store() {
store(history);
}
static void store(List history) {
Preferences _prefs = getPrefs();
for (int i = 0; i < history.size(); i++) {
HistoryItem hi = history.get(i);
if ((hi.id != i) && (hi.id >= history.size())) {
_prefs.remove(PROP_URL_PREFIX + hi.id);
_prefs.remove(PROP_ICON_PREFIX + hi.id);
}
hi.id = i;
_prefs.put(PROP_URL_PREFIX + i, hi.getPath());
if (hi.getIconBytes() == null) {
_prefs.remove(PROP_ICON_PREFIX + i);
} else {
_prefs.putByteArray(PROP_ICON_PREFIX + i, hi.getIconBytes());
}
}
LOG.log(Level.FINE, "Stored");
}
/**
* Clear the history. Should be called only from tests.
*/
static void clear() {
try {
synchronized (HISTORY_LOCK) {
history.clear();
PCH_SUPPORT.firePropertyChange(PROPERTY_RECENT_FILES, null, null);
getPrefs().clear();
getPrefs().flush();
}
} catch (BackingStoreException ex) {
LOG.log(Level.WARNING, null, ex);
}
}
static Preferences getPrefs() {
if (prefs == null) {
prefs = NbPreferences.forModule(RecentFiles.class).node(PREFS_NODE);
}
return prefs;
}
/** Adds file represented by given TopComponent to the list,
* if conditions are met.
*/
private static void addFile(final TopComponent tc) {
RP.post(new Runnable() {
@Override
public void run() {
addFile(obtainPath(tc));
}
});
}
static void addFile(String path) {
if (path != null) {
historyProbablyValid = false;
synchronized (HISTORY_LOCK) {
// avoid duplicates
HistoryItem hItem = null;
do {
hItem = findHistoryItem(path);
} while (history.remove(hItem));
final HistoryItem newItem = new HistoryItem(0, path);
history.add(0, newItem);
for (int i = MAX_HISTORY_ITEMS; i < history.size(); i++) {
history.remove(i);
}
newItem.setIcon(findIconForPath(newItem.getPath()));
PCH_SUPPORT.firePropertyChange(PROPERTY_RECENT_FILES, null, null);
store();
}
}
}
/** Removes file represented by given TopComponent from the list */
private static void removeFile(final TopComponent tc) {
RP.post(new Runnable() {
@Override
public void run() {
historyProbablyValid = false;
String path = obtainPath(tc);
if (path != null) {
synchronized (HISTORY_LOCK) {
HistoryItem hItem = findHistoryItem(path);
if (hItem != null) {
history.remove(hItem);
PCH_SUPPORT.firePropertyChange(PROPERTY_RECENT_FILES, null, null);
}
store();
}
}
}
});
}
private static Icon findIconForPath(String path) {
FileObject fo = RecentFiles.convertPath2File(path);
final Icon i;
if (fo == null) {
i = null;
} else {
DataObject dObj;
try {
dObj = DataObject.find(fo);
} catch (DataObjectNotFoundException e) {
dObj = null;
}
i = dObj == null
? null
: new ImageIcon(dObj.getNodeDelegate().getIcon(
BeanInfo.ICON_COLOR_16x16));
}
return i;
}
private static String obtainPath(TopComponent tc) {
Object file = tc.getClientProperty( RECENT_FILE_KEY );
if( file instanceof File )
return ((File)file).getPath();
if( tc instanceof CloneableTopComponent ) {
DataObject dObj = tc.getLookup().lookup(DataObject.class);
if (dObj != null) {
FileObject fo = dObj.getPrimaryFile();
if (fo != null) {
return convertFile2Path(fo);
}
}
}
return null;
}
private static HistoryItem findHistoryItem(String path) {
for (HistoryItem hItem : history) {
if (path.equals(hItem.getPath())) {
return hItem;
}
}
return null;
}
static String convertFile2Path(FileObject fo) {
File f = FileUtil.toFile(fo);
return f == null ? null : f.getPath();
}
static FileObject convertPath2File(String path) {
File f = new File(path);
f = FileUtil.normalizeFile(f);
return f == null ? null : FileUtil.toFileObject(f);
}
/** Checks recent files history and removes non-valid entries */
private static void checkHistory() {
assert Thread.holdsLock(HISTORY_LOCK);
historyProbablyValid = !history.isEmpty();
}
private static byte[] iconToBytes(Icon icon) {
if (icon == null) {
return null;
} else {
ByteArrayOutputStream out = new ByteArrayOutputStream();
try {
ObjectOutputStream objOut = new ObjectOutputStream(out);
try {
//#138000
Icon icn = icon;
if (!(icn instanceof Serializable)) {
icn = new ImageIcon(ImageUtilities.icon2Image(icn));
}
objOut.writeObject(icn);
return out.toByteArray();
} finally {
objOut.close();
}
} catch (IOException ex) {
return null;
} finally {
try {
out.close();
} catch (IOException ex) {
}
}
}
}
private static Icon bytesToIcon(byte[] bytes) {
if (bytes == null) {
return null;
} else {
ObjectInputStream objin = null;
try {
ByteArrayInputStream in = new ByteArrayInputStream(bytes);
objin = new ObjectInputStream(in);
Object obj = objin.readObject();
return (obj instanceof Icon) ? (Icon) obj : null;
} catch (Exception ex) {
return null;
} finally {
try {
if (objin != null) {
objin.close();
}
} catch (IOException ex) {
}
}
}
}
static void pruneHistory() {
synchronized (HISTORY_LOCK) {
Iterator it = history.iterator();
while (it.hasNext()) {
HistoryItem historyItem = it.next();
File f = new File(historyItem.getPath());
if (!f.exists()) {
it.remove();
}
}
}
}
/**
* One item of the recently closed files history.
* Comparable by the time field, ascending from most recent to older items.
*/
public static final class HistoryItem implements Comparable {
private int id;
private String path;
private String fileName;
private Icon icon = null;
HistoryItem(int id, String path) {
this(id, path, null);
}
HistoryItem(int id, String path, byte[] iconBytes) {
this.path = path;
this.id = id;
this.icon = bytesToIcon(iconBytes);
}
public String getPath() {
return path;
}
public String getFileName() {
if (fileName == null) {
int pos = path.lastIndexOf(File.separatorChar);
if ((pos != -1) && (pos < path.length())) {
fileName = path.substring(pos + 1);
} else {
fileName = path;
}
}
return fileName;
}
/**
* Get specified icon, or a default one if no icon is specified.
*/
public Icon getIcon() {
return this.icon == null
? ImageUtilities.loadImageIcon(
"org/openide/resources/actions/empty.gif", false) //NOI18N
: this.icon;
}
/**
* Set icon of this history item. The icon can be set after
* initialization, usually after it was loaded in a background thread.
*/
void setIcon(Icon icon) {
this.icon = icon;
}
/**
* Return bytes for the icon, or null if no icon is specified.
*/
byte[] getIconBytes() {
return iconToBytes(icon);
}
@Override
public int compareTo(HistoryItem o) {
return this.id - o.id;
}
@Override
public boolean equals(Object obj) {
if (obj instanceof HistoryItem) {
return ((HistoryItem) obj).getPath().equals(path);
}
return false;
}
@Override
public int hashCode() {
int hash = 7;
hash = 17 * hash + (this.path != null ? this.path.hashCode() : 0);
return hash;
}
}
/** Receives info about opened and closed TopComponents from window system.
*/
private static class WindowRegistryL implements PropertyChangeListener {
@Override
public void propertyChange(PropertyChangeEvent evt) {
String name = evt.getPropertyName();
if (TopComponent.Registry.PROP_TC_CLOSED.equals(name)) {
addFile((TopComponent) evt.getNewValue());
}
if (TopComponent.Registry.PROP_TC_OPENED.equals(name)) {
removeFile((TopComponent) evt.getNewValue());
}
}
}
/**
* {@link Runnable} that will be invoked during shutdown sequence and that
* will add non-persistent {@link TopComponent}s to the list of recent
* files. See bug #218695.
*/
@OnStop
public static final class NonPersistentDocumentsAdder implements Runnable {
@Override
public void run() {
for (TopComponent tc : TopComponent.getRegistry().getOpened()) {
if (TopComponent.PERSISTENCE_NEVER == tc.getPersistenceType()) {
addFile(tc);
}
}
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy