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