001 /* 002 * This file is part of McIDAS-V 003 * 004 * Copyright 2007-2013 005 * Space Science and Engineering Center (SSEC) 006 * University of Wisconsin - Madison 007 * 1225 W. Dayton Street, Madison, WI 53706, USA 008 * https://www.ssec.wisc.edu/mcidas 009 * 010 * All Rights Reserved 011 * 012 * McIDAS-V is built on Unidata's IDV and SSEC's VisAD libraries, and 013 * some McIDAS-V source code is based on IDV and VisAD source code. 014 * 015 * McIDAS-V is free software; you can redistribute it and/or modify 016 * it under the terms of the GNU Lesser Public License as published by 017 * the Free Software Foundation; either version 3 of the License, or 018 * (at your option) any later version. 019 * 020 * McIDAS-V is distributed in the hope that it will be useful, 021 * but WITHOUT ANY WARRANTY; without even the implied warranty of 022 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 023 * GNU Lesser Public License for more details. 024 * 025 * You should have received a copy of the GNU Lesser Public License 026 * along with this program. If not, see http://www.gnu.org/licenses. 027 */ 028 029 package edu.wisc.ssec.mcidasv.chooser.adde; 030 031 import static edu.wisc.ssec.mcidasv.servermanager.AddeEntry.DEFAULT_ACCOUNT; 032 import static edu.wisc.ssec.mcidasv.util.CollectionHelpers.arrList; 033 import static edu.wisc.ssec.mcidasv.McIDASV.isLoopback; 034 035 import static javax.swing.GroupLayout.DEFAULT_SIZE; 036 import static javax.swing.GroupLayout.Alignment.BASELINE; 037 import static javax.swing.GroupLayout.Alignment.LEADING; 038 import static javax.swing.GroupLayout.Alignment.TRAILING; 039 import static javax.swing.LayoutStyle.ComponentPlacement.RELATED; 040 import static javax.swing.LayoutStyle.ComponentPlacement.UNRELATED; 041 042 import java.awt.Component; 043 import java.awt.event.ActionEvent; 044 import java.awt.event.ActionListener; 045 import java.awt.event.ItemEvent; 046 import java.awt.event.ItemListener; 047 import java.awt.event.KeyEvent; 048 import java.awt.event.KeyListener; 049 import java.awt.event.MouseAdapter; 050 import java.awt.event.MouseEvent; 051 import java.io.EOFException; 052 import java.io.InputStream; 053 import java.net.ConnectException; 054 import java.net.URL; 055 import java.net.URLConnection; 056 import java.util.ArrayList; 057 import java.util.Arrays; 058 import java.util.Collections; 059 import java.util.Comparator; 060 import java.util.Enumeration; 061 import java.util.HashMap; 062 import java.util.Hashtable; 063 import java.util.LinkedHashMap; 064 import java.util.List; 065 import java.util.Map; 066 import java.util.Vector; 067 068 import javax.swing.GroupLayout; 069 import javax.swing.JButton; 070 import javax.swing.JCheckBox; 071 import javax.swing.JComboBox; 072 import javax.swing.JComponent; 073 import javax.swing.JLabel; 074 import javax.swing.JMenu; 075 import javax.swing.JMenuItem; 076 import javax.swing.JPanel; 077 import javax.swing.JPopupMenu; 078 import javax.swing.JTabbedPane; 079 import javax.swing.JTextField; 080 import javax.swing.SwingUtilities; 081 082 import org.bushe.swing.event.annotation.AnnotationProcessor; 083 import org.bushe.swing.event.annotation.EventSubscriber; 084 import org.slf4j.Logger; 085 import org.slf4j.LoggerFactory; 086 import org.w3c.dom.Element; 087 088 import edu.wisc.ssec.mcidas.adde.AddeURLException; 089 import edu.wisc.ssec.mcidas.adde.DataSetInfo; 090 091 import visad.DateTime; 092 093 import ucar.unidata.idv.chooser.IdvChooser; 094 import ucar.unidata.idv.chooser.IdvChooserManager; 095 import ucar.unidata.idv.chooser.adde.AddeServer; 096 import ucar.unidata.idv.chooser.adde.AddeServer.Group; 097 import ucar.unidata.util.DatedThing; 098 import ucar.unidata.util.GuiUtils; 099 import ucar.unidata.util.LogUtil; 100 import ucar.unidata.util.Misc; 101 import ucar.unidata.util.PreferenceList; 102 import ucar.unidata.util.StringUtil; 103 import ucar.unidata.xml.XmlObjectStore; 104 105 import edu.wisc.ssec.mcidasv.Constants; 106 import edu.wisc.ssec.mcidasv.McIDASV; 107 import edu.wisc.ssec.mcidasv.ParameterSet; 108 import edu.wisc.ssec.mcidasv.PersistenceManager; 109 import edu.wisc.ssec.mcidasv.servermanager.AddeAccount; 110 import edu.wisc.ssec.mcidasv.servermanager.AddeEntry; 111 import edu.wisc.ssec.mcidasv.servermanager.AddeEntry.EditorAction; 112 import edu.wisc.ssec.mcidasv.servermanager.AddeEntry.EntryType; 113 import edu.wisc.ssec.mcidasv.servermanager.EntryStore; 114 import edu.wisc.ssec.mcidasv.servermanager.EntryTransforms; 115 import edu.wisc.ssec.mcidasv.servermanager.LocalEntryEditor; 116 import edu.wisc.ssec.mcidasv.servermanager.RemoteAddeEntry; 117 import edu.wisc.ssec.mcidasv.servermanager.RemoteEntryEditor; 118 import edu.wisc.ssec.mcidasv.servermanager.TabbedAddeManager; 119 import edu.wisc.ssec.mcidasv.ui.ParameterTree; 120 import edu.wisc.ssec.mcidasv.ui.UIManager; 121 import edu.wisc.ssec.mcidasv.util.CollectionHelpers; 122 import edu.wisc.ssec.mcidasv.util.McVGuiUtils; 123 import edu.wisc.ssec.mcidasv.util.McVGuiUtils.Position; 124 import edu.wisc.ssec.mcidasv.util.McVGuiUtils.TextColor; 125 import edu.wisc.ssec.mcidasv.util.McVGuiUtils.Width; 126 127 /** 128 * 129 * @version $Revision$ 130 */ 131 public class AddeChooser extends ucar.unidata.idv.chooser.adde.AddeChooser implements Constants { 132 133 private static final Logger logger = LoggerFactory.getLogger(AddeChooser.class); 134 135 private JComboBox serverSelector; 136 137 /** List of descriptors */ 138 private PreferenceList descList; 139 140 /** Descriptor/name hashtable */ 141 protected Hashtable descriptorTable; 142 143 /** List of available descriptors. */ 144 protected List<String> descriptorList; 145 146 /** List of comments associated with list of descriptors. */ 147 protected List<String> commentList; 148 149 /** Property for the descriptor table */ 150 public static final String DESCRIPTOR_TABLE = "DESCRIPTOR_TABLE"; 151 152 /** Connect button--we need to be able to disable this */ 153 JButton connectButton = McVGuiUtils.makeImageTextButton(ICON_CONNECT_SMALL, "Connect"); 154 155 /** Parameter button--we need to be able to disable this */ 156 JButton parameterButton = 157 McVGuiUtils.makeImageButton("/edu/wisc/ssec/mcidasv/resources/icons/toolbar/document-open22.png", 158 this, "doParameters", null, "Load parameter set"); 159 160 /** Manage button */ 161 JButton manageButton = 162 McVGuiUtils.makeImageButton("/edu/wisc/ssec/mcidasv/resources/icons/toolbar/preferences-system22.png", 163 this, "doManager", null, "Manage servers"); 164 165 /** Public button--we need to draw a menu from this */ 166 JButton publicButton = 167 McVGuiUtils.makeImageButton("/edu/wisc/ssec/mcidasv/resources/icons/toolbar/show-layer-controls22.png", 168 this, "showGroups", null, "List public datasets"); 169 170 /** descriptor label */ 171 protected JLabel descriptorLabel = new JLabel(getDescriptorLabel()+":"); 172 173 /** A widget for the list of dataset descriptors */ 174 protected JComboBox descriptorComboBox = new JComboBox(); 175 176 /** The descriptor names */ 177 protected String[] descriptorNames; 178 179 /** Flag to keep from infinite looping */ 180 protected boolean ignoreDescriptorChange = false; 181 182 /** 183 * List of JComponent-s that depend on a descriptor being selected 184 * to be enabled 185 */ 186 protected ArrayList compsThatNeedDescriptor = new ArrayList(); 187 188 /** Selection label text */ 189 protected String LABEL_SELECT = " -- Select -- "; 190 191 /** Separator string */ 192 protected static String separator = "----------------"; 193 194 /** Name separator string */ 195 protected static String nameSeparator = " - "; 196 197 /** Reference back to the server manager */ 198 protected EntryStore serverManager; 199 200 public boolean allServersFlag; 201 202 /** Command for opening up the server manager */ 203 protected static final String CMD_MANAGER = "cmd.manager"; 204 205 private String lastBadServer = ""; 206 private String lastBadGroup = ""; 207 208 private String lastServerName = ""; 209 private String lastServerGroup = ""; 210 private String lastServerUser = ""; 211 private String lastServerProj = ""; 212 private AddeServer lastServer = new AddeServer(""); 213 214 private List<AddeServer> addeServers; 215 216 /** Used for parameter set restore */ 217 private static final String TAG_FOLDER = "folder"; 218 private static final String TAG_DEFAULT = "default"; 219 private static final String ATTR_NAME = "name"; 220 private static final String ATTR_SERVER = "server"; 221 private static final String ATTR_GROUP = "GROUP"; 222 private static final String ATTR_DESCRIPTOR = "DESCRIPTOR"; 223 private static final String ATTR_POS = "POS"; 224 private static final String ATTR_DAY = "DAY"; 225 private static final String ATTR_TIME = "TIME"; 226 private List restoreTimes = new ArrayList(); 227 public Element restoreElement; 228 private boolean shouldAddSource = false; 229 final JCheckBox cb = new JCheckBox("Add source",shouldAddSource); 230 231 /** Maps favorite type to the BundleTree that shows the Manage window for the type */ 232 private Hashtable parameterTrees = new Hashtable(); 233 234 /** 235 * Create an AddeChooser associated with an IdvChooser 236 * 237 * @param mgr The chooser manager 238 * @param root The chooser.xml node 239 */ 240 public AddeChooser(IdvChooserManager mgr, Element root) { 241 super(mgr, root); 242 AnnotationProcessor.process(this); 243 descriptorList = new ArrayList<String>(); 244 commentList = new ArrayList<String>(); 245 246 simpleMode = !getProperty(IdvChooser.ATTR_SHOWDETAILS, true); 247 248 loadButton = McVGuiUtils.makeImageTextButton(ICON_ACCEPT_SMALL, getLoadCommandName()); 249 loadButton.setActionCommand(getLoadCommandName()); 250 loadButton.addActionListener(this); 251 252 cancelButton = McVGuiUtils.makeImageButton(ICON_CANCEL, "Cancel"); 253 cancelButton.setActionCommand(GuiUtils.CMD_CANCEL); 254 cancelButton.addActionListener(this); 255 cancelButton.setEnabled(false); 256 257 serverSelector = getServerSelector(); 258 259 serverSelector.setToolTipText("Right click to manage servers"); 260 serverSelector.getEditor().getEditorComponent().addMouseListener( 261 new MouseAdapter() { 262 public void mouseReleased(MouseEvent e) { 263 if (!SwingUtilities.isRightMouseButton(e)) { 264 return; 265 } 266 267 AddeServer server = getAddeServer(); 268 if (server == null) { 269 return; 270 } 271 List<JMenuItem> items = new ArrayList<JMenuItem>(); 272 273 // Set the right-click behavior 274 if (isLocalServer()) { 275 items.add(GuiUtils.makeMenuItem("Manage local ADDE data", 276 AddeChooser.this, 277 "doManager", null)); 278 } 279 else { 280 items.add(GuiUtils.makeMenuItem("Manage ADDE servers", 281 AddeChooser.this, 282 "doManager", null)); 283 } 284 JPopupMenu popup = GuiUtils.makePopupMenu(items); 285 popup.show(serverSelector, e.getX(), e.getY()); 286 } 287 }); 288 serverSelector.setMaximumRowCount(16); 289 290 groupSelector.setToolTipText("Right click to manage servers"); 291 groupSelector.getEditor().getEditorComponent().addMouseListener( 292 new MouseAdapter() { 293 public void mouseReleased(MouseEvent e) { 294 if (!SwingUtilities.isRightMouseButton(e)) { 295 return; 296 } 297 298 AddeServer server = getAddeServer(); 299 if (server == null) { 300 return; 301 } 302 List<JMenuItem> items = new ArrayList<JMenuItem>(); 303 304 // Set the right-click behavior 305 if (isLocalServer()) { 306 items.add(GuiUtils.makeMenuItem("Manage local ADDE data", 307 AddeChooser.this, "doManager", null)); 308 } 309 else { 310 items.add(GuiUtils.makeMenuItem("Manage ADDE servers", 311 AddeChooser.this, "doManager", null)); 312 } 313 JPopupMenu popup = GuiUtils.makePopupMenu(items); 314 popup.show(groupSelector, e.getX(), e.getY()); 315 } 316 }); 317 groupSelector.setMaximumRowCount(16); 318 319 // serverManager = ((McIDASV)getIdv()).getServerManager(); 320 // serverManager.addManagedChooser(this); 321 addServerComp(descriptorLabel); 322 // addServerComp(descriptorComboBox); 323 324 descriptorComboBox.addItemListener(new ItemListener() { 325 public void itemStateChanged(ItemEvent e) { 326 if ( !ignoreDescriptorChange 327 && (e.getStateChange() == e.SELECTED)) { 328 descriptorChanged(); 329 } 330 } 331 }); 332 333 // Update the server list and load the saved state 334 updateServerList(); 335 loadServerState(); 336 337 // Default to no parameter button unless the overriding class wants one 338 hideParameterButton(); 339 } 340 341 /** 342 * Force a reload of the available servers and groups. 343 */ 344 public void updateServerList() { 345 updateServers(); 346 updateGroups(); 347 } 348 349 /** 350 * Returns a {@link java.util.Map Map} containing {@code user} and {@code proj} 351 * keys for the given {@code server/group} combination. 352 * 353 * <p>The values are either the specific ADDE account details for 354 * {@code server/group} or {@link edu.wisc.ssec.mcidasv.servermanager.AddeEntry#DEFAULT_ACCOUNT DEFAULT_ACCOUNT} 355 * values. 356 * 357 * @param server Server name. Should not be {@code null}. 358 * @param group Group name on {@code name}. Should not be {@code null}. 359 * 360 * @return {@code Map} containing the accounting details for {@code server/group}. 361 */ 362 protected Map<String, String> getAccounting(final String server, final String group) { 363 Map<String, String> acctInfo = new HashMap<String, String>(); 364 EntryStore entryStore = ((McIDASV)getIdv()).getServerManager(); 365 String strType = this.getDataType(); 366 EntryType type = EntryTransforms.strToEntryType(strType); 367 AddeAccount acct = entryStore.getAccountingFor(server, group, type); 368 acctInfo.put("user", acct.getUsername()); 369 acctInfo.put("proj", acct.getProject()); 370 return acctInfo; 371 } 372 373 /** 374 * Returns a {@link java.util.Map Map} containing {@code user} and {@code proj} 375 * keys for the given {@code server/group} combination. 376 * 377 * <p>The values are either the specific ADDE account details for 378 * {@code server/group} or {@link edu.wisc.ssec.mcidasv.servermanager.AddeEntry#DEFAULT_ACCOUNT DEFAULT_ACCOUNT} 379 * values. 380 * 381 * @param server Server name. Should not be {@code null}. 382 * @param group Group name on {@code name}. Should not be {@code null}. 383 * 384 * @return {@code Map} containing the accounting details for {@code server/group}. 385 */ 386 protected Map<String, String> getAccounting(final AddeServer server, final String group) { 387 return getAccounting(server.getName(), group); 388 } 389 390 private List<AddeServer> getManagedServers(final String type) { 391 EntryStore entryStore = ((McIDASV)getIdv()).getServerManager(); 392 return arrList(entryStore.getIdvStyleEntries(type)); 393 } 394 395 public void updateServers() { 396 Object selected = serverSelector.getSelectedItem(); 397 398 String type = getGroupType(); 399 List<AddeServer> managedServers = getManagedServers(type); 400 List<AddeServer> localList = arrList(); 401 List<AddeServer> remoteList = arrList(); 402 addeServers = CollectionHelpers.arrList(); 403 for (AddeServer server : managedServers) { 404 if (server.getIsLocal()) 405 localList.add(server); 406 else 407 remoteList.add(server); 408 } 409 410 // logger.debug("{}: updateServers: local size={} contents={}", new Object[] { getDataType(), localList.size(), localList }); 411 // logger.debug("{}: updateServers: remote size={} contents={}", new Object[] { getDataType(), remoteList.size(), remoteList }); 412 413 // server list doesn't need a separator if there's only remote servers 414 if (!localList.isEmpty()) { 415 addeServers.addAll(localList); 416 addeServers.add(new AddeServer(separator)); 417 } 418 Comparator<AddeServer> byServer = new ServerComparator(); 419 Collections.sort(remoteList, byServer); 420 addeServers.addAll(remoteList); 421 422 // always making this call helps to ensure the chooser stays up to date 423 // with the server manager. 424 GuiUtils.setListData(serverSelector, addeServers); 425 if (!addeServers.isEmpty()) { 426 if (selected == null || !containsServerName(addeServers, selected)) { 427 selected = serverSelector.getItemAt(0); 428 // logger.debug("updateServers: selecting item at idx=0, item={} chooser={}", selected, this.getDataType()); 429 } 430 431 int index = getSelectorIndex(selected, serverSelector); 432 serverSelector.setSelectedIndex(index); 433 } 434 } 435 436 /** 437 * Searches the given {@link java.util.List List} of {@link ucar.unidata.idv.chooser.adde.AddeServer AddeServers} 438 * for {@code server}. 439 * 440 * @param servers Servers to search. {@code null} is permitted. 441 * @param server Server to search for within {@code servers}. {@code null} is permitted. 442 * 443 * @return {@code true} if {@code servers} contains {@code server} or {@code false} otherwise. 444 */ 445 protected static boolean containsServerName(final List<AddeServer> servers, final Object server) { 446 if (servers == null || server == null) { 447 return false; 448 } 449 String serverName = (server instanceof AddeServer) ? ((AddeServer)server).getName() : server.toString(); 450 for (AddeServer tmp : servers) { 451 if (tmp.getName().equals(serverName)) { 452 return true; 453 } 454 } 455 return false; 456 } 457 458 /** 459 * Searches the given {@link java.util.List List} of {@link ucar.unidata.idv.chooser.adde.AddeServer.Group Groups} 460 * for {@code group}. 461 * 462 * @param groups Groups to search. {@code null} is permitted. 463 * @param group Group to search for within {@code group}. {@code null} is permitted. 464 * 465 * @return {@code true} if {@code groups} contains {@code group} or {@code false} otherwise. 466 */ 467 protected static boolean containsGroupName(final List<Group> groups, final Object group) { 468 if (groups == null || group == null) { 469 return false; 470 } 471 String groupName = (group instanceof Group) ? ((Group)group).getName() : group.toString(); 472 for (Group tmp : groups) { 473 if (tmp.getName().equals(groupName)) { 474 return true; 475 } 476 } 477 return false; 478 } 479 480 /** 481 * Sort the groups alphabetically 482 */ 483 public void updateGroups() { 484 if (addingServer || groupSelector == null || getAddeServer() == null) 485 return; 486 487 Object selected = groupSelector.getSelectedItem(); 488 489 EntryStore servManager = ((McIDASV)getIdv()).getServerManager(); 490 491 List<Group> groups = CollectionHelpers.arrList(); 492 if (isLocalServer()) { 493 groups.addAll(servManager.getIdvStyleLocalGroups()); 494 } else { 495 String sel = null; 496 Object obj = serverSelector.getSelectedItem(); 497 if (obj instanceof String) { 498 sel = (String)obj; 499 // logger.debug("updateGroups: string={} chooser={}", sel, this.getDataType()); 500 } else if (obj instanceof AddeServer) { 501 sel = ((AddeServer)obj).getName(); 502 // logger.debug("updateGroups: server selection={} chooser={}", sel, this.getDataType()); 503 } else { 504 sel = obj.toString(); 505 // logger.debug("updateGroups: unknown type={}; toString={}", sel.getClass().getName(), sel); 506 } 507 508 EntryType selType = EntryTransforms.strToEntryType(getGroupType()); 509 groups.addAll(servManager.getIdvStyleRemoteGroups(sel, selType)); 510 } 511 // logger.trace("updateGroups: selected={} (type={}) chooser={} contents={}", new Object[] { serverSelector.getSelectedItem(), serverSelector.getSelectedItem().getClass().getName(), this.getDataType(), groups}); 512 Comparator<Group> byGroup = new GroupComparator(); 513 Collections.sort(groups, byGroup); 514 GuiUtils.setListData(groupSelector, groups); 515 if (!groups.isEmpty()) { 516 if (selected == null || !containsGroupName(groups, selected)) { 517 selected = groupSelector.getItemAt(0); 518 } 519 groupSelector.setSelectedItem(selected); 520 } 521 } 522 523 /** 524 * Load any saved server state 525 */ 526 //TODO: Make loadServerState protected in IDV, remove from here 527 private void loadServerState() { 528 if (addeServers == null) { 529 // logger.debug("loadServerState: addeServers == null chooser={}", this.getDataType()); 530 return; 531 } 532 String id = getId(); 533 String[] serverState = 534 (String[]) getIdv().getStore().get(Constants.PREF_SERVERSTATE + '.' + id); 535 if (serverState == null) { 536 // serverState = Constants.DEFAULT_SERVERSTATE; 537 // logger.debug("loadServerState: serverState == null chooser={}",this.getDataType()); 538 return; 539 } 540 AddeServer server = AddeServer.findServer(addeServers, serverState[0]); 541 if (server == null) { 542 // logger.debug("loadServerState: server == null chooser={}",this.getDataType()); 543 return; 544 } 545 // logger.debug("loadServerState: selecting server={} chooser={}", server, this.getDataType()); 546 serverSelector.setSelectedItem(server); 547 setGroups(); 548 updateGroups(); 549 if (serverState[1] != null) { 550 Group group = new Group(getDataType(), serverState[1], serverState[1]); 551 int index = getSelectorIndex(group, groupSelector); 552 if (index >= 0) { 553 // logger.debug("loadServerState: selecting index={} group={} chooser={}", new Object[] { index, group, this.getDataType() }); 554 groupSelector.setSelectedIndex(index); 555 } else { 556 // logger.debug("loadServerState: group == null chooser={}", this.getDataType()); 557 } 558 } else { 559 // logger.debug("loadServerState: serverState[1] == null chooser={}", this.getDataType()); 560 } 561 } 562 563 /** 564 * Decide if the server you're asking about is actually a separator 565 */ 566 protected static boolean isSeparator(AddeServer checkServer) { 567 if (checkServer != null) { 568 if (checkServer.getName().equals(separator)) { 569 return true; 570 } 571 } 572 return false; 573 } 574 575 /** 576 * Decide if the server you're asking about is local 577 */ 578 protected boolean isLocalServer() { 579 return isLocalServer(getAddeServer()); 580 } 581 582 protected static boolean isLocalServer(AddeServer checkServer) { 583 if (checkServer != null) { 584 return checkServer.getIsLocal(); 585 } 586 return false; 587 } 588 589 private void setBadServer(String name, String group) { 590 if (name == null) { 591 name = ""; 592 } 593 if (group == null) { 594 group = ""; 595 } 596 597 lastBadServer = name; 598 lastBadGroup = group; 599 } 600 601 private boolean isBadServer(String name, String group) { 602 assert lastBadServer != null; 603 assert lastBadGroup != null; 604 return lastBadServer.equals(name) && lastBadGroup.equals(group); 605 } 606 607 private void setLastServer(String name, String group, AddeServer server) { 608 // logger.trace("name='{}' group='{}' server='{}' old: name='{}' group='{}' server='{}'", new Object[] { name, group, server, lastServerName, lastServerGroup, lastServer }); 609 if (name == null) { 610 name = ""; 611 } 612 if (group == null) { 613 group = ""; 614 } 615 if (server == null) { 616 server = new AddeServer(name); 617 Group addeGroup = new Group(getDataType(), group, group); 618 server.addGroup(addeGroup); 619 } 620 lastServerName = name; 621 lastServerGroup = group; 622 lastServer = server; 623 } 624 625 private boolean isLastServer(String name, String group) { 626 assert lastServer != null; 627 assert lastServerName != null; 628 assert lastServerGroup != null; 629 return lastServerName.equals(name) && lastServerGroup.equals(group); 630 } 631 632 @EventSubscriber(eventClass=EntryStore.Event.class) 633 public void onServerManagerDataEvent(EntryStore.Event evt) { 634 EntryStore servManager = ((McIDASV)getIdv()).getServerManager(); 635 // logger.debug("onServerManagerDataEvent: evt={} server={}", evt, servManager.getLastAdded()); 636 this.updateServerList(); 637 } 638 639 @EventSubscriber(eventClass=TabbedAddeManager.Event.class) 640 public void onServerManagerWindowEvent(TabbedAddeManager.Event evt) { 641 // logger.debug("onServerManagerWindowEvent: caught event bus obj"); 642 } 643 644 private boolean addingServer = false; 645 646 /** 647 * Search a given {@link JComboBox} for the index of a given object. Mostly 648 * useful for searching {@link #serverSelector} or {@link #groupSelector}. 649 * 650 * @param needle An object. {@code null} values are permitted. 651 * @param haystack {@code JComboBox} to search. {@code null} values are 652 * permitted, but return {@code -1}. 653 * 654 * @return Either the index of {@code needle} within {@code haystack}, or 655 * {@code -1} if {@code needle} could not be found (or {@code haystack} is 656 * {@code null}). 657 */ 658 protected static int getSelectorIndex(final Object needle, 659 final JComboBox haystack) 660 { 661 if (haystack == null) { 662 return -1; 663 } 664 665 String name = null; 666 if (needle instanceof AddeServer) { 667 name = ((AddeServer)needle).getName(); 668 } else if (needle instanceof Group) { 669 name = ((Group)needle).getName(); 670 } else if (needle instanceof AddeEntry) { 671 name = ((AddeEntry)needle).getAddress(); 672 } else { 673 name = needle.toString(); 674 } 675 676 if (isLoopback(name)) { 677 return 0; 678 } 679 680 for (int i = 0; i < haystack.getItemCount(); i++) { 681 Object item = haystack.getItemAt(i); 682 String tmpName; 683 if (item instanceof AddeServer) { 684 tmpName = ((AddeServer)item).getName(); 685 } else { 686 tmpName = item.toString(); 687 } 688 689 if (name.equals(tmpName)) { 690 return i; 691 } 692 } 693 return -1; 694 } 695 696 /** 697 * Get the selected AddeServer 698 * 699 * @return the server or null 700 */ 701 protected AddeServer getAddeServer() { 702 if (lastServerName != null && lastServerName.equals("unset")) { 703 return null; 704 } 705 706 Object selected = serverSelector.getSelectedItem(); 707 if ((selected != null) && (selected instanceof AddeServer)) { 708 AddeServer server = (AddeServer)selected; 709 String group = getGroup(true); 710 Map<String, String> accounting = getAccounting(server, group); 711 // logger.trace("accounting: new: u='{}' p='{}' old: u='{}' p='{}'", new Object[] { accounting.get("user"), accounting.get("proj"), lastServerUser, lastServerProj }); 712 lastServerUser = accounting.get("user"); 713 lastServerProj = accounting.get("proj"); 714 setLastServer(server.getName(), group, server); 715 return (AddeServer)selected; 716 } else if ((selected != null) && (selected instanceof String)) { 717 718 EntryStore servManager = ((McIDASV)getIdv()).getServerManager(); 719 String server = (String)selected; 720 String group = getGroup(true); 721 722 if (isBadServer(server, group)) { 723 // logger.trace("getAddeServer: returning null; known bad server; server={} group={}", server, group); 724 return null; 725 } 726 727 if (isLastServer(server, group)) { 728 // logger.trace("getAddeServer: returning last server name; server={} group={}", server, group); 729 return lastServer; 730 } 731 732 EditorAction editorAction = EditorAction.INVALID; 733 if (!isLoopback(server)) { 734 RemoteEntryEditor editor = new RemoteEntryEditor(servManager, server, ""); 735 editor.setVisible(true); 736 editorAction = editor.getEditorAction(); 737 } else { 738 LocalEntryEditor editor = new LocalEntryEditor(servManager, group); 739 editor.setVisible(true); 740 editorAction = editor.getEditorAction(); 741 } 742 743 int servIndex = 0; 744 int groupIndex = 0; 745 746 if (editorAction != EditorAction.CANCELLED && editorAction != EditorAction.INVALID) { 747 748 List<AddeServer> added = arrList(EntryTransforms.convertMcvServers(servManager.getLastAddedByType(EntryTransforms.strToEntryType(getDataType())))); 749 AddeServer first = null; 750 if (!added.isEmpty()) { 751 first = added.get(0); 752 servIndex = getSelectorIndex(first, serverSelector); 753 setLastServer(server, group, first); 754 } 755 756 serverSelector.setSelectedIndex(servIndex); 757 groupSelector.setSelectedIndex(groupIndex); 758 // logger.trace("getAddeServer: serverIdx={} groupIdx={}", servIndex, groupIndex); 759 760 return first; 761 } else { 762 // logger.trace("getAddeServer: returning null due to cancel request"); 763 setBadServer(server, group); 764 return null; 765 } 766 767 768 769 } else if (selected == null) { 770 // logger.trace("getAddeServer: null object in selector; returning null"); 771 } else { 772 // logger.debug("getAddeServer: unknown obj type={}; toString={}", selected.getClass().getName(), selected.toString()); 773 } 774 return null; 775 } 776 777 /** 778 * A utility to add a component to the list of components that 779 * need the descriptor 780 * 781 * @param comp The component 782 * @return The component 783 */ 784 protected JComponent addDescComp(JComponent comp) { 785 compsThatNeedDescriptor.add(comp); 786 return comp; 787 } 788 789 /** 790 * Set LABEL_SELECT from elsewhere 791 */ 792 protected void setSelectString(String string) { 793 LABEL_SELECT = string; 794 } 795 796 /** 797 * Reset the descriptor stuff 798 */ 799 protected void resetDescriptorBox() { 800 ignoreDescriptorChange = true; 801 descriptorComboBox.setSelectedItem(LABEL_SELECT); 802 ignoreDescriptorChange = false; 803 } 804 805 /** 806 * Handle when the user presses the connect button 807 * 808 * @throws Exception On badness 809 */ 810 public void handleConnect() throws Exception { 811 AddeServer server = getAddeServer(); 812 if (server == null) { 813 return; 814 } 815 setState(STATE_CONNECTING); 816 connectToServer(); 817 handleUpdate(); 818 } 819 820 @Override protected void handleConnectionError(Exception e) { 821 if (e != null && e.getMessage() != null) { 822 String msg = e.getMessage(); 823 int msgPos = msg.indexOf("AddeURLException:"); 824 if (msgPos >= 0 && msg.length() > 18) { 825 msg = msg.substring(msgPos + 18); 826 setState(STATE_UNCONNECTED); 827 setHaveData(false); 828 resetDescriptorBox(); 829 GuiUtils.showDialog("ADDE Error", new JLabel(msg)); 830 return; 831 } 832 if (msg.indexOf("Connecting to server:localhost:") >= 0) { 833 setState(STATE_UNCONNECTED); 834 setHaveData(false); 835 resetDescriptorBox(); 836 GuiUtils.showDialog("ADDE Error", new JLabel("Local server is not responding")); 837 return; 838 } 839 } 840 super.handleConnectionError(e); 841 } 842 843 /** 844 * Handle unknown data set error 845 */ 846 @Override protected void handleUnknownDataSetError() { 847 String server = getServer(); 848 String group = getGroup(); 849 Map<String, String> acct = getAccounting(server, group); 850 String user = acct.get("user"); 851 String proj = acct.get("proj"); 852 853 StringBuilder msg = new StringBuilder("Could not connect to dataset \""); 854 msg.append(getGroup()).append("\" on server \"").append(getServer()).append("\"."); 855 if (DEFAULT_ACCOUNT.getUsername().equals(user) && DEFAULT_ACCOUNT.getProject().equals(proj)) { 856 msg.append("\n\nDataset may require ADDE accounting information."); 857 } else { 858 msg.append("\n\nAccounting information:\nusername: \"") 859 .append(user).append("\"\nproject: \"").append(proj).append('"'); 860 } 861 LogUtil.userErrorMessage(msg.toString()); 862 setState(STATE_UNCONNECTED); 863 } 864 865 /** 866 * Handle the event 867 * 868 * @param ae The event 869 */ 870 public void actionPerformed(ActionEvent ae) { 871 String cmd = ae.getActionCommand(); 872 if (cmd.equals(CMD_MANAGER)) { 873 doManager(); 874 } 875 else { 876 super.actionPerformed(ae); 877 } 878 } 879 880 /** 881 * Go directly to the Server Manager 882 */ 883 public void doManager() { 884 // if (isLocalServer()) { 885 // ((McIDASV)getIdv()).showAddeManager(); 886 // return; 887 // } 888 getIdv().getPreferenceManager().showTab(Constants.PREF_LIST_ADDE_SERVERS); 889 } 890 891 /** 892 * Show the parameter restore tree 893 */ 894 public void doParameters() { 895 JPopupMenu popup = new JPopupMenu(); 896 JMenuItem mi = new JMenuItem("Manage..."); 897 mi.addActionListener(new ActionListener() { 898 public void actionPerformed(ActionEvent ae) { 899 System.out.println(ae); 900 showParameterSetDialog(getParameterSetType()); 901 } 902 }); 903 popup.add(mi); 904 905 // Add the checkbox to automatically create a data source 906 cb.addActionListener(new ActionListener() { 907 public void actionPerformed(ActionEvent ae) { 908 shouldAddSource = cb.isSelected(); 909 } 910 }); 911 popup.addSeparator(); 912 popup.add(cb); 913 914 final PersistenceManager pm = (PersistenceManager)getIdv().getPersistenceManager(); 915 List<ParameterSet> parameterSets = pm.getAllParameterSets(getParameterSetType()); 916 917 for (int i=0; i<parameterSets.size(); i++) { 918 if (i==0) popup.addSeparator(); 919 final ParameterSet ps = parameterSets.get(i); 920 921 // Parameter set at root 922 if (ps.getCategories().size() == 0) { 923 mi = new JMenuItem(ps.getName()); 924 mi.addActionListener(new ActionListener() { 925 public void actionPerformed(ActionEvent ae) { 926 restoreParameterSet(ps.getElement()); 927 } 928 }); 929 popup.add(mi); 930 } 931 932 // Recurse into folders 933 else { 934 // Find or make the menu for the given parameter set 935 JMenu m = getPopupSubMenuForParameterSet(popup, ps); 936 // Create parameter set entry 937 mi = new JMenuItem(ps.getName()); 938 mi.addActionListener(new ActionListener() { 939 public void actionPerformed(ActionEvent ae) { 940 restoreParameterSet(ps.getElement()); 941 } 942 }); 943 m.add(mi); 944 } 945 946 } 947 948 popup.show(parameterButton, 0, (int) parameterButton.getBounds().getHeight()); 949 } 950 951 private JMenu getPopupSubMenuForParameterSet(JPopupMenu popup, final ParameterSet ps) { 952 List<String> menuNames = ps.getCategories(); 953 if (menuNames.size() < 1) return null; 954 955 // Build the complete menu 956 String menuName = menuNames.get(0); 957 menuNames.remove(0); 958 JMenu theMenu = new JMenu(); 959 960 // Look for the menu in popup 961 boolean found = false; 962 for (int i=0; i<popup.getComponentCount(); i++) { 963 Component thisComponent = popup.getComponent(i); 964 if (thisComponent instanceof JMenu && ((JMenu)thisComponent).getText().equals(menuName)) { 965 theMenu = mergeMenuNames((JMenu)thisComponent, menuNames); 966 found = true; 967 } 968 } 969 970 // Make a new menu, add the root, return the leaf 971 if (!found) { 972 JMenu theRoot = new JMenu(menuName); 973 theMenu = makeMenuRecursive(theRoot, menuNames); 974 popup.add(theRoot); 975 } 976 977 return theMenu; 978 } 979 980 /** 981 * Make a new recursive menu 982 * 983 * @param rootMenu The root menu to add items to 984 * @param menuNames List of string names for submenus 985 * @return A new JMenu representing the leaf 986 */ 987 private JMenu makeMenuRecursive(JMenu rootMenu, List<String> menuNames) { 988 if (menuNames.size() < 1) return rootMenu; 989 JMenu newMenu = new JMenu(menuNames.get(0)); 990 rootMenu.add(newMenu); 991 menuNames.remove(0); 992 return makeMenuRecursive(newMenu, menuNames); 993 } 994 995 /** 996 * Recurse into a menu, returning either a pointer to the designated names path 997 * or a pointer to the leaf menu added by merging new names 998 * 999 * @param thisMenu The root menu to merge 1000 * @param menuNames List of string names to look for 1001 * @return A new JMenu representing the leaf matched by menuNames 1002 */ 1003 private JMenu mergeMenuNames(JMenu thisMenu, List<String> menuNames) { 1004 if (menuNames.size() < 1) return thisMenu; 1005 boolean found = false; 1006 String menuName = menuNames.get(0); 1007 for (int i=0; i<thisMenu.getItemCount(); i++) { 1008 JMenuItem mi = thisMenu.getItem(i); 1009 if (!(mi instanceof JMenu)) continue; 1010 if (mi.getText().equals(menuName)) { 1011 menuNames.remove(0); 1012 thisMenu = mergeMenuNames((JMenu)mi, menuNames); 1013 found = true; 1014 } 1015 } 1016 if (!found) { 1017 thisMenu = makeMenuRecursive(thisMenu, menuNames); 1018 } 1019 return thisMenu; 1020 } 1021 1022 /** 1023 * Return the parameter type associated with this chooser. Override! 1024 */ 1025 protected String getParameterSetType() { 1026 return "adde"; 1027 } 1028 1029 /** 1030 * Show the parameter set manager 1031 */ 1032 private void showParameterSetDialog(final String parameterSetType) { 1033 ParameterTree tree = (ParameterTree) parameterTrees.get(parameterSetType); 1034 if (tree == null) { 1035 tree = new ParameterTree((UIManager)getIdv().getIdvUIManager() , parameterSetType); 1036 parameterTrees.put(parameterSetType, tree); 1037 } 1038 else { 1039 //DAVEP 1040 System.out.println("Should refresh the parameter tree here"); 1041 } 1042 tree.setVisible(true); 1043 } 1044 1045 /** 1046 * Clear the selected parameter set 1047 */ 1048 protected void clearParameterSet() { 1049 restoreElement = null; 1050 restoreTimes = new ArrayList(); 1051 shouldAddSource = false; 1052 } 1053 1054 /** 1055 * Restore the selected parameter set using element attributes 1056 * 1057 * @param restoreElement 1058 * @return 1059 */ 1060 protected boolean restoreParameterSet(Element restoreElement) { 1061 if (restoreElement == null) return false; 1062 if (!restoreElement.getTagName().equals("default")) return false; 1063 1064 this.restoreElement = restoreElement; 1065 1066 boolean oldISCE = ignoreStateChangedEvents; 1067 ignoreStateChangedEvents = true; 1068 1069 // Restore server 1070 String server = restoreElement.getAttribute(ATTR_SERVER); 1071 if (server != null) serverSelector.setSelectedItem(new AddeServer(server)); 1072 1073 // Restore group 1074 String group = restoreElement.getAttribute(ATTR_GROUP); 1075 if (group != null) groupSelector.setSelectedItem(group); 1076 1077 // Act as though the user hit "connect" 1078 readFromServer(); 1079 1080 // Restore descriptor 1081 String descriptor = restoreElement.getAttribute(ATTR_DESCRIPTOR); 1082 if (descriptor != null) { 1083 Enumeration enumeration = descriptorTable.keys(); 1084 for (int i = 0; enumeration.hasMoreElements(); i++) { 1085 String key = enumeration.nextElement().toString(); 1086 Object val = descriptorTable.get(key); 1087 if (descriptor.equals(val)) { 1088 descriptorComboBox.setSelectedItem(val + nameSeparator + key); 1089 descriptorChanged(); 1090 break; 1091 } 1092 } 1093 } 1094 1095 // Restore date/time 1096 if (restoreElement.hasAttribute(ATTR_POS)) { 1097 setDoAbsoluteTimes(false); 1098 Integer pos = new Integer(restoreElement.getAttribute(ATTR_POS)); 1099 if (pos.intValue() >= 0) { 1100 getRelativeTimesList().setSelectedIndex(pos); 1101 } 1102 restoreTimes = new ArrayList(); 1103 } 1104 else if ((restoreElement.hasAttribute(ATTR_DAY)) && (restoreElement.hasAttribute(ATTR_TIME))) { 1105 setDoAbsoluteTimes(true); 1106 String dateStr = restoreElement.getAttribute(ATTR_DAY); 1107 String timeStr = restoreElement.getAttribute(ATTR_TIME); 1108 List dateS = StringUtil.split(dateStr, ","); 1109 List timeS = StringUtil.split(timeStr, ","); 1110 int numImages = timeS.size(); 1111 restoreTimes = new ArrayList(); 1112 try { 1113 DateTime dt = new DateTime(); 1114 dt.resetFormat(); 1115 String dtformat = dt.getFormatPattern(); 1116 for (int ix=0; ix<numImages; ix++) { 1117 DateTime restoreTime = dt.createDateTime((String)dateS.get(ix) + " " + (String)timeS.get(ix)); 1118 restoreTimes.add(restoreTime); 1119 } 1120 } catch (Exception e) { 1121 System.out.println("Exception e=" + e); 1122 return false; 1123 } 1124 } 1125 1126 System.out.println("Returning from AddeChooser.restoreParameterSet()"); 1127 1128 ignoreStateChangedEvents = oldISCE; 1129 return true; 1130 } 1131 1132 /** 1133 * Set the absolute times list. The times list can contain any of the object types 1134 * that makeDatedObjects knows how to handle, i.e., Date, visad.DateTime, DatedThing, AddeImageDescriptor, etc. 1135 * 1136 * @param times List of thinggs to put into absolute times list 1137 */ 1138 protected void setAbsoluteTimes(List times) { 1139 super.setAbsoluteTimes(times); 1140 restoreAbsoluteTimes(); 1141 } 1142 1143 protected void restoreAbsoluteTimes() { 1144 List allTimes = makeDatedObjects(super.getAbsoluteTimes()); 1145 if (restoreTimes.size() > 0 && allTimes.size() > 0) { 1146 int[] indices = new int[restoreTimes.size()]; 1147 try { 1148 DateTime rtdt; 1149 DateTime atdt; 1150 DatedThing at; 1151 for (int i = 0; i < restoreTimes.size(); i++) { 1152 rtdt = (DateTime)restoreTimes.get(i); 1153 for (int j = 0; j < allTimes.size(); j++) { 1154 at = (DatedThing)allTimes.get(j); 1155 atdt = new DateTime(at.getDate()); 1156 if (atdt.equals(rtdt)) { 1157 indices[i] = j; 1158 } 1159 } 1160 } 1161 } catch (Exception e) { 1162 System.out.println("Exception e=" + e); 1163 } 1164 setSelectedAbsoluteTimes(indices); 1165 } 1166 } 1167 1168 /** 1169 * show/hide the parameter restore button 1170 */ 1171 public void showParameterButton() { 1172 parameterButton.setVisible(true); 1173 } 1174 1175 public void hideParameterButton() { 1176 parameterButton.setVisible(false); 1177 } 1178 1179 /** 1180 * Override and simulate clicking Add Source if requested 1181 */ 1182 public void setHaveData(boolean have) { 1183 super.setHaveData(have); 1184 if (have && shouldAddSource) { 1185 System.out.println("Adding source at setHaveData"); 1186 // Even though setHaveData should mean we can go, we can't... wait a few jiffies 1187 Misc.runInABit(100, AddeChooser.this, "doClickLoad", null); 1188 } 1189 } 1190 1191 public void doClickLoad() { 1192 loadButton.doClick(); 1193 } 1194 1195 public void showServers() { 1196 allServersFlag = !allServersFlag; 1197 XmlObjectStore store = getIdv().getStore(); 1198 store.put(Constants.PREF_SYSTEMSERVERSIMG, allServersFlag); 1199 store.save(); 1200 updateServers(); 1201 updateGroups(); 1202 } 1203 1204 protected String getStateString() { 1205 int state = getState(); 1206 switch (state) { 1207 case STATE_CONNECTED: return "Connected to server"; 1208 case STATE_UNCONNECTED: return "Not connected to server"; 1209 case STATE_CONNECTING: return "Connecting to server"; 1210 default: return "Unknown state: " + state; 1211 } 1212 } 1213 1214 /** 1215 * Disable/enable any components that depend on the server. 1216 * Try to update the status label with what we know here. 1217 */ 1218 protected void updateStatus() { 1219 super.updateStatus(); 1220 if (getState() == STATE_CONNECTED) { 1221 lastServer = new AddeServer(""); 1222 lastServerGroup = ""; 1223 lastServerName = ""; 1224 lastServerProj = ""; 1225 lastServerUser = ""; 1226 1227 if (!haveDescriptorSelected()) { 1228 if (!usingStations() || haveStationSelected()) { 1229 // String name = getDataName().toLowerCase(); 1230 String name = getDescriptorLabel().toLowerCase(); 1231 if (StringUtil.startsWithVowel(name)) { 1232 setStatus("Please select an " + name); 1233 } else { 1234 setStatus("Please select a " + name); 1235 } 1236 } 1237 } 1238 } 1239 1240 GuiUtils.enableTree(connectButton, getState() != STATE_CONNECTING); 1241 } 1242 1243 /** 1244 * Get the data type ID 1245 * 1246 * @return the data type 1247 */ 1248 public String getDataType() { 1249 return "ANY"; 1250 } 1251 1252 /** 1253 * Check if the server is ok 1254 * 1255 * @return status code 1256 */ 1257 protected int checkIfServerIsOk() { 1258 try { 1259 StringBuffer buff = getUrl(REQ_TEXT); 1260 appendKeyValue(buff, PROP_FILE, FILE_PUBLICSRV); 1261 URL url = new URL(buff.toString()); 1262 URLConnection urlc = url.openConnection(); 1263 InputStream is = urlc.getInputStream(); 1264 is.close(); 1265 return STATUS_OK; 1266 } catch (AddeURLException ae) { 1267 String aes = ae.toString(); 1268 if (aes.indexOf("Invalid project number") >= 0) { 1269 LogUtil.userErrorMessage("Invalid project number"); 1270 return STATUS_NEEDSLOGIN; 1271 } 1272 if (aes.indexOf("Invalid user id") >= 0) { 1273 LogUtil.userErrorMessage("Invalid user ID"); 1274 return STATUS_NEEDSLOGIN; 1275 } 1276 if (aes.indexOf("Accounting data") >= 0) { 1277 return STATUS_NEEDSLOGIN; 1278 } 1279 if (aes.indexOf("cannot run server 'txtgserv'") >= 0) { 1280 return STATUS_OK; 1281 } 1282 LogUtil.userErrorMessage("Error connecting to server " + getServer() + ":\n" 1283 + ae.getMessage()); 1284 return STATUS_ERROR; 1285 } catch (ConnectException exc) { 1286 setState(STATE_UNCONNECTED); 1287 setHaveData(false); 1288 resetDescriptorBox(); 1289 String message = "Error connecting to server " + getServer(); 1290 if (isLocalServer()) 1291 message += "\n\nLocal servers can be restarted from the\n'Local ADDE Data Manager' in the 'Tools' menu"; 1292 LogUtil.userErrorMessage(message); 1293 return STATUS_ERROR; 1294 } catch (EOFException exc) { 1295 setState(STATE_UNCONNECTED); 1296 setHaveData(false); 1297 resetDescriptorBox(); 1298 LogUtil.userErrorMessage("Server " + getServer() + " is not responding"); 1299 return STATUS_ERROR; 1300 } catch (Exception exc) { 1301 logException("Connecting to server: " + getServer(), exc); 1302 return STATUS_ERROR; 1303 } 1304 } 1305 1306 public boolean canAccessServer() { 1307 // Set<Types> defaultTypes = EnumSet.of(ServerPropertyDialog.convertDataType(getDataType())); 1308 // while (true) { 1309 // int status = checkIfServerIsOk(); 1310 // if (status == STATUS_OK) { 1311 // break; 1312 // } 1313 // if (status == STATUS_ERROR) { 1314 // setState(STATE_UNCONNECTED); 1315 // return false; 1316 // } 1317 // 1318 //// AddeServer server = getAddeServer(); 1319 // AddeServer server = getAddeServer2(serverSelector, groupSelector); 1320 // Map<String, String> accounting = serverManager.getAccountingFor(server, type) 1321 // 1322 // String name = server.getName(); 1323 // String group = getGroup(); 1324 // String user = accounting.get("user"); 1325 // String proj = accounting.get("proj"); 1326 // 1327 // ServerPropertyDialog dialog = new ServerPropertyDialog(null, true, serverManager); 1328 // dialog.setTitle("Edit Server Information"); 1329 // dialog.showDialog(name, group, user, proj, defaultTypes); 1330 // 1331 // if (!dialog.getAddedDatasetDescriptors().isEmpty()) { 1332 // System.err.println("verified info: " + dialog.getAddedDatasetDescriptors()); 1333 // break; 1334 // } 1335 // } 1336 return true; 1337 } 1338 1339 public Map<String, String> getAccountingInfo() { 1340 AddeServer server = getAddeServer(); 1341 Map<String, String> map = new LinkedHashMap<String, String>(); 1342 if (server != null) { 1343 List<AddeServer.Group> groups = server.getGroups(); 1344 Map<String, String>acctInfo = getAccounting(server, groups.get(0).toString()); 1345 map.put("user", acctInfo.get("user")); 1346 map.put("proj", acctInfo.get("proj")); 1347 map.put("server", server.getName()); 1348 map.put("group", getGroup()); 1349 } else { 1350 map.put("user", RemoteAddeEntry.DEFAULT_ACCOUNT.getUsername()); 1351 map.put("proj", RemoteAddeEntry.DEFAULT_ACCOUNT.getUsername()); 1352 map.put("server", ""); 1353 map.put("group", ""); 1354 } 1355 return map; 1356 } 1357 1358 /** 1359 * Saves the currently selected server and group to a chooser-specific 1360 * preference. Preference ID is {@code PREF_SERVERSTATE+'.'+getId()}. 1361 */ 1362 @Override public void saveServerState() { 1363 String[] serverState = { getServer(), getGroup() }; 1364 getIdv().getStore().put(PREF_SERVERSTATE+'.'+getId(), serverState); 1365 getIdv().getStore().save(); 1366 } 1367 1368 /** 1369 * Connect to the server. 1370 */ 1371 protected void connectToServer() { 1372 clearParameterSet(); 1373 setDescriptors(null); 1374 setDoAbsoluteTimes(false); 1375 if (!canAccessServer()) { 1376 logger.debug("couldn't connect! shucks! golly!"); 1377 return; 1378 } else { 1379 logger.debug("you have successfully used the server manager! it is a miracle!"); 1380 } 1381 readFromServer(); 1382 saveServerState(); 1383 ignoreStateChangedEvents = true; 1384 if (descList != null) { 1385 descList.saveState(groupSelector); 1386 } 1387 ignoreStateChangedEvents = false; 1388 } 1389 1390 /** 1391 * Do server connection stuff... override this with type-specific methods 1392 */ 1393 protected void readFromServer() { 1394 readDescriptors(); 1395 readTimes(); 1396 } 1397 1398 // what the request needs to look like: 1399 // adde://localhost:8112/imagedata?&PORT=112&COMPRES S=gzip&USER=idv&PROJ=0 1400 // &VERSION=1&DEBUG=false&TRAC E=0&GROUP=MYDATA&DESCRIPTOR=ENTRY4&BAND=1 1401 // &LATLON= 30.37139 71.74912&PLACE=CENTER&SIZE=1000 1000&UNI T=BRIT 1402 // &MAG=1 1&SPAC=1&NAV=X&AUX=YES&DOC=X&POS=0 1403 1404 /** 1405 * Generate a list of image descriptors for the descriptor list. 1406 */ 1407 protected void readDescriptors() { 1408 try { 1409 StringBuffer buff = getGroupUrl(REQ_DATASETINFO, getGroup()); 1410 buff.append("&type=").append(getDataType()); 1411 logger.debug("readDesc: buff={}", buff.toString()); 1412 DataSetInfo dsinfo = new DataSetInfo(buff.toString()); 1413 1414 descriptorTable = dsinfo.getDescriptionTable(); 1415 descriptorList.clear(); 1416 commentList.clear(); 1417 descriptorList.addAll(dsinfo.getDescriptorList()); 1418 commentList.addAll(dsinfo.getCommentList()); 1419 int count = commentList.size(); 1420 String[] names = new String[count]; 1421 for (int i = 0; i < count; i++) { 1422 if (!isLocalServer()) { 1423 names[i] = descriptorList.get(i) + nameSeparator + commentList.get(i); 1424 } else { 1425 names[i] = commentList.get(i); 1426 } 1427 } 1428 logger.debug("readDesc: names={}", names); 1429 Arrays.sort(names); 1430 setDescriptors(names); 1431 setState(STATE_CONNECTED); 1432 } catch (Exception e) { 1433 handleConnectionError(e); 1434 } 1435 } 1436 1437 /** 1438 * Initialize the descriptor list from a list of names 1439 * 1440 * @param names list of names 1441 */ 1442 protected void setDescriptors(String[] names) { 1443 synchronized (WIDGET_MUTEX) { 1444 ignoreDescriptorChange = true; 1445 descriptorComboBox.removeAllItems(); 1446 descriptorNames = names; 1447 if ((names == null) || (names.length == 0)) { 1448 return; 1449 } 1450 descriptorComboBox.addItem(LABEL_SELECT); 1451 for (int j = 0; j < names.length; j++) { 1452 logger.trace("adding names[{}}='{}' to combo box", j, names[j]); 1453 descriptorComboBox.addItem(names[j]); 1454 } 1455 ignoreDescriptorChange = false; 1456 } 1457 } 1458 1459 /** 1460 * Respond to a change in the descriptor list. 1461 */ 1462 protected void descriptorChanged() { 1463 readTimes(); 1464 updateStatus(); 1465 } 1466 1467 /** 1468 * Check if a descriptor (image type) has been chosen 1469 * 1470 * @return true if an image type has been chosen 1471 */ 1472 protected boolean haveDescriptorSelected() { 1473 if ( !GuiUtils.anySelected(descriptorComboBox)) { 1474 return false; 1475 } 1476 return (getDescriptor() != null); 1477 } 1478 1479 /** 1480 * Get the selected descriptor. 1481 * 1482 * @return the currently selected descriptor. 1483 */ 1484 protected String getDescriptor() { 1485 return getDescriptorFromSelection(getSelectedDescriptor()); 1486 } 1487 1488 /** 1489 * Get the descriptor relating to the selection. 1490 * 1491 * @param selection String name from the widget. Can be {@code null}. 1492 * 1493 * @return Either the descriptor associated with {@code selection} or {@code null} if {@link #descriptorTable} or 1494 * {@code selection} is {@code null}. 1495 */ 1496 protected String getDescriptorFromSelection(String selection) { 1497 if (descriptorTable == null) { 1498 return null; 1499 } 1500 if (selection == null) { 1501 return null; 1502 } 1503 1504 String descriptor = null; 1505 if (!selection.contains(nameSeparator)) { 1506 descriptor = (String)descriptorTable.get(selection); 1507 } else { 1508 String[] toks = selection.split(nameSeparator, 2); 1509 String firstToken = toks[0].trim(); 1510 if (descriptorList.contains(firstToken)) { 1511 descriptor = firstToken; 1512 } else { 1513 String key = toks[1].trim(); 1514 descriptor = (String)descriptorTable.get(key); 1515 } 1516 } 1517 return descriptor; 1518 } 1519 1520 /** 1521 * Get the selected descriptor. 1522 * 1523 * @return the selected descriptor 1524 */ 1525 public String getSelectedDescriptor() { 1526 String selection = (String) descriptorComboBox.getSelectedItem(); 1527 if (selection == null) { 1528 return null; 1529 } 1530 if (selection.equals(LABEL_SELECT)) { 1531 return null; 1532 } 1533 return selection; 1534 } 1535 1536 /** 1537 * Get the descriptor table for this chooser 1538 * 1539 * @return a Hashtable of descriptors and names 1540 */ 1541 public Hashtable getDescriptorTable() { 1542 return descriptorTable; 1543 } 1544 1545 /** 1546 * Get any extra key=value pairs that are appended to all requests. 1547 * 1548 * @param buff The buffer to append onto 1549 */ 1550 protected void appendMiscKeyValues(StringBuffer buff) { 1551 appendKeyValue(buff, PROP_COMPRESS, DEFAULT_COMPRESS); 1552 appendKeyValue(buff, PROP_PORT, DEFAULT_PORT); 1553 // appendKeyValue(buff, PROP_DEBUG, DEFAULT_DEBUG); 1554 appendKeyValue(buff, PROP_DEBUG, Boolean.toString(EntryStore.isAddeDebugEnabled(false))); 1555 appendKeyValue(buff, PROP_VERSION, DEFAULT_VERSION); 1556 appendKeyValue(buff, PROP_USER, getLastAddedUser()); 1557 appendKeyValue(buff, PROP_PROJ, getLastAddedProj()); 1558 } 1559 1560 public String getLastAddedUser() { 1561 if (lastServerUser != null && lastServerUser.length() > 0) { 1562 logger.debug("getLastAddedUser: using non-default {}", lastServerUser); 1563 return lastServerUser; 1564 } 1565 else { 1566 logger.debug("getLastAddedUser: using default {}", DEFAULT_USER); 1567 return DEFAULT_USER; 1568 } 1569 } 1570 1571 public String getLastAddedProj() { 1572 if (lastServerProj != null && lastServerProj.length() > 0) { 1573 logger.debug("getLastAddedProj: using non-default {}", lastServerProj); 1574 return lastServerProj; 1575 } 1576 else { 1577 logger.debug("getLastAddedProj: using default {}", DEFAULT_PROJ); 1578 return DEFAULT_PROJ; 1579 } 1580 } 1581 1582 /** 1583 * Show the groups dialog. This method is not meant to be called 1584 * but is public by reason of implementation (or insanity). 1585 */ 1586 public void showGroups() { 1587 JPopupMenu popup = new JPopupMenu(); 1588 popup.add(new JMenuItem("Reading public datasets...")); 1589 popup.show(publicButton, 0, (int) publicButton.getBounds().getHeight()); 1590 1591 List groups = readGroups(); 1592 popup.removeAll(); 1593 if ((groups == null) || (groups.size() == 0)) { 1594 popup.add(new JMenuItem("No public datasets available")); 1595 popup.setVisible(false); 1596 popup.setVisible(true); 1597 return; 1598 } 1599 1600 JMenuItem mi; 1601 for (int i = 0; i < groups.size(); i++) { 1602 final String group = groups.get(i).toString(); 1603 mi = new JMenuItem(group); 1604 mi.addActionListener(new ActionListener() { 1605 public void actionPerformed(ActionEvent ae) { 1606 groupSelector.setSelectedItem(group); 1607 doConnect(); 1608 } 1609 }); 1610 popup.add(mi); 1611 } 1612 popup.setVisible(false); 1613 popup.setVisible(true); 1614 } 1615 1616 /** 1617 * return the String id of the chosen server name 1618 * 1619 * @return the server name 1620 */ 1621 public String getServer() { 1622 AddeServer server = getAddeServer(); 1623 if (server!=null) 1624 return server.getName(); 1625 else 1626 return ""; 1627 } 1628 1629 protected String getGroup() { 1630 return getGroup(false); 1631 } 1632 1633 /** 1634 * Is the group selector editable? Override if ya want. 1635 * @return 1636 */ 1637 protected boolean isGroupEditable() { 1638 return true; 1639 } 1640 1641 /** 1642 * Get the image group from the GUI. 1643 * 1644 * @return The image group. 1645 */ 1646 protected String getGroup(final boolean fromGetServer) { 1647 Object selected = groupSelector.getSelectedItem(); 1648 if (selected == null) { 1649 return null; 1650 } 1651 1652 if (selected instanceof AddeServer.Group) { 1653 AddeServer.Group group = (AddeServer.Group) selected; 1654 return group.getName(); 1655 } 1656 1657 if (selected instanceof String) { 1658 return (String)selected; 1659 } 1660 1661 String groupName = selected.toString().trim(); 1662 if (!fromGetServer && (groupName.length() > 0)) { 1663 //Force the get in case they typed a server name 1664 getServer(); 1665 1666 AddeServer server = getAddeServer(); 1667 if (server != null) { 1668 AddeServer.Group group = 1669 getIdv().getIdvChooserManager().addAddeServerGroup( 1670 server, groupName, getGroupType()); 1671 if (!group.getActive()) { 1672 getIdv().getIdvChooserManager().activateAddeServerGroup( 1673 server, group); 1674 } 1675 //Now put the list of groups back in to the selector 1676 setGroups(); 1677 groupSelector.setSelectedItem(group); 1678 } 1679 } 1680 return groupName; 1681 } 1682 1683 /** 1684 * Get the server selector 1685 * @return The server selector 1686 */ 1687 public JComboBox getServerSelector() { 1688 if (serverSelector == null) 1689 serverSelector = super.getServerSelector(); 1690 1691 ItemListener[] ell = serverSelector.getItemListeners(); 1692 for (int i=0; i<ell.length; i++) { 1693 serverSelector.removeItemListener((ItemListener)ell[i]); 1694 } 1695 updateServers(); 1696 updateGroups(); 1697 serverSelector.addItemListener(new ItemListener() { 1698 public void itemStateChanged(ItemEvent e) { 1699 if ( !ignoreStateChangedEvents) { 1700 Object selected = serverSelector.getSelectedItem(); 1701 if (selected instanceof AddeServer) { 1702 AddeServer selectedServer = (AddeServer)selected; 1703 if (selectedServer != null) { 1704 if (isSeparator(selectedServer)) { 1705 connectButton.setEnabled(false); 1706 return; 1707 } 1708 } 1709 } 1710 setState(STATE_UNCONNECTED); 1711 connectButton.setEnabled(true); 1712 // setGroups(); 1713 resetDescriptorBox(); 1714 updateGroups(); 1715 // System.err.println("itemStateChanged"); 1716 } 1717 // else { 1718 // System.out.println("Ignoring state change here..."); 1719 // } 1720 } 1721 }); 1722 1723 serverSelector.getEditor().getEditorComponent().addKeyListener(new KeyListener() { 1724 public void keyTyped(final KeyEvent e) {} 1725 public void keyPressed(final KeyEvent e) {} 1726 public void keyReleased(final KeyEvent e) { 1727 JTextField field = (JTextField)serverSelector.getEditor().getEditorComponent(); 1728 boolean partialMatch = false; 1729 for (int i = 0; i < serverSelector.getItemCount(); i++) { 1730 String entry = serverSelector.getItemAt(i).toString(); 1731 if (entry.toLowerCase().startsWith(field.getText().toLowerCase())) 1732 partialMatch = true; 1733 } 1734 1735 if (!partialMatch && groupSelector != null) { 1736 logger.debug("aha! chooser=", getDataType()); 1737 ((JTextField)groupSelector.getEditor().getEditorComponent()).setText(""); 1738 } 1739 } 1740 }); 1741 1742 return serverSelector; 1743 } 1744 1745 /** 1746 * Enable or disable the GUI widgets based on what has been 1747 * selected. 1748 */ 1749 protected void enableWidgets() { 1750 synchronized (WIDGET_MUTEX) { 1751 boolean newEnabledState = (getState() == STATE_CONNECTED); 1752 for (int i = 0; i < compsThatNeedDescriptor.size(); i++) { 1753 JComponent comp = (JComponent) compsThatNeedDescriptor.get(i); 1754 if (comp.isEnabled() != newEnabledState) { 1755 GuiUtils.enableTree(comp, newEnabledState); 1756 } 1757 } 1758 } 1759 } 1760 1761 /** 1762 * Add a listener to the given combobox that will set the 1763 * state to unconnected 1764 * 1765 * @param box The box to listen to. 1766 */ 1767 protected void clearOnChange(final JComboBox box) { 1768 box.addItemListener(new ItemListener() { 1769 public void itemStateChanged(ItemEvent e) { 1770 if ( !ignoreStateChangedEvents) { 1771 setState(STATE_UNCONNECTED); 1772 GuiUtils.setListData(descriptorComboBox, new Vector()); 1773 // System.err.println("clearOnChange"); 1774 } 1775 // else { 1776 // System.out.println("Ignoring state change in clearOnChange for: " + box.toString()); 1777 // } 1778 } 1779 }); 1780 } 1781 1782 /** 1783 * Get the descriptor widget label 1784 * 1785 * @return label for the descriptor widget 1786 */ 1787 public String getDescriptorLabel() { 1788 return "Descriptor"; 1789 } 1790 1791 protected int getNumTimesToSelect() { 1792 return 5; 1793 } 1794 1795 /** 1796 * Get the default selected index for the relative times list. 1797 * 1798 * @return default index 1799 */ 1800 protected int getDefaultRelativeTimeIndex() { 1801 return 4; 1802 } 1803 1804 /** 1805 * Check the times lists 1806 */ 1807 protected void checkTimesLists() { 1808 super.checkTimesLists(); 1809 if (timesCardPanelExtra == null) { 1810 return; 1811 } 1812 if (getDoAbsoluteTimes()) { 1813 timesCardPanelExtra.show("absolute"); 1814 } else { 1815 timesCardPanelExtra.show("relative"); 1816 } 1817 } 1818 1819 /** Card panel to hold extra relative and absolute time components */ 1820 private GuiUtils.CardLayoutPanel timesCardPanelExtra; 1821 1822 /** 1823 * Set the relative and absolute extra components 1824 */ 1825 protected JPanel makeTimesPanel(JComponent relativeCard, JComponent absoluteCard) { 1826 JPanel timesPanel = super.makeTimesPanel(false,true); 1827 1828 // Make a new timesPanel that has extra components tacked on the bottom, inside the tabs 1829 Component[] comps = timesPanel.getComponents(); 1830 1831 if (comps.length==1 && comps[0] instanceof JTabbedPane) { 1832 timesCardPanelExtra = new GuiUtils.CardLayoutPanel(); 1833 if (relativeCard == null) relativeCard = new JPanel(); 1834 if (absoluteCard == null) absoluteCard = new JPanel(); 1835 timesCardPanelExtra.add(relativeCard, "relative"); 1836 timesCardPanelExtra.add(absoluteCard, "absolute"); 1837 timesPanel = GuiUtils.centerBottom(comps[0], timesCardPanelExtra); 1838 } 1839 1840 return timesPanel; 1841 } 1842 1843 /** 1844 * Make the UI for this selector. 1845 * 1846 * Thank you NetBeans for helping with the layout! 1847 * 1848 * @return The gui 1849 */ 1850 private JPanel innerPanel = new JPanel(); 1851 1852 private JLabel statusLabel = new JLabel("Status"); 1853 1854 /** 1855 * Super setStatus() takes a second string to enable "simple" mode 1856 * which highlights the required component. We don't really care 1857 * about that feature, and we don't want getStatusLabel() to 1858 * change the label background color. 1859 */ 1860 @Override 1861 public void setStatus(String statusString, String foo) { 1862 if (statusString == null) 1863 statusString = ""; 1864 statusLabel.setText(statusString); 1865 } 1866 1867 protected void setInnerPanel(JPanel newInnerPanel) { 1868 innerPanel = newInnerPanel; 1869 } 1870 1871 /** 1872 * Create the basic layout 1873 */ 1874 protected JComponent doMakeContents() { 1875 JPanel outerPanel = new JPanel(); 1876 1877 JLabel serverLabelInner = new JLabel("Server:"); 1878 McVGuiUtils.setLabelPosition(serverLabelInner, Position.RIGHT); 1879 JPanel serverLabel = GuiUtils.leftRight(parameterButton, serverLabelInner); 1880 McVGuiUtils.setComponentWidth(serverLabel); 1881 1882 clearOnChange(serverSelector); 1883 McVGuiUtils.setComponentWidth(serverSelector, Width.DOUBLE); 1884 1885 JLabel groupLabel = McVGuiUtils.makeLabelRight("Dataset:"); 1886 1887 groupSelector.setEditable(isGroupEditable()); 1888 clearOnChange(groupSelector); 1889 McVGuiUtils.setComponentWidth(groupSelector, Width.DOUBLE); 1890 1891 McVGuiUtils.setComponentWidth(connectButton, Width.DOUBLE); 1892 connectButton.setActionCommand(CMD_CONNECT); 1893 connectButton.addActionListener(this); 1894 1895 /** Set the attributes for the descriptor label and combo box, even though 1896 * they are not used here. Extending classes can add them to the panel if 1897 * necessary. 1898 */ 1899 McVGuiUtils.setComponentWidth(descriptorLabel); 1900 McVGuiUtils.setLabelPosition(descriptorLabel, Position.RIGHT); 1901 1902 McVGuiUtils.setComponentWidth(descriptorComboBox, Width.DOUBLEDOUBLE); 1903 1904 if (descriptorComboBox.getMinimumSize().getWidth() < ELEMENT_DOUBLE_WIDTH) { 1905 McVGuiUtils.setComponentWidth(descriptorComboBox, Width.DOUBLE); 1906 } 1907 1908 JLabel statusLabelLabel = McVGuiUtils.makeLabelRight(""); 1909 1910 statusLabel.setText("Status"); 1911 McVGuiUtils.setLabelPosition(statusLabel, Position.RIGHT); 1912 McVGuiUtils.setComponentColor(statusLabel, TextColor.STATUS); 1913 1914 JButton helpButton = McVGuiUtils.makeImageButton(ICON_HELP, "Show help"); 1915 helpButton.setActionCommand(GuiUtils.CMD_HELP); 1916 helpButton.addActionListener(this); 1917 1918 JButton refreshButton = McVGuiUtils.makeImageButton(ICON_REFRESH, "Refresh"); 1919 refreshButton.setActionCommand(GuiUtils.CMD_UPDATE); 1920 refreshButton.addActionListener(this); 1921 1922 McVGuiUtils.setComponentWidth(loadButton, Width.DOUBLE); 1923 1924 GroupLayout layout = new GroupLayout(outerPanel); 1925 outerPanel.setLayout(layout); 1926 layout.setHorizontalGroup( 1927 layout.createParallelGroup(LEADING) 1928 .addGroup(TRAILING, layout.createSequentialGroup() 1929 .addGroup(layout.createParallelGroup(TRAILING) 1930 .addGroup(layout.createSequentialGroup() 1931 .addContainerGap() 1932 .addComponent(helpButton) 1933 .addGap(GAP_RELATED) 1934 .addComponent(refreshButton) 1935 .addGap(GAP_RELATED) 1936 .addComponent(cancelButton) 1937 .addPreferredGap(RELATED) 1938 .addComponent(loadButton)) 1939 .addGroup(LEADING, layout.createSequentialGroup() 1940 .addContainerGap() 1941 .addGroup(layout.createParallelGroup(LEADING) 1942 .addComponent(innerPanel, DEFAULT_SIZE, DEFAULT_SIZE, Short.MAX_VALUE) 1943 .addGroup(layout.createSequentialGroup() 1944 .addComponent(serverLabel) 1945 .addGap(GAP_RELATED) 1946 .addComponent(serverSelector) 1947 .addGap(GAP_RELATED) 1948 .addComponent(manageButton) 1949 .addGap(GAP_RELATED) 1950 .addComponent(groupLabel) 1951 .addGap(GAP_RELATED) 1952 .addComponent(groupSelector) 1953 .addGap(GAP_RELATED) 1954 .addComponent(publicButton) 1955 .addPreferredGap(RELATED, DEFAULT_SIZE, Short.MAX_VALUE) 1956 .addComponent(connectButton)) 1957 .addGroup(layout.createSequentialGroup() 1958 .addComponent(statusLabelLabel) 1959 .addGap(GAP_RELATED) 1960 .addComponent(statusLabel, DEFAULT_SIZE, DEFAULT_SIZE, Short.MAX_VALUE))))) 1961 .addContainerGap()) 1962 ); 1963 layout.setVerticalGroup( 1964 layout.createParallelGroup(LEADING) 1965 .addGroup(layout.createSequentialGroup() 1966 .addContainerGap() 1967 .addGroup(layout.createParallelGroup(BASELINE) 1968 .addComponent(serverLabel) 1969 .addComponent(serverSelector) 1970 .addComponent(manageButton) 1971 .addComponent(groupLabel) 1972 .addComponent(groupSelector) 1973 .addComponent(publicButton) 1974 .addComponent(connectButton)) 1975 .addPreferredGap(UNRELATED) 1976 .addComponent(innerPanel, DEFAULT_SIZE, DEFAULT_SIZE, Short.MAX_VALUE) 1977 .addPreferredGap(UNRELATED) 1978 .addGroup(layout.createParallelGroup(BASELINE) 1979 .addComponent(statusLabelLabel) 1980 .addComponent(statusLabel)) 1981 .addPreferredGap(UNRELATED) 1982 .addGroup(layout.createParallelGroup(BASELINE) 1983 .addComponent(loadButton) 1984 .addComponent(cancelButton) 1985 .addComponent(refreshButton) 1986 .addComponent(helpButton)) 1987 .addContainerGap()) 1988 ); 1989 1990 return outerPanel; 1991 1992 } 1993 1994 public class ServerComparator implements Comparator<AddeServer> { 1995 public int compare(AddeServer server1, AddeServer server2) { 1996 return server1.getName().compareTo(server2.getName()); 1997 } 1998 } 1999 2000 public class GroupComparator implements Comparator<Group> { 2001 public int compare(Group group1, Group group2) { 2002 return group1.getName().compareTo(group2.getName()); 2003 } 2004 } 2005 } 2006