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