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