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