/* * $Id$ * * Copyright (c) 2002, 2011, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package com.sun.javatest.tool; import java.awt.event.ActionListener; import java.io.*; import java.lang.ref.WeakReference; import java.nio.charset.StandardCharsets; import java.util.*; import javax.swing.JMenu; import javax.swing.JMenuItem; import javax.swing.event.MenuEvent; import javax.swing.event.MenuListener; import com.sun.javatest.WorkDirectory; import com.sun.javatest.util.I18NResourceBundle; /** * A class to maintain a history of recently used files. The history is * maintained in a specified file in a WorkDirectory, and can be * dynamically added to a menu by means of a Listener class. * The format of the file is one file per line, with most recently * added entries appearing first. Lines beginning with # are ignored. */ public class FileHistory { /** * Get a shared FileHistory object for a specified file and work directory. * @param wd The work directory in which the history file is maintained. * @param name The name of the file within the work direectory's jtData/ * subdirectory. * @return the specified FileHistory object */ public static FileHistory getFileHistory(WorkDirectory wd, String name) { if (cache == null) cache = new WeakHashMap<>(8); // first, get a map for all files in this wd Map map = cache.get(wd); if (map == null) { map = new HashMap<>(8); cache.put(wd, map); } // then, get the FileHistory for the specified file FileHistory h = map.get(name); if (h == null) { h = new FileHistory(wd, name); map.put(name, h); } return h; } /** * Get a shared FileHistory object for a specified file and path to work directory. * @param wdFile The path th work directory in which the history file is maintained. * @param name The name of the file within the work direectory's jtData/ * subdirectory. * @return the specified FileHistory object */ public static FileHistory getFileHistory(File wdFile, String name) { if (cache == null) cache = new WeakHashMap<>(8); if (!WorkDirectory.isWorkDirectory(wdFile)) return null; // let's find in the cache work dir corresponding to the path Iterator it = cache.keySet().iterator(); WorkDirectory wd = null; while (it.hasNext()) { WorkDirectory tempWD = it.next(); if (tempWD.getRoot().equals(wdFile)) { wd = tempWD; break; } } if (wd != null) return FileHistory.getFileHistory(wd, name); else return null; } /** * Add a new file to the history. * The file in the work directory for this history will be updated. * @param file the file to be added to the history */ public void add(File file) { ensureEntriesUpToDate(); file = file.getAbsoluteFile(); entries.remove(file); entries.add(0, file); writeEntries(); } /** * Get the most recent entries from the history. Only entries for * files that exist on this system are returned. Thus the history * can accommodate files for different systems, which will likely not * exist on all systems on which the history is used. * @param count the number of most recent, existing files * to be returned. * @return an array of the most recent, existing entries */ public File[] getRecentEntries(int count) { ensureEntriesUpToDate(); // scan the entries, skipping those which do not exist, // collecting up to count entries. Non-existent entries are // skipped but not deleted because they might be for other // platforms. Vector v = new Vector<>(); for (int i = 0; i < entries.size() && v.size() < count; i++) { File f = entries.elementAt(i); if (f.exists()) v.add(f); } File[] e = new File[v.size()]; v.copyInto(e); return e; } /** * Get the latest valid entry from a file history object. An entry * is valid if it identifies a file that exists on the current system. * @return the latest valid entry from afile history object, or null * if none found. */ public File getLatestEntry() { ensureEntriesUpToDate(); // scan the entries, skipping those which do not exist, // looking for the first entry. Non-existent entries are // skipped but not deleted because they might be for other // platforms. for (int i = 0; i < entries.size(); i++) { File f = entries.elementAt(i); if (f.exists()) return f; } return null; } public File getRelativeLatestEntry(String newRoot, String oldRoot) { ensureEntriesUpToDate(); for (int i = 0; i < entries.size(); i++) { File f = entries.elementAt(i); if (f.exists()) { return f; } else { String sf = f.getPath(); String[] diff = WorkDirectory.getDiffInPaths(newRoot, oldRoot); if (diff != null) { File toCheck = new File(diff[0] + sf.substring(diff[1].length())); if (toCheck.exists()) { return toCheck; } } } } return null; } private FileHistory(WorkDirectory workDir, String name) { workDirRef = new WeakReference<>(workDir); // just used for logging errors this.name = name; historyFile = workDir.getSystemFile(name); } private void ensureEntriesUpToDate() { if (entries == null || historyFile.lastModified() > historyFileLastModified) readEntries(); } private void readEntries() { if (entries == null) entries = new Vector<>(); else entries.clear(); if (historyFile.exists()) { try { BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(historyFile), StandardCharsets.UTF_8)); String line; while ((line = br.readLine()) != null) { String p = line.trim(); if (p.length() == 0 || p.startsWith("#")) continue; entries.add(new File(p)); } br.close(); } catch (IOException e) { WorkDirectory workDir = workDirRef.get(); workDir.log(i18n, "fh.cantRead", new Object[] { name, e } ); } historyFileLastModified = historyFile.lastModified(); } } private void writeEntries() { try { BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(historyFile), StandardCharsets.UTF_8)); bw.write("# Configuration File History"); bw.newLine(); bw.write("# written at " + (new Date())); bw.newLine(); for (int i = 0; i < entries.size(); i++) { bw.write(entries.elementAt(i).toString()); bw.newLine(); } bw.close(); } catch (IOException e) { WorkDirectory workDir = workDirRef.get(); workDir.log(i18n, "fh.cantWrite", new Object[] { name, e } ); } historyFileLastModified = historyFile.lastModified(); } /** * A class that will dynamically add the latest entries for a * FileHistory onto a menu. To do this, an instance of this class * should be added to the menu with * {@link javax.swing.JMenu#addMenuListener addMenuListener}. */ public static class Listener implements MenuListener { /** * Create a Listener that can be used to dynamically add the * latest entries from a FileHistory onto a menu. The dynamic * entries will be added to the end of the menu when it is * selected. Any previous values added by this listener * will automatically be removed. * @param l An ActionListener that will be notified when * any of the dynamic menu entries are invoked. When this * action listener is notified, the action command will be * the path of the file. The corresponding File object will * be registered on the source as a client property named * FILE. */ public Listener(ActionListener l) { this(null, -1, l); } /** * Create a Listener that can be used to dynamically add the * latest entries from a FileHistory onto a menu. * Any previous values added by this listener will automatically * be removed. * @param o The position in the menu at which to insert the * dynamic entries. * @param l An ActionListener that will be notified when * any of the dynamic menu entries are invoked. When this * action listener is notified, the action command will be * the path of the file. The corresponding File object will * be registered on the source as a client property named * FILE. */ public Listener(int o, ActionListener l) { this(null, o, l); } /** * Create a Listener that can be used to dynamically add the * latest entries from a FileHistory onto a menu. * Any previous values added by this listener will automatically * be removed. * @param h The FileHistory from which to determine the * entries to be added. * @param o The position in the menu at which to insert the * dynamic entries. * @param l An ActionListener that will be notified when * any of the dynamic menu entries are invoked. When this * action listener is notified, the action command will be * the path of the file. The corresponding File object will * be registered on the source as a client property named * FILE. */ public Listener(FileHistory h, int o, ActionListener l) { history = h; offset = o; clientListener = l; } /** * Get the FileHistory object from which to obtain the dynamic menu * entries. * @return the FileHistory object from which to obtain the dynamic menu * entries * @see #setFileHistory */ public FileHistory getFileHistory() { return history; } /** * Specify the FileHistory object from which to obtain the dynamic menu * entries. * @param h the FileHistory object from which to obtain the dynamic menu * entries * @see #getFileHistory */ public void setFileHistory(FileHistory h) { history = h; } public void menuSelected(MenuEvent e) { // Add the recent entries, or a disabled marker if none JMenu menu = (JMenu) (e.getSource()); File[] entries = (history == null ? null : history.getRecentEntries(5)); if (entries == null || entries.length == 0) { JMenuItem noEntries = new JMenuItem(i18n.getString("fh.empty")); noEntries.putClientProperty(FILE_HISTORY, this); noEntries.setEnabled(false); if (offset < 0) menu.add(noEntries); else menu.insert(noEntries, offset); } else { for (int i = 0; i < entries.length; i++) { JMenuItem mi = new JMenuItem(i + " " + entries[i].getPath()); mi.setActionCommand(entries[i].getPath()); mi.addActionListener(clientListener); mi.putClientProperty(FILE, entries[i]); mi.putClientProperty(FILE_HISTORY, this); mi.setMnemonic('0' + i); if (offset < 0) menu.add(mi); else menu.insert(mi, offset + i); } } } public void menuDeselected(MenuEvent e) { removeDynamicEntries((JMenu) (e.getSource())); } public void menuCanceled(MenuEvent e) { removeDynamicEntries((JMenu) (e.getSource())); } private void removeDynamicEntries(JMenu menu) { // Clear out any old menu items previously added by this // menu listener; remove them from bottom up because // removing an item affects index of subsequent items for (int i = menu.getItemCount() -1; i >= 0; i--) { JMenuItem mi = menu.getItem(i); if (mi != null && mi.getClientProperty(FILE_HISTORY) == this) menu.remove(mi); } } private FileHistory history; private int offset; private ActionListener clientListener; } private WeakReference workDirRef; private String name; private File historyFile; private long historyFileLastModified; private Vector entries; /** * The name of the client property used to access the File that identifies * which dynamically added menu entry has been selected. * @see Listener */ public static final String FILE = "file"; private static WeakHashMap> cache; private static final String FILE_HISTORY = "fileHistory"; private static I18NResourceBundle i18n = I18NResourceBundle.getBundleForClass(FileHistory.class); }