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