001/*
002 * This file is part of McIDAS-V
003 *
004 * Copyright 2007-2024
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 https://www.gnu.org/licenses/.
027 */
028
029package edu.wisc.ssec.mcidasv.control;
030
031import java.awt.Color;
032import java.awt.Container;
033import java.awt.Dimension;
034import java.rmi.RemoteException;
035import java.util.ArrayList;
036import java.util.Collection;
037import java.util.HashMap;
038import java.util.HashSet;
039import java.util.Hashtable;
040import java.util.Iterator;
041import java.util.LinkedHashSet;
042import java.util.List;
043import java.util.Map;
044import java.util.Map.Entry;
045import java.util.Set;
046
047import javax.swing.JComponent;
048import javax.swing.JPanel;
049import javax.swing.JTabbedPane;
050
051import org.python.core.PyDictionary;
052import org.python.core.PyFloat;
053import org.python.core.PyInteger;
054import org.slf4j.Logger;
055import org.slf4j.LoggerFactory;
056
057import visad.ConstantMap;
058import visad.Data;
059import visad.Real;
060import visad.VisADException;
061import visad.georef.MapProjection;
062
063import ucar.unidata.data.DataChoice;
064import ucar.unidata.data.DataSource;
065import ucar.unidata.data.DirectDataChoice;
066import ucar.unidata.idv.MapViewManager;
067import ucar.unidata.util.GuiUtils;
068import ucar.unidata.util.LogUtil;
069import ucar.unidata.view.geoloc.MapProjectionDisplay;
070import ucar.visad.display.DisplayMaster;
071
072import edu.wisc.ssec.mcidasv.Constants;
073import edu.wisc.ssec.mcidasv.McIDASV;
074import edu.wisc.ssec.mcidasv.data.ComboDataChoice;
075import edu.wisc.ssec.mcidasv.data.hydra.MultiDimensionSubset;
076import edu.wisc.ssec.mcidasv.data.hydra.MultiSpectralData;
077import edu.wisc.ssec.mcidasv.data.hydra.MultiSpectralDataSource;
078import edu.wisc.ssec.mcidasv.display.hydra.MultiSpectralDisplay;
079import edu.wisc.ssec.mcidasv.display.hydra.MultiSpectralDisplay.DragLine;
080import edu.wisc.ssec.mcidasv.jython.Console;
081import edu.wisc.ssec.mcidasv.jython.ConsoleCallback;
082
083public 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        Map<String, double[]> 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     * Return a mapping of names to their {@link edu.wisc.ssec.mcidasv.control.LinearCombo.Selector Selectors}.
301     * 
302     * @param objMap {@code Map} of objects.
303     * 
304     * @return Map of name to {@code Selector}.
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     * Selectors are objects that allow users to select a given wavenumber/band
498     * by simply dragging within the GUI.
499     */
500    public static class Selector extends JythonThing {
501
502        /** */
503        private final String ID;
504
505        /** */
506        private float waveNumber;
507
508        /** */
509        private ConstantMap[] color;
510
511        /** */
512        private Console console;
513
514        /** */
515        private HydraControl control;
516
517        /** */
518        private Data data;
519
520        /** */
521        private MultiSpectralDisplay display;
522
523        /**
524         * Create a new Selector.
525         * 
526         * @param waveNumber Initial {@literal "wave number (or band)"} of the Selector.
527         * @param color RGB triple that will be the color of the Selector.
528         * @param control Control that created the Selector.
529         * @param console Console that created the Selector.
530         */
531        public Selector(final float waveNumber, final ConstantMap[] color, final HydraControl control, final Console console) {
532            super();
533            this.ID = hashCode() + "_jython";
534            this.waveNumber = waveNumber;
535            this.control = control;
536            this.console = console;
537            this.display = control.getMultiSpectralDisplay();
538
539            this.color = new ConstantMap[color.length];
540            for (int i = 0; i < this.color.length; i++) {
541                ConstantMap mappedColor = color[i];
542                this.color[i] = (ConstantMap)mappedColor.clone();
543            }
544
545            if (control instanceof LinearCombo) {
546                LinearCombo lc = (LinearCombo)control;
547                try {
548                    lc.addSelector(this);
549                } catch (Exception e) {
550                    logger.error("Could not create selector", e);
551                }
552            }
553        }
554
555        /**
556         * Attempts removal of a known name for the current Selector.
557         * 
558         * @param name Name (within Jython namespace) to remove.
559         * 
560         * @return {@code true} if removal was successful, {@code false} 
561         * otherwise.
562         */
563        public boolean removeName(final String name) {
564            return jythonNames.remove(name);
565        }
566
567        /**
568         * Returns the known Jython names associated with this Selector.
569         * 
570         * @return {@literal "Names"} (aka variables) in a Jython namespace that
571         * refer to this Selector. Collection may be empty, but never 
572         * {@code null}.
573         */
574        public Collection<String> getNames() {
575            return new LinkedHashSet<String>(jythonNames);
576        }
577
578        /**
579         * Resets the known names of a Selector.
580         */
581        public void clearNames() {
582            jythonNames.clear();
583        }
584
585        /**
586         * Attempts to associate a Jython {@literal "variable"/"name"} with 
587         * this Selector.
588         * 
589         * @param name Name used within the Jython namespace. Cannot be 
590         * {@code null}.
591         * 
592         * @return {@code true} if {@code name} was successfully added, 
593         * {@code false} otherwise.
594         */
595        public boolean addName(final String name) {
596            return jythonNames.add(name);
597        }
598
599        /**
600         * Returns a Jython name associated with this Selector. Consider using
601         * {@link #getNames()} instead.
602         * 
603         * @return Either a blank {@code String} if there are no associated 
604         * names, or the {@literal "first"} (iteration-order) name.
605         * 
606         * @see #getNames()
607         */
608        public String getName() {
609            if (jythonNames.isEmpty()) {
610                return "";
611            } else {
612                return jythonNames.iterator().next();
613            }
614        }
615
616        /**
617         * Changes the {@literal "selected"} wave number to the given value.
618         * 
619         * <p><b>WARNING:</b>no bounds-checking is currently being performed,
620         * but this is expected to change in the near future.</p>
621         * 
622         * @param newWaveNumber New wave number to associate with the current
623         * Selector.
624         */
625        public void setWaveNumber(final float newWaveNumber) {
626            waveNumber = newWaveNumber;
627            try {
628                display.setSelectorValue(ID, waveNumber);
629            } catch (Exception e) {
630                LogUtil.logException("Selector.setWaveNumber", e);
631            }
632        }
633
634        /**
635         * Returns the {@literal "selected"} wave number associated with this
636         * {@code Selector}.
637         * 
638         * @return Wave number currently selected by this {@code Selector}.
639         */
640        public float getWaveNumber() {
641            return waveNumber;
642        }
643
644        /**
645         * Returns the color associated with this {@code Selector}.
646         * 
647         * @return {@literal "Color"} for this {@code Selector}.
648         */
649        public ConstantMap[] getColor() {
650            return color;
651        }
652
653        /**
654         * Returns the data selected by the location of this {@code Selector}.
655         * 
656         * @return Data selected by this {@code Selector}.
657         */
658        public Data getData() {
659            return control.getMultiSpectralDisplay().getImageDataFrom(waveNumber);
660        }
661
662        /**
663         * Returns an identifier for this {@code Selector}.
664         * 
665         * @return ID for this {@code Selector}.
666         */
667       public String getId() {
668            return ID;
669        }
670
671       /**
672        * Returns a {@code String} representation of the relevant information
673        * {@literal "stored"} by this {@code Selector}.
674        *
675        * @return {@code String} representation of this {@code Selector}.
676        */
677       @Override public String toString() {
678           int hashLen = 0;
679           int idLen = 0;
680           int waveLen = 0;
681           int colorLen = 0;
682           int namesLen = 0;
683           return String.format("[Selector@%x: id=%s, waveNumber=%f, color=%s, jythonNames=%s]",
684               hashCode(), ID, waveNumber, colorString(color), jythonNames);
685           
686       }
687    }
688
689    public static abstract class Combination extends JythonThing {
690        private final Object left;
691        private final Object right;
692
693        private final String leftName;
694        private final String rightName;
695
696        private final Data leftData;
697        private final Data rightData;
698
699        private Data operationData;
700
701        public Combination(final Object lhs, final Object rhs) throws VisADException, RemoteException {
702            left = lhs;
703            right = rhs;
704
705            leftName = extractName(left);
706            rightName = extractName(right);
707
708            leftData = extractData(left);
709            rightData = extractData(right);
710        }
711
712        private static Data extractData(final Object obj) throws VisADException, RemoteException {
713            if (obj instanceof JythonThing) {
714                return ((JythonThing)obj).getData();
715            }
716            if (obj instanceof PyFloat) {
717                return new Real(((PyFloat)obj).getValue());
718            }
719            if (obj instanceof PyInteger) {
720                return new Real(((PyInteger)obj).getValue());
721            }
722            if (obj instanceof Double) {
723                return new Real((Double)obj);
724            }
725            if (obj instanceof Integer) {
726                return new Real((Integer)obj);
727            }
728            if (obj instanceof Data) {
729                return (Data)obj;
730            }
731            throw new IllegalArgumentException("Can't figure out what to do with " + obj);
732        }
733
734        protected static String extractName(final Object obj) {
735            if (obj instanceof JythonThing) {
736                return ((JythonThing)obj).getName();
737            }
738            if (obj instanceof PyFloat) {
739                return ((PyFloat)obj).toString();
740            }
741            if (obj instanceof PyInteger) {
742                return ((PyInteger)obj).toString();
743            }
744            if (obj instanceof Double) {
745                return ((Double)obj).toString();
746            }
747            if (obj instanceof Integer) {
748                return ((Integer)obj).toString();
749            }
750            throw new IllegalArgumentException("UGH: "+obj);
751        }
752
753        protected void setOperationData(final Data opData) {
754            operationData = opData;
755        }
756
757        protected Data getOperationData() {
758            return operationData;
759        }
760
761        //public Data 
762        
763        public Object getLeft() {
764            return left;
765        }
766
767        public Object getRight() {
768            return right;
769        }
770
771        public String getLeftName() {
772            return leftName;
773        }
774
775        public String getRightName() {
776            return rightName;
777        }
778
779        public Data getLeftData() {
780            return leftData;
781        }
782
783        public Data getRightData() {
784            return rightData;
785        }
786
787        public boolean removeName(final String name) {
788            return true;
789        }
790
791        public boolean addName(final String name) {
792            return true;
793        }
794
795        public String getName() {
796            return getFriendlyString();
797        }
798
799        public Data getData() {
800            return operationData;
801        }
802
803        public Collection<String> getNames() {
804            Set<String> set = new LinkedHashSet<String>(1);
805            set.add(getFriendlyString());
806            return set;
807        }
808
809        public abstract String getFriendlyString();
810        public abstract String getPersistableString();
811        public abstract String toString();
812    }
813
814    private static class AddCombination extends Combination {
815        public AddCombination(final Object lhs, final Object rhs) throws VisADException, RemoteException {
816            super(lhs, rhs);
817            setOperationData(getLeftData().add(getRightData()));
818        }
819        public String getFriendlyString() {
820            return String.format("(%s + %s)", getLeftName(), getRightName());
821        }
822        public String getPersistableString() {
823            return getFriendlyString();
824        }
825        public String toString() {
826            return String.format("[AddCombo@%x: leftName=%s, rightName=%s, friendlyString=%s, persistableString=%s]", hashCode(), getLeftName(), getRightName(), getFriendlyString(), getPersistableString());
827        }
828    }
829    private static class SubtractCombination extends Combination {
830        public SubtractCombination(final Object lhs, final Object rhs) throws VisADException, RemoteException {
831            super(lhs, rhs);
832            setOperationData(getLeftData().subtract(getRightData()));
833        }
834        public String getFriendlyString() {
835            return String.format("(%s - %s)", getLeftName(), getRightName());
836        }
837        public String getPersistableString() {
838            return getFriendlyString();
839        }
840        public String toString() {
841            return String.format("[SubtractCombo@%x: leftName=%s, rightName=%s, friendlyString=%s, persistableString=%s]", 
842                hashCode(), getLeftName(), getRightName(), getFriendlyString(), getPersistableString());
843        }
844    }
845    private static class MultiplyCombination extends Combination {
846        public MultiplyCombination(final Object lhs, final Object rhs) throws VisADException, RemoteException {
847            super(lhs, rhs);
848            setOperationData(getLeftData().multiply(getRightData()));
849        }
850        public String getFriendlyString() {
851            return String.format("(%s * %s)", getLeftName(), getRightName());
852        }
853        public String getPersistableString() {
854            return getFriendlyString();
855        }
856        public String toString() {
857            return String.format("[MultiplyCombo@%x: leftName=%s, rightName=%s, friendlyString=%s, persistableString=%s]", 
858                hashCode(), getLeftName(), getRightName(), getFriendlyString(), getPersistableString());
859        }
860    }
861    private static class DivideCombination extends Combination {
862        public DivideCombination(final Object lhs, final Object rhs) throws VisADException, RemoteException {
863            super(lhs, rhs);
864            setOperationData(getLeftData().divide(getRightData()));
865        }
866        public String getFriendlyString() {
867            return String.format("(%s / %s)", getLeftName(), getRightName());
868        }
869        public String getPersistableString() {
870            return getFriendlyString();
871        }
872        public String toString() {
873            return String.format("[DivideCombo@%x: leftName=%s, rightName=%s, friendlyString=%s, persistableString=%s]", 
874                hashCode(), getLeftName(), getRightName(), getFriendlyString(), getPersistableString());
875        }
876    }
877    private static class ExponentCombination extends Combination {
878        public ExponentCombination(final Object lhs, final Object rhs) throws VisADException, RemoteException {
879            super(lhs, rhs);
880            setOperationData(getLeftData().pow(getRightData()));
881        }
882        public String getFriendlyString() {
883            return String.format("(%s**%s)", getLeftName(), getRightName());
884        }
885        public String getPersistableString() {
886            return getFriendlyString();
887        }
888        public String toString() {
889            return String.format("[ExponentCombo@%x: leftName=%s, rightName=%s, friendlyString=%s, persistableString=%s]", 
890                hashCode(), getLeftName(), getRightName(), getFriendlyString(), getPersistableString());
891        }
892    }
893    private static class ModuloCombination extends Combination {
894        public ModuloCombination(final Object lhs, final Object rhs) throws VisADException, RemoteException {
895            super(lhs, rhs);
896            setOperationData(getLeftData().remainder(getRightData()));
897        }
898        public String getFriendlyString() {
899            return String.format("(%s %% %s)", getLeftName(), getRightName());
900        }
901        public String getPersistableString() {
902            return getFriendlyString();
903        }
904        public String toString() {
905            return String.format("[ModuloCombo@%x: leftName=%s, rightName=%s, friendlyString=%s, persistableString=%s]", 
906                hashCode(), getLeftName(), getRightName(), getFriendlyString(), getPersistableString());
907        }
908    }
909    private static class NegateCombination extends Combination {
910        public NegateCombination(final Object lhs) throws VisADException, RemoteException {
911            super(lhs, null);
912            setOperationData(getLeftData().negate());
913        }
914        public String getFriendlyString() {
915            return String.format("(-%s)", getLeftName());
916        }
917        public String getPersistableString() {
918            return getFriendlyString();
919        }
920        public String toString() {
921            return String.format("[NegateCombo@%x: leftName=%s, friendlyString=%s, persistableString=%s]", 
922                hashCode(), getLeftName(), getFriendlyString(), getPersistableString());
923        }
924    }
925}