KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > eclipse > jdt > internal > ui > text > java > ContentAssistHistory


1 /*******************************************************************************
2  * Copyright (c) 2005, 2006 IBM Corporation and others.
3  * All rights reserved. This program and the accompanying materials
4  * are made available under the terms of the Eclipse Public License v1.0
5  * which accompanies this distribution, and is available at
6  * http://www.eclipse.org/legal/epl-v10.html
7  *
8  * Contributors:
9  * IBM Corporation - initial API and implementation
10  *******************************************************************************/

11 package org.eclipse.jdt.internal.ui.text.java;
12
13 import java.io.IOException JavaDoc;
14 import java.io.StringReader JavaDoc;
15 import java.io.StringWriter JavaDoc;
16 import java.util.ArrayList JavaDoc;
17 import java.util.Collections JavaDoc;
18 import java.util.HashMap JavaDoc;
19 import java.util.HashSet JavaDoc;
20 import java.util.Iterator JavaDoc;
21 import java.util.LinkedHashMap JavaDoc;
22 import java.util.LinkedHashSet JavaDoc;
23 import java.util.List JavaDoc;
24 import java.util.Map JavaDoc;
25 import java.util.Set JavaDoc;
26 import java.util.Map.Entry;
27
28 import javax.xml.parsers.DocumentBuilder JavaDoc;
29 import javax.xml.parsers.DocumentBuilderFactory JavaDoc;
30 import javax.xml.parsers.ParserConfigurationException JavaDoc;
31 import javax.xml.transform.OutputKeys JavaDoc;
32 import javax.xml.transform.Transformer JavaDoc;
33 import javax.xml.transform.TransformerException JavaDoc;
34 import javax.xml.transform.TransformerFactory JavaDoc;
35 import javax.xml.transform.dom.DOMSource JavaDoc;
36 import javax.xml.transform.stream.StreamResult JavaDoc;
37
38 import org.eclipse.core.runtime.Assert;
39 import org.eclipse.core.runtime.CoreException;
40 import org.eclipse.core.runtime.IProgressMonitor;
41 import org.eclipse.core.runtime.IStatus;
42 import org.eclipse.core.runtime.NullProgressMonitor;
43 import org.eclipse.core.runtime.Preferences;
44
45
46 import org.eclipse.jdt.core.Flags;
47 import org.eclipse.jdt.core.IType;
48 import org.eclipse.jdt.core.ITypeHierarchy;
49 import org.eclipse.jdt.core.JavaModelException;
50
51 import org.eclipse.jdt.internal.ui.JavaPlugin;
52 import org.eclipse.jdt.internal.ui.JavaUIException;
53 import org.eclipse.jdt.internal.ui.JavaUIStatus;
54
55 import org.w3c.dom.Document JavaDoc;
56 import org.w3c.dom.Element JavaDoc;
57 import org.w3c.dom.Node JavaDoc;
58 import org.w3c.dom.NodeList JavaDoc;
59 import org.xml.sax.InputSource JavaDoc;
60 import org.xml.sax.SAXException JavaDoc;
61
62 /**
63  * An LRU cache for code assist.
64  *
65  * @since 3.2
66  */

