001    // License: GPL. See LICENSE file for details.
002    package org.openstreetmap.josm.data.validation.tests;
003    
004    import static org.openstreetmap.josm.tools.I18n.marktr;
005    import static org.openstreetmap.josm.tools.I18n.tr;
006    
007    import java.text.MessageFormat;
008    import java.util.Collection;
009    import java.util.HashMap;
010    import java.util.LinkedList;
011    
012    import org.openstreetmap.josm.data.osm.Node;
013    import org.openstreetmap.josm.data.osm.OsmPrimitive;
014    import org.openstreetmap.josm.data.osm.Relation;
015    import org.openstreetmap.josm.data.osm.RelationMember;
016    import org.openstreetmap.josm.data.osm.Way;
017    import org.openstreetmap.josm.data.validation.Severity;
018    import org.openstreetmap.josm.data.validation.Test;
019    import org.openstreetmap.josm.data.validation.TestError;
020    import org.openstreetmap.josm.gui.preferences.map.TaggingPresetPreference;
021    import org.openstreetmap.josm.gui.tagging.TaggingPreset;
022    import org.openstreetmap.josm.gui.tagging.TaggingPreset.PresetType;
023    
024    /**
025     * Check for wrong relations
026     *
027     */
028    public class RelationChecker extends Test {
029    
030        protected static final int ROLE_UNKNOWN      = 1701;
031        protected static final int ROLE_EMPTY        = 1702;
032        protected static final int WRONG_TYPE        = 1703;
033        protected static final int HIGH_COUNT        = 1704;
034        protected static final int LOW_COUNT         = 1705;
035        protected static final int ROLE_MISSING      = 1706;
036        protected static final int RELATION_UNKNOWN  = 1707;
037        protected static final int RELATION_EMPTY    = 1708;
038    
039        /**
040         * Constructor
041         */
042        public RelationChecker() {
043            super(tr("Relation checker"),
044                    tr("This plugin checks for errors in relations."));
045        }
046    
047        @Override
048        public void initialize() {
049            initializePresets();
050        }
051    
052        static Collection<TaggingPreset> relationpresets = new LinkedList<TaggingPreset>();
053    
054        /**
055         * Reads the presets data.
056         *
057         */
058        public void initializePresets() {
059            Collection<TaggingPreset> presets = TaggingPresetPreference.taggingPresets;
060            if (presets != null) {
061                for (TaggingPreset p : presets) {
062                    for (TaggingPreset.Item i : p.data) {
063                        if (i instanceof TaggingPreset.Roles) {
064                            relationpresets.add(p);
065                            break;
066                        }
067                    }
068                }
069            }
070        }
071    
072        public static class RoleInfo {
073            int total = 0;
074            Collection<Node> nodes = new LinkedList<Node>();
075            Collection<Way> ways = new LinkedList<Way>();
076            Collection<Way> closedways = new LinkedList<Way>();
077            Collection<Way> openways = new LinkedList<Way>();
078            Collection<Relation> relations = new LinkedList<Relation>();
079        }
080    
081        @Override
082        public void visit(Relation n) {
083            LinkedList<TaggingPreset.Role> allroles = new LinkedList<TaggingPreset.Role>();
084            for (TaggingPreset p : relationpresets) {
085                boolean matches = true;
086                TaggingPreset.Roles r = null;
087                for (TaggingPreset.Item i : p.data) {
088                    if (i instanceof TaggingPreset.Key) {
089                        TaggingPreset.Key k = (TaggingPreset.Key) i;
090                        if (!k.value.equals(n.get(k.key))) {
091                            matches = false;
092                            break;
093                        }
094                    } else if (i instanceof TaggingPreset.Roles) {
095                        r = (TaggingPreset.Roles) i;
096                    }
097                }
098                if (matches && r != null) {
099                    allroles.addAll(r.roles);
100                }
101            }
102            if (allroles.size() == 0) {
103                errors.add( new TestError(this, Severity.WARNING, tr("Relation type is unknown"),
104                        RELATION_UNKNOWN, n) );
105            } else {
106                HashMap<String,RoleInfo> map = new HashMap<String, RoleInfo>();
107                for (RelationMember m : n.getMembers()) {
108                    String s = "";
109                    if (m.hasRole()) {
110                        s = m.getRole();
111                    }
112                    RoleInfo ri = map.get(s);
113                    if (ri == null) {
114                        ri = new RoleInfo();
115                    }
116                    ri.total++;
117                    if (m.isRelation()) {
118                        ri.relations.add(m.getRelation());
119                    } else if(m.isWay()) {
120                        ri.ways.add(m.getWay());
121                        if (m.getWay().isClosed()) {
122                            ri.closedways.add(m.getWay());
123                        } else {
124                            ri.openways.add(m.getWay());
125                        }
126                    }
127                    else if (m.isNode()) {
128                        ri.nodes.add(m.getNode());
129                    }
130                    map.put(s, ri);
131                }
132                if(map.isEmpty()) {
133                    errors.add( new TestError(this, Severity.ERROR, tr("Relation is empty"),
134                            RELATION_EMPTY, n) );
135                } else {
136                    LinkedList<String> done = new LinkedList<String>();
137                    for (TaggingPreset.Role r : allroles) {
138                        done.add(r.key);
139                        String keyname = r.key;
140                        if ("".equals(keyname)) {
141                            keyname = tr("<empty>");
142                        }
143                        RoleInfo ri = map.get(r.key);
144                        long count = (ri == null) ? 0 : ri.total;
145                        long vc = r.getValidCount(count);
146                        if (count != vc) {
147                            if (count == 0) {
148                                String s = marktr("Role {0} missing");
149                                errors.add(new TestError(this, Severity.WARNING, tr("Role verification problem"),
150                                        tr(s, keyname), MessageFormat.format(s, keyname), ROLE_MISSING, n));
151                            }
152                            else if (vc > count) {
153                                String s = marktr("Number of {0} roles too low ({1})");
154                                errors.add(new TestError(this, Severity.WARNING, tr("Role verification problem"),
155                                        tr(s, keyname, count), MessageFormat.format(s, keyname, count), LOW_COUNT, n));
156                            } else {
157                                String s = marktr("Number of {0} roles too high ({1})");
158                                errors.add(new TestError(this, Severity.WARNING, tr("Role verification problem"),
159                                        tr(s, keyname, count), MessageFormat.format(s, keyname, count), HIGH_COUNT, n));
160                            }
161                        }
162                        if (ri != null) {
163                            Collection<OsmPrimitive> wrongTypes = new LinkedList<OsmPrimitive>();
164                            if (!r.types.contains(PresetType.WAY)) {
165                                wrongTypes.addAll(r.types.contains(PresetType.CLOSEDWAY) ? ri.openways : ri.ways);
166                            }
167                            if (!r.types.contains(PresetType.NODE)) {
168                                wrongTypes.addAll(ri.nodes);
169                            }
170                            if (!r.types.contains(PresetType.RELATION)) {
171                                wrongTypes.addAll(ri.relations);
172                            }
173                            if (!wrongTypes.isEmpty()) {
174                                String s = marktr("Member for role {0} of wrong type");
175                                LinkedList<OsmPrimitive> highlight = new LinkedList<OsmPrimitive>(wrongTypes);
176                                highlight.addFirst(n);
177                                errors.add(new TestError(this, Severity.WARNING, tr("Role verification problem"),
178                                        tr(s, keyname), MessageFormat.format(s, keyname), WRONG_TYPE,
179                                        highlight, wrongTypes));
180                            }
181                        }
182                    }
183                    for (String key : map.keySet()) {
184                        if (!done.contains(key)) {
185                            if (key.length() > 0) {
186                                String s = marktr("Role {0} unknown");
187                                errors.add(new TestError(this, Severity.WARNING, tr("Role verification problem"),
188                                        tr(s, key), MessageFormat.format(s, key), ROLE_UNKNOWN, n));
189                            } else {
190                                String s = marktr("Empty role found");
191                                errors.add(new TestError(this, Severity.WARNING, tr("Role verification problem"),
192                                        tr(s), s, ROLE_EMPTY, n));
193                            }
194                        }
195                    }
196                }
197            }
198        }
199    }