// 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();
}
}