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