001/*
002 * This file is part of McIDAS-V
003 *
004 * Copyright 2007-2015
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
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        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     * 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     * 
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         * @return {@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         * {@code Selector}.
638         * 
639         * @return Wave number currently selected by this {@code Selector}.
640         */
641        public float getWaveNumber() {
642            return waveNumber;
643        }
644
645        /**
646         * Returns the color associated with this {@code Selector}.
647         * 
648         * @return {@literal "Color"} for this {@code Selector}.
649         */
650        public ConstantMap[] getColor() {
651            return color;
652        }
653
654        /**
655         * Returns the data selected by the location of this {@code Selector}.
656         * 
657         * @return Data selected by this {@code Selector}.
658         */
659        public Data getData() {
660            return control.getMultiSpectralDisplay().getImageDataFrom(waveNumber);
661        }
662
663        /**
664         * Returns an identifier for this {@code Selector}.
665         * 
666         * @return ID for this {@code Selector}.
667         */
668       public String getId() {
669            return ID;
670        }
671
672       /**
673        * Returns a {@code String} representation of the relevant information
674        * {@literal "stored"} by this {@code Selector}.
675        *
676        * @return {@code String} representation of this {@code Selector}.
677        */
678       @Override public String toString() {
679           int hashLen = 0;
680           int idLen = 0;
681           int waveLen = 0;
682           int colorLen = 0;
683           int namesLen = 0;
684           return String.format("[Selector@%x: id=%s, waveNumber=%f, color=%s, jythonNames=%s]",
685               hashCode(), ID, waveNumber, colorString(color), jythonNames);
686           
687       }
688    }
689
690    public static abstract class Combination extends JythonThing {
691        private final Object left;
692        private final Object right;
693
694        private final String leftName;
695        private final String rightName;
696
697        private final Data leftData;
698        private final Data rightData;
699
700        private Data operationData;
701
702        public Combination(final Object lhs, final Object rhs) throws VisADException, RemoteException {
703            left = lhs;
704            right = rhs;
705
706            leftName = extractName(left);
707            rightName = extractName(right);
708
709            leftData = extractData(left);
710            rightData = extractData(right);
711        }
712
713        private static Data extractData(final Object obj) throws VisADException, RemoteException {
714            if (obj instanceof JythonThing) {
715                return ((JythonThing)obj).getData();
716            }
717            if (obj instanceof PyFloat) {
718                return new Real(((PyFloat)obj).getValue());
719            }
720            if (obj instanceof PyInteger) {
721                return new Real(((PyInteger)obj).getValue());
722            }
723            if (obj instanceof Double) {
724                return new Real((Double)obj);
725            }
726            if (obj instanceof Integer) {
727                return new Real((Integer)obj);
728            }
729            if (obj instanceof Data) {
730                return (Data)obj;
731            }
732            throw new IllegalArgumentException("Can't figure out what to do with " + obj);
733        }
734
735        protected static String extractName(final Object obj) {
736            if (obj instanceof JythonThing) {
737                return ((JythonThing)obj).getName();
738            }
739            if (obj instanceof PyFloat) {
740                return ((PyFloat)obj).toString();
741            }
742            if (obj instanceof PyInteger) {
743                return ((PyInteger)obj).toString();
744            }
745            if (obj instanceof Double) {
746                return ((Double)obj).toString();
747            }
748            if (obj instanceof Integer) {
749                return ((Integer)obj).toString();
750            }
751            throw new IllegalArgumentException("UGH: "+obj);
752        }
753
754        protected void setOperationData(final Data opData) {
755            operationData = opData;
756        }
757
758        protected Data getOperationData() {
759            return operationData;
760        }
761
762        //public Data 
763        
764        public Object getLeft() {
765            return left;
766        }
767
768        public Object getRight() {
769            return right;
770        }
771
772        public String getLeftName() {
773            return leftName;
774        }
775
776        public String getRightName() {
777            return rightName;
778        }
779
780        public Data getLeftData() {
781            return leftData;
782        }
783
784        public Data getRightData() {
785            return rightData;
786        }
787
788        public boolean removeName(final String name) {
789            return true;
790        }
791
792        public boolean addName(final String name) {
793            return true;
794        }
795
796        public String getName() {
797            return getFriendlyString();
798        }
799
800        public Data getData() {
801            return operationData;
802        }
803
804        public Collection<String> getNames() {
805            Set<String> set = new LinkedHashSet<String>(1);
806            set.add(getFriendlyString());
807            return set;
808        }
809
810        public abstract String getFriendlyString();
811        public abstract String getPersistableString();
812        public abstract String toString();
813    }
814
815    private static class AddCombination extends Combination {
816        public AddCombination(final Object lhs, final Object rhs) throws VisADException, RemoteException {
817            super(lhs, rhs);
818            setOperationData(getLeftData().add(getRightData()));
819        }
820        public String getFriendlyString() {
821            return String.format("(%s + %s)", getLeftName(), getRightName());
822        }
823        public String getPersistableString() {
824            return getFriendlyString();
825        }
826        public String toString() {
827            return String.format("[AddCombo@%x: leftName=%s, rightName=%s, friendlyString=%s, persistableString=%s]", hashCode(), getLeftName(), getRightName(), getFriendlyString(), getPersistableString());
828        }
829    }
830    private static class SubtractCombination extends Combination {
831        public SubtractCombination(final Object lhs, final Object rhs) throws VisADException, RemoteException {
832            super(lhs, rhs);
833            setOperationData(getLeftData().subtract(getRightData()));
834        }
835        public String getFriendlyString() {
836            return String.format("(%s - %s)", getLeftName(), getRightName());
837        }
838        public String getPersistableString() {
839            return getFriendlyString();
840        }
841        public String toString() {
842            return String.format("[SubtractCombo@%x: leftName=%s, rightName=%s, friendlyString=%s, persistableString=%s]", 
843                hashCode(), getLeftName(), getRightName(), getFriendlyString(), getPersistableString());
844        }
845    }
846    private static class MultiplyCombination extends Combination {
847        public MultiplyCombination(final Object lhs, final Object rhs) throws VisADException, RemoteException {
848            super(lhs, rhs);
849            setOperationData(getLeftData().multiply(getRightData()));
850        }
851        public String getFriendlyString() {
852            return String.format("(%s * %s)", getLeftName(), getRightName());
853        }
854        public String getPersistableString() {
855            return getFriendlyString();
856        }
857        public String toString() {
858            return String.format("[MultiplyCombo@%x: leftName=%s, rightName=%s, friendlyString=%s, persistableString=%s]", 
859                hashCode(), getLeftName(), getRightName(), getFriendlyString(), getPersistableString());
860        }
861    }
862    private static class DivideCombination extends Combination {
863        public DivideCombination(final Object lhs, final Object rhs) throws VisADException, RemoteException {
864            super(lhs, rhs);
865            setOperationData(getLeftData().divide(getRightData()));
866        }
867        public String getFriendlyString() {
868            return String.format("(%s / %s)", getLeftName(), getRightName());
869        }
870        public String getPersistableString() {
871            return getFriendlyString();
872        }
873        public String toString() {
874            return String.format("[DivideCombo@%x: leftName=%s, rightName=%s, friendlyString=%s, persistableString=%s]", 
875                hashCode(), getLeftName(), getRightName(), getFriendlyString(), getPersistableString());
876        }
877    }
878    private static class ExponentCombination extends Combination {
879        public ExponentCombination(final Object lhs, final Object rhs) throws VisADException, RemoteException {
880            super(lhs, rhs);
881            setOperationData(getLeftData().pow(getRightData()));
882        }
883        public String getFriendlyString() {
884            return String.format("(%s**%s)", getLeftName(), getRightName());
885        }
886        public String getPersistableString() {
887            return getFriendlyString();
888        }
889        public String toString() {
890            return String.format("[ExponentCombo@%x: leftName=%s, rightName=%s, friendlyString=%s, persistableString=%s]", 
891                hashCode(), getLeftName(), getRightName(), getFriendlyString(), getPersistableString());
892        }
893    }
894    private static class ModuloCombination extends Combination {
895        public ModuloCombination(final Object lhs, final Object rhs) throws VisADException, RemoteException {
896            super(lhs, rhs);
897            setOperationData(getLeftData().remainder(getRightData()));
898        }
899        public String getFriendlyString() {
900            return String.format("(%s %% %s)", getLeftName(), getRightName());
901        }
902        public String getPersistableString() {
903            return getFriendlyString();
904        }
905        public String toString() {
906            return String.format("[ModuloCombo@%x: leftName=%s, rightName=%s, friendlyString=%s, persistableString=%s]", 
907                hashCode(), getLeftName(), getRightName(), getFriendlyString(), getPersistableString());
908        }
909    }
910    private static class NegateCombination extends Combination {
911        public NegateCombination(final Object lhs) throws VisADException, RemoteException {
912            super(lhs, null);
913            setOperationData(getLeftData().negate());
914        }
915        public String getFriendlyString() {
916            return String.format("(-%s)", getLeftName());
917        }
918        public String getPersistableString() {
919            return getFriendlyString();
920        }
921        public String toString() {
922            return String.format("[NegateCombo@%x: leftName=%s, friendlyString=%s, persistableString=%s]", 
923                hashCode(), getLeftName(), getFriendlyString(), getPersistableString());
924        }
925    }
926}