Source code: com/port80/eclipse/jdt/annotation/AnnotationManager.java
1 package com.port80.eclipse.jdt.annotation;
2
3 import java.io.File;
4 import java.sql.Timestamp;
5 import java.util.ArrayList;
6 import java.util.HashMap;
7 import java.util.List;
8 import java.util.Map;
9
10 import org.eclipse.core.resources.ResourcesPlugin;
11 import org.eclipse.core.runtime.IPath;
12 import org.eclipse.jdt.core.IClassFile;
13 import org.eclipse.jdt.core.ICompilationUnit;
14 import org.eclipse.jdt.core.IJavaElement;
15 import org.eclipse.jdt.internal.ui.viewsupport.JavaUILabelProvider;
16 import org.eclipse.jface.preference.IPreferenceStore;
17 import org.eclipse.jface.viewers.IStructuredSelection;
18 import org.eclipse.jface.viewers.ITreeContentProvider;
19 import org.eclipse.jface.viewers.LabelProvider;
20 import org.eclipse.jface.viewers.StructuredSelection;
21 import org.eclipse.jface.viewers.Viewer;
22 import org.eclipse.swt.graphics.Image;
23 import org.eclipse.ui.IMemento;
24 import org.eclipse.ui.IPartListener;
25 import org.eclipse.ui.IWorkbenchPart;
26 import org.eclipse.ui.XMLMemento;
27
28 import com.port80.eclipse.jdt.IConstants;
29 import com.port80.eclipse.jdt.JdtPlugin;
30 import com.port80.eclipse.jdt.Util;
31 import com.port80.eclipse.jdt.util.JavaUtil;
32 import com.port80.eclipse.jdt.util.PersistentFolder;
33 import com.port80.eclipse.jdt.util.PersistentItem;
34
35 /**
36 * Data model for AnnotationView.
37 *
38 * Annotation keep the set of visited IResources together with access information.
39 * It kept all visited objects automatically upon visiting instead of manually by user.
40 * It can be used to backtrace visit history. It can be further bookmark or annotated
41 * by user. This is an enchancement of the BookmarkView which, unfortunately as of
42 * Eclipse 2.0, cannot bookmark binary classes and it is not easy for annotation.
43 *
44 * AnnotationManager also works without the AnnotationView active and manage
45 * only the visited object list and its related actions.
46 *
47 * @author chrisl
48 */
49 public class AnnotationManager extends LabelProvider implements ITreeContentProvider {
50
51 // Static fields ///////////////////////////////////////////////////////
52 //
53 private static final String NAME = "AnnotationManager";
54 private static final String ID = "com.port80.eclipse.jdt.annotation.AnnotationManager";
55 private static final String TAG_ROOT = "Annotation";
56 private static final String TAG_VERSION = "Version";
57 private static final String[] VERSIONS = new String[] { "1.0" };
58
59 public static final String INBOX = "INBOX";
60 public static final int RET_FAIL = -1;
61 public static final int RET_OK = 0;
62 public static final int RET_CANCEL = 1;
63 public static final int RET_RESET = 2;
64
65 private static final boolean DEBUG = true;
66 private static final boolean VERBOSE = true;
67
68 // Instance fields /////////////////////////////////////////////////////
69 //
70 private PersistentFolder fRoot = new PersistentFolder("");
71 private PersistentFolder fInbox = new PersistentFolder(INBOX);
72 private JavaUILabelProvider labelProvider = new JavaUILabelProvider();
73 // A Map is used instead of a Set, so that we can retreive the original item already exists
74 // and equals to the requested one.
75 private Map fAnnotation = new HashMap();
76 private List fModelListeners = new ArrayList();
77 private Map fFolders = new HashMap();
78
79 private int fInboxLimit;
80 private IPartListener fPartListener;
81
82 // Constructors ////////////////////////////////////////////////////////
83 //
84 public AnnotationManager() {
85 fFolders.put(INBOX, fInbox);
86 fRoot.addChild(fInbox);
87 restoreXMLState();
88 initPrefs();
89 }
90
91 public void initPrefs() {
92 IPreferenceStore prefs = JdtPlugin.getDefault().getPreferenceStore();
93 fInboxLimit = prefs.getInt(IConstants.PREF_ANNOTATIONVIEW_INBOX_LIMIT);
94 }
95
96 ////////////////////////////////////////////////////////////////////////
97 //
98
99 public void addModelListener(IAnnotationModelListener l) {
100 fModelListeners.add(l);
101 }
102 public void removeModelListener(IAnnotationModelListener l) {
103 fModelListeners.remove(l);
104 }
105
106 private void fireModelChange() {
107 for (int i = 0; i < fModelListeners.size(); ++i) {
108 ((IAnnotationModelListener) fModelListeners.get(i)).refresh();
109 }
110 }
111
112 private void fireModelChange(PersistentItem item) {
113 for (int i = 0; i < fModelListeners.size(); ++i) {
114 ((IAnnotationModelListener) fModelListeners.get(i)).refresh(item);
115 }
116 }
117
118 private void fireModelChange(IStructuredSelection selection) {
119 for (int i = 0; i < fModelListeners.size(); ++i)
120 ((IAnnotationModelListener) fModelListeners.get(i)).setSelection(selection, true);
121 }
122
123 ////////////////////////////////////////////////////////////////////////
124 //
125
126 public Object getRoot() {
127 return fRoot;
128 }
129
130 public Object findItem(Object a) {
131 return fAnnotation.get(a);
132 }
133
134 ////////////////////////////////////////////////////////////////////////
135
136 /**
137 * Find folder with given name, create one if not found.
138 * @return fInbox if name==null, otherwise the folder with the given name.
139 */
140 public PersistentFolder findFolder(String name) {
141 if (name == null || name.length() == 0)
142 return fInbox;
143 PersistentFolder folder = (PersistentFolder) fFolders.get(name);
144 if (folder == null) {
145 folder = new PersistentFolder(name);
146 fFolders.put(name, folder);
147 fRoot.addChild(folder);
148 }
149 return folder;
150 }
151
152 public void saveState(IMemento memento) {
153 if (DEBUG)
154 System.err.println(NAME + ".saveState(IMemento)");
155 memento.putString(TAG_VERSION, VERSIONS[0]);
156 Object[] items = fAnnotation.values().toArray();
157 IMemento child;
158 for (int i = 0; i < items.length; ++i) {
159 child = memento.createChild(PersistentItem.NAME);
160 PersistentItem item = (PersistentItem) items[i];
161 item.saveState(child);
162 }
163 }
164
165 public boolean restoreState(IMemento memento) {
166 if (DEBUG)
167 System.err.println(NAME + ".restoreState(IMemento)");
168 if (memento == null)
169 return true;
170 IMemento[] ms = memento.getChildren(PersistentItem.NAME);
171 if (ms == null)
172 return false;
173 fAnnotation.clear();
174 PersistentItem item;
175 if (DEBUG)
176 System.err.println(NAME + ".restoreState(IMemento): " + ms.length + " items.");
177 for (int i = 0; i < ms.length; ++i) {
178 item = PersistentItem.restore(ms[i]);
179 if (item != null) {
180 fAnnotation.put(item, item);
181 PersistentFolder folder = findFolder(item.getParent());
182 folder.addChild(item);
183 }
184 }
185 return true;
186 }
187
188 public boolean saveXMLState() {
189 if (saveXMLState(IConstants.ANNOTATION_STATE_FILENAME)) {
190 // Remove recover files if save succeeded.
191 IPath path = JdtPlugin.getDefault().getStateLocation();
192 File file =
193 ((IPath) path.clone())
194 .append(IConstants.ANNOTATION_STATE_FILENAME + ".recover")
195 .toFile();
196 if (file.exists())
197 file.delete();
198 file =
199 ((IPath) path.clone())
200 .append(IConstants.ANNOTATION_STATE_FILENAME + ".recover.backup")
201 .toFile();
202 if (file.exists())
203 file.delete();
204 return true;
205 }
206 return false;
207 }
208
209 /** Save Annotation to state file in metadata area.*/
210 public boolean saveXMLState(String filename) {
211 XMLMemento memento = XMLMemento.createWriteRoot(ID);
212 saveState(memento);
213 return Util.saveXMLState(filename, memento);
214 }
215
216 /** Restore Annotation from state file in metadata area.*/
217 public boolean restoreXMLState() {
218 IMemento memento = Util.restoreXMLState(IConstants.ANNOTATION_STATE_FILENAME);
219 return restoreState(memento);
220 }
221
222 ////////////////////////////////////////////////////////////////////////
223 //
224
225 public boolean contains(PersistentItem a) {
226 return contains(a, a.getParent());
227 }
228
229 public boolean contains(Object a, String parent) {
230 if (a == null)
231 return false;
232 PersistentItem item;
233 if (a instanceof PersistentItem) {
234 item = (PersistentItem) a;
235 } else {
236 item = PersistentItem.create(a, parent);
237 if (item == null)
238 return false;
239 }
240 return fAnnotation.get(item) != null;
241 }
242
243 /**
244 * Get item in Annotation that represents the given object.
245 * @return The PersistentItem if it exists, else null.
246 */
247 public PersistentItem get(PersistentItem a) {
248 return get(a, a.getParent());
249 }
250
251 public PersistentItem get(Object a, String parent) {
252 if (a == null)
253 return null;
254 PersistentItem item;
255 if (a instanceof PersistentItem) {
256 item = (PersistentItem) a;
257 } else {
258 item = PersistentItem.create(a, parent);
259 if (item == null)
260 return null;
261 }
262 item = (PersistentItem) fAnnotation.get(item);
263 return item;
264 }
265
266 /**
267 * Added automatically Type or File visited on editor activation.
268 * @return Item added or the original item that is updated.
269 */
270 public PersistentItem addAutomatic(IJavaElement element) {
271 element = findAutomaticElement(element);
272 if (false)
273 System.err.println(JdtPlugin.msg(NAME + ".addAutomaticFromEditor(): suitable", element));
274 if (element == null)
275 return null;
276 PersistentItem item = PersistentItem.create(element);
277 item = add(item, INBOX);
278 fireModelChange(new StructuredSelection(item));
279 return item;
280 }
281
282 /**
283 * Add an object to the specified folder in Annotation. If folder is null, add to INBOX.
284 * If object is a PersistentItem, it's parent is set to the specified folder regardless of its existing value.
285 * @return Item added or the original item that is updated.
286 * @param a The object to add.
287 * @param parent The folder to add to.
288 */
289 public PersistentItem add(PersistentItem item, String parent) {
290 if (item == null) {
291 JdtPlugin.log(NAME + ".add(PresistentItem,String): item should not be null, parent=" + parent);
292 return null;
293 }
294 if (parent == null)
295 parent = INBOX;
296 item.setParent(parent);
297 PersistentItem original = (PersistentItem) fAnnotation.get(item);
298 if (original == null) {
299 long time = System.currentTimeMillis();
300 item.setATime(new Timestamp(time));
301 fAnnotation.put(item, item);
302 PersistentFolder folder = findFolder(parent);
303 PersistentItem child;
304 if (parent == INBOX)
305 for (int i = folder.getChildrenCount(); i >= fInboxLimit; --i) {
306 child=folder.removeOldestChild();
307 fAnnotation.remove(child);
308 child.setParent(null);
309 }
310 folder.addChild(item);
311 saveXMLState(IConstants.ANNOTATION_RECOVER_FILENAME);
312 fireModelChange();
313 } else {
314 item = original;
315 original.setATime(new Timestamp(System.currentTimeMillis()));
316 fireModelChange(original);
317 }
318 return item;
319 }
320
321 /**
322 * Remove object from Annotation.
323 * @return True if successfully removed, else false (object invalid or not exist in Annotation).
324 */
325 public boolean remove(PersistentItem a) {
326 if (a == null)
327 return false;
328 return remove(a, a.getParent());
329 }
330
331 public boolean remove(Object a, String parent) {
332 if (a == null)
333 return false;
334 PersistentItem item;
335 if (a instanceof PersistentItem) {
336 item = (PersistentItem) a;
337 } else {
338 item = PersistentItem.create(a, parent);
339 if (item == null)
340 return false;
341 }
342 if (item instanceof PersistentFolder) {
343 PersistentFolder folder = (PersistentFolder) item;
344 removeAll(folder.getChildren());
345 fRoot.removeChild(item);
346 item.setParent(null);
347 return true;
348 }
349 item = (PersistentItem) fAnnotation.remove(item);
350 if (item != null) {
351 PersistentFolder folder = findFolder(parent);
352 folder.removeChild(item);
353 item.setParent(null);
354 saveXMLState(IConstants.ANNOTATION_RECOVER_FILENAME);
355 fireModelChange();
356 return true;
357 }
358 return false;
359 }
360
361 /**
362 * Remove an array of items. Ignoring objects that are not an item.
363 * @return true if any item is removed.
364 * @param items An array of AnnotationItem.
365 */
366 public boolean removeAll(Object[] items) {
367 if (items == null || items.length == 0)
368 return false;
369 PersistentItem item;
370 boolean removed = false;
371 for (int i = 0; i < items.length; ++i) {
372 if (items[i] instanceof PersistentFolder) {
373 PersistentFolder folder = (PersistentFolder) items[i];
374 removeAll(folder.getChildren());
375 fRoot.removeChild(folder);
376 folder.setParent(null);
377 removed = true;
378 continue;
379 }
380 if (!(items[i] instanceof PersistentItem))
381 continue;
382 item = (PersistentItem) fAnnotation.remove(items[i]);
383 if (item != null) {
384 PersistentFolder folder = findFolder(item.getParent());
385 folder.removeChild(item);
386 item.setParent(null);
387 removed = true;
388 }
389 }
390 if (removed) {
391 saveXMLState(IConstants.ANNOTATION_RECOVER_FILENAME);
392 fireModelChange();
393 }
394 return removed;
395 }
396
397 /**
398 * @return A Suitable Java element that can be added automatically to a Annotation (IType)
399 * that enclose the given Java element.
400 */
401 protected IJavaElement findAutomaticElement(IJavaElement element) {
402 if (element == null)
403 return null;
404 String message = NAME + ".findAutomaticElement()";
405 int type = element.getElementType();
406 if (type < IJavaElement.COMPILATION_UNIT)
407 return null;
408 if (type > IJavaElement.TYPE)
409 return findAutomaticElement(element.getParent());
410 try {
411 switch (type) {
412 case IJavaElement.COMPILATION_UNIT :
413 message = message + ": COMPILATION_UNIT";
414 return ((ICompilationUnit) element).getTypes()[0];
415 case IJavaElement.CLASS_FILE :
416 message = message + ": CLASS_FILE";
417 return ((IClassFile) element).getType();
418 case IJavaElement.TYPE :
419 return element;
420 default :
421 System.err.println(JdtPlugin.msg(message, element));
422 }
423 } catch (Exception e) {
424 JdtPlugin.log(message, element, e);
425 }
426 return null;
427 }
428
429 /**
430 * Added currently selected IJavaElement in editor to the Annotation INBOX.
431 * @return PresistentItem added, null if cannot create item.
432 */
433 public PersistentItem addFromEditor(IWorkbenchPart part) {
434 IJavaElement element = JavaUtil.getSelectedElementFromEditor(part);
435 element = findSuitableElement(element);
436 if (false)
437 System.err.println(JdtPlugin.msg(NAME + ".addFromEditor(): suitable", element));
438 if (element != null) {
439 return add(PersistentItem.create(element), INBOX);
440 }
441 return null;
442 }
443
444 /**
445 * @return A Suitable Java element that can be added to Annotation (IType,IMETHOD)
446 * that enclose the given Java element.
447 */
448 protected IJavaElement findSuitableElement(IJavaElement element) {
449 if (element == null)
450 return null;
451 String message = NAME + ".findSuitableElement()";
452 int type = element.getElementType();
453 if (type < IJavaElement.COMPILATION_UNIT)
454 return null;
455 if (type > IJavaElement.METHOD || type == IJavaElement.FIELD)
456 return findSuitableElement(element.getParent());
457 try {
458 switch (type) {
459 case IJavaElement.COMPILATION_UNIT :
460 message = message + ": COMPILATION_UNIT";
461 return ((ICompilationUnit) element).getTypes()[0];
462 case IJavaElement.CLASS_FILE :
463 message = message + ": CLASS_FILE";
464 return ((IClassFile) element).getType();
465 case IJavaElement.TYPE :
466 return element;
467 case IJavaElement.METHOD :
468 return element;
469 default :
470 System.err.println(JdtPlugin.msg(message, element));
471 }
472 } catch (Exception e) {
473 JdtPlugin.log(message, element, e);
474 }
475 return null;
476 }
477
478 public boolean copyItem(PersistentItem item, String newfolder) {
479 if (internalCopyItem(item, newfolder)) {
480 saveXMLState(IConstants.ANNOTATION_RECOVER_FILENAME);
481 fireModelChange();
482 return true;
483 }
484 return false;
485 }
486
487 public boolean copyItems(Object[] items, String newfolder) {
488 boolean moved = false;
489 for (int i = 0; i < items.length; ++i) {
490 if (!(items[i] instanceof PersistentItem))
491 continue;
492 if (internalCopyItem((PersistentItem) items[i], newfolder))
493 moved = true;
494 }
495 if (moved) {
496 saveXMLState(IConstants.ANNOTATION_RECOVER_FILENAME);
497 fireModelChange();
498 return true;
499 }
500 return false;
501 }
502
503 private boolean internalCopyItem(PersistentItem item, String newfolder) {
504 if (item == null)
505 return false;
506 if (item instanceof PersistentFolder)
507 return false;
508 //
509 // Duplicate item and item to new folder.
510 //
511 IMemento m = XMLMemento.createWriteRoot(ID);
512 item.saveState(m);
513 PersistentItem dup = (PersistentItem) item.clone();
514 if (dup == null) {
515 JdtPlugin.log(
516 JdtPlugin.getString(NAME, "internalCopyItem", "DuplicateFailed") + ": item=" + item);
517 return false;
518 }
519 dup.setParent(newfolder);
520 if (fAnnotation.containsKey(dup)) {
521 JdtPlugin.log(
522 JdtPlugin.getString(NAME, "internalCopyItem", "DestinationExist") + ": item=" + item);
523 return false;
524 } else {
525 fAnnotation.put(dup, dup);
526 PersistentFolder folder = findFolder(newfolder);
527 folder.addChild(dup);
528 return true;
529 }
530 }
531
532 /**
533 * Move item to another folder, create new folder if not yet exists.
534 * The item to be moved must exists, otherwise error and do nothing.
535 * @return true if move success, else false.
536 * @param item The item to move.
537 * @param newfolder The new folder to move the item to.
538 */
539 public boolean moveItem(PersistentItem item, String newfolder) {
540 if (internalMoveItem(item, newfolder)) {
541 saveXMLState(IConstants.ANNOTATION_RECOVER_FILENAME);
542 fireModelChange();
543 return true;
544 }
545 return false;
546 }
547
548 /**
549 * Move all items to the new folder. Ignoring objects that is not a valid item.
550 * @return true if any item is moved, else false.
551 * @param items An array of PersistentItem to be moved.
552 * @param newfolder The new folder to move to.
553 */
554 public boolean moveItems(Object[] items, String newfolder) {
555 boolean moved = false;
556 for (int i = 0; i < items.length; ++i) {
557 if (!(items[i] instanceof PersistentItem))
558 continue;
559 if (internalMoveItem((PersistentItem) items[i], newfolder))
560 moved = true;
561 }
562 if (moved) {
563 saveXMLState(IConstants.ANNOTATION_RECOVER_FILENAME);
564 fireModelChange();
565 return true;
566 }
567 return false;
568 }
569
570 private boolean internalMoveItem(PersistentItem src, String newfolder) {
571 if (src == null)
572 return false;
573 if (src instanceof PersistentFolder)
574 return false;
575 if (!fAnnotation.containsKey(src)) {
576 JdtPlugin.log(
577 JdtPlugin.getString(NAME, "internalMoveItem", "SourceItemNotExist") + ": item=" + src);
578 return false;
579 }
580 PersistentItem dest = (PersistentItem) src.clone();
581 dest.setParent(newfolder);
582 if (fAnnotation.containsKey(dest)) {
583 //TODO: May be we should merge to dest. if dest. already exists.
584 JdtPlugin.log(
585 JdtPlugin.getString(NAME, "internalMoveItem", "DestinationItemExist")
586 + ": item="
587 + src);
588 return false;
589 }
590 PersistentItem original = (PersistentItem) fAnnotation.remove(src);
591 PersistentFolder folder = findFolder(original.getParent());
592 folder.removeChild(original);
593 original.setParent(newfolder);
594 fAnnotation.put(original, original);
595 folder = findFolder(newfolder);
596 folder.addChild(original);
597 return true;
598 }
599
600 // ITreeContentProvider /////////////////////////////////////////////
601 //
602
603 public Object getParent(Object child) {
604 if (child instanceof PersistentItem) {
605 return ((PersistentItem) child).getParent();
606 }
607 return null;
608 }
609
610 public Object[] getChildren(Object parent) {
611 if (parent instanceof PersistentFolder) {
612 return ((PersistentFolder) parent).getChildren();
613 }
614 return new Object[0];
615 }
616
617 public boolean hasChildren(Object parent) {
618 if (parent instanceof PersistentFolder)
619 return ((PersistentFolder) parent).hasChildren();
620 return false;
621 }
622
623 // IStructuredContentProvider interface ///////////////////////////
624 //
625
626 public Object[] getElements(Object parent) {
627 if (parent == null || parent.equals(ResourcesPlugin.getWorkspace())) {
628 return getChildren(fRoot);
629 }
630 return getChildren(parent);
631 }
632
633 // IContentProvider interface /////////////////////////////////
634 //
635
636 public void inputChanged(Viewer v, Object oldInput, Object newInput) {
637 }
638
639 public void dispose() {
640 }
641
642 // LabelProvider interface /////////////////////////////////////
643 //
644
645 public String getText(Object obj) {
646 if (!(obj instanceof PersistentItem))
647 return null;
648 PersistentItem item = (PersistentItem) obj;
649 String ret = item.getName();
650 ret += " #" + item.getCount() + " ";
651 String desc = item.getAnnotation();
652 if (desc != null)
653 ret += desc;
654 return ret;
655 }
656
657 public Image getImage(Object obj) {
658 return ((PersistentItem) obj).getImage();
659 }
660
661 ////////////////////////////////////////////////////////////////////////
662
663 /** Validate Memento is in valid version format. */
664 boolean validVersion(IMemento memento) {
665 String version = memento.getString(TAG_VERSION);
666 for (int i = 0; i < VERSIONS.length; i++) {
667 if (VERSIONS[i].equals(version)) {
668 return true;
669 }
670 }
671 return false;
672 }
673
674 ////////////////////////////////////////////////////////////////////////
675
676 }