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