/*
 * 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.lang.ref.SoftReference;
import java.util.Collection;
import java.util.Iterator;
import java.util.Map;

import org.jboss.logging.Logger;
import org.jboss.util.collection.ConcurrentReferenceHashMap;

/**
 * Utility Class
 *
 * Utility Class that provides a Logger instance (log) and
 * caching of toString() and hashCode() values.
 *
 * You most probably want to override the method that
 * comes from JBossInterface:
 *
 *    public void toShortString(StringBuffer buffer)
 *
 * to append to the buffer the key class properties, and
 * also override the following methods to provide the
 * hashCode and the class properties that should be cached:
 *
 *    protected void toString(StringBuffer buffer)
 *    protected int getHashCode()
 *
 * Cached values can be flushed using flushJBossObjectCache()
 *
 * Caching can be disabled by simply overriding toString()
 * and hashCode(), or returning false from methods:
 *
 *    protected boolean cacheToString()
 *    protected boolean cacheGetHashCode()
 *
 * @author Adrian Brock
 * @author Ales Justin
 * @version $Revision$
 */
@SuppressWarnings("unchecked")
public class JBossObject implements JBossInterface
{
   /** The loggers */
   private static final Map<Class<?>, Logger> loggers = new ConcurrentReferenceHashMap<Class<?>, Logger>();

   /** The log */
   protected Logger log;

   /** Cached toString */
   protected transient SoftReference toString;

   /** Cached hashCode */
   protected transient int hashCode = Integer.MIN_VALUE;

   /**
    * Create a new object
    */
   public JBossObject()
   {
      log = createLog();
   }

   /**
    * Create a new object using the specified Logger instace
    *
    * @param log the Logger instance to use
    */
   public JBossObject(Logger log)
   {
      this.log = (log != null) ? log : createLog();
   }

   /**
    * Create logger.
    *
    * @return the logger
    */
   private Logger createLog()
   {
      Class<?> clazz = getClass();
      Logger logger = loggers.get(clazz);
      if (logger == null)
      {
         logger = Logger.getLogger(clazz);
         loggers.put(clazz, logger);
      }
      return logger;
   }

   /**
    * Safe equality check
    *
    * @param one an object
    * @param two another object
    * @return true when they are equal
    */
   public static boolean equals(Object one, Object two)
   {
      if (one == null && two != null)
         return false;
      if (one != null && one.equals(two) == false)
         return false;
      return true;
   }

   /**
    * Safe inequality check
    *
    * @param one an object
    * @param two another object
    * @return true when they are not equal
    */
   public static boolean notEqual(Object one, Object two)
   {
      return equals(one, two) == false;
   }

   /**
    * List the set of JBossObjects
    *
    * @param buffer the buffer
    * @param objects the collection of objects
    */
   public static void list(JBossStringBuilder buffer, Collection objects)
   {
      if (objects == null)
         return;

      buffer.append('[');
      if (objects.isEmpty() == false)
      {
         for (Iterator i = objects.iterator(); i.hasNext();)
         {
            Object object = i.next();
            if (object instanceof JBossObject)
               ((JBossObject) object).toShortString(buffer);
            else
               buffer.append(object.toString());
            if (i.hasNext())
               buffer.append(", ");
         }
      }
      buffer.append(']');
   }

   /**
    * Override toString to cache the value
    *
    * @return the String
    */
   public String toString()
   {
      if (cacheToString() == false)
         return toStringImplementation();

      String result = null;
      if (toString != null)
         result = (String) toString.get();

      if (result == null)
      {
         result = toStringImplementation();
         toString = new SoftReference(result);
      }
      return result;
   }

   /**
    * Override hashCode to cache the value
    *
    * @return the hashCode
    */
   public int hashCode()
   {
      if (hashCode == Integer.MIN_VALUE || cacheGetHashCode() == false)
         hashCode = getHashCode();
      return hashCode;
   }

   public Object clone()
   {
      try
      {
         return super.clone();
      }
      catch (CloneNotSupportedException e)
      {
         throw new RuntimeException(e);
      }
   }

   public String toShortString()
   {
      JBossStringBuilder buffer = new JBossStringBuilder();
      toShortString(buffer);
      return buffer.toString();
   }

   /**
    * Append the key class properties to the buffer
    *
    * @param buffer the buffer
    */
   public void toShortString(JBossStringBuilder buffer)
   {
   }

   /**
    * Get the class short name
    *
    * @return the short name of the class
    */
   public String getClassShortName()
   {
      String longName = getClass().getName();
      int dot = longName.lastIndexOf('.');
      if (dot != -1)
         return longName.substring(dot + 1);
      return longName;
   }

   /**
    * Implementation of String
    *
    * @return the string
    */
   protected String toStringImplementation()
   {
      JBossStringBuilder buffer = new JBossStringBuilder();
      buffer.append(getClassShortName()).append('@');
      buffer.append(Integer.toHexString(System.identityHashCode(this)));
      buffer.append('{');
      toString(buffer);
      buffer.append('}');
      return buffer.toString();
   }

   /**
    * Flush the JBossObject cached values
    */
   protected void flushJBossObjectCache()
   {
      toString = null;
      hashCode = Integer.MIN_VALUE;
   }

   /**
    * Append the class properties to the buffer
    *
    * @param buffer the buffer
    */
   protected void toString(JBossStringBuilder buffer)
   {
   }

   /**
    * Calculate the hashcode
    *
    * @return the hash code
    */
   protected int getHashCode()
   {
      return super.hashCode();
   }

   /**
    * Whether we should cache the result toString()
    *
    * @return true by default
    */
   protected boolean cacheToString()
   {
      return true;
   }

   /**
    * Whether we should cache the result hashCode()
    *
    * @return true by default
    */
   protected boolean cacheGetHashCode()
   {
      return true;
   }
}


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

import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.Map;
import java.util.Iterator;

import java.io.IOException;

import org.jboss.util.ThrowableHandler;

/**
 * A more robust replacement of <tt>java.lang.System</tt> for property
 * access.
 *
 * @version <tt>$Revision$</tt>
 * @author  Jason Dillon
 * @author  Adrian Brock
 */
@SuppressWarnings("unchecked")
public final class PropertyManager
{
   /** Property reader list property name */
   public static final String READER_PROPERTY_NAME = "org.jboss.util.property.reader";

   /** Token which specifies the default property reader */
   public static final String DEFAULT_PROPERTY_READER_TOKEN = "DEFAULT";

   /** The default property reader name array */
   private static final String[] DEFAULT_PROPERTY_READERS = { DEFAULT_PROPERTY_READER_TOKEN };

   /** Default property container */
   private static PropertyMap props;

   /**
    * Do not allow instantiation of this class.
    */
   private PropertyManager()
   {
   }

   /**
    * Initialize the property system.
    */
   static
   {
      // construct default property container and initialze from system props
      props = new PropertyMap();
      PrivilegedAction action = new PrivilegedAction()
      {
         public Object run()
         {
            props.putAll(System.getProperties());

            // replace system props to enable notifications via System.setProperty()
            System.setProperties(props);

            // load properties from initial property readers
            String[] readerNames = getArrayProperty(READER_PROPERTY_NAME, DEFAULT_PROPERTY_READERS);

            // construct each source and read its properties
            for (int i = 0; i < readerNames.length; i++)
            {
               try
               {
                  if (readerNames[i].equals(DEFAULT_PROPERTY_READER_TOKEN))
                  {
                     load(new DefaultPropertyReader());
                  }
                  else
                  {
                     load(readerNames[i]);
                  }
               }
               catch (IOException e)
               {
                  ThrowableHandler.add(e);
               }
            }
            return null;
         }
      };
      AccessController.doPrivileged(action);
   }

   /**
    * Get the default <tt>PropertyMap</tt>.
    *
    * @return  Default <tt>PropertyMap</tt>.
    */
   public static PropertyMap getDefaultPropertyMap()
   {
      SecurityManager sm = System.getSecurityManager();
      if (sm != null)
         sm.checkPropertiesAccess();
      return props;
   }

   /**
    * Add a property listener.
    *
    * @param listener   Property listener to add.
    */
   public static void addPropertyListener(final PropertyListener listener)
   {
      SecurityManager sm = System.getSecurityManager();
      if (sm != null)
         sm.checkPropertiesAccess();
      props.addPropertyListener(listener);
   }

   /**
    * Add an array of property listeners.
    *
    * @param listeners     Array of property listeners to add.
    */
   public static void addPropertyListeners(final PropertyListener[] listeners)
   {
      SecurityManager sm = System.getSecurityManager();
      if (sm != null)
         sm.checkPropertiesAccess();
      props.addPropertyListeners(listeners);
   }

   /**
    * Remove a property listener.
    *
    * @param listener   Property listener to remove.
    * @return           True if listener was removed.
    */
   public static boolean removePropertyListener(final PropertyListener listener)
   {
      return props.removePropertyListener(listener);
   }

   /**
    * Load properties from a map.
    *
    * @param prefix  Prefix to append to all map keys (or <tt>null</tt>).
    * @param map     Map containing properties to load.
    * @throws PropertyException
    */
   public static void load(final String prefix, final Map map) throws PropertyException
   {
      SecurityManager sm = System.getSecurityManager();
      if (sm != null)
         sm.checkPropertiesAccess();
      props.load(prefix, map);
   }

   /**
    * Load properties from a map.
    *
    * @param map  Map containing properties to load.
    * @throws PropertyException
    * @throws IOException
    */
   public static void load(final Map map) throws PropertyException, IOException
   {
      SecurityManager sm = System.getSecurityManager();
      if (sm != null)
         sm.checkPropertiesAccess();
      props.load(map);
   }

   /**
    * Load properties from a <tt>PropertyReader</tt>.
    *
    * @param reader  <tt>PropertyReader</tt> to read properties from.
    * @throws PropertyException
    * @throws IOException
    */
   public static void load(final PropertyReader reader) throws PropertyException, IOException
   {
      SecurityManager sm = System.getSecurityManager();
      if (sm != null)
         sm.checkPropertiesAccess();
      props.load(reader);
   }

   /**
    * Load properties from a <tt>PropertyReader</tt> specifed by the
    * given class name.
    *
    * @param classname     Class name of a <tt>PropertyReader</tt> to
    *                      read from.
    * @throws PropertyException
    * @throws IOException
    */
   public static void load(final String classname) throws PropertyException, IOException
   {
      SecurityManager sm = System.getSecurityManager();
      if (sm != null)
         sm.checkPropertiesAccess();
      props.load(classname);
   }

