001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.data.osm;
003
004import static org.openstreetmap.josm.tools.I18n.tr;
005
006import java.util.ArrayList;
007import java.util.HashMap;
008import java.util.HashSet;
009import java.util.List;
010import java.util.Map;
011import java.util.Objects;
012import java.util.Set;
013
014import org.openstreetmap.josm.tools.Utils;
015
016/**
017 * A simple class to keep a list of user names.
018 *
019 * Instead of storing user names as strings with every OSM primitive, we store
020 * a reference to an user object, and make sure that for each username there
021 * is only one user object.
022 *
023 * @since 227
024 */
025public final class User {
026
027    private static long uidCounter;
028
029    /**
030     * the map of known users
031     */
032    private static Map<Long, User> userMap = new HashMap<>();
033
034    /**
035     * The anonymous user is a local user used in places where no user is known.
036     * @see #getAnonymous()
037     */
038    private static final User anonymous = createLocalUser(tr("<anonymous>"));
039
040    private static long getNextLocalUid() {
041        uidCounter--;
042        return uidCounter;
043    }
044
045    /**
046     * Creates a local user with the given name
047     *
048     * @param name the name
049     * @return a new local user with the given name
050     */
051    public static synchronized User createLocalUser(String name) {
052        for (long i = -1; i >= uidCounter; --i) {
053            User olduser = getById(i);
054            if (olduser != null && olduser.hasName(name))
055                return olduser;
056        }
057        User user = new User(getNextLocalUid(), name);
058        userMap.put(user.getId(), user);
059        return user;
060    }
061
062    private static User lastUser;
063
064    /**
065     * Creates a user known to the OSM server
066     *
067     * @param uid  the user id
068     * @param name the name
069     * @return a new OSM user with the given name and uid
070     */
071    public static synchronized User createOsmUser(long uid, String name) {
072
073        if (lastUser != null && lastUser.getId() == uid) {
074            return lastUser;
075        }
076
077        Long ouid = uid;
078        User user = userMap.get(ouid);
079        if (user == null) {
080            user = new User(uid, name);
081            userMap.put(ouid, user);
082        }
083        if (name != null) user.addName(name);
084
085        lastUser = user;
086
087        return user;
088    }
089
090    /**
091     * clears the static map of user ids to user objects
092     */
093    public static synchronized void clearUserMap() {
094        userMap.clear();
095    }
096
097    /**
098     * Returns the user with user id <code>uid</code> or null if this user doesn't exist
099     *
100     * @param uid the user id
101     * @return the user; null, if there is no user with  this id
102     */
103    public static synchronized User getById(long uid) {
104        return userMap.get(uid);
105    }
106
107    /**
108     * Returns the list of users with name <code>name</code> or the empty list if
109     * no such users exist
110     *
111     * @param name the user name
112     * @return the list of users with name <code>name</code> or the empty list if
113     * no such users exist
114     */
115    public static synchronized List<User> getByName(String name) {
116        if (name == null) {
117            name = "";
118        }
119        List<User> ret = new ArrayList<>();
120        for (User user: userMap.values()) {
121            if (user.hasName(name)) {
122                ret.add(user);
123            }
124        }
125        return ret;
126    }
127
128    /**
129     * Replies the anonymous user
130     * @return The anonymous user
131     */
132    public static User getAnonymous() {
133        return anonymous;
134    }
135
136    /** the user name */
137    private final Set<String> names = new HashSet<>();
138    /** the user id */
139    private final long uid;
140
141    /**
142     * Replies the user name
143     *
144     * @return the user name. Never <code>null</code>, but may be the empty string
145     */
146    public String getName() {
147        return Utils.join("/", names);
148    }
149
150    /**
151     * Returns the list of user names
152     *
153     * @return list of names
154     */
155    public List<String> getNames() {
156        return new ArrayList<>(names);
157    }
158
159    /**
160     * Adds a user name to the list if it is not there, yet.
161     *
162     * @param name User name
163     */
164    public void addName(String name) {
165        names.add(name);
166    }
167
168    /**
169     * Returns true if the name is in the names list
170     *
171     * @param name User name
172     * @return <code>true</code> if the name is in the names list
173     */
174    public boolean hasName(String name) {
175        return names.contains(name);
176    }
177
178    /**
179     * Replies the user id. If this user is known to the OSM server the positive user id
180     * from the server is replied. Otherwise, a negative local value is replied.
181     *
182     * A negative local is only unique during an editing session. It is lost when the
183     * application is closed and there is no guarantee that a negative local user id is
184     * always bound to a user with the same name.
185     *
186     * @return the user id
187     */
188    public long getId() {
189        return uid;
190    }
191
192    /**
193     * Private constructor, only called from get method.
194     * @param uid user id
195     * @param name user name
196     */
197    private User(long uid, String name) {
198        this.uid = uid;
199        if (name != null) {
200            addName(name);
201        }
202    }
203
204    /**
205     * Determines if this user is known to OSM
206     * @return {@code true} if this user is known to OSM, {@code false} otherwise
207     */
208    public boolean isOsmUser() {
209        return uid > 0;
210    }
211
212    /**
213     * Determines if this user is local
214     * @return {@code true} if this user is local, {@code false} otherwise
215     */
216    public boolean isLocalUser() {
217        return uid < 0;
218    }
219
220    @Override
221    public int hashCode() {
222        return Objects.hash(uid);
223    }
224
225    @Override
226    public boolean equals(Object obj) {
227        if (this == obj) return true;
228        if (obj == null || getClass() != obj.getClass()) return false;
229        User user = (User) obj;
230        return uid == user.uid;
231    }
232
233    @Override
234    public String toString() {
235        StringBuilder s = new StringBuilder();
236        s.append("id:").append(uid);
237        if (names.size() == 1) {
238            s.append(" name:").append(getName());
239        } else if (names.size() > 1) {
240            s.append(String.format(" %d names:%s", names.size(), getName()));
241        }
242        return s.toString();
243    }
244}