1 /*
2  * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved.
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
5  * This code is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License version 2 only, as
7  * published by the Free Software Foundation.
8  *
9  * This code is distributed in the hope that it will be useful, but WITHOUT
10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
12  * version 2 for more details (a copy is included in the LICENSE file that
13  * accompanied this code).
14  *
15  * You should have received a copy of the GNU General Public License version
16  * 2 along with this work; if not, write to the Free Software Foundation,
17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18  *
19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20  * or visit www.oracle.com if you need additional information or have any
21  * questions.
22  *
23  */
24 package com.sun.hotspot.igv.filterwindow;
25 
26 import com.sun.hotspot.igv.data.ChangedEvent;
27 import com.sun.hotspot.igv.data.ChangedListener;
28 import com.sun.hotspot.igv.filter.CustomFilter;
29 import com.sun.hotspot.igv.filter.Filter;
30 import com.sun.hotspot.igv.filter.FilterChain;
31 import com.sun.hotspot.igv.filter.FilterSetting;
32 import com.sun.hotspot.igv.filterwindow.actions.*;
33 import java.awt.BorderLayout;
34 import java.awt.event.ActionEvent;
35 import java.awt.event.ActionListener;
36 import java.io.*;
37 import java.util.*;
38 import javax.swing.JComboBox;
39 import javax.swing.UIManager;
40 import javax.swing.border.Border;
41 import org.openide.DialogDisplayer;
42 import org.openide.ErrorManager;
43 import org.openide.NotifyDescriptor;
44 import org.openide.awt.Toolbar;
45 import org.openide.awt.ToolbarPool;
46 import org.openide.explorer.ExplorerManager;
47 import org.openide.explorer.ExplorerUtils;
48 import org.openide.filesystems.FileLock;
49 import org.openide.filesystems.FileObject;
50 import org.openide.filesystems.FileUtil;
51 import org.openide.nodes.AbstractNode;
52 import org.openide.nodes.Children;
53 import org.openide.nodes.Node;
54 import org.openide.util.*;
55 import org.openide.util.actions.SystemAction;
56 import org.openide.windows.TopComponent;
57 import org.openide.windows.WindowManager;
58 
59 /**
60  *
61  * @author Thomas Wuerthinger
62  */
63 public final class FilterTopComponent extends TopComponent implements LookupListener, ExplorerManager.Provider {
64 
65     private static FilterTopComponent instance;
66     public static final String FOLDER_ID = "Filters";
67     public static final String AFTER_ID = "after";
68     public static final String ENABLED_ID = "enabled";
69     public static final String PREFERRED_ID = "FilterTopComponent";
70     private CheckListView view;
71     private ExplorerManager manager;
72     private FilterChain filterChain;
73     private FilterChain sequence;
74     private Lookup.Result<FilterChain> result;
75     private JComboBox comboBox;
76     private List<FilterSetting> filterSettings;
77     private FilterSetting customFilterSetting = new FilterSetting("-- Custom --");
78     private ChangedEvent<FilterTopComponent> filterSettingsChangedEvent;
79     private ActionListener comboBoxActionListener = new ActionListener() {
80 
81         @Override
82         public void actionPerformed(ActionEvent e) {
83             comboBoxSelectionChanged();
84         }
85     };
86 
getFilterSettingsChangedEvent()87     public ChangedEvent<FilterTopComponent> getFilterSettingsChangedEvent() {
88         return filterSettingsChangedEvent;
89     }
90 
getSequence()91     public FilterChain getSequence() {
92         return sequence;
93     }
94 
updateSelection()95     public void updateSelection() {
96         Node[] nodes = this.getExplorerManager().getSelectedNodes();
97         int[] arr = new int[nodes.length];
98         for (int i = 0; i < nodes.length; i++) {
99             int index = sequence.getFilters().indexOf(((FilterNode) nodes[i]).getFilter());
100             arr[i] = index;
101         }
102         view.showSelection(arr);
103     }
104 
comboBoxSelectionChanged()105     private void comboBoxSelectionChanged() {
106 
107         Object o = comboBox.getSelectedItem();
108         if (o == null) {
109             return;
110         }
111         assert o instanceof FilterSetting;
112         FilterSetting s = (FilterSetting) o;
113 
114         if (s != customFilterSetting) {
115             FilterChain chain = getFilterChain();
116             chain.getChangedEvent().beginAtomic();
117             List<Filter> toRemove = new ArrayList<>();
118             for (Filter f : chain.getFilters()) {
119                 if (!s.containsFilter(f)) {
120                     toRemove.add(f);
121                 }
122             }
123             for (Filter f : toRemove) {
124                 chain.removeFilter(f);
125             }
126 
127             for (Filter f : s.getFilters()) {
128                 if (!chain.containsFilter(f)) {
129                     chain.addFilter(f);
130                 }
131             }
132 
133             chain.getChangedEvent().endAtomic();
134             filterSettingsChangedEvent.fire();
135         } else {
136             this.updateComboBoxSelection();
137         }
138 
139         SystemAction.get(RemoveFilterSettingsAction.class).setEnabled(comboBox.getSelectedItem() != this.customFilterSetting);
140         SystemAction.get(SaveFilterSettingsAction.class).setEnabled(comboBox.getSelectedItem() == this.customFilterSetting);
141     }
142 
updateComboBox()143     private void updateComboBox() {
144         comboBox.removeAllItems();
145         comboBox.addItem(customFilterSetting);
146         for (FilterSetting s : filterSettings) {
147             comboBox.addItem(s);
148         }
149 
150         this.updateComboBoxSelection();
151     }
152 
addFilterSetting()153     public void addFilterSetting() {
154         NotifyDescriptor.InputLine l = new NotifyDescriptor.InputLine("Name of the new profile:", "Filter Profile");
155         if (DialogDisplayer.getDefault().notify(l) == NotifyDescriptor.OK_OPTION) {
156             String name = l.getInputText();
157 
158             FilterSetting toRemove = null;
159             for (FilterSetting s : filterSettings) {
160                 if (s.getName().equals(name)) {
161                     NotifyDescriptor.Confirmation conf = new NotifyDescriptor.Confirmation("Filter profile \"" + name + "\" already exists, do you want to replace it?", "Filter");
162                     if (DialogDisplayer.getDefault().notify(conf) == NotifyDescriptor.YES_OPTION) {
163                         toRemove = s;
164                         break;
165                     } else {
166                         return;
167                     }
168                 }
169             }
170 
171             if (toRemove != null) {
172                 filterSettings.remove(toRemove);
173             }
174             FilterSetting setting = createFilterSetting(name);
175             filterSettings.add(setting);
176 
177             // Sort alphabetically
178             Collections.sort(filterSettings, new Comparator<FilterSetting>() {
179 
180                 @Override
181                 public int compare(FilterSetting o1, FilterSetting o2) {
182                     return o1.getName().compareTo(o2.getName());
183                 }
184             });
185 
186             updateComboBox();
187         }
188     }
189 
canRemoveFilterSetting()190     public boolean canRemoveFilterSetting() {
191         return comboBox.getSelectedItem() != customFilterSetting;
192     }
193 
removeFilterSetting()194     public void removeFilterSetting() {
195         if (canRemoveFilterSetting()) {
196             Object o = comboBox.getSelectedItem();
197             assert o instanceof FilterSetting;
198             FilterSetting f = (FilterSetting) o;
199             assert f != customFilterSetting;
200             assert filterSettings.contains(f);
201             NotifyDescriptor.Confirmation l = new NotifyDescriptor.Confirmation("Do you really want to remove filter profile \"" + f + "\"?", "Filter Profile");
202             if (DialogDisplayer.getDefault().notify(l) == NotifyDescriptor.YES_OPTION) {
203                 filterSettings.remove(f);
204                 updateComboBox();
205             }
206         }
207     }
208 
createFilterSetting(String name)209     private FilterSetting createFilterSetting(String name) {
210         FilterSetting s = new FilterSetting(name);
211         FilterChain chain = this.getFilterChain();
212         for (Filter f : chain.getFilters()) {
213             s.addFilter(f);
214         }
215         return s;
216     }
217 
updateComboBoxSelection()218     private void updateComboBoxSelection() {
219         List<Filter> filters = this.getFilterChain().getFilters();
220         boolean found = false;
221         for (FilterSetting s : filterSettings) {
222             if (s.getFilterCount() == filters.size()) {
223                 boolean ok = true;
224                 for (Filter f : filters) {
225                     if (!s.containsFilter(f)) {
226                         ok = false;
227                     }
228                 }
229 
230                 if (ok) {
231                     if (comboBox.getSelectedItem() != s) {
232                         comboBox.setSelectedItem(s);
233                     }
234                     found = true;
235                     break;
236                 }
237             }
238         }
239 
240         if (!found && comboBox.getSelectedItem() != customFilterSetting) {
241             comboBox.setSelectedItem(customFilterSetting);
242         }
243     }
244 
245     private class FilterChildren extends Children.Keys<Filter> implements ChangedListener<CheckNode> {
246 
247         private HashMap<Filter, Node> nodeHash = new HashMap<>();
248 
249         @Override
createNodes(Filter filter)250         protected Node[] createNodes(Filter filter) {
251             if (nodeHash.containsKey(filter)) {
252                 return new Node[]{nodeHash.get(filter)};
253             }
254 
255             FilterNode node = new FilterNode(filter);
256             node.getSelectionChangedEvent().addListener(this);
257             nodeHash.put(filter, node);
258             return new Node[]{node};
259         }
260 
FilterChildren()261         public FilterChildren() {
262             sequence.getChangedEvent().addListener(new ChangedListener<FilterChain>() {
263 
264                 @Override
265                 public void changed(FilterChain source) {
266                     addNotify();
267                 }
268             });
269 
270             setBefore(false);
271         }
272 
273         @Override
addNotify()274         protected void addNotify() {
275             setKeys(sequence.getFilters());
276             updateSelection();
277         }
278 
279         @Override
changed(CheckNode source)280         public void changed(CheckNode source) {
281             FilterNode node = (FilterNode) source;
282             Filter f = node.getFilter();
283             FilterChain chain = getFilterChain();
284             if (node.isSelected()) {
285                 if (!chain.containsFilter(f)) {
286                     chain.addFilter(f);
287                 }
288             } else {
289                 if (chain.containsFilter(f)) {
290                     chain.removeFilter(f);
291                 }
292             }
293             view.revalidate();
294             view.repaint();
295             updateComboBoxSelection();
296         }
297     }
298 
getFilterChain()299     public FilterChain getFilterChain() {
300         return filterChain;
301     }
302 
FilterTopComponent()303     private FilterTopComponent() {
304         filterSettingsChangedEvent = new ChangedEvent<>(this);
305         initComponents();
306         setName(NbBundle.getMessage(FilterTopComponent.class, "CTL_FilterTopComponent"));
307         setToolTipText(NbBundle.getMessage(FilterTopComponent.class, "HINT_FilterTopComponent"));
308         //        setIcon(Utilities.loadImage(ICON_PATH, true));
309 
310         sequence = new FilterChain();
311         filterChain = new FilterChain();
312         initFilters();
313         manager = new ExplorerManager();
314         manager.setRootContext(new AbstractNode(new FilterChildren()));
315         associateLookup(ExplorerUtils.createLookup(manager, getActionMap()));
316         view = new CheckListView();
317 
318         ToolbarPool.getDefault().setPreferredIconSize(16);
319         Toolbar toolBar = new Toolbar();
320         Border b = (Border) UIManager.get("Nb.Editor.Toolbar.border"); //NOI18N
321         toolBar.setBorder(b);
322         comboBox = new JComboBox();
323         toolBar.add(comboBox);
324         this.add(toolBar, BorderLayout.NORTH);
325         toolBar.add(SaveFilterSettingsAction.get(SaveFilterSettingsAction.class));
326         toolBar.add(RemoveFilterSettingsAction.get(RemoveFilterSettingsAction.class));
327         toolBar.addSeparator();
328         toolBar.add(NewFilterAction.get(NewFilterAction.class));
329         toolBar.add(RemoveFilterAction.get(RemoveFilterAction.class).createContextAwareInstance(this.getLookup()));
330         toolBar.add(MoveFilterUpAction.get(MoveFilterUpAction.class).createContextAwareInstance(this.getLookup()));
331         toolBar.add(MoveFilterDownAction.get(MoveFilterDownAction.class).createContextAwareInstance(this.getLookup()));
332         this.add(view, BorderLayout.CENTER);
333 
334         filterSettings = new ArrayList<>();
335         updateComboBox();
336 
337         comboBox.addActionListener(comboBoxActionListener);
338         setChain(filterChain);
339     }
340 
newFilter()341     public void newFilter() {
342         CustomFilter cf = new CustomFilter("My custom filter", "");
343         if (cf.openInEditor()) {
344             sequence.addFilter(cf);
345             FileObject fo = getFileObject(cf);
346             FilterChangedListener listener = new FilterChangedListener(fo, cf);
347             listener.changed(cf);
348             cf.getChangedEvent().addListener(listener);
349         }
350     }
351 
removeFilter(Filter f)352     public void removeFilter(Filter f) {
353         com.sun.hotspot.igv.filter.CustomFilter cf = (com.sun.hotspot.igv.filter.CustomFilter) f;
354 
355         sequence.removeFilter(cf);
356         try {
357             getFileObject(cf).delete();
358         } catch (IOException ex) {
359             Exceptions.printStackTrace(ex);
360         }
361 
362     }
363 
364     private static class FilterChangedListener implements ChangedListener<Filter> {
365 
366         private FileObject fileObject;
367         private CustomFilter filter;
368 
FilterChangedListener(FileObject fo, CustomFilter cf)369         public FilterChangedListener(FileObject fo, CustomFilter cf) {
370             fileObject = fo;
371             filter = cf;
372         }
373 
374         @Override
changed(Filter source)375         public void changed(Filter source) {
376             try {
377                 if (!fileObject.getName().equals(filter.getName())) {
378                     FileLock lock = fileObject.lock();
379                     fileObject.move(lock, fileObject.getParent(), filter.getName(), "");
380                     lock.releaseLock();
381                     FileObject newFileObject = fileObject.getParent().getFileObject(filter.getName());
382                     fileObject = newFileObject;
383                 }
384 
385                 FileLock lock = fileObject.lock();
386                 OutputStream os = fileObject.getOutputStream(lock);
387                 try (Writer w = new OutputStreamWriter(os)) {
388                     String s = filter.getCode();
389                     w.write(s);
390                 }
391                 lock.releaseLock();
392 
393             } catch (IOException ex) {
394                 Exceptions.printStackTrace(ex);
395             }
396         }
397     }
398 
initFilters()399     public void initFilters() {
400         FileObject folder = FileUtil.getConfigRoot().getFileObject(FOLDER_ID);
401         FileObject[] children = folder.getChildren();
402 
403         List<CustomFilter> customFilters = new ArrayList<>();
404         HashMap<CustomFilter, String> afterMap = new HashMap<>();
405         Set<CustomFilter> enabledSet = new HashSet<>();
406         HashMap<String, CustomFilter> map = new HashMap<>();
407 
408         for (final FileObject fo : children) {
409             InputStream is = null;
410 
411             String code = "";
412             FileLock lock = null;
413             try {
414                 lock = fo.lock();
415                 is = fo.getInputStream();
416                 BufferedReader r = new BufferedReader(new InputStreamReader(is));
417                 String s;
418                 StringBuilder sb = new StringBuilder();
419                 while ((s = r.readLine()) != null) {
420                     sb.append(s);
421                     sb.append("\n");
422                 }
423                 code = sb.toString();
424             } catch (FileNotFoundException ex) {
425                 Exceptions.printStackTrace(ex);
426             } catch (IOException ex) {
427                 Exceptions.printStackTrace(ex);
428             } finally {
429                 try {
430                     is.close();
431                 } catch (IOException ex) {
432                     Exceptions.printStackTrace(ex);
433                 }
434                 lock.releaseLock();
435             }
436 
437             String displayName = fo.getName();
438 
439 
440             final CustomFilter cf = new CustomFilter(displayName, code);
441             map.put(displayName, cf);
442 
443             String after = (String) fo.getAttribute(AFTER_ID);
444             afterMap.put(cf, after);
445 
446             Boolean enabled = (Boolean) fo.getAttribute(ENABLED_ID);
447             if (enabled != null && (boolean) enabled) {
448                 enabledSet.add(cf);
449             }
450 
451             cf.getChangedEvent().addListener(new FilterChangedListener(fo, cf));
452 
453             customFilters.add(cf);
454         }
455 
456         for (int j = 0; j < customFilters.size(); j++) {
457             for (int i = 0; i < customFilters.size(); i++) {
458                 List<CustomFilter> copiedList = new ArrayList<>(customFilters);
459                 for (CustomFilter cf : copiedList) {
460 
461                     String after = afterMap.get(cf);
462 
463                     if (map.containsKey(after)) {
464                         CustomFilter afterCf = map.get(after);
465                         int index = customFilters.indexOf(afterCf);
466                         int currentIndex = customFilters.indexOf(cf);
467 
468                         if (currentIndex < index) {
469                             customFilters.remove(currentIndex);
470                             customFilters.add(index, cf);
471                         }
472                     }
473                 }
474             }
475         }
476 
477         for (CustomFilter cf : customFilters) {
478             sequence.addFilter(cf);
479             if (enabledSet.contains(cf)) {
480                 filterChain.addFilter(cf);
481             }
482         }
483     }
484 
485     /** This method is called from within the constructor to
486      * initialize the form.
487      * WARNING: Do NOT modify this code. The content of this method is
488      * always regenerated by the Form Editor.
489      */
490     // <editor-fold defaultstate="collapsed" desc=" Generated Code ">//GEN-BEGIN:initComponents
initComponents()491     private void initComponents() {
492 
493         setLayout(new java.awt.BorderLayout());
494 
495     }// </editor-fold>//GEN-END:initComponents
496     // Variables declaration - do not modify//GEN-BEGIN:variables
497     // End of variables declaration//GEN-END:variables
498     /**
499      * Gets default instance. Do not use directly: reserved for *.settings files only,
500      * i.e. deserialization routines; otherwise you could get a non-deserialized instance.
501      * To obtain the singleton instance, use {@link findInstance}.
502      */
getDefault()503     public static synchronized FilterTopComponent getDefault() {
504         if (instance == null) {
505             instance = new FilterTopComponent();
506         }
507         return instance;
508     }
509 
510     /**
511      * Obtain the FilterTopComponent instance. Never call {@link #getDefault} directly!
512      */
findInstance()513     public static synchronized FilterTopComponent findInstance() {
514         TopComponent win = WindowManager.getDefault().findTopComponent(PREFERRED_ID);
515         if (win == null) {
516             ErrorManager.getDefault().log(ErrorManager.WARNING, "Cannot find Filter component. It will not be located properly in the window system.");
517             return getDefault();
518         }
519         if (win instanceof FilterTopComponent) {
520             return (FilterTopComponent) win;
521         }
522         ErrorManager.getDefault().log(ErrorManager.WARNING, "There seem to be multiple components with the '" + PREFERRED_ID + "' ID. That is a potential source of errors and unexpected behavior.");
523         return getDefault();
524     }
525 
526     @Override
getPersistenceType()527     public int getPersistenceType() {
528         return TopComponent.PERSISTENCE_ALWAYS;
529     }
530 
531     @Override
preferredID()532     protected String preferredID() {
533         return PREFERRED_ID;
534     }
535 
536     @Override
getExplorerManager()537     public ExplorerManager getExplorerManager() {
538         return manager;
539     }
540 
541     @Override
componentOpened()542     public void componentOpened() {
543         Lookup.Template<FilterChain> tpl = new Lookup.Template<>(FilterChain.class);
544         result = Utilities.actionsGlobalContext().lookup(tpl);
545         result.addLookupListener(this);
546     }
547 
548     @Override
componentClosed()549     public void componentClosed() {
550         result.removeLookupListener(this);
551         result = null;
552     }
553 
554     @Override
resultChanged(LookupEvent lookupEvent)555     public void resultChanged(LookupEvent lookupEvent) {
556         setChain(Utilities.actionsGlobalContext().lookup(FilterChain.class));
557     }
558 
setChain(FilterChain chain)559     public void setChain(FilterChain chain) {
560         updateComboBoxSelection();
561     }
562 
getFileObject(CustomFilter cf)563     private FileObject getFileObject(CustomFilter cf) {
564         FileObject fo = FileUtil.getConfigRoot().getFileObject(FOLDER_ID + "/" + cf.getName());
565         if (fo == null) {
566             try {
567                 fo = FileUtil.getConfigRoot().getFileObject(FOLDER_ID).createData(cf.getName());
568             } catch (IOException ex) {
569                 Exceptions.printStackTrace(ex);
570             }
571         }
572         return fo;
573     }
574 
575     @Override
requestFocus(boolean temporary)576     public boolean requestFocus(boolean temporary) {
577         view.requestFocus();
578         return super.requestFocus(temporary);
579     }
580 
581     @Override
requestFocusInWindow(boolean temporary)582     protected boolean requestFocusInWindow(boolean temporary) {
583         view.requestFocus();
584         return super.requestFocusInWindow(temporary);
585     }
586 
587     @Override
requestActive()588     public void requestActive() {
589         super.requestActive();
590         view.requestFocus();
591     }
592 
593     @Override
writeExternal(ObjectOutput out)594     public void writeExternal(ObjectOutput out) throws IOException {
595         super.writeExternal(out);
596 
597         out.writeInt(filterSettings.size());
598         for (FilterSetting f : filterSettings) {
599             out.writeUTF(f.getName());
600 
601             out.writeInt(f.getFilterCount());
602             for (Filter filter : f.getFilters()) {
603                 CustomFilter cf = (CustomFilter) filter;
604                 out.writeUTF(cf.getName());
605             }
606         }
607 
608         CustomFilter prev = null;
609         for (Filter f : this.sequence.getFilters()) {
610             CustomFilter cf = (CustomFilter) f;
611             FileObject fo = getFileObject(cf);
612             if (getFilterChain().containsFilter(cf)) {
613                 fo.setAttribute(ENABLED_ID, true);
614             } else {
615                 fo.setAttribute(ENABLED_ID, false);
616             }
617 
618             if (prev == null) {
619                 fo.setAttribute(AFTER_ID, null);
620             } else {
621                 fo.setAttribute(AFTER_ID, prev.getName());
622             }
623 
624             prev = cf;
625         }
626     }
627 
findFilter(String name)628     public CustomFilter findFilter(String name) {
629         for (Filter f : sequence.getFilters()) {
630 
631             CustomFilter cf = (CustomFilter) f;
632             if (cf.getName().equals(name)) {
633                 return cf;
634             }
635         }
636 
637         return null;
638     }
639 
640     @Override
readExternal(ObjectInput in)641     public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
642         super.readExternal(in);
643 
644         int filterSettingsCount = in.readInt();
645         for (int i = 0; i < filterSettingsCount; i++) {
646             String name = in.readUTF();
647             FilterSetting s = new FilterSetting(name);
648             int filterCount = in.readInt();
649             for (int j = 0; j < filterCount; j++) {
650                 String filterName = in.readUTF();
651                 CustomFilter filter = findFilter(filterName);
652                 if (filter != null) {
653                     s.addFilter(filter);
654                 }
655             }
656 
657             filterSettings.add(s);
658         }
659         updateComboBox();
660     }
661 
662     final static class ResolvableHelper implements Serializable {
663 
664         private static final long serialVersionUID = 1L;
665 
readResolve()666         public Object readResolve() {
667             return FilterTopComponent.getDefault();
668         }
669     }
670 }
671