/*
 * JBoss, Home of Professional Open Source
 * Copyright 2015, Red Hat, Inc., and individual contributors as indicated
 * by the @authors tag.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

// Catalog.java - Represents OASIS Open Catalog files.

package org.jboss.util.xml.catalog;

import java.io.DataInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Vector;

import javax.xml.parsers.SAXParserFactory;

import org.jboss.util.xml.catalog.helpers.PublicId;
import org.jboss.util.xml.catalog.readers.CatalogReader;
import org.jboss.util.xml.catalog.readers.OASISXMLCatalogReader;
import org.jboss.util.xml.catalog.readers.SAXCatalogReader;
import org.jboss.util.xml.catalog.readers.TR9401CatalogReader;

/**
 * Represents OASIS Open Catalog files.
 *
 *
This class implements the semantics of OASIS Open Catalog files
 * (defined by
 * OASIS Technical
 * Resolution 9401:1997 (Amendment 2 to TR 9401)).

 *
 *
The primary purpose of the Catalog is to associate resources in the
 * document with local system identifiers. Some entities
 * (document types, XML entities, and notations) have names and all of them
 * can have either public or system identifiers or both. (In XML, only a
 * notation can have a public identifier without a system identifier, but
 * the methods implemented in this class obey the Catalog semantics
 * from the SGML
 * days when system identifiers were optional.)

 *
 *
The system identifiers returned by the resolution methods in this
 * class are valid, i.e. usable by, and in fact constructed by, the
 * <tt>java.net.URL</tt> class. Unfortunately, this class seems to behave in
 * somewhat non-standard ways and the system identifiers returned may
 * not be directly usable in a browser or filesystem context.
 *
 *
This class recognizes all of the Catalog entries defined in
 * TR9401:1997:

 *
 *

 *
BASE
 * changes the base URI for resolving relative system identifiers. The
 * initial base URI is the URI of the location of the catalog (which is,
 * in turn, relative to the location of the current working directory
 * at startup, as returned by the <tt>user.dir</tt> system property).

 *
CATALOG
 * processes other catalog files. An included catalog occurs logically
 * at the end of the including catalog.

 *
DELEGATE_PUBLIC
 * specifies alternate catalogs for some public identifiers. The delegated
 * catalogs are not loaded until they are needed, but they are cached
 * once loaded.

 *
DELEGATE_SYSTEM
 * specifies alternate catalogs for some system identifiers. The delegated
 * catalogs are not loaded until they are needed, but they are cached
 * once loaded.

 *
DELEGATE_URI
 * specifies alternate catalogs for some URIs. The delegated
 * catalogs are not loaded until they are needed, but they are cached
 * once loaded.

 *
REWRITE_SYSTEM
 * specifies alternate prefix for a system identifier.

 *
REWRITE_URI
 * specifies alternate prefix for a URI.

 *
DOCTYPE
 * associates the names of root elements with URIs. (In other words, an XML
 * processor might infer the doctype of an XML document that does not include
 * a doctype declaration by looking for the DOCTYPE entry in the
 * catalog which matches the name of the root element of the document.)

 *
DOCUMENT
 * provides a default document.

 *
DTDDECL
 * recognized and silently ignored. Not relevant for XML.

 *
ENTITY
 * associates entity names with URIs.

 *
LINKTYPE
 * recognized and silently ignored. Not relevant for XML.

 *
NOTATION
 * associates notation names with URIs.

 *
OVERRIDE
 * changes the override behavior. Initial behavior is set by the
 * system property <tt>xml.catalog.override</tt>. The default initial
 * behavior is 'YES', that is, entries in the catalog override
 * system identifiers specified in the document.

 *
PUBLIC
 * maps a public identifier to a system identifier.

 *
SGMLDECL
 * recognized and silently ignored. Not relevant for XML.

 *
SYSTEM
 * maps a system identifier to another system identifier.

 *
URI
 * maps a URI to another URI.

 *

 *
 *
Note that BASE entries are treated as described by RFC2396. In
 * particular, this has the counter-intuitive property that after a BASE
 * entry identifing "http://example.com/a/b/c" as the base URI,
 * the relative URI "foo" is resolved to the absolute URI
 * "http://example.com/a/b/foo". You must provide the trailing slash if
 * you do not want the final component of the path to be discarded as a
 * filename would in a URI for a resource: "http://example.com/a/b/c/".
 *

 *
 *
Note that subordinate catalogs (all catalogs except the first,
 * including CATALOG and DELEGATE* catalogs) are only loaded if and when
 * they are required.

 *
 *
This class relies on classes which implement the CatalogReader
 * interface to actually load catalog files. This allows the catalog
 * semantics to be implemented for TR9401 text-based catalogs, XML
 * catalogs, or any number of other storage formats.

 *
 *
Additional catalogs may also be loaded with the
 * {@link #parseCatalog(String)} method.

 *

 *

 *
 *
Change Log:

 *

 *
2.0

 *

Rewrite to use CatalogReaders.

 *
1.1

 *

Allow quoted components in <tt>xml.catalog.files</tt>
 * so that URLs containing colons can be used on Unix.
 * The string passed to <tt>xml.catalog.files</tt> can now have the form:

 *

 * unquoted-path-with-no-sep-chars:"double-quoted path with or without sep chars":'single-quoted path with or without sep chars'
 *

 *
(Where ":" is the separater character in this example.)

 *
If an unquoted path contains an embedded double or single quote
 * character, no special processig is performed on that character. No
 * path can contain separater characters, double, and single quotes
 * simultaneously.

 *
Fix bug in calculation of BASE entries: if
 * a catalog contains multiple BASE entries, each is relative to the preceding
 * base, not the default base URI of the catalog.

 *

 *
1.0.1

 *

Fixed a bug in the calculation of the list of subordinate catalogs.
 * This bug caused an infinite loop where parsing would alternately process
 * two catalogs indefinitely.

 *

 *

 *
 * @see CatalogReader
 * @see CatalogEntry
 *
 * @author Norman Walsh
 * Norman.Walsh@Sun.COM
 *
 * @version 1.0
 *
 *
Derived from public domain code originally published by Arbortext,
 * Inc.

 */
@SuppressWarnings("unchecked")
public class Catalog {
  /** The BASE Catalog Entry type. */
  public static final int BASE     = CatalogEntry.addEntryType("BASE", 1);

  /** The CATALOG Catalog Entry type. */
  public static final int CATALOG  = CatalogEntry.addEntryType("CATALOG", 1);

  /** The DOCUMENT Catalog Entry type. */
  public static final int DOCUMENT = CatalogEntry.addEntryType("DOCUMENT", 1);

  /** The OVERRIDE Catalog Entry type. */
  public static final int OVERRIDE = CatalogEntry.addEntryType("OVERRIDE", 1);

  /** The SGMLDECL Catalog Entry type. */
  public static final int SGMLDECL = CatalogEntry.addEntryType("SGMLDECL", 1);

  /** The DELEGATE_PUBLIC Catalog Entry type. */
  public static final int DELEGATE_PUBLIC = CatalogEntry.addEntryType("DELEGATE_PUBLIC", 2);

  /** The DELEGATE_SYSTEM Catalog Entry type. */
  public static final int DELEGATE_SYSTEM = CatalogEntry.addEntryType("DELEGATE_SYSTEM", 2);

  /** The DELEGATE_URI Catalog Entry type. */
  public static final int DELEGATE_URI = CatalogEntry.addEntryType("DELEGATE_URI", 2);

  /** The DOCTYPE Catalog Entry type. */
  public static final int DOCTYPE  = CatalogEntry.addEntryType("DOCTYPE", 2);

  /** The DTDDECL Catalog Entry type. */
  public static final int DTDDECL  = CatalogEntry.addEntryType("DTDDECL", 2);

  /** The ENTITY Catalog Entry type. */
  public static final int ENTITY   = CatalogEntry.addEntryType("ENTITY", 2);

  /** The LINKTYPE Catalog Entry type. */
  public static final int LINKTYPE = CatalogEntry.addEntryType("LINKTYPE", 2);

  /** The NOTATION Catalog Entry type. */
  public static final int NOTATION = CatalogEntry.addEntryType("NOTATION", 2);

  /** The PUBLIC Catalog Entry type. */
  public static final int PUBLIC   = CatalogEntry.addEntryType("PUBLIC", 2);

  /** The SYSTEM Catalog Entry type. */
  public static final int SYSTEM   = CatalogEntry.addEntryType("SYSTEM", 2);

  /** The URI Catalog Entry type. */
  public static final int URI      = CatalogEntry.addEntryType("URI", 2);

  /** The REWRITE_SYSTEM Catalog Entry type. */
  public static final int REWRITE_SYSTEM = CatalogEntry.addEntryType("REWRITE_SYSTEM", 2);

  /** The REWRITE_URI Catalog Entry type. */
  public static final int REWRITE_URI = CatalogEntry.addEntryType("REWRITE_URI", 2);

  /**
   * The base URI for relative system identifiers in the catalog.
   * This may be changed by BASE entries in the catalog.
   */
  protected URL base;

  /** The base URI of the Catalog file currently being parsed. */
  protected URL catalogCwd;

  /** The catalog entries currently known to the system. */
  protected Vector catalogEntries = new Vector();

  /** The default initial override setting. */
  protected boolean default_override = true;

  /** The catalog manager in use for this instance. */
  protected CatalogManager catalogManager = CatalogManager.getStaticManager();

  /**
   * A vector of catalog files to be loaded.
   *
   *
This list is initially established by
   * loadSystemCatalogs when
   * it parses the system catalog list, but CATALOG entries may
   * contribute to it during the course of parsing.

   *
   * @see #loadSystemCatalogs
   * @see #localCatalogFiles
   */
  protected Vector catalogFiles = new Vector();

  /**
   * A vector of catalog files constructed during processing of
   * CATALOG entries in the current catalog.
   *
   *
This two-level system is actually necessary to correctly implement
   * the semantics of the CATALOG entry. If one catalog file includes
   * another with a CATALOG entry, the included catalog logically
   * occurs at the end of the including catalog, and after any
   * preceding CATALOG entries. In other words, the CATALOG entry
   * cannot insert anything into the middle of a catalog file.

   *
   *
When processing reaches the end of each catalog files, any
   * elements on this vector are added to the front of the
   * catalogFiles vector.

   *
   * @see #catalogFiles
   */
  protected Vector localCatalogFiles = new Vector();

  /**
   * A vector of Catalogs.
   *
   *
The semantics of Catalog resolution are such that each
   * catalog is effectively a list of Catalogs (in other words,
   * a recursive list of Catalog instances).

   *
   *
Catalogs that are processed as the result of CATALOG or
   * DELEGATE* entries are subordinate to the catalog that contained
   * them, but they may in turn have subordinate catalogs.

   *
   *
Catalogs are only loaded when they are needed, so this vector
   * initially contains a list of Catalog filenames (URLs). If, during
   * processing, one of these catalogs has to be loaded, the resulting
   * Catalog object is placed in the vector, effectively caching it
   * for the next query.

   */
  protected Vector catalogs = new Vector();