   /**
    * Set a property.
    *
    * @param name    Property name.
    * @param value   Property value.
    * @return        Previous property value or <tt>null</tt>.
    */
   public static String setProperty(final String name, final String value)
   {
      SecurityManager sm = System.getSecurityManager();
      if (sm != null)
         sm.checkPropertyAccess(name);
      return (String) props.setProperty(name, value);
   }

   /**
    * Remove a property.
    *
    * @param name    Property name.
    * @return        Removed property value or <tt>null</tt>.
    */
   public static String removeProperty(final String name)
   {
      SecurityManager sm = System.getSecurityManager();
      if (sm != null)
         sm.checkPropertyAccess(name);
      return props.removeProperty(name);
   }

   /**
    * Get a property.
    *
    * @param name          Property name.
    * @param defaultValue  Default property value.
    * @return              Property value or default.
    */
   public static String getProperty(final String name, final String defaultValue)
   {
      SecurityManager sm = System.getSecurityManager();
      if (sm != null)
         sm.checkPropertyAccess(name);
      return props.getProperty(name, defaultValue);
   }

   /**
    * Get a property.
    *
    * @param name       Property name.
    * @return           Property value or <tt>null</tt>.
    */
   public static String getProperty(final String name)
   {
      SecurityManager sm = System.getSecurityManager();
      if (sm != null)
         sm.checkPropertyAccess(name);
      return props.getProperty(name);
   }

   /**
    * Get an array style property.
    *
    * @param base             Base property name.
    * @param defaultValues    Default property values.
    * @return                 Array of property values or default.
    */
   public static String[] getArrayProperty(final String base, final String[] defaultValues)
   {
      SecurityManager sm = System.getSecurityManager();
      if (sm != null)
         sm.checkPropertiesAccess();
      return props.getArrayProperty(base, defaultValues);
   }

   /**
    * Get an array style property.
    *
    * @param name       Property name.
    * @return           Array of property values or empty array.
    */
   public static String[] getArrayProperty(final String name)
   {
      SecurityManager sm = System.getSecurityManager();
      if (sm != null)
         sm.checkPropertyAccess(name);
      return props.getArrayProperty(name);
   }

   /**
    * Return an iterator over all contained property names.
    *
    * @return     Property name iterator.
    */
   public static Iterator names()
   {
      SecurityManager sm = System.getSecurityManager();
      if (sm != null)
         sm.checkPropertiesAccess();
      return props.names();
   }

   /**
    * Check if this map contains a given property.
    *
    * @param name    Property name.
    * @return        True if contains property.
    */
   public static boolean containsProperty(final String name)
   {
      SecurityManager sm = System.getSecurityManager();
      if (sm != null)
         sm.checkPropertyAccess(name);
      return props.containsProperty(name);
   }

   /**
    * Get a property group for the given property base.
    *
    * @param basename   Base property name.
    * @return           Property group.
    */
   public static PropertyGroup getPropertyGroup(final String basename)
   {
      SecurityManager sm = System.getSecurityManager();
      if (sm != null)
         sm.checkPropertiesAccess();
      return props.getPropertyGroup(basename);
   }

   /**
    * Get a property group for the given property base at the given index.
    *
    * @param basename   Base property name.
    * @param index      Array property index.
    * @return           Property group.
    */
   public static PropertyGroup getPropertyGroup(final String basename, final int index)
   {
      SecurityManager sm = System.getSecurityManager();
      if (sm != null)
         sm.checkPropertiesAccess();
      return props.getPropertyGroup(basename, index);
   }
}


/*
 * 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.lang.reflect.Constructor;
import java.lang.reflect.Array;

import java.lang.ref.Reference;

import java.io.IOException;
import java.io.ObjectOutputStream;
import java.io.ObjectInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ByteArrayInputStream;
import java.io.Serializable;

import org.jboss.util.stream.Streams;

/**
 * A collection of Object utilities.
 *
 * @author  Jason Dillon
 * @author  Ales Justin
 */
@SuppressWarnings("unchecked")
public final class Objects
{
   /////////////////////////////////////////////////////////////////////////
   //                           Coercion Methods                          //
   /////////////////////////////////////////////////////////////////////////

   /**
    * Get a compatible constructor for the given value type
    *
    * @param type       Class to look for constructor in
    * @param valueType  Argument type for constructor
    * @return           Constructor or null
    */
   public static Constructor getCompatibleConstructor(final Class type,
                                                      final Class valueType)
   {
      // first try and find a constructor with the exact argument type
      try {
         return type.getConstructor(new Class[] { valueType });
      }
      catch (Exception ignore) {
         // if the above failed, then try and find a constructor with
         // an compatible argument type

         // get an array of compatible types
         Class[] types = type.getClasses();

         for (int i=0; i<types.length; i++) {
            try {
               return type.getConstructor(new Class[] { types[i] });
            }
            catch (Exception ignore2) {}
         }
      }

      // if we get this far, then we can't find a compatible constructor
      return null;
   }

   /////////////////////////////////////////////////////////////////////////
   //                            Cloning Methods                          //
   /////////////////////////////////////////////////////////////////////////

   /**
    * Copy an serializable object deeply.
    *
    * @param obj  Object to copy.
    * @return     Copied object.
    *
    * @throws IOException
    * @throws ClassNotFoundException
    */
   public static Object copy(final Serializable obj)
      throws IOException, ClassNotFoundException
   {
      ObjectOutputStream out = null;
      ObjectInputStream in = null;
      Object copy = null;

      try {
         // write the object
         ByteArrayOutputStream baos = new ByteArrayOutputStream();
         out = new ObjectOutputStream(baos);
         out.writeObject(obj);
         out.flush();

         // read in the copy
         byte data[] = baos.toByteArray();
         ByteArrayInputStream bais = new ByteArrayInputStream(data);
         in = new ObjectInputStream(bais);
         copy = in.readObject();
      }
      finally {
         Streams.close(out);
         Streams.close(in);
      }

      return copy;
   }

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

   /**
    * Dereference the given object if it is non-null and is an
    * instance of Reference.  If the object is null
    * then null is returned.  If the object is not an instance of
    * Reference, then the object is returned.
    *
    * @param obj  Object to dereference.
    * @return     Dereferenced object.
    */
   @SuppressWarnings("unchecked")
   public static Object deref(final Object obj) {
      if (obj != null && obj instanceof Reference) {
         Reference ref = (Reference)obj;
         return ref.get();
      }

      return obj;
   }

   /**
    * Dereference an object
    *
    * @param <T> the expected type
    * @param obj the object or reference
    * @param expected the expected type
    * @return the object or null
    */
   public static <T> T deref(final Object obj, Class<T> expected)
   {
      Object result = deref(obj);
      if (result == null)
         return null;
      return expected.cast(result);
   }

   /**
    * Check if the given object is an array (primitve or native).
    *
    * @param obj  Object to test.
    * @return     True of the object is an array.
    */
   public static boolean isArray(final Object obj) {
      if (obj != null)
         return obj.getClass().isArray();
      return false;
   }

   /**
    * @return an Object array for the given object.
    *
    * @param obj  Object to convert to an array.  Converts primitive
    *             arrays to Object arrays consisting of their wrapper
    *             classes.  If the object is not an array (object or primitve)
    *             then a new array of the given type is created and the
    *             object is set as the sole element.
    */
   public static Object[] toArray(final Object obj) {
      // if the object is an array, the cast and return it.
      if (obj instanceof Object[]) {
         return (Object[])obj;
      }

      // if the object is an array of primitives then wrap the array
      Class type = obj.getClass();
      Object array;
      if (type.isArray()) {
         int length = Array.getLength(obj);
         Class componentType = type.getComponentType();
         array = Array.newInstance(componentType, length);
         for (int i=0; i<length; i++) {
            Array.set(array, i, Array.get(obj, i));
         }
      }
      else {
         array = Array.newInstance(type, 1);
         Array.set(array, 0, obj);
      }

      return (Object[])array;
   }

   /**
    * Test the equality of two object arrays.
    *
    * @param a       The first array.
    * @param b       The second array.
    * @param deep    True to traverse elements which are arrays.
    * @return        True if arrays are equal.
    */
   public static boolean equals(final Object[] a, final Object[] b,
                                final boolean deep)
   {
      if (a == b) return true;
      if (a == null || b == null) return false;
      if (a.length != b.length) return false;

      for (int i=0; i<a.length; i++) {
         Object x = a[i];
         Object y = b[i];

         if (x != y) return false;
         if (x == null || y == null) return false;
         if (deep) {
            if (x instanceof Object[] && y instanceof Object[]) {
               if (! equals((Object[])x, (Object[])y, true)) return false;
            }
            else {
               return false;
            }
         }
         if (! x.equals(y)) return false;
      }

      return true;
   }

   /**
    * Test the equality of two object arrays.
    *
    * @param a    The first array.
    * @param b    The second array.
    * @return     True if arrays are equal.
    */
   public static boolean equals(final Object[] a, final Object[] b) {
      return equals(a, b, true);
   }

   /**
    * Test the equality of two objects.
    * They can be an (multi dimensional) array.
    *
    * @param first the first obejct
    * @param second the second object
    * @return true if equal, false otherwise
    */
   public static boolean equals(Object first, Object second)
   {
      if (isArray(first))
      {
         if (isArray(second) == false)
            return false;

         int lenght = Array.getLength(first);
         if (lenght != Array.getLength(second))
            return false;

         for (int i = 0; i < lenght; i++)
         {
            if (equals(Array.get(first, i), Array.get(second, i)) == false)
               return false;
         }

         return true;
      }
      else
      {
         return JBossObject.equals(first, second);
      }
   }
}


/*
 * 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 java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.List;
import java.util.LinkedList;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Map;
import java.util.HashMap;
import java.util.Set;
import java.util.HashSet;
import java.util.Collections;
import java.util.Properties;
import javax.naming.Context;
import javax.naming.ldap.LdapContext;

import org.jboss.util.NullArgumentException;

/**
 * A replacement for the standard java.util.Properties
 * class which adds, among others, property event capabilities.
 *
 * @todo consider moving the JNDI property handling to a InitialContextFactoryBuilder
 *
 * @version <tt>$Revision$</tt>
 * @author  Jason Dillon
 * @author Scott.Stark@jboss.org
 * @author  Adrian Brock
 */
