001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.gui.mappaint.mapcss;
003
004import java.util.Arrays;
005
006import org.openstreetmap.josm.gui.mappaint.Cascade;
007import org.openstreetmap.josm.gui.mappaint.Environment;
008import org.openstreetmap.josm.gui.mappaint.Keyword;
009import org.openstreetmap.josm.gui.mappaint.MapPaintStyles;
010import org.openstreetmap.josm.gui.mappaint.MapPaintStyles.IconReference;
011import org.openstreetmap.josm.gui.mappaint.StyleKeys;
012import org.openstreetmap.josm.tools.Logging;
013
014/**
015 * A MapCSS Instruction.
016 *
017 * For example a simple assignment like <code>width: 3;</code>, but may also
018 * be a set instruction (<code>set highway;</code>).
019 * A MapCSS {@link Declaration} is a list of instructions.
020 */
021@FunctionalInterface
022public interface Instruction extends StyleKeys {
023
024    /**
025     * Execute the instruction in the given environment.
026     * @param env the environment
027     */
028    void execute(Environment env);
029
030    /**
031     * A float value that will be added to the current float value. Specified as +5 or -3 in MapCSS
032     */
033    class RelativeFloat {
034        public final float val;
035
036        public RelativeFloat(float val) {
037            this.val = val;
038        }
039
040        @Override
041        public String toString() {
042            return "RelativeFloat{" + "val=" + val + '}';
043        }
044    }
045
046    /**
047     * An instruction that assigns a given value to a variable on evaluation
048     */
049    class AssignmentInstruction implements Instruction {
050        public final String key;
051        public final Object val;
052        public final boolean isSetInstruction;
053
054        public AssignmentInstruction(String key, Object val, boolean isSetInstruction) {
055            this.key = key.intern();
056            this.isSetInstruction = isSetInstruction;
057            if (val instanceof LiteralExpression) {
058                Object litValue = ((LiteralExpression) val).evaluate(null);
059                if (litValue instanceof Keyword && "none".equals(((Keyword) litValue).val)) {
060                    this.val = null;
061                } else if (TEXT.equals(key)) {
062                    /* Special case for declaration 'text: ...'
063                     *
064                     * - Treat the value 'auto' as keyword.
065                     * - Treat any other literal value 'litval' as as reference to tag with key 'litval'
066                     *
067                     * - Accept function expressions as is. This allows for
068                     *     tag(a_tag_name)                 value of a tag
069                     *     eval("a static text")           a static text
070                     *     parent_tag(a_tag_name)          value of a tag of a parent relation
071                     */
072                    if (litValue.equals(Keyword.AUTO)) {
073                        this.val = Keyword.AUTO;
074                    } else {
075                        String s = Cascade.convertTo(litValue, String.class);
076                        if (s != null) {
077                            this.val = new MapPaintStyles.TagKeyReference(s);
078                        } else {
079                            this.val = litValue;
080                        }
081                    }
082                } else {
083                    this.val = litValue;
084                }
085            } else {
086                this.val = val;
087            }
088        }
089
090        @Override
091        public void execute(Environment env) {
092            Object value;
093            if (val instanceof Expression) {
094                try {
095                    value = ((Expression) val).evaluate(env);
096                } catch (RuntimeException ex) {
097                    Logging.error(ex);
098                    value = null;
099                }
100            } else {
101                value = val;
102            }
103            if (ICON_IMAGE.equals(key) || FILL_IMAGE.equals(key) || REPEAT_IMAGE.equals(key)) {
104                if (value instanceof String) {
105                    value = new IconReference((String) value, env.source);
106                }
107            }
108            env.mc.getOrCreateCascade(env.layer).putOrClear(key, value);
109        }
110
111        @Override
112        public String toString() {
113            return key + ": " + (val instanceof float[] ? Arrays.toString((float[]) val) :
114                (val instanceof String ? ("String<"+val+'>') : val)) + ';';
115        }
116    }
117}