001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.data.osm;
003
004import java.io.Serializable;
005import java.util.ArrayList;
006import java.util.List;
007import java.util.Objects;
008import java.util.regex.MatchResult;
009import java.util.regex.Matcher;
010import java.util.regex.Pattern;
011
012public class SimplePrimitiveId implements PrimitiveId, Serializable {
013
014    private static final long serialVersionUID = 1L;
015
016    private final long id;
017    private final OsmPrimitiveType type;
018
019    public static final Pattern ID_PATTERN = Pattern.compile("(n|node|w|way|r|rel|relation)[ /]?(\\d+)");
020
021    public static final Pattern MULTIPLE_IDS_PATTERN = Pattern.compile(ID_PATTERN.pattern() + "(-(\\d+))?");
022
023    public SimplePrimitiveId(long id, OsmPrimitiveType type) {
024        this.id = id;
025        this.type = type;
026    }
027
028    @Override
029    public OsmPrimitiveType getType() {
030        return type;
031    }
032
033    @Override
034    public long getUniqueId() {
035        return id;
036    }
037
038    @Override
039    public boolean isNew() {
040        return id <= 0;
041    }
042
043    @Override
044    public int hashCode() {
045        return Objects.hash(id, type);
046    }
047
048    @Override
049    public boolean equals(Object obj) {
050        if (this == obj) return true;
051        if (obj == null || getClass() != obj.getClass()) return false;
052        SimplePrimitiveId that = (SimplePrimitiveId) obj;
053        return id == that.id &&
054                type == that.type;
055    }
056
057    @Override
058    public String toString() {
059        return type.toString() + ' ' + id;
060    }
061
062    /**
063     * Parses a {@code SimplePrimitiveId} from the string {@code s}.
064     * @param s the string to be parsed, e.g., {@code n1}, {@code node1},
065     * {@code w1}, {@code way1}, {@code r1}, {@code rel1}, {@code relation1}.
066     * @return the parsed {@code SimplePrimitiveId}
067     * @throws IllegalArgumentException if the string does not match the pattern
068     */
069    public static SimplePrimitiveId fromString(String s) {
070        final Matcher m = ID_PATTERN.matcher(s);
071        if (m.matches()) {
072            return new SimplePrimitiveId(Long.parseLong(m.group(m.groupCount())), getOsmPrimitiveType(s.charAt(0)));
073        } else {
074            throw new IllegalArgumentException("The string " + s + " does not match the pattern " + ID_PATTERN);
075        }
076    }
077
078    /**
079     * Parses a range {@code SimplePrimitiveId} from the string {@code s}.
080     * @param s the string to be parsed, e.g., {@code node1}, {@code node1-7}, {@code node70-7}.
081     * @return the parsed {@code SimplePrimitiveId}s
082     * @throws IllegalArgumentException if the string does not match the pattern
083     */
084    public static List<SimplePrimitiveId> multipleFromString(String s) {
085        final Matcher m = MULTIPLE_IDS_PATTERN.matcher(s);
086        if (m.matches()) {
087            return extractIdsInto(m, new ArrayList<SimplePrimitiveId>());
088        } else {
089            throw new IllegalArgumentException("The string " + s + " does not match the pattern " + MULTIPLE_IDS_PATTERN);
090        }
091    }
092
093    /**
094     * Attempts to parse extract any primitive id from the string {@code s}.
095     * @param s the string to be parsed, e.g., {@code "n1, w1"}, {@code "node1 and rel2"}, {@code "node 123-29"}.
096     * @return the parsed list of {@code OsmPrimitiveType}s.
097     */
098    public static List<SimplePrimitiveId> fuzzyParse(String s) {
099        final List<SimplePrimitiveId> ids = new ArrayList<>();
100        final Matcher m = MULTIPLE_IDS_PATTERN.matcher(s);
101        while (m.find()) {
102            extractIdsInto(m, ids);
103        }
104        return ids;
105    }
106
107    private static List<SimplePrimitiveId> extractIdsInto(MatchResult m, List<SimplePrimitiveId> ids) {
108        final OsmPrimitiveType type = getOsmPrimitiveType(m.group(1).charAt(0));
109        final String firstId = m.group(2);
110        final String lastId = m.group(4);
111        if (lastId != null) {
112            final long lastIdParsed;
113            if (lastId.length() < firstId.length()) {
114                // parse ranges such as 123-25 or 123-5
115                lastIdParsed = Long.parseLong(firstId.substring(0, firstId.length() - lastId.length()) + lastId);
116            } else {
117                // parse ranges such as 123-125 or 998-1001
118                lastIdParsed = Long.parseLong(lastId);
119            }
120            for (long i = Long.parseLong(firstId); i <= lastIdParsed; i++) {
121                ids.add(new SimplePrimitiveId(i, type));
122            }
123        } else {
124            ids.add(new SimplePrimitiveId(Long.parseLong(firstId), type));
125        }
126        return ids;
127    }
128
129    private static OsmPrimitiveType getOsmPrimitiveType(char firstChar) {
130        return firstChar == 'n' ? OsmPrimitiveType.NODE : firstChar == 'w' ? OsmPrimitiveType.WAY : OsmPrimitiveType.RELATION;
131    }
132}