  /**
   * A vector of DELEGATE* Catalog entries constructed during
   * processing of the Catalog.
   *
   *
This two-level system has two purposes; first, it allows
   * us to sort the DELEGATE* entries by the length of the partial
   * public identifier so that a linear search encounters them in
   * the correct order and second, it puts them all at the end of
   * the Catalog.

   *
   *
When processing reaches the end of each catalog file, any
   * elements on this vector are added to the end of the
   * catalogEntries vector. This assures that matching
   * PUBLIC keywords are encountered before DELEGATE* entries.

   */
  protected Vector localDelegate = new Vector();

  /**
   * A hash of CatalogReaders.
   *
   *
This hash maps MIME types to elements in the readerArr
   * vector. This allows the Catalog to quickly locate the reader
   * for a particular MIME type.

   */
  protected Hashtable readerMap = new Hashtable();

  /**
   * A vector of CatalogReaders.
   *
   *
This vector contains all of the readers in the order that they
   * were added. In the event that a catalog is read from a file, where
   * the MIME type is unknown, each reader is attempted in turn until
   * one succeeds.

   */
  protected Vector readerArr = new Vector();

  /**
   * Constructs an empty Catalog.
   *
   *
The constructor interrogates the relevant system properties
   * using the default (static) CatalogManager
   * and initializes the catalog data structures.

   */
  public Catalog() {
    // nop;
  }

  /**
   * Constructs an empty Catalog with a specific CatalogManager.
   *
   *
The constructor interrogates the relevant system properties
   * using the specified Catalog Manager
   * and initializes the catalog data structures.

 * @param manager
   */
  public Catalog(CatalogManager manager) {
    catalogManager = manager;
  }

  /**
   * @return the CatalogManager used by this catalog.
   *
   */
  public CatalogManager getCatalogManager() {
    return catalogManager;
  }

  /**
   * Establish the CatalogManager used by this catalog.
 * @param manager
   *
   */
  public void setCatalogManager(CatalogManager manager) {
    catalogManager = manager;
  }

  /**
   * Setup readers.
   */
  public void setupReaders() {
    SAXParserFactory spf = SAXParserFactory.newInstance();
    spf.setNamespaceAware(true);
    spf.setValidating(false);

    SAXCatalogReader saxReader = new SAXCatalogReader(spf);

    saxReader.setCatalogParser(null, "XMLCatalog",
			       "org.apache.xml.resolver.readers.XCatalogReader");

    saxReader.setCatalogParser(OASISXMLCatalogReader.namespaceName,
			       "catalog",
			       "org.apache.xml.resolver.readers.OASISXMLCatalogReader");

    addReader("application/xml", saxReader);

    TR9401CatalogReader textReader = new TR9401CatalogReader();
    addReader("text/plain", textReader);
  }

  /**
   * Add a new CatalogReader to the Catalog.
   *
   *
This method allows you to add a new CatalogReader to the
   * catalog. The reader will be associated with the specified mimeType.
   * You can only have one reader per mimeType.

   *
   *
In the absence of a mimeType (e.g., when reading a catalog
   * directly from a file on the local system), the readers are attempted
   * in the order that you add them to the Catalog.

   *
   *
Note that subordinate catalogs (created by CATALOG or
   * DELEGATE* entries) get a copy of the set of readers present in
   * the primary catalog when they are created. Readers added subsequently
   * will not be available. For this reason, it is best to add all
   * of the readers before the first call to parse a catalog.

   *
   * @param mimeType The MIME type associated with this reader.
   * @param reader The CatalogReader to use.
   */
  public void addReader(String mimeType, CatalogReader reader) {
    if (readerMap.containsKey(mimeType)) {
      Integer pos = (Integer) readerMap.get(mimeType);
      readerArr.set(pos.intValue(), reader);
    } else {
      readerArr.add(reader);
      Integer pos = new Integer(readerArr.size()-1);
      readerMap.put(mimeType, pos);
    }
  }

  /**
   * Copies the reader list from the current Catalog to a new Catalog.
   *
   *
This method is used internally when constructing a new catalog.
   * It copies the current reader associations over to the new catalog.
   *

   *
   * @param newCatalog The new Catalog.
   */
  protected void copyReaders(Catalog newCatalog) {
    // Have to copy the readers in the right order...convert hash to arr
    Vector mapArr = new Vector(readerMap.size());

    // Pad the mapArr out to the right length
    for (int count = 0; count < readerMap.size(); count++) {
      mapArr.add(null);
    }

    Enumeration enumt = readerMap.keys();
    while (enumt.hasMoreElements()) {
      String mimeType = (String) enumt.nextElement();
      Integer pos = (Integer) readerMap.get(mimeType);
      mapArr.set(pos.intValue(), mimeType);
    }

    for (int count = 0; count < mapArr.size(); count++) {
      String mimeType = (String) mapArr.get(count);
      Integer pos = (Integer) readerMap.get(mimeType);
      newCatalog.addReader(mimeType,
			   (CatalogReader)
			   readerArr.get(pos.intValue()));
    }
  }

  /**
   * Create a new Catalog object.
   *
   *
This method constructs a new instance of the running Catalog
   * class (which might be a subtype of org.apache.xml.resolver.Catalog).
   * All new catalogs are managed by the same CatalogManager.
   *

   *
   *
N.B. All Catalog subtypes should call newCatalog() to construct
   * a new Catalog. Do not simply use "new Subclass()" since that will
   * confuse future subclasses.

   *
   * @return the catalog
   */
  protected Catalog newCatalog() {
    String catalogClass = this.getClass().getName();

    try {
      Catalog c = (Catalog) (Class.forName(catalogClass).newInstance());
      c.setCatalogManager(catalogManager);
      copyReaders(c);
      return c;
    } catch (ClassNotFoundException cnfe) {
      catalogManager.debug.message(1, "Class Not Found Exception: " + catalogClass);
    } catch (IllegalAccessException iae) {
      catalogManager.debug.message(1, "Illegal Access Exception: " + catalogClass);
    } catch (InstantiationException ie) {
      catalogManager.debug.message(1, "Instantiation Exception: " + catalogClass);
    } catch (ClassCastException cce) {
      catalogManager.debug.message(1, "Class Cast Exception: " + catalogClass);
    } catch (Exception e) {
      catalogManager.debug.message(1, "Other Exception: " + catalogClass);
    }

    Catalog c = new Catalog();
    c.setCatalogManager(catalogManager);
    copyReaders(c);
    return c;
  }

  /**
   * @return the current base URI.
   */
  public String getCurrentBase() {
    return base.toString();
  }

  /**
   * @return the default override setting associated with this
   * catalog.
   *
   *
All catalog files loaded by this catalog will have the
   * initial override setting specified by this default.

   */
  public String getDefaultOverride() {
    if (default_override) {
      return "yes";
    } else {
      return "no";
    }
  }

  /**
   * Load the system catalog files.
   *
   *
The method adds all of the
   * catalogs specified in the <tt>xml.catalog.files</tt> property
   * to the Catalog list.

   *
   * @throws MalformedURLException  One of the system catalogs is
   * identified with a filename that is not a valid URL.
   * @throws IOException One of the system catalogs cannot be read.
   */
  public void loadSystemCatalogs()
    throws MalformedURLException, IOException {

    Vector catalogs = catalogManager.getCatalogFiles();
    if (catalogs != null) {
      for (int count = 0; count < catalogs.size(); count++) {
	catalogFiles.addElement(catalogs.elementAt(count));
      }
    }

    if (catalogFiles.size() > 0) {
      // This is a little odd. The parseCatalog() method expects
      // a filename, but it adds that name to the end of the
      // catalogFiles vector, and then processes that vector.
      // This allows the system to handle CATALOG entries
      // correctly.
      //
      // In this init case, we take the last element off the
      // catalogFiles vector and pass it to parseCatalog. This
      // will "do the right thing" in the init case, and allow
      // parseCatalog() to do the right thing in the non-init
      // case. Honest.
      //
      String catfile = (String) catalogFiles.lastElement();
      catalogFiles.removeElement(catfile);
      parseCatalog(catfile);
    }
  }

  /**
   * Parse a catalog file, augmenting internal data structures.
   *
   * @param fileName The filename of the catalog file to process
   *
   * @throws MalformedURLException The fileName cannot be turned into
   * a valid URL.
   * @throws IOException Error reading catalog file.
   */
  public synchronized void parseCatalog(String fileName)
    throws MalformedURLException, IOException {

    default_override = catalogManager.getPreferPublic();
    catalogManager.debug.message(4, "Parse catalog: " + fileName);

    // Put the file into the list of catalogs to process...
    // In all cases except the case when initCatalog() is the
    // caller, this will be the only catalog initially in the list...
    catalogFiles.addElement(fileName);

    // Now process all the pending catalogs...
    parsePendingCatalogs();
  }

  /**
   * Parse a catalog file, augmenting internal data structures.
   *
   *
Catalogs retrieved over the net may have an associated MIME type.
   * The MIME type can be used to select an appropriate reader.

   *
   * @param mimeType The MIME type of the catalog file.
   * @param is The InputStream from which the catalog should be read
   *
   * @throws CatalogException Failed to load catalog
   * mimeType.
   * @throws IOException Error reading catalog file.
   */
  public synchronized void parseCatalog(String mimeType, InputStream is)
    throws IOException, CatalogException {

    default_override = catalogManager.getPreferPublic();
    catalogManager.debug.message(4, "Parse " + mimeType + " catalog on input stream");

    CatalogReader reader = null;

    if (readerMap.containsKey(mimeType)) {
      int arrayPos = ((Integer) readerMap.get(mimeType)).intValue();
      reader = (CatalogReader) readerArr.get(arrayPos);
    }

    if (reader == null) {
      String msg = "No CatalogReader for MIME type: " + mimeType;
      catalogManager.debug.message(2, msg);
      throw new CatalogException(CatalogException.UNPARSEABLE, msg);
    }

    reader.readCatalog(this, is);

    // Now process all the pending catalogs...
    parsePendingCatalogs();
  }

  /**
   * Parse a catalog document, augmenting internal data structures.
   *
   *
This method supports catalog files stored in jar files: e.g.,
   * jar:file:///path/to/filename.jar!/path/to/catalog.xml". That URI
   * doesn't survive transmogrification through the URI processing that
   * the parseCatalog(String) performs and passing it as an input stream
   * doesn't set the base URI appropriately.

   *
   *
Written by Stefan Wachter (2002-09-26)

   *
   * @param aUrl The URL of the catalog document to process
   *
   * @throws IOException Error reading catalog file.
   */
  public synchronized void parseCatalog(URL aUrl) throws IOException {
    catalogCwd = aUrl;
    base = aUrl;

    default_override = catalogManager.getPreferPublic();
    catalogManager.debug.message(4, "Parse catalog: " + aUrl.toString());

    DataInputStream inStream = null;
    boolean parsed = false;

    for (int count = 0; !parsed && count < readerArr.size(); count++) {
      CatalogReader reader = (CatalogReader) readerArr.get(count);

      try {
      	inStream = new DataInputStream(aUrl.openStream());
      } catch (FileNotFoundException fnfe) {
        // No catalog; give up!
        break;
      }

      try {
        reader.readCatalog(this, inStream);
        parsed=true;
      } catch (CatalogException ce) {
        if (ce.getExceptionType() == CatalogException.PARSE_FAILED) {
          // give up!
          break;
        } else {
          // try again!
        }
      }

      try {
      	inStream.close();
      } catch (IOException e) {
      	//nop
      }
    }

    if (parsed) parsePendingCatalogs();
  }

