/**
   * 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));
  }