您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

Identity.java 17KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514
  1. /*
  2. * Copyright (c) 2006-2007 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. package com.dmdirc.config;
  23. import com.dmdirc.Main;
  24. import com.dmdirc.Precondition;
  25. import com.dmdirc.logger.ErrorLevel;
  26. import com.dmdirc.logger.Logger;
  27. import com.dmdirc.util.WeakList;
  28. import java.io.File;
  29. import java.io.FileInputStream;
  30. import java.io.FileNotFoundException;
  31. import java.io.FileOutputStream;
  32. import java.io.FileWriter;
  33. import java.io.IOException;
  34. import java.io.InputStream;
  35. import java.io.OutputStream;
  36. import java.io.Serializable;
  37. import java.net.MalformedURLException;
  38. import java.util.ArrayList;
  39. import java.util.HashSet;
  40. import java.util.List;
  41. import java.util.Properties;
  42. /**
  43. * An identity is a group of settings that are applied to a connection, server,
  44. * network or channel. Identities may be automatically applied in certain
  45. * cases, or the user may manually apply them.
  46. * <p>
  47. * Note: this class has a natural ordering that is inconsistent with equals.
  48. *
  49. * @author chris
  50. */
  51. public class Identity extends ConfigSource implements Serializable,
  52. Comparable<Identity> {
  53. /**
  54. * A version number for this class. It should be changed whenever the class
  55. * structure is changed (or anything else that would prevent serialized
  56. * objects being unserialized with the new class).
  57. */
  58. private static final long serialVersionUID = 1;
  59. /** The domain used for identity settings. */
  60. private static final String DOMAIN = "identity".intern();
  61. /** The target for this identity. */
  62. protected final ConfigTarget myTarget;
  63. /** The configuration details for this identity. */
  64. protected final Properties properties;
  65. /** The config change listeners for this source. */
  66. protected final List<ConfigChangeListener> listeners
  67. = new WeakList<ConfigChangeListener>();
  68. /** The file that this identity is read from. */
  69. protected File file;
  70. /** Whether this identity needs to be saved. */
  71. protected boolean needSave;
  72. /**
  73. * Creates a new identity with the specified properties. Saving is not
  74. * supported using this method (i.e., it should only be used for defaults).
  75. *
  76. * @param properties The properties to use for this identity
  77. */
  78. public Identity(final Properties properties) {
  79. this(properties, null);
  80. }
  81. /**
  82. * Creates a new identity with the specified properties and targets#.
  83. * Saving is not supported using this method (i.e., it should only be used
  84. * for defaults).
  85. *
  86. * @param properties The properties to use for this identity
  87. * @param target The target of this identity
  88. */
  89. @Precondition("The specified Properties is not null")
  90. public Identity(final Properties properties, final ConfigTarget target) {
  91. assert(properties != null);
  92. this.properties = properties;
  93. if (target == null) {
  94. myTarget = new ConfigTarget();
  95. myTarget.setGlobalDefault();
  96. } else {
  97. myTarget = target;
  98. }
  99. }
  100. /**
  101. * Creates a new instance of Identity.
  102. *
  103. * @param file The file to load this identity from
  104. * @param forceDefault Whether to force this identity to be loaded as default
  105. * identity or not
  106. * @throws InvalidIdentityFileException Missing required properties
  107. * @throws IOException Input/output exception
  108. */
  109. public Identity(final File file, final boolean forceDefault) throws IOException,
  110. InvalidIdentityFileException {
  111. this(new FileInputStream(file), forceDefault);
  112. this.file = file;
  113. }
  114. /**
  115. * Creates a new read-only identity.
  116. *
  117. * @param stream The input stream to read the identity from
  118. * @param forceDefault Whether to force this identity to be loaded as default
  119. * identity or not
  120. * @throws InvalidIdentityFileException Missing required properties
  121. * @throws IOException Input/output exception
  122. */
  123. public Identity(final InputStream stream, final boolean forceDefault) throws IOException,
  124. InvalidIdentityFileException {
  125. properties = new Properties();
  126. properties.load(stream);
  127. myTarget = new ConfigTarget();
  128. if (!properties.containsKey(DOMAIN + ".name") && !forceDefault) {
  129. throw new InvalidIdentityFileException("No name specified");
  130. }
  131. if (hasOption(DOMAIN, "ircd")) {
  132. myTarget.setIrcd(getOption(DOMAIN, "ircd"));
  133. } else if (hasOption(DOMAIN, "network")) {
  134. myTarget.setNetwork(getOption(DOMAIN, "network"));
  135. } else if (hasOption(DOMAIN, "server")) {
  136. myTarget.setServer(getOption(DOMAIN, "server"));
  137. } else if (hasOption(DOMAIN, "channel")) {
  138. myTarget.setChannel(getOption(DOMAIN, "channel"));
  139. } else if (hasOption(DOMAIN, "globaldefault")) {
  140. myTarget.setGlobalDefault();
  141. } else if (forceDefault && !isProfile()) {
  142. myTarget.setGlobal();
  143. } else if (isProfile()) {
  144. myTarget.setProfile();
  145. } else {
  146. throw new InvalidIdentityFileException("No target and no profile");
  147. }
  148. stream.close();
  149. }
  150. /**
  151. * Returns the properties object belonging to this identity.
  152. *
  153. * @return This identity's property object
  154. */
  155. public Properties getProperties() {
  156. return properties;
  157. }
  158. /**
  159. * Returns the name of this identity.
  160. *
  161. * @return The name of this identity
  162. */
  163. public String getName() {
  164. if (hasOption(DOMAIN, "name")) {
  165. return getOption(DOMAIN, "name");
  166. } else {
  167. return "Unnamed";
  168. }
  169. }
  170. /**
  171. * Determines whether this identity can be used as a profile when
  172. * connecting to a server. Profiles are identities that can supply
  173. * nick, ident, real name, etc.
  174. *
  175. * @return True iff this identity can be used as a profile
  176. */
  177. public boolean isProfile() {
  178. return hasOption("profile", "nickname") && hasOption("profile", "realname");
  179. }
  180. /** {@inheritDoc} */
  181. @Override
  182. public boolean hasOption(final String domain, final String option) {
  183. return properties.containsKey(domain + "." + option);
  184. }
  185. /** {@inheritDoc} */
  186. @Override
  187. public String getOption(final String domain, final String option) {
  188. return properties.getProperty(domain + "." + option);
  189. }
  190. /**
  191. * Sets the specified option in this identity to the specified value.
  192. *
  193. * @param domain The domain of the option
  194. * @param option The name of the option
  195. * @param value The new value for the option
  196. */
  197. public void setOption(final String domain, final String option,
  198. final String value) {
  199. final String oldValue = getOption(domain, option);
  200. if ((oldValue == null && value != null)
  201. || (oldValue != null && !oldValue.equals(value))) {
  202. properties.setProperty(domain + "." + option, value);
  203. needSave = true;
  204. for (ConfigChangeListener listener :
  205. new ArrayList<ConfigChangeListener>(listeners)) {
  206. listener.configChanged(domain, option);
  207. }
  208. }
  209. }
  210. /**
  211. * Sets the specified option in this identity to the specified value.
  212. *
  213. * @param domain The domain of the option
  214. * @param option The name of the option
  215. * @param value The new value for the option
  216. */
  217. public void setOption(final String domain, final String option,
  218. final int value) {
  219. setOption(domain, option, String.valueOf(value));
  220. }
  221. /**
  222. * Sets the specified option in this identity to the specified value.
  223. *
  224. * @param domain The domain of the option
  225. * @param option The name of the option
  226. * @param value The new value for the option
  227. */
  228. public void setOption(final String domain, final String option,
  229. final boolean value) {
  230. setOption(domain, option, String.valueOf(value));
  231. }
  232. /**
  233. * Sets the specified option in this identity to the specified value.
  234. *
  235. * @param domain The domain of the option
  236. * @param option The name of the option
  237. * @param value The new value for the option
  238. */
  239. public void setOption(final String domain, final String option,
  240. final List<String> value) {
  241. final StringBuilder temp = new StringBuilder();
  242. for (String part : value) {
  243. temp.append('\n');
  244. temp.append(part);
  245. }
  246. setOption(domain, option, temp.length() > 0 ? temp.substring(1) : temp.toString());
  247. }
  248. /**
  249. * Unsets a specified option.
  250. *
  251. * @param domain domain of the option
  252. * @param option name of the option
  253. */
  254. public void unsetOption(final String domain, final String option) {
  255. properties.remove(domain + "." + option);
  256. needSave = true;
  257. for (ConfigChangeListener listener : new ArrayList<ConfigChangeListener>(listeners)) {
  258. listener.configChanged(domain, option);
  259. }
  260. }
  261. /**
  262. * Returns a list of options avaiable in this identity.
  263. *
  264. * @return Option list
  265. */
  266. public List<String> getOptions() {
  267. final List<String> res = new ArrayList<String>();
  268. for (Object key : properties.keySet()) {
  269. res.add((String) key);
  270. }
  271. return res;
  272. }
  273. /**
  274. * Saves this identity to disk if it has been updated.
  275. */
  276. public void save() {
  277. if (needSave && file != null) {
  278. if (myTarget.getType() == ConfigTarget.TYPE.GLOBAL) {
  279. // If we're the global config, unset useless settings that are
  280. // covered by global defaults.
  281. final ConfigManager globalConfig = new ConfigManager("", "", "");
  282. globalConfig.removeIdentity(this);
  283. for (Object key : new HashSet<Object>(properties.keySet())) {
  284. final String skey = (String) key;
  285. final String domain = skey.substring(0, skey.indexOf('.'));
  286. final String option = skey.substring(1 + skey.indexOf('.'));
  287. final String global = globalConfig.getOption(domain, option, null);
  288. if (properties.getProperty((String) key).equals(global)
  289. || "temp".equals(domain)) {
  290. properties.remove(key);
  291. }
  292. }
  293. }
  294. try {
  295. final OutputStream stream = new FileOutputStream(file);
  296. properties.store(stream, null);
  297. stream.close();
  298. needSave = false;
  299. } catch (FileNotFoundException ex) {
  300. Logger.userError(ErrorLevel.MEDIUM,
  301. "Unable to save identity file: " + ex.getMessage());
  302. } catch (IOException ex) {
  303. Logger.userError(ErrorLevel.MEDIUM,
  304. "Unable to save identity file: " + ex.getMessage());
  305. }
  306. }
  307. }
  308. /**
  309. * Deletes this identity from disk.
  310. */
  311. public void delete() {
  312. if (file != null) {
  313. file.delete();
  314. }
  315. IdentityManager.removeIdentity(this);
  316. }
  317. /**
  318. * Retrieves this identity's target.
  319. *
  320. * @return The target of this identity
  321. */
  322. public ConfigTarget getTarget() {
  323. return myTarget;
  324. }
  325. /**
  326. * Adds a new config change listener for this identity.
  327. *
  328. * @param listener The listener to be added
  329. */
  330. public void addListener(final ConfigChangeListener listener) {
  331. listeners.add(listener);
  332. }
  333. /**
  334. * Removes the specific config change listener from this identity.
  335. *
  336. * @param listener The listener to be removed
  337. */
  338. public void removeListener(final ConfigChangeListener listener) {
  339. listeners.remove(listener);
  340. }
  341. /**
  342. * Returns a string representation of this object (its name).
  343. *
  344. * @return A string representation of this object
  345. */
  346. @Override
  347. public String toString() {
  348. return getName();
  349. }
  350. /** {@inheritDoc} */
  351. @Override
  352. public int hashCode() {
  353. return getName().hashCode() + getTarget().hashCode();
  354. }
  355. /** {@inheritDoc} */
  356. @Override
  357. public boolean equals(final Object obj) {
  358. if (obj instanceof Identity
  359. && getName().equals(((Identity) obj).getName())
  360. && getTarget() == ((Identity) obj).getTarget()) {
  361. return true;
  362. }
  363. return false;
  364. }
  365. /**
  366. * Compares this identity to another config source to determine which
  367. * is more specific.
  368. *
  369. * @param target The Identity to compare to
  370. * @return -1 if this config source is less specific, 0 if they're equal,
  371. * +1 if this config is more specific.
  372. */
  373. @Override
  374. public int compareTo(final Identity target) {
  375. return target.getTarget().compareTo(myTarget);
  376. }
  377. /**
  378. * Creates a new identity containing the specified properties.
  379. *
  380. * @param properties The properties to be included in the identity
  381. * @return A new identity containing the specified properties
  382. */
  383. protected static Identity createIdentity(final Properties properties) {
  384. if (!properties.containsKey(DOMAIN + ".name")
  385. || properties.getProperty(DOMAIN + ".name").isEmpty()) {
  386. Logger.appError(ErrorLevel.LOW, "createIdentity caleld with invalid identity",
  387. new InvalidIdentityFileException("identity.name is not set"));
  388. return null;
  389. }
  390. final String fs = System.getProperty("file.separator");
  391. final String location = Main.getConfigDir() + "identities" + fs;
  392. final String name = properties.getProperty(DOMAIN + ".name");
  393. final File file = new File(location + name);
  394. if (!file.exists()) {
  395. final FileWriter writer;
  396. try {
  397. writer = new FileWriter(location + name);
  398. properties.store(writer, "");
  399. writer.close();
  400. } catch (IOException ex) {
  401. Logger.userError(ErrorLevel.MEDIUM,
  402. "Unable to write new identity file: " + ex.getMessage());
  403. return null;
  404. }
  405. }
  406. try {
  407. final Identity identity = new Identity(file, false);
  408. IdentityManager.addIdentity(identity);
  409. return identity;
  410. } catch (MalformedURLException ex) {
  411. Logger.userError(ErrorLevel.MEDIUM,
  412. "Unable to open new identity file: " + ex.getMessage());
  413. return null;
  414. } catch (InvalidIdentityFileException ex) {
  415. Logger.userError(ErrorLevel.MEDIUM,
  416. "Unable to open new identity file: " + ex.getMessage());
  417. return null;
  418. } catch (IOException ex) {
  419. Logger.userError(ErrorLevel.MEDIUM,
  420. "Unable to open new identity file: " + ex.getMessage());
  421. return null;
  422. }
  423. }
  424. /**
  425. * Generates an empty identity for the specified target.
  426. *
  427. * @param target The target for the new identity
  428. * @return An empty identity for the specified target
  429. */
  430. public static Identity buildIdentity(final ConfigTarget target) {
  431. final Properties properties = new Properties();
  432. properties.setProperty(DOMAIN + ".name", target.getData());
  433. properties.setProperty(DOMAIN + "." + target.getTypeName(), target.getData());
  434. return createIdentity(properties);
  435. }
  436. /**
  437. * Generates an empty profile witht he specified name. Note the name is used
  438. * as a file name, so should be sanitised.
  439. *
  440. * @param name The name of the profile to create
  441. * @return A new profile with the specified name
  442. */
  443. public static Identity buildProfile(final String name) {
  444. final Properties properties = new Properties();
  445. properties.setProperty(DOMAIN + ".name", name);
  446. properties.setProperty("profile.nickname", "DMDircUser");
  447. properties.setProperty("profile.realname", "DMDircUser");
  448. return createIdentity(properties);
  449. }
  450. }