@SuppressWarnings("unchecked")
public class PropertyMap extends Properties
{
   /** Serial version uid */
   private static final long serialVersionUID = 8747802918099008518L;

   /** Property name separator */
   public static final String PROPERTY_NAME_SEPARATOR = ".";

   /** Empty array property */
   public static final String[] EMPTY_ARRAY_PROPERTY = new String[0];

   /** Property listener list */
   protected transient List unboundListeners;

   /** Bound property name -> listener list map */
   protected transient Map boundListeners;

   /**
    * This map avoids heavy contention for the properties that JNDI looks
    * up everytime a new InitialContext instance is created. Once the container is
    * up and running getProperty calls other than for the JNDI property are very rare,
    * so the double lookup is not much of a performance problem.
    * If at all possible, this class should be read-only and use no locks at all.
    */
   private transient Map jndiMap;
   private final static Object NULL_VALUE = new Object();

   /**
    * Construct a PropertyMap with default properties.
    *
    * @param defaults   Default properties.
    */
   public PropertyMap(Properties defaults)
   {
      super(defaults);
      init();
   }

   /**
    * Construct a PropertyMap.
    */
   public PropertyMap()
   {
      this(null);
   }

   /** Initialized listener lists and the JNDI properties cache map
    */
   private void init()
   {
      unboundListeners = Collections.synchronizedList(new ArrayList());
      boundListeners = Collections.synchronizedMap(new HashMap());
      jndiMap = new HashMap();

      PrivilegedAction action = new PrivilegedAction()
      {
         public Object run()
         {
            Object value = System.getProperty(Context.PROVIDER_URL);
            if (value == null) value = NULL_VALUE;
            jndiMap.put(Context.PROVIDER_URL, value);

            value = System.getProperty(Context.INITIAL_CONTEXT_FACTORY);
            if (value == null) value = NULL_VALUE;
            jndiMap.put(Context.INITIAL_CONTEXT_FACTORY, value);

            value = System.getProperty(Context.OBJECT_FACTORIES);
            if (value == null) value = NULL_VALUE;
            jndiMap.put(Context.OBJECT_FACTORIES, value);

            value = System.getProperty(Context.URL_PKG_PREFIXES);
            if (value == null) value = NULL_VALUE;
            jndiMap.put(Context.URL_PKG_PREFIXES, value);

            value = System.getProperty(Context.STATE_FACTORIES);
            if (value == null) value = NULL_VALUE;
            jndiMap.put(Context.STATE_FACTORIES, value);

            value = System.getProperty(Context.DNS_URL);
            if (value == null) value = NULL_VALUE;
            jndiMap.put(Context.DNS_URL, value);

            value = System.getProperty(LdapContext.CONTROL_FACTORIES);
            if (value == null) value = NULL_VALUE;
            jndiMap.put(LdapContext.CONTROL_FACTORIES, value);
            return null;
         }
      };
      AccessController.doPrivileged(action);
   }

   /** Called by setProperty to update the jndiMap cache values.
    * @param name the property name
    * @param value the property value
    */
   private void updateJndiCache(String name, String value)
   {
      if( name == null )
         return;

      boolean isJndiProperty = name.equals(Context.PROVIDER_URL)
         || name.equals(Context.INITIAL_CONTEXT_FACTORY)
         || name.equals(Context.OBJECT_FACTORIES)
         || name.equals(Context.URL_PKG_PREFIXES)
         || name.equals(Context.STATE_FACTORIES)
         || name.equals(Context.DNS_URL)
         || name.equals(LdapContext.CONTROL_FACTORIES)
      ;
      if( isJndiProperty == true )
         jndiMap.put(name, value);
   }

   /**
    * Set a property.
    *
    * @param name    Property name.
    * @param value   Property value.
    * @return        Previous property value or null.
    */
   public Object put(Object name, Object value)
   {
      if (name == null)
         throw new NullArgumentException("name");
      // value can be null

      // check if this is a new addition or not prior to updating the hash
      boolean add = !containsKey(name);
      Object prev = super.put(name, value);

      PropertyEvent event =
         new PropertyEvent(this, name.toString(), value.toString());

      // fire propertyAdded or propertyChanged
      if (add)
      {
         firePropertyAdded(event);
      }
      else
      {
         firePropertyChanged(event);
      }

      return prev;
   }

   /**
    * Remove a property.
    *
    * @param name    Property name.
    * @return        Removed property value.
    */
   public Object remove(Object name)
   {
      if (name == null)
         throw new NullArgumentException("name");

      // check if there is a property with this name
      boolean contains = containsKey(name);
      Object value = null;

      if (contains)
      {
         value = super.remove(name);
         if (defaults != null)
         {
            Object obj = defaults.remove(name);
            if (value == null)
            {
               value = obj;
            }
         }
         // Remove any JNDI property value
         jndiMap.remove(name);

         PropertyEvent event = new PropertyEvent(this, name.toString(), value.toString());
         firePropertyRemoved(event);
      }

      return value;
   }

   /**
    * Returns a set of keys for all entries in this group and optionally
    * all of the keys in the defaults map.
    * @param includeDefaults
    * @return the key set
    */
   public Set keySet(final boolean includeDefaults)
   {
      if (includeDefaults)
      {
         Set set = new HashSet();
         set.addAll(defaults.keySet());
         set.addAll(super.keySet());
         return Collections.synchronizedSet(set);
      }

      return super.keySet();
   }

   /**
    * Returns a set of entrys for all entries in this group and optionally
    * all of the entrys in the defaults map.
    * @param includeDefaults
    * @return  the entry set
    */
   public Set entrySet(final boolean includeDefaults)
   {
      if (includeDefaults)
      {
         Set set = new HashSet();
         set.addAll(defaults.entrySet());
         set.addAll(super.entrySet());
         return Collections.synchronizedSet(set);
      }

      return super.entrySet();
   }

   /**
    * Add a property listener.
    *
    * @param listener   Property listener to add.
    */
   public void addPropertyListener(PropertyListener listener)
   {
      if (listener == null)
         throw new NullArgumentException("listener");

      if (listener instanceof BoundPropertyListener)
      {
         addPropertyListener((BoundPropertyListener) listener);
      }
      else
      {
         // only add the listener if it is not in the list already
         if (!unboundListeners.contains(listener))
            unboundListeners.add(listener);
      }
   }

   /**
    * Add a bound property listener.
    *
    * @param listener   Bound property listener to add.
    */
   protected void addPropertyListener(BoundPropertyListener listener)
   {
      // get the bound property name
      String name = listener.getPropertyName();

      // get the bound listener list for the property
      List list = (List) boundListeners.get(name);

      // if list is null, then add a new list
      if (list == null)
      {
         list = Collections.synchronizedList(new ArrayList());
         boundListeners.put(name, list);
      }

      // if listener is not in the list already, then add it
      if (!list.contains(listener))
      {
         list.add(listener);
         // notify listener that is is bound
         listener.propertyBound(this);
      }
   }

   /**
    * Add an array of property listeners.
    *
    * @param listeners     Array of property listeners to add.
    */
   public void addPropertyListeners(PropertyListener[] listeners)
   {
      if (listeners == null)
         throw new NullArgumentException("listeners");

      for (int i = 0; i < listeners.length; i++)
      {
         addPropertyListener(listeners[i]);
      }
   }

   /**
    * Remove a property listener.
    *
    * @param listener   Property listener to remove.
    * @return           True if listener was removed.
    */
   public boolean removePropertyListener(PropertyListener listener)
   {
      if (listener == null)
         throw new NullArgumentException("listener");

      boolean removed = false;
      if (listener instanceof BoundPropertyListener)
      {
         removed = removePropertyListener((BoundPropertyListener) listener);
      }
      else
      {
         removed = unboundListeners.remove(listener);
      }

      return removed;
   }

   /**
    * Remove a bound property listener.
    *
    * @param listener   Bound property listener to remove.
    * @return           True if listener was removed.
    */
   protected boolean removePropertyListener(BoundPropertyListener listener)
   {
      // get the bound property name
      String name = listener.getPropertyName();

      // get the bound listener list for the property
      List list = (List) boundListeners.get(name);
      boolean removed = false;
      if (list != null)
      {
         removed = list.remove(listener);

         // notify listener that is was unbound
         if (removed) listener.propertyUnbound(this);
      }
      return removed;
   }

   /**
    * Fire a property added event to the given list of listeners.
    *
    * @param list    Listener list.
    * @param event   Property event.
    */
   private void firePropertyAdded(List list, PropertyEvent event)
   {
      if (list == null) return;

      int size = list.size();
      for (int i = 0; i < size; i++)
      {
         PropertyListener listener = (PropertyListener) list.get(i);
         listener.propertyAdded(event);
      }
   }

   /**
    * Fire a property added event to all registered listeners.
    *
    * @param event   Property event.
    */
   protected void firePropertyAdded(PropertyEvent event)
   {
      // fire all bound listeners (if any) first
      if (boundListeners != null)
      {
         List list = (List) boundListeners.get(event.getPropertyName());
         if (list != null)
         {
            firePropertyAdded(list, event);
         }
      }

      // next fire all unbound listeners
      firePropertyAdded(unboundListeners, event);
   }

   /**
    * Fire a property removed event to the given list of listeners.
    *
    * @param list    Listener list.
    * @param event   Property event.
    */
   private void firePropertyRemoved(List list, PropertyEvent event)
   {
      if (list == null) return;

      int size = list.size();
      for (int i = 0; i < size; i++)
      {
         PropertyListener listener = (PropertyListener) list.get(i);
         listener.propertyRemoved(event);
      }
   }

   /**
    * Fire a property removed event to all registered listeners.
    *
    * @param event   Property event.
    */
   protected void firePropertyRemoved(PropertyEvent event)
   {
      // fire all bound listeners (if any) first
      if (boundListeners != null)
      {
         List list = (List) boundListeners.get(event.getPropertyName());
         if (list != null)
         {
            firePropertyRemoved(list, event);
         }
      }

      // next fire all unbound listeners
      firePropertyRemoved(unboundListeners, event);
   }