  /**
   * Parse all of the pending catalogs.
   *
   *
Catalogs may refer to other catalogs, this method parses
   * all of the currently pending catalog files.

   * @throws MalformedURLException
   * @throws IOException
   */
  protected synchronized void parsePendingCatalogs()
    throws MalformedURLException, IOException {

    if (!localCatalogFiles.isEmpty()) {
      // Move all the localCatalogFiles into the front of
      // the catalogFiles queue
      Vector newQueue = new Vector();
      Enumeration q = localCatalogFiles.elements();
      while (q.hasMoreElements()) {
	newQueue.addElement(q.nextElement());
      }

      // Put the rest of the catalogs on the end of the new list
      for (int curCat = 0; curCat < catalogFiles.size(); curCat++) {
	String catfile = (String) catalogFiles.elementAt(curCat);
	newQueue.addElement(catfile);
      }

      catalogFiles = newQueue;
      localCatalogFiles.clear();
    }

    // Suppose there are no catalog files to process, but the
    // single catalog already parsed included some delegate
    // entries? Make sure they don't get lost.
    if (catalogFiles.isEmpty() && !localDelegate.isEmpty()) {
      Enumeration e = localDelegate.elements();
      while (e.hasMoreElements()) {
	catalogEntries.addElement(e.nextElement());
      }
      localDelegate.clear();
    }

    // Now process all the files on the catalogFiles vector. This
    // vector can grow during processing if CATALOG entries are
    // encountered in the catalog
    while (!catalogFiles.isEmpty()) {
      String catfile = (String) catalogFiles.elementAt(0);
      try {
	catalogFiles.remove(0);
      } catch (ArrayIndexOutOfBoundsException e) {
	// can't happen
      }

      if (catalogEntries.size() == 0 && catalogs.size() == 0) {
	// We haven't parsed any catalogs yet, let this
	// catalog be the first...
	try {
	  parseCatalogFile(catfile);
	} catch (CatalogException ce) {
	  System.out.println("FIXME: " + ce.toString());
	}
      } else {
	// This is a subordinate catalog. We save its name,
	// but don't bother to load it unless it's necessary.
	catalogs.addElement(catfile);
      }

      if (!localCatalogFiles.isEmpty()) {
	// Move all the localCatalogFiles into the front of
	// the catalogFiles queue
	Vector newQueue = new Vector();
	Enumeration q = localCatalogFiles.elements();
	while (q.hasMoreElements()) {
	  newQueue.addElement(q.nextElement());
	}

	// Put the rest of the catalogs on the end of the new list
	for (int curCat = 0; curCat < catalogFiles.size(); curCat++) {
	  catfile = (String) catalogFiles.elementAt(curCat);
	  newQueue.addElement(catfile);
	}

	catalogFiles = newQueue;
	localCatalogFiles.clear();
      }

      if (!localDelegate.isEmpty()) {
	Enumeration e = localDelegate.elements();
	while (e.hasMoreElements()) {
	  catalogEntries.addElement(e.nextElement());
	}
	localDelegate.clear();
      }
    }

    // We've parsed them all, reinit the vector...
    catalogFiles.clear();
  }

  /**
   * Parse a single catalog file, augmenting internal data structures.
   *
   * @param fileName The filename of the catalog file to process
   *
   * @throws MalformedURLException The fileName cannot be turned into
   * a valid URL.
   * @throws IOException Error reading catalog file.
   * @throws CatalogException for any error
   */
  protected synchronized void parseCatalogFile(String fileName)
    throws MalformedURLException, IOException, CatalogException {

    // The base-base is the cwd. If the catalog file is specified
    // with a relative path, this assures that it gets resolved
    // properly...
    try {
      // tack on a basename because URLs point to files not dirs
      String userdir = fixSlashes(System.getProperty("user.dir"));
      catalogCwd = new URL("file:" + userdir + "/basename");
    } catch (MalformedURLException e) {
      String userdir = fixSlashes(System.getProperty("user.dir"));
      catalogManager.debug.message(1, "Malformed URL on cwd", userdir);
      catalogCwd = null;
    }

    // The initial base URI is the location of the catalog file
    try {
      base = new URL(catalogCwd, fixSlashes(fileName));
    } catch (MalformedURLException e) {
      try {
	base = new URL("file:" + fixSlashes(fileName));
      } catch (MalformedURLException e2) {
	catalogManager.debug.message(1, "Malformed URL on catalog filename",
		      fixSlashes(fileName));
	base = null;
      }
    }

    catalogManager.debug.message(2, "Loading catalog", fileName);
    catalogManager.debug.message(4, "Default BASE", base.toString());

    fileName = base.toString();

    DataInputStream inStream = null;
    boolean parsed = false;
    boolean notFound = false;

    for (int count = 0; !parsed && count < readerArr.size(); count++) {
      CatalogReader reader = (CatalogReader) readerArr.get(count);

      try {
	notFound = false;
	inStream = new DataInputStream(base.openStream());
      } catch (FileNotFoundException fnfe) {
	// No catalog; give up!
	notFound = true;
	break;
      }

      try {
	reader.readCatalog(this, inStream);
	parsed = true;
      } catch (CatalogException ce) {
	if (ce.getExceptionType() == CatalogException.PARSE_FAILED) {
	  // give up!
	  break;
	} else {
	  // try again!
	}
      }

      try {
	inStream.close();
      } catch (IOException e) {
	//nop
      }
    }

    if (!parsed) {
      if (notFound) {
	catalogManager.debug.message(3, "Catalog does not exist", fileName);
      } else {
	catalogManager.debug.message(1, "Failed to parse catalog", fileName);
      }
    }
  }

  /**
   * Cleanup and process a Catalog entry.
   *
   *
This method processes each Catalog entry, changing mapped
   * relative system identifiers into absolute ones (based on the current
   * base URI), and maintaining other information about the current
   * catalog.

   *
   * @param entry The CatalogEntry to process.
   */
  public void addEntry(CatalogEntry entry) {
    int type = entry.getEntryType();

    if (type == BASE) {
      String value = entry.getEntryArg(0);
      URL newbase = null;

      catalogManager.debug.message(5, "BASE CUR", base.toString());
      catalogManager.debug.message(4, "BASE STR", value);

      try {
	value = fixSlashes(value);
	newbase = new URL(base, value);
      } catch (MalformedURLException e) {
	try {
	  newbase = new URL("file:" + value);
	} catch (MalformedURLException e2) {
	  catalogManager.debug.message(1, "Malformed URL on base", value);
	  newbase = null;
	}
      }

      if (newbase != null) {
	base = newbase;
      }

      catalogManager.debug.message(5, "BASE NEW", base.toString());
    } else if (type == CATALOG) {
      String fsi = makeAbsolute(entry.getEntryArg(0));

      catalogManager.debug.message(4, "CATALOG", fsi);

      localCatalogFiles.addElement(fsi);
    } else if (type == PUBLIC) {
      String publicid = PublicId.normalize(entry.getEntryArg(0));
      String systemid = makeAbsolute(normalizeURI(entry.getEntryArg(1)));

      entry.setEntryArg(0, publicid);
      entry.setEntryArg(1, systemid);

      catalogManager.debug.message(4, "PUBLIC", publicid, systemid);

      catalogEntries.addElement(entry);
    } else if (type == SYSTEM) {
      String systemid = normalizeURI(entry.getEntryArg(0));
      String fsi = makeAbsolute(normalizeURI(entry.getEntryArg(1)));

      entry.setEntryArg(1, fsi);

      catalogManager.debug.message(4, "SYSTEM", systemid, fsi);

      catalogEntries.addElement(entry);
    } else if (type == URI) {
      String uri = normalizeURI(entry.getEntryArg(0));
      String altURI = makeAbsolute(normalizeURI(entry.getEntryArg(1)));

      entry.setEntryArg(1, altURI);

      catalogManager.debug.message(4, "URI", uri, altURI);

      catalogEntries.addElement(entry);
    } else if (type == DOCUMENT) {
      String fsi = makeAbsolute(normalizeURI(entry.getEntryArg(0)));
      entry.setEntryArg(0, fsi);

      catalogManager.debug.message(4, "DOCUMENT", fsi);

      catalogEntries.addElement(entry);
    } else if (type == OVERRIDE) {
      catalogManager.debug.message(4, "OVERRIDE", entry.getEntryArg(0));

      catalogEntries.addElement(entry);
    } else if (type == SGMLDECL) {
      // meaningless in XML
      String fsi = makeAbsolute(normalizeURI(entry.getEntryArg(0)));
      entry.setEntryArg(0, fsi);

      catalogManager.debug.message(4, "SGMLDECL", fsi);

      catalogEntries.addElement(entry);
    } else if (type == DELEGATE_PUBLIC) {
      String ppi = PublicId.normalize(entry.getEntryArg(0));
      String fsi = makeAbsolute(normalizeURI(entry.getEntryArg(1)));

      entry.setEntryArg(0, ppi);
      entry.setEntryArg(1, fsi);

      catalogManager.debug.message(4, "DELEGATE_PUBLIC", ppi, fsi);

      addDelegate(entry);
    } else if (type == DELEGATE_SYSTEM) {
      String psi = normalizeURI(entry.getEntryArg(0));
      String fsi = makeAbsolute(normalizeURI(entry.getEntryArg(1)));

      entry.setEntryArg(0, psi);
      entry.setEntryArg(1, fsi);

      catalogManager.debug.message(4, "DELEGATE_SYSTEM", psi, fsi);

      addDelegate(entry);
    } else if (type == DELEGATE_URI) {
      String pui = normalizeURI(entry.getEntryArg(0));
      String fsi = makeAbsolute(normalizeURI(entry.getEntryArg(1)));

      entry.setEntryArg(0, pui);
      entry.setEntryArg(1, fsi);

      catalogManager.debug.message(4, "DELEGATE_URI", pui, fsi);

      addDelegate(entry);
    } else if (type == REWRITE_SYSTEM) {
      String psi = normalizeURI(entry.getEntryArg(0));
      String rpx = makeAbsolute(normalizeURI(entry.getEntryArg(1)));

      entry.setEntryArg(0, psi);
      entry.setEntryArg(1, rpx);

      catalogManager.debug.message(4, "REWRITE_SYSTEM", psi, rpx);

      catalogEntries.addElement(entry);
    } else if (type == REWRITE_URI) {
      String pui = normalizeURI(entry.getEntryArg(0));
      String upx = makeAbsolute(normalizeURI(entry.getEntryArg(1)));

      entry.setEntryArg(0, pui);
      entry.setEntryArg(1, upx);

      catalogManager.debug.message(4, "REWRITE_URI", pui, upx);

      catalogEntries.addElement(entry);
    } else if (type == DOCTYPE) {
      String fsi = makeAbsolute(normalizeURI(entry.getEntryArg(1)));
      entry.setEntryArg(1, fsi);

      catalogManager.debug.message(4, "DOCTYPE", entry.getEntryArg(0), fsi);

      catalogEntries.addElement(entry);
    } else if (type == DTDDECL) {
      // meaningless in XML
      String fpi = PublicId.normalize(entry.getEntryArg(0));
      entry.setEntryArg(0, fpi);
      String fsi = makeAbsolute(normalizeURI(entry.getEntryArg(1)));
      entry.setEntryArg(1, fsi);

      catalogManager.debug.message(4, "DTDDECL", fpi, fsi);

      catalogEntries.addElement(entry);
    } else if (type == ENTITY) {
      String fsi = makeAbsolute(normalizeURI(entry.getEntryArg(1)));
      entry.setEntryArg(1, fsi);

      catalogManager.debug.message(4, "ENTITY", entry.getEntryArg(0), fsi);

      catalogEntries.addElement(entry);
    } else if (type == LINKTYPE) {
      // meaningless in XML
      String fsi = makeAbsolute(normalizeURI(entry.getEntryArg(1)));
      entry.setEntryArg(1, fsi);

      catalogManager.debug.message(4, "LINKTYPE", entry.getEntryArg(0), fsi);

      catalogEntries.addElement(entry);
    } else if (type == NOTATION) {
      String fsi = makeAbsolute(normalizeURI(entry.getEntryArg(1)));
      entry.setEntryArg(1, fsi);

      catalogManager.debug.message(4, "NOTATION", entry.getEntryArg(0), fsi);

      catalogEntries.addElement(entry);
    } else {
      catalogEntries.addElement(entry);
    }
  }

