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