001    // License: GPL. For details, see LICENSE file.
002    package org.openstreetmap.josm.gui.mappaint.mapcss;
003    
004    import static org.openstreetmap.josm.tools.I18n.tr;
005    
006    import java.awt.Color;
007    import java.io.ByteArrayInputStream;
008    import java.io.IOException;
009    import java.io.InputStream;
010    import java.util.ArrayList;
011    import java.util.List;
012    import java.util.Map.Entry;
013    
014    import org.openstreetmap.josm.data.osm.Node;
015    import org.openstreetmap.josm.data.osm.OsmPrimitive;
016    import org.openstreetmap.josm.gui.mappaint.Cascade;
017    import org.openstreetmap.josm.gui.mappaint.Environment;
018    import org.openstreetmap.josm.gui.mappaint.MultiCascade;
019    import org.openstreetmap.josm.gui.mappaint.Range;
020    import org.openstreetmap.josm.gui.mappaint.StyleSource;
021    import org.openstreetmap.josm.gui.mappaint.mapcss.Selector.GeneralSelector;
022    import org.openstreetmap.josm.gui.mappaint.mapcss.parsergen.MapCSSParser;
023    import org.openstreetmap.josm.gui.mappaint.mapcss.parsergen.ParseException;
024    import org.openstreetmap.josm.gui.mappaint.mapcss.parsergen.TokenMgrError;
025    import org.openstreetmap.josm.gui.preferences.SourceEntry;
026    import org.openstreetmap.josm.io.MirroredInputStream;
027    import org.openstreetmap.josm.tools.CheckParameterUtil;
028    import org.openstreetmap.josm.tools.LanguageInfo;
029    import org.openstreetmap.josm.tools.Utils;
030    
031    public class MapCSSStyleSource extends StyleSource {
032        final public List<MapCSSRule> rules;
033        private Color backgroundColorOverride;
034        private String css = null;
035    
036        public MapCSSStyleSource(String url, String name, String shortdescription) {
037            super(url, name, shortdescription);
038            rules = new ArrayList<MapCSSRule>();
039        }
040    
041        public MapCSSStyleSource(SourceEntry entry) {
042            super(entry);
043            rules = new ArrayList<MapCSSRule>();
044        }
045    
046        /**
047         * <p>Creates a new style source from the MapCSS styles supplied in
048         * {@code css}</p>
049         * 
050         * @param css the MapCSS style declaration. Must not be null.
051         * @throws IllegalArgumentException thrown if {@code css} is null
052         */
053        public MapCSSStyleSource(String css) throws IllegalArgumentException{
054            super(null, null, null);
055            CheckParameterUtil.ensureParameterNotNull(css);
056            this.css = css;
057            rules = new ArrayList<MapCSSRule>();
058        }
059    
060        @Override
061        public void loadStyleSource() {
062            init();
063            rules.clear();
064            try {
065                MapCSSParser parser = new MapCSSParser(getSourceInputStream(), "UTF-8");
066                parser.sheet(this);
067                loadMeta();
068                loadCanvas();
069            } catch(IOException e) {
070                System.err.println(tr("Warning: failed to load Mappaint styles from ''{0}''. Exception was: {1}", url, e.toString()));
071                e.printStackTrace();
072                logError(e);
073            } catch (TokenMgrError e) {
074                System.err.println(tr("Warning: failed to parse Mappaint styles from ''{0}''. Error was: {1}", url, e.getMessage()));
075                e.printStackTrace();
076                logError(e);
077            } catch (ParseException e) {
078                System.err.println(tr("Warning: failed to parse Mappaint styles from ''{0}''. Error was: {1}", url, e.getMessage()));
079                e.printStackTrace();
080                logError(new ParseException(e.getMessage())); // allow e to be garbage collected, it links to the entire token stream
081            }
082        }
083    
084        @Override
085        public InputStream getSourceInputStream() throws IOException {
086            if (css != null)
087                return new ByteArrayInputStream(css.getBytes("UTF-8"));
088    
089            MirroredInputStream in = new MirroredInputStream(url);
090            InputStream zip = in.getZipEntry("mapcss", "style");
091            if (zip != null) {
092                zipIcons = in.getFile();
093                return zip;
094            } else {
095                zipIcons = null;
096                return in;
097            }
098        }
099    
100        /**
101         * load meta info from a selector "meta"
102         */
103        private void loadMeta() {
104            Cascade c = constructSpecial("meta");
105            String pTitle = c.get("title", null, String.class);
106            if (title == null) {
107                title = pTitle;
108            }
109            String pIcon = c.get("icon", null, String.class);
110            if (icon == null) {
111                icon = pIcon;
112            }
113        }
114    
115        private void loadCanvas() {
116            Cascade c = constructSpecial("canvas");
117            backgroundColorOverride = c.get("background-color", null, Color.class);
118        }
119    
120        private Cascade constructSpecial(String type) {
121    
122            MultiCascade mc = new MultiCascade();
123            Node n = new Node();
124            String code = LanguageInfo.getJOSMLocaleCode();
125            n.put("lang", code);
126            // create a fake environment to read the meta data block
127            Environment env = new Environment(n, mc, "default", this);
128    
129            NEXT_RULE:
130            for (MapCSSRule r : rules) {
131                for (Selector s : r.selectors) {
132                    if ((s instanceof GeneralSelector)) {
133                        GeneralSelector gs = (GeneralSelector) s;
134                        if (gs.getBase().equals(type)) {
135                            if (!gs.matchesConditions(env)) {
136                                continue NEXT_RULE;
137                            }
138                            r.execute(env);
139                        }
140                    }
141                }
142            }
143            return mc.getCascade("default");
144        }
145    
146        @Override
147        public Color getBackgroundColorOverride() {
148            return backgroundColorOverride;
149        }
150    
151        @Override
152        public void apply(MultiCascade mc, OsmPrimitive osm, double scale, OsmPrimitive multipolyOuterWay, boolean pretendWayIsClosed) {
153            Environment env = new Environment(osm, mc, null, this);
154            for (MapCSSRule r : rules) {
155                for (Selector s : r.selectors) {
156                    env.clearSelectorMatchingInformation();
157                    if (s.matches(env)) { // as side effect env.parent will be set (if s is a child selector)
158                        if (s.getRange().contains(scale)) {
159                            mc.range = Range.cut(mc.range, s.getRange());
160                        } else {
161                            mc.range = mc.range.reduceAround(scale, s.getRange());
162                            continue;
163                        }
164    
165                        String sub = s.getSubpart();
166                        if (sub == null) {
167                            sub = "default";
168                        }
169    
170                        if (sub.equals("*")) {
171                            for (Entry<String, Cascade> entry : mc.getLayers()) {
172                                env.layer = entry.getKey();
173                                if (Utils.equal(env.layer, "*")) {
174                                    continue;
175                                }
176                                r.execute(env);
177                            }
178                        }
179                        env.layer = sub;
180                        r.execute(env);
181                    }
182                }
183            }
184        }
185    
186        @Override
187        public String toString() {
188            return Utils.join("\n", rules);
189        }
190    }