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