/*
 * 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.HashMap;
import java.util.Map;

/**
 * Implementation of a Least Recently Used cache policy.
 *
 * @author Simone Bordet
 * @version $Revision$
 */
@SuppressWarnings("unchecked")
public class LRUCachePolicy
   implements CachePolicy
{
   // Constants -----------------------------------------------------

   // Attributes ----------------------------------------------------
   /**
    * The map holding the cached objects
    */
   protected Map m_map;
   /**
    * The linked list used to implement the LRU algorithm
    */
   protected LRUList m_list;
   /**
    * The maximum capacity of this cache
    */
   protected int m_maxCapacity;
   /**
    * The minimum capacity of this cache
    */
   protected int m_minCapacity;

   // Static --------------------------------------------------------

   // Constructors --------------------------------------------------
   /**
    * Creates a LRU cache policy object with zero cache capacity.
    *
    * @see #create
    */
   public LRUCachePolicy()
   {
   }
   /**
    * Creates a LRU cache policy object with the specified minimum
    * and maximum capacity.
    * @param min
    * @param max
    *
    * @see #create
    */
   public LRUCachePolicy(int min, int max)
   {
      if (min < 2 || min > max) {throw new IllegalArgumentException("Illegal cache capacities");}
      m_minCapacity = min;
      m_maxCapacity = max;
   }

   /**
    * Create map holding entries.
    *
    * @return the map
    */
   protected Map createMap()
   {
      return new HashMap();
   }

   // Public --------------------------------------------------------

   // Service implementation ----------------------------------------------
   /**
    * Initializes the cache, creating all required objects and initializing their
    * values.
    * @see #start
    * @see #destroy
    */
   public void create()
   {
      m_map = createMap();
      m_list = createList();
      m_list.m_maxCapacity = m_maxCapacity;
      m_list.m_minCapacity = m_minCapacity;
      m_list.m_capacity = m_maxCapacity;
   }
   /**
    * Starts this cache that is now ready to be used.
    * @see #create
    * @see #stop
    */
   public void start()
   {
   }
   /**
    * Stops this cache thus {@link #flush}ing all cached objects.

    * After this method is called, a call to {@link #start} will restart the cache.
    * @see #start
    * @see #destroy
    */
   public void stop()
   {
      if (m_list != null)
      {
         flush();
      }
   }
   /**
    * Destroys the cache that is now unusable.

    * To have it working again it must be re-{@link #create}ed and
    * re-{@link #start}ed.
    *
    * @see #create
    */
   public void destroy()
   {
      if( m_map != null )
         m_map.clear();
      if( m_list != null )
         m_list.clear();
   }

   public Object get(Object key)
   {
      if (key == null)
      {
         throw new IllegalArgumentException("Requesting an object using a null key");
      }

      LRUCacheEntry value = (LRUCacheEntry)m_map.get(key);
      if (value != null)
      {
         m_list.promote(value);
         return value.m_object;
      }
      else
      {
         cacheMiss();
         return null;
      }
   }
   public Object peek(Object key)
   {
      if (key == null)
      {
         throw new IllegalArgumentException("Requesting an object using a null key");
      }

      LRUCacheEntry value = (LRUCacheEntry)m_map.get(key);
      if (value == null)
      {
         return null;
      }
      else
      {
         return value.m_object;
      }
   }
   public void insert(Object key, Object o)
   {
      if (o == null) {throw new IllegalArgumentException("Cannot insert a null object in the cache");}
      if (key == null) {throw new IllegalArgumentException("Cannot insert an object in the cache with null key");}
      if (m_map.containsKey(key))
      {
         throw new IllegalStateException("Attempt to put in the cache an object that is already there");
      }
      m_list.demote();
      LRUCacheEntry entry = createCacheEntry(key, o);
      m_map.put(key, entry);
      m_list.promote(entry);
   }
   public void remove(Object key)
   {
      if (key == null) {throw new IllegalArgumentException("Removing an object using a null key");}

      Object value = m_map.remove(key);
      if (value != null)
      {
         m_list.remove((LRUCacheEntry)value);
      }
      //else Do nothing, the object isn't in the cache list
   }
   public void flush()
   {
      LRUCacheEntry entry = null;
      while ((entry = m_list.m_tail) != null)
      {
         ageOut(entry);
      }
   }

   public int size() {
      return m_list.m_count;
   }

   // Y overrides ---------------------------------------------------

   // Package protected ---------------------------------------------

   // Protected -----------------------------------------------------
   /**
    * Factory method for the linked list used by this cache implementation.
    * @return the lru list
    */
   protected LRUList createList() {return new LRUList();}
   /**
    * Callback method called when the cache algorithm ages out of the cache
    * the given entry.

    * The implementation here is removing the given entry from the cache.
    * @param entry
    */
   protected void ageOut(LRUCacheEntry entry)
   {
      remove(entry.m_key);
   }
   /**
    * Callback method called when a cache miss happens.
    */
   protected void cacheMiss()
   {
   }
   /**
    * Factory method for cache entries
    * @param key
    * @param value
    * @return the entry
    */
   protected LRUCacheEntry createCacheEntry(Object key, Object value)
   {
      return new LRUCacheEntry(key, value);
   }

   // Private -------------------------------------------------------

   // Inner classes -------------------------------------------------
   /**
    * Double queued list used to store cache entries.
    */
   public class LRUList
   {
      /** The maximum capacity of the cache list */
      @SuppressWarnings("hiding")
      public int m_maxCapacity;
      /** The minimum capacity of the cache list */
      @SuppressWarnings("hiding")
      public int m_minCapacity;
      /** The current capacity of the cache list */
      public int m_capacity;
      /** The number of cached objects */
      public int m_count;
      /** The head of the double linked list */
      public LRUCacheEntry m_head;
      /** The tail of the double linked list */
      public LRUCacheEntry m_tail;
      /** The cache misses happened */
      public int m_cacheMiss;
      /**
       * Creates a new double queued list.
       */
      protected LRUList()
      {
         m_head = null;
         m_tail = null;
         m_count = 0;
      }
      /**
       * Promotes the cache entry entry to the last used position
       * of the list.

       * If the object is already there, does nothing.
       * @param entry the object to be promoted, cannot be null
       * @see #demote
       * @throws IllegalStateException if this method is called with a full cache
       */
      protected void promote(LRUCacheEntry entry)
      {
         if (entry == null) {throw new IllegalArgumentException("Trying to promote a null object");}
         if (m_capacity < 1) {throw new IllegalStateException("Can't work with capacity < 1");}

         entryPromotion(entry);

         entry.m_time = System.currentTimeMillis();
         if (entry.m_prev == null)
         {
            if (entry.m_next == null)
            {
               // entry is new or there is only the head
               if (m_count == 0) // cache is empty
               {
                  m_head = entry;
                  m_tail = entry;
                  ++m_count;
                  entryAdded(entry);
               }
               else if (m_count == 1 && m_head == entry) {} // there is only the head and I want to promote it, do nothing
               else if (m_count < m_capacity)
               {
                  entry.m_prev = null;
                  entry.m_next = m_head;
                  m_head.m_prev = entry;
                  m_head = entry;
                  ++m_count;
                  entryAdded(entry);
               }
               else if (m_count < m_maxCapacity)
               {
                  entry.m_prev = null;
                  entry.m_next = m_head;
                  m_head.m_prev = entry;
                  m_head = entry;
                  ++m_count;
                  int oldCapacity = m_capacity;
                  ++m_capacity;
                  entryAdded(entry);
                  capacityChanged(oldCapacity);
               }
               else {throw new IllegalStateException("Attempt to put a new cache entry on a full cache");}
            }
            else {} // entry is the head, do nothing
         }
         else
         {
            if (entry.m_next == null) // entry is the tail
            {
               LRUCacheEntry beforeLast = entry.m_prev;
               beforeLast.m_next = null;
               entry.m_prev = null;
               entry.m_next = m_head;
               m_head.m_prev = entry;
               m_head = entry;
               m_tail = beforeLast;
            }
            else // entry is in the middle of the list
            {
               LRUCacheEntry previous = entry.m_prev;
               previous.m_next = entry.m_next;
               entry.m_next.m_prev = previous;
               entry.m_prev = null;
               entry.m_next = m_head;
               m_head.m_prev = entry;
               m_head = entry;
            }
         }
      }
      /**
       * Demotes from the cache the least used entry.

       * If the cache is not full, does nothing.
       * @see #promote
       */
      protected void demote()
      {
         if (m_capacity < 1) {throw new IllegalStateException("Can't work with capacity < 1");}
         if (m_count > m_maxCapacity) {throw new IllegalStateException("Cache list entries number (" + m_count + ") > than the maximum allowed (" + m_maxCapacity + ")");}
         if (m_count == m_maxCapacity)
         {
            LRUCacheEntry entry = m_tail;

            // the entry will be removed by ageOut
            ageOut(entry);
         }
         else {} // cache is not full, do nothing
      }
      /**
       * Removes from the cache list the specified entry.
       * @param entry
       */
      protected void remove(LRUCacheEntry entry)
      {
         if (entry == null) {throw new IllegalArgumentException("Cannot remove a null entry from the cache");}
         if (m_count < 1) {throw new IllegalStateException("Trying to remove an entry from an empty cache");}

         entry.m_key = entry.m_object = null;
         if (m_count == 1)
         {
            m_head = m_tail = null;
         }
         else
         {
            if (entry.m_prev == null) // the head
            {
               m_head = entry.m_next;
               m_head.m_prev = null;
               entry.m_next = null;
            }
            else if (entry.m_next == null) // the tail
            {
               m_tail = entry.m_prev;
               m_tail.m_next = null;
               entry.m_prev = null;
            }
            else // in the middle
            {
               entry.m_next.m_prev = entry.m_prev;
               entry.m_prev.m_next = entry.m_next;
               entry.m_prev = null;
               entry.m_next = null;
            }
         }
         --m_count;
         entryRemoved(entry);
      }
      /**
       * Callback that signals that the given entry is just about to be added.
       * @param entry
       */
      protected void entryPromotion(LRUCacheEntry entry) {}
      /**
       * Callback that signals that the given entry has been added to the cache.
       * @param entry
       */
      protected void entryAdded(LRUCacheEntry entry) {}
      /**
       * Callback that signals that the given entry has been removed from the cache.
       * @param entry
       */
      protected void entryRemoved(LRUCacheEntry entry) {}
      /**
       * Callback that signals that the capacity of the cache is changed.
       * @param oldCapacity the capacity before the change happened
       */
      protected void capacityChanged(int oldCapacity) {}

      protected void clear()
      {
         LRUCacheEntry entry = m_head;
         m_head = null;
         m_tail = null;
         m_count = 0;
         for (; entry != null; entry = entry.m_next)
            entryRemoved(entry);
      }

      public String toString()
      {
         String s = Integer.toHexString(super.hashCode());
         s += " size: " + m_count;
         for (LRUCacheEntry entry = m_head; entry != null; entry = entry.m_next)
         {
            s += "\n" + entry;
         }
         return s;
      }
   }

   /**
    * Double linked cell used as entry in the cache list.
    */
   public class LRUCacheEntry
   {
      /** Reference to the next cell in the list */
      public  LRUCacheEntry m_next;
      /** Reference to the previous cell in the list */
      public LRUCacheEntry m_prev;
      /** The key used to retrieve the cached object */
      public Object m_key;
      /** The cached object */
      public Object m_object;
      /** The timestamp of the creation */
      public long m_time;
      /**
       * Creates a new double linked cell, storing the object we
       * want to cache and the key that is used to retrieve it.
       * @param key
       * @param object
       */
      protected LRUCacheEntry(Object key, Object object)
      {
         m_key = key;
         m_object = object;
         m_next = null;
         m_prev = null;
         m_time = 0; // Set when inserted in the list.
      }
      public String toString()
      {
         return "key: " + m_key + ", object: " + ( m_object==null ? "null" : Integer.toHexString(m_object.hashCode())) + ", entry: " + Integer.toHexString(super.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.property;

import org.jboss.util.FieldInstance;
import org.jboss.util.NullArgumentException;
import org.jboss.util.Classes;
import org.jboss.util.ThrowableHandler;
import org.jboss.util.propertyeditor.PropertyEditors;

import java.beans.PropertyEditor;

/**
 * Binds property values to class fields.
 *
 * @version <tt>$Revision$</tt>
 * @author  Jason Dillon
 */
public class FieldBoundPropertyListener
   extends BoundPropertyAdapter
{
   /** Property name which we are bound to */
   protected final String propertyName;

   /** Field instance */
   protected final FieldInstance fieldInstance;

   /**
    * Constructs a <tt>FieldBoundPropertyListener</tt>.
    *
    * @param instance         Instance object.
    * @param fieldName        Field name.
    * @param propertyName     Property to bind to.
    *
    * @throws NullArgumentException    Property name is <tt>null</tt>.
    */
   public FieldBoundPropertyListener(final Object instance,
                                     final String fieldName,
                                     final String propertyName)
   {
      if (propertyName == null)
         throw new NullArgumentException("propertyName");
      // FieldInstance checks instance & fieldName

      this.propertyName = propertyName;

      try {
         // construct field instance
         fieldInstance = new FieldInstance(instance, fieldName);
         try {
            fieldInstance.getField().setAccessible(true);
         }
         catch (SecurityException e) {
            ThrowableHandler.add(e);
         }

         // force the given class to load, so that any CoersionHelpers
         // that are nested in it are loaded properly
         Classes.forceLoad(fieldInstance.getField().getType());
      }
      catch (NoSuchFieldException e) {
         throw new PropertyException(e);
      }
   }

   /**
    * Constructs a <tt>FieldBoundPropertyListener</tt>.
    *
    *
Field name is used for property name.
    *
    * @param instance         Instance object.
    * @param fieldName        Field name.
    */
   public FieldBoundPropertyListener(final Object instance,
                                     final String fieldName)
   {
      this(instance, fieldName, fieldName);
   }

   /**
    * Get the property name which this listener is bound to.
    *
    * @return     Property name.
    */
   public final String getPropertyName() {
      return propertyName;
   }

   /**
    * Filter the property value prior to coercing and binding to field.
    *
    *
Allows instance to filter values prior to object coercion and
    *    field binding.
    *
    * @param value   Property value.
    * @return the filtered value
    */
   public String filterValue(String value) {
      return value;
   }

   /**
    * Coerce and set specified value to field.
    *
    * @param value   Field value.
    *
    * @throws PropertyException     Failed to set field value.
    */
   protected void setFieldValue(String value) {
      try {
         // filter property value
         value = filterValue(value);

         // coerce value to field type
         Class<?> type = fieldInstance.getField().getType();
         PropertyEditor editor = PropertyEditors.findEditor(type);
         editor.setAsText(value);
         Object coerced = editor.getValue();

         // bind value to field
         fieldInstance.set(coerced);
      }
      catch (IllegalAccessException e) {
         throw new PropertyException(e);
      }
   }

   /**
    * Notifies that a property has been added.
    *
    * @param event   Property event.
    */
   public void propertyAdded(final PropertyEvent event) {
      setFieldValue(event.getPropertyValue());
   }

   /**
    * Notifies that a property has changed
    *
    * @param event   Property event
    */
   public void propertyChanged(final PropertyEvent event) {
      setFieldValue(event.getPropertyValue());
   }

   /**
    * Notifies that this listener was bound to a property.
    *
    * @param map     PropertyMap which contains property bound to.
    */
   public void propertyBound(final PropertyMap map) {
      // only set the field if the map contains the property already
      if (map.containsProperty(propertyName)) {
         setFieldValue(map.getProperty(propertyName));
      }
   }
}


/*
 * 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.
 */

// TextCatalogReader.java - Read text/plain Catalog files

package org.jboss.util.xml.catalog.readers;

import java.io.InputStream;
import java.io.IOException;
import java.io.FileNotFoundException;
import java.net.URL;
import java.net.URLConnection;
import java.net.MalformedURLException;
import java.util.Vector;
import java.util.Stack;
import org.jboss.util.xml.catalog.Catalog;
import org.jboss.util.xml.catalog.CatalogEntry;
import org.jboss.util.xml.catalog.CatalogException;
import org.jboss.util.xml.catalog.readers.CatalogReader;

/**
 * Parses plain text Catalog files.
 *
 *
This class reads plain text Open Catalog files.

 *
 * @see Catalog
 *
 * @author Norman Walsh
 * Norman.Walsh@Sun.COM
 *
 * @version 1.0
 */
@SuppressWarnings("unchecked")
public class TextCatalogReader implements CatalogReader {
  /** The input stream used to read the catalog */
  protected InputStream catfile = null;

  /**
   * Character lookahead stack. Reading a catalog sometimes requires
   * up to two characters of lookahead.
   */
  protected int[] stack = new int[3];

  /**
   * Token stack. Recognizing an unexpected catalog entry requires
   * the ability to "push back" a token.
   */
  protected Stack tokenStack = new Stack();

  /** The current position on the lookahead stack */
  protected int top = -1;

  /** Are keywords in the catalog case sensitive? */
  protected boolean caseSensitive = false;

  /**
   * Construct a CatalogReader object.
   */
  public TextCatalogReader() { }

  public void setCaseSensitive(boolean isCaseSensitive) {
    caseSensitive = isCaseSensitive;
  }

  public boolean getCaseSensitive() {
    return caseSensitive;
  }

  /**
   * Start parsing a text catalog file. The file is
   * actually read and parsed
   * as needed by nextEntry.

   *
   * @param fileUrl  The URL or filename of the catalog file to process
   *
   * @throws MalformedURLException Improper fileUrl
   * @throws IOException Error reading catalog file
   */
  public void readCatalog(Catalog catalog, String fileUrl)
    throws MalformedURLException, IOException {
    URL catURL = null;

    try {
      catURL = new URL(fileUrl);
    } catch (MalformedURLException e) {
      catURL = new URL("file:///" + fileUrl);
    }

    URLConnection urlCon = catURL.openConnection();
    try {
      readCatalog(catalog, urlCon.getInputStream());
    } catch (FileNotFoundException e) {
      catalog.getCatalogManager().debug.message(1, "Failed to load catalog, file not found",
						catURL.toString());
    }
  }

  public void readCatalog(Catalog catalog, InputStream is)
    throws MalformedURLException, IOException {

    catfile = is;

    if (catfile == null) {
      return;
    }

    Vector unknownEntry = null;

    while (true) {
      String token = nextToken();

      if (token == null) {
	if (unknownEntry != null) {
	  catalog.unknownEntry(unknownEntry);
	  unknownEntry = null;
	}
	catfile.close();
	catfile = null;
	return;
      }

      String entryToken = null;
      if (caseSensitive) {
	entryToken = token;
      } else {
	entryToken = token.toUpperCase();
      }

      try {
	int type = CatalogEntry.getEntryType(entryToken);
	int numArgs = CatalogEntry.getEntryArgCount(type);
	Vector args = new Vector();

	if (unknownEntry != null) {
	  catalog.unknownEntry(unknownEntry);
	  unknownEntry = null;
	}

	for (int count = 0; count < numArgs; count++) {
	  args.addElement(nextToken());
	}

	catalog.addEntry(new CatalogEntry(entryToken, args));
      } catch (CatalogException cex) {
	if (cex.getExceptionType() == CatalogException.INVALID_ENTRY_TYPE) {
	  if (unknownEntry == null) {
	    unknownEntry = new Vector();
	  }
	  unknownEntry.addElement(token);
	} else if (cex.getExceptionType() == CatalogException.INVALID_ENTRY) {
	  catalog.getCatalogManager().debug.message(1, "Invalid catalog entry", token);
	  unknownEntry = null;
	}
      }
    }
  }

  /**
     * The destructor.
     *
     *
Makes sure the catalog file is closed.

     */
  protected void finalize() {
    if (catfile != null) {
      try {
	catfile.close();
      } catch (IOException e) {
	// whatever...
      }
    }
    catfile = null;
  }

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

    /**
     * Return the next token in the catalog file.
     *
     * @return The Catalog file token from the input stream.
     * @throws IOException If an error occurs reading from the stream.
     */
  protected String nextToken() throws IOException {
    String token = "";
    int ch, nextch;

    if (!tokenStack.empty()) {
      return (String) tokenStack.pop();
    }

    // Skip over leading whitespace and comments
    while (true) {
      // skip leading whitespace
      ch = catfile.read();
      while (ch <= ' ') {      // all ctrls are whitespace
	ch = catfile.read();
	if (ch < 0) {
	  return null;
	}
      }

      // now 'ch' is the current char from the file
      nextch = catfile.read();
      if (nextch < 0) {
	return null;
      }

      if (ch == '-' && nextch == '-') {
	// we've found a comment, skip it...
	ch = ' ';
	nextch = nextChar();
	while (ch != '-' || nextch != '-') {
	  ch = nextch;
	  nextch = nextChar();
	}

	// Ok, we've found the end of the comment,
	// loop back to the top and start again...
      } else {
	stack[++top] = nextch;
	stack[++top] = ch;
	break;
      }
    }

    ch = nextChar();
    if (ch == '"' || ch == '\'') {
      int quote = ch;
      while ((ch = nextChar()) != quote) {
	char[] chararr = new char[1];
	chararr[0] = (char) ch;
	String s = new String(chararr);
	token = token.concat(s);
      }
      return token;
    } else {
      // return the next whitespace or comment delimited
      // string
      while (ch > ' ') {
	nextch = nextChar();
	if (ch == '-' && nextch == '-') {
	  stack[++top] = ch;
	  stack[++top] = nextch;
	  return token;
	} else {
	  char[] chararr = new char[1];
	  chararr[0] = (char) ch;
	  String s = new String(chararr);
	  token = token.concat(s);
	  ch = nextch;
	}
      }
      return token;
    }
  }

  /**
     * Return the next logical character from the input stream.
     *
     * @return The next (logical) character from the input stream. The
     * character may be buffered from a previous lookahead.
     *
     * @throws IOException If an error occurs reading from the stream.
     */
  protected int nextChar() throws IOException {
    if (top < 0) {
      return catfile.read();
    } else {
      return stack[top--];
    }
  }
}


/*
 * 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.Iterator;
import java.util.Enumeration;
import java.util.NoSuchElementException;
import java.util.Map;
import java.util.HashMap;

import org.jboss.util.Null;

/**
 * A collection of Iterator and Enumeration
 * utilities.
 *
 * @version <tt>$Revision$</tt>
 * @author  Jason Dillon
 */
@SuppressWarnings("unchecked")
public final class Iterators
{
   /////////////////////////////////////////////////////////////////////////
   //                    Enumeration/Iterator Conversion                  //
   /////////////////////////////////////////////////////////////////////////

   /**
    * An Enumeration to Iterator wrapper.
    */
   private static final class Enum2Iterator
      implements Iterator
   {
      private final Enumeration enumeration;

      public Enum2Iterator(final Enumeration enumeration) {
         this.enumeration = enumeration;
      }

      public boolean hasNext() {
         return enumeration.hasMoreElements();
      }

      public Object next() {
         return enumeration.nextElement();
      }

      public void remove() {
         throw new UnsupportedOperationException("Enumerations are immutable");
      }
   }

   /**
    * Return an Iterator wrapper for the given Enumeration
    *
    * @param enumeration    Enumeration to wrap
    * @return        Enumeration wrapped as an Iterator
    */
   public static Iterator forEnumeration(final Enumeration enumeration) {
      return new Enum2Iterator(enumeration);
   }

   /**
    * An Iterator to Enumeration wrapper class.
    */
   private static final class Iter2Enumeration
      implements Enumeration
   {
      private final Iterator iter;

      public Iter2Enumeration(final Iterator iter) {
         this.iter = iter;
      }

      public boolean hasMoreElements() {
         return iter.hasNext();
      }

      public Object nextElement() {
         return iter.next();
      }
   }

   /**
    * Return an Enumeration for the given Iterator.
    *
    * @param iter    Iterator to wrap.
    * @return        Enumeration wrapper.
    */
   public static Enumeration toEnumeration(final Iterator iter) {
      return new Iter2Enumeration(iter);
   }

   /////////////////////////////////////////////////////////////////////////
   //                           Iterator Wrappers                         //
   /////////////////////////////////////////////////////////////////////////

   /**
    * Wraps an Iterator making it immutable, by disabling calls to
    * remove()
    */
   private static final class ImmutableIterator
      implements Iterator
   {
      private final Iterator iter;

      public ImmutableIterator(final Iterator iter) {
         this.iter = iter;
      }

      public boolean hasNext() {
         return iter.hasNext();
      }

      public Object next() {
         return iter.next();
      }

      public void remove() {
         throw new UnsupportedOperationException("iterator is immutable");
      }
   }

   /**
    * Make an Iterator immutable
    *
    * @param iter    Iterator to make immutable
    * @return        Imutable iterator
    */
   public static Iterator makeImmutable(final Iterator iter) {
      return new ImmutableIterator(iter);
   }

   /**
    * Wraps an Iterator making it synchronized.
    */
   private static final class SyncIterator
      implements Iterator
   {
      private final Iterator iter;

      public SyncIterator(final Iterator iter) {
         this.iter = iter;
      }

      public synchronized boolean hasNext() {
         return iter.hasNext();
      }

      public synchronized Object next() {
         return iter.next();
      }

      public synchronized void remove() {
         iter.remove();
      }
   }

   /**
    * Returns a synchronized version of the given Iterator.
    *
    * @param iter    Iterator to synchronize.
    * @return        Synchronized Iterator.
    */
   public static Iterator makeSynchronized(final Iterator iter) {
      return new SyncIterator(iter);
   }

   /**
    * Wraps an Enumeration making it synchronized.
    */
   private static final class SyncEnumeration
      implements Enumeration
   {
      private final Enumeration enumeration;

      public SyncEnumeration(final Enumeration enumeration) {
         this.enumeration = enumeration;
      }

      public synchronized boolean hasMoreElements() {
         return enumeration.hasMoreElements();
      }

      public synchronized Object nextElement() {
         return enumeration.nextElement();
      }
   }

   /**
    * Returns a synchronized version of the given Enumeration.
    *
    * @param enumeration    Enumeration to synchronize.
    * @return        Synchronized Enumeration.
    */
   public static Enumeration makeSynchronized(final Enumeration enumeration) {
      return new SyncEnumeration(enumeration);
   }

   /////////////////////////////////////////////////////////////////////////
   //                            Empty Iterator                           //
   /////////////////////////////////////////////////////////////////////////

   /** An empty Iterator */
   public static final Iterator EMPTY_ITERATOR = new EmptyIterator();

   /**
    * An empty Iterator
    */
   private static final class EmptyIterator
      implements Iterator
   {
      public boolean hasNext() {
         return false;
      }

      public Object next() {
         throw new NoSuchElementException("no more elements");
      }

      public void remove() {
         throw new IllegalStateException("no more elements");
      }
   }

   /////////////////////////////////////////////////////////////////////////
   //                              Misc Methods                           //
   /////////////////////////////////////////////////////////////////////////

   /**
    * Returns an Iterator containing the union of all of the elements
    * in the given iterator array.
    *
    * @param iters   Array of iterators.
    * @return        Iterator containing the union.
    */
   public static Iterator union(final Iterator iters[]) {
      Map map = new HashMap();

      for (int i=0; i < iters.length; i++) {
         if (iters[i] != null) {
            while (iters[i].hasNext()) {
               Object obj = iters[i].next();
               if (!map.containsKey(obj)) {
                  map.put(obj, Null.VALUE);
               }
            }
         }
      }

      return map.keySet().iterator();
   }

   /**
    * Return a delimited string representation of all of the elements
    * in the given Iterator.
    *
    * @param iter    Iterator to convert to string.
    * @param delim   Elemement delimiter.
    * @return        Delimited string value.
    */
   public static String toString(final Iterator iter, final String delim) {
      StringBuffer buff = new StringBuffer();
      while (iter.hasNext()) {
         buff.append(iter.next());

         if (iter.hasNext()) {
            buff.append(delim);
         }
      }

      return buff.toString();
   }

   /**
    * Return a comma delimited string representation of all of the elements
    * in the given Iterator.
    *
    * @param iter    Iterator to convert to string.
    * @return        Delimited string value.
    */
   public static String toString(final Iterator iter) {
      return toString(iter, ",");
   }
}


/*
 * 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.security.AccessController;
import java.security.PrivilegedAction;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

import org.jboss.util.NestedRuntimeException;
import org.jboss.util.Strings;

/**
 * A property editor for {@link java.util.Date}.
 *
 * @author Jason Dillon
 * @author David Jencks
 * @author Scott Stark
 * @author Adrian Brock
 * @author Ales Justin
 * @author Dimitris Andreadis
 * @version <tt>$Revision$</tt>
 */
@SuppressWarnings("unchecked")
public class DateEditor extends PropertyEditorSupport
{
   /** The formats to use when parsing the string date */
   private static DateFormat[] formats;
   static
   {
      initialize();
   }

   /**
    * Setup the parsing formats. Offered as a separate static method to allow
    * testing of locale changes, since SimpleDateFormat will use the default
    * locale upon construction. Should not be normally used!
    */
   public static void initialize()
   {
      PrivilegedAction action = new PrivilegedAction()
      {
         public Object run()
         {
            String defaultFormat = System.getProperty("org.jboss.util.propertyeditor.DateEditor.format", "MMM d, yyyy");
            String defaultLocale = System.getProperty("org.jboss.util.propertyeditor.DateEditor.locale");
            DateFormat defaultDateFormat;
            if (defaultLocale == null || defaultLocale.length() == 0)
            {
               defaultDateFormat = new SimpleDateFormat(defaultFormat);
            }
            else
            {
               defaultDateFormat = new SimpleDateFormat(defaultFormat, Strings.parseLocaleString(defaultLocale));
            }

            formats = new DateFormat[]
            {
               defaultDateFormat,
               // Tue Jan 04 00:00:00 PST 2005
               new SimpleDateFormat("EEE MMM d HH:mm:ss z yyyy"),
               // Wed, 4 Jul 2001 12:08:56 -0700
               new SimpleDateFormat("EEE, d MMM yyyy HH:mm:ss Z")
            };
            return null;
         }
      };
      AccessController.doPrivileged(action);
   }

   /** Keep the text version of the date */
   private String text;

   /**
    * Sets directly the java.util.Date value
    *
    * @param value a java.util.Date
    */
   public void setValue(Object value)
   {
      if (value instanceof Date || value == null)
      {
         this.text = null;
         super.setValue(value);
      }
      else
      {
         throw new IllegalArgumentException("setValue() expected java.util.Date value, got "
               + value.getClass().getName());
      }
   }

   /**
    * Parse the text into a java.util.Date by trying
    * one by one the registered DateFormat(s).
    *
    * @param text the string date
    */
   public void setAsText(String text)
   {
      ParseException pe = null;

      for (int i = 0; i < formats.length; i++)
      {
         try
         {
            // try to parse the date
            DateFormat df = formats[i];
            Date date = df.parse(text);

            // store the date in both forms
            this.text = text;
            super.setValue(date);

            // done
            return;
         }
         catch (ParseException e)
         {
            // remember the last seen exception
            pe = e;
         }
      }
      // couldn't parse
      throw new NestedRuntimeException(pe);
   }

   /**
    * Returns either the cached string date, or the stored
    * java.util.Date instance formated to string using the
    * last of the registered DateFormat(s)
    *
    * @return date as string
    */
   public String getAsText()
   {
      if (text == null)
      {
         DateFormat df = formats[formats.length - 1];
         Date date = (Date)getValue();
         text = df.format(date);
      }
      return text;
   }

}


/*
 * 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.graph;

import java.util.ArrayList;
import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

/**
 * A directed graph data structure.
 *
 * @author Scott.Stark@jboss.org
 * @author Ales.Justin@jboss.org
 * @version $Revision$
 * @param <T>
 */
@SuppressWarnings("unchecked")
public class Graph<T>
{
   /** Color used to mark unvisited nodes */
   public static final int VISIT_COLOR_WHITE = 1;
   /** Color used to mark nodes as they are first visited in DFS order */
   public static final int VISIT_COLOR_GREY = 2;
   /** Color used to mark nodes after descendants are completely visited */
   public static final int VISIT_COLOR_BLACK = 3;
   /** Map<String, Vertex> of graph verticies */
   private Map<String, Vertex<T>> verticies;
   /** Vector<Edge> of edges in the graph */
   private List<Edge<T>> edges;
   /** The vertex identified as the root of the graph */
   private Vertex<T> rootVertex;

   /**
    * Construct a new graph without any vertices or edges
    */
   public Graph()
   {
      verticies = new LinkedHashMap<String, Vertex<T>>();
      edges = new ArrayList<Edge<T>>();
   }

   /**
    * Are there any verticies in the graph
    * @return true if there are no verticies in the graph
    */
   public boolean isEmpty()
   {
      return verticies.size() == 0;
   }

   /**
    * Add a vertex to the graph
    * @param v the Vertex to add
    * @return true if the vertex was added, false if it was already in the graph.
    */
   public boolean addVertex(Vertex<T> v)
   {
      if( verticies.containsValue(v) == false )
      {
         verticies.put(v.getName(), v);
         return true;
      }
      return false;
   }

   /**
    * Get the vertex count.
    * @return the number of verticies in the graph.
    */
   public int size()
   {
      return verticies.size();
   }

   /**
    * Get the root vertex
    * @return the root vertex if one is set, null if no vertex has been set as the root.
    */
   public Vertex<T> getRootVertex()
   {
      return rootVertex;
   }

   /**
    * Set a root vertex. If root does no exist in the graph it is added.
    * @param root - the vertex to set as the root and optionally add if it
    *    does not exist in the graph.
    */
   public void setRootVertex(Vertex<T> root)
   {
      this.rootVertex = root;
      if( verticies.containsValue(root) == false )
         addVertex(root);
   }

   /**
    * Get the given Vertex.
    * @param n the index [0, size()-1] of the Vertex to access
    * @return the nth Vertex
    */
   public Vertex<T> getVertex(int n)
   {
      return getVerticies().get(n);
   }

   /**
    * Get the graph verticies
    *
    * @return the graph verticies
    */
   public List<Vertex<T>> getVerticies()
   {
      return new ArrayList<Vertex<T>>(verticies.values());
   }

   /**
    * Insert a directed, weighted Edge<T> into the graph.
    *
    * @param from - the Edge<T> starting vertex
    * @param to - the Edge<T> ending vertex
    * @param cost - the Edge<T> weight/cost
    * @return true if the Edge<T> was added, false if from already has this Edge<T>
    * @throws IllegalArgumentException if from/to are not verticies in
    * the graph
    */
   public boolean addEdge(Vertex<T> from, Vertex<T> to, int cost)
      throws IllegalArgumentException
   {
      if( verticies.containsValue(from) == false )
         throw new IllegalArgumentException("from is not in graph");
      if( verticies.containsValue(to) == false )
         throw new IllegalArgumentException("to is not in graph");

      Edge<T> e = new Edge<T>(from, to, cost);
      if (from.findEdge(to) != null)
         return false;
      else
      {
         from.addEdge(e);
         to.addEdge(e);
         edges.add(e);
         return true;
      }
   }

   /**
    * Insert a bidirectional Edge<T> in the graph
    *
    * @param from - the Edge<T> starting vertex
    * @param to - the Edge<T> ending vertex
    * @param cost - the Edge<T> weight/cost
    * @return true if edges between both nodes were added, false otherwise
    * @throws IllegalArgumentException if from/to are not verticies in
    * the graph
    */
   public boolean insertBiEdge(Vertex<T> from, Vertex<T> to, int cost)
      throws IllegalArgumentException
   {
      return addEdge(from, to, cost) && addEdge(to, from, cost);
   }

   /**
    * Get the graph edges
    * @return the graph edges
    */
   public List<Edge<T>> getEdges()
   {
      return this.edges;
   }

   /**
    * Remove a vertex from the graph
    * @param v the Vertex to remove
    * @return true if the Vertex was removed
    */
   public boolean removeVertex(Vertex<T> v)
   {
      if (!verticies.containsValue(v))
         return false;

      verticies.remove(v.getName());
      if( v == rootVertex )
         rootVertex = null;

      // Remove the edges associated with v
      for(int n = 0; n < v.getOutgoingEdgeCount(); n ++)
      {
         Edge<T> e = v.getOutgoingEdge(n);
         v.remove(e);
         Vertex<T> to = e.getTo();
         to.remove(e);
         edges.remove(e);
      }
      for(int n = 0; n < v.getIncomingEdgeCount(); n ++)
      {
         Edge<T> e = v.getIncomingEdge(n);
         v.remove(e);
         Vertex<T> predecessor = e.getFrom();
         predecessor.remove(e);
      }
      return true;
   }

   /**
    * Remove an Edge<T> from the graph
    * @param from - the Edge<T> starting vertex
    * @param to - the Edge<T> ending vertex
    * @return true if the Edge<T> exists, false otherwise
    */
   public boolean removeEdge(Vertex<T> from, Vertex<T> to)
   {
      Edge<T> e = from.findEdge(to);
      if (e == null)
         return false;
      else
      {
         from.remove(e);
         to.remove(e);
         edges.remove(e);
         return true;
      }
   }

   /**
    * Clear the mark state of all verticies in the graph by calling
    * clearMark() on all verticies.
    * @see Vertex#clearMark()
    */
   public void clearMark()
   {
      for (Vertex<T> w : verticies.values())
         w.clearMark();
   }

   /**
    * Clear the mark state of all edges in the graph by calling
    * clearMark() on all edges.
    */
   public void clearEdges()
   {
      for (Edge<T> e : edges)
         e.clearMark();
   }

   /**
    * Perform a depth first serach using recursion.
    * @param v - the Vertex to start the search from
    * @param visitor - the vistor to inform prior to
    * @see Visitor#visit(Graph, Vertex)
    */
   public void depthFirstSearch(Vertex<T> v, final Visitor<T> visitor)
   {
      VisitorEX<T, RuntimeException> wrapper = new VisitorEX<T, RuntimeException>()
      {
         public void visit(Graph<T> g, Vertex<T> v) throws RuntimeException
         {
            if (visitor != null)
               visitor.visit(g, v);
         }
      };
      this.depthFirstSearch(v, wrapper);
   }
   /**
    * Perform a depth first serach using recursion. The search may
    * be cut short if the visitor throws an exception.
    * @param <E> exception type
    *
    * @param v - the Vertex to start the search from
    * @param visitor - the vistor to inform prior to
    * @see Visitor#visit(Graph, Vertex)
    * @throws E if visitor.visit throws an exception
    */
   public <E extends Exception> void depthFirstSearch(Vertex<T> v, VisitorEX<T, E> visitor)
      throws E
   {
      if( visitor != null )
         visitor.visit(this, v);
      v.visit();
      for (int i = 0; i < v.getOutgoingEdgeCount(); i++)
      {
         Edge<T> e = v.getOutgoingEdge(i);
         if (!e.getTo().visited())
         {
            depthFirstSearch(e.getTo(), visitor);
         }
      }
   }

   /**
    * Perform a breadth first search of this graph, starting at v.
    *
    * @param v - the search starting point
    * @param visitor - the vistor whose vist method is called prior
    * to visting a vertex.
    */
   public void breadthFirstSearch(Vertex<T> v, final Visitor<T> visitor)
   {
      VisitorEX<T, RuntimeException> wrapper = new VisitorEX<T, RuntimeException>()
      {
         public void visit(Graph<T> g, Vertex<T> v) throws RuntimeException
         {
            if (visitor != null)
               visitor.visit(g, v);
         }
      };
      this.breadthFirstSearch(v, wrapper);
   }

   /**
    * Perform a breadth first search of this graph, starting at v. The
    * vist may be cut short if visitor throws an exception during
    * a vist callback.
    * @param <E> exception type
    *
    * @param v - the search starting point
    * @param visitor - the vistor whose vist method is called prior
    * to visting a vertex.
    * @throws E if vistor.visit throws an exception
    */
   public <E extends Exception> void breadthFirstSearch(Vertex<T> v, VisitorEX<T, E> visitor)
      throws E
   {
      LinkedList<Vertex<T>> q = new LinkedList<Vertex<T>>();

      q.add(v);
      if( visitor != null )
         visitor.visit(this, v);
      v.visit();
      while (q.isEmpty() == false)
      {
         v = q.removeFirst();
         for (int i = 0; i < v.getOutgoingEdgeCount(); i++)
         {
            Edge<T> e = v.getOutgoingEdge(i);
            Vertex<T> to = e.getTo();
            if (!to.visited())
            {
               q.add(to);
               if( visitor != null )
                  visitor.visit(this, to);
               to.visit();
            }
         }
      }
   }

   /**
    * Find the spanning tree using a DFS starting from v.
    * @param v - the vertex to start the search from
    * @param visitor - visitor invoked after each vertex
    * is visited and an edge is added to the tree.
    */
   public void dfsSpanningTree(Vertex<T> v, DFSVisitor<T> visitor)
   {
      v.visit();
      if( visitor != null )
         visitor.visit(this, v);

      for (int i = 0; i < v.getOutgoingEdgeCount(); i++)
      {
         Edge<T> e = v.getOutgoingEdge(i);
         if (!e.getTo().visited())
         {
            if( visitor != null )
               visitor.visit(this, v, e);
            e.mark();
            dfsSpanningTree(e.getTo(), visitor);
         }
      }
   }

   /**
    * Search the verticies for one with name.
    *
    * @param name - the vertex name
    * @return the first vertex with a matching name, null if no
    *    matches are found
    */
   public Vertex<T> findVertexByName(String name)
   {
      return verticies.get(name);
   }

   /**
    * Search the verticies for one with data.
    *
    * @param data - the vertex data to match
    * @param compare - the comparator to perform the match
    * @return the first vertex with a matching data, null if no
    *    matches are found
    */
   public Vertex<T> findVertexByData(T data, Comparator<T> compare)
   {
      Vertex<T> match = null;
      for(Vertex<T> v : verticies.values())
      {
         if( compare.compare(data, v.getData()) == 0 )
         {
            match = v;
            break;
         }
      }
      return match;
   }

   /** Search the graph for cycles. In order to detect cycles, we use
    * a modified depth first search called a colored DFS. All nodes are
    * initially marked white. When a node is encountered, it is marked
    * grey, and when its descendants are completely visited, it is
    * marked black. If a grey node is ever encountered, then there is
    * a cycle.
    * @return the edges that form cycles in the graph. The array will
    * be empty if there are no cycles.
    */
   public Edge<T>[] findCycles()
   {
      ArrayList<Edge<T>> cycleEdges = new ArrayList<Edge<T>>();
      // Mark all verticies as white
      for(int n = 0; n < verticies.size(); n ++)
      {
         Vertex<T> v = getVertex(n);
         v.setMarkState(VISIT_COLOR_WHITE);
      }
      for(int n = 0; n < verticies.size(); n ++)
      {
         Vertex<T> v = getVertex(n);
         visit(v, cycleEdges);
      }

      Edge<T>[] cycles = new Edge[cycleEdges.size()];
      cycleEdges.toArray(cycles);
      return cycles;
   }

   private void visit(Vertex<T> v, ArrayList<Edge<T>> cycleEdges)
   {
      v.setMarkState(VISIT_COLOR_GREY);
      int count = v.getOutgoingEdgeCount();
      for(int n = 0; n < count; n ++)
      {
         Edge<T> e = v.getOutgoingEdge(n);
         Vertex<T> u = e.getTo();
         if( u.getMarkState() == VISIT_COLOR_GREY )
         {
            // A cycle Edge<T>
            cycleEdges.add(e);
         }
         else if( u.getMarkState() == VISIT_COLOR_WHITE )
         {
            visit(u, cycleEdges);
         }
      }
      v.setMarkState(VISIT_COLOR_BLACK);
   }

   public String toString()
   {
      StringBuffer tmp = new StringBuffer("Graph[");
      for (Vertex<T> v : verticies.values())
         tmp.append(v);
      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.
 */

// ExtendedXMLCatalogReader.java - Read XML Catalog files

package org.jboss.util.xml.catalog.readers;

import java.util.Vector;

import org.jboss.util.xml.catalog.Catalog;
import org.jboss.util.xml.catalog.CatalogEntry;
import org.jboss.util.xml.catalog.CatalogException;
import org.jboss.util.xml.catalog.Resolver;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;

/**
 * Parse Extended OASIS Entity Resolution Technical Committee
 * XML Catalog files.
 *
 * @see Catalog
 *
 * @author Norman Walsh
 * Norman.Walsh@Sun.COM
 *
 * @version 1.0
 */
@SuppressWarnings("unchecked")
public class ExtendedXMLCatalogReader extends OASISXMLCatalogReader {
  /** The namespace name of extended catalog elements */
  public static final String extendedNamespaceName = "http://nwalsh.com/xcatalog/1.0";

  /**
   * The SAX startElement method recognizes elements
   * from the plain catalog format and instantiates CatalogEntry
   * objects for them.
   *
   * @param namespaceURI The namespace name of the element.
   * @param localName The local name of the element.
   * @param qName The QName of the element.
   * @param atts The list of attributes on the element.
   *
   * @see CatalogEntry
   */
  public void startElement (String namespaceURI,
			    String localName,
			    String qName,
			    Attributes atts)
    throws SAXException {

    // Check before calling the super because super will report our
    // namespace as an extension namespace, but that doesn't count
    // for this element.
    boolean inExtension = inExtensionNamespace();

    super.startElement(namespaceURI, localName, qName, atts);

    int entryType = -1;
    Vector entryArgs = new Vector();

    if (namespaceURI != null && extendedNamespaceName.equals(namespaceURI)
	&& !inExtension) {
      // This is an Extended XML Catalog entry

      if (atts.getValue("xml:base") != null) {
	String baseURI = atts.getValue("xml:base");
	entryType = Catalog.BASE;
	entryArgs.add(baseURI);
	baseURIStack.push(baseURI);

	debug.message(4, "xml:base", baseURI);

	try {
	  CatalogEntry ce = new CatalogEntry(entryType, entryArgs);
	  catalog.addEntry(ce);
	} catch (CatalogException cex) {
	  if (cex.getExceptionType() == CatalogException.INVALID_ENTRY_TYPE) {
	    debug.message(1, "Invalid catalog entry type", localName);
	  } else if (cex.getExceptionType() == CatalogException.INVALID_ENTRY) {
	    debug.message(1, "Invalid catalog entry (base)", localName);
	  }
	}

	entryType = -1;
	entryArgs = new Vector();
      } else {
	baseURIStack.push(baseURIStack.peek());
      }

      if (localName.equals("uriSuffix")) {
	if (checkAttributes(atts, "suffix", "uri")) {
	  entryType = Resolver.URISUFFIX;
	  entryArgs.add(atts.getValue("suffix"));
	  entryArgs.add(atts.getValue("uri"));

	  debug.message(4, "uriSuffix",
			atts.getValue("suffix"),
			atts.getValue("uri"));
	}
      } else if (localName.equals("systemSuffix")) {
	if (checkAttributes(atts, "suffix", "uri")) {
	  entryType = Resolver.SYSTEMSUFFIX;
	  entryArgs.add(atts.getValue("suffix"));
	  entryArgs.add(atts.getValue("uri"));

	  debug.message(4, "systemSuffix",
			atts.getValue("suffix"),
			atts.getValue("uri"));
	}
      } else {
	// This is equivalent to an invalid catalog entry type
	debug.message(1, "Invalid catalog entry type", localName);
      }

      if (entryType >= 0) {
	try {
	  CatalogEntry ce = new CatalogEntry(entryType, entryArgs);
	  catalog.addEntry(ce);
	} catch (CatalogException cex) {
	  if (cex.getExceptionType() == CatalogException.INVALID_ENTRY_TYPE) {
	    debug.message(1, "Invalid catalog entry type", localName);
	  } else if (cex.getExceptionType() == CatalogException.INVALID_ENTRY) {
	    debug.message(1, "Invalid catalog entry", localName);
	  }
	}
      }
    }
  }

  /** The SAX endElement method does nothing. */
  public void endElement (String namespaceURI,
			  String localName,
			  String qName)
    throws SAXException {

    super.endElement(namespaceURI, localName, qName);

    // Check after popping the stack so we don't erroneously think we
    // are our own extension namespace...
    boolean inExtension = inExtensionNamespace();

    int entryType = -1;
    Vector entryArgs = new Vector();

    if (namespaceURI != null
	&& (extendedNamespaceName.equals(namespaceURI))
	&& !inExtension) {

      String popURI = (String) baseURIStack.pop();
      String baseURI = (String) baseURIStack.peek();

      if (!baseURI.equals(popURI)) {
	entryType = catalog.BASE;
	entryArgs.add(baseURI);

	debug.message(4, "(reset) xml:base", baseURI);

	try {
	  CatalogEntry ce = new CatalogEntry(entryType, entryArgs);
	  catalog.addEntry(ce);
	} catch (CatalogException cex) {
	  if (cex.getExceptionType() == CatalogException.INVALID_ENTRY_TYPE) {
	    debug.message(1, "Invalid catalog entry type", localName);
	  } else if (cex.getExceptionType() == CatalogException.INVALID_ENTRY) {
	    debug.message(1, "Invalid catalog entry (rbase)", localName);
	  }
	}
      }
    }
  }
}


/*
 * 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.xml;

// $Id$

import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.io.Writer;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import org.w3c.dom.Attr;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

/**
 * Traverse a DOM tree in order to print a document that is parsed.
 *
 * @author Andy Clark, IBM
 * @author Thomas.Diesler@jboss.org
 */
@SuppressWarnings("unchecked")
public class DOMWriter
{
   // Print writer
   private PrintWriter out;
   // True, if canonical output
   private boolean canonical;
   // True, if pretty printing should be used
   private boolean prettyprint;
   // True, if the XML declaration should be written
   private boolean writeXMLDeclaration;
   // True, if whitespace should be ignored
   private boolean ignoreWhitespace;
   // Explicit character set encoding
   private String charsetName;
   // indent for the pretty printer
   private int prettyIndent;
   // True, if the XML declaration has been written
   private boolean wroteXMLDeclaration;
   // The node that started the write
   private Node rootNode;
   // True if we want namespace completion
   private boolean completeNamespaces = true;
   // The current default namespace
   private String currentDefaultNamespace;

   public DOMWriter(Writer w)
   {
      this.out = new PrintWriter(w);
   }

   public DOMWriter(Writer w, String charsetName)
   {
      this.out = new PrintWriter(w);
      this.charsetName = charsetName;
      this.writeXMLDeclaration = true;
   }

   public DOMWriter(OutputStream stream)
   {
      try
      {
         this.out = new PrintWriter(new OutputStreamWriter(stream, "UTF-8"));
      }
      catch (UnsupportedEncodingException e)
      {
         // ignore, UTF-8 should be available
      }
   }

   public DOMWriter(OutputStream stream, String charsetName)
   {
      try
      {
         this.out = new PrintWriter(new OutputStreamWriter(stream, charsetName));
         this.charsetName = charsetName;
         this.writeXMLDeclaration = true;
      }
      catch (UnsupportedEncodingException e)
      {
         throw new IllegalArgumentException("Unsupported encoding: " + charsetName);
      }
   }

   /**
    * Print a node with explicit prettyprinting.
    * The defaults for all other DOMWriter properties apply.
    *
    */
   public static String printNode(Node node, boolean prettyprint)
   {
      StringWriter strw = new StringWriter();
      new DOMWriter(strw).setPrettyprint(prettyprint).print(node);
      return strw.toString();
   }

   public boolean isCanonical()
   {
      return canonical;
   }

   /**
    * Set wheter entities should appear in their canonical form.
    * The default is false.
    */
   public DOMWriter setCanonical(boolean canonical)
   {
      this.canonical = canonical;
      return this;
   }

   public boolean isIgnoreWhitespace()
   {
      return ignoreWhitespace;
   }

   /**
    * Set whether whitespace should be ignored.
    * The default is false.
    */
   public DOMWriter setIgnoreWhitespace(boolean ignoreWhitespace)
   {
      this.ignoreWhitespace = ignoreWhitespace;
      return this;
   }

   /**
    * Set wheter subelements should have their namespaces completed.
    * Setting this to false may lead to invalid XML fragments.
    * The default is true.
    */
   public DOMWriter setCompleteNamespaces(boolean complete)
   {
      this.completeNamespaces = complete;
      return this;
   }

   public boolean isPrettyprint()
   {
      return prettyprint;
   }

   /**
    * Set wheter element should be indented.
    * The default is false.
    */
   public DOMWriter setPrettyprint(boolean prettyprint)
   {
      this.prettyprint = prettyprint;
      return this;
   }

   public boolean isWriteXMLDeclaration()
   {
      return writeXMLDeclaration;
   }

   /**
    * Set wheter the XML declaration should be written.
    * The default is false.
    */
   public DOMWriter setWriteXMLDeclaration(boolean flag)
   {
      this.writeXMLDeclaration = flag;
      return this;
   }

   public void print(Node node)
   {
      if (prettyprint && ignoreWhitespace)
         throw new IllegalStateException("Cannot pretty print and ignore whitespace");

      rootNode = node;
      printInternal(node, false);
   }

   private void printInternal(Node node, boolean indentEndMarker)
   {
      // is there anything to do?
      if (node == null)
      {
         return;
      }

      // JBAS-2117 - Don't skip the DOCUMENT_NODE
      // if (node instanceof Document) node = ((Document)node).getDocumentElement();

      if (wroteXMLDeclaration == false && writeXMLDeclaration == true && canonical == false)
      {
         out.print("<?xml version='1.0'");
         if (charsetName != null)
            out.print(" encoding='" + charsetName + "'");

         out.print("?>");
         if (prettyprint)
            out.println();

         wroteXMLDeclaration = true;
      }

      int type = node.getNodeType();
      boolean hasChildNodes = node.getChildNodes().getLength() > 0;

      String nodeName = node.getNodeName();
      switch (type)
      {
         // print document
         case Node.DOCUMENT_NODE:
         {
            NodeList children = node.getChildNodes();
            for (int iChild = 0; iChild < children.getLength(); iChild++)
            {
               printInternal(children.item(iChild), false);
            }
            out.flush();
            break;
         }

            // print element with attributes
         case Node.ELEMENT_NODE:
         {
            Element element = (Element)node;
            if (prettyprint)
            {
               for (int i = 0; i < prettyIndent; i++)
               {
                  out.print(' ');
               }
               prettyIndent++;
            }

            out.print('<');
            out.print(nodeName);

            Map nsMap = new HashMap();
            String elPrefix = node.getPrefix();
            String elNamespaceURI = node.getNamespaceURI();
            if (elPrefix != null)
            {
               String nsURI = getNamespaceURI(elPrefix, element, rootNode);
               nsMap.put(elPrefix, nsURI);
            }

            Attr attrs[] = sortAttributes(node.getAttributes());
            for (int i = 0; i < attrs.length; i++)
            {
               Attr attr = attrs[i];
               String atPrefix = attr.getPrefix();
               String atName = attr.getNodeName();
               String atValue = normalize(attr.getNodeValue(), canonical);

               if (atName.equals("xmlns"))
                  currentDefaultNamespace = atValue;

               if (atPrefix != null && !atPrefix.equals("xmlns") && !atPrefix.equals("xml"))
               {
                  String nsURI = getNamespaceURI(atPrefix, element, rootNode);
                  nsMap.put(atPrefix, nsURI);
                  // xsi:type='ns1:SubType', xsi:type='xsd:string'
                  if (atName.equals(atPrefix + ":type") && atValue.indexOf(":") > 0)
                  {
                     // xsi defined on the envelope
                     if (nsURI == null)
                        nsURI = getNamespaceURI(atPrefix, element, null);

                     if ("http://www.w3.org/2001/XMLSchema-instance".equals(nsURI))
                     {
                        String typePrefix = atValue.substring(0, atValue.indexOf(":"));
                        String typeURI = getNamespaceURI(typePrefix, element, rootNode);
                        nsMap.put(typePrefix, typeURI);
                     }
                  }
               }

               out.print(" " + atName + "='" + atValue + "'");
            }

            // Add namespace declaration for prefixes
            // that are defined further up the tree
            if (completeNamespaces)
            {
               Iterator itPrefix = nsMap.keySet().iterator();
               while (itPrefix.hasNext())
               {
                  String prefix = (String)itPrefix.next();
                  String nsURI = (String)nsMap.get(prefix);
                  if (nsURI == null)
                  {
                     nsURI = getNamespaceURI(prefix, element, null);
                     out.print(" xmlns:" + prefix + "='" + nsURI + "'");
                  }
               }
            }

            // The SAX ContentHandler will by default not add the namespace declaration
            // <Hello xmlns='http://somens'>World</Hello>
            if (elPrefix == null && elNamespaceURI != null)
            {
               String defaultNamespace = element.getAttribute("xmlns");
               if (defaultNamespace.length() == 0 && !elNamespaceURI.equals(currentDefaultNamespace))
               {
                  out.print(" xmlns='" + elNamespaceURI + "'");
                  currentDefaultNamespace = elNamespaceURI;
               }
            }

            if (hasChildNodes)
            {
               out.print('>');
            }

            // Find out if the end marker is indented
            indentEndMarker = isEndMarkerIndented(node);

            if (indentEndMarker)
            {
               out.print('\n');
            }

            NodeList childNodes = node.getChildNodes();
            int len = childNodes.getLength();
            for (int i = 0; i < len; i++)
            {
               Node childNode = childNodes.item(i);
               printInternal(childNode, false);
            }
            break;
         }

            // handle entity reference nodes
         case Node.ENTITY_REFERENCE_NODE:
         {
            if (canonical)
            {
               NodeList children = node.getChildNodes();
               if (children != null)
               {
                  int len = children.getLength();
                  for (int i = 0; i < len; i++)
                  {
                     printInternal(children.item(i), false);
                  }
               }
            }
            else
            {
               out.print('&');
               out.print(nodeName);
               out.print(';');
            }
            break;
         }

            // print cdata sections
         case Node.CDATA_SECTION_NODE:
         {
            if (canonical)
            {
               out.print(normalize(node.getNodeValue(), canonical));
            }
            else
            {
               out.print("<![CDATA[");
               out.print(node.getNodeValue());
               out.print("]]>");
            }
            break;
         }

            // print text
         case Node.TEXT_NODE:
         {
            String text = normalize(node.getNodeValue(), canonical);
            if (text.trim().length() > 0)
            {
               out.print(text);
            }
            else if (prettyprint == false && ignoreWhitespace == false)
            {
               out.print(text);
            }
            break;
         }

            // print processing instruction
         case Node.PROCESSING_INSTRUCTION_NODE:
         {
            out.print("<?");
            out.print(nodeName);
            String data = node.getNodeValue();
            if (data != null && data.length() > 0)
            {
               out.print(' ');
               out.print(data);
            }
            out.print("?>");
            break;
         }

            // print comment
         case Node.COMMENT_NODE:
         {
            for (int i = 0; i < prettyIndent; i++)
            {
               out.print(' ');
            }

            out.print("<!--");
            String data = node.getNodeValue();
            if (data != null)
            {
               out.print(data);
            }
            out.print("-->");

            if (prettyprint)
            {
               out.print('\n');
            }

            break;
         }
      }

      if (type == Node.ELEMENT_NODE)
      {
         if (prettyprint)
            prettyIndent--;

         if (hasChildNodes == false)
         {
            out.print("/>");
         }
         else
         {
            if (indentEndMarker)
            {
               for (int i = 0; i < prettyIndent; i++)
               {
                  out.print(' ');
               }
            }

            out.print("</");
            out.print(nodeName);
            out.print('>');
         }

         if (prettyIndent > 0)
         {
            out.print('\n');
         }
      }
      out.flush();
   }

   private String getNamespaceURI(String prefix, Element element, Node stopNode)
   {
      Node parent = element.getParentNode();
      String nsURI = element.getAttribute("xmlns:" + prefix);
      if (nsURI.length() == 0 && element != stopNode && parent instanceof Element)
         return getNamespaceURI(prefix, (Element)parent, stopNode);

      return (nsURI.length() > 0 ? nsURI : null);
   }

   private boolean isEndMarkerIndented(Node node)
   {
      if (prettyprint)
      {
         NodeList childNodes = node.getChildNodes();
         int len = childNodes.getLength();
         for (int i = 0; i < len; i++)
         {
            Node children = childNodes.item(i);
            if (children.getNodeType() == Node.ELEMENT_NODE)
            {
               return true;
            }
         }
      }
      return false;
   }

   /** Returns a sorted list of attributes. */
   private Attr[] sortAttributes(NamedNodeMap attrs)
   {

      int len = (attrs != null) ? attrs.getLength() : 0;
      Attr array[] = new Attr[len];
      for (int i = 0; i < len; i++)
      {
         array[i] = (Attr)attrs.item(i);
      }
      for (int i = 0; i < len - 1; i++)
      {
         String name = array[i].getNodeName();
         int index = i;
         for (int j = i + 1; j < len; j++)
         {
            String curName = array[j].getNodeName();
            if (curName.compareTo(name) < 0)
            {
               name = curName;
               index = j;
            }
         }
         if (index != i)
         {
            Attr temp = array[i];
            array[i] = array[index];
            array[index] = temp;
         }
      }
      return (array);
   }

   /** Normalizes the given string. */
   public static String normalize(String s, boolean canonical)
   {
      StringBuffer str = new StringBuffer();

      int len = (s != null) ? s.length() : 0;
      for (int i = 0; i < len; i++)
      {
         char ch = s.charAt(i);
         switch (ch)
         {
            case '<':
            {
               str.append("<");
               break;
            }
            case '>':
            {
               str.append(">");
               break;
            }
            case '&':
            {
               str.append("&");
               break;
            }
            case '"':
            {
               str.append(""");
               break;
            }
            case '\'':
            {
               str.append("'");
               break;
            }
            case '\r':
            case '\n':
            {
               if (canonical)
               {
                  str.append("&#");
                  str.append(Integer.toString(ch));
                  str.append(';');
                  break;
               }
               // else, default append char
            }
            default:
            {
               str.append(ch);
            }
         }
      }
      return (str.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.xml;

// $Id$

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import javax.xml.namespace.QName;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.jboss.logging.Logger;
import org.jboss.util.StringPropertyReplacer;
import org.w3c.dom.Attr;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.dom.Text;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

/**
 * DOM2 utilites
 *
 * @author Thomas.Diesler@jboss.org
 * @version $Revision$
 */
@SuppressWarnings("unchecked")
public final class DOMUtils
{
    private static Logger log = Logger.getLogger(DOMUtils.class);

    // All elements created by the same thread are created by the same builder and belong to the same doc
    private static ThreadLocal documentThreadLocal = new ThreadLocal();
    private static ThreadLocal builderThreadLocal = new ThreadLocal() {
        protected Object initialValue() {
            try
            {
                DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
                factory.setValidating(false);
                factory.setNamespaceAware(true);
                DocumentBuilder builder = factory.newDocumentBuilder();
                builder.setEntityResolver(new JBossEntityResolver());
                return builder;
            }
            catch (ParserConfigurationException e)
            {
                throw new RuntimeException("Failed to create DocumentBuilder", e);
            }
        }
    };

    // Hide the constructor
    private DOMUtils()
    {
    }

    public static DocumentBuilder getDocumentBuilder()
    {
        DocumentBuilder builder = (DocumentBuilder)builderThreadLocal.get();
        return builder;
    }

    /** Parse the given XML string and return the root Element
    * @param xmlString
    * @return the element
    * @throws IOException
     */
    public static Element parse(String xmlString) throws IOException
    {
        try
        {
            return parse(new ByteArrayInputStream(xmlString.getBytes("UTF-8")));
        }
        catch (IOException e)
        {
            log.error("Cannot parse: " + xmlString);
            throw e;
        }
    }

    /** Parse the given XML stream and return the root Element
    * @param xmlStream
    * @return the element
    * @throws IOException
     */
    public static Element parse(InputStream xmlStream) throws IOException
    {
        try
        {
            Document doc = getDocumentBuilder().parse(xmlStream);
            Element root = doc.getDocumentElement();
            return root;
        }
        catch (SAXException e)
        {
            throw new IOException(e.toString());
        }
    }

    /** Parse the given input source and return the root Element
    * @param source
    * @return the element
    * @throws IOException
     */
    public static Element parse(InputSource source) throws IOException
    {
        try
        {
            Document doc = getDocumentBuilder().parse(source);
            Element root = doc.getDocumentElement();
            return root;
        }
        catch (SAXException e)
        {
            throw new IOException(e.toString());
        }
    }

    /** Create an Element for a given name
    * @param localPart
    * @return the element
     */
    public static Element createElement(String localPart)
    {
        Document doc = getOwnerDocument();
        log.trace("createElement {}" + localPart);
        return doc.createElement(localPart);
    }

    /** Create an Element for a given name and prefix
    * @param localPart
    * @param prefix
    * @return the element
     */
    public static Element createElement(String localPart, String prefix)
    {
        Document doc = getOwnerDocument();
        log.trace("createElement {}" + prefix + ":" + localPart);
        return doc.createElement(prefix + ":" + localPart);
    }

    /** Create an Element for a given name, prefix and uri
    * @param localPart
    * @param prefix
    * @param uri
    * @return the element
     */
    public static Element createElement(String localPart, String prefix, String uri)
    {
        Document doc = getOwnerDocument();
        if (prefix == null || prefix.length() == 0)
        {
            log.trace("createElement {" + uri + "}" + localPart);
            return doc.createElementNS(uri, localPart);
        }
        else
        {
            log.trace("createElement {" + uri + "}" + prefix + ":" + localPart);
            return doc.createElementNS(uri, prefix + ":" + localPart);
        }
    }

    /** Create an Element for a given QName
    * @param qname
    * @return the element
     */
    public static Element createElement(QName qname)
    {
        return createElement(qname.getLocalPart(), qname.getPrefix(), qname.getNamespaceURI());
    }

    /** Create a org.w3c.dom.Text node
    * @param value
    * @return the text node
     */
    public static Text createTextNode(String value)
    {
        Document doc = getOwnerDocument();
        return doc.createTextNode(value);
    }

    /** @return the qname of the given node.
    * @param el
     */
    public static QName getElementQName(Element el)
    {
        String qualifiedName = el.getNodeName();
        return resolveQName(el, qualifiedName);
    }

    /** Transform the giveen qualified name into a QName
    * @param el
    * @param qualifiedName
    * @return the resolved name
     */
    public static QName resolveQName(Element el, String qualifiedName)
    {
        QName qname;
        String prefix = "";
        String namespaceURI = "";
        String localPart = qualifiedName;

        int colIndex = qualifiedName.indexOf(":");
        if (colIndex > 0)
        {
            prefix = qualifiedName.substring(0, colIndex);
            localPart = qualifiedName.substring(colIndex + 1);

            if ("xmlns".equals(prefix))
            {
                namespaceURI = "URI:XML_PREDEFINED_NAMESPACE";
            }
            else
            {
                Element nsElement = el;
                while (namespaceURI.equals("") && nsElement != null)
                {
                    namespaceURI = nsElement.getAttribute("xmlns:" + prefix);
                    if (namespaceURI.equals(""))
                        nsElement = getParentElement(nsElement);
                }
            }

            if (namespaceURI.equals(""))
                throw new IllegalArgumentException("Cannot find namespace uri for: " + qualifiedName);
        }

        qname = new QName(namespaceURI, localPart, prefix);
        return qname;
    }

    /** Get the value from the given attribute
    * @param el
    * @param attrName
     *
     * @return null if the attribute value is empty or the attribute is not present
     */
    public static String getAttributeValue(Element el, String attrName)
    {
        return getAttributeValue(el, new QName(attrName));
    }

    /** Get the value from the given attribute
    * @param el
    * @param attrName
     *
     * @return null if the attribute value is empty or the attribute is not present
     */
    public static String getAttributeValue(Element el, QName attrName)
    {
        String attr = null;
        if ("".equals(attrName.getNamespaceURI()))
            attr = el.getAttribute(attrName.getLocalPart());
        else attr = el.getAttributeNS(attrName.getNamespaceURI(), attrName.getLocalPart());

        if ("".equals(attr))
            attr = null;

        return attr;
    }

    /** @return the qname value from the given attribute
    * @param el
    * @param attrName
     */
    public static QName getAttributeValueAsQName(Element el, String attrName)
    {
        return getAttributeValueAsQName(el, new QName(attrName));

    }

    /** @return the qname value from the given attribute
    * @param el
    * @param attrName
     */
    public static QName getAttributeValueAsQName(Element el, QName attrName)
    {
        QName qname = null;

        String qualifiedName = getAttributeValue(el, attrName);
        if (qualifiedName != null)
        {
            qname = resolveQName(el, qualifiedName);
        }

        return qname;
    }

    /** @return the boolean value from the given attribute
    * @param el
    * @param attrName
     */
    public static boolean getAttributeValueAsBoolean(Element el, String attrName)
    {
        return getAttributeValueAsBoolean(el, new QName(attrName));
    }

    /** @return the boolean value from the given attribute
    * @param el
    * @param attrName
     */
    public static boolean getAttributeValueAsBoolean(Element el, QName attrName)
    {
        String attrVal = getAttributeValue(el, attrName);
        boolean ret = "true".equalsIgnoreCase(attrVal) || "1".equalsIgnoreCase(attrVal);
        return ret;
    }

    /** @return the integer value from the given attribute
    * @param el
    * @param attrName
     */
    public static Integer getAttributeValueAsInteger(Element el, String attrName)
    {
        return getAttributeValueAsInteger(el, new QName(attrName));
    }

    /** @param el
    * @param attrName
    * @return the integer value from the given attribute
     */
    public static Integer getAttributeValueAsInteger(Element el, QName attrName)
    {
        String attrVal = getAttributeValue(el, attrName);
        return (attrVal != null ? new Integer(attrVal) : null);
    }

    /** @param el
    * @return the attributes as Map<QName, String>
     */
    public static Map getAttributes(Element el)
    {
        Map attmap = new HashMap();
        NamedNodeMap attribs = el.getAttributes();
        for (int i = 0; i < attribs.getLength(); i++)
        {
            Attr attr = (Attr)attribs.item(i);
            String name = attr.getName();
            QName qname = resolveQName(el, name);
            String value = attr.getNodeValue();
            attmap.put(qname, value);
        }
        return attmap;
    }

    /** Copy attributes between elements
    * @param destElement
    * @param srcElement
     */
    public static void copyAttributes(Element destElement, Element srcElement)
    {
        NamedNodeMap attribs = srcElement.getAttributes();
        for (int i = 0; i < attribs.getLength(); i++)
        {
            Attr attr = (Attr)attribs.item(i);
            String uri = attr.getNamespaceURI();
            String qname = attr.getName();
            String value = attr.getNodeValue();

            // Prevent DOMException: NAMESPACE_ERR: An attempt is made to create or
            // change an object in a way which is incorrect with regard to namespaces.
            if (uri == null && qname.startsWith("xmlns"))
            {
               log.trace("Ignore attribute: [uri=" + uri + ",qname=" + qname + ",value=" + value + "]");
            }
            else
            {
               destElement.setAttributeNS(uri, qname, value);
            }
        }
    }

    /** True if the node has child elements
    * @param node
    * @return true when has child elements
     */
    public static boolean hasChildElements(Node node)
    {
        NodeList nlist = node.getChildNodes();
        for (int i = 0; i < nlist.getLength(); i++)
        {
            Node child = nlist.item(i);
            if (child.getNodeType() == Node.ELEMENT_NODE)
                return true;
        }
        return false;
    }

    /** Gets child elements
    * @param node
    * @return the iterator
     */
    public static Iterator getChildElements(Node node)
    {
        ArrayList list = new ArrayList();
        NodeList nlist = node.getChildNodes();
        for (int i = 0; i < nlist.getLength(); i++)
        {
            Node child = nlist.item(i);
            if (child.getNodeType() == Node.ELEMENT_NODE)
                list.add(child);
        }
        return list.iterator();
    }

    /** Get the concatenated text content, or null.
    * @param node
     * @return getTextContent(node, false).
     */
    public static String getTextContent(Node node)
    {
       return getTextContent(node, false);
    }
    /** Get the concatenated text content, or null.
    * @param node node to search for TEXT_NODE conent
     * @param replaceProps flag indicating if ${x} property refs should be replace
    * @return the text content
     */
    public static String getTextContent(Node node, boolean replaceProps)
    {
        boolean hasTextContent = false;
        StringBuffer buffer = new StringBuffer();
        NodeList nlist = node.getChildNodes();
        for (int i = 0; i < nlist.getLength(); i++)
        {
            Node child = nlist.item(i);
            if (child.getNodeType() == Node.TEXT_NODE)
            {
                buffer.append(child.getNodeValue());
                hasTextContent = true;
            }
        }
        String text = (hasTextContent ? buffer.toString() : null);
        if ( text != null && replaceProps)
           text = StringPropertyReplacer.replaceProperties(text);

        return text;
    }

    /** @return the first child element
    * @param node
     */
    public static Element getFirstChildElement(Node node)
    {
        return getFirstChildElementIntern(node, null);
    }

    /** @param node
    * @param nodeName
    * @return the first child element for a given local name without namespace
     */
    public static Element getFirstChildElement(Node node, String nodeName)
    {
        return getFirstChildElementIntern(node, new QName(nodeName));
    }

    /** @param node
    * @param nodeName
    * @return the first child element for a given qname
     */
    public static Element getFirstChildElement(Node node, QName nodeName)
    {
        return getFirstChildElementIntern(node, nodeName);
    }

    private static Element getFirstChildElementIntern(Node node, QName nodeName)
    {
        Element childElement = null;
        Iterator it = getChildElementsIntern(node, nodeName);
        if (it.hasNext())
        {
            childElement = (Element)it.next();
        }
        return childElement;
    }

    /** Gets the child elements for a given local name without namespace
    * @param node
    * @param nodeName
    * @return the iterator
     */
    public static Iterator getChildElements(Node node, String nodeName)
    {
        return getChildElementsIntern(node, new QName(nodeName));
    }

    /** Gets the child element for a given qname
    * @param node
    * @param nodeName
    * @return the iterator
     */
    public static Iterator getChildElements(Node node, QName nodeName)
    {
        return getChildElementsIntern(node, nodeName);
    }

    private static Iterator getChildElementsIntern(Node node, QName nodeName)
    {
        ArrayList list = new ArrayList();
        NodeList nlist = node.getChildNodes();
        for (int i = 0; i < nlist.getLength(); i++)
        {
            Node child = nlist.item(i);
            if (child.getNodeType() == Node.ELEMENT_NODE)
            {
                if (nodeName == null)
                {
                    list.add(child);
                }
                else
                {
                    QName qname;
                    if (nodeName.getNamespaceURI().length() > 0)
                    {
                        qname = new QName(child.getNamespaceURI(), child.getLocalName());
                    }
                    else
                    {
                        qname = new QName(child.getLocalName());
                    }
                    if (qname.equals(nodeName))
                    {
                        list.add(child);
                    }
                }
            }
        }
        return list.iterator();
    }

    /** Gets parent element or null if there is none
    * @param node
    * @return the element
     */
    public static Element getParentElement(Node node)
    {
        Node parent = node.getParentNode();
        return (parent instanceof Element ? (Element)parent : null);
    }

    /** @return the owner document that is associated with the current thread */
    public static Document getOwnerDocument()
    {
        Document doc = (Document)documentThreadLocal.get();
        if (doc == null)
        {
            doc = getDocumentBuilder().newDocument();
            documentThreadLocal.set(doc);
        }
        return doc;
    }
}


/*
 * 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.deadlock;

import java.util.HashMap;
import java.util.HashSet;

/**
 * Created by IntelliJ IDEA.
 * User: wburke
 * Date: Aug 21, 2003
 * Time: 2:10:46 PM
 * To change this template use Options | File Templates.
 */
@SuppressWarnings("unchecked")
public class DeadlockDetector
{
   // TODO Maybe this should be an MBean in the future
   public static DeadlockDetector singleton = new DeadlockDetector();
   // This following is for deadlock detection
   protected HashMap waiting = new HashMap();

   public void deadlockDetection(Object holder, Resource resource)
           throws ApplicationDeadlockException
   {
      HashSet set = new HashSet();
      set.add(holder);

      Object checkHolder = resource.getResourceHolder();

      synchronized (waiting)
      {
         addWaiting(holder, resource);

         while (checkHolder != null)
         {
            Resource waitingFor = (Resource)waiting.get(checkHolder);
            Object holding = null;
            if (waitingFor != null)
            {
               holding = waitingFor.getResourceHolder();
            }
            if (holding != null)
            {
               if (set.contains(holding))
               {
                  // removeWaiting should be cleaned up in acquire
                  String msg = "Application deadlock detected, resource="+resource
                     +", holder="+holder+", waitingResource="+waitingFor
                     +", waitingResourceHolder="+holding;
                  throw new ApplicationDeadlockException(msg, true);
               }
               set.add(holding);
            }
            checkHolder = holding;
         }
      }
   }

   /**
    * Add a transaction waiting for a lock
    * @param holder
    * @param resource
    */
   public void addWaiting(Object holder, Resource resource)
   {
      synchronized (waiting)
      {
         waiting.put(holder, resource);
      }
   }

   /**
    * Remove a transaction waiting for a lock
    * @param holder
    */
   public void removeWaiting(Object holder)
   {
      synchronized (waiting)
      {
         waiting.remove(holder);
      }
   }

}