   /**
    * Fire a property changed event to the given list of listeners.
    *
    * @param list    Listener list.
    * @param event   Property event.
    */
   private void firePropertyChanged(List list, PropertyEvent event)
   {
      if (list == null) return;

      int size = list.size();
      for (int i = 0; i < size; i++)
      {
         PropertyListener listener = (PropertyListener) list.get(i);
         listener.propertyChanged(event);
      }
   }

   /**
    * Fire a property changed event to all listeners.
    *
    * @param event   Property event.
    */
   protected void firePropertyChanged(PropertyEvent event)
   {
      // fire all bound listeners (if any) first
      if (boundListeners != null)
      {
         List list = (List) boundListeners.get(event.getPropertyName());
         if (list != null)
         {
            firePropertyChanged(list, event);
         }
      }

      // next fire all unbound listeners
      firePropertyChanged(unboundListeners, event);
   }

   /**
    * Make a optionaly prefixed property name.
    *
    * @param base    Base property name.
    * @param prefix  Optional prefix (can be null).
    * @return        Property name.
    */
   protected String makePrefixedPropertyName(String base, String prefix)
   {
      String name = base;

      if (prefix != null)
      {
         StringBuffer buff = new StringBuffer(base);
         if (prefix != null)
         {
            buff.insert(0, PROPERTY_NAME_SEPARATOR);
            buff.insert(0, prefix);
         }
         return buff.toString();
      }

      return name;
   }

   /**
    * Load properties from a map.
    *
    * @param prefix  Prefix to append to all map keys (or null).
    * @param map     Map containing properties to load.
    * @throws PropertyException
    */
   public void load(String prefix, Map map) throws PropertyException
   {
      // prefix can be null
      if (map == null)
         throw new NullArgumentException("map");

      // set properties for each key in map
      Iterator iter = map.keySet().iterator();
      while (iter.hasNext())
      {
         // make a string key with optional prefix
         String key = String.valueOf(iter.next());
         String name = makePrefixedPropertyName(key, prefix);
         String value = String.valueOf(map.get(name));

         // set the property
         setProperty(name, value);
      }
   }

   /**
    * Load properties from a map.
    *
    * @param map  Map containing properties to load.
    * @throws PropertyException
    */
   public void load(Map map) throws PropertyException
   {
      load(null, map);
   }

   /**
    * Load properties from a PropertyReader.
    *
    * @param reader  PropertyReader to read properties from.
    * @throws PropertyException
    * @throws IOException
    */
   public void load(PropertyReader reader) throws PropertyException, IOException
   {
      if (reader == null)
         throw new NullArgumentException("reader");

      load(reader.readProperties());
   }

   /**
    * Load properties from a PropertyReader specifed by the given class name.
    *
    * @param className     Class name of a PropertyReader to read from.
    * @throws PropertyException
    * @throws IOException
    */
   public void load(String className) throws PropertyException, IOException
   {
      if (className == null)
         throw new NullArgumentException("className");

      PropertyReader reader = null;

      try
      {
         Class type = Class.forName(className);
         reader = (PropertyReader) type.newInstance();
      }
      catch (Exception e)
      {
         throw new PropertyException(e);
      }

      // load the properties from the source
      load(reader);
   }

   /**
    * Set a property.
    *
    *
Returns Object instead of String due to limitations with
    *    java.util.Properties.
    *
    * @param name    Property name.
    * @param value   Property value.
    * @return        Previous property value or null.
    */
   public synchronized Object setProperty(String name, String value)
   {
      updateJndiCache(name, value);
      return put(name, value);
   }

   public String getProperty(String name)
   {
      Object value = jndiMap.get(name);
      if (value != null)
      {
         // key was in the map
         return (value == NULL_VALUE) ? null : (String) value;
      }
      return super.getProperty(name);
   }

   /**
    * Remove a property.
    *
    * @param name    Property name.
    * @return        Removed property value or null.
    */
   public String removeProperty(String name)
   {
      return (String) remove(name);
   }

   /**
    * Make an indexed property name.
    *
    * @param base    Base property name.
    * @param index   Property index.
    * @return        Indexed property name.
    */
   protected String makeIndexPropertyName(String base, int index)
   {
      return base + PROPERTY_NAME_SEPARATOR + index;
   }

   /**
    * Get an array style property.
    *
    *
Array properties are specified as:
    *    base_property_name.INDEX.
    *
    *
Indexes begin with zero and must be contiguous.  A break in
    *    continuity signals the end of the array.
    *
    * @param base          Base property name.
    * @param defaultValues Default property values.
    * @return              Array of property values or default.
    */
   public String[] getArrayProperty(String base, String[] defaultValues)
   {
      if (base == null)
         throw new NullArgumentException("base");

      // create a new list to store indexed values into
      List list = new LinkedList();

      int i = 0;
      while (true)
      {
         // make the index property name
         String name = makeIndexPropertyName(base, i);

         // see if there is a value for this property
         String value = getProperty(name);

         if (value != null)
         {
            list.add(value);
         }
         else if (i >= 0)
         {
            break; // no more index properties
         }

         i++;
      }

      String values[] = defaultValues;

      // if the list is not empty, then return it as an array
      if (list.size() != 0)
      {
         values = (String[]) list.toArray(new String[list.size()]);
      }

      return values;
   }

   /**
    * Get an array style property.
    *
    * @param name       Property name.
    * @return           Array of property values or empty array.
    */
   public String[] getArrayProperty(String name)
   {
      return getArrayProperty(name, EMPTY_ARRAY_PROPERTY);
   }

   /**
    * Return an iterator over all contained property names.
    *
    * @return     Property name iterator.
    */
   public Iterator names()
   {
      return keySet().iterator();
   }

   /**
    * Check if this map contains a given property.
    *
    * @param name    Property name.
    * @return        True if contains property.
    */
   public boolean containsProperty(String name)
   {
      return containsKey(name);
   }

   /**
    * Get a property group for the given property base.
    *
    * @param basename   Base property name.
    * @return           Property group.
    */
   public PropertyGroup getPropertyGroup(String basename)
   {
      return new PropertyGroup(basename, this);
   }

   /**
    * Get a property group for the given property base at the given index.
    *
    * @param basename   Base property name.
    * @param index      Array property index.
    * @return           Property group.
    */
   public PropertyGroup getPropertyGroup(String basename, int index)
   {
      String name = makeIndexPropertyName(basename, index);
      return getPropertyGroup(name);
   }

   private void readObject(ObjectInputStream stream)
      throws IOException, ClassNotFoundException
   {
      // reset the listener lists
      init();

      stream.defaultReadObject();
   }

   private void writeObject(ObjectOutputStream stream)
      throws IOException
   {
      stream.defaultWriteObject();
   }
}


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

import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.Collections;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import org.jboss.logging.Logger;
import org.jboss.util.NotImplementedException;
import org.jboss.util.StringPropertyReplacer;
import org.w3c.dom.ls.LSInput;
import org.w3c.dom.ls.LSResourceResolver;
import org.xml.sax.EntityResolver;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

/**
 * Local entity resolver to handle standard J2EE DTDs and Schemas as well as JBoss
 * specific DTDs.
 * <p/>
 * Function boolean isEntityResolved() is here to avoid validation errors in
 * descriptors that do not have a DOCTYPE declaration.
 *
 * @author Scott.Stark@jboss.org
 * @author Thomas.Diesler@jboss.org
 * @author Dimitris.Andreadis@jboss.org
 * @version $Revision$
 */
@SuppressWarnings("unchecked")
public class JBossEntityResolver implements EntityResolver, LSResourceResolver
{
   private static final Logger log = Logger.getLogger(JBossEntityResolver.class);

   /** A class wide Map<String, String> of publicId/systemId to dtd/xsd file */
   private static Map entities = new ConcurrentHashMap ();
   /** A class flag indicating whether an attempt to resolve a systemID as a
    non-file URL should produce a warning rather than a trace level log msg.
    */
   private static boolean warnOnNonFileURLs;

   /** Should system property refs in system ids be replaced */
   private boolean replaceSystemProperties = true;

   /** A local entities map that overrides the class level entities */
   private Map localEntities;

   private ThreadLocal<Boolean> entityResolved = new ThreadLocal<Boolean>();