67 public final class ContentAssistHistory {
68     /**
69      * Persistence implementation.
70      *
71      * @since 3.2
72      */

73     private static final class ReaderWriter {
74
75         private static final String JavaDoc NODE_ROOT= "history"; //$NON-NLS-1$
76
private static final String JavaDoc NODE_LHS= "lhs"; //$NON-NLS-1$
77
private static final String JavaDoc NODE_RHS= "rhs"; //$NON-NLS-1$
78
private static final String JavaDoc ATTRIBUTE_NAME= "name"; //$NON-NLS-1$
79
private static final String JavaDoc ATTRIBUTE_MAX_LHS= "maxLHS"; //$NON-NLS-1$
80
private static final String JavaDoc ATTRIBUTE_MAX_RHS= "maxRHS"; //$NON-NLS-1$
81

82         public void store(ContentAssistHistory history, StreamResult JavaDoc result) throws CoreException {
83             try {
84                 DocumentBuilderFactory JavaDoc factory= DocumentBuilderFactory.newInstance();
85                 DocumentBuilder JavaDoc builder= factory.newDocumentBuilder();
86                 Document JavaDoc document= builder.newDocument();
87                 
88                 Element JavaDoc rootElement = document.createElement(NODE_ROOT);
89                 rootElement.setAttribute(ATTRIBUTE_MAX_LHS, Integer.toString(history.fMaxLHS));
90                 rootElement.setAttribute(ATTRIBUTE_MAX_RHS, Integer.toString(history.fMaxRHS));
91                 document.appendChild(rootElement);
92         
93                 for (Iterator JavaDoc leftHandSides= history.fLHSCache.keySet().iterator(); leftHandSides.hasNext();) {
94                     String JavaDoc lhs= (String JavaDoc) leftHandSides.next();
95                     Element JavaDoc lhsElement= document.createElement(NODE_LHS);
96                     lhsElement.setAttribute(ATTRIBUTE_NAME, lhs);
97                     rootElement.appendChild(lhsElement);
98                     
99                     Set JavaDoc rightHandSides= (Set JavaDoc) history.fLHSCache.get(lhs);
100                     for (Iterator JavaDoc rhsIterator= rightHandSides.iterator(); rhsIterator.hasNext();) {
101                         String JavaDoc rhs= (String JavaDoc) rhsIterator.next();
102                         Element JavaDoc rhsElement= document.createElement(NODE_RHS);
103                         rhsElement.setAttribute(ATTRIBUTE_NAME, rhs);
104                         lhsElement.appendChild(rhsElement);
105                     }
106                 }
107                 
108                 Transformer JavaDoc transformer=TransformerFactory.newInstance().newTransformer();
109                 transformer.setOutputProperty(OutputKeys.METHOD, "xml"); //$NON-NLS-1$
110
transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8"); //$NON-NLS-1$
111
transformer.setOutputProperty(OutputKeys.INDENT, "no"); //$NON-NLS-1$
112
DOMSource JavaDoc source = new DOMSource JavaDoc(document);
113
114                 transformer.transform(source, result);
115             } catch (TransformerException JavaDoc e) {
116                 throw createException(e, JavaTextMessages.ContentAssistHistory_serialize_error);
117             } catch (ParserConfigurationException JavaDoc e) {
118                 throw createException(e, JavaTextMessages.ContentAssistHistory_serialize_error);
119             }
120         }
121         
122         public ContentAssistHistory load(InputSource JavaDoc source) throws CoreException {
123             Element JavaDoc root;
124             try {
125                 DocumentBuilder JavaDoc parser = DocumentBuilderFactory.newInstance().newDocumentBuilder();
126                 root = parser.parse(source).getDocumentElement();
127             } catch (SAXException JavaDoc e) {
128                 throw createException(e, JavaTextMessages.ContentAssistHistory_deserialize_error);
129             } catch (ParserConfigurationException JavaDoc e) {
130                 throw createException(e, JavaTextMessages.ContentAssistHistory_deserialize_error);
131             } catch (IOException JavaDoc e) {
132                 throw createException(e, JavaTextMessages.ContentAssistHistory_deserialize_error);
133             }
134             
135             if (root == null || !root.getNodeName().equalsIgnoreCase(NODE_ROOT))
136                 return null;
137             
138             int maxLHS= parseNaturalInt(root.getAttribute(ATTRIBUTE_MAX_LHS), DEFAULT_TRACKED_LHS);
139             int maxRHS= parseNaturalInt(root.getAttribute(ATTRIBUTE_MAX_RHS), DEFAULT_TRACKED_RHS);
140             
141             ContentAssistHistory history= new ContentAssistHistory(maxLHS, maxRHS);
142             
143             NodeList JavaDoc list= root.getChildNodes();
144             int length= list.getLength();
145             for (int i= 0; i < length; ++i) {
146                 Node JavaDoc lhsNode= list.item(i);
147                 if (lhsNode.getNodeType() == Node.ELEMENT_NODE) {
148                     Element JavaDoc lhsElement= (Element JavaDoc) lhsNode;
149                     if (lhsElement.getNodeName().equalsIgnoreCase(NODE_LHS)) {
150                         String JavaDoc lhs= lhsElement.getAttribute(ATTRIBUTE_NAME);
151                         if (lhs != null) {
152                             Set JavaDoc cache= history.getCache(lhs);
153                             NodeList JavaDoc children= lhsElement.getChildNodes();
154                             int nRHS= children.getLength();
155                             for (int j= 0; j < nRHS; j++) {
156                                 Node JavaDoc rhsNode= children.item(j);
157                                 if (rhsNode.getNodeType() == Node.ELEMENT_NODE) {
158                                     Element JavaDoc rhsElement= (Element JavaDoc) rhsNode;
159                                     if (rhsElement.getNodeName().equalsIgnoreCase(NODE_RHS)) {
160                                         String JavaDoc rhs= rhsElement.getAttribute(ATTRIBUTE_NAME);
161                                         if (rhs != null) {
162                                             cache.add(rhs);
163                                         }
164                                     }
165                                 }
166                             }
167                         }
168                     }
169                 }
170             }
171             
172             return history;
173         }
174
175         private int parseNaturalInt(String JavaDoc attribute, int defaultValue) {
176             try {
177                 int integer= Integer.parseInt(attribute);
178                 if (integer > 0)
179                     return integer;
180                 return defaultValue;
181             } catch (NumberFormatException JavaDoc e) {
182                 return defaultValue;
183             }
184         }
185
186         private JavaUIException createException(Exception JavaDoc e, String JavaDoc message) {
187             return new JavaUIException(JavaUIStatus.createError(IStatus.ERROR, message, e));
188         }
189     }
190
191     /**
192      * Most recently used variant with capped size that only counts
193      * {@linkplain #put(Object, Object) put} as access. This is implemented by always removing an
194      * element before it gets put back.
195      *
196      * @since 3.2
197      */

198     private static final class MRUMap extends LinkedHashMap JavaDoc {
199         private static final long serialVersionUID= 1L;
200         private final int fMaxSize;
201         
202         /**
203          * Creates a new <code>MRUMap</code> with the given size.
204          *
205          * @param maxSize the maximum size of the cache, must be &gt; 0
206          */

207         public MRUMap(int maxSize) {
208             Assert.isLegal(maxSize > 0);
209             fMaxSize= maxSize;
210         }
211         
212         /*
213          * @see java.util.HashMap#put(java.lang.Object, java.lang.Object)
214          */

215         public Object JavaDoc put(Object JavaDoc key, Object JavaDoc value) {
216             Object JavaDoc object= remove(key);
217             super.put(key, value);
218             return object;
219         }
220         
221         /*
222          * @see java.util.LinkedHashMap#removeEldestEntry(java.util.Map.Entry)
223          */

224         protected boolean removeEldestEntry(Entry eldest) {
225             return size() > fMaxSize;
226         }
227     }
228     
229     /**
230      * Most recently used variant with capped size that orders the elements by addition.
231      * This is implemented by always removing an element before it gets added back.
232      *
233      * @since 3.2
234      */

235     private static final class MRUSet extends LinkedHashSet JavaDoc {
236         private static final long serialVersionUID= 1L;
237         private final int fMaxSize;
238         
239         /**
240          * Creates a new <code>MRUSet</code> with the given size.
241          *
242          * @param maxSize the maximum size of the cache, must be &gt; 0
243          */

244         public MRUSet(int maxSize) {
245             Assert.isLegal(maxSize > 0);
246             fMaxSize= maxSize;
247         }
248         
249         /*
250          * @see java.util.HashSet#add(java.lang.Object)
251          */

252         public boolean add(Object JavaDoc o) {
253             if (remove(o)) {
254                 super.add(o);
255                 return false;
256             }
257                 
258             if (size() >= fMaxSize)
259                 remove(this.iterator().next());
260             
261             super.add(o);
262             return true;
263         }
264     }
265     
266     /**
267      * A ranking of the most recently selected types.
268      *
269      * @since 3.2
270      */

271     public static final class RHSHistory {
272         private final LinkedHashMap JavaDoc fHistory;
273         private List JavaDoc fList;
274         
275         RHSHistory(LinkedHashMap JavaDoc history) {
276             fHistory= history;
277         }
278         
279         /**
280          * Returns the rank of a type in the history in [0.0,&nbsp;1.0]. The rank of the most
281          * recently selected type is 1.0, the rank of any type that is not remembered is zero.
282          *
283          * @param type the fully qualified type name to get the rank for
284          * @return the rank of <code>type</code>
285          */

286         public float getRank(String JavaDoc type) {
287             if (fHistory == null)
288                 return 0.0F;
289             Integer JavaDoc integer= (Integer JavaDoc) fHistory.get(type);
290             return integer == null ? 0.0F : integer.floatValue() / fHistory.size();
291         }
292         
293         /**
294          * Returns the size of the history.
295          *
296          * @return the size of the history
297          */

298         public int size() {
299             return fHistory == null ? 0 : fHistory.size();
300         }
301         
302         /**
303          * Returns the list of remembered types ordered by recency. The first element is the
304          * <i>least</i>, the last element the <i>most</i> recently remembered type.
305          *
306          * @return the list of remembered types as fully qualified type names (element type:
307          * {@link String})
308          */

309         public List JavaDoc getTypes() {
310             if (fHistory == null)
311                 return Collections.EMPTY_LIST;
312             if (fList == null) {
313                 fList= Collections.unmodifiableList(new ArrayList JavaDoc(fHistory.keySet()));
314             }
315             return fList;
316         }
317     }
318     
319     private static final RHSHistory EMPTY_HISTORY= new RHSHistory(null);
320     private static final int DEFAULT_TRACKED_LHS= 100;
321     private static final int DEFAULT_TRACKED_RHS= 10;
322     
323     private static final Set JavaDoc UNCACHEABLE;
324     static {
325         Set JavaDoc uncacheable= new HashSet JavaDoc();
326         uncacheable.add("java.lang.Object"); //$NON-NLS-1$
327
uncacheable.add("java.lang.Comparable"); //$NON-NLS-1$
328
uncacheable.add("java.io.Serializable"); //$NON-NLS-1$
329
uncacheable.add("java.io.Externalizable"); //$NON-NLS-1$
330
UNCACHEABLE= Collections.unmodifiableSet(uncacheable);
331     }
332     
333     private final LinkedHashMap JavaDoc/*<IType, MRUSet<IType>>*/ fLHSCache;
334     private final int fMaxLHS;
335     private final int fMaxRHS;
336     
337     /**
338      * Creates a new history.
339      *
340      * @param maxLHS the maximum number of tracked left hand sides (&gt; 0)
341      * @param maxRHS the maximum number of tracked right hand sides per left hand side(&gt; 0)
342      */

343     public ContentAssistHistory(int maxLHS, int maxRHS) {
344         Assert.isLegal(maxLHS > 0);
345         Assert.isLegal(maxRHS > 0);
346         fMaxLHS= maxLHS;
347         fMaxRHS= maxRHS;
348         fLHSCache= new MRUMap(fMaxLHS);
349     }
350     
351     /**
352      * Creates a new history, equivalent to
353      * <code>ContentAssistHistory(DEFAULT_TRACKED_LHS, DEFAULT_TRACKED_RHS})</code>.
354      */

355     public ContentAssistHistory() {
356         this(DEFAULT_TRACKED_LHS, DEFAULT_TRACKED_RHS);
357     }
358     
359     /**
360      * Remembers the selection of a right hand side type (proposal type) for a certain left hand side (expected
361      * type) in content assist.
362      *
363      * @param lhs the left hand side / expected type
364      * @param rhs the selected right hand side
365      */

366     public void remember(IType lhs, IType rhs) {
367         Assert.isLegal(lhs != null);
368         Assert.isLegal(rhs != null);
369         
370         try {
371             if (!isCacheableRHS(rhs))
372                 return;
373             ITypeHierarchy hierarchy= rhs.newSupertypeHierarchy(getProgressMonitor());
374             if (hierarchy.contains(lhs)) {
375                 // TODO remember for every member of the LHS hierarchy or not? Yes for now.
376
IType[] allLHSides= hierarchy.getAllSupertypes(lhs);
377                 for (int i= 0; i < allLHSides.length; i++)
378                     rememberInternal(allLHSides[i], rhs);
379                 rememberInternal(lhs, rhs);
380             }
381         } catch (JavaModelException x) {
382             JavaPlugin.log(x);
383         }
384     }
385     
386     /**
387      * Returns the {@link RHSHistory history} of the types that have been selected most recently as
388      * right hand sides for the given type.
389      *
390      * @param lhs the fully qualified type name of an expected type for which right hand sides are
391      * requested, or <code>null</code>
392      * @return the right hand side history for the given type
393      */

394     public RHSHistory getHistory(String JavaDoc lhs) {
395         MRUSet rhsCache= (MRUSet) fLHSCache.get(lhs);
396         if (rhsCache != null) {
397             int count= rhsCache.size();
398             LinkedHashMap JavaDoc history= new LinkedHashMap JavaDoc((int) (count / 0.75));
399             int rank= 1;
400             for (Iterator JavaDoc it= rhsCache.iterator(); it.hasNext(); rank++) {
401                 String JavaDoc type= (String JavaDoc) it.next();
402                 history.put(type, new Integer JavaDoc(rank));
403             }
404             return new RHSHistory(history);
405         }
406         return EMPTY_HISTORY;
407     }
408     
409     /**
410      * Returns a read-only map from {@link IType} to {@link RHSHistory}, where each value is the
411      * history for the key type (see {@link #getHistory(String)}.
412      *
413      * @return the set of remembered right hand sides ordered by least recent selection
414      */

415     public Map JavaDoc getEntireHistory() {
416         HashMap JavaDoc map= new HashMap JavaDoc((int) (fLHSCache.size() / 0.75));
417         for (Iterator JavaDoc it= fLHSCache.entrySet().iterator(); it.hasNext();) {
418             Entry entry= (Entry) it.next();
419             String JavaDoc lhs= (String JavaDoc) entry.getKey();
420             map.put(lhs, getHistory(lhs));
421         }
422         return Collections.unmodifiableMap(map);
423     }
424     
425     private void rememberInternal(IType lhs, IType rhs) throws JavaModelException {
426         if (isCacheableLHS(lhs))
427             getCache(lhs.getFullyQualifiedName()).add(rhs.getFullyQualifiedName());
428     }
429
430     private boolean isCacheableLHS(IType type) throws JavaModelException {
431         return !Flags.isFinal(type.getFlags()) && !UNCACHEABLE.contains(type.getFullyQualifiedName());
432     }
433     
434     private boolean isCacheableRHS(IType type) throws JavaModelException {
435         return !type.isInterface() && !Flags.isAbstract(type.getFlags());
436     }
437
438     private Set JavaDoc getCache(String JavaDoc lhs) {
439         MRUSet rhsCache= (MRUSet) fLHSCache.get(lhs);
440         if (rhsCache == null) {
441             rhsCache= new MRUSet(fMaxRHS);
442             fLHSCache.put(lhs, rhsCache);
443         }
444         
445         return rhsCache;
446     }
447
448     private IProgressMonitor getProgressMonitor() {
449         return new NullProgressMonitor();
450     }
451     
452     /**
453      * Stores the history as XML document into the given preferences.
454      *
455      * @param history the history to store
456      * @param preferences the preferences to store the history into
457      * @param key the key under which to store the history
458      * @throws CoreException if serialization fails
459      * @see #load(Preferences, String) on how to restore a history stored by this method
460      */

461     public static void store(ContentAssistHistory history, Preferences preferences, String JavaDoc key) throws CoreException {
462         StringWriter JavaDoc writer= new StringWriter JavaDoc();
463         new ReaderWriter().store(history, new StreamResult JavaDoc(writer));
464         preferences.setValue(key, writer.toString());
465     }
466     
467     /**
468      * Loads a history from an XML encoded preference value.
469      *
470      * @param preferences the preferences to retrieve the history from
471      * @param key the key under which the history is stored
472      * @return the deserialized history, or <code>null</code> if there is nothing stored under the
473      * given key
474      * @throws CoreException if deserialization fails
475      * @see #store(ContentAssistHistory, Preferences, String) on how to store a history such that it
476      * can be read by this method
477      */

478     public static ContentAssistHistory load(Preferences preferences, String JavaDoc key) throws CoreException {
479         String JavaDoc value= preferences.getString(key);
480         if (value != null && value.length() > 0) {
481             return new ReaderWriter().load(new InputSource JavaDoc(new StringReader JavaDoc(value)));
482         }
483         return null;
484     }
485 }
486
Popular Tags