/**
  * Adds an object to the contents of this AbstractReferenceManufacturer. This is used in
  * conditions where this AbstractReferenceManufacturer was not used to construct the object.
  *
  * <p>Implementation Note: There are various situations where this "external construction" may
  * happen - the primary one being loading of "game mode" information like CDOMStat objects.
  *
  * @param item The object to be imported into this AbstractReferenceManufacturer
  * @param key The identifier of the object to be imported into this AbstractReferenceManufacturer
  * @throws IllegalArgumentException if the given object is not of the Class that this
  *     AbstractReferenceManufacturer constructs and references
  */
 @Override
 public void addObject(T item, String key) {
   if (!factory.isMember(item)) {
     throw new IllegalArgumentException(
         "Attempted to register a "
             + item.getClass().getName()
             + " in "
             + factory.getReferenceDescription());
   }
   T current = active.get(key);
   if (current == null) {
     active.put(key, item);
   } else {
     duplicates.addToListFor(new CaseInsensitiveString(key), item);
   }
 }
 /**
  * Remove the given object from this AbstractReferenceManufacturer. Returns true if the object was
  * removed from this AbstractReferenceManufacturer; false otherwise.
  *
  * @param item The object to be removed from this AbstractReferenceManufacturer.
  * @return true if the object was removed from this AbstractReferenceManufacturer; false
  *     otherwise.
  */
 @Override
 public boolean forgetObject(T item) {
   if (!factory.isMember(item)) {
     throw new IllegalArgumentException(
         "Object to be forgotten does not match Class " + "of this AbstractReferenceManufacturer");
   }
   String key = active.getKeyFor(item);
   if (key == null) {
     /*
      * TODO This is a bug - the key name is not necessarily loaded into
      * the object, it may have been consumed by the object context... :P
      */
     CaseInsensitiveString ocik = new CaseInsensitiveString(item.getKeyName());
     duplicates.removeFromListFor(ocik, item);
   } else {
     CaseInsensitiveString ocik = new CaseInsensitiveString(key);
     List<T> list = duplicates.getListFor(ocik);
     if (list == null) {
       // No replacement
       active.remove(key);
     } else {
       T newActive = duplicates.getElementInList(ocik, 0);
       duplicates.removeFromListFor(ocik, newActive);
       active.put(key, newActive);
     }
   }
   return true;
 }
 /**
  * Gets the object represented by the given identifier. Will return null if an object with the
  * given identifier is not present in this AbstractReferenceManufacturer.
  *
  * <p>Note that this is testing *object* presence. This will not return an object if a reference
  * for the given identifier has been requested; it will only return true if an object with the
  * given identifier has actually been constructed by or imported into this
  * AbstractReferenceManufacturer.
  *
  * @param key identifier of the object to be returned
  * @return The object stored in this AbstractReferenceManufacturer with the given identifier, or
  *     null if this AbstractReferenceManufacturer does not contain an object with the given
  *     identifier.
  */
 @Override
 public T getObject(String key) {
   T po = active.get(key);
   if (po != null) {
     List<T> list = duplicates.getListFor(new CaseInsensitiveString(key));
     if ((list != null) && !list.isEmpty()) {
       Logging.errorPrint(
           "Reference to Constructed "
               + factory.getReferenceDescription()
               + " "
               + key
               + " is ambiguous");
       StringBuilder sb = new StringBuilder(1000);
       sb.append("Locations: ");
       sb.append(po.getSourceURI());
       for (T dupe : list) {
         sb.append(", ");
         sb.append(dupe.getSourceURI());
       }
       Logging.errorPrint(sb.toString());
     }
     return po;
   }
   return null;
 }
 /**
  * Returns a CDOMGroupRef for the given Class or Class/Context provided by this
  * AbstractReferenceManufacturer.
  *
  * @return A CDOMGroupRef which is intended to contain all the objects of the Class or
  *     Class/Context this AbstractReferenceManufacturer represents.
  */
 @Override
 public CDOMGroupRef<T> getAllReference() {
   if (allRef == null) {
     allRef = factory.getAllReference();
   }
   return allRef;
 }
 private boolean resolveGroupReferences() {
   for (T obj : getAllObjects()) {
     if (allRef != null) {
       allRef.addResolution(obj);
     }
     for (Map.Entry<FixedStringList, WeakReference<CDOMGroupRef<T>>> me :
         typeReferences.entrySet()) {
       CDOMGroupRef<T> trt = me.getValue().get();
       if (trt != null) {
         boolean typeOkay = true;
         for (String type : me.getKey()) {
           if (!obj.isType(type)) {
             typeOkay = false;
             break;
           }
         }
         if (typeOkay) {
           trt.addResolution(obj);
         }
       }
     }
   }
   if (allRef != null && allRef.getObjectCount() == 0) {
     Logging.errorPrint(
         "Error: No "
             + factory.getReferenceDescription()
             + " objects were loaded but were referred to in the data");
     fireUnconstuctedEvent(allRef);
     return false;
   }
   return true;
 }
 /**
  * Constructs a new Loadable of the Class or Class/Category represented by this
  * AbstractReferenceManufacturer
  *
  * <p>This should remain protected (vs. public) as it is for "internal use only"; it serves as a
  * convenience method to wrap the .newInstance call and the possible Exceptions. Other classes
  * should use constructObject(String)
  *
  * @param key The identifier of the Loadable to be constructed
  * @return The new Loadable of the Class or Class/Category represented by this
  *     AbstractReferenceManufacturer
  * @throws IllegalArgumentException if the given identifier is null or empty (length is zero)
  */
 @Override
 public T buildObject(String key) {
   if (key == null || key.equals("")) {
     throw new IllegalArgumentException("Cannot build empty name");
   }
   T obj = factory.newInstance();
   obj.setName(key);
   return obj;
 }
 private boolean resolvePrimitiveReferences(UnconstructedValidator validator) {
   boolean resolutionSuccessful = true;
   for (Entry<String, WeakReference<CDOMSingleRef<T>>> me1 : referenced.entrySet()) {
     CDOMSingleRef<T> value = me1.getValue().get();
     if (value != null) {
       resolutionSuccessful &= factory.resolve(this, me1.getKey(), value, validator);
     }
   }
   return resolutionSuccessful;
 }
 @Override
 public ObjectContainer<T> convertObjectContainer(String typeStr) {
   /*
    * TODO Really needs to do a Primitive search... :/
    */
   if (typeStr.startsWith("TYPE=") || typeStr.startsWith("TYPE.")) {
     return factory.getTypeReference(typeStr.substring(5).split("\\."));
   }
   throw new IllegalArgumentException("ObjectContainer does not support: " + typeStr);
 }
 private boolean validateDuplicates() {
   boolean returnGood = true;
   for (CaseInsensitiveString second : duplicates.getKeySet()) {
     List<T> list = duplicates.getListFor(second);
     T good = active.get(second.toString());
     /*
      * CONSIDER Should get CDOMObject reference out of here :(
      */
     if (good instanceof CDOMObject) {
       CDOMObject cdo = (CDOMObject) good;
       for (int i = 0; i < list.size(); i++) {
         T dupe = list.get(i);
         if (cdo.isCDOMEqual((CDOMObject) dupe)) {
           for (Iterator<WeakReference<T>> it = manufactured.iterator(); it.hasNext(); ) {
             WeakReference<T> wr = it.next();
             T mfg = wr.get();
             if (mfg == null) {
               it.remove();
             }
             // Yes this is instance equality, not .equals
             else if (mfg == good) {
               forgetObject(good);
               break;
             }
           }
         }
       }
     }
     if (duplicates.containsListFor(second)) {
       Logging.errorPrint(
           "More than one "
               + factory.getReferenceDescription()
               + " with key/name "
               + good.getKeyName()
               + " was built");
       List<T> dupes = duplicates.getListFor(second);
       StringBuilder sb = new StringBuilder(1000);
       sb.append("Sources: ");
       sb.append(good.isInternal() ? "<internal>" : good.getSourceURI());
       for (T dupe : dupes) {
         sb.append(", ").append(dupe.isInternal() ? "<internal>" : dupe.getSourceURI());
         if (!dupe.getKeyName().equals(good.getKeyName())) {
           Logging.errorPrint("Key case differed for " + dupe.getKeyName());
         }
       }
       Logging.errorPrint(sb.toString());
       returnGood = false;
     }
   }
   return returnGood;
 }
 /**
  * Resolves the references that have been requested from this AbstractReferenceManufacturer, using
  * the objects contained within this AbstractReferenceManufacturer.
  *
  * <p>This method guarantees that all references are resolved.
  *
  * <p>Note: Implementations of AbstractReferenceManufacturer may place limits on the number of
  * times resolveReferences() can be called. The reason for this is that some references may only
  * be resolved once, and the AbstractReferenceManufacturer is not required to maintain a list of
  * references that have been resolved and those which have not been resolved.
  */
 @Override
 public boolean resolveReferences(UnconstructedValidator validator) {
   boolean resolutionSuccessful = resolvePrimitiveReferences(validator);
   resolutionSuccessful &= resolveGroupReferences();
   for (WeakReference<CDOMGroupRef<T>> ref : typeReferences.values()) {
     CDOMGroupRef<T> trt = ref.get();
     if (trt != null && trt.getObjectCount() == 0) {
       Logging.errorPrint(
           "Error: No "
               + factory.getReferenceDescription()
               + " objects of "
               + trt.getLSTformat(false)
               + " were loaded but were referred to in the data");
       fireUnconstuctedEvent(trt);
       resolutionSuccessful = false;
     }
   }
   isResolved = true;
   return resolutionSuccessful;
 }
 /**
  * Gets a reference to the Class or Class/Context provided by this AbstractReferenceManufacturer.
  * The reference will be a reference to the objects identified by the given types.
  *
  * @param types An array of the types of objects to which the returned CDOMReference will refer.
  * @return A CDOMGroupRef which is intended to contain objects of a given Type for the Class or
  *     Class/Context this AbstractReferenceManufacturer represents.
  * @throws IllegalArgumentException if any of the given Strings is null, empty (length is zero),
  *     or contains a period (.), equals (=), comma (,) or pipe (|)
  */
 @Override
 public CDOMGroupRef<T> getTypeReference(String... types) {
   for (String type : types) {
     if (type == null || type.length() == 0) {
       throw new IllegalArgumentException(
           "Attempt to acquire empty Type "
               + "(the type String contains a null or empty element)");
     }
     if (type.indexOf('.') != -1) {
       throw new IllegalArgumentException(
           "Cannot build Reference with type conaining a period: " + type);
     }
     if (type.indexOf('=') != -1) {
       throw new IllegalArgumentException(
           "Cannot build Reference with type conaining an equals: " + type);
     }
     if (type.indexOf(',') != -1) {
       throw new IllegalArgumentException(
           "Cannot build Reference with type conaining a comma: " + type);
     }
     if (type.indexOf('|') != -1) {
       throw new IllegalArgumentException(
           "Cannot build Reference with type conaining a pipe: " + type);
     }
   }
   Arrays.sort(types);
   FixedStringList typeList = new FixedStringList(types);
   WeakReference<CDOMGroupRef<T>> ref = typeReferences.get(typeList);
   if (ref != null) {
     CDOMGroupRef<T> trt = ref.get();
     if (trt != null) {
       return trt;
     }
   }
   // Didn't find the appropriate key, create new
   CDOMGroupRef<T> cgr = factory.getTypeReference(types);
   typeReferences.put(typeList, new WeakReference<CDOMGroupRef<T>>(cgr));
   return cgr;
 }
 private boolean validateNames() {
   if (!Logging.isLoggable(Logging.LST_WARNING)) {
     return true;
   }
   for (String key : active.keySet()) {
     T value = active.get(key);
     if (value.isInternal()) {
       continue;
     }
     /*
      * http://wiki.pcgen.org/index.php?title=Data_LST_Standards
      *
      * Characters which should never be used in object names are Commas
      * (,), Pipes (|), Backslashes (\), Colons (:), Semicolons (;),
      * Periods (.), Brackets ([]), Percent (%), Asterisk (*) and Equals
      * (=).
      */
     if (key.indexOf(',') != -1 && factory.getReferenceClass() != RollMethod.class) {
       Logging.log(
           Logging.LST_WARNING,
           "Found "
               + factory.getReferenceDescription()
               + " with KEY: "
               + key
               + " which contains a comma "
               + "(prohibited character in a key)");
     }
     if (key.indexOf('|') != -1) {
       Logging.log(
           Logging.LST_WARNING,
           "Found "
               + factory.getReferenceDescription()
               + " with KEY: "
               + key
               + " which contains a pipe "
               + "(prohibited character in a key)");
     }
     if (key.indexOf('\\') != -1) {
       Logging.log(
           Logging.LST_WARNING,
           "Found "
               + factory.getReferenceDescription()
               + " with KEY: "
               + key
               + " which contains a backslash "
               + "(prohibited character in a key)");
     }
     if (key.indexOf(':') != -1) {
       Logging.log(
           Logging.LST_WARNING,
           "Found "
               + factory.getReferenceDescription()
               + " with KEY: "
               + key
               + " which contains a colon "
               + "(prohibited character in a key)");
     }
     if (key.indexOf(';') != -1) {
       Logging.log(
           Logging.LST_WARNING,
           "Found "
               + factory.getReferenceDescription()
               + " with KEY: "
               + key
               + " which contains a semicolon "
               + "(prohibited character in a key)");
     }
     // if (key.indexOf('.') != -1)
     // {
     // Logging.log(Logging.LST_WARNING, "Found "
     // + getReferenceDescription() + " with KEY: " + key
     // + " which contains a period "
     // + "(prohibited character in a key)");
     // }
     if (key.indexOf('%') != -1) {
       Logging.log(
           Logging.LST_WARNING,
           "Found "
               + factory.getReferenceDescription()
               + " with KEY: "
               + key
               + " which contains a percent sign "
               + "(prohibited character in a key)");
     }
     if (key.indexOf('*') != -1) {
       Logging.log(
           Logging.LST_WARNING,
           "Found "
               + factory.getReferenceDescription()
               + " with KEY: "
               + key
               + " which contains an asterisk "
               + "(prohibited character in a key)");
     }
     if (key.indexOf('=') != -1) {
       Logging.log(
           Logging.LST_WARNING,
           "Found "
               + factory.getReferenceDescription()
               + " with KEY: "
               + key
               + " which contains an equals sign "
               + "(prohibited character in a key)");
     }
     if ((key.indexOf('[') != -1) || (key.indexOf(']') != -1)) {
       Logging.log(
           Logging.LST_WARNING,
           "Found "
               + factory.getReferenceDescription()
               + " with KEY: "
               + key
               + " which contains a bracket  "
               + "(prohibited character in a key)");
     }
   }
   return true;
 }
  /**
   * Gets a reference to the Class or Class/Context provided by this AbstractReferenceManufacturer.
   * The reference will be a reference to the object identified by the given key.
   *
   * @param key The key used to identify the object to which the returned CDOMReference will refer.
   * @return A CDOMReference that refers to the object identified by the given key
   * @throws IllegalArgumentException if the given key is null or empty
   */
  @Override
  public CDOMSingleRef<T> getReference(String key) {
    /*
     * TODO This is incorrect, but a hack for now :)
     *
     * Mainly this throws around IllegalArgumentException in order to catch
     * bad parsing issues (design flaws in the code). Not sure if we want to
     * continue that long term? Once tokens are truly tested this may not be
     * necessary or desirable.
     */
    if (key == null) {
      throw new IllegalArgumentException("Cannot request a reference to null identifier");
    }
    if (key.length() == 0) {
      throw new IllegalArgumentException("Cannot request a reference to an empty identifier");
    }
    /*
     * Items thrown below this point are for protection from coding errors
     * in LST files, not part of the public interface of this method
     */
    try {
      Integer.parseInt(key);
      throw new IllegalArgumentException("A number cannot be a valid single item: " + key);
    } catch (NumberFormatException nfe) {
      // ok
    }
    if (key.contains("=")) {
      throw new IllegalArgumentException(
          "= cannot be a in valid single item (perhaps something like TYPE= "
              + "is not supported in this token?): "
              + key);
    }
    if (key.equalsIgnoreCase("ANY")) {
      throw new IllegalArgumentException(
          "Any cannot be a valid single item (not supported in this token?)");
    }
    if (key.equalsIgnoreCase("ALL")) {
      throw new IllegalArgumentException(
          "All cannot be a valid single item (not supported in this token?)");
    }
    if (key.contains(":")) {
      throw new IllegalArgumentException(
          ": cannot exist in a valid single item (did you try to use a "
              + "PRE where it is not supported?) "
              + key);
    }
    if (key.equalsIgnoreCase("%LIST")) {
      throw new IllegalArgumentException(
          "%LIST cannot be a valid single item (not supported in this token?)");
    }

    WeakReference<CDOMSingleRef<T>> wr = referenced.get(key);
    if (wr != null) {
      CDOMSingleRef<T> ref = wr.get();
      if (ref != null) {
        return ref;
      }
    }
    CDOMSingleRef<T> ref;
    if (isResolved) {
      T current = active.get(key);
      if (current == null) {
        throw new IllegalArgumentException(
            key + " is not valid post-resolution " + "because it was never constructed");
      }
      ref = CDOMDirectSingleRef.getRef(current);
    } else {
      CDOMSingleRef<T> lr = factory.getReference(key);
      referenced.put(key, new WeakReference<CDOMSingleRef<T>>(lr));
      ref = lr;
    }
    return ref;
  }
 /**
  * The class of object this AbstractReferenceManufacturer represents.
  *
  * @return The class of object this AbstractReferenceManufacturer represents.
  */
 @Override
 public Class<T> getReferenceClass() {
   return factory.getReferenceClass();
 }
 @Override
 public Indirect<T> convertIndirect(String key) {
   return factory.getReference(key);
 }
 @Override
 public String getReferenceDescription() {
   return factory.getReferenceDescription();
 }