  /**
   * Handle unknown CatalogEntry types.
   *
   *
This method exists to allow subclasses to deal with unknown
   * entry types.

 * @param strings
   */
  public void unknownEntry(Vector strings) {
    if (strings != null && strings.size() > 0) {
      String keyword = (String) strings.elementAt(0);
      catalogManager.debug.message(2, "Unrecognized token parsing catalog", keyword);
    }
  }

  /**
   * Parse all subordinate catalogs.
   *
   *
This method recursively parses all of the subordinate catalogs.
   * If this method does not throw an exception, you can be confident that
   * no subsequent call to any resolve*() method will either, with two
   * possible exceptions:

   *
   *

   *

Delegated catalogs are re-parsed each time they are needed
   * (because a variable list of them may be needed in each case,
   * depending on the length of the matching partial public identifier).

   *
But they are parsed by this method, so as long as they don't
   * change or disappear while the program is running, they shouldn't
   * generate errors later if they don't generate errors now.

   *

If you add new catalogs with parseCatalog, they
   * won't be loaded until they are needed or until you call
   * parseAllCatalogs again.

   *

   *
   *
On the other hand, if you don't call this method, you may
   * successfully parse documents without having to load all possible
   * catalogs.

   *
   * @throws MalformedURLException The filename (URL) for a
   * subordinate or delegated catalog is not a valid URL.
   * @throws IOException Error reading some subordinate or delegated
   * catalog file.
   */
  public void parseAllCatalogs()
    throws MalformedURLException, IOException {

    // Parse all the subordinate catalogs
    for (int catPos = 0; catPos < catalogs.size(); catPos++) {
      Catalog c = null;

      try {
	c = (Catalog) catalogs.elementAt(catPos);
      } catch (ClassCastException e) {
	String catfile = (String) catalogs.elementAt(catPos);
	c = newCatalog();

	c.parseCatalog(catfile);
	catalogs.setElementAt(c, catPos);
	c.parseAllCatalogs();
      }
    }

    // Parse all the DELEGATE catalogs
    Enumeration enumt = catalogEntries.elements();
    while (enumt.hasMoreElements()) {
      CatalogEntry e = (CatalogEntry) enumt.nextElement();
      if (e.getEntryType() == DELEGATE_PUBLIC
	  || e.getEntryType() == DELEGATE_SYSTEM
	  || e.getEntryType() == DELEGATE_URI) {
	Catalog dcat = newCatalog();
	dcat.parseCatalog(e.getEntryArg(1));
      }
    }
  }

  /**
   * Return the applicable DOCTYPE system identifier.
   *
   * @param entityName The name of the entity (element) for which
   * a doctype is required.
   * @param publicId The nominal public identifier for the doctype
   * (as provided in the source document).
   * @param systemId The nominal system identifier for the doctype
   * (as provided in the source document).
   *
   * @return The system identifier to use for the doctype.
   *
   * @throws MalformedURLException The formal system identifier of a
   * subordinate catalog cannot be turned into a valid URL.
   * @throws IOException Error reading subordinate catalog file.
   */
  public String resolveDoctype(String entityName,
			       String publicId,
			       String systemId)
    throws MalformedURLException, IOException {
    String resolved = null;

    catalogManager.debug.message(3, "resolveDoctype("
		  +entityName+","+publicId+","+systemId+")");

    systemId = normalizeURI(systemId);

    if (publicId != null && publicId.startsWith("urn:publicid:")) {
      publicId = PublicId.decodeURN(publicId);
    }

    if (systemId != null && systemId.startsWith("urn:publicid:")) {
      systemId = PublicId.decodeURN(systemId);
      if (publicId != null && !publicId.equals(systemId)) {
	catalogManager.debug.message(1, "urn:publicid: system identifier differs from public identifier; using public identifier");
	systemId = null;
      } else {
	publicId = systemId;
	systemId = null;
      }
    }

    if (systemId != null) {
      // If there's a SYSTEM entry in this catalog, use it
      resolved = resolveLocalSystem(systemId);
      if (resolved != null) {
	return resolved;
      }
    }

    if (publicId != null) {
      // If there's a PUBLIC entry in this catalog, use it
      resolved = resolveLocalPublic(DOCTYPE,
				    entityName,
				    publicId,
				    systemId);
      if (resolved != null) {
	return resolved;
      }
    }

    // If there's a DOCTYPE entry in this catalog, use it
    boolean over = default_override;
    Enumeration enumt = catalogEntries.elements();
    while (enumt.hasMoreElements()) {
      CatalogEntry e = (CatalogEntry) enumt.nextElement();
      if (e.getEntryType() == OVERRIDE) {
	over = e.getEntryArg(0).equalsIgnoreCase("YES");
	continue;
      }

      if (e.getEntryType() == DOCTYPE
	  && e.getEntryArg(0).equals(entityName)) {
	if (over || systemId == null) {
	  return e.getEntryArg(1);
	}
      }
    }

    // Otherwise, look in the subordinate catalogs
    return resolveSubordinateCatalogs(DOCTYPE,
				      entityName,
				      publicId,
				      systemId);
  }

  /**
   * Return the applicable DOCUMENT entry.
   *
   * @return The system identifier to use for the doctype.
   *
   * @throws MalformedURLException The formal system identifier of a
   * subordinate catalog cannot be turned into a valid URL.
   * @throws IOException Error reading subordinate catalog file.
   */
  public String resolveDocument()
    throws MalformedURLException, IOException {
    // If there's a DOCUMENT entry, return it

    catalogManager.debug.message(3, "resolveDocument");

    Enumeration enumt = catalogEntries.elements();
    while (enumt.hasMoreElements()) {
      CatalogEntry e = (CatalogEntry) enumt.nextElement();
      if (e.getEntryType() == DOCUMENT) {
	return e.getEntryArg(1); //FIXME check this
      }
    }

    return resolveSubordinateCatalogs(DOCUMENT,
				      null, null, null);
  }

  /**
   * Return the applicable ENTITY system identifier.
   *
   * @param entityName The name of the entity for which
   * a system identifier is required.
   * @param publicId The nominal public identifier for the entity
   * (as provided in the source document).
   * @param systemId The nominal system identifier for the entity
   * (as provided in the source document).
   *
   * @return The system identifier to use for the entity.
   *
   * @throws MalformedURLException The formal system identifier of a
   * subordinate catalog cannot be turned into a valid URL.
   * @throws IOException Error reading subordinate catalog file.
   */
  public String resolveEntity(String entityName,
			      String publicId,
			      String systemId)
    throws MalformedURLException, IOException {
    String resolved = null;

    catalogManager.debug.message(3, "resolveEntity("
		  +entityName+","+publicId+","+systemId+")");

    systemId = normalizeURI(systemId);

    if (publicId != null && publicId.startsWith("urn:publicid:")) {
      publicId = PublicId.decodeURN(publicId);
    }

    if (systemId != null && systemId.startsWith("urn:publicid:")) {
      systemId = PublicId.decodeURN(systemId);
      if (publicId != null && !publicId.equals(systemId)) {
	catalogManager.debug.message(1, "urn:publicid: system identifier differs from public identifier; using public identifier");
	systemId = null;
      } else {
	publicId = systemId;
	systemId = null;
      }
    }

    if (systemId != null) {
      // If there's a SYSTEM entry in this catalog, use it
      resolved = resolveLocalSystem(systemId);
      if (resolved != null) {
	return resolved;
      }
    }

    if (publicId != null) {
      // If there's a PUBLIC entry in this catalog, use it
      resolved = resolveLocalPublic(ENTITY,
				    entityName,
				    publicId,
				    systemId);
      if (resolved != null) {
	return resolved;
      }
    }

    // If there's a ENTITY entry in this catalog, use it
    boolean over = default_override;
    Enumeration enumt = catalogEntries.elements();
    while (enumt.hasMoreElements()) {
      CatalogEntry e = (CatalogEntry) enumt.nextElement();
      if (e.getEntryType() == OVERRIDE) {
	over = e.getEntryArg(0).equalsIgnoreCase("YES");
	continue;
      }

      if (e.getEntryType() == ENTITY
	  && e.getEntryArg(0).equals(entityName)) {
	if (over || systemId == null) {
	  return e.getEntryArg(1);
	}
      }
    }

    // Otherwise, look in the subordinate catalogs
    return resolveSubordinateCatalogs(ENTITY,
				      entityName,
				      publicId,
				      systemId);
  }

  /**
   * Return the applicable NOTATION system identifier.
   *
   * @param notationName The name of the notation for which
   * a doctype is required.
   * @param publicId The nominal public identifier for the notation
   * (as provided in the source document).
   * @param systemId The nominal system identifier for the notation
   * (as provided in the source document).
   *
   * @return The system identifier to use for the notation.
   *
   * @throws MalformedURLException The formal system identifier of a
   * subordinate catalog cannot be turned into a valid URL.
   * @throws IOException Error reading subordinate catalog file.
   */
  public String resolveNotation(String notationName,
				String publicId,
				String systemId)
    throws MalformedURLException, IOException {
    String resolved = null;

    catalogManager.debug.message(3, "resolveNotation("
		  +notationName+","+publicId+","+systemId+")");

    systemId = normalizeURI(systemId);

    if (publicId != null && publicId.startsWith("urn:publicid:")) {
      publicId = PublicId.decodeURN(publicId);
    }

    if (systemId != null && systemId.startsWith("urn:publicid:")) {
      systemId = PublicId.decodeURN(systemId);
      if (publicId != null && !publicId.equals(systemId)) {
	catalogManager.debug.message(1, "urn:publicid: system identifier differs from public identifier; using public identifier");
	systemId = null;
      } else {
	publicId = systemId;
	systemId = null;
      }
    }

    if (systemId != null) {
      // If there's a SYSTEM entry in this catalog, use it
      resolved = resolveLocalSystem(systemId);
      if (resolved != null) {
	return resolved;
      }
    }

    if (publicId != null) {
      // If there's a PUBLIC entry in this catalog, use it
      resolved = resolveLocalPublic(NOTATION,
				    notationName,
				    publicId,
				    systemId);
      if (resolved != null) {
	return resolved;
      }
    }

    // If there's a NOTATION entry in this catalog, use it
    boolean over = default_override;
    Enumeration enumt = catalogEntries.elements();
    while (enumt.hasMoreElements()) {
      CatalogEntry e = (CatalogEntry) enumt.nextElement();
      if (e.getEntryType() == OVERRIDE) {
	over = e.getEntryArg(0).equalsIgnoreCase("YES");
	continue;
      }

      if (e.getEntryType() == NOTATION
	  && e.getEntryArg(0).equals(notationName)) {
	if (over || systemId == null) {
	  return e.getEntryArg(1);
	}
      }
    }

    // Otherwise, look in the subordinate catalogs
    return resolveSubordinateCatalogs(NOTATION,
				      notationName,
				      publicId,
				      systemId);
  }