   static
   {
      AccessController.doPrivileged(new PrivilegedAction()
      {
         public Object run()
         {
            warnOnNonFileURLs = new Boolean(System.getProperty("org.jboss.resolver.warning", "false")).booleanValue();
            return null;
         }
      });

      // xml
      registerEntity("-//W3C//DTD/XMLSCHEMA 200102//EN", "XMLSchema.dtd");
      registerEntity("http://www.w3.org/2001/XMLSchema.dtd", "XMLSchema.dtd");
      registerEntity("datatypes", "datatypes.dtd"); // This dtd doesn't have a publicId - see XMLSchema.dtd
      registerEntity("http://www.w3.org/XML/1998/namespace", "xml.xsd");
      registerEntity("http://www.w3.org/2001/xml.xsd", "xml.xsd");
      registerEntity("http://www.w3.org/2005/05/xmlmime", "xml-media-types.xsd");

      // Java EE common
      registerEntity("http://java.sun.com/xml/ns/j2ee/j2ee_1_4.xsd", "j2ee_1_4.xsd");
      registerEntity("http://java.sun.com/xml/ns/javaee/javaee_5.xsd", "javaee_5.xsd");
      // JBoss common
      registerEntity("http://www.jboss.org/j2ee/schema/jboss-common_5_1.xsd", "jboss-common_5_1.xsd");

      // Java EE WS
      registerEntity("http://schemas.xmlsoap.org/soap/encoding/", "soap-encoding_1_1.xsd");
      registerEntity("http://www.ibm.com/webservices/xsd/j2ee_web_services_client_1_1.xsd", "j2ee_web_services_client_1_1.xsd");
      registerEntity("http://www.ibm.com/webservices/xsd/j2ee_web_services_1_1.xsd", "j2ee_web_services_1_1.xsd");
      registerEntity("http://www.ibm.com/webservices/xsd/j2ee_jaxrpc_mapping_1_1.xsd", "j2ee_jaxrpc_mapping_1_1.xsd");
      registerEntity("http://java.sun.com/xml/ns/javaee/javaee_web_services_client_1_2.xsd", "javaee_web_services_client_1_2.xsd");

      // ejb
      registerEntity("-//Sun Microsystems, Inc.//DTD Enterprise JavaBeans 1.1//EN", "ejb-jar_1_1.dtd");
      registerEntity("-//Sun Microsystems, Inc.//DTD Enterprise JavaBeans 2.0//EN", "ejb-jar_2_0.dtd");
      registerEntity("http://java.sun.com/xml/ns/j2ee/ejb-jar_2_1.xsd", "ejb-jar_2_1.xsd");
      // ejb3
      registerEntity("http://java.sun.com/xml/ns/javaee/ejb-jar_3_0.xsd", "ejb-jar_3_0.xsd");
      // jboss ejb2
      registerEntity("-//JBoss//DTD JBOSS//EN", "jboss.dtd");
      registerEntity("-//JBoss//DTD JBOSS 2.4//EN", "jboss_2_4.dtd");
      registerEntity("-//JBoss//DTD JBOSS 3.0//EN", "jboss_3_0.dtd");
      registerEntity("-//JBoss//DTD JBOSS 3.2//EN", "jboss_3_2.dtd");
      registerEntity("-//JBoss//DTD JBOSS 4.0//EN", "jboss_4_0.dtd");
      registerEntity("-//JBoss//DTD JBOSS 4.2//EN", "jboss_4_2.dtd");
      registerEntity("-//JBoss//DTD JBOSS 5.0//EN", "jboss_5_0.dtd");
      registerEntity("-//JBoss//DTD JBOSS 5.1.EAP//EN", "jboss_5_1_eap.dtd");
      registerEntity("-//JBoss//DTD JBOSS 6.0//EN", "jboss_6_0.dtd");
      registerEntity("-//JBoss//DTD JBOSSCMP-JDBC 3.0//EN", "jbosscmp-jdbc_3_0.dtd");
      registerEntity("-//JBoss//DTD JBOSSCMP-JDBC 3.2//EN", "jbosscmp-jdbc_3_2.dtd");
      registerEntity("-//JBoss//DTD JBOSSCMP-JDBC 4.0//EN", "jbosscmp-jdbc_4_0.dtd");
      registerEntity("-//JBoss//DTD JBOSSCMP-JDBC 4.2//EN", "jbosscmp-jdbc_4_2.dtd");
      // jboss ejb3
      registerEntity("http://www.jboss.org/j2ee/schema/jboss_5_0.xsd", "jboss_5_0.xsd");
      registerEntity("http://www.jboss.org/j2ee/schema/jboss_5_1.xsd", "jboss_5_1.xsd");

      // ear stuff
      registerEntity("-//Sun Microsystems, Inc.//DTD J2EE Application 1.2//EN", "application_1_2.dtd");
      registerEntity("-//Sun Microsystems, Inc.//DTD J2EE Application 1.3//EN", "application_1_3.dtd");
      registerEntity("-//Sun Microsystems, Inc.//DTD J2EE Application Client 1.3//EN", "application-client_1_3.dtd");
      registerEntity("http://java.sun.com/xml/ns/j2ee/application_1_4.xsd", "application_1_4.xsd");
      registerEntity("http://java.sun.com/xml/ns/javaee/application_5.xsd", "application_5.xsd");
      // jboss-app
      registerEntity("-//JBoss//DTD J2EE Application 1.3//EN", "jboss-app_3_0.dtd");
      registerEntity("-//JBoss//DTD J2EE Application 1.3V2//EN", "jboss-app_3_2.dtd");
      registerEntity("-//JBoss//DTD J2EE Application 1.4//EN", "jboss-app_4_0.dtd");
      registerEntity("-//JBoss//DTD J2EE Application 4.2//EN", "jboss-app_4_2.dtd");
      registerEntity("-//JBoss//DTD Java EE Application 5.0//EN", "jboss-app_5_0.dtd");

      // connector descriptors
      registerEntity("-//Sun Microsystems, Inc.//DTD Connector 1.0//EN", "connector_1_0.dtd");
      registerEntity("http://java.sun.com/xml/ns/j2ee/connector_1_5.xsd", "connector_1_5.xsd");
      registerEntity("http://java.sun.com/xml/ns/j2ee/connector_1_6.xsd", "connector_1_6.xsd");
      // jboss-ds
      registerEntity("-//JBoss//DTD JBOSS JCA Config 1.0//EN", "jboss-ds_1_0.dtd");
      registerEntity("-//JBoss//DTD JBOSS JCA Config 1.5//EN", "jboss-ds_1_5.dtd");
      registerEntity("http://www.jboss.org/j2ee/schema/jboss-ds_5_0.xsd", "jboss-ds_5_0.xsd");
      // jboss-ra
      registerEntity("http://www.jboss.org/j2ee/schema/jboss-ra_1_0.xsd", "jboss-ra_1_0.xsd");

      // war meta-data
      registerEntity("-//Sun Microsystems, Inc.//DTD Web Application 2.2//EN", "web-app_2_2.dtd");
      registerEntity("-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN", "web-app_2_3.dtd");
      registerEntity("http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd", "web-app_2_4.xsd");
      registerEntity("http://java.sun.com/xml/ns/j2ee/web-app_2_5.xsd", "web-app_2_5.xsd");
      registerEntity("http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd", "web-app_3_0.xsd");
      // jboss-web
      registerEntity("-//JBoss//DTD Web Application 2.2//EN", "jboss-web.dtd");
      registerEntity("-//JBoss//DTD Web Application 2.3//EN", "jboss-web_3_0.dtd");
      registerEntity("-//JBoss//DTD Web Application 2.3V2//EN", "jboss-web_3_2.dtd");
      registerEntity("-//JBoss//DTD Web Application 2.4//EN", "jboss-web_4_0.dtd");
      registerEntity("-//JBoss//DTD Web Application 4.2//EN", "jboss-web_4_2.dtd");
      registerEntity("-//JBoss//DTD Web Application 5.0//EN", "jboss-web_5_0.dtd");
      registerEntity("http://www.jboss.org/j2ee/schema/jboss-web_5_1.xsd", "jboss-web_5_1.xsd");

      // application client
      registerEntity("http://java.sun.com/xml/ns/j2ee/application-client_1_4.xsd", "application-client_1_4.xsd");
      registerEntity("http://java.sun.com/xml/ns/javaee/application-client_5.xsd", "application-client_5.xsd");
      // jboss-client
      registerEntity("-//JBoss//DTD Application Client 3.2//EN", "jboss-client_3_2.dtd");
      registerEntity("-//JBoss//DTD Application Client 4.0//EN", "jboss-client_4_0.dtd");
      registerEntity("-//JBoss//DTD Application Client 4.2//EN", "jboss-client_4_2.dtd");
      registerEntity("-//JBoss//DTD Application Client 5.0//EN", "jboss-client_5_0.dtd");
      registerEntity("http://www.jboss.org/j2ee/schema/jboss-client_5_1.xsd", "jboss-client_5_1.xsd");

      // jboss-specific
      registerEntity("-//JBoss//DTD Web Service Reference 4.0//EN", "service-ref_4_0.dtd");
      registerEntity("-//JBoss//DTD Web Service Reference 4.2//EN", "service-ref_4_2.dtd");
      registerEntity("-//JBoss//DTD Web Service Reference 5.0//EN", "service-ref_5_0.dtd");
      registerEntity("-//JBoss//DTD MBean Service 3.2//EN", "jboss-service_3_2.dtd");
      registerEntity("-//JBoss//DTD MBean Service 4.0//EN", "jboss-service_4_0.dtd");
      registerEntity("-//JBoss//DTD MBean Service 4.2//EN", "jboss-service_4_2.dtd");
      registerEntity("-//JBoss//DTD MBean Service 5.0//EN", "jboss-service_5_0.dtd");
      registerEntity("-//JBoss//DTD JBOSS XMBEAN 1.0//EN", "jboss_xmbean_1_0.dtd");
      registerEntity("-//JBoss//DTD JBOSS XMBEAN 1.1//EN", "jboss_xmbean_1_1.dtd");
      registerEntity("-//JBoss//DTD JBOSS XMBEAN 1.2//EN", "jboss_xmbean_1_2.dtd");
      registerEntity("-//JBoss//DTD JBOSS Security Config 3.0//EN", "security_config.dtd");
      registerEntity("http://www.jboss.org/j2ee/schema/security-config_4_0.xsd", "security-config_4_0.xsd");
      registerEntity("urn:jboss:aop-deployer", "aop-deployer_1_1.xsd");
      registerEntity("urn:jboss:aop-beans:1.0", "aop-beans_1_0.xsd");
      registerEntity("urn:jboss:bean-deployer", "bean-deployer_1_0.xsd");
      registerEntity("urn:jboss:bean-deployer:2.0", "bean-deployer_2_0.xsd");
      registerEntity("urn:jboss:javabean:1.0", "javabean_1_0.xsd");
      registerEntity("urn:jboss:javabean:2.0", "javabean_2_0.xsd");
      registerEntity("urn:jboss:spring-beans:2.0", "mc-spring-beans_2_0.xsd");
      registerEntity("urn:jboss:policy:1.0", "policy_1_0.xsd");
      registerEntity("urn:jboss:osgi-beans:1.0", "osgi-beans_1_0.xsd");
      registerEntity("urn:jboss:seam-components:1.0", "seam-components_1_0.xsd");
      registerEntity("urn:jboss:security-config:4.1", "security-config_4_1.xsd");
      registerEntity("urn:jboss:security-config:5.0", "security-config_5_0.xsd");
      registerEntity("urn:jboss:jndi-binding-service:1.0", "jndi-binding-service_1_0.xsd");
      registerEntity("urn:jboss:user-roles:1.0", "user-roles_1_0.xsd");
   }

   /**
    Obtain a read-only view of the current entity map.

    @return Map<String, String> of the publicID/systemID to dtd/schema file name
    */
   public static Map getEntityMap()
   {
      return Collections.unmodifiableMap(entities);
   }

   public static boolean isWarnOnNonFileURLs()
   {
      return warnOnNonFileURLs;
   }

   public static void setWarnOnNonFileURLs(boolean warnOnNonFileURLs)
   {
      JBossEntityResolver.warnOnNonFileURLs = warnOnNonFileURLs;
   }

