/* * Copyright (c) 2012, the Dart project authors. * * Licensed under the Eclipse Public License v1.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.eclipse.org/legal/epl-v10.html * * 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 com.google.dart.tools.internal.corext.util; import com.google.dart.tools.ui.DartToolsPlugin; import com.google.dart.tools.ui.Messages; import com.google.dart.tools.ui.internal.DartUiException; import com.google.dart.tools.ui.internal.DartUiStatus; import com.google.dart.tools.ui.internal.util.CorextMessages; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.IStatus; 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; import org.xml.sax.helpers.DefaultHandler; 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; /** * 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 DartUiException createException(Throwable t, String message) { return new DartUiException(DartUiStatus.createError(IStatus.ERROR, message, t)); } private final Map<Object, Object> fHistory; private final Hashtable<Object, Integer> fPositions; private final String fFileName; private final String fRootNodeName; private final String fInfoNodeName; public History(String fileName) { this(fileName, DEFAULT_ROOT_NODE_NAME, DEFAULT_INFO_NODE_NAME); } public History(String fileName, String rootNodeName, String infoNodeName) { fHistory = new LinkedHashMap<Object, Object>(80, 0.75f, true) { private static final long serialVersionUID = 1L; @Override protected boolean removeEldestEntry(Map.Entry<Object, Object> eldest) { return size() > MAX_HISTORY_SIZE; } }; fFileName = fileName; fRootNodeName = rootNodeName; fInfoNodeName = infoNodeName; fPositions = new Hashtable<Object, Integer>(MAX_HISTORY_SIZE); } 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); } /** * 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 = 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 fPositions.get(key).intValue(); } public synchronized boolean isEmpty() { return fHistory.isEmpty(); } public synchronized void load() { IPath stateLocation = DartToolsPlugin.getDefault().getStateLocation().append(fFileName); File file = stateLocation.toFile(); 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) { DartToolsPlugin.log(e); } catch (CoreException e) { DartToolsPlugin.log(e); } finally { try { if (reader != null) { reader.close(); } } catch (IOException e) { DartToolsPlugin.log(e); } } } } 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; } public synchronized void save() { IPath stateLocation = DartToolsPlugin.getDefault().getStateLocation().append(fFileName); File file = stateLocation.toFile(); OutputStream out = null; try { out = new FileOutputStream(file); save(out); } catch (IOException e) { DartToolsPlugin.log(e); } catch (CoreException e) { DartToolsPlugin.log(e); } catch (TransformerFactoryConfigurationError e) { // The XML library can be misconficgured (e.g. via // -Djava.endorsed.dirs=C:\notExisting\xerces-2_7_1) DartToolsPlugin.log(e); } finally { try { if (out != null) { out.close(); } } catch (IOException e) { DartToolsPlugin.log(e); } } } /** * Return a new instance of an Object given <code>element</code> * * @param element The element containing required information to create the Object * @return return a new instance of an Object given <code>element</code> */ 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); protected Set<Object> getKeys() { return fHistory.keySet(); } protected Collection<Object> 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); private void load(InputSource inputSource) throws CoreException { Element root; try { DocumentBuilder parser = DocumentBuilderFactory.newInstance().newDocumentBuilder(); parser.setErrorHandler(new DefaultHandler()); 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); if (object != null) { fHistory.put(getKey(object), object); } } } } rebuildPositions(); } private void rebuildPositions() { fPositions.clear(); Collection<Object> values = fHistory.values(); int pos = 0; for (Iterator<Object> iter = values.iterator(); iter.hasNext();) { Object element = iter.next(); fPositions.put(getKey(element), new Integer(pos)); pos++; } } 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<Object> 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)); } } }