source: josm/trunk/src/org/openstreetmap/josm/data/osm/User.java @ 13100

Last change on this file since 13100 was 13100, checked in by Don-vip, 9 months ago

see #15534 - ensure user names are never null, as expected

  • Property svn:eol-style set to native
File size: 7.1 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.data.osm;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5
6import java.util.ArrayList;
7import java.util.Collection;
8import java.util.HashMap;
9import java.util.LinkedHashSet;
10import java.util.List;
11import java.util.Map;
12import java.util.Objects;
13
14/**
15 * A simple class to keep a list of user names.
16 *
17 * Instead of storing user names as strings with every OSM primitive, we store
18 * a reference to an user object, and make sure that for each username there
19 * is only one user object.
20 *
21 * @since 227
22 */
23public final class User {
24
25    private static long uidCounter;
26
27    /**
28     * the map of known users
29     */
30    private static Map<Long, User> userMap = new HashMap<>();
31
32    /**
33     * The anonymous user is a local user used in places where no user is known.
34     * @see #getAnonymous()
35     */
36    private static final User ANONYMOUS = createLocalUser(tr("<anonymous>"));
37
38    private static long getNextLocalUid() {
39        uidCounter--;
40        return uidCounter;
41    }
42
43    /**
44     * Creates a local user with the given name
45     *
46     * @param name the name
47     * @return a new local user with the given name
48     */
49    public static synchronized User createLocalUser(String name) {
50        for (long i = -1; i >= uidCounter; --i) {
51            User olduser = getById(i);
52            if (olduser != null && olduser.hasName(name))
53                return olduser;
54        }
55        User user = new User(getNextLocalUid(), name);
56        userMap.put(user.getId(), user);
57        return user;
58    }
59
60    private static User lastUser;
61
62    /**
63     * Creates a user known to the OSM server
64     *
65     * @param uid  the user id
66     * @param name the name
67     * @return a new OSM user with the given name and uid
68     */
69    public static synchronized User createOsmUser(long uid, String name) {
70
71        if (lastUser != null && lastUser.getId() == uid) {
72            lastUser.setPreferredName(name);
73            return lastUser;
74        }
75
76        User user = userMap.computeIfAbsent(uid, k -> new User(uid, name));
77        if (name != null) user.addName(name);
78
79        lastUser = user;
80
81        return user;
82    }
83
84    /**
85     * clears the static map of user ids to user objects
86     */
87    public static synchronized void clearUserMap() {
88        userMap.clear();
89        lastUser = null;
90    }
91
92    /**
93     * Returns the user with user id <code>uid</code> or null if this user doesn't exist
94     *
95     * @param uid the user id
96     * @return the user; null, if there is no user with  this id
97     */
98    public static synchronized User getById(long uid) {
99        return userMap.get(uid);
100    }
101
102    /**
103     * Returns the list of users with name <code>name</code> or the empty list if
104     * no such users exist
105     *
106     * @param name the user name
107     * @return the list of users with name <code>name</code> or the empty list if
108     * no such users exist
109     */
110    public static synchronized List<User> getByName(String name) {
111        if (name == null) {
112            name = "";
113        }
114        List<User> ret = new ArrayList<>();
115        for (User user: userMap.values()) {
116            if (user.hasName(name)) {
117                ret.add(user);
118            }
119        }
120        return ret;
121    }
122
123    /**
124     * Replies the anonymous user
125     * @return The anonymous user
126     */
127    public static User getAnonymous() {
128        return ANONYMOUS;
129    }
130
131    /** the user name */
132    private final LinkedHashSet<String> names = new LinkedHashSet<>();
133    /** the user id */
134    private final long uid;
135
136    /**
137     * Replies the user name
138     *
139     * @return the user name. Never <code>null</code>, but may be the empty string
140     * @see #getByName(String)
141     * @see #createOsmUser(long, String)
142     * @see #createLocalUser(String)
143     */
144    public String getName() {
145        return names.isEmpty() ? "" : names.iterator().next();
146    }
147
148    /**
149     * Returns the list of user names
150     *
151     * @return list of names
152     */
153    public List<String> getNames() {
154        return new ArrayList<>(names);
155    }
156
157    /**
158     * Adds a user name to the list if it is not there, yet.
159     *
160     * @param name User name
161     * @throws NullPointerException if name is null
162     */
163    public void addName(String name) {
164        names.add(Objects.requireNonNull(name, "name"));
165    }
166
167    /**
168     * Sets the preferred user name, i.e., the one that will be returned when calling {@link #getName()}.
169     *
170     * Rationale: A user can change its name multiple times and after reading various (outdated w.r.t. user name)
171     * data files it is unclear which is the up-to-date user name.
172     * @param name the preferred user name to set
173     * @throws NullPointerException if name is null
174     */
175    public void setPreferredName(String name) {
176        if (names.size() == 1 && names.contains(name)) {
177            return;
178        }
179        final Collection<String> allNames = new LinkedHashSet<>(names);
180        names.clear();
181        names.add(Objects.requireNonNull(name, "name"));
182        names.addAll(allNames);
183    }
184
185    /**
186     * Returns true if the name is in the names list
187     *
188     * @param name User name
189     * @return <code>true</code> if the name is in the names list
190     */
191    public boolean hasName(String name) {
192        return names.contains(name);
193    }
194
195    /**
196     * Replies the user id. If this user is known to the OSM server the positive user id
197     * from the server is replied. Otherwise, a negative local value is replied.
198     *
199     * A negative local is only unique during an editing session. It is lost when the
200     * application is closed and there is no guarantee that a negative local user id is
201     * always bound to a user with the same name.
202     *
203     * @return the user id
204     */
205    public long getId() {
206        return uid;
207    }
208
209    /**
210     * Private constructor, only called from get method.
211     * @param uid user id
212     * @param name user name
213     */
214    private User(long uid, String name) {
215        this.uid = uid;
216        if (name != null) {
217            addName(name);
218        }
219    }
220
221    /**
222     * Determines if this user is known to OSM
223     * @return {@code true} if this user is known to OSM, {@code false} otherwise
224     */
225    public boolean isOsmUser() {
226        return uid > 0;
227    }
228
229    /**
230     * Determines if this user is local
231     * @return {@code true} if this user is local, {@code false} otherwise
232     */
233    public boolean isLocalUser() {
234        return uid < 0;
235    }
236
237    @Override
238    public int hashCode() {
239        return Objects.hash(uid);
240    }
241
242    @Override
243    public boolean equals(Object obj) {
244        if (this == obj) return true;
245        if (obj == null || getClass() != obj.getClass()) return false;
246        User user = (User) obj;
247        return uid == user.uid;
248    }
249
250    @Override
251    public String toString() {
252        StringBuilder s = new StringBuilder();
253        s.append("id:").append(uid);
254        if (names.size() == 1) {
255            s.append(" name:").append(getName());
256        } else if (names.size() > 1) {
257            s.append(String.format(" %d names:%s", names.size(), getName()));
258        }
259        return s.toString();
260    }
261}
Note: See TracBrowser for help on using the repository browser.