  /**
   * Return the applicable PUBLIC or SYSTEM identifier.
   *
   *
This method searches the Catalog and returns the system
   * identifier specified for the given system or
   * public identifiers. If
   * no appropriate PUBLIC or SYSTEM entry is found in the Catalog,
   * null is returned.

   *
   * @param publicId The public identifier to locate in the catalog.
   * Public identifiers are normalized before comparison.
   * @param systemId The nominal system identifier for the entity
   * in question (as provided in the source document).
   *
   * @throws MalformedURLException The formal system identifier of a
   * subordinate catalog cannot be turned into a valid URL.
   * @throws IOException Error reading subordinate catalog file.
   *
   * @return The system identifier to use.
   * Note that the nominal system identifier is not returned if a
   * match is not found in the catalog, instead null is returned
   * to indicate that no match was found.
   */
  public String resolvePublic(String publicId, String systemId)
    throws MalformedURLException, IOException {

    catalogManager.debug.message(3, "resolvePublic("+publicId+","+systemId+")");

    systemId = normalizeURI(systemId);

    if (publicId != null && publicId.startsWith("urn:publicid:")) {
      publicId = PublicId.decodeURN(publicId);
    }

    if (systemId != null && systemId.startsWith("urn:publicid:")) {
      systemId = PublicId.decodeURN(systemId);
      if (publicId != null && !publicId.equals(systemId)) {
	catalogManager.debug.message(1, "urn:publicid: system identifier differs from public identifier; using public identifier");
	systemId = null;
      } else {
	publicId = systemId;
	systemId = null;
      }
    }

    // If there's a SYSTEM entry in this catalog, use it
    if (systemId != null) {
      String resolved = resolveLocalSystem(systemId);
      if (resolved != null) {
	return resolved;
      }
    }

    // If there's a PUBLIC entry in this catalog, use it
    String resolved = resolveLocalPublic(PUBLIC,
					 null,
					 publicId,
					 systemId);
    if (resolved != null) {
      return resolved;
    }

    // Otherwise, look in the subordinate catalogs
    return resolveSubordinateCatalogs(PUBLIC,
				      null,
				      publicId,
				      systemId);
  }

  /**
   * Return the applicable PUBLIC or SYSTEM identifier.
   *
   *
This method searches the Catalog and returns the system
   * identifier specified for the given system or public identifiers.
   * If no appropriate PUBLIC or SYSTEM entry is found in the Catalog,
   * delegated Catalogs are interrogated.

   *
   *
There are four possible cases:

   *
   *

   *
If the system identifier provided matches a SYSTEM entry
   * in the current catalog, the SYSTEM entry is returned.
   *
If the system identifier is not null, the PUBLIC entries
   * that were encountered when OVERRIDE YES was in effect are
   * interrogated and the first matching entry is returned.

   *
If the system identifier is null, then all of the PUBLIC
   * entries are interrogated and the first matching entry
   * is returned. This may not be the same as the preceding case, if
   * some PUBLIC entries are encountered when OVERRIDE NO is in effect. In
   * XML, the only place where a public identifier may occur without
   * a system identifier is in a notation declaration.

   *
Finally, if the public identifier matches one of the partial
   * public identifiers specified in a DELEGATE* entry in
   * the Catalog, the delegated catalog is interrogated. The first
   * time that the delegated catalog is required, it will be
   * retrieved and parsed. It is subsequently cached.
   *

   *

   *
   * @param entityType The CatalogEntry type for which this query is
   * being conducted. This is necessary in order to do the approprate
   * query on a delegated catalog.
   * @param entityName The name of the entity being searched for, if
   * appropriate.
   * @param publicId The public identifier of the entity in question.
   * @param systemId The nominal system identifier for the entity
   * in question (as provided in the source document).
   *
   * @throws MalformedURLException The formal system identifier of a
   * delegated catalog cannot be turned into a valid URL.
   * @throws IOException Error reading delegated catalog file.
   *
   * @return The system identifier to use.
   * Note that the nominal system identifier is not returned if a
   * match is not found in the catalog, instead null is returned
   * to indicate that no match was found.
   */
  protected synchronized String resolveLocalPublic(int entityType,
						   String entityName,
						   String publicId,
						   String systemId)
    throws MalformedURLException, IOException {

    // Always normalize the public identifier before attempting a match
    publicId = PublicId.normalize(publicId);

    // If there's a SYSTEM entry in this catalog, use it
    if (systemId != null) {
      String resolved = resolveLocalSystem(systemId);
      if (resolved != null) {
	return resolved;
      }
    }

    // If there's a PUBLIC entry in this catalog, use it
    boolean over = default_override;
    Enumeration enumt = catalogEntries.elements();
    while (enumt.hasMoreElements()) {
      CatalogEntry e = (CatalogEntry) enumt.nextElement();
      if (e.getEntryType() == OVERRIDE) {
	over = e.getEntryArg(0).equalsIgnoreCase("YES");
	continue;
      }

      if (e.getEntryType() == PUBLIC
	  && e.getEntryArg(0).equals(publicId)) {
	if (over || systemId == null) {
	  return e.getEntryArg(1);
	}
      }
    }

    // If there's a DELEGATE_PUBLIC entry in this catalog, use it
    over = default_override;
    enumt = catalogEntries.elements();
    Vector delCats = new Vector();
    while (enumt.hasMoreElements()) {
      CatalogEntry e = (CatalogEntry) enumt.nextElement();
      if (e.getEntryType() == OVERRIDE) {
	over = e.getEntryArg(0).equalsIgnoreCase("YES");
	continue;
      }

      if (e.getEntryType() == DELEGATE_PUBLIC
	  && (over || systemId == null)) {
	String p = e.getEntryArg(0);
	if (p.length() <= publicId.length()
	    && p.equals(publicId.substring(0, p.length()))) {
	  // delegate this match to the other catalog

	  delCats.addElement(e.getEntryArg(1));
	}
      }
    }

    if (delCats.size() > 0) {
      Enumeration enumCats = delCats.elements();

      if (catalogManager.debug.getDebug() > 1) {
	catalogManager.debug.message(2, "Switching to delegated catalog(s):");
	while (enumCats.hasMoreElements()) {
	  String delegatedCatalog = (String) enumCats.nextElement();
	  catalogManager.debug.message(2, "\t" + delegatedCatalog);
	}
      }

      Catalog dcat = newCatalog();

      enumCats = delCats.elements();
      while (enumCats.hasMoreElements()) {
	String delegatedCatalog = (String) enumCats.nextElement();
	dcat.parseCatalog(delegatedCatalog);
      }

      return dcat.resolvePublic(publicId, null);
    }

    // Nada!
    return null;
  }

  /**
   * Return the applicable SYSTEM system identifier.
   *
   *
If a SYSTEM entry exists in the Catalog
   * for the system ID specified, return the mapped value.

   *
   *
On Windows-based operating systems, the comparison between
   * the system identifier provided and the SYSTEM entries in the
   * Catalog is case-insensitive.

   *
   * @param systemId The system ID to locate in the catalog.
   *
   * @return The resolved system identifier.
   *
   * @throws MalformedURLException The formal system identifier of a
   * subordinate catalog cannot be turned into a valid URL.
   * @throws IOException Error reading subordinate catalog file.
   */
  public String resolveSystem(String systemId)
    throws MalformedURLException, IOException {

    catalogManager.debug.message(3, "resolveSystem("+systemId+")");

    systemId = normalizeURI(systemId);

    if (systemId != null && systemId.startsWith("urn:publicid:")) {
      systemId = PublicId.decodeURN(systemId);
      return resolvePublic(systemId, null);
    }

    // If there's a SYSTEM entry in this catalog, use it
    if (systemId != null) {
      String resolved = resolveLocalSystem(systemId);
      if (resolved != null) {
	return resolved;
      }
    }

    // Otherwise, look in the subordinate catalogs
    return resolveSubordinateCatalogs(SYSTEM,
				      null,
				      null,
				      systemId);
  }

  /**
   * Return the applicable SYSTEM system identifier in this
   * catalog.
   *
   *
If a SYSTEM entry exists in the catalog file
   * for the system ID specified, return the mapped value.

   *
   * @param systemId The system ID to locate in the catalog
   *
   * @return The mapped system identifier or null
   * @throws MalformedURLException
   * @throws IOException
   */
  protected String resolveLocalSystem(String systemId)
    throws MalformedURLException, IOException {

    String osname = System.getProperty("os.name");
    boolean windows = (osname.indexOf("Windows") >= 0);
    Enumeration enumt = catalogEntries.elements();
    while (enumt.hasMoreElements()) {
      CatalogEntry e = (CatalogEntry) enumt.nextElement();
      if (e.getEntryType() == SYSTEM
	  && (e.getEntryArg(0).equals(systemId)
	      || (windows
		  && e.getEntryArg(0).equalsIgnoreCase(systemId)))) {
	return e.getEntryArg(1);
      }
    }

    // If there's a REWRITE_SYSTEM entry in this catalog, use it
    enumt = catalogEntries.elements();
    String startString = null;
    String prefix = null;
    while (enumt.hasMoreElements()) {
      CatalogEntry e = (CatalogEntry) enumt.nextElement();

      if (e.getEntryType() == REWRITE_SYSTEM) {
	String p = e.getEntryArg(0);
	if (p.length() <= systemId.length()
	    && p.equals(systemId.substring(0, p.length()))) {
	  // Is this the longest prefix?
	  if (startString == null
	      || p.length() > startString.length()) {
	    startString = p;
	    prefix = e.getEntryArg(1);
	  }
	}
      }

      if (prefix != null) {
	// return the systemId with the new prefix
	return prefix + systemId.substring(startString.length());
      }
    }

    // If there's a DELEGATE_SYSTEM entry in this catalog, use it
    enumt = catalogEntries.elements();
    Vector delCats = new Vector();
    while (enumt.hasMoreElements()) {
      CatalogEntry e = (CatalogEntry) enumt.nextElement();

      if (e.getEntryType() == DELEGATE_SYSTEM) {
	String p = e.getEntryArg(0);
	if (p.length() <= systemId.length()
	    && p.equals(systemId.substring(0, p.length()))) {
	  // delegate this match to the other catalog

	  delCats.addElement(e.getEntryArg(1));
	}
      }
    }

    if (delCats.size() > 0) {
      Enumeration enumCats = delCats.elements();

      if (catalogManager.debug.getDebug() > 1) {
	catalogManager.debug.message(2, "Switching to delegated catalog(s):");
	while (enumCats.hasMoreElements()) {
	  String delegatedCatalog = (String) enumCats.nextElement();
	  catalogManager.debug.message(2, "\t" + delegatedCatalog);
	}
      }

      Catalog dcat = newCatalog();

      enumCats = delCats.elements();
      while (enumCats.hasMoreElements()) {
	String delegatedCatalog = (String) enumCats.nextElement();
	dcat.parseCatalog(delegatedCatalog);
      }

      return dcat.resolveSystem(systemId);
    }

    return null;
  }

