blob: 744ef79697955e4ff97fe45fb9cf06b6f6a8ebd5 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2006 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.jdt.internal.corext.util;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.util.Collection;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.TransformerFactoryConfigurationError;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.jdt.internal.corext.CorextMessages;
import org.eclipse.jdt.internal.ui.JavaPlugin;
import org.eclipse.jdt.internal.ui.JavaUIException;
import org.eclipse.jdt.internal.ui.JavaUIStatus;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
/**
* History stores a list of key, object pairs. The list is bounded at size
* MAX_HISTORY_SIZE. If the list exceeds this size the eldest element is removed
* from the list. An element can be added/renewed with a call to <code>accessed(Object)</code>.
*
* The history can be stored to/loaded from an xml file.
*/
public abstract class History {
private static final String DEFAULT_ROOT_NODE_NAME= "histroyRootNode"; //$NON-NLS-1$
private static final String DEFAULT_INFO_NODE_NAME= "infoNode"; //$NON-NLS-1$
private static final int MAX_HISTORY_SIZE= 60;
private static JavaUIException createException(Throwable t, String message) {
return new JavaUIException(JavaUIStatus.createError(IStatus.ERROR, message, t));
}
private final Map fHistory;
private final Hashtable fPositions;
private final String fFileName;
private final String fRootNodeName;
private final String fInfoNodeName;
public History(String fileName, String rootNodeName, String infoNodeName) {
fHistory= new LinkedHashMap(80, 0.75f, true) {
private static final long serialVersionUID= 1L;
protected boolean removeEldestEntry(Map.Entry eldest) {
return size() > MAX_HISTORY_SIZE;
}
};
fFileName= fileName;
fRootNodeName= rootNodeName;
fInfoNodeName= infoNodeName;
fPositions= new Hashtable(MAX_HISTORY_SIZE);
}
public History(String fileName) {
this(fileName, DEFAULT_ROOT_NODE_NAME, DEFAULT_INFO_NODE_NAME);
}
public synchronized void accessed(Object object) {
fHistory.put(getKey(object), object);
rebuildPositions();
}
public synchronized boolean contains(Object object) {
return fHistory.containsKey(getKey(object));
}
public synchronized boolean containsKey(Object key) {
return fHistory.containsKey(key);
}
public synchronized boolean isEmpty() {
return fHistory.isEmpty();
}
public synchronized Object remove(Object object) {
Object removed= fHistory.remove(getKey(object));
rebuildPositions();
return removed;
}
public synchronized Object removeKey(Object key) {
Object removed= fHistory.remove(key);
rebuildPositions();
return removed;
}
/**
* Normalized position in history of object denoted by key.
* The position is a value between zero and one where zero
* means not contained in history and one means newest element
* in history. The lower the value the older the element.
*
* @param key The key of the object to inspect
* @return value in [0.0, 1.0] the lower the older the element
*/
public synchronized float getNormalizedPosition(Object key) {
if (!containsKey(key))
return 0.0f;
int pos= ((Integer)fPositions.get(key)).intValue() + 1;
//containsKey(key) implies fHistory.size()>0
return (float)pos / (float)fHistory.size();
}
/**
* Absolute position of object denoted by key in the
* history or -1 if !containsKey(key). The higher the
* newer.
*
* @param key The key of the object to inspect
* @return value between 0 and MAX_HISTORY_SIZE - 1, or -1
*/
public synchronized int getPosition(Object key) {
if (!containsKey(key))
return -1;
return ((Integer)fPositions.get(key)).intValue();
}
public synchronized void load() {
IPath stateLocation= JavaPlugin.getDefault().getStateLocation().append(fFileName);
File file= new File(stateLocation.toOSString());
if (file.exists()) {
InputStreamReader reader= null;
try {
reader = new InputStreamReader(new FileInputStream(file), "utf-8");//$NON-NLS-1$
load(new InputSource(reader));
} catch (IOException e) {
JavaPlugin.log(e);
} catch (CoreException e) {
JavaPlugin.log(e);
} finally {
try {
if (reader != null)
reader.close();
} catch (IOException e) {
JavaPlugin.log(e);
}
}
}
}
public synchronized void save() {
IPath stateLocation= JavaPlugin.getDefault().getStateLocation().append(fFileName);
File file= new File(stateLocation.toOSString());
OutputStream out= null;
try {
out= new FileOutputStream(file);
save(out);
} catch (IOException e) {
JavaPlugin.log(e);
} catch (CoreException e) {
JavaPlugin.log(e);
} catch (TransformerFactoryConfigurationError e) {
// The XML library can be misconficgured (e.g. via
// -Djava.endorsed.dirs=C:\notExisting\xerces-2_7_1)
JavaPlugin.log(e);
} finally {
try {
if (out != null) {
out.close();
}
} catch (IOException e) {
JavaPlugin.log(e);
}
}
}
protected Set getKeys() {
return fHistory.keySet();
}
protected Collection getValues() {
return fHistory.values();
}
/**
* Store <code>Object</code> in <code>Element</code>
*
* @param object The object to store
* @param element The Element to store to
*/
protected abstract void setAttributes(Object object, Element element);
/**
* Return a new instance of an Object given <code>element</code>
*
* @param element The element containing required information to create the Object
*/
protected abstract Object createFromElement(Element element);
/**
* Get key for object
*
* @param object The object to calculate a key for, not null
* @return The key for object, not null
*/
protected abstract Object getKey(Object object);
private void rebuildPositions() {
fPositions.clear();
Collection values= fHistory.values();
int pos=0;
for (Iterator iter= values.iterator(); iter.hasNext();) {
Object element= iter.next();
fPositions.put(getKey(element), new Integer(pos));
pos++;
}
}
private void load(InputSource inputSource) throws CoreException {
Element root;
try {
DocumentBuilder parser = DocumentBuilderFactory.newInstance().newDocumentBuilder();
root = parser.parse(inputSource).getDocumentElement();
} catch (SAXException e) {
throw createException(e, Messages.format(CorextMessages.History_error_read, fFileName));
} catch (ParserConfigurationException e) {
throw createException(e, Messages.format(CorextMessages.History_error_read, fFileName));
} catch (IOException e) {
throw createException(e, Messages.format(CorextMessages.History_error_read, fFileName));
}
if (root == null) return;
if (!root.getNodeName().equalsIgnoreCase(fRootNodeName)) {
return;
}
NodeList list= root.getChildNodes();
int length= list.getLength();
for (int i= 0; i < length; ++i) {
Node node= list.item(i);
if (node.getNodeType() == Node.ELEMENT_NODE) {
Element type= (Element) node;
if (type.getNodeName().equalsIgnoreCase(fInfoNodeName)) {
Object object= createFromElement(type);
fHistory.put(getKey(object), object);
}
}
}
rebuildPositions();
}
private void save(OutputStream stream) throws CoreException {
try {
DocumentBuilderFactory factory= DocumentBuilderFactory.newInstance();
DocumentBuilder builder= factory.newDocumentBuilder();
Document document= builder.newDocument();
Element rootElement = document.createElement(fRootNodeName);
document.appendChild(rootElement);
Iterator values= getValues().iterator();
while (values.hasNext()) {
Object object= values.next();
Element element= document.createElement(fInfoNodeName);
setAttributes(object, element);
rootElement.appendChild(element);
}
Transformer transformer=TransformerFactory.newInstance().newTransformer();
transformer.setOutputProperty(OutputKeys.METHOD, "xml"); //$NON-NLS-1$
transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8"); //$NON-NLS-1$
transformer.setOutputProperty(OutputKeys.INDENT, "yes"); //$NON-NLS-1$
DOMSource source = new DOMSource(document);
StreamResult result = new StreamResult(stream);
transformer.transform(source, result);
} catch (TransformerException e) {
throw createException(e, Messages.format(CorextMessages.History_error_serialize, fFileName));
} catch (ParserConfigurationException e) {
throw createException(e, Messages.format(CorextMessages.History_error_serialize, fFileName));
}
}
}