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 }