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