   /**
    * Register the mapping from the public id/system id to the dtd/xsd file
    * name. This overwrites any existing mapping.
    *
    * @param id  the DOCTYPE public id or system id such as
    * "-//Sun Microsystems, Inc.//DTD Enterprise JavaBeans 1.1//EN"
    * @param dtdFileName the simple dtd/xsd file name, "ejb-jar.dtd"
    */
   public static void registerEntity(String id, String dtdFileName)
   {
      entities.put(id, dtdFileName);
   }

   public boolean isReplaceSystemProperties()
   {
      return replaceSystemProperties;
   }

   public void setReplaceSystemProperties(boolean replaceSystemProperties)
   {
      this.replaceSystemProperties = replaceSystemProperties;
   }

   /**
    * Register the mapping from the public id/system id to the dtd/xsd file
    * name. This overwrites any existing mapping.
    *
    * @param id  the DOCTYPE public id or system id such as
    * "-//Sun Microsystems, Inc.//DTD Enterprise JavaBeans 1.1//EN"
    * @param dtdOrSchema the simple dtd/xsd file name, "ejb-jar.dtd"
    */
   public synchronized void registerLocalEntity(String id, String dtdOrSchema)
   {
      if( localEntities == null )
         localEntities = new ConcurrentHashMap();
      localEntities.put(id, dtdOrSchema);
   }

   /**
    Returns DTD/Schema inputSource. The resolution logic is:

    1. Check the publicId against the current registered values in the class
    mapping of entity name to dtd/schema file name. If found, the resulting
    file name is passed to the loadClasspathResource to locate the file as a
    classpath resource.

    2. Check the systemId against the current registered values in the class
    mapping of entity name to dtd/schema file name. If found, the resulting
    file name is passed to the loadClasspathResource to locate the file as a
    classpath resource.

    3. Strip the systemId name down to the simple file name by removing an URL
    style path elements (myschemas/x.dtd becomes x.dtd), and call
    loadClasspathResource to locate the simple file name as a classpath resource.

    4. Attempt to resolve the systemId as a URL from which the schema can be
    read. If the URL input stream can be opened this returned as the resolved
    input.

    @param publicId - Public ID of DTD, or null if it is a schema
    @param systemId - the system ID of DTD or Schema
    @return InputSource of entity
    */
   public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException
   {
      entityResolved.set(Boolean.FALSE);

      // nothing to resolve
      if( publicId == null && systemId == null )
         return null;

      boolean trace = log.isTraceEnabled();

      boolean resolvePublicIdFirst = true;
      if(publicId != null && systemId != null)
      {
         String registeredSystemId = null;
         if(localEntities != null)
            registeredSystemId = (String) localEntities.get(publicId);
         if(registeredSystemId == null)
            registeredSystemId = (String) entities.get(publicId);

         if(registeredSystemId != null && !registeredSystemId.equals(systemId))
         {
            resolvePublicIdFirst = false;
            if(trace)
               log.trace("systemId argument '" + systemId + "' for publicId '" +
                     publicId + "' is different from the registered systemId '" +
                     registeredSystemId + "', resolution will be based on the argument");
         }
      }

      InputSource inputSource = null;

      if(resolvePublicIdFirst)
      {
         // Look for a registered publicID
         inputSource = resolvePublicID(publicId, trace);
      }

      if( inputSource == null )
      {
         // Try to resolve the systemID from the registry
         inputSource = resolveSystemID(systemId, trace);
      }

      if( inputSource == null )
      {
         // Try to resolve the systemID as a classpath reference under dtd or schema
         inputSource = resolveClasspathName(systemId, trace);
      }

      if( inputSource == null )
      {
         // Try to resolve the systemID as a absolute URL
         inputSource = resolveSystemIDasURL(systemId, trace);
      }

      entityResolved.set(new Boolean(inputSource != null));

      if (inputSource == null)
         log.debug("Cannot resolve [publicID=" + publicId + ",systemID=" + systemId + "]");

      return inputSource;
   }

   /**
    * Returns the boolean value to inform id DTD was found in the XML file or not
    *
    * @return boolean - true if DTD was found in XML
    */
   public boolean isEntityResolved()
   {
       Boolean value = entityResolved.get();
       return value != null ? value.booleanValue() : false;
   }

   /**
    Load the schema from the class entity to schema file mapping.
    @see #registerEntity(String, String)

    @param publicId - the public entity name of the schema
    @param trace - trace level logging flag
    @return the InputSource for the schema file found on the classpath, null
      if the publicId is not registered or found.
    */
   protected InputSource resolvePublicID(String publicId, boolean trace)
   {
      if( publicId == null )
         return null;

      if (trace)
         log.trace("resolvePublicID, publicId=" + publicId);

      InputSource inputSource = null;

      String filename = null;
      if( localEntities != null )
         filename = (String) localEntities.get(publicId);
      if( filename == null )
         filename = (String) entities.get(publicId);

      if( filename != null )
      {
         if (trace)
            log.trace("Found entity from publicId=" + publicId + " fileName=" + filename);

         InputStream ins = loadClasspathResource(filename, trace);
         if( ins != null )
         {
            inputSource = new InputSource(ins);
            inputSource.setPublicId(publicId);
         }
         else
         {
            log.trace("Cannot load publicId from classpath resource: " + filename);

            // Try the file name as a URI
            inputSource = resolveSystemIDasURL(filename, trace);

            if (inputSource == null)
               log.warn("Cannot load publicId from resource: " + filename);
         }
      }

      return inputSource;
   }

   /**
    Attempt to use the systemId as a URL from which the schema can be read. This
    checks to see whether the systemId is a key to an entry in the class
    entity map.

    @param systemId - the systemId
    @param trace - trace level logging flag
    @return the URL InputSource if the URL input stream can be opened, null
      if the systemId is not a URL or could not be opened.
    */
   protected InputSource resolveSystemID(String systemId, boolean trace)
   {
      if( systemId == null )
         return null;

      if( trace )
         log.trace("resolveSystemID, systemId="+systemId);

      InputSource inputSource = null;

      // Try to resolve the systemId as an entity key
      String filename = null;
      if( localEntities != null )
         filename = (String) localEntities.get(systemId);
      if( filename == null )
         filename = (String) entities.get(systemId);

      if ( filename != null )
      {
         if( trace )
            log.trace("Found entity systemId=" + systemId + " fileName=" + filename);

         InputStream ins = loadClasspathResource(filename, trace);
         if( ins != null )
         {
            inputSource = new InputSource(ins);
            inputSource.setSystemId(systemId);
         }
         else
         {
            log.warn("Cannot load systemId from resource: " + filename);
         }
      }

      return inputSource;
   }

   /**
   Attempt to use the systemId as a URL from which the schema can be read. This
   uses the systemID as a URL.

   @param systemId - the systemId
   @param trace - trace level logging flag
   @return the URL InputSource if the URL input stream can be opened, null
     if the systemId is not a URL or could not be opened.
   */
  protected InputSource resolveSystemIDasURL(String systemId, boolean trace)
  {
     if( systemId == null )
        return null;

     if( trace )
        log.trace("resolveSystemIDasURL, systemId="+systemId);

     InputSource inputSource = null;

     // Try to use the systemId as a URL to the schema
      try
      {
         if (trace)
            log.trace("Trying to resolve systemId as a URL");
         // Replace any system property refs if isReplaceSystemProperties is true
         if(isReplaceSystemProperties())
            systemId = StringPropertyReplacer.replaceProperties(systemId);
         URL url = new URL(systemId);
         if (warnOnNonFileURLs
             && url.getProtocol().equalsIgnoreCase("file") == false
             && url.getProtocol().equalsIgnoreCase("vfszip") == false)
         {
            log.warn("Trying to resolve systemId as a non-file URL: " + systemId);
         }

         InputStream ins = url.openStream();
         if (ins != null)
         {
            inputSource = new InputSource(ins);
            inputSource.setSystemId(systemId);
         }
         else
         {
            log.warn("Cannot load systemId as URL: " + systemId);
         }

         if (trace)
            log.trace("Resolved systemId as a URL");
      }
      catch (MalformedURLException ignored)
      {
         if (trace)
            log.trace("SystemId is not a url: " + systemId, ignored);
      }
      catch (IOException e)
      {
         if (trace)
            log.trace("Failed to obtain URL.InputStream from systemId: " + systemId, e);
      }
      return inputSource;
  }

   /**
    Resolve the systemId as a classpath resource. If not found, the
    systemId is simply used as a classpath resource name.

    @param systemId - the system ID of DTD or Schema
    @param trace - trace level logging flag
    @return the InputSource for the schema file found on the classpath, null
      if the systemId is not registered or found.
    */
   protected InputSource resolveClasspathName(String systemId, boolean trace)
   {
      if( systemId == null )
         return null;

      if( trace )
         log.trace("resolveClasspathName, systemId="+systemId);
      String filename = systemId;
      // Parse the systemId as a uri to get the final path component
      try
      {
         URI url = new URI(systemId);
         String path = url.getPath();
         if( path == null )
            path = url.getSchemeSpecificPart();
         int slash = path.lastIndexOf('/');
         if( slash >= 0 )
            filename = path.substring(slash + 1);
         else
            filename = path;

         if(filename.length() == 0)
            return null;

         if (trace)
            log.trace("Mapped systemId to filename: " + filename);
      }
      catch (URISyntaxException e)
      {
         if (trace)
            log.trace("systemId: is not a URI, using systemId as resource", e);
      }

      // Resolve the filename as a classpath resource
      InputStream is = loadClasspathResource(filename, trace);
      InputSource inputSource = null;
      if( is != null )
      {
         inputSource = new InputSource(is);
         inputSource.setSystemId(systemId);
      }
      return inputSource;
   }

   /**
    Look for the resource name on the thread context loader resource path. This
    first simply tries the resource name as is, and if not found, the resource
    is prepended with either "dtd/" or "schema/" depending on whether the
    resource ends in ".dtd" or ".xsd".

    @param resource - the classpath resource name of the schema
    @param trace - trace level logging flag
    @return the resource InputStream if found, null if not found.
    */
   protected InputStream loadClasspathResource(String resource, boolean trace)
   {
      ClassLoader loader = Thread.currentThread().getContextClassLoader();
      URL url = loader.getResource(resource);
      if( url == null )
      {
         /* Prefix the simple filename with the schema type patch as this is the
               naming convention for the jboss bundled schemas.
            */
         if( resource.endsWith(".dtd") )
            resource = "dtd/" + resource;
         else if( resource.endsWith(".xsd") )
            resource = "schema/" + resource;
         url = loader.getResource(resource);
      }

      InputStream inputStream = null;
      if( url != null )
      {
         if( trace )
            log.trace(resource+" maps to URL: "+url);
         try
         {
            inputStream = url.openStream();
         }
         catch(IOException e)
         {
            log.debug("Failed to open url stream", e);
         }
      }
      return inputStream;
   }

