001/* 002 * This file is part of McIDAS-V 003 * 004 * Copyright 2007-2015 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 */ 028package edu.wisc.ssec.mcidasv.servermanager; 029 030import static java.util.Objects.requireNonNull; 031 032import static javax.swing.GroupLayout.DEFAULT_SIZE; 033import static javax.swing.GroupLayout.PREFERRED_SIZE; 034import static javax.swing.GroupLayout.Alignment.BASELINE; 035import static javax.swing.GroupLayout.Alignment.LEADING; 036import static javax.swing.GroupLayout.Alignment.TRAILING; 037import static javax.swing.LayoutStyle.ComponentPlacement.RELATED; 038import static javax.swing.LayoutStyle.ComponentPlacement.UNRELATED; 039 040import static edu.wisc.ssec.mcidasv.util.CollectionHelpers.newLinkedHashSet; 041import static edu.wisc.ssec.mcidasv.util.CollectionHelpers.newMap; 042import static edu.wisc.ssec.mcidasv.util.CollectionHelpers.set; 043import static edu.wisc.ssec.mcidasv.util.McVGuiUtils.runOnEDT; 044import static edu.wisc.ssec.mcidasv.util.McVGuiUtils.safeGetText; 045 046import java.awt.Color; 047import java.awt.Frame; 048import java.awt.event.ActionEvent; 049import java.awt.event.ActionListener; 050import java.awt.event.WindowEvent; 051import java.util.Collection; 052import java.util.Collections; 053import java.util.EnumSet; 054import java.util.LinkedHashSet; 055import java.util.LinkedHashMap; 056import java.util.List; 057import java.util.Map; 058import java.util.Set; 059import java.util.StringTokenizer; 060import java.util.concurrent.Callable; 061import java.util.concurrent.CompletionService; 062import java.util.concurrent.ExecutionException; 063import java.util.concurrent.ExecutorCompletionService; 064import java.util.concurrent.ExecutorService; 065import java.util.concurrent.Executors; 066 067import javax.swing.BorderFactory; 068import javax.swing.GroupLayout; 069import javax.swing.JButton; 070import javax.swing.JCheckBox; 071import javax.swing.JDialog; 072import javax.swing.JLabel; 073import javax.swing.JPanel; 074import javax.swing.JTextField; 075import javax.swing.SwingUtilities; 076import javax.swing.WindowConstants; 077import javax.swing.event.DocumentEvent; 078import javax.swing.event.DocumentListener; 079 080import org.slf4j.Logger; 081import org.slf4j.LoggerFactory; 082 083import ucar.unidata.util.LogUtil; 084 085import edu.wisc.ssec.mcidasv.servermanager.AddeEntry.EditorAction; 086import edu.wisc.ssec.mcidasv.servermanager.AddeEntry.EntrySource; 087import edu.wisc.ssec.mcidasv.servermanager.AddeEntry.EntryType; 088import edu.wisc.ssec.mcidasv.servermanager.AddeEntry.EntryValidity; 089import edu.wisc.ssec.mcidasv.util.CollectionHelpers; 090import edu.wisc.ssec.mcidasv.util.McVTextField; 091 092/** 093 * Simple dialog that allows the user to define or modify {@link RemoteAddeEntry}s. 094 */ 095@SuppressWarnings("serial") 096public class RemoteEntryEditor extends JDialog { 097 098 /** Logger object. */ 099 private static final Logger logger = LoggerFactory.getLogger(RemoteEntryEditor.class); 100 101 /** Possible entry verification states. */ 102 public enum AddeStatus { PREFLIGHT, BAD_SERVER, BAD_ACCOUNTING, NO_METADATA, OK, BAD_GROUP }; 103 104 /** Number of threads in the thread pool. */ 105 private static final int POOL = 5; 106 107 /** Whether or not to input in the dataset, username, and project fields should be uppercased. */ 108 private static final String PREF_FORCE_CAPS = "mcv.servers.forcecaps"; 109 110 /** Background {@link java.awt.Color Color} of an {@literal "invalid"} {@link JTextField JTextField}. */ 111 private static final Color ERROR_FIELD_COLOR = Color.PINK; 112 113 /** Text {@link java.awt.Color Color} of an {@literal "invalid"} {@link JTextField JTextField}. */ 114 private static final Color ERROR_TEXT_COLOR = Color.WHITE; 115 116 /** Background {@link java.awt.Color Color} of a {@literal "valid"} {@link JTextField JTextField}. */ 117 private static final Color NORMAL_FIELD_COLOR = Color.WHITE; 118 119 /** Text {@link java.awt.Color Color} of a {@literal "valid"} {@link JTextField JTextField}. */ 120 private static final Color NORMAL_TEXT_COLOR = Color.BLACK; 121 122 /** 123 * Contains any {@code JTextField}s that may be in an invalid 124 * (to McIDAS-V) state. 125 */ 126 private final Set<JTextField> badFields = newLinkedHashSet(25); 127 128 /** Reference back to the server manager. */ 129 private final EntryStore entryStore; 130 131// private final TabbedAddeManager manager; 132 133 /** Current contents of the editor. */ 134 private final Set<RemoteAddeEntry> currentEntries = newLinkedHashSet(); 135 136 /** The last dialog action performed by the user. */ 137 private EditorAction editorAction = EditorAction.INVALID; 138 139 /** Initial contents of {@link #serverField}. Be aware that {@code null} is allowed. */ 140 private final String serverText; 141 142 /** Initial contents of {@link #datasetField}. Be aware that {@code null} is allowed. */ 143 private final String datasetText; 144 145 /** Whether or not the editor is prompting the user to adjust input. */ 146 private boolean inErrorState = false; 147 148 // if we decide to restore error overlays for known "bad" values. 149// private Set<RemoteAddeEntry> invalidEntries = CollectionHelpers.newLinkedHashSet(); 150 151 /** 152 * Populates the server and dataset text fields with given {@link String}s. 153 * This only works if the dialog <b>is not yet visible</b>. 154 * 155 * <p>This is mostly useful when adding an entry from a chooser. 156 * 157 * @param address Should be the address of a server, but empty and 158 * {@code null} values are allowed. 159 * @param group Should be the name of a group/dataset on {@code server}, 160 * but empty and {@code null} values are allowed. 161 */ 162 public RemoteEntryEditor(EntryStore entryStore, String address, String group) { 163 super((JDialog)null, true); 164 this.entryStore = entryStore; 165// this.manager = null; 166 this.serverText = address; 167 this.datasetText = group; 168 initComponents(RemoteAddeEntry.INVALID_ENTRIES); 169 } 170 171 // TODO(jon): hold back on javadocs, this is likely to change 172 public RemoteEntryEditor(Frame parent, boolean modal, final TabbedAddeManager manager, final EntryStore store) { 173 this(parent, modal, manager, store, RemoteAddeEntry.INVALID_ENTRIES); 174 } 175 176 public RemoteEntryEditor(Frame parent, boolean modal, final TabbedAddeManager manager, final EntryStore store, final RemoteAddeEntry entry) { 177 this(parent, modal, manager, store, CollectionHelpers.list(entry)); 178 } 179 180 // TODO(jon): hold back on javadocs, this is likely to change 181 public RemoteEntryEditor(Frame parent, boolean modal, final TabbedAddeManager manager, final EntryStore store, final List<RemoteAddeEntry> entries) { 182 super(manager, modal); 183 this.entryStore = store; 184// this.manager = manager; 185 this.serverText = null; 186 this.datasetText = null; 187 if (! entries.equals(RemoteAddeEntry.INVALID_ENTRIES)) { 188 currentEntries.addAll(entries); 189 } 190 initComponents(entries); 191 } 192 193 /** 194 * Poll the various UI components and attempt to construct valid ADDE 195 * entries based upon the information provided by the user. 196 * 197 * @param ignoreCheckboxes Whether or not the {@literal "type"} checkboxes 198 * should get ignored. Setting this to {@code true} means that <i>all</i> 199 * types are considered valid--which is useful when attempting to verify 200 * the user's input. 201 * 202 * @return {@link Set} of entries that represent the user's input, or an 203 * empty {@code Set} if the input was invalid somehow. 204 */ 205 private Set<RemoteAddeEntry> pollWidgets(final boolean ignoreCheckboxes) { 206 String host = safeGetText(serverField).trim(); 207 String dataset = safeGetText(datasetField).trim(); 208 String username = RemoteAddeEntry.DEFAULT_ACCOUNT.getUsername(); 209 String project = RemoteAddeEntry.DEFAULT_ACCOUNT.getProject(); 210 if (acctBox.isSelected()) { 211 username = safeGetText(userField).trim(); 212 project = safeGetText(projField).trim(); 213 } 214 215 // determine the "valid" types 216 Set<EntryType> selectedTypes = newLinkedHashSet(); 217 if (!ignoreCheckboxes) { 218 if (imageBox.isSelected()) { 219 selectedTypes.add(EntryType.IMAGE); 220 } 221 if (pointBox.isSelected()) { 222 selectedTypes.add(EntryType.POINT); 223 } 224 if (gridBox.isSelected()) { 225 selectedTypes.add(EntryType.GRID); 226 } 227 if (textBox.isSelected()) { 228 selectedTypes.add(EntryType.TEXT); 229 } 230 if (navBox.isSelected()) { 231 selectedTypes.add(EntryType.NAV); 232 } 233 if (radarBox.isSelected()) { 234 selectedTypes.add(EntryType.RADAR); 235 } 236 } else { 237 selectedTypes.addAll(set(EntryType.IMAGE, EntryType.POINT, EntryType.GRID, EntryType.TEXT, EntryType.NAV, EntryType.RADAR)); 238 } 239 240 if (selectedTypes.isEmpty()) { 241 selectedTypes.add(EntryType.UNKNOWN); 242 } 243 244 // deal with the user trying to add multiple groups at once (even though this UI doesn't work right with it) 245 StringTokenizer tok = new StringTokenizer(dataset, ","); 246 Set<String> newDatasets = newLinkedHashSet(); 247 while (tok.hasMoreTokens()) { 248 newDatasets.add(tok.nextToken().trim()); 249 } 250 251 // create a new entry for each group and its valid types. 252 Set<RemoteAddeEntry> entries = newLinkedHashSet(); 253 for (String newGroup : newDatasets) { 254 for (EntryType type : selectedTypes) { 255 RemoteAddeEntry.Builder builder = new RemoteAddeEntry.Builder(host, newGroup).type(type).validity(EntryValidity.VERIFIED).source(EntrySource.USER); 256 if (acctBox.isSelected()) { 257 builder = builder.account(username, project); 258 } 259 RemoteAddeEntry newEntry = builder.build(); 260 List<AddeEntry> matches = entryStore.searchWithPrefix(newEntry.asStringId()); 261 if (matches.isEmpty()) { 262 entries.add(newEntry); 263 } else if (matches.size() == 1) { 264 AddeEntry matchedEntry = matches.get(0); 265 if (matchedEntry.getEntrySource() != EntrySource.SYSTEM) { 266 entries.add(newEntry); 267 } else { 268 entries.add((RemoteAddeEntry)matchedEntry); 269 } 270 } else { 271 // results should only be empty or a single entry 272 logger.warn("server manager returned unexpected results={}", matches); 273 } 274 } 275 } 276 return entries; 277 } 278 279 private void disposeDisplayable(final boolean refreshManager) { 280 if (isDisplayable()) { 281 dispose(); 282 } 283 TabbedAddeManager tmpController = TabbedAddeManager.getTabbedManager(); 284 if (refreshManager && (tmpController != null)) { 285 tmpController.refreshDisplay(); 286 } 287 } 288 289 /** 290 * Creates new {@link RemoteAddeEntry}s based upon the contents of the dialog 291 * and adds {@literal "them"} to the managed servers. If the dialog is 292 * displayed, we call {@link #dispose()} and attempt to refresh the 293 * server manager GUI if it is available. 294 */ 295 private void addEntry() { 296 Set<RemoteAddeEntry> addedEntries = pollWidgets(false); 297 entryStore.addEntries(addedEntries); 298// if (manager != null) { 299// manager.addEntries(addedEntries); 300// } 301 disposeDisplayable(true); 302 } 303 304 /** 305 * Replaces the entries within {@link #currentEntries} with new entries 306 * from {@link #pollWidgets(boolean)}. If the dialog is displayed, we call 307 * {@link #dispose()} and attempt to refresh the server manager GUI if it's 308 * available. 309 */ 310 private void editEntry() { 311 Set<RemoteAddeEntry> newEntries = pollWidgets(false); 312 entryStore.replaceEntries(currentEntries, newEntries); 313// if (manager != null) { 314// manager.replaceEntries(currentEntries, newEntries); 315// } 316 logger.trace("currentEntries={}", currentEntries); 317 disposeDisplayable(true); 318 } 319 320 /** 321 * Attempts to verify that the current contents of the GUI are 322 * {@literal "valid"}. 323 */ 324 private void verifyInput() { 325 resetBadFields(); 326 Set<RemoteAddeEntry> unverifiedEntries = pollWidgets(true); 327 328 // the editor GUI only works with one server address at a time. so 329 // although there may be several RemoteAddeEntry objs, they'll all have 330 // the same address and the follow *isn't* as dumb as it looks! 331 if (!unverifiedEntries.isEmpty()) { 332 if (!RemoteAddeEntry.checkHost(unverifiedEntries.toArray(new RemoteAddeEntry[0])[0])) { 333 setStatus("Could not connect to the given server."); 334 setBadField(serverField, true); 335 return; 336 } 337 } else { 338 setStatus("Please specify "); 339 setBadField(serverField, true); 340 return; 341 } 342 343 setStatus("Contacting server..."); 344 Set<RemoteAddeEntry> verifiedEntries = checkGroups(unverifiedEntries); 345 EnumSet<EntryType> presentTypes = EnumSet.noneOf(EntryType.class); 346 if (!verifiedEntries.isEmpty()) { 347 for (RemoteAddeEntry verifiedEntry : verifiedEntries) { 348 presentTypes.add(verifiedEntry.getEntryType()); 349 } 350 imageBox.setSelected(presentTypes.contains(EntryType.IMAGE)); 351 pointBox.setSelected(presentTypes.contains(EntryType.POINT)); 352 gridBox.setSelected(presentTypes.contains(EntryType.GRID)); 353 textBox.setSelected(presentTypes.contains(EntryType.TEXT)); 354 navBox.setSelected(presentTypes.contains(EntryType.NAV)); 355 radarBox.setSelected(presentTypes.contains(EntryType.RADAR)); 356 } 357 } 358 359 /** 360 * Displays a short status message in {@link #statusLabel}. 361 * 362 * @param msg Status message. Shouldn't be {@code null}. 363 */ 364 private void setStatus(final String msg) { 365 assert msg != null; 366 logger.debug("msg={}", msg); 367 runOnEDT(new Runnable() { 368 public void run() { 369 statusLabel.setText(msg); 370 } 371 }); 372 statusLabel.revalidate(); 373 } 374 375 /** 376 * Marks a {@code JTextField} as {@literal "valid"} or {@literal "invalid"}. 377 * Mostly this just means that the field is highlighted in order to provide 378 * to the user a sense of {@literal "what do I fix"} when something goes 379 * wrong. 380 * 381 * @param field {@code JTextField} to mark. 382 * @param isBad {@code true} means that the field is {@literal "invalid"}, 383 * {@code false} means that the field is {@literal "valid"}. 384 */ 385 private void setBadField(final JTextField field, final boolean isBad) { 386 assert field != null; 387 assert field == serverField || field == datasetField || field == userField || field == projField; 388 389 if (isBad) { 390 badFields.add(field); 391 } else { 392 badFields.remove(field); 393 } 394 395 runOnEDT(new Runnable() { 396 public void run() { 397 if (isBad) { 398 field.setForeground(ERROR_TEXT_COLOR); 399 field.setBackground(ERROR_FIELD_COLOR); 400 } else { 401 field.setForeground(NORMAL_TEXT_COLOR); 402 field.setBackground(NORMAL_FIELD_COLOR); 403 } 404 } 405 }); 406 field.revalidate(); 407 } 408 409 /** 410 * Determines whether or not any fields are in an invalid state. Useful 411 * for disallowing the user to add invalid entries to the server manager. 412 * 413 * @return Whether or not any fields are invalid. 414 */ 415 private boolean anyBadFields() { 416 assert badFields != null; 417 return !badFields.isEmpty(); 418 } 419 420 /** 421 * Clear out {@link #badFields} and {@literal "set"} the field's status to 422 * valid. 423 */ 424 private void resetBadFields() { 425 Set<JTextField> fields = new LinkedHashSet<>(badFields); 426 for (JTextField field : fields) { 427 setBadField(field, false); 428 } 429 } 430 431 /** 432 * @see #editorAction 433 */ 434 public EditorAction getEditorAction() { 435 return editorAction; 436 } 437 438 /** 439 * @see #editorAction 440 */ 441 private void setEditorAction(final EditorAction editorAction) { 442 this.editorAction = editorAction; 443 } 444 445 /** 446 * Controls the value associated with the {@link #PREF_FORCE_CAPS} preference. 447 * 448 * @param value {@code true} causes user input into the dataset, username, 449 * and project fields to be capitalized. 450 * 451 * @see #getForceMcxCaps() 452 */ 453 private void setForceMcxCaps(final boolean value) { 454 entryStore.getIdvStore().put(PREF_FORCE_CAPS, value); 455 } 456 457 /** 458 * Returns the value associated with the {@link #PREF_FORCE_CAPS} preference. 459 * 460 * @see #setForceMcxCaps(boolean) 461 */ 462 private boolean getForceMcxCaps() { 463 return entryStore.getIdvStore().get(PREF_FORCE_CAPS, true); 464 } 465 466 // TODO(jon): oh man clean this junk up 467 /** This method is called from within the constructor to 468 * initialize the form. 469 * WARNING: Do NOT modify this code. The content of this method is 470 * always regenerated by the Form Editor. 471 */ 472 @SuppressWarnings("unchecked") 473 // <editor-fold defaultstate="collapsed" desc="Generated Code"> 474 private void initComponents(final List<RemoteAddeEntry> initEntries) { 475 assert SwingUtilities.isEventDispatchThread(); 476 entryPanel = new JPanel(); 477 serverLabel = new JLabel(); 478 serverField = new JTextField(); 479 datasetLabel = new JLabel(); 480 datasetField = new McVTextField(); 481 acctBox = new JCheckBox(); 482 userLabel = new JLabel(); 483 userField = new McVTextField(); 484 projLabel = new JLabel(); 485 projField = new JTextField(); 486 capBox = new JCheckBox(); 487 typePanel = new JPanel(); 488 imageBox = new JCheckBox(); 489 pointBox = new JCheckBox(); 490 gridBox = new JCheckBox(); 491 textBox = new JCheckBox(); 492 navBox = new JCheckBox(); 493 radarBox = new JCheckBox(); 494 statusPanel = new JPanel(); 495 statusLabel = new JLabel(); 496 verifyAddButton = new JButton(); 497 verifyServer = new JButton(); 498 addServer = new JButton(); 499 cancelButton = new JButton(); 500 501 boolean forceCaps = getForceMcxCaps(); 502 datasetField.setUppercase(forceCaps); 503 userField.setUppercase(forceCaps); 504 505 if (initEntries == RemoteAddeEntry.INVALID_ENTRIES) { 506 setTitle("Add Remote Dataset"); 507 } else { 508 setTitle("Edit Remote Dataset"); 509 } 510 setResizable(false); 511 setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); 512 addWindowListener(new java.awt.event.WindowAdapter() { 513 public void windowClosed(WindowEvent evt) { 514 formWindowClosed(evt); 515 } 516 }); 517 518 serverLabel.setText("Server:"); 519 if (serverText != null) { 520 serverField.setText(serverText); 521 } 522 523 datasetLabel.setText("Dataset:"); 524 if (datasetText != null) { 525 datasetField.setText(datasetText); 526 } 527 528 acctBox.setText("Specify accounting information:"); 529 acctBox.addActionListener(new ActionListener() { 530 public void actionPerformed(ActionEvent evt) { 531 acctBoxActionPerformed(evt); 532 } 533 }); 534 535 userLabel.setText("Username:"); 536 userField.setEnabled(acctBox.isSelected()); 537 538 projLabel.setText("Project #:"); 539 projField.setEnabled(acctBox.isSelected()); 540 541 capBox.setText("Automatically capitalize dataset and username?"); 542 capBox.setSelected(forceCaps); 543 capBox.addActionListener(new ActionListener() { 544 public void actionPerformed(ActionEvent evt) { 545 capBoxActionPerformed(evt); 546 } 547 }); 548 549 DocumentListener inputListener = new DocumentListener() { 550 public void changedUpdate(DocumentEvent evt) { 551 reactToValueChanges(); 552 } 553 public void insertUpdate(DocumentEvent evt) { 554 if (inErrorState) { 555 verifyAddButton.setEnabled(true); 556 verifyServer.setEnabled(true); 557 inErrorState = false; 558 resetBadFields(); 559 } 560 } 561 public void removeUpdate(DocumentEvent evt) { 562 if (inErrorState) { 563 verifyAddButton.setEnabled(true); 564 verifyServer.setEnabled(true); 565 inErrorState = false; 566 resetBadFields(); 567 } 568 } 569 }; 570 571 serverField.getDocument().addDocumentListener(inputListener); 572 datasetField.getDocument().addDocumentListener(inputListener); 573 userField.getDocument().addDocumentListener(inputListener); 574 projField.getDocument().addDocumentListener(inputListener); 575 576 GroupLayout entryPanelLayout = new GroupLayout(entryPanel); 577 entryPanel.setLayout(entryPanelLayout); 578 entryPanelLayout.setHorizontalGroup( 579 entryPanelLayout.createParallelGroup(LEADING) 580 .addGroup(entryPanelLayout.createSequentialGroup() 581 .addGroup(entryPanelLayout.createParallelGroup(LEADING) 582 .addComponent(serverLabel, TRAILING) 583 .addComponent(datasetLabel, TRAILING) 584 .addComponent(userLabel, TRAILING) 585 .addComponent(projLabel, TRAILING)) 586 .addPreferredGap(RELATED) 587 .addGroup(entryPanelLayout.createParallelGroup(LEADING) 588 .addComponent(serverField, DEFAULT_SIZE, 419, Short.MAX_VALUE) 589 .addComponent(capBox) 590 .addComponent(acctBox) 591 .addComponent(datasetField, DEFAULT_SIZE, 419, Short.MAX_VALUE) 592 .addComponent(userField, DEFAULT_SIZE, 419, Short.MAX_VALUE) 593 .addComponent(projField, DEFAULT_SIZE, 419, Short.MAX_VALUE)) 594 .addContainerGap()) 595 ); 596 entryPanelLayout.setVerticalGroup( 597 entryPanelLayout.createParallelGroup(LEADING) 598 .addGroup(entryPanelLayout.createSequentialGroup() 599 .addGroup(entryPanelLayout.createParallelGroup(BASELINE) 600 .addComponent(serverLabel) 601 .addComponent(serverField, PREFERRED_SIZE, DEFAULT_SIZE, PREFERRED_SIZE)) 602 .addPreferredGap(RELATED) 603 .addGroup(entryPanelLayout.createParallelGroup(BASELINE) 604 .addComponent(datasetLabel) 605 .addComponent(datasetField, PREFERRED_SIZE, DEFAULT_SIZE, PREFERRED_SIZE)) 606 .addGap(16, 16, 16) 607 .addComponent(acctBox) 608 .addPreferredGap(RELATED) 609 .addGroup(entryPanelLayout.createParallelGroup(BASELINE) 610 .addComponent(userLabel) 611 .addComponent(userField, PREFERRED_SIZE, DEFAULT_SIZE, PREFERRED_SIZE)) 612 .addPreferredGap(RELATED) 613 .addGroup(entryPanelLayout.createParallelGroup(BASELINE) 614 .addComponent(projLabel) 615 .addComponent(projField, PREFERRED_SIZE, DEFAULT_SIZE, PREFERRED_SIZE)) 616 .addPreferredGap(RELATED) 617 .addComponent(capBox) 618 .addGap(0, 0, Short.MAX_VALUE)) 619 ); 620 621 typePanel.setBorder(BorderFactory.createTitledBorder("Dataset Types")); 622 623 ActionListener typeInputListener = new ActionListener() { 624 public void actionPerformed(ActionEvent evt) { 625 if (inErrorState) { 626 verifyAddButton.setEnabled(true); 627 verifyServer.setEnabled(true); 628 inErrorState = false; 629 resetBadFields(); 630 } 631 } 632 }; 633 634 imageBox.setText("Image"); 635 imageBox.addActionListener(typeInputListener); 636 typePanel.add(imageBox); 637 638 pointBox.setText("Point"); 639 pointBox.addActionListener(typeInputListener); 640 typePanel.add(pointBox); 641 642 gridBox.setText("Grid"); 643 gridBox.addActionListener(typeInputListener); 644 typePanel.add(gridBox); 645 646 textBox.setText("Text"); 647 textBox.addActionListener(typeInputListener); 648 typePanel.add(textBox); 649 650 navBox.setText("Navigation"); 651 navBox.addActionListener(typeInputListener); 652 typePanel.add(navBox); 653 654 radarBox.setText("Radar"); 655 radarBox.addActionListener(typeInputListener); 656 typePanel.add(radarBox); 657 658 statusPanel.setBorder(BorderFactory.createTitledBorder("Status")); 659 660 statusLabel.setText("Please provide the address of a remote ADDE server."); 661 662 GroupLayout statusPanelLayout = new GroupLayout(statusPanel); 663 statusPanel.setLayout(statusPanelLayout); 664 statusPanelLayout.setHorizontalGroup( 665 statusPanelLayout.createParallelGroup(LEADING) 666 .addGroup(statusPanelLayout.createSequentialGroup() 667 .addContainerGap() 668 .addComponent(statusLabel) 669 .addContainerGap(154, Short.MAX_VALUE)) 670 ); 671 statusPanelLayout.setVerticalGroup( 672 statusPanelLayout.createParallelGroup(LEADING) 673 .addGroup(statusPanelLayout.createSequentialGroup() 674 .addComponent(statusLabel) 675 .addContainerGap(DEFAULT_SIZE, Short.MAX_VALUE)) 676 ); 677 678 if (initEntries.equals(RemoteAddeEntry.INVALID_ENTRIES)) { 679 verifyAddButton.setText("Verify and Add Server"); 680 } else { 681 verifyAddButton.setText("Verify and Save Changes"); 682 } 683 verifyAddButton.addActionListener(new ActionListener() { 684 public void actionPerformed(ActionEvent evt) { 685 if (initEntries == RemoteAddeEntry.INVALID_ENTRIES) 686 verifyAddButtonActionPerformed(evt); 687 else 688 verifyEditButtonActionPerformed(evt); 689 } 690 }); 691 692 if (initEntries == RemoteAddeEntry.INVALID_ENTRIES) { 693 verifyServer.setText("Verify Server"); 694 } else { 695 verifyServer.setText("Verify Changes"); 696 } 697 verifyServer.addActionListener(new ActionListener() { 698 public void actionPerformed(ActionEvent evt) { 699 verifyServerActionPerformed(evt); 700 } 701 }); 702 703 if (initEntries.equals(RemoteAddeEntry.INVALID_ENTRIES)) { 704 addServer.setText("Add Server"); 705 } else { 706 addServer.setText("Save Changes"); 707 } 708 addServer.addActionListener(new ActionListener() { 709 public void actionPerformed(ActionEvent evt) { 710 if (initEntries == RemoteAddeEntry.INVALID_ENTRIES) { 711 addServerActionPerformed(evt); 712 } else { 713 editServerActionPerformed(evt); 714 } 715 } 716 }); 717 718 cancelButton.setText("Cancel"); 719 cancelButton.addActionListener(new ActionListener() { 720 public void actionPerformed(ActionEvent evt) { 721 cancelButtonActionPerformed(evt); 722 } 723 }); 724 725 GroupLayout layout = new GroupLayout(getContentPane()); 726 getContentPane().setLayout(layout); 727 layout.setHorizontalGroup( 728 layout.createParallelGroup(LEADING) 729 .addGroup(layout.createSequentialGroup() 730 .addContainerGap() 731 .addGroup(layout.createParallelGroup(LEADING) 732 .addComponent(statusPanel, DEFAULT_SIZE, DEFAULT_SIZE, Short.MAX_VALUE) 733 .addComponent(typePanel, 0, 0, Short.MAX_VALUE) 734 .addComponent(entryPanel, DEFAULT_SIZE, DEFAULT_SIZE, Short.MAX_VALUE) 735 .addGroup(layout.createSequentialGroup() 736 .addComponent(verifyAddButton) 737 .addPreferredGap(RELATED) 738 .addComponent(verifyServer) 739 .addPreferredGap(RELATED) 740 .addComponent(addServer) 741 .addPreferredGap(RELATED) 742 .addComponent(cancelButton))) 743 .addContainerGap()) 744 ); 745 layout.setVerticalGroup( 746 layout.createParallelGroup(LEADING) 747 .addGroup(layout.createSequentialGroup() 748 .addContainerGap() 749 .addComponent(entryPanel, PREFERRED_SIZE, DEFAULT_SIZE, PREFERRED_SIZE) 750 .addPreferredGap(UNRELATED) 751 .addComponent(typePanel, PREFERRED_SIZE, 57, PREFERRED_SIZE) 752 .addGap(18, 18, 18) 753 .addComponent(statusPanel, PREFERRED_SIZE, DEFAULT_SIZE, PREFERRED_SIZE) 754 .addGap(18, 18, 18) 755 .addGroup(layout.createParallelGroup(BASELINE) 756 .addComponent(verifyServer) 757 .addComponent(addServer) 758 .addComponent(cancelButton) 759 .addComponent(verifyAddButton)) 760 .addContainerGap(17, Short.MAX_VALUE)) 761 ); 762 763 if ((initEntries != null) && !RemoteAddeEntry.INVALID_ENTRIES.equals(initEntries)) { 764 RemoteAddeEntry initEntry = initEntries.get(0); 765 boolean hasSystemEntry = false; 766 for (RemoteAddeEntry entry : initEntries) { 767 if (entry.getEntrySource() == EntrySource.SYSTEM) { 768 initEntry = entry; 769 hasSystemEntry = true; 770 break; 771 } 772 } 773 serverField.setText(initEntry.getAddress()); 774 datasetField.setText(initEntry.getGroup()); 775 776 if (!RemoteAddeEntry.DEFAULT_ACCOUNT.equals(initEntry.getAccount())) { 777 acctBox.setSelected(true); 778 userField.setEnabled(true); 779 userField.setText(initEntry.getAccount().getUsername()); 780 projField.setEnabled(true); 781 projField.setText(initEntry.getAccount().getProject()); 782 } 783 784 if (hasSystemEntry) { 785 serverField.setEnabled(false); 786 datasetField.setEnabled(false); 787 acctBox.setEnabled(false); 788 userField.setEnabled(false); 789 projField.setEnabled(false); 790 capBox.setEnabled(false); 791 } 792 793 for (RemoteAddeEntry entry : initEntries) { 794 boolean nonDefaultSource = entry.getEntrySource() != EntrySource.SYSTEM; 795 if (entry.getEntryType() == EntryType.IMAGE) { 796 imageBox.setSelected(true); 797 imageBox.setEnabled(nonDefaultSource); 798 } else if (entry.getEntryType() == EntryType.POINT) { 799 pointBox.setSelected(true); 800 pointBox.setEnabled(nonDefaultSource); 801 } else if (entry.getEntryType() == EntryType.GRID) { 802 gridBox.setSelected(true); 803 gridBox.setEnabled(nonDefaultSource); 804 } else if (entry.getEntryType() == EntryType.TEXT) { 805 textBox.setSelected(true); 806 textBox.setEnabled(nonDefaultSource); 807 } else if (entry.getEntryType() == EntryType.NAV) { 808 navBox.setSelected(true); 809 navBox.setEnabled(nonDefaultSource); 810 } else if (entry.getEntryType() == EntryType.RADAR) { 811 radarBox.setSelected(true); 812 radarBox.setEnabled(nonDefaultSource); 813 } 814 } 815 } 816 pack(); 817 }// </editor-fold> 818 819 private void acctBoxActionPerformed(ActionEvent evt) { 820 assert SwingUtilities.isEventDispatchThread(); 821 resetBadFields(); 822 boolean enabled = acctBox.isSelected(); 823 userField.setEnabled(enabled); 824 projField.setEnabled(enabled); 825 verifyAddButton.setEnabled(true); 826 verifyServer.setEnabled(true); 827 } 828 829 private void capBoxActionPerformed(ActionEvent evt) { 830 assert SwingUtilities.isEventDispatchThread(); 831 boolean forceCaps = capBox.isSelected(); 832 datasetField.setUppercase(forceCaps); 833 userField.setUppercase(forceCaps); 834 setForceMcxCaps(forceCaps); 835 if (!forceCaps) { 836 return; 837 } 838 datasetField.setText(safeGetText(datasetField).toUpperCase()); 839 userField.setText(safeGetText(userField).toUpperCase()); 840 } 841 842 private void verifyAddButtonActionPerformed(ActionEvent evt) { 843 verifyInput(); 844 if (!anyBadFields()) { 845 setEditorAction(EditorAction.ADDED_VERIFIED); 846 addEntry(); 847 } else { 848 inErrorState = true; 849 verifyAddButton.setEnabled(false); 850 verifyServer.setEnabled(false); 851 } 852 } 853 854 private void verifyEditButtonActionPerformed(ActionEvent evt) { 855 verifyInput(); 856 if (!anyBadFields()) { 857 setEditorAction(EditorAction.EDITED_VERIFIED); 858 editEntry(); 859 } else { 860 inErrorState = true; 861 verifyAddButton.setEnabled(false); 862 verifyServer.setEnabled(false); 863 } 864 } 865 866 private void cancelButtonActionPerformed(ActionEvent evt) { 867 setEditorAction(EditorAction.CANCELLED); 868 disposeDisplayable(false); 869 } 870 871 private void formWindowClosed(WindowEvent evt) { 872 setEditorAction(EditorAction.CANCELLED); 873 disposeDisplayable(false); 874 } 875 876 private void verifyServerActionPerformed(ActionEvent evt) { 877 verifyInput(); 878 if (anyBadFields()) { 879 // save poll widget state 880 // toggle a "listen for *any* input event" switch to on 881// invalidEntries.clear(); 882// invalidEntries.addAll(pollWidgets(false)); 883 inErrorState = true; 884 verifyAddButton.setEnabled(false); 885 verifyServer.setEnabled(false); 886 } 887 } 888 889 private void addServerActionPerformed(ActionEvent evt) { 890 setEditorAction(EditorAction.ADDED); 891 addEntry(); 892 } 893 894 private void editServerActionPerformed(ActionEvent evt) { 895 setEditorAction(EditorAction.EDITED); 896 editEntry(); 897 } 898 899 private void reactToValueChanges() { 900 assert SwingUtilities.isEventDispatchThread(); 901 if (inErrorState) { 902 verifyAddButton.setEnabled(true); 903 verifyServer.setEnabled(true); 904 inErrorState = false; 905 resetBadFields(); 906 } 907 } 908 909 /** 910 * Attempt to verify a {@link Set} of {@link RemoteAddeEntry}s. Useful for 911 * checking a {@literal "MCTABLE.TXT"} after importing. 912 * 913 * @param entries {@code Set} of remote ADDE entries to validate. Cannot 914 * be {@code null}. 915 * 916 * @return {@code Set} of {@code RemoteAddeEntry}s that McIDAS-V was able 917 * to connect to. 918 * 919 * @throws NullPointerException if {@code entries} is {@code null}. 920 */ 921 public Set<RemoteAddeEntry> checkHosts(final Set<RemoteAddeEntry> entries) { 922 requireNonNull(entries, "entries cannot be null"); 923 Set<RemoteAddeEntry> goodEntries = newLinkedHashSet(); 924 Set<String> checkedHosts = newLinkedHashSet(); 925 Map<String, Boolean> hostStatus = newMap(); 926 for (RemoteAddeEntry entry : entries) { 927 String host = entry.getAddress(); 928 if (hostStatus.get(host).equals(Boolean.FALSE)) { 929 continue; 930 } else if (hostStatus.get(host).equals(Boolean.TRUE)) { 931 goodEntries.add(entry); 932 } else { 933 checkedHosts.add(host); 934 if (RemoteAddeEntry.checkHost(entry)) { 935 goodEntries.add(entry); 936 hostStatus.put(host, Boolean.TRUE); 937 } else { 938 hostStatus.put(host, Boolean.FALSE); 939 } 940 } 941 } 942 return goodEntries; 943 } 944 945 public Set<RemoteAddeEntry> checkHosts2(final Set<RemoteAddeEntry> entries) { 946 requireNonNull(entries, "entries cannot be null"); 947 if (entries.isEmpty()) { 948 return Collections.emptySet(); 949 } 950 951 Set<RemoteAddeEntry> verified = newLinkedHashSet(entries.size()); 952 Set<String> hosts = newLinkedHashSet(entries.size()); 953// Set<String> validHosts = newLinkedHashSet(entries.size()); 954 955 ExecutorService exec = Executors.newFixedThreadPool(POOL); 956 CompletionService<StatusWrapper> ecs = new ExecutorCompletionService<StatusWrapper>(exec); 957 for (RemoteAddeEntry entry : entries) { 958 ecs.submit(new VerifyHostTask(new StatusWrapper(entry))); 959 } 960 961 try { 962 for (int i = 0; i < entries.size(); i++) { 963 StatusWrapper pairing = ecs.take().get(); 964 RemoteAddeEntry entry = pairing.getEntry(); 965 AddeStatus status = pairing.getStatus(); 966// setStatus(entry.getAddress()+": attempting to connect..."); 967// statuses.add(status); 968// entry2Status.put(entry, status); 969 if (status == AddeStatus.OK) { 970 verified.add(entry); 971// setStatus("Found host name "+entry.getAddress()); 972 } 973 } 974 } catch (InterruptedException e) { 975 LogUtil.logException("interrupted while checking ADDE entries", e); 976 } catch (ExecutionException e) { 977 LogUtil.logException("ADDE validation execution error", e); 978 } finally { 979 exec.shutdown(); 980 } 981 return verified; 982 } 983// public Set<RemoteAddeEntry> checkHosts2(final Set<RemoteAddeEntry> entries) { 984// Contract.notNull(entries, "entries cannot be null"); 985// if (entries.isEmpty()) { 986// return Collections.emptySet(); 987// } 988// 989// Set<RemoteAddeEntry> verified = newLinkedHashSet(entries.size()); 990// ExecutorService exec = Executors.newFixedThreadPool(POOL); 991// CompletionService<StatusWrapper> ecs = new ExecutorCompletionService<StatusWrapper>(exec); 992//// Map<RemoteAddeEntry, AddeStatus> entry2Status = new LinkedHashMap<RemoteAddeEntry, AddeStatus>(entries.size()); 993// 994// for (RemoteAddeEntry entry : entries) { 995// StatusWrapper check = new StatusWrapper(entry); 996// ecs.submit(new VerifyHostTask(check)); 997// } 998// 999// try { 1000// for (int i = 0; i < entries.size(); i++) { 1001// StatusWrapper pairing = ecs.take().get(); 1002// RemoteAddeEntry entry = pairing.getEntry(); 1003// AddeStatus status = pairing.getStatus(); 1004// setStatus(entry.getAddress()+": attempting to connect..."); 1005// statuses.add(status); 1006//// entry2Status.put(entry, status); 1007// if (status == AddeStatus.OK) { 1008// verified.add(entry); 1009//// setStatus("Found host name "+entry.getAddress()); 1010// } 1011// } 1012// } catch (InterruptedException e) { 1013// 1014// } catch (ExecutionException e) { 1015// 1016// } finally { 1017// exec.shutdown(); 1018// } 1019// 1020// if (statuses.contains(AddeStatus.BAD_SERVER)) { 1021// setStatus("Could not connect to the server."); 1022// setBadField(serverField, true); 1023// } else { 1024// setStatus("Connected to server."); 1025// } 1026//// 1027//// if (!statuses.contains(AddeStatus.OK)) { 1028//// if (statuses.contains(AddeStatus.BAD_ACCOUNTING)) { 1029//// setStatus("Incorrect accounting information."); 1030//// setBadField(userField, true); 1031//// setBadField(projField, true); 1032//// } else if (statuses.contains(AddeStatus.BAD_GROUP)) { 1033//// setStatus("Dataset does not appear to be valid."); 1034//// setBadField(datasetField, true); 1035//// } else if (statuses.contains(AddeStatus.BAD_SERVER)) { 1036//// setStatus("Could not connect to the ADDE server."); 1037//// setBadField(serverField, true); 1038//// } else { 1039//// logger.warn("guru meditation error: statuses={}", statuses); 1040//// } 1041//// } else { 1042//// setStatus("Finished verifying."); 1043//// } 1044// 1045// return verified; 1046// } 1047 1048 public Set<RemoteAddeEntry> checkGroups(final Set<RemoteAddeEntry> entries) { 1049 requireNonNull(entries, "entries cannot be null"); 1050 if (entries.isEmpty()) { 1051 return Collections.emptySet(); 1052 } 1053 1054 Set<RemoteAddeEntry> verified = newLinkedHashSet(entries.size()); 1055 Collection<AddeStatus> statuses = EnumSet.noneOf(AddeStatus.class); 1056 ExecutorService exec = Executors.newFixedThreadPool(POOL); 1057 CompletionService<StatusWrapper> ecs = new ExecutorCompletionService<StatusWrapper>(exec); 1058 Map<RemoteAddeEntry, AddeStatus> entry2Status = new LinkedHashMap<RemoteAddeEntry, AddeStatus>(entries.size()); 1059 1060 // submit new verification tasks to the pool's queue ... (apologies for the pun?) 1061 for (RemoteAddeEntry entry : entries) { 1062 StatusWrapper pairing = new StatusWrapper(entry); 1063 ecs.submit(new VerifyEntryTask(pairing)); 1064 } 1065 1066 // use completion service magic to only deal with finished verification tasks 1067 try { 1068 for (int i = 0; i < entries.size(); i++) { 1069 StatusWrapper pairing = ecs.take().get(); 1070 RemoteAddeEntry entry = pairing.getEntry(); 1071 AddeStatus status = pairing.getStatus(); 1072 setStatus(entry.getEntryText()+": attempting verification..."); 1073 statuses.add(status); 1074 entry2Status.put(entry, status); 1075 if (status == AddeStatus.OK) { 1076 verified.add(entry); 1077 setStatus("Found accessible "+entry.getEntryType().toString().toLowerCase()+" data."); 1078 } 1079 } 1080 } catch (InterruptedException e) { 1081 LogUtil.logException("interrupted while checking ADDE entries", e); 1082 } catch (ExecutionException e) { 1083 LogUtil.logException("ADDE validation execution error", e); 1084 } finally { 1085 exec.shutdown(); 1086 } 1087 1088 if (!statuses.contains(AddeStatus.OK)) { 1089 if (statuses.contains(AddeStatus.BAD_ACCOUNTING)) { 1090 setStatus("Incorrect accounting information."); 1091 setBadField(userField, true); 1092 setBadField(projField, true); 1093 } else if (statuses.contains(AddeStatus.BAD_GROUP)) { 1094 setStatus("Dataset does not appear to be valid."); 1095 setBadField(datasetField, true); 1096 } else if (statuses.contains(AddeStatus.BAD_SERVER)) { 1097 setStatus("Could not connect to the ADDE server."); 1098 setBadField(serverField, true); 1099 } else { 1100 logger.warn("guru meditation error: statuses={}", statuses); 1101 } 1102 } else { 1103 setStatus("Finished verifying."); 1104 } 1105 return verified; 1106 } 1107 1108 private static Map<RemoteAddeEntry, AddeStatus> bulkPut(final Collection<RemoteAddeEntry> entries, final AddeStatus status) { 1109 Map<RemoteAddeEntry, AddeStatus> map = new LinkedHashMap<>(entries.size()); 1110 for (RemoteAddeEntry entry : entries) { 1111 map.put(entry, status); 1112 } 1113 return map; 1114 } 1115 1116 /** 1117 * Associates a {@link RemoteAddeEntry} with one of the states from 1118 * {@link AddeStatus}. 1119 */ 1120 private static class StatusWrapper { 1121 /** */ 1122 private final RemoteAddeEntry entry; 1123 1124 /** Current {@literal "status"} of {@link #entry}. */ 1125 private AddeStatus status; 1126 1127 /** 1128 * Builds an entry/status pairing. 1129 * 1130 * @param entry The {@code RemoteAddeEntry} to wrap up. 1131 * 1132 * @throws NullPointerException if {@code entry} is {@code null}. 1133 */ 1134 public StatusWrapper(final RemoteAddeEntry entry) { 1135 requireNonNull(entry, "cannot create a entry/status pair with a null descriptor"); 1136 this.entry = entry; 1137 } 1138 1139 /** 1140 * Set the {@literal "status"} of this {@link #entry} to a given 1141 * {@link AddeStatus}. 1142 * 1143 * @param status New status of {@code entry}. 1144 */ 1145 public void setStatus(AddeStatus status) { 1146 this.status = status; 1147 } 1148 1149 /** 1150 * Returns the current {@literal "status"} of {@link #entry}. 1151 * 1152 * @return One of {@link AddeStatus}. 1153 */ 1154 public AddeStatus getStatus() { 1155 return status; 1156 } 1157 1158 /** 1159 * Returns the {@link RemoteAddeEntry} stored in this wrapper. 1160 * 1161 * @return {@link #entry} 1162 */ 1163 public RemoteAddeEntry getEntry() { 1164 return entry; 1165 } 1166 } 1167 1168 /** 1169 * Represents an ADDE entry verification task. These are executed asynchronously 1170 * by the completion service within {@link RemoteEntryEditor#checkGroups(Set)}. 1171 */ 1172 private class VerifyEntryTask implements Callable<StatusWrapper> { 1173 private final StatusWrapper entryStatus; 1174 public VerifyEntryTask(final StatusWrapper descStatus) { 1175 requireNonNull(descStatus, "cannot verify or set status of a null descriptor/status pair"); 1176 this.entryStatus = descStatus; 1177 } 1178 1179 @Override public StatusWrapper call() throws Exception { 1180 entryStatus.setStatus(RemoteAddeEntry.checkEntry(entryStatus.getEntry())); 1181 return entryStatus; 1182 } 1183 } 1184 1185 private class VerifyHostTask implements Callable<StatusWrapper> { 1186 private final StatusWrapper entryStatus; 1187 public VerifyHostTask(final StatusWrapper descStatus) { 1188 entryStatus = requireNonNull(descStatus, "cannot verify or set status of a null descriptor/status pair"); 1189 } 1190 @Override public StatusWrapper call() throws Exception { 1191 boolean validHost = RemoteAddeEntry.checkHost(entryStatus.getEntry()); 1192 if (validHost) { 1193 entryStatus.setStatus(AddeStatus.OK); 1194 } else { 1195 entryStatus.setStatus(AddeStatus.BAD_SERVER); 1196 } 1197 return entryStatus; 1198 } 1199 } 1200 1201 // Variables declaration - do not modify 1202 private JCheckBox acctBox; 1203 private JButton addServer; 1204 private JButton cancelButton; 1205 private JCheckBox capBox; 1206 private McVTextField datasetField; 1207 private JLabel datasetLabel; 1208 private JPanel entryPanel; 1209 private JCheckBox gridBox; 1210 private JCheckBox imageBox; 1211 private JCheckBox navBox; 1212 private JCheckBox pointBox; 1213 private JTextField projField; 1214 private JLabel projLabel; 1215 private JCheckBox radarBox; 1216 private JTextField serverField; 1217 private JLabel serverLabel; 1218 private JLabel statusLabel; 1219 private JPanel statusPanel; 1220 private JCheckBox textBox; 1221 private JPanel typePanel; 1222 private McVTextField userField; 1223 private JLabel userLabel; 1224 private JButton verifyAddButton; 1225 private JButton verifyServer; 1226 // End of variables declaration 1227}