File Source: Identity.java
1 /*
2 * Copyright (c) 2006-2009 Chris Smith, Shane Mc Cormack, Gregory Holmes
3 *
4 * Permission is hereby granted, free of charge, to any person obtaining a copy
5 * of this software and associated documentation files (the "Software"), to deal
6 * in the Software without restriction, including without limitation the rights
7 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 * copies of the Software, and to permit persons to whom the Software is
9 * furnished to do so, subject to the following conditions:
10 *
11 * The above copyright notice and this permission notice shall be included in
12 * all copies or substantial portions of the Software.
13 *
14 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20 * SOFTWARE.
21 */
22
23 package com.dmdirc.config;
24
25 import com.dmdirc.Main;
26 import com.dmdirc.interfaces.ConfigChangeListener;
27 import com.dmdirc.logger.ErrorLevel;
28 import com.dmdirc.logger.Logger;
29 import com.dmdirc.util.ConfigFile;
30 import com.dmdirc.util.InvalidConfigFileException;
31 import com.dmdirc.util.WeakList;
32
33 import java.io.File;
34 import java.io.FileInputStream;
35 import java.io.IOException;
36 import java.io.InputStream;
37 import java.io.Serializable;
38 import java.util.ArrayList;
39 import java.util.HashMap;
40 import java.util.HashSet;
41 import java.util.LinkedList;
42 import java.util.List;
43 import java.util.Map;
44 import java.util.Set;
45 import java.util.logging.Level;
46
47 /**
48 * An identity is a group of settings that are applied to a connection, server,
49 * network or channel. Identities may be automatically applied in certain
50 * cases, or the user may manually apply them.
51 * <p>
52 * Note: this class has a natural ordering that is inconsistent with equals.
53 *
54 * @author chris
55 */
/*
P/P * Method: int compareTo(Object)
*
* Preconditions:
* this.myTarget != null
* init'ed(this.myTarget.type)
* x0 != null
* x0.myTarget != null
* x0.myTarget.type != null
* (soft) init'ed(this.myTarget.order)
* (soft) this.myTarget.order - x0.myTarget.order in {-231..232-1}
* (soft) init'ed(x0.myTarget.order)
*
* Postconditions:
* init'ed(return_value)
*/
56 public class Identity extends ConfigSource implements Serializable,
57 Comparable<Identity> {
58
59 /**
60 * A version number for this class. It should be changed whenever the class
61 * structure is changed (or anything else that would prevent serialized
62 * objects being unserialized with the new class).
63 */
64 private static final long serialVersionUID = 1;
65
66 /** The domain used for identity settings. */
/*
P/P * Method: com.dmdirc.config.Identity__static_init
*
* Postconditions:
* DOMAIN != null
* init'ed(LOGGER)
* PROFILE_DOMAIN != null
*/
67 private static final String DOMAIN = "identity".intern();
68
69 /** The domain used for profile settings. */
70 private static final String PROFILE_DOMAIN = "profile".intern();
71
72 /** A regular expression that will match all characters illegal in file names. */
73 protected static final String ILLEGAL_CHARS = "[\\\\\"/:\\*\\?\"<>\\|]";
74
75 /** A logger for this class. */
76 private static final java.util.logging.Logger LOGGER = java.util.logging
77 .Logger.getLogger(Identity.class.getName());
78
79 /** The target for this identity. */
80 protected final ConfigTarget myTarget;
81
82 /** The configuration details for this identity. */
83 protected final ConfigFile file;
84
85 /** The global config manager. */
86 protected ConfigManager globalConfig;
87
88 /** The config change listeners for this source. */
89 protected final List<ConfigChangeListener> listeners
90 = new WeakList<ConfigChangeListener>();
91
92 /** Whether this identity needs to be saved. */
93 protected boolean needSave;
94
95 /**
96 * Creates a new instance of Identity.
97 *
98 * @param file The file to load this identity from
99 * @param forceDefault Whether to force this identity to be loaded as default
100 * identity or not
101 * @throws InvalidIdentityFileException Missing required properties
102 * @throws IOException Input/output exception
103 */
104 public Identity(final File file, final boolean forceDefault) throws IOException,
105 InvalidIdentityFileException {
/*
P/P * Method: void com.dmdirc.config.Identity(File, bool)
*
* Preconditions:
* (soft) forceDefault == 1
*
* Postconditions:
* java.lang.StringBuilder:toString(...)._tainted == 0
* this.file == &new ConfigFile(Identity#2)
* init'ed(this.globalConfig)
* this.listeners == &new WeakList(Identity#1)
* this.myTarget == &new ConfigTarget(getTarget#1)
* init'ed(this.needSave)
* this.sources == undefined
* this.sources == null
* new ArrayList(getSources#1) num objects == 0
* new ConfigFile(Identity#2) num objects == 1
* ...
*/
106 super();
107
108 this.file = new ConfigFile(file);
109 this.file.setAutomake(true);
110 initFile(forceDefault, new FileInputStream(file));
111 myTarget = getTarget(forceDefault);
112
113 if (myTarget.getType() == ConfigTarget.TYPE.PROFILE) {
114 migrateProfile();
115 }
116 }
117
118 /**
119 * Creates a new read-only identity.
120 *
121 * @param stream The input stream to read the identity from
122 * @param forceDefault Whether to force this identity to be loaded as default
123 * identity or not
124 * @throws InvalidIdentityFileException Missing required properties
125 * @throws IOException Input/output exception
126 */
127 public Identity(final InputStream stream, final boolean forceDefault) throws IOException,
128 InvalidIdentityFileException {
/*
P/P * Method: void com.dmdirc.config.Identity(InputStream, bool)
*
* Preconditions:
* (soft) forceDefault == 1
*
* Postconditions:
* java.lang.StringBuilder:toString(...)._tainted == 0
* this.file == &new ConfigFile(Identity#2)
* init'ed(this.globalConfig)
* this.listeners == &new WeakList(Identity#1)
* this.myTarget == &new ConfigTarget(getTarget#1)
* init'ed(this.needSave)
* this.sources == undefined
* this.sources == null
* new ArrayList(getSources#1) num objects == 0
* new ConfigFile(Identity#2) num objects == 1
* ...
*/
129 super();
130
131 this.file = new ConfigFile(stream);
132 this.file.setAutomake(true);
133 initFile(forceDefault, stream);
134 myTarget = getTarget(forceDefault);
135
136 if (myTarget.getType() == ConfigTarget.TYPE.PROFILE) {
137 migrateProfile();
138 }
139 }
140
141 /**
142 * Creates a new identity from the specified config file.
143 *
144 * @param configFile The config file to use
145 * @param target The target of this identity
146 */
147 public Identity(final ConfigFile configFile, final ConfigTarget target) {
/*
P/P * Method: void com.dmdirc.config.Identity(ConfigFile, ConfigTarget)
*
* Preconditions:
* configFile != null
* target != null
* init'ed(target.type)
* (soft) init'ed(com.dmdirc.config.ConfigManager$1__static_init.new int[](ConfigManager$1__static_init#1)[...])
*
* Postconditions:
* java.lang.StringBuilder:toString(...)._tainted == 0
* this.file == configFile
* this.file != null
* this.globalConfig in Addr_Set{null,&new ConfigManager(setOption#2)}
* this.listeners == &new WeakList(Identity#1)
* this.myTarget == target
* this.myTarget != null
* possibly_updated(this.needSave)
* new ArrayList(getSources#1) num objects <= 1
* new ConfigManager(setOption#2) num objects <= 1
* ...
*/
148 super();
149
150 this.file = configFile;
151 this.file.setAutomake(true);
152 this.myTarget = target;
153
154 if (myTarget.getType() == ConfigTarget.TYPE.PROFILE) {
155 migrateProfile();
156 }
157 }
158
159 /**
160 * Determines and returns the target for this identity from its contents.
161 *
162 * @param forceDefault Whether to force this to be a default identity
163 * @return A ConfigTarget for this identity
164 * @throws InvalidIdentityFileException If the identity isn't valid
165 */
166 private ConfigTarget getTarget(final boolean forceDefault)
167 throws InvalidIdentityFileException {
/*
P/P * Method: ConfigTarget getTarget(bool)
*
* Preconditions:
* this.file != null
*
* Presumptions:
* com.dmdirc.util.ConfigFile:isKeyDomain(...)@330 == 1
* java.util.Map:containsKey(...)@330 == 1
*
* Postconditions:
* return_value == &new ConfigTarget(getTarget#1)
* new ConfigTarget(getTarget#1) num objects == 1
* init'ed(return_value.data)
* init'ed(return_value.order)
* return_value.type in Addr_Set{&com.dmdirc.config.ConfigTarget$TYPE__static_init.new ConfigTarget$TYPE(ConfigTarget$TYPE__static_init#2),&com.dmdirc.config.ConfigTarget$TYPE__static_init.new ConfigTarget$TYPE(ConfigTarget$TYPE__static_init#4),&com.dmdirc.config.ConfigTarget$TYPE__static_init.new ConfigTarget$TYPE(ConfigTarget$TYPE__static_init#1),&com.dmdirc.config.ConfigTarget$TYPE__static_init.new ConfigTarget$TYPE(ConfigTarget$TYPE__static_init#8),&com.dmdirc.config.ConfigTarget$TYPE__static_init.new ConfigTarget$TYPE(ConfigTarget$TYPE__static_init#7),&com.dmdirc.config.ConfigTarget$TYPE__static_init.new ConfigTarget$TYPE(ConfigTarget$TYPE__static_init#6),&com.dmdirc.config.ConfigTarget$TYPE__static_init.new ConfigTarget$TYPE(ConfigTarget$TYPE__static_init#5)}
*
* Test Vectors:
* forceDefault: {0}, {1}
*/
168 final ConfigTarget target = new ConfigTarget();
169
170 if (hasOption(DOMAIN, "ircd")) {
171 target.setIrcd(getOption(DOMAIN, "ircd"));
172 } else if (hasOption(DOMAIN, "network")) {
173 target.setNetwork(getOption(DOMAIN, "network"));
174 } else if (hasOption(DOMAIN, "server")) {
175 target.setServer(getOption(DOMAIN, "server"));
176 } else if (hasOption(DOMAIN, "channel")) {
177 target.setChannel(getOption(DOMAIN, "channel"));
178 } else if (hasOption(DOMAIN, "globaldefault")) {
179 target.setGlobalDefault();
180 } else if (hasOption(DOMAIN, "global") || (forceDefault && !isProfile())) {
181 target.setGlobal();
182 } else if (isProfile()) {
183 target.setProfile();
184 } else {
185 throw new InvalidIdentityFileException("No target and no profile");
186 }
187
188 if (hasOption(DOMAIN, "order")) {
189 target.setOrder(getOptionInt(DOMAIN, "order"));
190 }
191
192 return target;
193 }
194
195 /**
196 * Initialises this identity from a file.
197 *
198 * @param forceDefault Whether to force this to be a default identity
199 * @param stream The stream to load properties from if needed (or null)
200 * @param file The file to load this identity from (or null)
201 * @throws InvalidIdentityFileException if the identity file is invalid
202 * @throws IOException On I/O exception when reading the identity
203 */
204 private void initFile(final boolean forceDefault, final InputStream stream)
205 throws InvalidIdentityFileException, IOException {
206 try {
/*
P/P * Method: void initFile(bool, InputStream)
*
* Preconditions:
* this.file != null
* (soft) forceDefault == 1
*/
207 this.file.read();
208 } catch (InvalidConfigFileException ex) {
209 throw new InvalidIdentityFileException(ex);
210 }
211
212 if (!hasOption(DOMAIN, "name") && !forceDefault) {
213 throw new InvalidIdentityFileException("No name specified");
214 }
215 }
216
217 /**
218 * Attempts to reload this identity from disk. If this identity has been
219 * modified (i.e., {@code needSave} is true), then this method silently
220 * returns straight away. All relevant ConfigChangeListeners are fired for
221 * new, altered and deleted properties. The target of the identity will not
222 * be changed by this method, even if it has changed on disk.
223 *
224 * @throws java.io.IOException On I/O exception when reading the identity
225 * @throws InvalidConfigFileException if the config file is no longer valid
226 */
227 public void reload() throws IOException, InvalidConfigFileException {
/*
P/P * Method: void reload()
*
* Preconditions:
* init'ed(this.needSave)
* (soft) this.file != null
*
* Presumptions:
* change.length@263 >= 2
* com.dmdirc.util.ConfigFile:getKeyDomains(...)@235 != null
* com.dmdirc.util.ConfigFile:getKeyDomains(...)@239 != null
* java.util.Iterator:next(...)@239 != null
* java.util.Iterator:next(...)@242 != null
* ...
*
* Test Vectors:
* this.needSave: {0}, {1}
* java.lang.String:equals(...)@248: {1}, {0}
* java.util.Iterator:hasNext(...)@239: {1}, {0}
* java.util.Iterator:hasNext(...)@242: {0}, {1}
* java.util.Iterator:hasNext(...)@256: {0}, {1}
* java.util.Iterator:hasNext(...)@263: {0}, {1}
* java.util.Map:containsKey(...)@246: {1}, {0}
* java.util.Map:containsKey(...)@248: {0}, {1}
* java.util.Map:containsKey(...)@255: {0}, {1}
*/
228 if (needSave) {
229 return;
230 }
231
232 final List<String[]> changes = new LinkedList<String[]>();
233
234 synchronized (this) {
235 final Map<String, Map<String, String>> oldProps = file.getKeyDomains();
236
237 file.read();
238
239 for (Map.Entry<String, Map<String, String>> entry : file.getKeyDomains().entrySet()) {
240 final String domain = entry.getKey();
241
242 for (Map.Entry<String, String> subentry : entry.getValue().entrySet()) {
243 final String key = subentry.getKey();
244 final String value = subentry.getValue();
245
246 if (!oldProps.containsKey(domain)) {
247 changes.add(new String[]{domain, key});
248 } else if (!oldProps.get(domain).containsKey(key)
249 || !oldProps.get(domain).get(key).equals(value)) {
250 changes.add(new String[]{domain, key});
251 oldProps.get(domain).remove(key);
252 }
253 }
254
255 if (oldProps.containsKey(domain)) {
256 for (String key : oldProps.get(domain).keySet()) {
257 changes.add(new String[]{domain, key});
258 }
259 }
260 }
261 }
262
263 for (String[] change : changes) {
264 fireSettingChange(change[0], change[1]);
265 }
266 }
267
268 /**
269 * Fires the config changed listener for the specified option after this
270 * identity is reloaded.
271 *
272 * @param domain The domain of the option that's changed
273 * @param key The key of the option that's changed
274 * @since 0.6.3m1
275 */
276 private void fireSettingChange(final String domain, final String key) {
/*
P/P * Method: void fireSettingChange(String, String)
*
* Presumptions:
* java.util.ArrayList:iterator(...)@277 != null
* java.util.Iterator:next(...)@277 != null
*
* Test Vectors:
* java.util.Iterator:hasNext(...)@277: {0}, {1}
*/
277 for (ConfigChangeListener listener : new ArrayList<ConfigChangeListener>(listeners)) {
278 listener.configChanged(domain, key);
279 }
280 }
281
282 /**
283 * Returns the name of this identity.
284 *
285 * @return The name of this identity
286 */
287 public String getName() {
/*
P/P * Method: String getName()
*
* Preconditions:
* this.file != null
*
* Postconditions:
* init'ed(return_value)
*/
288 if (hasOption(DOMAIN, "name")) {
289 return getOption(DOMAIN, "name");
290 } else {
291 return "Unnamed";
292 }
293 }
294
295 /**
296 * Checks if this profile needs migrating from the old method of
297 * storing nicknames (profile.nickname + profile.altnicks) to the new
298 * method (profile.nicknames), and performs the migration if needed.
299 *
300 * @since 0.6.3m1
301 */
302 protected void migrateProfile() {
/*
P/P * Method: void migrateProfile()
*
* Preconditions:
* this.file != null
* (soft) init'ed(com.dmdirc.config.ConfigManager$1__static_init.new int[](ConfigManager$1__static_init#1)[...])
* (soft) init'ed(this.globalConfig)
* (soft) this.listeners != null
* (soft) this.myTarget != null
* (soft) init'ed(this.myTarget.type)
*
* Postconditions:
* java.lang.StringBuilder:toString(...)._tainted == 0
* this.globalConfig == One-of{old this.globalConfig, &new ConfigManager(setOption#2)}
* init'ed(this.globalConfig)
* possibly_updated(this.needSave)
* new ArrayList(getSources#1) num objects <= 1
* new ConfigManager(setOption#2) num objects <= 1
* init'ed(new ConfigManager(setOption#2).channel)
* init'ed(new ConfigManager(setOption#2).ircd)
* init'ed(new ConfigManager(setOption#2).listeners)
* init'ed(new ConfigManager(setOption#2).network)
* ...
*/
303 if (hasOption(PROFILE_DOMAIN, "nickname")) {
304 // Migrate
305
306 setOption(PROFILE_DOMAIN, "nicknames", getOption(PROFILE_DOMAIN, "nickname")
307 + (hasOption(PROFILE_DOMAIN, "altnicks") ? "\n"
308 + getOption(PROFILE_DOMAIN, "altnicks") : ""));
309 unsetOption(PROFILE_DOMAIN, "nickname");
310 unsetOption(PROFILE_DOMAIN, "altnicks");
311 }
312 }
313
314 /**
315 * Determines whether this identity can be used as a profile when
316 * connecting to a server. Profiles are identities that can supply
317 * nick, ident, real name, etc.
318 *
319 * @return True iff this identity can be used as a profile
320 */
321 public boolean isProfile() {
/*
P/P * Method: bool isProfile()
*
* Preconditions:
* this.file != null
*
* Postconditions:
* init'ed(return_value)
*/
322 return (hasOption(PROFILE_DOMAIN, "nicknames")
323 || hasOption(PROFILE_DOMAIN, "nickname"))
324 && hasOption(PROFILE_DOMAIN, "realname");
325 }
326
327 /** {@inheritDoc} */
328 @Override
329 protected boolean hasOption(final String domain, final String option) {
/*
P/P * Method: bool hasOption(String, String)
*
* Preconditions:
* this.file != null
*
* Presumptions:
* com.dmdirc.util.ConfigFile:getKeyDomain(...)@330 != null
*
* Postconditions:
* init'ed(return_value)
*/
330 return file.isKeyDomain(domain) && file.getKeyDomain(domain).containsKey(option);
331 }
332
333 /** {@inheritDoc} */
334 @Override
335 public synchronized String getOption(final String domain, final String option) {
/*
P/P * Method: String getOption(String, String)
*
* Preconditions:
* this.file != null
*
* Presumptions:
* com.dmdirc.util.ConfigFile:getKeyDomain(...)@336 != null
*
* Postconditions:
* init'ed(return_value)
*/
336 return file.getKeyDomain(domain).get(option);
337 }
338
339 /**
340 * Sets the specified option in this identity to the specified value.
341 *
342 * @param domain The domain of the option
343 * @param option The name of the option
344 * @param value The new value for the option
345 */
346 public void setOption(final String domain, final String option,
347 final String value) {
348 String oldValue;
349
/*
P/P * Method: void setOption(String, String, String)
*
* Preconditions:
* this.file != null
* this.myTarget != null
* init'ed(this.myTarget.type)
* (soft) init'ed(com.dmdirc.config.ConfigManager$1__static_init.new int[](ConfigManager$1__static_init#1)[...])
* (soft) init'ed(this.globalConfig)
* (soft) this.listeners != null
*
* Presumptions:
* com.dmdirc.util.ConfigFile:getKeyDomain(...)@379 != null
* java.util.logging.Logger:getLogger(...)@76 != null
* this.globalConfig.listeners@352 != null
* this.globalConfig.sources@352 != null
*
* Postconditions:
* java.lang.StringBuilder:toString(...)._tainted == 0
* this.globalConfig == One-of{old this.globalConfig, &new ConfigManager(setOption#2)}
* init'ed(this.globalConfig)
* possibly_updated(this.needSave)
* new ArrayList(getSources#1) num objects <= 1
* new ConfigManager(setOption#2) num objects <= 1
* init'ed(new ConfigManager(setOption#2).channel)
* init'ed(new ConfigManager(setOption#2).ircd)
* init'ed(new ConfigManager(setOption#2).listeners)
* init'ed(new ConfigManager(setOption#2).network)
* ...
*
* Test Vectors:
* this.globalConfig: Inverse{null}, Addr_Set{null}
* value: Inverse{null}, Addr_Set{null}
* java.lang.String:equals(...)@376: {1}, {0}
* java.util.Map:get(...)@336: Addr_Set{null}, Inverse{null}
*/
350 synchronized (this) {
351 oldValue = getOption(domain, option);
352 LOGGER.finer(getName() + ": setting " + domain + "." + option + " to " + value);
353
354 if (myTarget.getType() == ConfigTarget.TYPE.GLOBAL) {
355 // If we're the global config, don't set useless settings that are
356 // covered by global defaults.
357
358 if (globalConfig == null) {
359 globalConfig = new ConfigManager("", "", "");
360 }
361
362 globalConfig.removeIdentity(this);
363
364 if (globalConfig.hasOption(domain, option)
365 && globalConfig.getOption(domain, option).equals(value)) {
366 if (oldValue == null) {
367 return;
368 } else {
369 unsetOption(domain, option);
370 return;
371 }
372 }
373 }
374 }
375
376 if ((oldValue == null && value != null)
377 || (oldValue != null && !oldValue.equals(value))) {
378 synchronized (this) {
379 file.getKeyDomain(domain).put(option, value);
380 needSave = true;
381 }
382
383 fireSettingChange(domain, option);
384 }
385 }
386
387 /**
388 * Sets the specified option in this identity to the specified value.
389 *
390 * @param domain The domain of the option
391 * @param option The name of the option
392 * @param value The new value for the option
393 */
394 public void setOption(final String domain, final String option,
395 final int value) {
/*
P/P * Method: void setOption(String, String, int)
*
* Preconditions:
* this.file != null
* this.myTarget != null
* init'ed(this.myTarget.type)
* (soft) init'ed(com.dmdirc.config.ConfigManager$1__static_init.new int[](ConfigManager$1__static_init#1)[...])
* (soft) init'ed(this.globalConfig)
* (soft) this.listeners != null
*
* Postconditions:
* java.lang.StringBuilder:toString(...)._tainted == 0
* this.globalConfig == One-of{old this.globalConfig, &new ConfigManager(setOption#2*)}
* init'ed(this.globalConfig)
* possibly_updated(this.needSave)
* new ArrayList(getSources#1) num objects <= 1
* new ConfigManager(setOption#2*) num objects == new ArrayList(getSources#1) num objects
* new MapList(ConfigManager#1) num objects == new ArrayList(getSources#1) num objects
* init'ed(new ConfigManager(setOption#2*).channel)
* init'ed(new ConfigManager(setOption#2*).ircd)
* init'ed(new ConfigManager(setOption#2*).listeners)
* ...
*/
396 setOption(domain, option, String.valueOf(value));
397 }
398
399 /**
400 * Sets the specified option in this identity to the specified value.
401 *
402 * @param domain The domain of the option
403 * @param option The name of the option
404 * @param value The new value for the option
405 */
406 public void setOption(final String domain, final String option,
407 final boolean value) {
/*
P/P * Method: void setOption(String, String, bool)
*
* Preconditions:
* this.file != null
* this.myTarget != null
* init'ed(this.myTarget.type)
* (soft) init'ed(com.dmdirc.config.ConfigManager$1__static_init.new int[](ConfigManager$1__static_init#1)[...])
* (soft) init'ed(this.globalConfig)
* (soft) this.listeners != null
*
* Postconditions:
* java.lang.StringBuilder:toString(...)._tainted == 0
* this.globalConfig == One-of{old this.globalConfig, &new ConfigManager(setOption#2*)}
* init'ed(this.globalConfig)
* possibly_updated(this.needSave)
* new ArrayList(getSources#1) num objects <= 1
* new ConfigManager(setOption#2*) num objects == new ArrayList(getSources#1) num objects
* new MapList(ConfigManager#1) num objects == new ArrayList(getSources#1) num objects
* init'ed(new ConfigManager(setOption#2*).channel)
* init'ed(new ConfigManager(setOption#2*).ircd)
* init'ed(new ConfigManager(setOption#2*).listeners)
* ...
*/
408 setOption(domain, option, String.valueOf(value));
409 }
410
411 /**
412 * Sets the specified option in this identity to the specified value.
413 *
414 * @param domain The domain of the option
415 * @param option The name of the option
416 * @param value The new value for the option
417 */
418 public void setOption(final String domain, final String option,
419 final List<String> value) {
/*
P/P * Method: void setOption(String, String, List)
*
* Preconditions:
* this.file != null
* this.myTarget != null
* init'ed(this.myTarget.type)
* value != null
* (soft) init'ed(com.dmdirc.config.ConfigManager$1__static_init.new int[](ConfigManager$1__static_init#1)[...])
* (soft) init'ed(this.globalConfig)
* (soft) this.listeners != null
*
* Postconditions:
* java.lang.StringBuilder:toString(...)._tainted == 0
* this.globalConfig == One-of{old this.globalConfig, &new ConfigManager(setOption#2*)}
* init'ed(this.globalConfig)
* possibly_updated(this.needSave)
* new ArrayList(getSources#1) num objects <= 1
* new ConfigManager(setOption#2*) num objects == new ArrayList(getSources#1) num objects
* new MapList(ConfigManager#1) num objects == new ArrayList(getSources#1) num objects
* init'ed(new ConfigManager(setOption#2*).channel)
* init'ed(new ConfigManager(setOption#2*).ircd)
* init'ed(new ConfigManager(setOption#2*).listeners)
* ...
*
* Test Vectors:
* java.lang.StringBuilder:length(...)@425: {-231..0}, {1..232-1}
* java.util.Iterator:hasNext(...)@421: {0}, {1}
*/
420 final StringBuilder temp = new StringBuilder();
421 for (String part : value) {
422 temp.append('\n');
423 temp.append(part);
424 }
425 setOption(domain, option, temp.length() > 0 ? temp.substring(1) : temp.toString());
426 }
427
428 /**
429 * Unsets a specified option.
430 *
431 * @param domain domain of the option
432 * @param option name of the option
433 */
434 public synchronized void unsetOption(final String domain, final String option) {
/*
P/P * Method: void unsetOption(String, String)
*
* Preconditions:
* this.file != null
*
* Presumptions:
* com.dmdirc.util.ConfigFile:getKeyDomain(...)@435 != null
*
* Postconditions:
* this.needSave == 1
*/
435 file.getKeyDomain(domain).remove(option);
436 needSave = true;
437
438 fireSettingChange(domain, option);
439 }
440
441 /**
442 * Returns the set of domains available in this identity.
443 *
444 * @since 0.6
445 * @return The set of domains used by this identity
446 */
447 public Set<String> getDomains() {
/*
P/P * Method: Set getDomains()
*
* Preconditions:
* this.file != null
*
* Presumptions:
* com.dmdirc.util.ConfigFile:getKeyDomains(...)@448 != null
*
* Postconditions:
* return_value == &new HashSet(getDomains#1)
* new HashSet(getDomains#1) num objects == 1
*/
448 return new HashSet<String>(file.getKeyDomains().keySet());
449 }
450
451 /**
452 * Retrieves a map of all options within the specified domain in this
453 * identity.
454 *
455 * @param domain The domain to retrieve
456 * @since 0.6
457 * @return A map of option names to values
458 */
459 public synchronized Map<String, String> getOptions(final String domain) {
/*
P/P * Method: Map getOptions(String)
*
* Preconditions:
* this.file != null
*
* Postconditions:
* return_value == &new HashMap(getOptions#1)
* new HashMap(getOptions#1) num objects == 1
*/
460 return new HashMap<String, String>(file.getKeyDomain(domain));
461 }
462
463 /**
464 * Saves this identity to disk if it has been updated.
465 */
466 public synchronized void save() {
/*
P/P * Method: void save()
*
* Preconditions:
* init'ed(this.needSave)
* this.file != null
* (soft) init'ed(com.dmdirc.config.ConfigManager$1__static_init.new int[](ConfigManager$1__static_init#1)[...])
* (soft) init'ed(this.globalConfig)
* (soft) this.listeners != null
* (soft) init'ed(this.myTarget.type)
*
* Presumptions:
* init'ed(com.dmdirc.logger.ErrorLevel.MEDIUM)
* com.dmdirc.util.ConfigFile:getKeyDomain(...)@500 != null
* com.dmdirc.util.ConfigFile:getKeyDomain(...)@507 != null
* com.dmdirc.util.ConfigFile:getKeyDomains(...)@488 != null
* java.util.Iterator:next(...)@482 != null
* ...
*
* Postconditions:
* java.lang.StringBuilder:toString(...)._tainted == 0
* this.globalConfig == One-of{old this.globalConfig, &new ConfigManager(save#3)}
* init'ed(this.globalConfig)
* init'ed(this.needSave)
* new ArrayList(getSources#1) num objects <= 1
* new ConfigManager(save#3) num objects <= 1
* init'ed(new ConfigManager(save#3).channel)
* init'ed(new ConfigManager(save#3).ircd)
* init'ed(new ConfigManager(save#3).listeners)
* init'ed(new ConfigManager(save#3).network)
* ...
*
* Test Vectors:
* this.globalConfig: Inverse{null}, Addr_Set{null}
* this.needSave: {0}, {1}
* this.myTarget: Addr_Set{null}, Inverse{null}
* com.dmdirc.util.ConfigFile:isKeyDomain(...)@506: {0}, {1}
* com.dmdirc.util.ConfigFile:isWritable(...)@469: {0}, {1}
* java.lang.String:equals(...)@496: {0}, {1}
* java.util.Iterator:hasNext(...)@482: {0}, {1}
* java.util.Iterator:hasNext(...)@488: {0}, {1}
* java.util.Iterator:hasNext(...)@492: {0}, {1}
* java.util.logging.Logger:isLoggable(...)@481: {0}, {1}
*/
467 LOGGER.fine(getName() + ": save(); needsave = " + needSave);
468
469 if (needSave && file != null && file.isWritable()) {
470 if (myTarget != null && myTarget.getType() == ConfigTarget.TYPE.GLOBAL) {
471 LOGGER.finer(getName() + ": I'm a global config");
472 // If we're the global config, unset useless settings that are
473 // covered by global defaults.
474
475 if (globalConfig == null) {
476 globalConfig = new ConfigManager("", "", "");
477 }
478
479 globalConfig.removeIdentity(this);
480
481 if (LOGGER.isLoggable(Level.FINEST)) {
482 for (Identity source : globalConfig.getSources()) {
483 LOGGER.finest(getName() + ": source: " + source.getName());
484 }
485 }
486
487 for (Map.Entry<String, Map<String, String>> entry
488 : file.getKeyDomains().entrySet()) {
489 final String domain = entry.getKey();
490
491 for (Map.Entry<String, String> subentry :
492 new HashSet<Map.Entry<String, String>>(entry.getValue().entrySet())) {
493 final String key = subentry.getKey();
494 final String value = subentry.getValue();
495
496 if (globalConfig.hasOption(domain, key) &&
497 globalConfig.getOption(domain, key).equals(value)) {
498 LOGGER.finest(getName() + ": found superfluous setting: "
499 + domain + "." + key + " (= " + value + ")");
500 file.getKeyDomain(domain).remove(key);
501 }
502 }
503 }
504 }
505
506 if (file.isKeyDomain("temp")) {
507 file.getKeyDomain("temp").clear();
508 }
509
510 try {
511 file.write();
512
513 needSave = false;
514 } catch (IOException ex) {
515 Logger.userError(ErrorLevel.MEDIUM,
516 "Unable to save identity file: " + ex.getMessage());
517 }
518 }
519 }
520
521 /**
522 * Deletes this identity from disk.
523 */
524 public synchronized void delete() {
/*
P/P * Method: void delete()
*
* Preconditions:
* (soft) this.file != null
* (soft) this.listeners != null
*/
525 if (file != null) {
526 file.delete();
527 }
528
529 IdentityManager.removeIdentity(this);
530 }
531
532 /**
533 * Retrieves this identity's target.
534 *
535 * @return The target of this identity
536 */
537 public ConfigTarget getTarget() {
/*
P/P * Method: ConfigTarget getTarget()
*
* Postconditions:
* return_value == this.myTarget
* init'ed(return_value)
*/
538 return myTarget;
539 }
540
541 /**
542 * Retrieve this identity's ConfigFile.
543 *
544 * @return The ConfigFile object used by this identity
545 * @deprecated Direct access should be avoided to prevent synchronisation
546 * issues
547 */
548 @Deprecated
549 public ConfigFile getFile() {
/*
P/P * Method: ConfigFile getFile()
*
* Postconditions:
* return_value == this.file
* init'ed(return_value)
*/
550 return file;
551 }
552
553 /**
554 * Adds a new config change listener for this identity.
555 *
556 * @param listener The listener to be added
557 */
558 public void addListener(final ConfigChangeListener listener) {
/*
P/P * Method: void addListener(ConfigChangeListener)
*
* Preconditions:
* this.listeners != null
*/
559 listeners.add(listener);
560 }
561
562 /**
563 * Removes the specific config change listener from this identity.
564 *
565 * @param listener The listener to be removed
566 */
567 public void removeListener(final ConfigChangeListener listener) {
/*
P/P * Method: void removeListener(ConfigChangeListener)
*
* Preconditions:
* this.listeners != null
*/
568 listeners.remove(listener);
569 }
570
571 /**
572 * Returns a string representation of this object (its name).
573 *
574 * @return A string representation of this object
575 */
576 @Override
577 public String toString() {
/*
P/P * Method: String toString()
*
* Preconditions:
* this.file != null
*
* Postconditions:
* init'ed(return_value)
*/
578 return getName();
579 }
580
581 /** {@inheritDoc} */
582 @Override
583 public int hashCode() {
/*
P/P * Method: int hashCode()
*
* Preconditions:
* this.file != null
* this.myTarget != null
* this.myTarget.data != null
* this.myTarget.type != null
*
* Presumptions:
* com.dmdirc.config.ConfigTarget_TYPE:ordinal(...)@206 + java.lang.String:hashCode(...)@206 + java.lang.String:hashCode(...)@584 in {-231..232-1}
*
* Postconditions:
* init'ed(return_value)
*/
584 return getName().hashCode() + getTarget().hashCode();
585 }
586
587 /** {@inheritDoc} */
588 @Override
589 public boolean equals(final Object obj) {
/*
P/P * Method: bool equals(Object)
*
* Preconditions:
* (soft) obj.file != null
* (soft) this.file != null
*
* Postconditions:
* init'ed(return_value)
*
* Test Vectors:
* this.myTarget == obj.myTarget: {0}, {1}
* java.lang.String:equals(...)@590: {0}, {1}
*/
590 if (obj instanceof Identity
591 && getName().equals(((Identity) obj).getName())
592 && getTarget() == ((Identity) obj).getTarget()) {
593 return true;
594 }
595 return false;
596 }
597
598 /**
599 * Compares this identity to another config source to determine which
600 * is more specific.
601 *
602 * @param target The Identity to compare to
603 * @return -1 if this config source is less specific, 0 if they're equal,
604 * +1 if this config is more specific.
605 */
606 @Override
607 public int compareTo(final Identity target) {
/*
P/P * Method: int compareTo(Identity)
*
* Preconditions:
* target != null
* target.myTarget != null
* target.myTarget.type != null
* this.myTarget != null
* init'ed(this.myTarget.type)
* (soft) init'ed(target.myTarget.order)
* (soft) init'ed(this.myTarget.order)
* (soft) this.myTarget.order - target.myTarget.order in {-231..232-1}
*
* Postconditions:
* init'ed(return_value)
*/
608 return target.getTarget().compareTo(myTarget);
609 }
610
611 /**
612 * Creates a new identity containing the specified properties.
613 *
614 * @param settings The settings to populate the identity with
615 * @return A new identity containing the specified properties
616 * @throws IOException If the file cannot be created
617 * @throws InvalidIdentityFileException If the settings are invalid
618 * @since 0.6.3m1
619 */
620 protected static Identity createIdentity(final Map<String, Map<String, String>> settings)
621 throws IOException, InvalidIdentityFileException {
/*
P/P * Method: Identity createIdentity(Map)
*
* Preconditions:
* settings != null
* (soft) init'ed(com.dmdirc.config.ConfigManager$1__static_init.new int[](ConfigManager$1__static_init#1)[...])
*
* Presumptions:
* java.lang.String:isEmpty(...)@622 == 0
* java.util.Iterator:next(...)@640 != null
* java.util.Map:containsKey(...)@622 == 1
* java.util.Map:entrySet(...)@640 != null
* java.util.Map:get(...)@622 != null
* ...
*
* Postconditions:
* java.lang.StringBuilder:toString(...)._tainted == 0
* new ArrayList(getSources#1) num objects == 0
* new ConfigManager(setOption#2) num objects == 0
* new MapList(ConfigManager#1) num objects == 0
* return_value == &new Identity(createIdentity#7)
* new ConfigFile(Identity#2) num objects == 1
* new ConfigTarget(getTarget#1) num objects == 1
* new Identity(createIdentity#7) num objects == 1
* new WeakList(Identity#1) num objects == 1
* init'ed(new ConfigManager(setOption#2).channel)
* ...
*
* Test Vectors:
* java.io.File:exists(...)@634: {0}, {1}
* java.util.Iterator:hasNext(...)@640: {0}, {1}
*/
622 if (!settings.containsKey(DOMAIN) || !settings.get(DOMAIN).containsKey("name")
623 || settings.get(DOMAIN).get("name").isEmpty()) {
624 throw new InvalidIdentityFileException("identity.name is not set");
625 }
626
627 final String fs = System.getProperty("file.separator");
628 final String location = Main.getConfigDir() + "identities" + fs;
629 final String name = settings.get(DOMAIN).get("name").replaceAll(ILLEGAL_CHARS, "_");
630
631 File file = new File(location + name);
632 int attempt = 0;
633
634 while (file.exists()) {
635 file = new File(location + name + "-" + ++attempt);
636 }
637
638 final ConfigFile configFile = new ConfigFile(file);
639
640 for (Map.Entry<String, Map<String, String>> entry : settings.entrySet()) {
641 configFile.addDomain(entry.getKey(), entry.getValue());
642 }
643
644 configFile.write();
645
646 final Identity identity = new Identity(file, false);
647 IdentityManager.addIdentity(identity);
648
649 return identity;
650 }
651
652 /**
653 * Generates an empty identity for the specified target.
654 *
655 * @param target The target for the new identity
656 * @return An empty identity for the specified target
657 * @throws IOException if the file can't be written
658 * @see #createIdentity(java.util.Map)
659 */
660 public static Identity buildIdentity(final ConfigTarget target)
661 throws IOException {
/*
P/P * Method: Identity buildIdentity(ConfigTarget)
*
* Preconditions:
* target != null
* init'ed(target.data)
* target.type != null
* (soft) init'ed(com.dmdirc.config.ConfigTarget$1__static_init.new int[](ConfigTarget$1__static_init#1)[...])
*
* Presumptions:
* init'ed(com.dmdirc.logger.ErrorLevel.MEDIUM)
* java.util.Map:get(...)@665 != null
* java.util.Map:get(...)@666 != null
*
* Postconditions:
* java.lang.StringBuilder:toString(...)._tainted == 0
* init'ed(return_value)
* new ConfigFile(Identity#2) num objects == undefined
* new ConfigFile(Identity#2) num objects == 0, if init'ed
* new ConfigManager(setOption#2) num objects == new ConfigFile(Identity#2) num objects
* new ConfigTarget(getTarget#1) num objects == new ConfigFile(Identity#2) num objects
* new ConfigTarget(getTarget#1).order == new ConfigFile(Identity#2) num objects
* new Identity(createIdentity#7) num objects == new ConfigFile(Identity#2) num objects
* new Identity(createIdentity#7).needSave == new ConfigFile(Identity#2) num objects
* new MapList(ConfigManager#1) num objects == new ConfigFile(Identity#2) num objects
* ...
*/
662 final Map<String, Map<String, String>> settings
663 = new HashMap<String, Map<String, String>>();
664 settings.put(DOMAIN, new HashMap<String, String>(2));
665 settings.get(DOMAIN).put("name", target.getData());
666 settings.get(DOMAIN).put(target.getTypeName(), target.getData());
667
668 try {
669 return createIdentity(settings);
670 } catch (InvalidIdentityFileException ex) {
671 Logger.appError(ErrorLevel.MEDIUM, "Unable to create identity", ex);
672 return null;
673 }
674 }
675
676 /**
677 * Generates an empty profile witht he specified name. Note the name is used
678 * as a file name, so should be sanitised.
679 *
680 * @param name The name of the profile to create
681 * @return A new profile with the specified name
682 * @throws IOException If the file can't be written
683 * @see #createIdentity(java.util.Map)
684 */
685 public static Identity buildProfile(final String name) throws IOException {
/*
P/P * Method: Identity buildProfile(String)
*
* Presumptions:
* init'ed(com.dmdirc.logger.ErrorLevel.MEDIUM)
* java.lang.System:getProperty(...)@691 != null
* java.util.Map:get(...)@693 != null
* java.util.Map:get(...)@694 != null
* java.util.Map:get(...)@695 != null
*
* Postconditions:
* java.lang.StringBuilder:toString(...)._tainted == 0
* init'ed(return_value)
* new ConfigFile(Identity#2) num objects == undefined
* new ConfigFile(Identity#2) num objects == 0, if init'ed
* new ConfigManager(setOption#2) num objects == new ConfigFile(Identity#2) num objects
* new ConfigTarget(getTarget#1) num objects == new ConfigFile(Identity#2) num objects
* new ConfigTarget(getTarget#1).order == new ConfigFile(Identity#2) num objects
* new Identity(createIdentity#7) num objects == new ConfigFile(Identity#2) num objects
* new Identity(createIdentity#7).needSave == new ConfigFile(Identity#2) num objects
* new MapList(ConfigManager#1) num objects == new ConfigFile(Identity#2) num objects
* ...
*/
686 final Map<String, Map<String, String>> settings
687 = new HashMap<String, Map<String, String>>();
688 settings.put(DOMAIN, new HashMap<String, String>(1));
689 settings.put(PROFILE_DOMAIN, new HashMap<String, String>(2));
690
691 final String nick = System.getProperty("user.name").replace(' ', '_');
692
693 settings.get(DOMAIN).put("name", name);
694 settings.get(PROFILE_DOMAIN).put("nicknames", nick);
695 settings.get(PROFILE_DOMAIN).put("realname", nick);
696
697 try {
698 return createIdentity(settings);
699 } catch (InvalidIdentityFileException ex) {
700 Logger.appError(ErrorLevel.MEDIUM, "Unable to create identity", ex);
701 return null;
702 }
703 }
704
705 }
SofCheck Inspector Build Version : 2.17854
| Identity.java |
2009-Jun-25 01:54:24 |
| Identity.class |
2009-Sep-02 17:04:11 |