    public LSInput resolveResource(String type, String namespaceURI, String publicId, String systemId, String baseURI) {
        InputSource inputSource = null;
        try {
            inputSource = resolveEntity(publicId, systemId);
        } catch (Exception e) {
            log.debug("Failed to resolve resource", e);
        }
        LSInput result = null;
        if (inputSource != null) {
            result = new LSInputImpl(publicId, systemId, baseURI, inputSource);
        }
        return result;
    }

    private static class LSInputImpl implements LSInput {

        private final String systemID;
        private final String publicID;
        private final String baseURI;
        private final InputSource inputSource;

        public LSInputImpl(String publicID, String systemID, String baseURI, InputSource inputSource) {
            this.inputSource = inputSource;
            this.systemID = systemID;
            this.publicID = publicID;
            this.baseURI = baseURI;
        }

        public Reader getCharacterStream() {
            return inputSource.getCharacterStream();
        }

        public void setCharacterStream(Reader characterStream) {
            throw new NotImplementedException();
        }

        public InputStream getByteStream() {
            return inputSource.getByteStream();
        }

        public void setByteStream(InputStream byteStream) {
            throw new NotImplementedException();
        }

        public String getStringData() {
            return null;
        }

        public void setStringData(String stringData) {
            throw new NotImplementedException();
        }

        public String getSystemId() {
            return systemID;
        }

        public void setSystemId(String systemId) {
            throw new NotImplementedException();
        }

        public String getPublicId() {
            return publicID;
        }

        public void setPublicId(String publicId) {
            throw new NotImplementedException();
        }

        public String getBaseURI() {
            return baseURI;
        }

        public void setBaseURI(String baseURI) {
            throw new NotImplementedException();
        }

        public String getEncoding() {
            return null;
        }

        public void setEncoding(String encoding) {
            throw new NotImplementedException();
        }

        public boolean getCertifiedText() {
            return false;
        }

        public void setCertifiedText(boolean certifiedText) {
            throw new NotImplementedException();
        }
    }
}


/*
 * 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 org.w3c.dom.Document;

/**
 * A property editor for {@link org.w3c.dom.Element}.
 *
 * @version <tt>$Revision$</tt>
 * @author  Elias Ross
 */
public class ElementEditor extends DocumentEditor
{
   /**
    * Sets as an Element created by a String.
    */
   public void setAsText(String text)
   {
      Document d = getAsDocument(text);
      setValue(d.getDocumentElement());
   }
}


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

// PublicId.java - Information about public identifiers

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

/**
 * Static methods for dealing with public identifiers.
 *
 *
This class defines a set of static methods that can be called
 * to handle public identifiers.

 *
 * @author Norman Walsh
 * Norman.Walsh@Sun.COM
 *
 * @version 1.0
 */
public abstract class PublicId {
  protected PublicId() { }

  /**
   * Normalize a public identifier.
   *
   *
Public identifiers must be normalized according to the following
   * rules before comparisons between them can be made:

   *
   *

   *
Whitespace characters are normalized to spaces (e.g., line feeds,
   * tabs, etc. become spaces).

   *
Leading and trailing whitespace is removed.

   *
Multiple internal whitespaces are normalized to a single
   * space.

   *

   *
   *
This method is declared static so that other classes
   * can use it directly.

   *
   * @param publicId The unnormalized public identifier.
   *
   * @return The normalized identifier.
   */
  public static String normalize(String publicId) {
    String normal = publicId.replace('\t', ' ');
    normal = normal.replace('\r', ' ');
    normal = normal.replace('\n', ' ');
    normal = normal.trim();

    int pos;

    while ((pos = normal.indexOf("  ")) >= 0) {
      normal = normal.substring(0, pos) + normal.substring(pos+1);
    }

    return normal;
  }

  /**
   * Encode a public identifier as a "publicid" URN.
   *
   *
This method is declared static so that other classes
   * can use it directly.

   *
   * @param publicId The unnormalized public identifier.
   *
   * @return The normalized identifier.
   */
  public static String encodeURN(String publicId) {
    String urn = PublicId.normalize(publicId);

    urn = PublicId.stringReplace(urn, "%", "%25");
    urn = PublicId.stringReplace(urn, ";", "%3B");
    urn = PublicId.stringReplace(urn, "'", "%27");
    urn = PublicId.stringReplace(urn, "?", "%3F");
    urn = PublicId.stringReplace(urn, "#", "%23");
    urn = PublicId.stringReplace(urn, "+", "%2B");
    urn = PublicId.stringReplace(urn, " ", "+");
    urn = PublicId.stringReplace(urn, "::", ";");
    urn = PublicId.stringReplace(urn, ":", "%3A");
    urn = PublicId.stringReplace(urn, "//", ":");
    urn = PublicId.stringReplace(urn, "/", "%2F");

    return "urn:publicid:" + urn;
  }

  /**
   * Decode a "publicid" URN into a public identifier.
   *
   *
This method is declared static so that other classes
   * can use it directly.

   *
   * @param urn The unnormalized public identifier.
   *
   * @return The normalized identifier.
   */
  public static String decodeURN(String urn) {
    String publicId = "";

    if (urn.startsWith("urn:publicid:")) {
      publicId = urn.substring(13);
    } else {
      return urn;
    }

    publicId = PublicId.stringReplace(publicId, "%2F", "/");
    publicId = PublicId.stringReplace(publicId, ":", "//");
    publicId = PublicId.stringReplace(publicId, "%3A", ":");
    publicId = PublicId.stringReplace(publicId, ";", "::");
    publicId = PublicId.stringReplace(publicId, "+", " ");
    publicId = PublicId.stringReplace(publicId, "%2B", "+");
    publicId = PublicId.stringReplace(publicId, "%23", "#");
    publicId = PublicId.stringReplace(publicId, "%3F", "?");
    publicId = PublicId.stringReplace(publicId, "%27", "'");
    publicId = PublicId.stringReplace(publicId, "%3B", ";");
    publicId = PublicId.stringReplace(publicId, "%25", "%");

    return publicId;
    }

  /**
   * Replace one string with another.
   *
   */
  private static String stringReplace(String str,
				      String oldStr,
				      String newStr) {

    String result = "";
    int pos = str.indexOf(oldStr);

    //    System.out.println(str + ": " + oldStr + " => " + newStr);

    while (pos >= 0) {
      //      System.out.println(str + " (" + pos + ")");
      result += str.substring(0, pos);
      result += newStr;
      str = str.substring(pos+1);

      pos = str.indexOf(oldStr);
    }

    return result + str;
  }
}


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

/**
 * Simulates a stop watch with a lap counter.
 *
 * @version <tt>$Revision$</tt>
 * @author  Jason Dillon
 */
