/*******************************************************************************
* See the NOTICE file distributed with this work for additional information
* regarding copyright ownership.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
******************************************************************************/
package hr.fer.zemris.vhdllab.applets.editor.schema2.model.queries;
import hr.fer.zemris.vhdllab.applets.editor.schema2.constants.Constants;
import hr.fer.zemris.vhdllab.applets.editor.schema2.enums.EPropertyChange;
import hr.fer.zemris.vhdllab.applets.editor.schema2.interfaces.IQuery;
import hr.fer.zemris.vhdllab.applets.editor.schema2.interfaces.IQueryResult;
import hr.fer.zemris.vhdllab.applets.editor.schema2.interfaces.ISchemaInfo;
import hr.fer.zemris.vhdllab.applets.editor.schema2.interfaces.ISchemaWire;
import hr.fer.zemris.vhdllab.applets.editor.schema2.misc.CostSortedHash;
import hr.fer.zemris.vhdllab.applets.editor.schema2.misc.WireSegment;
import hr.fer.zemris.vhdllab.applets.editor.schema2.misc.XYLocation;
import hr.fer.zemris.vhdllab.applets.editor.schema2.model.QueryResult;
import hr.fer.zemris.vhdllab.applets.editor.schema2.model.queries.misc.WalkabilityMap;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
/**
* Nalazi najkraci put sa sto manje skretanja izmedu predane pocetne i zavrsne
* lokacije. Pritom je put pohranjen u obliku liste segmenata u QueryResult-u
* pod kljucem KEY_SEGMENTS, koji je tipa List<WireSegments>.
*
* U slucaju da put nije naden, pod navedenim kljucem nece biti pohranjeno
* nista, odnosno vratit ce se null, a pripadni IQueryResult nece biti uspjesan.
*
* @author Axel
*
*/
public class SmartConnect implements IQuery {
private static class ANode {
public XYLocation parentloc;
public ANode parent;
public int costsofar, estimate;
public ANode() {
}
public ANode(ANode parentnode, XYLocation parentlocation, int tohere, int totarget) {
parentloc = parentlocation;
parent = parentnode;
costsofar = tohere;
estimate = totarget;
}
@Override
public String toString() {
if (parent != null) return parentloc.toString() + parent.toString();
return "";
}
}
/* static fields */
public static final String QUERY_NAME = SmartConnect.class.getSimpleName();
public static final String KEY_SEGMENTS = "segments";
private static final int STEP = Constants.GRID_SIZE;
private static final int NORMAL_COST = STEP;
private static final int DETOUR_COST = STEP * 2;
private static final int SEARCH_LIMIT = 1000;
private static final Integer ALL_UNWALKABLE = 0;
private static final List<EPropertyChange> propdepend = new ArrayList<EPropertyChange>();
private static final List<EPropertyChange> ro_pd = Collections.unmodifiableList(propdepend);
{
propdepend.add(EPropertyChange.CANVAS_CHANGE);
propdepend.add(EPropertyChange.PROPERTY_CHANGE);
}
/* private fields */
private XYLocation begin, end;
private WalkabilityMap walkability;
/* ctors */
/**
* Konstruktor kojim se specificira pocetna i zavrsna tocka puta.
*/
public SmartConnect(XYLocation startLocation, XYLocation endLocation) {
begin = startLocation;
end = endLocation;
if (begin == null || end == null)
throw new IllegalArgumentException("Start and end location cannot be null.");
walkability = null;
}
/**
* Postavlja pocetnu i zavrsnu lokaciju, te walkability map.
* Ako se SmartConnect query stvori pomocu ovog ctora, algoritam
* trazenja puta ce raditi puno brze.
* @param startLocation
* @param endLocation
* @param walkabilitymap
* Klasa koja predstavlja prolaznost na mapi, a dobiva se pomocu
* InspectWalkability query-a. Bitno je da WalkabilityMap bude vazeci
* (tj. da je query kojim je dobiven obavljen neposredno prije ovog).
*/
public SmartConnect(XYLocation startLocation, XYLocation endLocation, WalkabilityMap walkabilitymap) {
begin = startLocation;
end = endLocation;
if (begin == null || end == null)
throw new IllegalArgumentException("Start and end location cannot be null.");
walkability = walkabilitymap;
if (walkability == null)
throw new IllegalArgumentException("Walkability map cannot be null.");
}
/* methods */
public List<EPropertyChange> getPropertyDependency() {
return ro_pd;
}
public String getQueryName() {
return QUERY_NAME;
}
public boolean isCacheable() {
return true;
}
@Override
public boolean equals(Object obj) {
if (obj == null)
return false;
if (!(obj instanceof SmartConnect))
return false;
SmartConnect other = (SmartConnect) obj;
return other.begin.equals(this.begin) && other.end.equals(this.end);
}
@Override
public int hashCode() {
return begin.hashCode() << 16 + end.hashCode();
}
public IQueryResult performQuery(ISchemaInfo info) {
List<WireSegment> segs = findPath(begin, end, info);
return (segs == null) ? (new QueryResult(false)) : (new QueryResult(KEY_SEGMENTS, segs));
}
/* path finding */
private List<WireSegment> findPath(XYLocation start, XYLocation target, ISchemaInfo info) {
/* prepare path start so it's divisible with STEP */
XYLocation actualstart = new XYLocation();
if (!prepareStartAndActualStart(start, target, actualstart, info)) return null;
/* A* */
CostSortedHash<XYLocation, ANode> openlist = new CostSortedHash<XYLocation, ANode>();
CostSortedHash<XYLocation, ANode> closedlist = new CostSortedHash<XYLocation, ANode>();
ANode goal = null, helper = new ANode();
XYLocation finder = new XYLocation();
int counter = 0;
/* put start on openlist */
ANode startnode = new ANode(null, null, 0, heuristic(start.x, start.y));
openlist.add(start, startnode.costsofar + startnode.estimate, startnode);
/* while there are reachable nodes */
if (isWalkable(info, start, null)) while (!openlist.isEmpty()) {
/* check search limit */
if (counter++ > SEARCH_LIMIT) break;
/* get cheapest node */
XYLocation currxy = openlist.cheapest();
ANode currnode = openlist.get(currxy);
//System.out.println("Iteration " + counter + ": " + currxy);
/* check if goal has been reached */
if (currxy.chebyshev(target.x, target.y) < STEP) {
goal = currnode;
finder = currxy;
break;
}
/* goal not reached - expand current node */
for (int i = 0, j = 1, t = 2; !(i == 0 && j == 1 && t != 2); t = i, i = j, j = -t) {
// System.out.println(i + ", " + j + "; t = " + t);
finder.x = currxy.x + i * STEP;
finder.y = currxy.y + j * STEP;
ANode neighbour = null;
if (!isWalkable(info, finder, currxy)) continue;
else if (closedlist.contains(finder)) continue;
else if (openlist.contains(finder)) {
/* neighbour already on openlist - update cost if necessary */
helper.parent = currnode;
helper.parentloc = currxy;
appendCost(helper, finder);
neighbour = openlist.get(finder);
int oldcost = neighbour.costsofar;
int newcost = helper.costsofar;
if (newcost < oldcost) {
/* lower cost found - update cost */
neighbour.costsofar = newcost;
neighbour.parent = currnode;
neighbour.parentloc = currxy;
openlist.updateCost(new XYLocation(finder), neighbour.costsofar + neighbour.estimate);
}
} else {
/* neighbour is completely new - add new neighbour to open list */
neighbour = new ANode(currnode, currxy, 0, heuristic(finder.x, finder.y));
appendCost(neighbour, finder);
openlist.add(new XYLocation(finder), neighbour.costsofar + neighbour.estimate, neighbour);
}
}
/* remove currentnode from open list and put it on closedlist */
openlist.remove(currxy);
closedlist.add(currxy, currnode.costsofar + currnode.estimate, currnode);
}
/* if goal is null, a path has not been found */
if (goal == null) return null;
/* otherwise, path must be reconstructed */
List<WireSegment> path = reconstructPath(goal, finder);
/* finish cutoffs */
WireSegment firstseg = null, lastseg = null;
int sz = path.size();
if (sz > 0) {
firstseg = path.get(0);
lastseg = path.get(sz - 1);
}
/* finish path start cutoff - lastseg is actually the start */
if (!start.equals(actualstart)) {
if (lastseg != null) {
/* find the last (the one nearest to start) segment orientation */
if (lastseg.isVertical()) {
path.add(new WireSegment(start.x, start.y, start.x, actualstart.y));
path.add(new WireSegment(start.x, actualstart.y, actualstart.x, actualstart.y));
} else {
path.add(new WireSegment(start.x, start.y, actualstart.x, start.y));
path.add(new WireSegment(actualstart.x, actualstart.y, actualstart.x, actualstart.y));
}
} else {
/* create any kind of segment */
path.add(new WireSegment(start.x, start.y, start.x, actualstart.y));
path.add(new WireSegment(start.x, actualstart.y, actualstart.x, actualstart.y));
}
}
/* finish path end cutoff - firstseg is actually the end */
if (!finder.equals(target)) {
if (firstseg != null) {
/* find the first (the one nearest to the target) segment orientation */
if (firstseg.isVertical()) {
path.add(new WireSegment(finder.x, finder.y, finder.x, target.y));
path.add(new WireSegment(finder.x, target.y, target.x, target.y));
} else {
path.add(new WireSegment(finder.x, finder.y, target.x, finder.y));
path.add(new WireSegment(target.x, finder.y, target.x, target.y));
}
} else {
/* create any kind of a segment */
path.add(new WireSegment(finder.x, finder.y, finder.x, target.y));
path.add(new WireSegment(finder.x, target.y, target.x, target.y));
}
}
return path;
}
private boolean prepareStartAndActualStart(XYLocation start, XYLocation target,
XYLocation actualstart, ISchemaInfo info)
{
int xstart = start.x - start.x % STEP;
int ystart = start.y - start.y % STEP;
XYLocation closest = new XYLocation();
if (xstart != start.x || ystart != start.y) {
actualstart.x = start.x;
actualstart.y = start.y;
/* find walkable slot closest to target */
int dist = Math.abs(target.x - xstart) + Math.abs(target.y - ystart), mindist = Integer.MAX_VALUE;
start.x = xstart; start.y = ystart;
if (isWalkable(info, start, null)) {
mindist = dist; closest.x = xstart; closest.y = ystart;
}
xstart = xstart + STEP;
dist = Math.abs(target.x - xstart) + Math.abs(target.y - ystart);
start.x = xstart;
if (dist < mindist && isWalkable(info, start, null)) {
mindist = dist; closest.x = xstart; closest.y = ystart;
}
ystart = ystart + STEP;
dist = Math.abs(target.x - xstart) + Math.abs(target.y - ystart);
start.y = ystart;
if (dist < mindist && isWalkable(info, start, null)) {
mindist = dist; closest.x = xstart; closest.y = ystart;
}
xstart = xstart - STEP;
dist = Math.abs(target.x - xstart) + Math.abs(target.y - ystart);
start.x = xstart;
if (dist < mindist && isWalkable(info, start, null)) {
mindist = dist; closest.x = xstart; closest.y = ystart;
}
/* if there is no adjacent free slot, no path can be found */
if (mindist == Integer.MAX_VALUE) return false;
/* otherwise, start from closest */
start.x = closest.x;
start.y = closest.y;
return true;
}
actualstart.x = start.x;
actualstart.y = start.y;
return true;
}
private List<WireSegment> reconstructPath(ANode goal, XYLocation modend) {
List<WireSegment> segs = new ArrayList<WireSegment>();
int x, y, xstart = modend.x, ystart = modend.y;
boolean vertical = false;
/* prepare */
// System.out.println(goal.toString());
if (goal.parent == null) return segs;
x = goal.parentloc.x;
y = goal.parentloc.y;
if (x == xstart) vertical = true;
goal = goal.parent;
if (goal.parent == null) {
segs.add(new WireSegment(xstart, ystart, x, y));
return segs;
}
/* iterate */
do {
if (vertical) {
if (xstart != goal.parentloc.x) {
vertical = false;
segs.add(new WireSegment(xstart, ystart, x, y));
xstart = x;
ystart = y;
x = goal.parentloc.x;
y = goal.parentloc.y;
} else {
y = goal.parentloc.y;
}
} else {
if (ystart != goal.parentloc.y) {
vertical = true;
segs.add(new WireSegment(xstart, ystart, x, y));
xstart = x;
ystart = y;
x = goal.parentloc.x;
y = goal.parentloc.y;
} else {
x = goal.parentloc.x;
}
}
goal = goal.parent;
} while (goal.parent != null);
/* finish */
segs.add(new WireSegment(xstart, ystart, x, y));
return segs;
}
/**
* Calculates and appends the cost to reach this node. Assumes the parent of
* the node has been set, and that his cost to reach has already been set.
*
* @param node
*/
private static void appendCost(ANode node, XYLocation location) {
if (node.parent.parent == null) {
node.costsofar = NORMAL_COST;
} else {
if (node.parent.parentloc.x == location.x || node.parent.parentloc.y == location.y) {
/* both nodes on same line */
node.costsofar = node.parent.costsofar + NORMAL_COST;
}
else {
/* detour between */
node.costsofar = node.parent.costsofar + DETOUR_COST;
}
}
}
/**
* Walkable slots are not:
* - slots within the component bounding rectangle
* - slots that are literally ON a wire, and form the same orientation as the wire
* with their parent slot
* @param info
* @param loc
* @return
*/
private boolean isWalkable(ISchemaInfo info, XYLocation loc, XYLocation parentloc) {
if (walkability != null) {
/* check with walkability map - optimized */
int direction = 0;
if (parentloc != null) {
/* parent location exists */
switch (parentloc.x - loc.x + 10 * (parentloc.y - loc.y)) {
case -10:
direction = WalkabilityMap.FROM_WEST;
break;
case 10:
direction = WalkabilityMap.FROM_EAST;
break;
case -100:
direction = WalkabilityMap.FROM_NORTH;
break;
case 100:
direction = WalkabilityMap.FROM_SOUTH;
break;
}
Integer walkinfo = walkability.walkmap.get(loc);
if (walkinfo != null) {
if ((walkinfo & direction) == 0) return false;
}
} else {
/* this is a first node */
Integer walkinfo = walkability.walkmap.get(loc);
if (walkinfo != null && walkinfo == ALL_UNWALKABLE) return false;
}
return true;
}
/* check by inspecting ISchemaInfo - not optimized */
/* check components */
if (info.getComponents().containsAt(loc.x, loc.y, 0)) return false;
/* check wires */
Set<ISchemaWire> wires = info.getWires().fetchAllWires(loc.x, loc.y);
if (wires != null && parentloc != null) {
boolean vertical = (loc.x == parentloc.x);
for (ISchemaWire sw : wires) {
Set<WireSegment> segments = sw.segmentsAt(loc.x, loc.y);
if (segments != null) for (WireSegment ws : segments) {
if (ws.isVertical() == vertical) return false;
}
}
}
return true;
}
private int heuristic(int x, int y) {
/* manhattan plus detour cost */
return Math.abs(end.x - x) + Math.abs(end.y - y)
+ ((end.x != x && end.y != y) ? (DETOUR_COST) : (0));
}
}