// License: GPL. For details, see LICENSE file.
package org.openstreetmap.josm.data.osm;
import static org.openstreetmap.josm.tools.I18n.tr;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
/**
* A simple class to keep a list of user names.
*
* Instead of storing user names as strings with every OSM primitive, we store
* a reference to an user object, and make sure that for each username there
* is only one user object.
*
* @since 227
*/
public final class User {
private static long uidCounter;
/**
* the map of known users
*/
private static Map<Long, User> userMap = new HashMap<>();
/**
* The anonymous user is a local user used in places where no user is known.
* @see #getAnonymous()
*/
private static final User anonymous = createLocalUser(tr("<anonymous>"));
private static long getNextLocalUid() {
uidCounter--;
return uidCounter;
}
/**
* Creates a local user with the given name
*
* @param name the name
* @return a new local user with the given name
*/
public static synchronized User createLocalUser(String name) {
for (long i = -1; i >= uidCounter; --i) {
User olduser = getById(i);
if (olduser != null && olduser.hasName(name))
return olduser;
}
User user = new User(getNextLocalUid(), name);
userMap.put(user.getId(), user);
return user;
}
private static User lastUser;
/**
* Creates a user known to the OSM server
*
* @param uid the user id
* @param name the name
* @return a new OSM user with the given name and uid
*/
public static synchronized User createOsmUser(long uid, String name) {
if (lastUser != null && lastUser.getId() == uid) {
return lastUser;
}
Long ouid = uid;
User user = userMap.get(ouid);
if (user == null) {
user = new User(uid, name);
userMap.put(ouid, user);
}
if (name != null) user.addName(name);
lastUser = user;
return user;
}
/**
* clears the static map of user ids to user objects
*/
public static synchronized void clearUserMap() {
userMap.clear();
}
/**
* Returns the user with user id <code>uid</code> or null if this user doesn't exist
*
* @param uid the user id
* @return the user; null, if there is no user with this id
*/
public static synchronized User getById(long uid) {
return userMap.get(uid);
}
/**
* Returns the list of users with name <code>name</code> or the empty list if
* no such users exist
*
* @param name the user name
* @return the list of users with name <code>name</code> or the empty list if
* no such users exist
*/
public static synchronized List<User> getByName(String name) {
if (name == null) {
name = "";
}
List<User> ret = new ArrayList<>();
for (User user: userMap.values()) {
if (user.hasName(name)) {
ret.add(user);
}
}
return ret;
}
/**
* Replies the anonymous user
* @return The anonymous user
*/
public static User getAnonymous() {
return anonymous;
}
/** the user name */
private final LinkedHashSet<String> names = new LinkedHashSet<>();
/** the user id */
private final long uid;
/**
* Replies the user name
*
* @return the user name. Never <code>null</code>, but may be the empty string
* @see #getByName(String)
* @see #createOsmUser(long, String)
* @see #createLocalUser(String)
*/
public String getName() {
return names.isEmpty() ? "" : names.iterator().next();
}
/**
* Returns the list of user names
*
* @return list of names
*/
public List<String> getNames() {
return new ArrayList<>(names);
}
/**
* Adds a user name to the list if it is not there, yet.
*
* @param name User name
*/
public void addName(String name) {
names.add(name);
}
/**
* Sets the preferred user name, i.e., the one that will be returned when calling {@link #getName()}.
*
* Rationale: A user can change its name multiple times and after reading various (outdated w.r.t. user name)
* data files it is unclear which is the up-to-date user name.
* @param name the preferred user name to set
*/
public void setPreferredName(String name) {
if (names.size() == 1 && names.contains(name)) {
return;
}
final Collection<String> allNames = new LinkedHashSet<>(names);
names.clear();
names.add(name);
names.addAll(allNames);
}
/**
* Returns true if the name is in the names list
*
* @param name User name
* @return <code>true</code> if the name is in the names list
*/
public boolean hasName(String name) {
return names.contains(name);
}
/**
* Replies the user id. If this user is known to the OSM server the positive user id
* from the server is replied. Otherwise, a negative local value is replied.
*
* A negative local is only unique during an editing session. It is lost when the
* application is closed and there is no guarantee that a negative local user id is
* always bound to a user with the same name.
*
* @return the user id
*/
public long getId() {
return uid;
}
/**
* Private constructor, only called from get method.
* @param uid user id
* @param name user name
*/
private User(long uid, String name) {
this.uid = uid;
if (name != null) {
addName(name);
}
}
/**
* Determines if this user is known to OSM
* @return {@code true} if this user is known to OSM, {@code false} otherwise
*/
public boolean isOsmUser() {
return uid > 0;
}
/**
* Determines if this user is local
* @return {@code true} if this user is local, {@code false} otherwise
*/
public boolean isLocalUser() {
return uid < 0;
}
@Override
public int hashCode() {
return Objects.hash(uid);
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null || getClass() != obj.getClass()) return false;
User user = (User) obj;
return uid == user.uid;
}
@Override
public String toString() {
StringBuilder s = new StringBuilder();
s.append("id:").append(uid);
if (names.size() == 1) {
s.append(" name:").append(getName());
} else if (names.size() > 1) {
s.append(String.format(" %d names:%s", names.size(), getName()));
}
return s.toString();
}
}