public class StopWatch
   implements Serializable, Cloneable
{
   /** The serialVersionUID */
   private static final long serialVersionUID = 4628094303187435707L;
   /** Total time */
   protected long total = 0;

   /** Start time */
   protected long start = -1;

   /** Stop time */
   protected long stop = -1;

   /** The lap count */
   protected int count = 0;

   /** Is the watch started */
   protected boolean running = false;

   /**
    * Default constructor.
    */
   public StopWatch() {}

   /**
    * Construct a StopWatch.
    *
    * @param running    Start the watch
    */
   public StopWatch(final boolean running)
   {
      if (running) start();
   }

   /**
    * Start the watch.
    *
    * @param reset   True to reset the watch prior to starting.
    */
   public void start(final boolean reset)
   {
      if (!running) {
         if (reset) reset();
         start = System.currentTimeMillis();
         running = true;
      }
   }

   /**
    * Start the watch.
    */
   public void start()
   {
      start(false);
   }

   /**
    * Stop the watch.
    *
    * @return  Elapsed time or 0 if the watch was never started.
    */
   public long stop()
   {
      long lap = 0;

      if (running) {
         count++;
         stop = System.currentTimeMillis();
         lap = stop - start;
         total += lap;
         running = false;
      }

      return lap;
   }

   /**
    * Reset the watch.
    */
   public void reset()
   {
      start = -1;
      stop = -1;
      total = 0;
      count = 0;
      running = false;
   }

   /**
    * Get the lap count.
    *
    * @return  The lap count.
    */
   public int getLapCount()
   {
      return count;
   }

   /**
    * Get the elapsed lap time since the watch was started.
    *
    * @return  Elapsed lap time or 0 if the watch was never started
    */
   public long getLapTime()
   {
      if (start == -1) {
         return 0;
      }
      else if (running) {
         return System.currentTimeMillis() - start;
      }
      else {
         return stop - start;
      }
   }

   /**
    * Get the average lap time since the watch was started.
    *
    * @return  Average lap time since the watch was started.
    */
   public long getAverageLapTime()
   {
      return (count == 0) ? 0 : getLapTime() / getLapCount();
   }

   /**
    * Get the elapsed time since the watch was created or last reset.
    *
    * @return  Elapsed time or 0 if the watch was never started.
    */
   public long getTime()
   {
      if (start == -1) {
         return 0;
      }
      else if (running) {
         return total + System.currentTimeMillis() - start;
      }
      else {
         return total;
      }
   }

   /**
    * Check if the watch is running.
    *
    * @return  True if the watch is running.
    */
   public boolean isRunning()
   {
      return running;
   }

   /**
    * Return a string representation.
    */
   public String toString()
   {
      StringBuffer buff = new StringBuffer();

      if (running) {
         // the watch has not been stopped
         formatElapsedTime(buff, getTime());

         // add the current lap time too if there is more than one lap
         if (count >= 1) {
            buff.append(", count=").append(count);
            buff.append(", current=");
            formatElapsedTime(buff, getLapTime());
         }
      }
      else {
         // the watch has been stopped
         formatElapsedTime(buff, getTime());

         // add extra info if there is more than one lap
         if (count > 1) {
            buff.append(", count=").append(count);
            buff.append(", average=");
            formatElapsedTime(buff, getAverageLapTime());
         }
      }

      return buff.toString();
   }

   private void formatElapsedTime(final StringBuffer buff, final long lapsed)
   {
      long m = lapsed / 60000;
      if (m != 0) {
         buff.append(m).append("m:");
      }

      long s = (lapsed - 60000 * m) / 1000;
      if (s != 0) {
         buff.append(s).append("s:");
      }

      // ms is always there, even if it was 0 too
      long ms = (lapsed - 60000 * m - 1000 * s);
      buff.append(ms).append("ms");
   }

   /**
    * Return a cloned copy of this object.
    *
    * @return  A cloned copy of this object.
    */
   public Object clone()
   {
      try {
         return super.clone();
      }
      catch (CloneNotSupportedException e) {
         throw new InternalError();
      }
   }

   /////////////////////////////////////////////////////////////////////////
   //                                Wrappers                             //
   /////////////////////////////////////////////////////////////////////////

   /**
    * Base wrapper class for other wrappers.
    */
   private static class Wrapper
      extends StopWatch
   {
      /** The serialVersionUID */
      private static final long serialVersionUID = 6859401939735540773L;
      protected StopWatch watch;

      public Wrapper(final StopWatch watch) {
         this.watch = watch;
      }

      public void start(final boolean reset) {
         watch.start(reset);
      }

      public void start() {
         watch.start();
      }

      public long stop() {
         return watch.stop();
      }

      public void reset() {
         watch.reset();
      }

      public long getLapTime() {
         return watch.getLapTime();
      }

      public long getAverageLapTime() {
         return watch.getAverageLapTime();
      }

      public int getLapCount() {
         return watch.getLapCount();
      }

      public long getTime() {
         return watch.getTime();
      }

      public boolean isRunning() {
         return watch.isRunning();
      }

      public String toString() {
         return watch.toString();
      }
   }

   /**
    * Return a synchronized stop watch.
    *
    * @param watch    StopWatch to synchronize.
    * @return         Synchronized stop watch.
    */
   public static StopWatch makeSynchronized(final StopWatch watch)
   {
      return new Wrapper(watch)
         {
         /** The serialVersionUID */
         private static final long serialVersionUID = -6284244000894114817L;
            public synchronized void start(final boolean reset) {
               this.watch.start(reset);
            }

            public synchronized void start() {
               this.watch.start();
            }

            public synchronized long stop() {
               return this.watch.stop();
            }

            public synchronized void reset() {
               this.watch.reset();
            }

            public synchronized long getLapTime() {
               return this.watch.getLapTime();
            }

            public synchronized long getAverageLapTime() {
               return this.watch.getAverageLapTime();
            }

            public synchronized int getLapCount() {
               return this.watch.getLapCount();
            }

            public synchronized long getTime() {
               return this.watch.getTime();
            }

            public synchronized boolean isRunning() {
               return this.watch.isRunning();
            }

            public synchronized String toString() {
               return this.watch.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;

import java.io.Serializable;

/**
 * A hash-code generator and a collection of static hash-code generation
 * methods.
 *
 * @version <tt>$Revision$</tt>
 * @author  Jason Dillon
 */
@SuppressWarnings("unchecked")
public final class HashCode
   implements Serializable, Cloneable, Comparable
{
   /** The serialVersionUID */
   private static final long serialVersionUID = 2391396931740264021L;
   /** Hashcode for a 'null' value. */
   private static final int NULL_HASHCODE = 0;

   /** Hashcode for a 'true' boolean */
   private static final int TRUE_HASHCODE = 1231; // completely arbitrary

   /** Hashcode for a 'false' boolean */
   private static final int FALSE_HASHCODE = 1237; // completely arbitrary

   /** The hash-code value. */
   private int value;

   /**
    * Construct a new <tt>HashCode</tt> using the given <tt>int</tt> as the
    * base value.
    *
    * @param value   <tt>int</tt> to use as the base value.
    */
   public HashCode(final int value) {
      this.value = value;
   }

   /**
    * Construct a new <tt>HashCode</tt>.
    */
   public HashCode() {
      this(0);
   }

   /**
    * Add the hash-code of the given value.
    *
    * @param b    Value to get hash-code from.
    * @return     This <tt>HashCode</tt>.
    */
   public HashCode add(final boolean b) {
      value ^= generate(b);
      return this;
   }

   /**
    * Add the hash-code of the given value.
    *
    * @param n    Value to get hash-code from.
    * @return     This <tt>HashCode</tt>.
    */
   public HashCode add(final byte n) {
      value ^= n;
      return this;
   }

   /**
    * Add the hash-code of the given value.
    *
    * @param n    Value to get hash-code from.
    * @return     This <tt>HashCode</tt>.
    */
   public HashCode add(final char n) {
      value ^= n;
      return this;
   }

   /**
    * Add the hash-code of the given value.
    *
    * @param n    Value to get hash-code from.
    * @return     This <tt>HashCode</tt>.
    */
   public HashCode add(final short n) {
      value ^= n;
      return this;
   }

   /**
    * Add the hash-code of the given value.
    *
    * @param n    Value to get hash-code from.
    * @return     This <tt>HashCode</tt>.
    */
   public HashCode add(final int n) {
      value ^= n;
      return this;
   }

   /**
    * Add the hash-code of the given value.
    *
    * @param n    Value to get hash-code from.
    * @return     This <tt>HashCode</tt>.
    */
   public HashCode add(final long n) {
      value ^= generate(n);
      return this;
   }

   /**
    * Add the hash-code of the given value.
    *
    * @param f    Value to get hash-code from.
    * @return     This <tt>HashCode</tt>.
    */
   public HashCode add(final float f) {
      value ^= generate(f);
      return this;
   }

   /**
    * Add the hash-code of the given value.
    *
    * @param f    Value to get hash-code from.
    * @return     This <tt>HashCode</tt>.
    */
   public HashCode add(final double f) {
      value ^= generate(f);
      return this;
   }

   /**
    * Add the hash-code of the given object.
    *
    * @param obj    Value to get hash-code from.
    * @return        This <tt>HashCode</tt>.
    */
   public HashCode add(final Object obj) {
      value ^= generate(obj);
      return this;
   }

   /**
    * Get the hash-code.
    *
    * @return   Hash-code.
    */
   public int hashCode() {
      return value;
   }

   /**
    * Compares this object with the specified <tt>int</tt> for order.
    *
    * @param other   Value to compare with.
    * @return        A negative integer, zero, or a positive integer as
    *                this object is less than, equal to, or greater than
    *                the specified object.
    */
   public int compareTo(final int other) {
      return (value < other) ? -1 : (value == other) ? 0 : 1;
   }

   /**
    * Compares this object with the specified object for order.
    *
    * @param obj   Value to compare with.
    * @return        A negative integer, zero, or a positive integer as
    *                this object is less than, equal to, or greater than
    *                the specified object.
    *
    * @throws ClassCastException    Object is not a <tt>HashCode</tt>.
    */
   public int compareTo(final Object obj) throws ClassCastException {
      HashCode hashCode = (HashCode)obj;
      return compareTo(hashCode.value);
   }

   /**
    * Test the equality of this <tt>HashCode</tt> and another object.
    *
    * @param obj    Object to test equality with.
    * @return       True if object is equal.
    */
   public boolean equals(final Object obj) {
      if (obj == this) return true;

      if (obj != null && obj.getClass() == getClass()) {
         return value == ((HashCode)obj).value;
      }

      return false;
   }

   /**
    * Return a string representation of this <tt>HashCode</tt>.
    *
    * @return  A string representation of this <tt>HashCode</tt>.
    */
   public String toString() {
      return String.valueOf(value);
   }

   /**
    * Return a cloned copy of this <tt>HashCode</tt>.
    *
    * @return   Cloned <tt>HashCode</tt>.
    */
   public Object clone() {
      try {
         return super.clone();
      }
      catch (CloneNotSupportedException e) {
         throw new InternalError();
      }
   }

   /////////////////////////////////////////////////////////////////////////
   //                           Generation Methods                        //
   /////////////////////////////////////////////////////////////////////////

   /**
    * Generate a hash code for a boolean value.
    *
    * @param value   Boolean value to generate hash code from.
    * @return        Hash code.
    */
   public static int generate(final boolean value) {
      return value ? TRUE_HASHCODE : FALSE_HASHCODE;
   }

   /**
    * Generate a hash code for a long value.
    *
    * @param value   Long value to generate hash code from.
    * @return        Hash code.
    */
   public static int generate(final long value) {
      return (int)(value ^ (value >> 32));
   }

   /**
    * Generate a hash code for a double value.
    *
    * @param value   Double value to generate hash code from.
    * @return        Hash code.
    */
   public static int generate(final double value) {
      return generate(Double.doubleToLongBits(value));
   }

   /**
    * Generate a hash code for a float value.
    *
    * @param value   Float value to generate hash code from.
    * @return        Hash code.
    */
   public static int generate(final float value) {
      return Float.floatToIntBits(value);
   }

   /**
    * Generate a hash code for a byte array.
    *
    * @param bytes   An array of bytes to generate a hash code from.
    * @return        Hash code.
    */
   public static int generate(final byte[] bytes) {
      int hashcode = 0;

      for (int i=0; i<bytes.length; i++) {
         hashcode <<= 1;
         hashcode ^= bytes[i];
      }

      return hashcode;
   }

   /**
    * Generate a hash code for an object array.
    *
    *
Does not handle nested primitive array elements.
    *
    * @param array   Array to generate hashcode for.
    * @param deep    True to traverse elements which are arrays to
    *                determine the elements hash code.
    * @return        Hash code.
    */
   public static int generate(final Object array[], final boolean deep) {
      int hashcode = 0;

      for (int i=0; i<array.length; i++) {
         if (deep && (array[i] instanceof Object[])) {
            hashcode ^= generate((Object[])array[i], true);
         }
         else {
            hashcode ^= array[i].hashCode();
         }
      }

      return hashcode;
   }

   /**
    * Generate a shallow hash code for an object array.
    *
    * @param array   Array to generate hashcode for.
    * @return        Hash code.
    */
   public static int generate(final Object array[]) {
      return generate(array, false);
   }

   /**
    * Generate a hash code for an object.
    *
    * @param obj     Object to generate hashcode for.
    * @return        Hash code.
    */
   public static int generate(final Object obj) {
      if (obj != null) {
         return obj.hashCode();
      }

      return NULL_HASHCODE;
   }
}