  /**
   * Return the applicable URI.
   *
   *
If a URI entry exists in the Catalog
   * for the URI specified, return the mapped value.

   *
   *
URI comparison is case sensitive.

   *
   * @param uri The URI to locate in the catalog.
   *
   * @return The resolved URI.
   *
   * @throws MalformedURLException The system identifier of a
   * subordinate catalog cannot be turned into a valid URL.
   * @throws IOException Error reading subordinate catalog file.
   */
  public String resolveURI(String uri)
    throws MalformedURLException, IOException {

    catalogManager.debug.message(3, "resolveURI("+uri+")");

    uri = normalizeURI(uri);

    if (uri != null && uri.startsWith("urn:publicid:")) {
      uri = PublicId.decodeURN(uri);
      return resolvePublic(uri, null);
    }

    // If there's a URI entry in this catalog, use it
    if (uri != null) {
      String resolved = resolveLocalURI(uri);
      if (resolved != null) {
	return resolved;
      }
    }

    // Otherwise, look in the subordinate catalogs
    return resolveSubordinateCatalogs(URI,
				      null,
				      null,
				      uri);
  }

  /**
   * Return the applicable URI in this catalog.
   *
   *
If a URI entry exists in the catalog file
   * for the URI specified, return the mapped value.

   *
   * @param uri The URI to locate in the catalog
   *
   * @return The mapped URI or null
   * @throws MalformedURLException
   * @throws IOException
   */
  protected String resolveLocalURI(String uri)
    throws MalformedURLException, IOException {
    Enumeration enumt = catalogEntries.elements();
    while (enumt.hasMoreElements()) {
      CatalogEntry e = (CatalogEntry) enumt.nextElement();
      if (e.getEntryType() == URI
	  && (e.getEntryArg(0).equals(uri))) {
	return e.getEntryArg(1);
      }
    }

    // If there's a REWRITE_URI entry in this catalog, use it
    enumt = catalogEntries.elements();
    String startString = null;
    String prefix = null;
    while (enumt.hasMoreElements()) {
      CatalogEntry e = (CatalogEntry) enumt.nextElement();

      if (e.getEntryType() == REWRITE_URI) {
	String p = e.getEntryArg(0);
	if (p.length() <= uri.length()
	    && p.equals(uri.substring(0, p.length()))) {
	  // Is this the longest prefix?
	  if (startString == null
	      || p.length() > startString.length()) {
	    startString = p;
	    prefix = e.getEntryArg(1);
	  }
	}
      }

      if (prefix != null) {
	// return the systemId with the new prefix
	return prefix + uri.substring(startString.length());
      }
    }

    // If there's a DELEGATE_URI entry in this catalog, use it
    enumt = catalogEntries.elements();
    Vector delCats = new Vector();
    while (enumt.hasMoreElements()) {
      CatalogEntry e = (CatalogEntry) enumt.nextElement();

      if (e.getEntryType() == DELEGATE_URI) {
	String p = e.getEntryArg(0);
	if (p.length() <= uri.length()
	    && p.equals(uri.substring(0, p.length()))) {
	  // delegate this match to the other catalog

	  delCats.addElement(e.getEntryArg(1));
	}
      }
    }

    if (delCats.size() > 0) {
      Enumeration enumCats = delCats.elements();

      if (catalogManager.debug.getDebug() > 1) {
	catalogManager.debug.message(2, "Switching to delegated catalog(s):");
	while (enumCats.hasMoreElements()) {
	  String delegatedCatalog = (String) enumCats.nextElement();
	  catalogManager.debug.message(2, "\t" + delegatedCatalog);
	}
      }

      Catalog dcat = newCatalog();

      enumCats = delCats.elements();
      while (enumCats.hasMoreElements()) {
	String delegatedCatalog = (String) enumCats.nextElement();
	dcat.parseCatalog(delegatedCatalog);
      }

      return dcat.resolveURI(uri);
    }

    return null;
  }

  /**
   * Search the subordinate catalogs, in order, looking for a match.
   *
   *
This method searches the Catalog and returns the system
   * identifier specified for the given entity type with the given
   * name, public, and system identifiers. In some contexts, these
   * may be null.

   *
   * @param entityType The CatalogEntry type for which this query is
   * being conducted. This is necessary in order to do the approprate
   * query on a subordinate catalog.
   * @param entityName The name of the entity being searched for, if
   * appropriate.
   * @param publicId The public identifier of the entity in question
   * (as provided in the source document).
   * @param systemId The nominal system identifier for the entity
   * in question (as provided in the source document). This parameter is
   * overloaded for the URI entry type.
   *
   * @throws MalformedURLException The formal system identifier of a
   * delegated catalog cannot be turned into a valid URL.
   * @throws IOException Error reading delegated catalog file.
   *
   * @return The system identifier to use.
   * Note that the nominal system identifier is not returned if a
   * match is not found in the catalog, instead null is returned
   * to indicate that no match was found.
   */
  protected synchronized String resolveSubordinateCatalogs(int entityType,
							   String entityName,
							   String publicId,
							   String systemId)
    throws MalformedURLException, IOException {

    for (int catPos = 0; catPos < catalogs.size(); catPos++) {
      Catalog c = null;

      try {
	c = (Catalog) catalogs.elementAt(catPos);
      } catch (ClassCastException e) {
	String catfile = (String) catalogs.elementAt(catPos);
	c = newCatalog();

	try {
	  c.parseCatalog(catfile);
	} catch (MalformedURLException mue) {
	  catalogManager.debug.message(1, "Malformed Catalog URL", catfile);
	} catch (FileNotFoundException fnfe) {
	  catalogManager.debug.message(1, "Failed to load catalog, file not found",
			catfile);
	} catch (IOException ioe) {
	  catalogManager.debug.message(1, "Failed to load catalog, I/O error", catfile);
	}

	catalogs.setElementAt(c, catPos);
      }

      String resolved = null;

      // Ok, now what are we supposed to call here?
      if (entityType == DOCTYPE) {
	resolved = c.resolveDoctype(entityName,
				    publicId,
				    systemId);
      } else if (entityType == DOCUMENT) {
	resolved = c.resolveDocument();
      } else if (entityType == ENTITY) {
	resolved = c.resolveEntity(entityName,
				   publicId,
				   systemId);
      } else if (entityType == NOTATION) {
	resolved = c.resolveNotation(entityName,
				     publicId,
				     systemId);
      } else if (entityType == PUBLIC) {
	resolved = c.resolvePublic(publicId, systemId);
      } else if (entityType == SYSTEM) {
	resolved = c.resolveSystem(systemId);
      } else if (entityType == URI) {
	resolved = c.resolveURI(systemId);
      }

      if (resolved != null) {
	return resolved;
      }
    }

    return null;
  }

  // -----------------------------------------------------------------

  /**
   * Replace backslashes with forward slashes. (URLs always use
   * forward slashes.)
   *
   * @param sysid The input system identifier.
   * @return The same system identifier with backslashes turned into
   * forward slashes.
   */
  protected String fixSlashes (String sysid) {
    return sysid.replace('\\', '/');
  }

  /**
   * Construct an absolute URI from a relative one, using the current
   * base URI.
   *
   * @param sysid The (possibly relative) system identifier
   * @return The system identifier made absolute with respect to the
   * current {@link #base}.
   */
  protected String makeAbsolute(String sysid) {
    URL local = null;

    sysid = fixSlashes(sysid);

    try {
      local = new URL(base, sysid);
    } catch (MalformedURLException e) {
      catalogManager.debug.message(1, "Malformed URL on system identifier", sysid);
    }

    if (local != null) {
      return local.toString();
    } else {
      return sysid;
    }
  }

  /**
   * Perform character normalization on a URI reference.
   *
   * @param uriref The URI reference
   * @return The normalized URI reference.
   */
  protected String normalizeURI(String uriref) {
    String newRef = "";
    byte[] bytes;

    if (uriref == null) {
      return null;
    }

    try {
      bytes = uriref.getBytes("UTF-8");
    } catch (UnsupportedEncodingException uee) {
      // this can't happen
      catalogManager.debug.message(1, "UTF-8 is an unsupported encoding!?");
      return uriref;
    }

    for (int count = 0; count < bytes.length; count++) {
      int ch = bytes[count] & 0xFF;

      if ((ch <= 0x20)    // ctrl
	  || (ch > 0x7F)  // high ascii
	  || (ch == 0x22) // "
	  || (ch == 0x3C) // <
	  || (ch == 0x3E) // >
	  || (ch == 0x5C) // \
	  || (ch == 0x5E) // ^
	  || (ch == 0x60) // `
	  || (ch == 0x7B) // {
	  || (ch == 0x7C) // |
	  || (ch == 0x7D) // }
	  || (ch == 0x7F)) {
	newRef += encodedByte(ch);
      } else {
	newRef += (char) bytes[count];
      }
    }

    return newRef;
  }

  /**
   * Perform %-encoding on a single byte.
   *
   * @param b The 8-bit integer that represents th byte. (Bytes are signed
              but encoding needs to look at the bytes unsigned.)
   * @return The %-encoded string for the byte in question.
   */
  protected String encodedByte (int b) {
    String hex = Integer.toHexString(b).toUpperCase();
    if (hex.length() < 2) {
      return "%0" + hex;
    } else {
      return "%" + hex;
    }
  }

  // -----------------------------------------------------------------

  /**
   * Add to the current list of delegated catalogs.
   *
   *
This method always constructs the {@link #localDelegate}
   * vector so that it is ordered by length of partial
   * public identifier.

   *
   * @param entry The DELEGATE catalog entry
   */
  protected void addDelegate(CatalogEntry entry) {
    int pos = 0;
    String partial = entry.getEntryArg(0);

    Enumeration local = localDelegate.elements();
    while (local.hasMoreElements()) {
      CatalogEntry dpe = (CatalogEntry) local.nextElement();
      String dp = dpe.getEntryArg(0);
      if (dp.equals(partial)) {
	// we already have this prefix
	return;
      }
      if (dp.length() > partial.length()) {
	pos++;
      }
      if (dp.length() < partial.length()) {
	break;
      }
    }

    // now insert partial into the vector at [pos]
    if (localDelegate.size() == 0) {
      localDelegate.addElement(entry);
    } else {
      localDelegate.insertElementAt(entry, pos);
    }
  }
}


/*
 * JBoss, Home of Professional Open Source
 * Copyright 2015, Red Hat, Inc., and individual contributors as indicated
 * by the @authors tag.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.jboss.net.sockets;

import java.io.IOException;
import java.io.Serializable;
import java.rmi.server.RMIClientSocketFactory;
import java.net.Socket;

/**
 * A RMIClientSocketFactory that installs a InterruptableInputStream to be
 * responsive to thead interruption events.
 *
 * @author Scott.Stark@jboss.org
 * @version $Revision$
 */
public class TimeoutClientSocketFactory
   implements RMIClientSocketFactory, Serializable
{
   private static final long serialVersionUID = -920483051658660269L;

   public TimeoutClientSocketFactory()
   {
   }

   /**
    * Create a server socket on the specified port (port 0 indicates
    * an anonymous port).
    * @param  port the port number
    * @return the server socket on the specified port
    * @exception java.io.IOException if an I/O error occurs during server socket
    * creation
    * @since 1.2
    */
   public Socket createSocket(String host, int port) throws IOException
   {
      Socket s = new Socket(host, port);
      s.setSoTimeout(1000);
      TimeoutSocket ts = new TimeoutSocket(s);
      return ts;
   }

   public boolean equals(Object obj)
   {
      return obj instanceof TimeoutClientSocketFactory;
   }
   public int hashCode()
   {
      return getClass().getName().hashCode();
   }

}


