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