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 }