/*
 * JBoss, Home of Professional Open Source
 * Copyright 2015, Red Hat, Inc., and individual contributors as indicated
 * by the @authors tag.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.jboss.util.propertyeditor;

import java.beans.PropertyEditorSupport;
import java.util.StringTokenizer;

/**
 * A property editor for int[].
 *
 * @version <tt>$Revision$</tt>
 */
public class IntArrayEditor extends PropertyEditorSupport
{
   /** Build a int[] from comma or eol seperated elements
    *
    */
   public void setAsText(final String text)
   {
      StringTokenizer stok = new StringTokenizer(text, ",\r\n");
      int[] theValue = new int[stok.countTokens()];
      int i = 0;
      while (stok.hasMoreTokens())
      {
         theValue[i++] = Integer.decode(stok.nextToken()).intValue();
      }
      setValue(theValue);
   }

   /**
    * @return a comma seperated string of the array elements
    */
   public String getAsText()
   {
      int[] theValue = (int[]) getValue();
      StringBuffer text = new StringBuffer();
      int length = theValue == null ? 0 : theValue.length;
      for(int n = 0; n < length; n ++)
      {
         if (n > 0)
            text.append(',');
         text.append(theValue[n]);
      }
      return text.toString();
   }
}


/*
 * JBoss, Home of Professional Open Source
 * Copyright 2015, Red Hat, Inc., and individual contributors as indicated
 * by the @authors tag.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.jboss.util.collection;

import java.lang.ref.ReferenceQueue;
import java.lang.ref.SoftReference;

/**
 * Soft value ref.
 *
 * @author  Bill Burke
 * @author  Adrian Brock
 * @author  Ales Justin
 * @param <K> the key type
 * @param <V> the value type
 */
class SoftValueRef<K, V> extends SoftReference<V> implements ValueRef<K, V>
{
   /**
    * The key
    */
   public K key;

   /**
    * Safely create a new SoftValueRef
    *
    * @param <K> the key type
    * @param <V> the value type
    * @param key the key
    * @param val the value
    * @param q   the reference queue
    * @return the reference or null if the value is null
    */
   static <K, V> SoftValueRef<K, V> create(K key, V val, ReferenceQueue<V> q)
   {
      if (val == null)
         return null;
      else
         return new SoftValueRef<K, V>(key, val, q);
   }

   /**
    * Create a new SoftValueRef.
    *
    * @param key the key
    * @param val the value
    * @param q   the reference queue
    */
   private SoftValueRef(K key, V val, ReferenceQueue<V> q)
   {
      super(val, q);
      this.key = key;
   }

   public K getKey()
   {
      return key;
   }

   public V getValue()
   {
      return get();
   }

   public V setValue(V value)
   {
      throw new UnsupportedOperationException("setValue");
   }

   @Override
   public String toString()
   {
      return String.valueOf(get());
   }
}


/*
 * JBoss, Home of Professional Open Source
 * Copyright 2015, Red Hat, Inc., and individual contributors as indicated
 * by the @authors tag.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.jboss.util.propertyeditor;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.Iterator;
import java.util.Properties;

import org.jboss.util.NestedRuntimeException;
import org.jboss.util.StringPropertyReplacer;

/**
 * A property editor for {@link java.util.Properties}.
 *
 * @author Jason Dillon
 * @author Scott.Stark@jboss.org
 * @version $Revision$
 */
@SuppressWarnings("unchecked")
public class PropertiesEditor extends TextPropertyEditorSupport
{
   /**
    * Returns a Properties object initialized with current getAsText value
    * interpretted as a .properties file contents. This replaces any
    * references of the form ${x} with the corresponding system property.
    *
    * @return a Properties object
    *
    * @throws NestedRuntimeException  An IOException occured.
    */
   public Object getValue()
   {
      try
      {
         // Load the current key=value properties into a Properties object
         String propsText = getAsText();
         Properties rawProps = new Properties(System.getProperties());
         ByteArrayInputStream bais = new ByteArrayInputStream(propsText.getBytes());
         rawProps.load(bais);
         // Now go through the rawProps and replace any ${x} refs
         Properties props = new Properties();
         Iterator keys = rawProps.keySet().iterator();
         while( keys.hasNext() )
         {
            String key = (String) keys.next();
            String value = rawProps.getProperty(key);
            String value2 = StringPropertyReplacer.replaceProperties(value, rawProps);
            props.setProperty(key, value2);
         }
         rawProps.clear();

         return props;
      }
      catch (IOException e)
      {
         throw new NestedRuntimeException(e);
      }
   }
}


/*
 * JBoss, Home of Professional Open Source
 * Copyright 2015, Red Hat, Inc., and individual contributors as indicated
 * by the @authors tag.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.jboss.util;

import java.util.List;
import java.util.ArrayList;
import java.util.Collections;

/**
 * A static singleton that handles processing throwables that otherwise would
 * be ignored or dumped to System.err.
 *
 * @version <tt>$Revision$</tt>
 * @author  Jason Dillon
 */
public final class ThrowableHandler
{
   /**
    * Container for throwable types.
    */
   public static interface Type
   {
      /** Unknown throwable. */
      int UNKNOWN = 0;

      /** Error throwable. */
      int ERROR = 1;

      /** Warning throwable. */
      int WARNING = 2;
   }

   /////////////////////////////////////////////////////////////////////////
   //                            Listener Methods                         //
   /////////////////////////////////////////////////////////////////////////

   /** The list of listeners */
   protected static List<ThrowableListener> listeners = Collections.synchronizedList(new ArrayList<ThrowableListener>());

   /**
    * Add a ThrowableListener to the listener list.  Listener is added only
    * if if it is not already in the list.
    *
    * @param listener   ThrowableListener to add to the list.
    */
   public static void addThrowableListener(ThrowableListener listener) {
      // only add the listener if it isn't already in the list
      if (!listeners.contains(listener)) {
         listeners.add(listener);
      }
   }

   /**
    * Remove a ThrowableListener from the listener list.
    *
    * @param listener   ThrowableListener to remove from the list.
    */
   public static void removeThrowableListener(ThrowableListener listener) {
      listeners.remove(listener);
   }

   /**
    * Fire onThrowable to all registered listeners.
    *
    * @param type    The type off the throwable.
    * @param t       Throwable
    */
   protected static void fireOnThrowable(int type, Throwable t) {
      Object[] list = listeners.toArray();

      for (int i=0; i<list.length; i++) {
         ((ThrowableListener)list[i]).onThrowable(type, t);
      }
   }

   /////////////////////////////////////////////////////////////////////////
   //                          Throwable Processing                       //
   /////////////////////////////////////////////////////////////////////////

   /**
    * Add a throwable that is to be handled.
    *
    * @param type    The type off the throwable.
    * @param t       Throwable to be handled.
    */
   public static void add(int type, Throwable t) {
      // don't add null throwables
      if (t == null) return;

      try {
         fireOnThrowable(type, t);
      }
      catch (Throwable bad) {
         // don't let these propagate, that could introduce unwanted side-effects
         System.err.println("Unable to handle throwable: " + t + " because of:");
         bad.printStackTrace();
      }
   }

   /**
    * Add a throwable that is to be handled with unknown type.
    *
    * @param t    Throwable to be handled.
    */
   public static void add(Throwable t) {
      add(Type.UNKNOWN, t);
   }

   /**
    * Add a throwable that is to be handled with error type.
    *
    * @param t    Throwable to be handled.
    */
   public static void addError(Throwable t) {
      add(Type.ERROR, t);
   }

   /**
    * Add a throwable that is to be handled with warning type.
    *
    * @param t    Throwable to be handled.
    */
   public static void addWarning(Throwable t) {
      add(Type.ERROR, t);
   }
}


/*
 * JBoss, Home of Professional Open Source
 * Copyright 2015, Red Hat, Inc., and individual contributors as indicated
 * by the @authors tag.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.jboss.util.collection;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.io.Serializable;

/**
 * LazyList.
 * It's serializable if the elements are serializable.
 *
 * @param <T> the collection type
 * @author Adrian Brock
 * @version $Revision$
 */
public class LazyList<T> implements List<T>, Serializable
{
   /** The serialVersionUID */
   private static final long serialVersionUID = 1L;
   /** The delegate list */
   private List<T> delegate = Collections.emptyList();

   /**
    * Create the list implementation
    *
    * @return the list
    */
   private List<T> createImplementation()
   {
      if (delegate instanceof ArrayList == false)
         return new ArrayList<T>(delegate);
      return delegate;
   }

   public void add(int index, T element)
   {
      delegate = createImplementation();
      delegate.add(index, element);
   }

   public boolean add(T o)
   {
      if (delegate.isEmpty())
      {
         delegate = Collections.singletonList(o);
         return true;
      }
      else
      {
         delegate = createImplementation();
         return delegate.add(o);
      }
   }

   public boolean addAll(Collection<? extends T> c)
   {
      delegate = createImplementation();
      return delegate.addAll(c);
   }

   public boolean addAll(int index, Collection<? extends T> c)
   {
      delegate = createImplementation();
      return delegate.addAll(index, c);
   }

   public void clear()
   {
      delegate = Collections.emptyList();
   }

   public boolean contains(Object o)
   {
      return delegate.contains(o);
   }

   public boolean containsAll(Collection<?> c)
   {
      return delegate.containsAll(c);
   }

   public T get(int index)
   {
      return delegate.get(index);
   }

   public int indexOf(Object o)
   {
      return delegate.indexOf(o);
   }

   public boolean isEmpty()
   {
      return delegate.isEmpty();
   }

   public Iterator<T> iterator()
   {
      return delegate.iterator();
   }

   public int lastIndexOf(Object o)
   {
      return delegate.lastIndexOf(o);
   }

   public ListIterator<T> listIterator()
   {
      return delegate.listIterator();
   }

   public ListIterator<T> listIterator(int index)
   {
      return delegate.listIterator(index);
   }

   public T remove(int index)
   {
      delegate = createImplementation();
      return delegate.remove(index);
   }

   public boolean remove(Object o)
   {
      delegate = createImplementation();
      return delegate.remove(o);
   }

   public boolean removeAll(Collection<?> c)
   {
      delegate = createImplementation();
      return delegate.removeAll(c);
   }

   public boolean retainAll(Collection<?> c)
   {
      delegate = createImplementation();
      return delegate.retainAll(c);
   }

   public T set(int index, T element)
   {
      delegate = createImplementation();
      return delegate.set(index, element);
   }

   public int size()
   {
      return delegate.size();
   }

   public List<T> subList(int fromIndex, int toIndex)
   {
      return delegate.subList(fromIndex, toIndex);
   }

   public Object[] toArray()
   {
      return delegate.toArray();
   }

   public  U[] toArray(U[] a)
   {
      return delegate.toArray(a);
   }

   @Override
   public String toString()
   {
      return delegate.toString();
   }
}


