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