protected static Object transform(Object object, NSDictionary lookup) {
   if (object instanceof NSDictionary) {
     return RuleModelUtilities.transformDictionary((NSDictionary) object, lookup);
   } else if (object instanceof NSArray) {
     return RuleModelUtilities.transformArray((NSArray) object, lookup);
   } else if (object instanceof String) {
     return RuleModelUtilities.transformString((String) object, lookup);
   } else {
     return object;
   }
 }
  protected static NSDictionary expansionLookup() {
    if (RuleModelUtilities.expansionLookup == null) {
      NSDictionary lookupDictionary = RuleModelUtilities.compressionLookup();
      Enumeration keys = lookupDictionary.keyEnumerator();
      NSMutableDictionary lookup = new NSMutableDictionary(lookupDictionary.count());

      while (keys.hasMoreElements()) {
        Object key = keys.nextElement();
        Object value = lookupDictionary.objectForKey(key);

        lookup.setObjectForKey(key, value);
      }

      // Direct To Web rule editor compatibility
      lookup.setObjectForKey(
          BooleanAssignment.class.getName(), "com.webobjects.directtoweb.BooleanAssignment");
      lookup.setObjectForKey(Rule.class.getName(), "com.webobjects.directtoweb.Rule");
      lookup.setObjectForKey(
          SimpleAssignment.class.getName(), "com.webobjects.directtoweb.Assignment");
      lookup.setObjectForKey(
          KeyValueAssignment.class.getName(), "com.webobjects.directtoweb.KeyValueAssignment");

      RuleModelUtilities.expansionLookup = lookup;
    }

    return RuleModelUtilities.expansionLookup;
  }
  protected static NSArray transformArray(NSArray array, NSDictionary lookup) {
    NSMutableArray transformedArray = new NSMutableArray(array.count());
    Enumeration objects = array.objectEnumerator();

    while (objects.hasMoreElements()) {
      Object object = objects.nextElement();

      transformedArray.addObject(RuleModelUtilities.transform(object, lookup));
    }

    return transformedArray;
  }
  protected static String encode(NSArray rules) {
    if (rules != null) {
      EOKeyValueArchiver archiver = new EOKeyValueArchiver();

      archiver.encodeObject(rules, RuleModel.RULES_KEY);

      return NSPropertyListSerialization.stringFromPropertyList(
          transformDictionary(archiver.dictionary(), RuleModelUtilities.compressionLookup()));
    } else {
      return null;
    }
  }
  protected static NSDictionary transformDictionary(NSDictionary dictionary, NSDictionary lookup) {
    NSMutableDictionary transformedDictionary = new NSMutableDictionary(dictionary.count());
    Enumeration keys = dictionary.keyEnumerator();

    while (keys.hasMoreElements()) {
      Object key = keys.nextElement();
      Object value = dictionary.objectForKey(key);

      transformedDictionary.setObjectForKey(RuleModelUtilities.transform(value, lookup), key);
    }

    return transformedDictionary;
  }
  protected static NSArray decode(String string) {
    if (string != null) {
      NSDictionary dictionary = NSPropertyListSerialization.dictionaryForString(string);
      EOKeyValueUnarchiver unarchiver =
          new EOKeyValueUnarchiver(
              transformDictionary(dictionary, RuleModelUtilities.expansionLookup()));
      NSArray rules = (NSArray) unarchiver.decodeObjectForKey(RuleModel.RULES_KEY);

      unarchiver.finishInitializationOfObjects();

      return rules;
    } else {
      return null;
    }
  }
  /**
   * Loads model files from all bundles in the classpath.<br>
   * Caveat: For compatibility with Apple's RuleEditor, this method automatically adds the
   * ".d2wmodel" extension to all names (with or without extension that you pass into this method.
   * That is we use "double extensions": the one specified as an argument plus the ".d2wmodel" one.
   * To the caller this is mostly transparent. Remember not to pass the extension or names including
   * the extension ".d2wmodel" as arguments. Remember to name your files including the ".d2wmodel"
   * extension.
   *
   * @param extension file name extension to look for
   * @param includeNames explicit list of file names (w/o extension) to accept, accepts all if null.
   * @param excludeNames explicit list of file names (w/o extension) to refuse, accepts all if null
   * @param includesFiles explicit list of file names (with extension) to accept in addition to
   *     otherwise accepted files
   */
  public static RuleModel loadFromBundles(
      String extension, NSSet includeNames, NSSet excludeNames, NSSet includesFiles) {
    NSMutableArray rules = new NSMutableArray();
    NSArray frameworkBundles = NSBundle.frameworkBundles();
    NSArray allBundles = frameworkBundles.arrayByAddingObject(NSBundle.mainBundle());
    Enumeration bundleEnumeration = allBundles.objectEnumerator();
    String fullExtension = extension + MODEL_EXT;
    int extLength = fullExtension.length();

    while (bundleEnumeration.hasMoreElements()) {
      NSBundle bundle = (NSBundle) bundleEnumeration.nextElement();
      NSArray ruleFilePaths = bundle.resourcePathsForLocalizedResources(fullExtension, null);
      Enumeration ruleFilePathEnumeration = ruleFilePaths.objectEnumerator();

      while (ruleFilePathEnumeration.hasMoreElements()) {
        String resourcePath = (String) ruleFilePathEnumeration.nextElement();

        if ((includeNames != null) || (excludeNames != null)) {
          int separatorIndex =
              resourcePath.lastIndexOf('.', resourcePath.length() - MODEL_EXT.length() - 1);

          if (separatorIndex < 0) {
            separatorIndex = 0;
          }

          int lastIndex = resourcePath.length() - extLength - 1;
          String resourceName = resourcePath.substring(separatorIndex + 1, lastIndex);

          if (includeNames != null) {
            if (!includeNames.containsObject(resourceName)) {
              continue;
            }
          }

          if (excludeNames != null) {
            if (excludeNames.containsObject(resourceName)) {
              continue;
            }
          }
        }

        // System.err.println("** bundle: " + bundle.name());
        // System.err.println("** resourcePath: " + resourcePath);

        InputStream inputStream = bundle.inputStreamForResourcePath(resourcePath);
        String string = StringUtilities.stringFromInputStream(inputStream);

        rules.addObjectsFromArray(RuleModelUtilities.decode(string));
      }

      if (includesFiles != null) {
        Enumeration includeFileEnumeration = includesFiles.objectEnumerator();

        while (includeFileEnumeration.hasMoreElements()) {
          String includeFile = (String) includeFileEnumeration.nextElement();

          if (includeFile.indexOf('.') > -1) {
            String includeFilePath =
                bundle.resourcePathForLocalizedResourceNamed(includeFile + MODEL_EXT, null);

            if (includeFilePath != null) {
              InputStream inputStream = bundle.inputStreamForResourcePath(includeFilePath);
              String string = StringUtilities.stringFromInputStream(inputStream);

              rules.addObjectsFromArray(RuleModelUtilities.decode(string));
            }
          }
        }
      }
    }

    return new RuleModel(rules);
  }
  public static RuleModel loadFromFile(File file) {
    String contents = StringUtilities.stringFromFile(file);

    return new RuleModel(RuleModelUtilities.decode(contents));
  }