/*
 * JBoss, Home of Professional Open Source
 * Copyright 2015, Red Hat, Inc., and individual contributors as indicated
 * by the @authors tag.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.jboss.util.collection;

import java.util.List;
import java.util.ArrayList;
import java.util.Set;
import java.util.AbstractSet;
import java.util.Iterator;
import java.util.Collection;
import java.io.Serializable;

import org.jboss.util.NullArgumentException;

/**
 * A thin wrapper around a List transforming it into a
 * modifiable Set.
 *
 * @version <tt>$Revision$</tt>
 * @author  Jason Dillon
 */
@SuppressWarnings("unchecked")
public class ListSet
   extends AbstractSet
   implements Set, Cloneable, Serializable
{
   /** The serialVersionUID */
   private static final long serialVersionUID = 7333619218072079496L;
   /** The <tt>List</tt> which will be used for element storage. */
   protected final List list;

   /**
    * Construct a <tt>ListSet</tt>.
    *
    * @param list    The <tt>List</tt> which will be used for element storage.
    *
    * @throws IllegalArgumentException    List is <tt>null</tt> or contains
    *                                     duplicate entries.
    */
   public ListSet(final List list) {
      if (list == null)
         throw new NullArgumentException("list");

      // make sure there are no duplicates
      int size = list.size();
      for (int i=0; i<size; i++) {
         Object obj = list.get(i);
         if (list.indexOf(obj) != list.lastIndexOf(obj)) {
            throw new IllegalArgumentException
               ("list contains duplicate entries");
         }
      }

      this.list = list;
   }

   /**
    * Construct a <tt>ListSet</tt> using an <tt>ArrayList</tt> for backing.
    */
   public ListSet() {
      this(new ArrayList());
   }

   /**
    * Construct a <tt>ListSet</tt> using an <tt>ArrayList</tt> for backing
    * and populated with the given elements.
    *
    * @param elements   The elements for the list.
    */
   public ListSet(final Collection elements) {
      this(new ArrayList(elements));
   }

   public List getList()
   {
      return list;
   }

   /**
    * Return the size of the set.
    *
    * @return  The size of the set.
    */
   public int size() {
      return list.size();
   }

   /**
    * Return an iteration over the elements in the set.
    *
    * @return  An iteration over the elements in the set.
    */
   public Iterator iterator() {
      return list.iterator();
   }

   /**
    * Add an element to the set.
    *
    * @param obj  Element to add to the set.
    * @return     True if the element was added.
    */
   public boolean add(final Object obj) {
      boolean added = false;

      if (!list.contains(obj)) {
         added = list.add(obj);
      }

      return added;
   }

   /**
    * Returns <tt>true</tt> if this set contains no elements.
    *
    * @return  <tt>true</tt> if this set contains no elements.
    */
   public boolean isEmpty() {
      return list.isEmpty();
   }

   /**
    * Returns <tt>true</tt> if this set contains the specified element.
    *
    * @param obj  Element whose presence in this set is to be tested.
    * @return     <tt>true</tt> if this set contains the specified element.
    */
   public boolean contains(final Object obj) {
      return list.contains(obj);
   }

   /**
    * Removes the given element from this set if it is present.
    *
    * @param obj  Object to be removed from this set, if present.
    * @return     <tt>true</tt> if the set contained the specified element.
    */
   public boolean remove(final Object obj) {
      return list.remove(obj);
   }

   /**
    * Removes all of the elements from this set.
    */
   public void clear() {
      list.clear();
   }

   /**
     * Returns a shallow copy of this <tt>ListSet</tt> instance.
     *
     * @return    A shallow copy of this set.
     */
   public Object clone() {
      try {
         return super.clone();
      }
      catch (CloneNotSupportedException e) {
         throw new InternalError();
      }
   }
}


/*
 * JBoss, Home of Professional Open Source
 * Copyright 2015, Red Hat, Inc., and individual contributors as indicated
 * by the @authors tag.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.jboss.net.sockets;

import java.io.IOException;
import java.rmi.server.RMIClientSocketFactory;
import java.net.Socket;
import java.util.concurrent.Semaphore;

/**
 *
 * @author Scott.Stark@jboss.org
 * @version $Revision$
 */
public class QueuedClientSocketFactory
   implements RMIClientSocketFactory, java.io.Externalizable
{
   private transient Semaphore permits;
   private long numPermits;
   public QueuedClientSocketFactory()
   {
   }

   public QueuedClientSocketFactory(long nPermits)
   {
      permits = new Semaphore((int)nPermits, true);
      numPermits = nPermits;
   }
   /**
    * Create a server socket on the specified port (port 0 indicates
    * an anonymous port).
    * @param  port the port number
    * @return the server socket on the specified port
    * @exception IOException if an I/O error occurs during server socket
    * creation
    * @since 1.2
    */
   public Socket createSocket(String host, int port) throws IOException
   {
      try
      {
         permits.acquire();
         return new Socket(host, port);
      }
      catch (InterruptedException ex)
      {
         throw new IOException("Failed to acquire FIFOSemaphore for ClientSocketFactory");
      }
      finally
      {
         permits.release();
      }
   }

   public boolean equals(Object obj)
   {
      return obj instanceof QueuedClientSocketFactory;
   }
   public int hashCode()
   {
      return getClass().getName().hashCode();
   }

   public void writeExternal(java.io.ObjectOutput out)
      throws IOException
   {
      out.writeLong(numPermits);
   }
   public void readExternal(java.io.ObjectInput in)
      throws IOException, ClassNotFoundException
   {
      numPermits = in.readLong();
      permits = new Semaphore((int)numPermits, true);
   }
}


/*
 * JBoss, Home of Professional Open Source
 * Copyright 2015, Red Hat, Inc., and individual contributors as indicated
 * by the @authors tag.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.jboss.net.sockets;

import java.io.IOException;
import java.io.Serializable;
import java.rmi.server.RMIClientSocketFactory;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;

/**
 * A RMIClientSocketFactory that adds a bind address override of the server
 * host to control what the address the client uses.
 *
 * @author Scott.Stark@jboss.org
 * @version $Revision: 30203 $
 */
public class DefaultClientSocketFactory
   implements RMIClientSocketFactory, Serializable
{
   private static final long serialVersionUID = -920483051658660269L;
   /** An override of the server address */
   private InetAddress bindAddress;

   public DefaultClientSocketFactory()
   {
   }

   public String getBindAddress()
   {
      String address = null;
      if( bindAddress != null )
         address = bindAddress.getHostAddress();
      return address;
   }
   public void setBindAddress(String host) throws UnknownHostException
   {
      bindAddress = InetAddress.getByName(host);
   }

   /**
    * Create a server socket on the specified port (port 0 indicates
    * an anonymous port).
    * @param  port the port number
    * @return the server socket on the specified port
    * @exception java.io.IOException if an I/O error occurs during server socket
    * creation
    * @since 1.2
    */
   public Socket createSocket(String host, int port) throws IOException
   {
      InetAddress addr = null;
      if( bindAddress != null )
         addr = bindAddress;
      else
         addr = InetAddress.getByName(host);
      Socket s = new Socket(addr, port);
      return s;
   }

   public boolean equals(Object obj)
   {
      boolean equals = obj instanceof DefaultClientSocketFactory;
      if( equals && bindAddress != null )
      {
         DefaultClientSocketFactory dcsf = (DefaultClientSocketFactory) obj;
         InetAddress dcsfa = dcsf.bindAddress;
         if( dcsfa != null )
            equals = bindAddress.equals(dcsfa);
         else
            equals = false;
      }
      return equals;
   }
   public int hashCode()
   {
      int hashCode = getClass().getName().hashCode();
      if( bindAddress != null )
         hashCode += bindAddress.toString().hashCode();
      return hashCode;
   }
   public String toString()
   {
      StringBuffer tmp = new StringBuffer(super.toString());
      tmp.append('[');
      tmp.append("bindAddress=");
      tmp.append(bindAddress);
      tmp.append(']');
      return tmp.toString();
   }
}


/*
 * JBoss, Home of Professional Open Source
 * Copyright 2015, Red Hat, Inc., and individual contributors as indicated
 * by the @authors tag.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.jboss.util;

/**
 * A class that represent a task that can be scheduled for one-shot or
 * repeated execution by a {@link TimerQueue}.

 * A similar class is present in java.util package of jdk version >= 1.3;
 * for compatibility with jdk 1.2 we reimplemented it.
 *
 * @see TimerQueue
 *
 * @author Simone Bordet
 * @version $Revision$
 */
@SuppressWarnings("unchecked")
public abstract class TimerTask
   implements Executable, Comparable
{
   /** The state before the first execution */
   static final int NEW = 1;
   /** The state after first execution if the TimerTask is repeating */
   static final int SCHEDULED = 2;
   /** The state after first execution if the TimerTask is not repeating */
   static final int EXECUTED = 3;
   /** The state when cancelled */
   static final int CANCELLED = 4;

   // Attributes ----------------------------------------------------
   private final Object m_lock = new Object();
   private int m_state;
   // this is a constant, and need not be locked
   private final long m_period;
   private long m_nextExecutionTime;

   /**
    * Creates a TimerTask object that will be executed once.
    */
   protected TimerTask()
   {
      m_state = NEW;
      m_period = 0;
   }

   /**
    * Creates a TimerTask object that will be executed every period
    * milliseconds.

    * @param period the execution period; if zero, will be executed only once.
    */
   protected TimerTask(long period)
   {
      m_state = NEW;
      if (period < 0) throw new IllegalArgumentException("Period can't be negative");
      m_period = period;
   }

   /**
    * Cancels the next execution of this TimerTask (if any).

    * If the TimerTask is executing it will prevent the next execution (if any).
    * @return true if one or more scheduled execution will not take place,
    * false otherwise.
    */
   public boolean cancel()
   {
      synchronized (getLock())
      {
         boolean ret = (m_state == SCHEDULED);
         m_state = CANCELLED;
         return ret;
      }
   }

   // Executable implementation ---------------------------------------

   /**
    * The task to be executed, to be implemented in subclasses.
    */
   public abstract void execute() throws Exception;

   // Comparable implementation ---------------------------------------

   /**
    * A TimerTask is less than another if it will be scheduled before.
    * @throws ClassCastException if other is not a TimerTask, according to the Comparable contract
    */
   public int compareTo(Object other)
   {
      if (other == this) return 0;
      TimerTask t = (TimerTask) other;
      long diff = getNextExecutionTime() - t.getNextExecutionTime();
      return (int) diff;
   }

   /** Returns the mutex that syncs the access to this object */
   Object getLock()
   {
      return m_lock;
   }

   /** Sets the state of execution of this TimerTask */
   void setState(int state)
   {
      synchronized (getLock())
      {
         m_state = state;
      }
   }

   /** Returns the state of execution of this TimerTask */
   int getState()
   {
      synchronized (getLock())
      {
         return m_state;
      }
   }

   /** Returns whether this TimerTask is periodic */
   boolean isPeriodic()
   {
      return m_period > 0;
   }

   /** Returns the next execution time for this TimerTask */
   long getNextExecutionTime()
   {
      synchronized (getLock())
      {
         return m_nextExecutionTime;
      }
   }

   /** Sets the next execution time for this TimerTask */
   void setNextExecutionTime(long time)
   {
      synchronized (getLock())
      {
         m_nextExecutionTime = time;
      }
   }

   /** @return the period of this TimerTask */
   protected long getPeriod()
   {
      return m_period;
   }
}