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 == &amp;new ConfigFile(Identity#2)
                  *    init'ed(this.globalConfig)
                  *    this.listeners == &amp;new WeakList(Identity#1)
                  *    this.myTarget == &amp;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 == &amp;new ConfigFile(Identity#2)
                  *    init'ed(this.globalConfig)
                  *    this.listeners == &amp;new WeakList(Identity#1)
                  *    this.myTarget == &amp;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,&amp;new ConfigManager(setOption#2)}
                  *    this.listeners == &amp;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 == &amp;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{&amp;com.dmdirc.config.ConfigTarget$TYPE__static_init.new ConfigTarget$TYPE(ConfigTarget$TYPE__static_init#2),&amp;com.dmdirc.config.ConfigTarget$TYPE__static_init.new ConfigTarget$TYPE(ConfigTarget$TYPE__static_init#4),&amp;com.dmdirc.config.ConfigTarget$TYPE__static_init.new ConfigTarget$TYPE(ConfigTarget$TYPE__static_init#1),&amp;com.dmdirc.config.ConfigTarget$TYPE__static_init.new ConfigTarget$TYPE(ConfigTarget$TYPE__static_init#8),&amp;com.dmdirc.config.ConfigTarget$TYPE__static_init.new ConfigTarget$TYPE(ConfigTarget$TYPE__static_init#7),&amp;com.dmdirc.config.ConfigTarget$TYPE__static_init.new ConfigTarget$TYPE(ConfigTarget$TYPE__static_init#6),&amp;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, &amp;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, &amp;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, &amp;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, &amp;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, &amp;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 == &amp;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 == &amp;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, &amp;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 == &amp;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