// License: GPL. For details, see LICENSE file. package org.openstreetmap.josm.plugins.turnrestrictions.qa; import static org.openstreetmap.josm.plugins.turnrestrictions.TurnRestrictionBuilder.isInnerNode; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Observable; import java.util.Observer; import java.util.Set; import org.openstreetmap.josm.data.osm.Node; import org.openstreetmap.josm.data.osm.OsmPrimitive; import org.openstreetmap.josm.data.osm.Way; import org.openstreetmap.josm.gui.tagging.TagEditorModel; import org.openstreetmap.josm.gui.tagging.TagModel; import org.openstreetmap.josm.plugins.turnrestrictions.TurnRestrictionBuilder; import org.openstreetmap.josm.plugins.turnrestrictions.editor.ExceptValueModel; import org.openstreetmap.josm.plugins.turnrestrictions.editor.NavigationControler; import org.openstreetmap.josm.plugins.turnrestrictions.editor.TurnRestrictionEditorModel; import org.openstreetmap.josm.plugins.turnrestrictions.editor.TurnRestrictionLegRole; import org.openstreetmap.josm.plugins.turnrestrictions.editor.TurnRestrictionType; import org.openstreetmap.josm.tools.CheckParameterUtil; /** * <p>IssuesModel is a model for an observable list of {@code Issues} * related to turn restriction.</p> * * <p>It is also an {@link Observer} to an {@link TurnRestrictionEditorModel} * and populates itself with issues it derives from the current state * in the {@link TurnRestrictionEditorModel}.</p> * */ public class IssuesModel extends Observable implements Observer { private final ArrayList<Issue> issues = new ArrayList<>(); private TurnRestrictionEditorModel editorModel; /** * Creates the model * * @param editorModel the editor model. Must not be null. * @throws IllegalArgumentException thrown if controler is null */ public IssuesModel(TurnRestrictionEditorModel editorModel) throws IllegalArgumentException { CheckParameterUtil.ensureParameterNotNull(editorModel, "editorModel"); this.editorModel = editorModel; this.editorModel.addObserver(this); } /** * Populates the model with a list of issues. Just clears the model * if {@code issues} is null or empty. * * @param issues the list of issues. */ public void populate(List<Issue> issues) { this.issues.clear(); if (issues != null) { this.issues.addAll(issues); } setChanged(); notifyObservers(); } /** * Replies the (unmodifiable) list of issues in this model. * * @return the (unmodifiable) list of issues in this model. */ public List<Issue> getIssues() { return Collections.unmodifiableList(issues); } /** * Replies the turn restriction editor model */ public TurnRestrictionEditorModel getEditorModel() { return editorModel; } /** * Populates this model with issues derived from the state of the * turn restriction editor model. If {@code editorModel} is null, the * list of issues is cleared. * * @param editorModel the editor model. */ public void populate() { issues.clear(); if (editorModel != null) { checkTags(editorModel); checkFromLeg(editorModel); checkToLeg(editorModel); checkFromAndToEquals(editorModel); checkVias(editorModel); } setChanged(); notifyObservers(); } /** * Checks whether there are required tags missing. */ protected void checkTags(TurnRestrictionEditorModel editorModel) { TagEditorModel tagEditorModel = editorModel.getTagEditorModel(); TagModel tag = tagEditorModel.get("type"); // missing marker tag for a turn restriction if (tag == null || !tag.getValue().trim().equals("restriction")) { issues.add(new RequiredTagMissingError(this, "type", "restriction")); } // missing or illegal restriction type ? tag = tagEditorModel.get("restriction"); if (tag == null) { issues.add(new MissingRestrictionTypeError(this)); } else if (!TurnRestrictionType.isStandardTagValue(tag.getValue())) { issues.add(new IllegalRestrictionTypeError(this, tag.getValue())); } // non-standard value for the 'except' tag? ExceptValueModel except = getEditorModel().getExcept(); if (!except.isStandard()) { issues.add(new NonStandardExceptWarning(this, except)); } } /** * Checks various data integrity restriction for the relation member with * role 'from'. * */ protected void checkFromLeg(TurnRestrictionEditorModel editorModel) { Set<OsmPrimitive> froms = editorModel.getTurnRestrictionLeg(TurnRestrictionLegRole.FROM); if (froms.isEmpty()) { issues.add(new MissingTurnRestrictionLegError(this, TurnRestrictionLegRole.FROM)); return; } else if (froms.size() > 1) { issues.add(new MultipleTurnRestrictionLegError(this, TurnRestrictionLegRole.FROM, froms.size())); return; } OsmPrimitive p = froms.iterator().next(); if (!(p instanceof Way)) { issues.add(new WrongTurnRestrictionLegTypeError(this, TurnRestrictionLegRole.FROM, p)); } } /** * Checks various data integrity restriction for the relation member with * role 'to'. * */ protected void checkToLeg(TurnRestrictionEditorModel editorModel) { Set<OsmPrimitive> toLegs = editorModel.getTurnRestrictionLeg(TurnRestrictionLegRole.TO); if (toLegs.isEmpty()) { issues.add(new MissingTurnRestrictionLegError(this, TurnRestrictionLegRole.TO)); return; } else if (toLegs.size() > 1) { issues.add(new MultipleTurnRestrictionLegError(this, TurnRestrictionLegRole.TO, toLegs.size())); return; } OsmPrimitive p = toLegs.iterator().next(); if (!(p instanceof Way)) { issues.add(new WrongTurnRestrictionLegTypeError(this, TurnRestrictionLegRole.TO, p)); } } /** * Creates an issue if this turn restriction has identical 'from' and to'. */ protected void checkFromAndToEquals(TurnRestrictionEditorModel editorModel) { Set<OsmPrimitive> toLegs = editorModel.getTurnRestrictionLeg(TurnRestrictionLegRole.TO); Set<OsmPrimitive> fromLegs = editorModel.getTurnRestrictionLeg(TurnRestrictionLegRole.FROM); if (toLegs.size() != 1 || fromLegs.size() != 1) return; OsmPrimitive from = fromLegs.iterator().next(); OsmPrimitive to = toLegs.iterator().next(); if (!(from instanceof Way)) return; if (!(to instanceof Way)) return; if (from.equals(to) && !"no_u_turn".equals(editorModel.getRestrictionTagValue())) { // identical from and to allowed for "no_u_turn" only // issues.add(new IdenticalTurnRestrictionLegsError(this, from)); } } /** * Checks the 'via' members in the turn restriction * * @param editorModel the editor model */ protected void checkVias(TurnRestrictionEditorModel editorModel) { Set<OsmPrimitive> toLegs = editorModel.getTurnRestrictionLeg(TurnRestrictionLegRole.TO); Set<OsmPrimitive> fromLegs = editorModel.getTurnRestrictionLeg(TurnRestrictionLegRole.FROM); // we only check vias if 'to' and 'from' are already OK if (toLegs.size() != 1 || fromLegs.size() != 1) return; if (!(toLegs.iterator().next() instanceof Way)) return; if (!(fromLegs.iterator().next() instanceof Way)) return; Way from = (Way) fromLegs.iterator().next(); Way to = (Way) toLegs.iterator().next(); Node intersect = TurnRestrictionBuilder.getUniqueCommonNode(from, to); if (intersect != null) { if (!editorModel.getVias().contains(intersect)) { issues.add(new IntersectionMissingAsViaError(this, from, to, intersect)); } if (isInnerNode(from, intersect) && isInnerNode(to, intersect)) { issues.add(new TurnRestrictionLegSplitRequiredError(this, from, to)); } else if (isInnerNode(from, intersect) && !isInnerNode(to, intersect)) { issues.add(new TurnRestrictionLegSplitRequiredError(this, TurnRestrictionLegRole.FROM, from, to, intersect)); } else if (!isInnerNode(from, intersect) && isInnerNode(to, intersect)) { issues.add(new TurnRestrictionLegSplitRequiredError(this, TurnRestrictionLegRole.TO, from, to, intersect)); } } else { if (editorModel.getVias().isEmpty() && !from.equals(to)) { // the two turn restriction legs aren't connected and we don't have configured // via objects issues.add(new MissingViaError(this)); } } } public NavigationControler getNavigationControler() { return editorModel.getNavigationControler(); } public int getNumWarnings() { int ret = 0; for (Issue issue: issues) { if (issue.getSeverity().equals(Severity.WARNING)) ret++; } return ret; } public int getNumErrors() { int ret = 0; for (Issue issue: issues) { if (issue.getSeverity().equals(Severity.ERROR)) ret++; } return ret; } /* ------------------------------------------------------------------------------------- */ /* interface Observer */ /* ------------------------------------------------------------------------------------- */ @Override public void update(Observable o, Object arg) { populate(); } }