/*******************************************************************************
* Copyright (c) 1998, 2015 Oracle and/or its affiliates. All rights reserved.
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v1.0 and Eclipse Distribution License v. 1.0
* which accompanies this distribution.
* The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html
* and the Eclipse Distribution License is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* Contributors:
* Oracle - initial API and implementation from Oracle TopLink
******************************************************************************/
package org.eclipse.persistence.tools.workbench.uitools;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.prefs.PreferenceChangeEvent;
import java.util.prefs.PreferenceChangeListener;
import java.util.prefs.Preferences;
import org.eclipse.persistence.tools.workbench.utility.AbstractModel;
/**
* This implementation of the RecentFilesManager interface uses the Java
* Preferences framework to maintain the list of recently-opened files.
* It stores the list of files in a preferences node as a series of preferences
* keyed by integer strings, starting with 1 ("1", "2", "3",...).
*
* The largest this can be is 9, the smallest 0.
*/
public final class PreferencesRecentFilesManager
extends AbstractModel
implements RecentFilesManager
{
/**
* A node with a list of file names keyed by integers indicating
* their order in the list. For example:
* "1" -> "c:/dev/foo/foo.mwp"
* "2" -> "c:/dev/bar/bar.mwp"
*/
private Preferences recentFilesNode;
/** Listens for outside changes to the recent files. */
private PreferenceChangeListener recentFilesListener;
/** Cache the recent files to improve synchronization. */
private List recentFiles;
/**
* A node with a preference for the maximum number
* of recent files allowed.
*/
private Preferences maxSizeNode;
/** The key to the max size preference. */
String maxSizeKeyName;
/** Listens for outside changes to the max size. */
private PreferenceChangeListener maxSizeListener;
/** Cache the max size so we can tell when it has changed. */
private int maxSize;
// ********** constructors **********
/**
* Construct a manager for the specified recent files and
* maximum size preferences settings.
*/
public PreferencesRecentFilesManager(Preferences recentFilesNode, Preferences maxSizeNode, String maxSizeKeyName) {
super();
this.initialize(recentFilesNode, maxSizeNode, maxSizeKeyName);
}
// ********** initialization **********
/**
* Build the listeners.
*/
protected void initialize() {
super.initialize();
this.recentFilesListener = this.buildRecentFilesListener();
this.maxSizeListener = this.buildMaxSizeListener();
}
private PreferenceChangeListener buildRecentFilesListener() {
return new PreferenceChangeListener() {
public void preferenceChange(PreferenceChangeEvent evt) {
PreferencesRecentFilesManager.this.recentFilesChanged(evt.getKey(), evt.getNewValue());
}
public String toString() {
return "recent files listener";
}
};
}
private PreferenceChangeListener buildMaxSizeListener() {
return new PreferenceChangeListener() {
public void preferenceChange(PreferenceChangeEvent evt) {
if (evt.getKey().equals(PreferencesRecentFilesManager.this.maxSizeKeyName)) {
PreferencesRecentFilesManager.this.maxSizeChanged();
}
}
public String toString() {
return "max size listener";
}
};
}
/**
* Begin listening to the nodes so we can
* propagate any events.
*/
private void initialize(Preferences recentFilesPreferences, Preferences maxSizePreferences, String maxSizePrefKeyName) {
this.recentFilesNode = recentFilesPreferences;
recentFilesPreferences.addPreferenceChangeListener(this.recentFilesListener);
this.maxSizeNode = maxSizePreferences;
this.maxSizeKeyName = maxSizePrefKeyName;
maxSizePreferences.addPreferenceChangeListener(this.maxSizeListener);
this.maxSize = this.buildMaxSize();
this.checkMaxSize(this.maxSize);
this.recentFiles = this.buildRecentFiles();
}
// ********** RecentFilesManager implementation **********
/**
* @see org.eclipse.persistence.tools.workbench.uitools.RecentFilesManager#getMaxSize()
*/
public synchronized int getMaxSize() {
return this.maxSize;
}
/**
* @see org.eclipse.persistence.tools.workbench.uitools.RecentFilesManager#setMaxSize(int)
*/
public synchronized void setMaxSize(int maxSize) {
if (this.maxSize == maxSize) {
return; // no change
}
this.checkMaxSize(maxSize);
this.maxSize = maxSize;
// rebuild the recent files list and see whether it has changed
List oldRecentFiles = this.recentFiles;
this.recentFiles = this.buildRecentFiles();
if ( ! oldRecentFiles.equals(this.recentFiles)) {
this.fireStateChanged();
}
// forward the change to the preferences node
this.maxSizeNode.putInt(this.maxSizeKeyName, maxSize);
}
/**
* @see RecentFilesManager#getRecentFiles()
*/
public synchronized File[] getRecentFiles() {
return (File[]) this.recentFiles.toArray(new File[this.recentFiles.size()]);
}
/**
* @see RecentFilesManager#setMostRecentFile(java.io.File)
*/
public synchronized void setMostRecentFile(File file) {
int index = this.recentFiles.indexOf(file);
if (index == 0) {
return; // the file is already at the top of the list
}
if (index == -1) {
// the file is a new addition to the list
this.recentFiles.add(0, file);
if (this.recentFiles.size() > this.maxSize) {
// remove extra files
while (this.recentFiles.size() > this.maxSize) {
this.recentFiles.remove(this.recentFiles.size() - 1);
}
}
this.fireStateChanged();
// replace all the preferences
this.replacePreferences(this.recentFiles);
} else {
// the file is already on the list - move it to the top
this.recentFiles.remove(index);
this.recentFiles.add(0, file);
this.fireStateChanged();
// replace only the preferences up to where the moved file was originally located
this.replacePreferences(this.recentFiles.subList(0, index + 1));
}
}
public synchronized void removeRecentFile(File file) {
int index = this.recentFiles.indexOf(file);
if (index == -1) {
return;// the file is not in the list
}
this.recentFiles.remove(index);
this.fireStateChanged();
this.recentFilesNode.remove(Integer.toString(this.recentFiles.size() + 1)); // Always remove the last entry
this.replacePreferences(this.recentFiles);
}
// ********** internal methods **********
/**
* Get the max size from the appropriate preference.
*/
private int buildMaxSize() {
return this.maxSizeNode.getInt(this.maxSizeKeyName, DEFAULT_MAX_SIZE);
}
/**
* The maximum size must be greater than zero.
*/
private void checkMaxSize(int size) {
if (size < 0) {
throw new IllegalArgumentException("the maximum size must not be negative");
}
else if (size > MAX_MAX_SIZE) {
throw new IllegalArgumentException("the maximum size must be less than " + MAX_MAX_SIZE);
}
}
/**
* Get the recent files list from the preferences node.
*/
private List buildRecentFiles() {
List result = new ArrayList(this.maxSize);
int count = 1;
String fileName = this.recentFilesNode.get(Integer.toString(count), null);
while ((fileName != null) && (count <= this.maxSize)) {
result.add(new File(fileName));
count++;
fileName = this.recentFilesNode.get(Integer.toString(count), null);
}
return result;
}
/**
* Replace the preferences with the entries in the
* specified list.
*/
private void replacePreferences(List files) {
for (int i = 0; i < files.size(); i++) {
// preference "indexes" are 1-based
this.recentFilesNode.put(Integer.toString(i + 1), ((File) files.get(i)).getPath());
}
}
/**
* The underlying recent files list changed; either because we
* changed it in #replacePreferences(List) or a third-party changed
* it. If this is called because of our own change, nothing will
* happen because the old and new files are the same.
*/
synchronized void recentFilesChanged(String key, String newValue) {
int count = 0;
try {
count = Integer.parseInt(key);
} catch (NumberFormatException ex) {
return; // non-integer key - ignore
}
if ( ! Integer.toString(count).equals(key)) {
return; // "unnormalized" key (e.g. "007") - ignore
}
if ((count < 1) || (count > this.recentFiles.size() + 1) || (count > this.maxSize)) {
return; // out of range key - ignore
}
int index = count - 1;
if (newValue == null) {
// a file was removed by a third-party - truncate the list
while (this.recentFiles.size() > index) {
this.recentFiles.remove(this.recentFiles.size() - 1);
}
this.fireStateChanged();
return;
}
File newFile = new File(newValue);
if (index == this.recentFiles.size()) {
// a file was added to the end by a third-party and we are still under our max size;
this.recentFiles.add(newFile);
this.fireStateChanged();
return;
}
if (newFile.equals(this.recentFiles.get(index))) {
return; // same file - probably a result of #replacePreferences(List)
}
// one of the existing files was changed by a third-party
this.recentFiles.set(index, newFile);
this.fireStateChanged();
}
/**
* The underlying max size preference changed; either because
* we changed it in #setMaxSize(int) or a third-party changed it.
* If this is called because of our own change, nothing will
* happen because the old and new values are the same.
*/
synchronized void maxSizeChanged() {
int newMaxSize = this.buildMaxSize();
if (this.maxSize == newMaxSize) {
return; // no change
}
this.checkMaxSize(newMaxSize);
this.maxSize = newMaxSize;
// rebuild the recent files list and see whether it has changed
List oldRecentFiles = this.recentFiles;
this.recentFiles = this.buildRecentFiles();
if ( ! oldRecentFiles.equals(this.recentFiles)) {
this.fireStateChanged();
}
}
}