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