001    // License: GPL. For details, see LICENSE file.
002    package org.openstreetmap.josm.gui.mappaint.mapcss;
003    
004    import static org.openstreetmap.josm.tools.Utils.equal;
005    
006    import java.awt.Color;
007    import java.lang.reflect.Array;
008    import java.lang.reflect.InvocationTargetException;
009    import java.lang.reflect.Method;
010    import java.util.ArrayList;
011    import java.util.Arrays;
012    import java.util.List;
013    
014    import org.openstreetmap.josm.Main;
015    import org.openstreetmap.josm.actions.search.SearchCompiler;
016    import org.openstreetmap.josm.actions.search.SearchCompiler.Match;
017    import org.openstreetmap.josm.actions.search.SearchCompiler.ParseError;
018    import org.openstreetmap.josm.data.osm.OsmPrimitive;
019    import org.openstreetmap.josm.gui.mappaint.Cascade;
020    import org.openstreetmap.josm.gui.mappaint.Environment;
021    import org.openstreetmap.josm.tools.CheckParameterUtil;
022    import org.openstreetmap.josm.tools.ColorHelper;
023    import org.openstreetmap.josm.tools.Utils;
024    
025    public interface Expression {
026        public Object evaluate(Environment env);
027    
028        public static class LiteralExpression implements Expression {
029            Object literal;
030    
031            public LiteralExpression(Object literal) {
032                CheckParameterUtil.ensureParameterNotNull(literal);
033                this.literal = literal;
034            }
035    
036            @Override
037            public Object evaluate(Environment env) {
038                return literal;
039            }
040    
041            @Override
042            public String toString() {
043                if (literal instanceof float[])
044                    return Arrays.toString((float[]) literal);
045                return "<"+literal.toString()+">";
046            }
047        }
048    
049        public static class FunctionExpression implements Expression {
050            String name;
051            List<Expression> args;
052    
053            public FunctionExpression(String name, List<Expression> args) {
054                this.name = name;
055                this.args = args;
056            }
057    
058            public static class EvalFunctions {
059                Environment env;
060    
061                public Object eval(Object o) {
062                    return o;
063                }
064    
065                public static float plus(float... args) {
066                    float res = 0;
067                    for (float f : args) {
068                        res += f;
069                    }
070                    return res;
071                }
072    
073                public Float minus(float... args) {
074                    if (args.length == 0)
075                        return 0f;
076                    if (args.length == 1)
077                        return -args[0];
078                    float res = args[0];
079                    for (int i=1; i<args.length; ++i) {
080                        res -= args[i];
081                    }
082                    return res;
083                }
084    
085                public static float times(float... args) {
086                    float res = 1;
087                    for (float f : args) {
088                        res *= f;
089                    }
090                    return res;
091                }
092    
093                public Float divided_by(float... args) {
094                    if (args.length == 0)
095                        return 1f;
096                    float res = args[0];
097                    for (int i=1; i<args.length; ++i) {
098                        if (args[i] == 0f)
099                            return null;
100                        res /= args[i];
101                    }
102                    return res;
103                }
104    
105                public static List list(Object... args) {
106                    return Arrays.asList(args);
107                }
108    
109                public Color rgb(float r, float g, float b) {
110                    Color c = null;
111                    try {
112                        c = new Color(r, g, b);
113                    } catch (IllegalArgumentException e) {
114                        return null;
115                    }
116                    return c;
117                }
118    
119                public Color html2color(String html) {
120                    return ColorHelper.html2color(html);
121                }
122    
123                public String color2html(Color c) {
124                    return ColorHelper.color2html(c);
125                }
126    
127                public float red(Color c) {
128                    return Utils.color_int2float(c.getRed());
129                }
130    
131                public float green(Color c) {
132                    return Utils.color_int2float(c.getGreen());
133                }
134    
135                public float blue(Color c) {
136                    return Utils.color_int2float(c.getBlue());
137                }
138    
139                public String concat(Object... args) {
140                    StringBuilder res = new StringBuilder();
141                    for (Object f : args) {
142                        res.append(f.toString());
143                    }
144                    return res.toString();
145                }
146    
147                public Object prop(String key) {
148                    return prop(key, null);
149                }
150    
151                public Object prop(String key, String layer) {
152                    Cascade c;
153                    if (layer == null) {
154                        c = env.mc.getCascade(env.layer);
155                    } else {
156                        c = env.mc.getCascade(layer);
157                    }
158                    return c.get(key);
159                }
160    
161                public Boolean is_prop_set(String key) {
162                    return is_prop_set(key, null);
163                }
164    
165                public Boolean is_prop_set(String key, String layer) {
166                    Cascade c;
167                    if (layer == null) {
168                        // env.layer is null if expression is evaluated
169                        // in ExpressionCondition, but MultiCascade.getCascade
170                        // handles this
171                        c = env.mc.getCascade(env.layer);
172                    } else {
173                        c = env.mc.getCascade(layer);
174                    }
175                    return c.containsKey(key);
176                }
177    
178                public String tag(String key) {
179                    return env.osm.get(key);
180                }
181    
182                public String parent_tag(String key) {
183                    if (env.parent == null) {
184                        // we don't have a matched parent, so just search all referrers
185                        for (OsmPrimitive parent : env.osm.getReferrers()) {
186                            String value = parent.get(key);
187                            if (value != null)
188                                return value;
189                        }
190                        return null;
191                    }
192                    return env.parent.get(key);
193                }
194    
195                public boolean has_tag_key(String key) {
196                    return env.osm.hasKey(key);
197                }
198    
199                public Float index() {
200                    if (env.index == null)
201                        return null;
202                    return new Float(env.index + 1);
203                }
204    
205                public String role() {
206                    return env.getRole();
207                }
208    
209                public boolean not(boolean b) {
210                    return !b;
211                }
212    
213                public boolean greater_equal(float a, float b) {
214                    return a >= b;
215                }
216    
217                public boolean less_equal(float a, float b) {
218                    return a <= b;
219                }
220    
221                public boolean greater(float a, float b) {
222                    return a > b;
223                }
224    
225                public boolean less(float a, float b) {
226                    return a < b;
227                }
228    
229                public int length(String s) {
230                    return s.length();
231                }
232    
233                @SuppressWarnings("unchecked")
234                public boolean equal(Object a, Object b) {
235                    // make sure the casts are done in a meaningful way, so
236                    // the 2 objects really can be considered equal
237                    for (Class klass : new Class[] {
238                            Float.class, Boolean.class, Color.class, float[].class, String.class }) {
239                        Object a2 = Cascade.convertTo(a, klass);
240                        Object b2 = Cascade.convertTo(b, klass);
241                        if (a2 != null && b2 != null && a2.equals(b2))
242                            return true;
243                    }
244                    return false;
245                }
246    
247                public Boolean JOSM_search(String s) {
248                    Match m;
249                    try {
250                        m = SearchCompiler.compile(s, false, false);
251                    } catch (ParseError ex) {
252                        return null;
253                    }
254                    return m.match(env.osm);
255                }
256    
257                public String JOSM_pref(String s, String def) {
258                    String res = Main.pref.get(s, null);
259                    return res != null ? res : def;
260                }
261    
262                public Color JOSM_pref_color(String s, Color def) {
263                    Color res = Main.pref.getColor(s, null);
264                    return res != null ? res : def;
265                }
266            }
267    
268            @Override
269            public Object evaluate(Environment env) {
270                if (equal(name, "cond")) { // this needs special handling since only one argument should be evaluated
271                    if (args.size() != 3)
272                        return null;
273                    Boolean b = Cascade.convertTo(args.get(0).evaluate(env), boolean.class);
274                    if (b == null)
275                        return null;
276                    return args.get(b ? 1 : 2).evaluate(env);
277                }
278                if (equal(name, "and")) {
279                    for (Expression arg : args) {
280                        Boolean b = Cascade.convertTo(arg.evaluate(env), boolean.class);
281                        if (b == null || !b)
282                            return false;
283                    }
284                    return true;
285                }
286                if (equal(name, "or")) {
287                    for (Expression arg : args) {
288                        Boolean b = Cascade.convertTo(arg.evaluate(env), boolean.class);
289                        if (b != null && b)
290                            return true;
291                    }
292                    return false;
293                }
294                EvalFunctions fn = new EvalFunctions();
295                fn.env = env;
296                Method[] customMethods = EvalFunctions.class.getDeclaredMethods();
297                List<Method> allMethods = new ArrayList<Method>();
298                allMethods.addAll(Arrays.asList(customMethods));
299                try {
300                    allMethods.add(Math.class.getMethod("abs", float.class));
301                    allMethods.add(Math.class.getMethod("acos", double.class));
302                    allMethods.add(Math.class.getMethod("asin", double.class));
303                    allMethods.add(Math.class.getMethod("atan", double.class));
304                    allMethods.add(Math.class.getMethod("atan2", double.class, double.class));
305                    allMethods.add(Math.class.getMethod("ceil", double.class));
306                    allMethods.add(Math.class.getMethod("cos", double.class));
307                    allMethods.add(Math.class.getMethod("cosh", double.class));
308                    allMethods.add(Math.class.getMethod("exp", double.class));
309                    allMethods.add(Math.class.getMethod("floor", double.class));
310                    allMethods.add(Math.class.getMethod("log", double.class));
311                    allMethods.add(Math.class.getMethod("max", float.class, float.class));
312                    allMethods.add(Math.class.getMethod("min", float.class, float.class));
313                    allMethods.add(Math.class.getMethod("random"));
314                    allMethods.add(Math.class.getMethod("round", float.class));
315                    allMethods.add(Math.class.getMethod("signum", double.class));
316                    allMethods.add(Math.class.getMethod("sin", double.class));
317                    allMethods.add(Math.class.getMethod("sinh", double.class));
318                    allMethods.add(Math.class.getMethod("sqrt", double.class));
319                    allMethods.add(Math.class.getMethod("tan", double.class));
320                    allMethods.add(Math.class.getMethod("tanh", double.class));
321                } catch (NoSuchMethodException ex) {
322                    throw new RuntimeException(ex);
323                } catch (SecurityException ex) {
324                    throw  new RuntimeException(ex);
325                }
326                for (Method m : allMethods) {
327                    if (!m.getName().equals(name)) {
328                        continue;
329                    }
330                    Class<?>[] expectedParameterTypes = m.getParameterTypes();
331                    Object[] convertedArgs = new Object[expectedParameterTypes.length];
332    
333                    if (expectedParameterTypes.length == 1 && expectedParameterTypes[0].isArray())
334                    {
335                        Class<?> arrayComponentType = expectedParameterTypes[0].getComponentType();
336                        Object arrayArg = Array.newInstance(arrayComponentType, args.size());
337                        for (int i=0; i<args.size(); ++i)
338                        {
339                            Object o = Cascade.convertTo(args.get(i).evaluate(env), arrayComponentType);
340                            if (o == null)
341                                return null;
342                            Array.set(arrayArg, i, o);
343                        }
344                        convertedArgs[0] = arrayArg;
345                    } else {
346                        if (args.size() != expectedParameterTypes.length) {
347                            continue;
348                        }
349                        for (int i=0; i<args.size(); ++i) {
350                            convertedArgs[i] = Cascade.convertTo(args.get(i).evaluate(env), expectedParameterTypes[i]);
351                            if (convertedArgs[i] == null)
352                                return null;
353                        }
354                    }
355                    Object result = null;
356                    try {
357                        result = m.invoke(fn, convertedArgs);
358                    } catch (IllegalAccessException ex) {
359                        throw new RuntimeException(ex);
360                    } catch (IllegalArgumentException ex) {
361                        throw new RuntimeException(ex);
362                    } catch (InvocationTargetException ex) {
363                        System.err.println(ex);
364                        return null;
365                    }
366                    return result;
367                }
368                return null;
369            }
370    
371            @Override
372            public String toString() {
373                return name + "(" + Utils.join(", ", args) + ")";
374            }
375    
376        }
377    }