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}