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