1 /******************************************************************************* 2 * Copyright (c) 2000, 2016 IBM Corporation and others. 3 * 4 * This program and the accompanying materials 5 * are made available under the terms of the Eclipse Public License 2.0 6 * which accompanies this distribution, and is available at 7 * https://www.eclipse.org/legal/epl-2.0/ 8 * 9 * SPDX-License-Identifier: EPL-2.0 10 * 11 * Contributors: 12 * IBM Corporation - initial API and implementation 13 *******************************************************************************/ 14 package org.eclipse.jdt.internal.corext.util; 15 16 import java.io.File; 17 import java.io.FileInputStream; 18 import java.io.FileOutputStream; 19 import java.io.IOException; 20 import java.io.InputStreamReader; 21 import java.io.OutputStream; 22 import java.util.Collection; 23 import java.util.Hashtable; 24 import java.util.Iterator; 25 import java.util.LinkedHashMap; 26 import java.util.Map; 27 import java.util.Set; 28 29 import javax.xml.parsers.DocumentBuilder; 30 import javax.xml.parsers.DocumentBuilderFactory; 31 import javax.xml.parsers.ParserConfigurationException; 32 import javax.xml.transform.OutputKeys; 33 import javax.xml.transform.Transformer; 34 import javax.xml.transform.TransformerException; 35 import javax.xml.transform.TransformerFactory; 36 import javax.xml.transform.TransformerFactoryConfigurationError; 37 import javax.xml.transform.dom.DOMSource; 38 import javax.xml.transform.stream.StreamResult; 39 40 import org.xml.sax.InputSource; 41 import org.xml.sax.SAXException; 42 import org.xml.sax.helpers.DefaultHandler; 43 44 import org.w3c.dom.Document; 45 import org.w3c.dom.Element; 46 import org.w3c.dom.Node; 47 import org.w3c.dom.NodeList; 48 49 import org.eclipse.core.runtime.CoreException; 50 import org.eclipse.core.runtime.IPath; 51 import org.eclipse.core.runtime.IStatus; 52 53 import org.eclipse.jdt.internal.corext.CorextMessages; 54 55 import org.eclipse.jdt.internal.ui.JavaPlugin; 56 import org.eclipse.jdt.internal.ui.JavaUIException; 57 import org.eclipse.jdt.internal.ui.JavaUIStatus; 58 import org.eclipse.jdt.internal.core.manipulation.util.BasicElementLabels; 59 60 /** 61 * History stores a list of key, object pairs. The list is bounded at size 62 * MAX_HISTORY_SIZE. If the list exceeds this size the eldest element is removed 63 * from the list. An element can be added/renewed with a call to <code>accessed(Object)</code>. 64 * 65 * The history can be stored to/loaded from an xml file. 66 * 67 * @param <K> key type 68 * @param <V> value type 69 */ 70 public abstract class History<K, V> { 71 72 private static final String DEFAULT_ROOT_NODE_NAME= "histroyRootNode"; //$NON-NLS-1$ 73 private static final String DEFAULT_INFO_NODE_NAME= "infoNode"; //$NON-NLS-1$ 74 private static final int MAX_HISTORY_SIZE= 60; 75 createException(Throwable t, String message)76 private static JavaUIException createException(Throwable t, String message) { 77 return new JavaUIException(JavaUIStatus.createError(IStatus.ERROR, message, t)); 78 } 79 80 private final Map<K, V> fHistory; 81 private final Hashtable<K, Integer> fPositions; 82 private final String fFileName; 83 private final String fRootNodeName; 84 private final String fInfoNodeName; 85 History(String fileName, String rootNodeName, String infoNodeName)86 public History(String fileName, String rootNodeName, String infoNodeName) { 87 fHistory= new LinkedHashMap<K, V>(80, 0.75f, true) { 88 private static final long serialVersionUID= 1L; 89 @Override 90 protected boolean removeEldestEntry(Map.Entry<K, V> eldest) { 91 return size() > MAX_HISTORY_SIZE; 92 } 93 }; 94 fFileName= fileName; 95 fRootNodeName= rootNodeName; 96 fInfoNodeName= infoNodeName; 97 fPositions= new Hashtable<>(MAX_HISTORY_SIZE); 98 } 99 History(String fileName)100 public History(String fileName) { 101 this(fileName, DEFAULT_ROOT_NODE_NAME, DEFAULT_INFO_NODE_NAME); 102 } 103 accessed(V object)104 public synchronized void accessed(V object) { 105 fHistory.put(getKey(object), object); 106 rebuildPositions(); 107 } 108 contains(V object)109 public synchronized boolean contains(V object) { 110 return fHistory.containsKey(getKey(object)); 111 } 112 containsKey(K key)113 public synchronized boolean containsKey(K key) { 114 return fHistory.containsKey(key); 115 } 116 isEmpty()117 public synchronized boolean isEmpty() { 118 return fHistory.isEmpty(); 119 } 120 remove(V object)121 public synchronized Object remove(V object) { 122 Object removed= fHistory.remove(getKey(object)); 123 rebuildPositions(); 124 return removed; 125 } 126 removeKey(Object key)127 public synchronized Object removeKey(Object key) { 128 Object removed= fHistory.remove(key); 129 rebuildPositions(); 130 return removed; 131 } 132 133 /** 134 * Normalized position in history of object denoted by key. 135 * The position is a value between zero and one where zero 136 * means not contained in history and one means newest element 137 * in history. The lower the value the older the element. 138 * 139 * @param key The key of the object to inspect 140 * @return value in [0.0, 1.0] the lower the older the element 141 */ getNormalizedPosition(K key)142 public synchronized float getNormalizedPosition(K key) { 143 if (!containsKey(key)) 144 return 0.0f; 145 146 int pos= fPositions.get(key).intValue() + 1; 147 148 //containsKey(key) implies fHistory.size()>0 149 return (float)pos / (float)fHistory.size(); 150 } 151 152 /** 153 * Absolute position of object denoted by key in the 154 * history or -1 if !containsKey(key). The higher the 155 * newer. 156 * 157 * @param key The key of the object to inspect 158 * @return value between 0 and MAX_HISTORY_SIZE - 1, or -1 159 */ getPosition(K key)160 public synchronized int getPosition(K key) { 161 if (!containsKey(key)) 162 return -1; 163 164 return fPositions.get(key).intValue(); 165 } 166 load()167 public synchronized void load() { 168 IPath stateLocation= JavaPlugin.getDefault().getStateLocation().append(fFileName); 169 File file= stateLocation.toFile(); 170 if (file.exists()) { 171 InputStreamReader reader= null; 172 try { 173 reader = new InputStreamReader(new FileInputStream(file), "utf-8");//$NON-NLS-1$ 174 load(new InputSource(reader)); 175 } catch (IOException e) { 176 JavaPlugin.log(e); 177 } catch (CoreException e) { 178 JavaPlugin.log(e); 179 } finally { 180 try { 181 if (reader != null) 182 reader.close(); 183 } catch (IOException e) { 184 JavaPlugin.log(e); 185 } 186 } 187 } 188 } 189 save()190 public synchronized void save() { 191 IPath stateLocation= JavaPlugin.getDefault().getStateLocation().append(fFileName); 192 File file= stateLocation.toFile(); 193 OutputStream out= null; 194 try { 195 out= new FileOutputStream(file); 196 save(out); 197 } catch (IOException e) { 198 JavaPlugin.log(e); 199 } catch (CoreException e) { 200 JavaPlugin.log(e); 201 } catch (TransformerFactoryConfigurationError e) { 202 // The XML library can be misconficgured (e.g. via 203 // -Djava.endorsed.dirs=C:\notExisting\xerces-2_7_1) 204 JavaPlugin.log(e); 205 } finally { 206 try { 207 if (out != null) { 208 out.close(); 209 } 210 } catch (IOException e) { 211 JavaPlugin.log(e); 212 } 213 } 214 } 215 getKeys()216 protected Set<K> getKeys() { 217 return fHistory.keySet(); 218 } 219 getValues()220 protected Collection<V> getValues() { 221 return fHistory.values(); 222 } 223 224 /** 225 * Store <code>Object</code> in <code>Element</code> 226 * 227 * @param object The object to store 228 * @param element The Element to store to 229 */ setAttributes(Object object, Element element)230 protected abstract void setAttributes(Object object, Element element); 231 232 /** 233 * Return a new instance of an Object given <code>element</code> 234 * 235 * @param element The element containing required information to create the Object 236 * @return return a new instance of an Object given <code>element</code> 237 */ createFromElement(Element element)238 protected abstract V createFromElement(Element element); 239 240 /** 241 * Get key for object 242 * 243 * @param object The object to calculate a key for, not null 244 * @return The key for object, not null 245 */ getKey(V object)246 protected abstract K getKey(V object); 247 rebuildPositions()248 private void rebuildPositions() { 249 fPositions.clear(); 250 int pos=0; 251 for (V element : fHistory.values()) { 252 fPositions.put(getKey(element), Integer.valueOf(pos)); 253 pos++; 254 } 255 } 256 load(InputSource inputSource)257 private void load(InputSource inputSource) throws CoreException { 258 Element root; 259 try { 260 DocumentBuilder parser = DocumentBuilderFactory.newInstance().newDocumentBuilder(); 261 parser.setErrorHandler(new DefaultHandler()); 262 root = parser.parse(inputSource).getDocumentElement(); 263 } catch (SAXException e) { 264 throw createException(e, Messages.format(CorextMessages.History_error_read, BasicElementLabels.getResourceName(fFileName))); 265 } catch (ParserConfigurationException e) { 266 throw createException(e, Messages.format(CorextMessages.History_error_read, BasicElementLabels.getResourceName(fFileName))); 267 } catch (IOException e) { 268 throw createException(e, Messages.format(CorextMessages.History_error_read, BasicElementLabels.getResourceName(fFileName))); 269 } 270 271 if (root == null) return; 272 if (!root.getNodeName().equalsIgnoreCase(fRootNodeName)) { 273 return; 274 } 275 NodeList list= root.getChildNodes(); 276 int length= list.getLength(); 277 for (int i= 0; i < length; ++i) { 278 Node node= list.item(i); 279 if (node.getNodeType() == Node.ELEMENT_NODE) { 280 Element type= (Element) node; 281 if (type.getNodeName().equalsIgnoreCase(fInfoNodeName)) { 282 V object= createFromElement(type); 283 if (object != null) { 284 fHistory.put(getKey(object), object); 285 } 286 } 287 } 288 } 289 rebuildPositions(); 290 } 291 save(OutputStream stream)292 private void save(OutputStream stream) throws CoreException { 293 try { 294 DocumentBuilderFactory factory= DocumentBuilderFactory.newInstance(); 295 DocumentBuilder builder= factory.newDocumentBuilder(); 296 Document document= builder.newDocument(); 297 298 Element rootElement = document.createElement(fRootNodeName); 299 document.appendChild(rootElement); 300 301 Iterator<V> values= getValues().iterator(); 302 while (values.hasNext()) { 303 Object object= values.next(); 304 Element element= document.createElement(fInfoNodeName); 305 setAttributes(object, element); 306 rootElement.appendChild(element); 307 } 308 309 Transformer transformer=TransformerFactory.newInstance().newTransformer(); 310 transformer.setOutputProperty(OutputKeys.METHOD, "xml"); //$NON-NLS-1$ 311 transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8"); //$NON-NLS-1$ 312 transformer.setOutputProperty(OutputKeys.INDENT, "yes"); //$NON-NLS-1$ 313 DOMSource source = new DOMSource(document); 314 StreamResult result = new StreamResult(stream); 315 316 transformer.transform(source, result); 317 } catch (TransformerException e) { 318 throw createException(e, Messages.format(CorextMessages.History_error_serialize, BasicElementLabels.getResourceName(fFileName))); 319 } catch (ParserConfigurationException e) { 320 throw createException(e, Messages.format(CorextMessages.History_error_serialize, BasicElementLabels.getResourceName(fFileName))); 321 } 322 } 323 324 } 325