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