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