001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.data.osm;
003
004import java.util.Comparator;
005import java.util.HashMap;
006import java.util.Map;
007
008import org.openstreetmap.josm.gui.DefaultNameFormatter;
009
010/**
011 * Comparator, comparing pritimives by:<ul>
012 * <li>type and ids in "quick" mode</li>
013 * <li>type and objects display names instead</li>
014 * </ul>
015 * @since 4113
016 */
017public class OsmPrimitiveComparator implements Comparator<OsmPrimitive> {
018    private final Map<OsmPrimitive, String> cache = new HashMap<>();
019    private final boolean relationsFirst;
020    private final boolean quick;
021
022    /**
023     * Constructs a new {@code OsmPrimitiveComparator}.
024     */
025    public OsmPrimitiveComparator() {
026        this(false, false);
027    }
028
029    /**
030     * Constructs a new {@code OsmPrimitiveComparator}.
031     * @param quick if {@code true}, sorts by type and ids (fast), otherwise sort by type and display names (slower)
032     * @param relationsFirst if {@code true}, always list relations first
033     */
034    public OsmPrimitiveComparator(boolean quick, boolean relationsFirst) {
035        this.quick = quick;
036        this.relationsFirst = relationsFirst;
037    }
038
039    private String cachedName(OsmPrimitive p) {
040        String name = cache.get(p);
041        if (name == null) {
042            name = p.getDisplayName(DefaultNameFormatter.getInstance());
043            cache.put(p, name);
044        }
045        return name;
046    }
047
048    private int compareName(OsmPrimitive a, OsmPrimitive b) {
049        String an = cachedName(a);
050        String bn = cachedName(b);
051        // make sure display names starting with digits are the end of the list
052        if (Character.isDigit(an.charAt(0)) && Character.isDigit(bn.charAt(0)))
053            return an.compareTo(bn);
054        else if (Character.isDigit(an.charAt(0)) && !Character.isDigit(bn.charAt(0)))
055            return 1;
056        else if (!Character.isDigit(an.charAt(0)) && Character.isDigit(bn.charAt(0)))
057            return -1;
058        return an.compareTo(bn);
059    }
060
061    private static int compareId(OsmPrimitive a, OsmPrimitive b) {
062        long idA = a.getUniqueId();
063        long idB = b.getUniqueId();
064        if (idA < idB) return -1;
065        if (idA > idB) return 1;
066        return 0;
067    }
068
069    private int compareType(OsmPrimitive a, OsmPrimitive b) {
070        if (relationsFirst) {
071            // show relations before ways, then nodes
072            if (a.getType().equals(OsmPrimitiveType.RELATION)) return -1;
073            if (a.getType().equals(OsmPrimitiveType.NODE)) return 1;
074            // a is a way
075            if (b.getType().equals(OsmPrimitiveType.RELATION)) return 1;
076            // b is a node
077        } else {
078            // show ways before relations, then nodes
079            if (a.getType().equals(OsmPrimitiveType.WAY)) return -1;
080            if (a.getType().equals(OsmPrimitiveType.NODE)) return 1;
081            // a is a relation
082            if (b.getType().equals(OsmPrimitiveType.WAY)) return 1;
083            // b is a node
084        }
085        return -1;
086    }
087
088    @Override
089    public int compare(OsmPrimitive a, OsmPrimitive b) {
090        if (a.getType().equals(b.getType()))
091            return quick ? compareId(a, b) : compareName(a, b);
092        return compareType(a, b);
093    }
094}