blob: 0c0520dfe01b3c4586064ae82b92c8e8f07da635 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2017 IBM Corporation and others.
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* SPDX-License-Identifier: EPL-2.0
*
*******************************************************************************/
package org.eclipse.dltk.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.nio.charset.StandardCharsets;
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.dltk.core.DLTKCore;
import org.eclipse.dltk.internal.ui.DLTKUIException;
import org.eclipse.dltk.internal.ui.DLTKUIStatus;
import org.eclipse.dltk.ui.DLTKUIPlugin;
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 DLTKUIException createException(Throwable t,
String message) {
return new DLTKUIException(
DLTKUIStatus.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;
@Override
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 = DLTKUIPlugin.getDefault().getStateLocation()
.append(fFileName);
File file = new File(stateLocation.toOSString());
if (file.exists()) {
try (InputStreamReader reader = new InputStreamReader(
new FileInputStream(file), StandardCharsets.UTF_8)) {
load(new InputSource(reader));
} catch (IOException e) {
DLTKUIPlugin.log(e);
} catch (CoreException e) {
DLTKUIPlugin.log(e);
}
}
}
public synchronized void save() {
IPath stateLocation = DLTKUIPlugin.getDefault().getStateLocation()
.append(fFileName);
File file = new File(stateLocation.toOSString());
try (OutputStream out = new FileOutputStream(file)) {
save(out);
} catch (IOException e) {
DLTKUIPlugin.log(e);
} catch (CoreException e) {
DLTKUIPlugin.log(e);
} catch (TransformerFactoryConfigurationError e) {
// The XML library can be misconficgured (e.g. via
// -Djava.endorsed.dirs=C:\notExisting\xerces-2_7_1)
DLTKUIPlugin.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();
if (element == null) {
continue;
}
Object key = getKey(element);
fPositions.put(key, Integer.valueOf(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)) {
try {
Object object = createFromElement(type);
if (object != null) {
fHistory.put(getKey(object), object);
}
} catch (Exception me) {
if (DLTKCore.DEBUG) {
me.printStackTrace();
}
}
}
}
}
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));
}
}
}