001 /*
002 * This file is part of McIDAS-V
003 *
004 * Copyright 2007-2013
005 * Space Science and Engineering Center (SSEC)
006 * University of Wisconsin - Madison
007 * 1225 W. Dayton Street, Madison, WI 53706, USA
008 * https://www.ssec.wisc.edu/mcidas
009 *
010 * All Rights Reserved
011 *
012 * McIDAS-V is built on Unidata's IDV and SSEC's VisAD libraries, and
013 * some McIDAS-V source code is based on IDV and VisAD source code.
014 *
015 * McIDAS-V is free software; you can redistribute it and/or modify
016 * it under the terms of the GNU Lesser Public License as published by
017 * the Free Software Foundation; either version 3 of the License, or
018 * (at your option) any later version.
019 *
020 * McIDAS-V is distributed in the hope that it will be useful,
021 * but WITHOUT ANY WARRANTY; without even the implied warranty of
022 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
023 * GNU Lesser Public License for more details.
024 *
025 * You should have received a copy of the GNU Lesser Public License
026 * along with this program. If not, see http://www.gnu.org/licenses.
027 */
028
029 package edu.wisc.ssec.mcidasv.control;
030
031 import java.awt.Color;
032 import java.awt.Container;
033 import java.awt.Dimension;
034 import java.rmi.RemoteException;
035 import java.util.ArrayList;
036 import java.util.Collection;
037 import java.util.HashMap;
038 import java.util.HashSet;
039 import java.util.Hashtable;
040 import java.util.Iterator;
041 import java.util.LinkedHashSet;
042 import java.util.List;
043 import java.util.Map;
044 import java.util.Map.Entry;
045 import java.util.Set;
046
047 import javax.swing.JComponent;
048 import javax.swing.JPanel;
049 import javax.swing.JTabbedPane;
050
051 import org.python.core.PyDictionary;
052 import org.python.core.PyFloat;
053 import org.python.core.PyInteger;
054 import org.slf4j.Logger;
055 import org.slf4j.LoggerFactory;
056
057 import visad.ConstantMap;
058 import visad.Data;
059 import visad.Real;
060 import visad.VisADException;
061 import visad.georef.MapProjection;
062
063 import ucar.unidata.data.DataChoice;
064 import ucar.unidata.data.DataSource;
065 import ucar.unidata.data.DirectDataChoice;
066 import ucar.unidata.idv.MapViewManager;
067 import ucar.unidata.util.GuiUtils;
068 import ucar.unidata.util.LogUtil;
069 import ucar.unidata.view.geoloc.MapProjectionDisplay;
070 import ucar.visad.display.DisplayMaster;
071
072 import edu.wisc.ssec.mcidasv.Constants;
073 import edu.wisc.ssec.mcidasv.McIDASV;
074 import edu.wisc.ssec.mcidasv.data.ComboDataChoice;
075 import edu.wisc.ssec.mcidasv.data.hydra.MultiDimensionSubset;
076 import edu.wisc.ssec.mcidasv.data.hydra.MultiSpectralData;
077 import edu.wisc.ssec.mcidasv.data.hydra.MultiSpectralDataSource;
078 import edu.wisc.ssec.mcidasv.display.hydra.MultiSpectralDisplay;
079 import edu.wisc.ssec.mcidasv.display.hydra.MultiSpectralDisplay.DragLine;
080 import edu.wisc.ssec.mcidasv.jython.Console;
081 import edu.wisc.ssec.mcidasv.jython.ConsoleCallback;
082
083 public class LinearCombo extends HydraControl implements ConsoleCallback {
084
085 /** Trusty logging object. */
086 private static final Logger logger = LoggerFactory.getLogger(LinearCombo.class);
087
088 /** Help topic identifier. */
089 public static final String HYDRA_HELP_ID =
090 "idv.controls.hydra.linearcombinationcontrol";
091
092 /**
093 * Path to the Jython source code that allows for interaction with a
094 * linear combination display control.
095 */
096 public static final String HYDRA_SRC =
097 "/edu/wisc/ssec/mcidasv/resources/python/linearcombo/hydra.py";
098
099 /** Name used in Jython namespace to refer to the {@literal "IDV god object"}. */
100 public static final String CONSOLE_IDV_OBJECT = "idv";
101
102 /**
103 * Name used in Jython namespace to refer back to an instantiation of a
104 * linear combination control.
105 */
106 public static final String CONSOLE_CONTROL_OBJECT = "_linearCombo";
107
108 public static final String CONSOLE_OBJECT = "_jythonConsole";
109
110 public static final String CONSOLE_DATA_OBJECT = "_data";
111
112 private Console console;
113
114 private MultiSpectralDisplay display;
115
116 private DisplayMaster displayMaster;
117
118 private String sourceFile = "";
119
120 private ComboDataChoice comboChoice;
121
122 private MultiSpectralDataSource source;
123
124 private List<String> jythonHistory;
125
126 private Map<String, Selector> selectorMap;
127
128 private Map<String, Selector> jythonMap;
129
130 private DataChoice dataChoice = null;
131
132 /**
133 *
134 */
135 public LinearCombo() {
136 super();
137 setHelpUrl(HYDRA_HELP_ID);
138 jythonHistory = new ArrayList<String>();
139 selectorMap = new HashMap<String, Selector>();
140 jythonMap = new HashMap<String, Selector>();
141 }
142
143 @Override public boolean init(final DataChoice choice) throws VisADException, RemoteException {
144 List<DataSource> sources = new ArrayList<DataSource>();
145 choice.getDataSources(sources);
146 dataChoice = choice;
147
148 ((McIDASV)getIdv()).getMcvDataManager().setHydraControl(choice, this);
149
150 source = ((MultiSpectralDataSource)sources.get(0));
151 sourceFile = source.getDatasetName();
152
153 MultiSpectralData data = source.getMultiSpectralData(choice);
154
155 Float fieldSelectorChannel = (Float)getDataSelection().getProperty(Constants.PROP_CHAN);
156 if (fieldSelectorChannel == null)
157 fieldSelectorChannel = data.init_wavenumber;
158
159 console = new Console();
160 console.setCallbackHandler(this);
161
162 console.injectObject(CONSOLE_IDV_OBJECT, getIdv());
163 console.injectObject(CONSOLE_CONTROL_OBJECT, this);
164 console.injectObject(CONSOLE_OBJECT, console);
165 console.injectObject(CONSOLE_DATA_OBJECT, source.getMultiSpectralData(choice));
166
167 console.runFile("__main__", "/edu/wisc/ssec/mcidasv/resources/python/console_init.py");
168 console.runFile("__main__", HYDRA_SRC);
169
170 display = new MultiSpectralDisplay((DirectDataChoice)choice);
171 display.setWaveNumber(fieldSelectorChannel);
172 display.setDisplayControl(this);
173 ((McIDASV)getIdv()).getMcvDataManager().setHydraDisplay(choice, display);
174 return true;
175 }
176
177 @Override public void initDone() {
178 MapViewManager viewManager = (MapViewManager)getViewManager();
179 MapProjectionDisplay dispMaster =
180 (MapProjectionDisplay)viewManager.getMaster();
181
182 try {
183 dispMaster.setMapProjection(getDataProjection());
184 } catch (Exception e) {
185 logException("problem setting MapProjection", e);
186 }
187
188 getIdv().getIdvUIManager().showDashboard();
189 console.queueBatch("history", jythonHistory);
190 jythonHistory.clear();
191 }
192
193 public List<String> getJythonHistory() {
194 return console.getHistory();
195 }
196
197 public void setJythonHistory(final List<String> persistedHistory) {
198 jythonHistory = persistedHistory;
199 }
200
201 @Override public MapProjection getDataProjection() {
202 MapProjection mp = null;
203 HashMap subset = null;
204 Hashtable table = dataChoice.getProperties();
205 MultiDimensionSubset dataSel =
206 (MultiDimensionSubset)table.get(MultiDimensionSubset.key);
207
208 if (dataSel != null) {
209 subset = dataSel.getSubset();
210 }
211 mp = source.getDataProjection(subset);
212 return mp;
213 }
214
215 @Override public Container doMakeContents() {
216 JTabbedPane pane = new JTabbedPane();
217 pane.add("Console", GuiUtils.inset(getConsoleTab(), 5));
218 GuiUtils.handleHeavyWeightComponentsInTabs(pane);
219 return pane;
220 }
221
222 private JComponent getConsoleTab() {
223 JPanel consolePanel = console.getPanel();
224 consolePanel.setPreferredSize(new Dimension(500, 150));
225 return GuiUtils.topCenter(display.getDisplayComponent(), consolePanel);
226 }
227
228 @Override public void doRemove() throws VisADException, RemoteException {
229 super.doRemove();
230 }
231
232 @Override public String toString() {
233 return "[LinearCombo@" + Integer.toHexString(hashCode()) +
234 ": sourceFile=" + sourceFile + ']';
235 }
236
237 public void moveSelector(final String id, final float wavenum) {
238 if (!selectorMap.containsKey(id)) {
239 return;
240 }
241 display.updateControlSelector(id, wavenum);
242 }
243
244 public void updateSelector(final String id, final float wavenum) {
245 if (!selectorMap.containsKey(id)) {
246 return;
247 }
248
249 selectorMap.get(id).setWaveNumber(wavenum);
250 String cmd = new StringBuilder("_linearCombo.moveSelector('")
251 .append(id)
252 .append("', ")
253 .append(wavenum)
254 .append(')')
255 .toString();
256
257 console.addPretendHistory(cmd);
258 }
259
260 protected void addSelector(final Selector selector) throws Exception {
261 ConstantMap[] mapping = selector.getColor();
262 float r = Double.valueOf(mapping[0].getConstant()).floatValue();
263 float g = Double.valueOf(mapping[1].getConstant()).floatValue();
264 float b = Double.valueOf(mapping[2].getConstant()).floatValue();
265 Color javaColor = new Color(r, g, b);
266 display.createSelector(selector.getId(), javaColor);
267 display.setSelectorValue(selector.getId(), selector.getWaveNumber());
268 selectorMap.put(selector.getId(), selector);
269 logger.trace("added selector={}", selector);
270 }
271
272 protected MultiSpectralDisplay getMultiSpectralDisplay() {
273 return display;
274 }
275
276 protected int getSelectorCount() {
277 return selectorMap.size();
278 }
279
280 private Set<String> getSelectorIds(final Map<String, Object> objMap) {
281 assert objMap != null : objMap;
282
283 Set<String> ids = new HashSet<String>();
284 Collection<Object> jython = objMap.values();
285
286 for (Iterator<Object> i = jython.iterator(); i.hasNext();) {
287 Object obj = i.next();
288 if (!(obj instanceof Selector)) {
289 continue;
290 }
291
292 String selectorId = ((Selector)obj).getId();
293 ids.add(selectorId);
294 }
295
296 return ids;
297 }
298
299 /**
300 *
301 *
302 * @param objMap
303 *
304 * @return
305 */
306 private Map<String, Selector> mapNamesToThings(final Map<String, Object> objMap) {
307 assert objMap != null : objMap;
308
309 Map<String, Selector> nameMap = new HashMap<String, Selector>(objMap.size());
310 Set<Selector> seen = new LinkedHashSet<Selector>();
311 for (Map.Entry<String, Object> entry : objMap.entrySet()) {
312 Object obj = entry.getValue();
313 if (!(obj instanceof Selector)) {
314 continue;
315 }
316
317 String name = entry.getKey();
318 Selector selector = (Selector)obj;
319 if (!seen.contains(selector)) {
320 seen.add(selector);
321 selector.clearNames();
322 }
323 nameMap.put(name, selector);
324 selector.addName(name);
325 }
326 return nameMap;
327 }
328
329 public float getInitialWavenumber() {
330 return display.getMultiSpectralData().init_wavenumber;
331 }
332
333 public PyDictionary getBandNameMappings() {
334 PyDictionary map = new PyDictionary();
335 MultiSpectralData data = display.getMultiSpectralData();
336 if (!data.hasBandNames())
337 return map;
338
339 for (Entry<String, Float> entry : data.getBandNameMap().entrySet()) {
340 map.__setitem__(entry.getKey(), new PyFloat(entry.getValue()));
341 }
342
343 return map;
344 }
345
346 public void addCombination(final String name, final Data combo) {
347 source.addChoice(name, combo);
348 }
349
350 // public void addRealCombination(final String name, final Combination combo) {
351 // source.addRealCombo(name, combo, console);
352 // }
353 //
354 // public Console getConsole() {
355 // return console;
356 // }
357
358 /**
359 * Called after Jython's internals have finished processing {@code line}
360 * (and before control is given back to the user).
361 *
362 * <p>This is where {@code LinearCombo} controls map Jython names to Java
363 * objects.
364 */
365 public void ranBlock(final String line) {
366 List<DragLine> dragLines = display.getSelectors();
367 Map<String, Object> javaObjects = console.getJavaInstances();
368 Set<String> ids = getSelectorIds(javaObjects);
369 for (DragLine dragLine : dragLines) {
370 String lineId = dragLine.getControlId();
371 if (!ids.contains(lineId)) {
372 display.removeSelector(lineId);
373 selectorMap.remove(lineId);
374 }
375 }
376
377 jythonMap = mapNamesToThings(javaObjects);
378 logger.trace("ranBlock: javaObjs={}", javaObjects);
379 }
380
381 // public void saveJythonThings() {
382 // // well, only selectors so far...
383 // for (Map.Entry<String, Selector> entry : jythonMap.entrySet()) {
384 // String cmd = String.format("%s.setWaveNumber(%f)", entry.getKey(), entry.getValue().getWaveNumber());
385 // System.err.println("saving: "+cmd);
386 // console.addMetaCommand(cmd);
387 // }
388 // }
389
390 public static abstract class JythonThing {
391 protected Set<String> jythonNames = new LinkedHashSet<String>();
392 public JythonThing() { }
393 public abstract Data getData();
394
395 public static String colorString(final ConstantMap[] color) {
396 if (color == null) {
397 return "[null]";
398 }
399 if (color.length != 3) {
400 return "[invalid color string]";
401 }
402
403 double r = color[0].getConstant();
404 double g = color[1].getConstant();
405 double b = color[2].getConstant();
406 return String.format("[r=%.3f; g=%.3f; b=%.3f]", r, g, b);
407 }
408
409 private static Data extractData(final Object other) throws VisADException, RemoteException {
410 if (other instanceof JythonThing) {
411 return ((JythonThing)other).getData();
412 }
413 if (other instanceof PyFloat) {
414 return new Real(((PyFloat)other).getValue());
415 }
416 if (other instanceof PyInteger) {
417 return new Real(((PyInteger)other).getValue());
418 }
419 if (other instanceof Double) {
420 return new Real((Double)other);
421 }
422 if (other instanceof Integer) {
423 return new Real((Integer)other);
424 }
425 if (other instanceof Data) {
426 return (Data)other;
427 }
428 throw new IllegalArgumentException("Can't figure out what to do with " + other);
429 }
430
431 private static String extractName(final Object other) {
432 if (other instanceof JythonThing) {
433 return ((JythonThing)other).getName();
434 }
435 if (other instanceof PyInteger) {
436 return ((PyInteger)other).toString();
437 }
438 if (other instanceof PyFloat) {
439 return ((PyFloat)other).toString();
440 }
441 if (other instanceof Double) {
442 return ((Double)other).toString();
443 }
444 if (other instanceof Integer) {
445 return ((Integer)other).toString();
446 }
447 throw new IllegalArgumentException("UGH: "+other);
448 }
449
450 public abstract boolean removeName(final String name);
451 public abstract boolean addName(final String name);
452 public abstract String getName();
453 public abstract Collection<String> getNames();
454
455 public Combination __add__(final Object other) throws VisADException, RemoteException {
456 return new AddCombination(this, other);
457 }
458 public Combination __sub__(final Object other) throws VisADException, RemoteException {
459 return new SubtractCombination(this, other);
460 }
461 public Combination __mul__(final Object other) throws VisADException, RemoteException {
462 return new MultiplyCombination(this, other);
463 }
464 public Combination __div__(final Object other) throws VisADException, RemoteException {
465 return new DivideCombination(this, other);
466 }
467 public Combination __pow__(final Object other) throws VisADException, RemoteException {
468 return new ExponentCombination(this, other);
469 }
470 public Combination __mod__(final Object other) throws VisADException, RemoteException {
471 return new ModuloCombination(this, other);
472 }
473 public Combination __radd__(final Object other) throws VisADException, RemoteException {
474 return new AddCombination(other, this);
475 }
476 public Combination __rsub__(final Object other) throws VisADException, RemoteException {
477 return new SubtractCombination(other, this);
478 }
479 public Combination __rmul__(final Object other) throws VisADException, RemoteException {
480 return new MultiplyCombination(other, this);
481 }
482 public Combination __rdiv__(final Object other) throws VisADException, RemoteException {
483 return new DivideCombination(other, this);
484 }
485 public Combination __rpow__(final Object other) throws VisADException, RemoteException {
486 return new ExponentCombination(other, this);
487 }
488 public Combination __rmod__(final Object other) throws VisADException, RemoteException {
489 return new ModuloCombination(other, this);
490 }
491 public Combination __neg__() throws VisADException, RemoteException {
492 return new NegateCombination(this);
493 }
494 }
495
496 /**
497 *
498 */
499 public static class Selector extends JythonThing {
500
501 /** */
502 private final String ID;
503
504 /** */
505 private float waveNumber;
506
507 /** */
508 private ConstantMap[] color;
509
510 /** */
511 private Console console;
512
513 /** */
514 private HydraControl control;
515
516 /** */
517 private Data data;
518
519 /** */
520 private MultiSpectralDisplay display;
521
522 /**
523 *
524 *
525 * @param waveNumber
526 * @param color
527 * @param control
528 * @param console
529 */
530 public Selector(final float waveNumber, final ConstantMap[] color, final HydraControl control, final Console console) {
531 super();
532 this.ID = hashCode() + "_jython";
533 this.waveNumber = waveNumber;
534 this.control = control;
535 this.console = console;
536 this.display = control.getMultiSpectralDisplay();
537
538 this.color = new ConstantMap[color.length];
539 for (int i = 0; i < this.color.length; i++) {
540 ConstantMap mappedColor = (ConstantMap)color[i];
541 this.color[i] = (ConstantMap)mappedColor.clone();
542 }
543
544 if (control instanceof LinearCombo) {
545 LinearCombo lc = (LinearCombo)control;
546 try {
547 lc.addSelector(this);
548 } catch (Exception e) {
549 // TODO(jon): no way jose
550 System.err.println("Could not create selector: "+e.getMessage());
551 e.printStackTrace();
552 }
553 }
554 }
555
556 /**
557 * Attempts removal of a known name for the current Selector.
558 *
559 * @param name Name (within Jython namespace) to remove.
560 *
561 * @return {@code true} if removal was successful, {@code false}
562 * otherwise.
563 */
564 public boolean removeName(final String name) {
565 return jythonNames.remove(name);
566 }
567
568 /**
569 * Returns the known Jython names associated with this Selector.
570 *
571 * @param {@literal "Names"} (aka variables) in a Jython namespace that
572 * refer to this Selector. Collection may be empty, but never
573 * {@code null}.
574 */
575 public Collection<String> getNames() {
576 return new LinkedHashSet<String>(jythonNames);
577 }
578
579 /**
580 * Resets the known names of a Selector.
581 */
582 public void clearNames() {
583 jythonNames.clear();
584 }
585
586 /**
587 * Attempts to associate a Jython {@literal "variable"/"name"} with
588 * this Selector.
589 *
590 * @param name Name used within the Jython namespace. Cannot be
591 * {@code null}.
592 *
593 * @return {@code true} if {@code name} was successfully added,
594 * {@code false} otherwise.
595 */
596 public boolean addName(final String name) {
597 return jythonNames.add(name);
598 }
599
600 /**
601 * Returns a Jython name associated with this Selector. Consider using
602 * {@link #getNames()} instead.
603 *
604 * @return Either a blank {@code String} if there are no associated
605 * names, or the {@literal "first"} (iteration-order) name.
606 *
607 * @see #getNames()
608 */
609 public String getName() {
610 if (jythonNames.isEmpty()) {
611 return "";
612 } else {
613 return jythonNames.iterator().next();
614 }
615 }
616
617 /**
618 * Changes the {@literal "selected"} wave number to the given value.
619 *
620 * <p><b>WARNING:</b>no bounds-checking is currently being performed,
621 * but this is expected to change in the near future.</p>
622 *
623 * @param newWaveNumber New wave number to associate with the current
624 * Selector.
625 */
626 public void setWaveNumber(final float newWaveNumber) {
627 waveNumber = newWaveNumber;
628 try {
629 display.setSelectorValue(ID, waveNumber);
630 } catch (Exception e) {
631 LogUtil.logException("Selector.setWaveNumber", e);
632 }
633 }
634
635 /**
636 * Returns the {@literal "selected"} wave number associated with this
637 * Selector.
638 *
639 * @return Wave number currently selected by this Selector.
640 */
641 public float getWaveNumber() {
642 return waveNumber;
643 }
644
645 /**
646 *
647 *
648 * @return
649 */
650 public ConstantMap[] getColor() {
651 return color;
652 }
653
654 /**
655 *
656 *
657 * @return
658 */
659 public Data getData() {
660 return control.getMultiSpectralDisplay().getImageDataFrom(waveNumber);
661 }
662
663 /**
664 *
665 *
666 * @return
667 */
668 public String getId() {
669 return ID;
670 }
671
672 /**
673 *
674 * @return
675 */
676 @Override public String toString() {
677 int hashLen = 0;
678 int idLen = 0;
679 int waveLen = 0;
680 int colorLen = 0;
681 int namesLen = 0;
682 return String.format("[Selector@%x: id=%s, waveNumber=%f, color=%s, jythonNames=%s]",
683 hashCode(), ID, waveNumber, colorString(color), jythonNames);
684
685 }
686 }
687
688 public static abstract class Combination extends JythonThing {
689 private final Object left;
690 private final Object right;
691
692 private final String leftName;
693 private final String rightName;
694
695 private final Data leftData;
696 private final Data rightData;
697
698 private Data operationData;
699
700 public Combination(final Object lhs, final Object rhs) throws VisADException, RemoteException {
701 left = lhs;
702 right = rhs;
703
704 leftName = extractName(left);
705 rightName = extractName(right);
706
707 leftData = extractData(left);
708 rightData = extractData(right);
709 }
710
711 private static Data extractData(final Object obj) throws VisADException, RemoteException {
712 if (obj instanceof JythonThing) {
713 return ((JythonThing)obj).getData();
714 }
715 if (obj instanceof PyFloat) {
716 return new Real(((PyFloat)obj).getValue());
717 }
718 if (obj instanceof PyInteger) {
719 return new Real(((PyInteger)obj).getValue());
720 }
721 if (obj instanceof Double) {
722 return new Real((Double)obj);
723 }
724 if (obj instanceof Integer) {
725 return new Real((Integer)obj);
726 }
727 if (obj instanceof Data) {
728 return (Data)obj;
729 }
730 throw new IllegalArgumentException("Can't figure out what to do with " + obj);
731 }
732
733 protected static String extractName(final Object obj) {
734 if (obj instanceof JythonThing) {
735 return ((JythonThing)obj).getName();
736 }
737 if (obj instanceof PyFloat) {
738 return ((PyFloat)obj).toString();
739 }
740 if (obj instanceof PyInteger) {
741 return ((PyInteger)obj).toString();
742 }
743 if (obj instanceof Double) {
744 return ((Double)obj).toString();
745 }
746 if (obj instanceof Integer) {
747 return ((Integer)obj).toString();
748 }
749 throw new IllegalArgumentException("UGH: "+obj);
750 }
751
752 protected void setOperationData(final Data opData) {
753 operationData = opData;
754 }
755
756 protected Data getOperationData() {
757 return operationData;
758 }
759
760 //public Data
761
762 public Object getLeft() {
763 return left;
764 }
765
766 public Object getRight() {
767 return right;
768 }
769
770 public String getLeftName() {
771 return leftName;
772 }
773
774 public String getRightName() {
775 return rightName;
776 }
777
778 public Data getLeftData() {
779 return leftData;
780 }
781
782 public Data getRightData() {
783 return rightData;
784 }
785
786 public boolean removeName(final String name) {
787 return true;
788 }
789
790 public boolean addName(final String name) {
791 return true;
792 }
793
794 public String getName() {
795 return getFriendlyString();
796 }
797
798 public Data getData() {
799 return operationData;
800 }
801
802 public Collection<String> getNames() {
803 Set<String> set = new LinkedHashSet<String>(1);
804 set.add(getFriendlyString());
805 return set;
806 }
807
808 public abstract String getFriendlyString();
809 public abstract String getPersistableString();
810 public abstract String toString();
811 }
812
813 private static class AddCombination extends Combination {
814 public AddCombination(final Object lhs, final Object rhs) throws VisADException, RemoteException {
815 super(lhs, rhs);
816 setOperationData(getLeftData().add(getRightData()));
817 }
818 public String getFriendlyString() {
819 return String.format("(%s + %s)", getLeftName(), getRightName());
820 }
821 public String getPersistableString() {
822 return getFriendlyString();
823 }
824 public String toString() {
825 return String.format("[AddCombo@%x: leftName=%s, rightName=%s, friendlyString=%s, persistableString=%s]", hashCode(), getLeftName(), getRightName(), getFriendlyString(), getPersistableString());
826 }
827 }
828 private static class SubtractCombination extends Combination {
829 public SubtractCombination(final Object lhs, final Object rhs) throws VisADException, RemoteException {
830 super(lhs, rhs);
831 setOperationData(getLeftData().subtract(getRightData()));
832 }
833 public String getFriendlyString() {
834 return String.format("(%s - %s)", getLeftName(), getRightName());
835 }
836 public String getPersistableString() {
837 return getFriendlyString();
838 }
839 public String toString() {
840 return String.format("[SubtractCombo@%x: leftName=%s, rightName=%s, friendlyString=%s, persistableString=%s]",
841 hashCode(), getLeftName(), getRightName(), getFriendlyString(), getPersistableString());
842 }
843 }
844 private static class MultiplyCombination extends Combination {
845 public MultiplyCombination(final Object lhs, final Object rhs) throws VisADException, RemoteException {
846 super(lhs, rhs);
847 setOperationData(getLeftData().multiply(getRightData()));
848 }
849 public String getFriendlyString() {
850 return String.format("(%s * %s)", getLeftName(), getRightName());
851 }
852 public String getPersistableString() {
853 return getFriendlyString();
854 }
855 public String toString() {
856 return String.format("[MultiplyCombo@%x: leftName=%s, rightName=%s, friendlyString=%s, persistableString=%s]",
857 hashCode(), getLeftName(), getRightName(), getFriendlyString(), getPersistableString());
858 }
859 }
860 private static class DivideCombination extends Combination {
861 public DivideCombination(final Object lhs, final Object rhs) throws VisADException, RemoteException {
862 super(lhs, rhs);
863 setOperationData(getLeftData().divide(getRightData()));
864 }
865 public String getFriendlyString() {
866 return String.format("(%s / %s)", getLeftName(), getRightName());
867 }
868 public String getPersistableString() {
869 return getFriendlyString();
870 }
871 public String toString() {
872 return String.format("[DivideCombo@%x: leftName=%s, rightName=%s, friendlyString=%s, persistableString=%s]",
873 hashCode(), getLeftName(), getRightName(), getFriendlyString(), getPersistableString());
874 }
875 }
876 private static class ExponentCombination extends Combination {
877 public ExponentCombination(final Object lhs, final Object rhs) throws VisADException, RemoteException {
878 super(lhs, rhs);
879 setOperationData(getLeftData().pow(getRightData()));
880 }
881 public String getFriendlyString() {
882 return String.format("(%s**%s)", getLeftName(), getRightName());
883 }
884 public String getPersistableString() {
885 return getFriendlyString();
886 }
887 public String toString() {
888 return String.format("[ExponentCombo@%x: leftName=%s, rightName=%s, friendlyString=%s, persistableString=%s]",
889 hashCode(), getLeftName(), getRightName(), getFriendlyString(), getPersistableString());
890 }
891 }
892 private static class ModuloCombination extends Combination {
893 public ModuloCombination(final Object lhs, final Object rhs) throws VisADException, RemoteException {
894 super(lhs, rhs);
895 setOperationData(getLeftData().remainder(getRightData()));
896 }
897 public String getFriendlyString() {
898 return String.format("(%s %% %s)", getLeftName(), getRightName());
899 }
900 public String getPersistableString() {
901 return getFriendlyString();
902 }
903 public String toString() {
904 return String.format("[ModuloCombo@%x: leftName=%s, rightName=%s, friendlyString=%s, persistableString=%s]",
905 hashCode(), getLeftName(), getRightName(), getFriendlyString(), getPersistableString());
906 }
907 }
908 private static class NegateCombination extends Combination {
909 public NegateCombination(final Object lhs) throws VisADException, RemoteException {
910 super(lhs, null);
911 setOperationData(getLeftData().negate());
912 }
913 public String getFriendlyString() {
914 return String.format("(-%s)", getLeftName());
915 }
916 public String getPersistableString() {
917 return getFriendlyString();
918 }
919 public String toString() {
920 return String.format("[NegateCombo@%x: leftName=%s, friendlyString=%s, persistableString=%s]",
921 hashCode(), getLeftName(), getFriendlyString(), getPersistableString());
922 }
923 }
924 }