/**
 * $RCSfile$
 * $Revision: $
 * $Date: $
 *
 * Copyright 2008 Jive Software.
 *
 * All rights reserved. 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.jivesoftware.whack.util;

import java.util.Date;
import java.util.Map;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * Performs tasks using worker threads. It also allows tasks to be scheduled to
 * be run at future dates. This class mimics relevant methods in both
 * {@link ExecutorService} and {@link Timer}. Any {@link TimerTask} that's
 * scheduled to be run in the future will automatically be run using the thread
 * executor's thread pool. This means that the standard restriction that
 * TimerTasks should run quickly does not apply.
 *
 * @author Matt Tucker
 */
public class TaskEngine {

    private static TaskEngine instance = new TaskEngine();

    /**
     * Returns a task engine instance (singleton).
     *
     * @return a task engine.
     */
    public static TaskEngine getInstance() {
        return instance;
    }

    private Timer timer;
    private ExecutorService executor;
    private Map<TimerTask, TimerTaskWrapper> wrappedTasks = new ConcurrentHashMap<TimerTask, TimerTaskWrapper>();

    /**
     * Constructs a new task engine.
     */
    private TaskEngine() {
        timer = new Timer("timer-whack", true);
        executor = Executors.newCachedThreadPool(new ThreadFactory() {

            final AtomicInteger threadNumber = new AtomicInteger(1);

            public Thread newThread(Runnable runnable) {
                // Use our own naming scheme for the threads.
                Thread thread = new Thread(Thread.currentThread()
                        .getThreadGroup(), runnable, "pool-whack"
                        + threadNumber.getAndIncrement(), 0);
                // Make workers daemon threads.
                thread.setDaemon(true);
                if (thread.getPriority() != Thread.NORM_PRIORITY) {
                    thread.setPriority(Thread.NORM_PRIORITY);
                }
                return thread;
            }
        });
    }

    /**
     * Submits a Runnable task for execution and returns a Future representing
     * that task.
     *
     * @param task
     *            the task to submit.
     * @return a Future representing pending completion of the task, and whose
     *         <tt>get()</tt> method will return <tt>null</tt> upon completion.
     * @throws java.util.concurrent.RejectedExecutionException
     *             if task cannot be scheduled for execution.
     * @throws NullPointerException
     *             if task null.
     */
    public Future<?> submit(Runnable task) {
        return executor.submit(task);
    }

    /**
     * Schedules the specified task for execution after the specified delay.
     *
     * @param task
     *            task to be scheduled.
     * @param delay
     *            delay in milliseconds before task is to be executed.
     * @throws IllegalArgumentException
     *             if <tt>delay</tt> is negative, or
     *             <tt>delay + System.currentTimeMillis()</tt> is negative.
     * @throws IllegalStateException
     *             if task was already scheduled or cancelled, or timer was
     *             cancelled.
     */
    public void schedule(TimerTask task, long delay) {
        timer.schedule(new TimerTaskWrapper(task), delay);
    }

    /**
     * Schedules the specified task for execution at the specified time. If the
     * time is in the past, the task is scheduled for immediate execution.
     *
     * @param task
     *            task to be scheduled.
     * @param time
     *            time at which task is to be executed.
     * @throws IllegalArgumentException
     *             if <tt>time.getTime()</tt> is negative.
     * @throws IllegalStateException
     *             if task was already scheduled or cancelled, timer was
     *             cancelled, or timer thread terminated.
     */
    public void schedule(TimerTask task, Date time) {
        timer.schedule(new TimerTaskWrapper(task), time);
    }

    /**
     * Schedules the specified task for repeated fixed-delay execution,
     * beginning after the specified delay. Subsequent executions take place at
     * approximately regular intervals separated by the specified period.
     *
     *

     * In fixed-delay execution, each execution is scheduled relative to the
     * actual execution time of the previous execution. If an execution is
     * delayed for any reason (such as garbage collection or other background
     * activity), subsequent executions will be delayed as well. In the long
     * run, the frequency of execution will generally be slightly lower than the
     * reciprocal of the specified period (assuming the system clock underlying
     * <tt>Object.wait(long)</tt> is accurate).
     *
     *

     * Fixed-delay execution is appropriate for recurring activities that
     * require "smoothness." In other words, it is appropriate for activities
     * where it is more important to keep the frequency accurate in the short
     * run than in the long run. This includes most animation tasks, such as
     * blinking a cursor at regular intervals. It also includes tasks wherein
     * regular activity is performed in response to human input, such as
     * automatically repeating a character as long as a key is held down.
     *
     * @param task
     *            task to be scheduled.
     * @param delay
     *            delay in milliseconds before task is to be executed.
     * @param period
     *            time in milliseconds between successive task executions.
     * @throws IllegalArgumentException
     *             if <tt>delay</tt> is negative, or
     *             <tt>delay + System.currentTimeMillis()</tt> is negative.
     * @throws IllegalStateException
     *             if task was already scheduled or cancelled, timer was
     *             cancelled, or timer thread terminated.
     */
    public void schedule(TimerTask task, long delay, long period) {
        TimerTaskWrapper taskWrapper = new TimerTaskWrapper(task);
        wrappedTasks.put(task, taskWrapper);
        timer.schedule(taskWrapper, delay, period);
    }

    /**
     * Schedules the specified task for repeated fixed-delay execution,
     * beginning at the specified time. Subsequent executions take place at
     * approximately regular intervals, separated by the specified period.
     *
     *

     * In fixed-delay execution, each execution is scheduled relative to the
     * actual execution time of the previous execution. If an execution is
     * delayed for any reason (such as garbage collection or other background
     * activity), subsequent executions will be delayed as well. In the long
     * run, the frequency of execution will generally be slightly lower than the
     * reciprocal of the specified period (assuming the system clock underlying
     * <tt>Object.wait(long)</tt> is accurate).
     *
     *

     * Fixed-delay execution is appropriate for recurring activities that
     * require "smoothness." In other words, it is appropriate for activities
     * where it is more important to keep the frequency accurate in the short
     * run than in the long run. This includes most animation tasks, such as
     * blinking a cursor at regular intervals. It also includes tasks wherein
     * regular activity is performed in response to human input, such as
     * automatically repeating a character as long as a key is held down.
     *
     * @param task
     *            task to be scheduled.
     * @param firstTime
     *            First time at which task is to be executed.
     * @param period
     *            time in milliseconds between successive task executions.
     * @throws IllegalArgumentException
     *             if <tt>time.getTime()</tt> is negative.
     * @throws IllegalStateException
     *             if task was already scheduled or cancelled, timer was
     *             cancelled, or timer thread terminated.
     */
    public void schedule(TimerTask task, Date firstTime, long period) {
        TimerTaskWrapper taskWrapper = new TimerTaskWrapper(task);
        wrappedTasks.put(task, taskWrapper);
        timer.schedule(taskWrapper, firstTime, period);
    }

    /**
     * Schedules the specified task for repeated fixed-rate execution,
     * beginning after the specified delay. Subsequent executions take place at
     * approximately regular intervals, separated by the specified period.
     *
     *

     * In fixed-rate execution, each execution is scheduled relative to the
     * scheduled execution time of the initial execution. If an execution is
     * delayed for any reason (such as garbage collection or other background
     * activity), two or more executions will occur in rapid succession to
     * "catch up." In the long run, the frequency of execution will be exactly
     * the reciprocal of the specified period (assuming the system clock
     * underlying <tt>Object.wait(long)</tt> is accurate).
     *
     *

     * Fixed-rate execution is appropriate for recurring activities that are
     * sensitive to absolute time, such as ringing a chime every hour on
     * the hour, or running scheduled maintenance every day at a particular
     * time. It is also appropriate for recurring activities where the total
     * time to perform a fixed number of executions is important, such as a
     * countdown timer that ticks once every second for ten seconds. Finally,
     * fixed-rate execution is appropriate for scheduling multiple repeating
     * timer tasks that must remain synchronized with respect to one another.
     *
     * @param task
     *            task to be scheduled.
     * @param delay
     *            delay in milliseconds before task is to be executed.
     * @param period
     *            time in milliseconds between successive task executions.
     * @throws IllegalArgumentException
     *             if <tt>delay</tt> is negative, or
     *             <tt>delay + System.currentTimeMillis()</tt> is negative.
     * @throws IllegalStateException
     *             if task was already scheduled or cancelled, timer was
     *             cancelled, or timer thread terminated.
     */
    public void scheduleAtFixedRate(TimerTask task, long delay, long period) {
        TimerTaskWrapper taskWrapper = new TimerTaskWrapper(task);
        wrappedTasks.put(task, taskWrapper);
        timer.scheduleAtFixedRate(taskWrapper, delay, period);
    }

    /**
     * Schedules the specified task for repeated fixed-rate execution,
     * beginning at the specified time. Subsequent executions take place at
     * approximately regular intervals, separated by the specified period.
     *
     *

     * In fixed-rate execution, each execution is scheduled relative to the
     * scheduled execution time of the initial execution. If an execution is
     * delayed for any reason (such as garbage collection or other background
     * activity), two or more executions will occur in rapid succession to
     * "catch up." In the long run, the frequency of execution will be exactly
     * the reciprocal of the specified period (assuming the system clock
     * underlying <tt>Object.wait(long)</tt> is accurate).
     *
     *

     * Fixed-rate execution is appropriate for recurring activities that are
     * sensitive to absolute time, such as ringing a chime every hour on
     * the hour, or running scheduled maintenance every day at a particular
     * time. It is also appropriate for recurring activities where the total
     * time to perform a fixed number of executions is important, such as a
     * countdown timer that ticks once every second for ten seconds. Finally,
     * fixed-rate execution is appropriate for scheduling multiple repeating
     * timer tasks that must remain synchronized with respect to one another.
     *
     * @param task
     *            task to be scheduled.
     * @param firstTime
     *            First time at which task is to be executed.
     * @param period
     *            time in milliseconds between successive task executions.
     * @throws IllegalArgumentException
     *             if <tt>time.getTime()</tt> is negative.
     * @throws IllegalStateException
     *             if task was already scheduled or cancelled, timer was
     *             cancelled, or timer thread terminated.
     */
    public void scheduleAtFixedRate(TimerTask task, Date firstTime, long period) {
        TimerTaskWrapper taskWrapper = new TimerTaskWrapper(task);
        wrappedTasks.put(task, taskWrapper);
        timer.scheduleAtFixedRate(taskWrapper, firstTime, period);
    }

    /**
     * Cancels the execution of a scheduled task.
     * {@link java.util.TimerTask#cancel()}
     *
     * @param task
     *            the scheduled task to cancel.
     */
    public void cancelScheduledTask(TimerTask task) {
        TaskEngine.TimerTaskWrapper taskWrapper = wrappedTasks.remove(task);
        if (taskWrapper != null) {
            taskWrapper.cancel();
        }
    }

    /**
     * Shuts down the task engine service.
     */
    public void shutdown() {
        if (executor != null) {
            executor.shutdownNow();
            executor = null;
        }

        if (timer != null) {
            timer.cancel();
            timer = null;
        }
    }

    /**
     * Wrapper class for a standard TimerTask. It simply executes the TimerTask
     * using the executor's thread pool.
     */
    private class TimerTaskWrapper extends TimerTask {

        private TimerTask task;

        public TimerTaskWrapper(TimerTask task) {
            this.task = task;
        }

        public void run() {
            executor.submit(task);
        }
    }
}


/*
 * Copyright 2010 Workplace Systems PLC (http://www.workplacesystems.com/).
 *
 * 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 com.workplacesystems.utilsj.collections.decorators;

import com.workplacesystems.utilsj.Callback;
import com.workplacesystems.utilsj.collections.Filter;
import com.workplacesystems.utilsj.collections.FilterableCollection;
import com.workplacesystems.utilsj.collections.SyncUtils;

/**
 *
 * @author  Administrator
 */
public class SynchronizedFilterableCollection<E> extends SynchronizedCollection<E> implements FilterableCollection<E> {
    /**
     * Factory method to create a synchronized map.
     *
     * @param map  the map to decorate, must not be null
     * @throws IllegalArgumentException if map is null
     */
    public static <E> FilterableCollection<E> decorate(FilterableCollection<E> collection) {
        return new SynchronizedFilterableCollection<E>(collection);
    }

    /**
     * Constructor that wraps (not copies).
     *
     * @param map  the map to decorate, must not be null
     * @throws IllegalArgumentException if map is null
     */
    protected SynchronizedFilterableCollection(FilterableCollection<E> collection) {
        super(collection);
    }

    /**
     * Constructor that wraps (not copies).
     *
     * @param map  the map to decorate, must not be null
     * @param lock  the lock object to use, must not be null
     * @throws IllegalArgumentException if map is null
     */
    protected SynchronizedFilterableCollection(FilterableCollection<E> collection, Object lock) {
        super(collection, lock);
    }

    protected FilterableCollection<E> getFilterableCollection()
    {
        return (FilterableCollection<E>) collection;
    }

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

    public FilterableCollection<E> filteredCollection(final Filter<? super E> filter) {
        return SyncUtils.synchronizeRead(lock, new Callback<FilterableCollection<E>>() {
            @Override
            protected void doAction() {
                FilterableCollection<E> _col = getFilterableCollection().filteredCollection(filter);
                _return(new SynchronizedFilterableCollection<E>(_col, lock));
            }
        });
    }

}


/*  Copyright © 2014 by Eric Kolotyluk <eric@kolotyluk.net>

	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 net.kolotyluk.java.files;

import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.nio.channels.FileChannel;
import java.nio.channels.FileChannel.MapMode;
import java.nio.file.Path;
import java.nio.ByteBuffer;
import java.nio.MappedByteBuffer;
import java.security.AccessController;
import java.security.PrivilegedAction;

/**
 * Some Useful File System Utilities and Extras
 * @author Eric Kolotyluk
 *
 */
public class Files {

  /**
   * Identical File Contents
   *
   *
Uses
   * Memory Mapped Files
   * for fast comparison of large files. This is about 4 times faster than calling

   * org.apache.commons.io.FileUtils.contentEquals(File file1,File file2)

   *
with identical 4
   * GiB
   * files. Performance may suffer slightly for smaller files.
   * A future version may be optimized for smaller files too.

   *
   *
Useful for checking if media files, such as music and video,
   * have identical or duplicate content. Video files can easily
   * be over 4 GiB in size. Technically this method will compare
   * files as large as the file system supports.

   *
   *
Performance is about 16 seconds for two 4 GiB files with
   * identical content on an Intel Xeon 5580 at 3.2 GHz using a
   * file system on a LSI MegaRAID SAS 9266-8i.

   *
   * @param file1 1st file to be compared
   * @param file2 2nd file to be compared
   * @return true when both files have identical contents
   * @throws IllegalArgumentException if the arguments are not files.
   * @throws IOException if there is a problem with File I/O
   * @throws RuntimeException if there was a problem unmapping the
   * buffers or closing the channels, which may leave one or more
   * files locked for read.
   */
  public static Boolean contentEquals(Path file1, Path file2) throws IOException
  {
      if (!java.nio.file.Files.isRegularFile(file1))
	      throw new IllegalArgumentException(file1 + "is not a regular file");

	  if (!java.nio.file.Files.isRegularFile(file2))
		  throw new IllegalArgumentException(file2 + "is not a regular file");

	  FileChannel channel1 = null;
	  FileChannel channel2 = null;

	  MappedByteBuffer buffer1 = null;
	  MappedByteBuffer buffer2 = null;

	  try
	  {
		  long size1 = java.nio.file.Files.size(file1);
		  long size2 = java.nio.file.Files.size(file2);

		  if (size1 != size2) return false;

		  long position = 0;
		  long length = Math.min(Integer.MAX_VALUE, size1 - position);

		  channel1 = FileChannel.open(file1);
		  channel2 = FileChannel.open(file2);

		  // Cannot map files larger than Integer.MAX_VALUE,
		  // so we have to do it in pieces.

		  while (length > 0)
		  {
			  buffer1 = channel1.map(MapMode.READ_ONLY, position, length);
			  buffer2 = channel2.map(MapMode.READ_ONLY, position, length);
			  // if (!buffer1.equals(buffer2)) return false;
			  // The line above is much slower than the line below.
			  // It should not be, but it is, possibly because it is
			  // loading the entire buffer into memory before comparing
			  // the contents. See the corresponding unit test. EK
			  for (int i = 0; i < length; i++) if (buffer1.get() != buffer2.get()) return false;
			  position += length;
			  length = Math.min(Integer.MAX_VALUE, size1 - position);
			  cleanDirectByteBuffer(buffer1); buffer1 = null;
			  cleanDirectByteBuffer(buffer2); buffer2 = null;
		  }
	  }
	  finally
	  {
		  // Is is important to clean up so we do not hold any
		  // file locks, in case the caller wants to do something
		  // else with the files.

		  // In terms of functional programming, holding a lock after
		  // returning to the caller would be an unwelcome side-effect.

		  cleanDirectByteBuffer(buffer1);
		  cleanDirectByteBuffer(buffer2);

		  if (channel1 != null) try
		  {
			  channel1.close();
		  }
		  catch (IOException e)
		  {
			  if (channel2 != null) channel2.close();
			  throw e;
		  }

		  if (channel2 != null) channel2.close();
	  }

	  return true;
  }

 /**
  * Clean or unmap a direct ByteBuffer
  *
  *
DirectByteBuffers are garbage collected by using a phantom reference and a
  * reference queue. Every once a while, the JVM checks the reference queue and
  * cleans the DirectByteBuffers. However, as this doesn't happen immediately
  * after discarding all references to a DirectByteBuffer, it's easy get
  * {@link OutOfMemoryError} problems using direct ByteBuffers.

  *
  *
Also, if a file is still mapped, via {@link MappedByteBuffer}, then it is
  * locked and cannot be destroyed or possibly written to if it was previously
  * mapped read. Trying to destroy or write to a locked file will result in an
  * {@link IOException}

  *
  *
This function explicitly calls the cleaner method of a {@link ByteBuffer} using
  * reflection because it is not publicly accessible.

  *
  * @param byteBuffer The DirectByteBuffer that will be "cleaned". Returns immediately if
  * the argument is null.
  * @throws IllegalArgumentException if byteBuffer isn't direct
  * @throws RuntimeException if cleaning may have failed
  */
  public static void cleanDirectByteBuffer(final ByteBuffer byteBuffer)
  {

	if (byteBuffer == null) return;

    if (!byteBuffer.isDirect())
    	throw new IllegalArgumentException("byteBuffer isn't direct!");

    AccessController.doPrivileged(new PrivilegedAction<Void>() {
        public Void run() {
        	try
        	{
        		Method cleanerMethod = byteBuffer.getClass().getMethod("cleaner");
        	    cleanerMethod.setAccessible(true);
        	    Object cleaner = cleanerMethod.invoke(byteBuffer);
        	    Method cleanMethod = cleaner.getClass().getMethod("clean");
        	    cleanMethod.setAccessible(true);
        	    cleanMethod.invoke(cleaner);
        	}
        	catch (NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e)
        	{
        		throw new RuntimeException("Could not clean MappedByteBuffer -- File may still be locked!");
        	}
           return null; // nothing to return
        }
    });

  }

}


/*
 * Copyright 2010 Workplace Systems PLC (http://www.workplacesystems.com/).
 *
 * 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 com.workplacesystems.utilsj.collections;

import java.util.AbstractCollection;
import java.util.AbstractMap;
import java.util.AbstractSet;
import java.util.Collection;
import java.util.ConcurrentModificationException;
import java.util.Iterator;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;

import com.workplacesystems.utilsj.ThreadSession;

/**
 * Transactional version of HashMap
 *
 * @author andy bell
 */

public class TransactionalHashMap<K,V> extends AbstractMap<K,V> implements TransactionalMap<K,V>
{
    /**
     * Object to get hash from.
     */
    private final Object hash_object = new Object();

    /**
     * The default initial capacity - MUST be a power of two.
     */
    static final int DEFAULT_INITIAL_CAPACITY = 16;

    /**
     * The maximum capacity, used if a higher value is implicitly specified
     * by either of the constructors with arguments.
     * MUST be a power of two <= 1<<30.
     */
    static final int MAXIMUM_CAPACITY = 1 << 30;

    /**
     * The load factor used when none specified in constructor.
     **/
    static final float DEFAULT_LOAD_FACTOR = 0.75f;

    /**
     * The table, resized as necessary. Length MUST Always be a power of two.
     */
    transient Entry<K,V>[] table;

    /**
     * The number of key-value mappings contained in this identity hash map.
     */
    transient int size;

    /**
     * The next size value at which to resize (capacity * load factor).
     * @serial
     */
    int threshold;

    /**
     * The load factor for the hash table.
     *
     * @serial
     */
    final float loadFactor;

    /**
     * The number of times this HashMap has been structurally modified
     * Structural modifications are those that change the number of mappings in
     * the HashMap or otherwise modify its internal structure (e.g.,
     * rehash).  This field is used to make iterators on Collection-views of
     * the HashMap fail-fast.  (See ConcurrentModificationException).
     */
    transient volatile int modCount;

    /**
     * The transactional mode the map is running in
     */
    private boolean auto_commit = true;

    /**
     * Constructs an empty <tt>HashMap</tt> with the specified initial
     * capacity and load factor.
     *
     * @param  initialCapacity The initial capacity.
     * @param  loadFactor      The load factor.
     * @throws IllegalArgumentException if the initial capacity is negative
     *         or the load factor is nonpositive.
     */
    @SuppressWarnings("unchecked")
    public TransactionalHashMap(int initialCapacity, float loadFactor)
    {
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal initial capacity: " +
                    initialCapacity);
        if (initialCapacity > MAXIMUM_CAPACITY)
            initialCapacity = MAXIMUM_CAPACITY;
        if (loadFactor <= 0 || Float.isNaN(loadFactor))
            throw new IllegalArgumentException("Illegal load factor: " +
                    loadFactor);

        // Find a power of 2 >= initialCapacity
        int capacity = 1;
        while (capacity < initialCapacity)
            capacity <<= 1;

        this.loadFactor = loadFactor;
        threshold = (int)(capacity * loadFactor);
        table = new Entry[capacity];
        init();
    }

    /**
     * Constructs an empty <tt>HashMap</tt> with the specified initial
     * capacity and the default load factor (0.75).
     *
     * @param  initialCapacity the initial capacity.
     * @throws IllegalArgumentException if the initial capacity is negative.
     */
    public TransactionalHashMap(int initialCapacity)
    {
        this(initialCapacity, DEFAULT_LOAD_FACTOR);
    }

    /**
     * Constructs an empty <tt>HashMap</tt> with the default initial capacity
     * (16) and the default load factor (0.75).
     */
    @SuppressWarnings("unchecked")
    public TransactionalHashMap()
    {
        this.loadFactor = DEFAULT_LOAD_FACTOR;
        threshold = (int)(DEFAULT_INITIAL_CAPACITY * DEFAULT_LOAD_FACTOR);
        table = new Entry[DEFAULT_INITIAL_CAPACITY];
        init();
    }

    /**
     * Constructs a new <tt>HashMap</tt> with the same mappings as the
     * specified <tt>Map</tt>.  The <tt>HashMap</tt> is created with
     * default load factor (0.75) and an initial capacity sufficient to
     * hold the mappings in the specified <tt>Map</tt>.
     *
     * @param   m the map whose mappings are to be placed in this map.
     * @throws  NullPointerException if the specified map is null.
     */
    public TransactionalHashMap(Map<? extends K,? extends V> m)
    {
        this(Math.max((int) (m.size() / DEFAULT_LOAD_FACTOR) + 1,
                DEFAULT_INITIAL_CAPACITY), DEFAULT_LOAD_FACTOR);
        putAllForCreate(m);
    }

    // internal utilities

    /**
     * Initialization hook for subclasses. This method is called
     * in all constructors and pseudo-constructors (clone, readObject)
     * after HashMap has been initialized but before any entries have
     * been inserted.  (In the absence of this method, readObject would
     * require explicit knowledge of subclasses.)
     */
    void init()
    {
    }

    private final String getThreadSessionKey()
    {
        return ".TransactionalBidiTreeMap(" + hash_object.hashCode() + ")-attach_id";
    }

    /**
     * Attach the current thread to a specific id rather than using the
     * current thread for transactional changes. This allows transactional
     * changes to be done by different threads.
     */
    public void attach(final String attach_id)
    {
        ThreadSession.putValue(getThreadSessionKey(), attach_id);
    }

    /**
     * Detach the current thread from the attach_id.
     */
    public void detach()
    {
        ThreadSession.removeValue(getThreadSessionKey());
    }

    /**
     * Sets this map's  auto commit state to the given state.
     * If a map is in auto commit mode then all changes are
     * available to all threads immediately, otherwise commit
     * must be called.
     *

     * The default for the map is auto commit is on.
     *

     * NOTE:  If this method is called while there are outstanding
     * transactions, commit is called.
     *
     * @param auto_commit - false to enable transaction support
     *                      within this map.
     */
    public void setAutoCommit(final boolean auto_commit)
    {

        if (!this.auto_commit && auto_commit)
            commit(null);

        this.auto_commit = auto_commit;
    }

    public boolean isAutoCommit()
    {
        return auto_commit;
    }

    /**
     * Commits the changes to the map so that all threads
     * see them.
     */
    public void commit()
    {

        if (auto_commit)
            return;

        commit(getCurrentThreadId());
    }

    /**
     * Rolls back the changes to the map.
     */
    public void rollback()
    {

        if (auto_commit)
            return;

        String id = getCurrentThreadId();

        Entry<K,V> tab[] = table;
        for (int i = 0; i < tab.length ; i++)
        {
            Entry<K,V> prev = table[i];
            Entry<K,V> e = prev;

            while (e != null)
            {
                Entry<K,V> next = e.next;
                if (e.is(Entry.ADDED, id))
                {
                    modCount++;
                    size--;
                    if (prev == e)
                        table[i] = next;
                    else
                        prev.next = next;
                }
                else if (e.is(Entry.DELETED, id))
                    e.setStatus(Entry.NO_CHANGE, null);

                prev = e;
                e = next;
            }
        }
    }

    /**
     * Commits the changes to the map so that all threads
     * see them.
     *
     * @param id the thread id to commit for, null commits
     *           all threads changes
     */
    private void commit(final String id)
    {

        Entry<K,V> tab[] = table;
        for (int i = 0; i < tab.length ; i++)
        {
            Entry<K,V> prev = table[i];
            Entry<K,V> e = prev;

            while (e != null)
            {
                Entry<K,V> next = e.next;
                if (e.is(Entry.DELETED, id))
                {
                    modCount++;
                    size--;
                    if (prev == e)
                        table[i] = next;
                    else
                        prev.next = next;
                }
                else if (e.is(Entry.ADDED, id))
                    e.setStatus(Entry.NO_CHANGE, null);

                prev = e;
                e = next;
            }
        }
    }

    /**
     * Retrieve the current thread id for use by the
     * transaction code.
     *
     * @return the thread id of the current thread
     */
    private String getCurrentThreadId()
    {
        String attach_id = (String)ThreadSession.getValue(getThreadSessionKey());
        if (attach_id != null)
            return attach_id;

        Thread thread = Thread.currentThread();
        return thread.toString() + "(" + thread.hashCode() + ")";
    }

    /**
     * Checks that this entry is valid for the current thread
     *
     * @param entry the entry to be checked
     *
     * @return true if entry is valid, otherwise false
     */
    private boolean validEntry(final Entry<K,V> entry)
    {
        if (auto_commit || entry == null)
            return (entry != null);

        String id = getCurrentThreadId();
        return !((entry.is(Entry.DELETED, id)) ||
                (entry.is(Entry.ADDED, null) && entry.is(Entry.NO_CHANGE, id)));
    }

    /**
     * Value representing null keys inside tables.
     */
    static final Object NULL_KEY = new Object();

    /**
     * Returns internal representation for key. Use NULL_KEY if key is null.
     */
    @SuppressWarnings("unchecked")
    static <T> T maskNull(T key)
    {
        return (key == null ? (T)NULL_KEY : key);
    }

    /**
     * Returns key represented by specified internal representation.
     */
    static <T> T  unmaskNull(T key)
    {
        return (key == NULL_KEY ? null : key);
    }

    /**
     * Returns a hash value for the specified object.  In addition to
     * the object's own hashCode, this method applies a "supplemental
     * hash function," which defends against poor quality hash functions.
     * This is critical because HashMap uses power-of two length
     * hash tables.

     *
     * The shift distances in this function were chosen as the result
     * of an automated search over the entire four-dimensional search space.
     */
    static int hash(Object x)
    {
        int h = x.hashCode();

        h += ~(h << 9);
        h ^=  (h >>> 14);
        h +=  (h << 4);
        h ^=  (h >>> 10);
        return h;
    }

    /**
     * Check for equality of non-null reference x and possibly-null y.
     */
    static boolean eq(Object x, Object y)
    {
        return x == y || x.equals(y);
    }

    /**
     * Returns index for hash code h.
     */
    static int indexFor(int h, int length)
    {
        return h & (length-1);
    }

    /**
     * Returns the number of key-value mappings in this map.
     *
     * @return the number of key-value mappings in this map.
     */
    @Override
    public int size()
    {
        return size(false);
    }

    public int size(boolean countAll)
    {
        if (countAll || auto_commit)
            return size;

        int size = 0;
        for (Iterator<Map.Entry<K,V>> i = entrySet().iterator(); i.hasNext(); i.next())
        {
            size++;
        }
        return size;
    }

    /**
     * Returns the value to which the specified key is mapped in this identity
     * hash map, or <tt>null</tt> if the map contains no mapping for this key.
     * A return value of <tt>null</tt> does not necessarily indicate
     * that the map contains no mapping for the key; it is also possible that
     * the map explicitly maps the key to <tt>null</tt>. The
     * <tt>containsKey</tt> method may be used to distinguish these two cases.
     *
     * @param   key the key whose associated value is to be returned.
     * @return  the value to which this map maps the specified key, or
     *          <tt>null</tt> if the map contains no mapping for this key.
     * @see #put(Object, Object)
     */
    @Override
    public V get(Object key)
    {
        Object k = maskNull(key);
        int hash = hash(k);
        int i = indexFor(hash, table.length);
        Entry<K,V> e = table[i];
        while (true)
        {
            if (e == null)
                return null;
            if (e.hash == hash && validEntry(e) && eq(k, e.key))
                return e.value;
            e = e.next;
        }
    }

    /**
     * Returns <tt>true</tt> if this map contains a mapping for the
     * specified key.
     *
     * @param   key   The key whose presence in this map is to be tested
     * @return <tt>true</tt> if this map contains a mapping for the specified
     * key.
     */
    @Override
    public boolean containsKey(Object key)
    {
        Object k = maskNull(key);
        int hash = hash(k);
        int i = indexFor(hash, table.length);
        Entry<K,V> e = table[i];
        while (e != null)
        {
            if (e.hash == hash && validEntry(e) && eq(k, e.key))
                return true;
            e = e.next;
        }
        return false;
    }

    /**
     * Returns the entry associated with the specified key in the
     * HashMap.  Returns null if the HashMap contains no mapping
     * for this key.
     */
    Entry<K,V> getEntry(Object key)
    {
        Object k = maskNull(key);
        int hash = hash(k);
        int i = indexFor(hash, table.length);
        Entry<K,V> e = table[i];
        while (e != null && !(e.hash == hash && validEntry(e) && eq(k, e.key)))
            e = e.next;
        return e;
    }

    /**
     * Associates the specified value with the specified key in this map.
     * If the map previously contained a mapping for this key, the old
     * value is replaced.
     *
     * @param key key with which the specified value is to be associated.
     * @param value value to be associated with the specified key.
     * @return previous value associated with specified key, or <tt>null</tt>
     *	       if there was no mapping for key.  A <tt>null</tt> return can
     *	       also indicate that the HashMap previously associated
     *	       <tt>null</tt> with the specified key.
     */
    @Override
    public V put(K key, V value) throws ConcurrentModificationException
    {
        K k = maskNull(key);
        int hash = hash(k);
        int i = indexFor(hash, table.length);

        V oldValue = null;
        for (Entry<K,V> e = table[i]; e != null; e = e.next)
        {
            if (e.hash == hash && eq(k, e.key))
            {
                //check if someone else has a pending add for the same key
                if (e.is(Entry.ADDED, null) && !e.is(Entry.ADDED, getCurrentThreadId()))
                    throw new ConcurrentModificationException();

                if (validEntry(e))
                {
                    oldValue = e.value;
                    //if not transactional can reuse entries
                    if (auto_commit)
                    {
                        e.value = value;
                        return oldValue;
                    }
                    else
                        e.setStatus(Entry.DELETED, getCurrentThreadId());
                }
            }
        }

        modCount++;
        addEntry(hash, k, value, i);
        return oldValue;
    }

    /**
     * This method is used instead of put by constructors and
     * pseudoconstructors (clone, readObject).  It does not resize the table,
     * check for comodification, etc.  It calls createEntry rather than
     * addEntry.
     */
    private void putForCreate(K key, V value)
    {
        K k = maskNull(key);
        int hash = hash(k);
        int i = indexFor(hash, table.length);

        /**
         * Look for preexisting entry for key.  This will never happen for
         * clone or deserialize.  It will only happen for construction if the
         * input Map is a sorted map whose ordering is inconsistent w/ equals.
         */
        for (Entry<K,V> e = table[i]; e != null; e = e.next)
        {
            if (e.hash == hash && eq(k, e.key))
            {
                e.value = value;
                return;
            }
        }

        createEntry(hash, k, value, i);
    }

    void putAllForCreate(Map<? extends K, ? extends V> m)
    {
        for (Iterator<? extends Map.Entry<? extends K, ? extends V>> i = m.entrySet().iterator(); i.hasNext(); )
        {
            Map.Entry<? extends K, ? extends V> e = i.next();
            putForCreate(e.getKey(), e.getValue());
        }
    }

    /**
     * Rehashes the contents of this map into a new array with a
     * larger capacity.  This method is called automatically when the
     * number of keys in this map reaches its threshold.
     *
     * If current capacity is MAXIMUM_CAPACITY, this method does not
     * resize the map, but but sets threshold to Integer.MAX_VALUE.
     * This has the effect of preventing future calls.
     *
     * @param newCapacity the new capacity, MUST be a power of two;
     *        must be greater than current capacity unless current
     *        capacity is MAXIMUM_CAPACITY (in which case value
     *        is irrelevant).
     */
    @SuppressWarnings("unchecked")
    void resize(int newCapacity)
    {
        Entry<K,V>[] oldTable = table;
        int oldCapacity = oldTable.length;
        if (oldCapacity == MAXIMUM_CAPACITY)
        {
            threshold = Integer.MAX_VALUE;
            return;
        }

        Entry<K,V>[] newTable = new Entry[newCapacity];
        transfer(newTable);
        table = newTable;
        threshold = (int)(newCapacity * loadFactor);
    }

    /**
     * Transfer all entries from current table to newTable.
     */
    void transfer(Entry<K,V>[] newTable)
    {
        Entry<K,V>[] src = table;
        int newCapacity = newTable.length;
        for (int j = 0; j < src.length; j++)
        {
            Entry<K,V> e = src[j];
            if (e != null)
            {
                src[j] = null;
                do
                {
                    Entry<K,V> next = e.next;
                    int i = indexFor(e.hash, newCapacity);
                    e.next = newTable[i];
                    newTable[i] = e;
                    e = next;
                } while (e != null);
            }
        }
    }

    /**
     * Copies all of the mappings from the specified map to this map
     * These mappings will replace any mappings that
     * this map had for any of the keys currently in the specified map.
     *
     * @param m mappings to be stored in this map.
     * @throws NullPointerException if the specified map is null.
     */
    @Override
    public void putAll(Map<? extends K, ? extends V> m)
    {
        int numKeysToBeAdded = m.size();
        if (numKeysToBeAdded == 0)
            return;

        /*
         * Expand the map if the map if the number of mappings to be added
         * is greater than or equal to threshold.  This is conservative; the
         * obvious condition is (m.size() + size) >= threshold, but this
         * condition could result in a map with twice the appropriate capacity,
         * if the keys to be added overlap with the keys already in this map.
         * By using the conservative calculation, we subject ourself
         * to at most one extra resize.
         */
        if (numKeysToBeAdded > threshold)
        {
            int targetCapacity = (int)(numKeysToBeAdded / loadFactor + 1);
            if (targetCapacity > MAXIMUM_CAPACITY)
                targetCapacity = MAXIMUM_CAPACITY;
            int newCapacity = table.length;
            while (newCapacity < targetCapacity)
                newCapacity <<= 1;
            if (newCapacity > table.length)
                resize(newCapacity);
        }

        for (Iterator<? extends Map.Entry<? extends K, ? extends V>> i = m.entrySet().iterator(); i.hasNext(); )
        {
            Map.Entry<? extends K, ? extends V> e = i.next();
            put(e.getKey(), e.getValue());
        }
    }

    /**
     * Removes the mapping for this key from this map if present.
     *
     * @param  key key whose mapping is to be removed from the map.
     * @return previous value associated with specified key, or <tt>null</tt>
     *	       if there was no mapping for key.  A <tt>null</tt> return can
     *	       also indicate that the map previously associated <tt>null</tt>
     *	       with the specified key.
     */
    @Override
    public V remove(Object key) throws ConcurrentModificationException
    {
        Entry<K,V> e = removeEntryForKey(key);
        return (e == null ? null : e.value);
    }

    /**
     * Removes and returns the entry associated with the specified key
     * in the HashMap.  Returns null if the HashMap contains no mapping
     * for this key.
     */
    Entry<K,V> removeEntryForKey(Object key) throws ConcurrentModificationException
    {
        Object k = maskNull(key);
        int hash = hash(k);
        int i = indexFor(hash, table.length);
        Entry<K,V> prev = table[i];
        Entry<K,V> e = prev;

        while (e != null)
        {
            Entry<K,V> next = e.next;
            if (e.hash == hash && validEntry(e) && eq(k, e.key))
            {
                if (e.is(Entry.DELETED, null) && !e.is(Entry.DELETED, getCurrentThreadId()))
                    throw new ConcurrentModificationException();

                if (auto_commit)
                {
                    modCount++;
                    size--;
                    if (prev == e)
                        table[i] = next;
                    else
                        prev.next = next;
                    return e;
                }
                else
                    e.setStatus(Entry.DELETED, getCurrentThreadId());
            }
            prev = e;
            e = next;
        }

        return e;
    }

    /**
     * Special version of remove for EntrySet.
     */
    @SuppressWarnings("unchecked")
    Entry<K,V> removeMapping(Object o)
    {
        if (!(o instanceof Map.Entry))
            return null;

        Map.Entry<K,V> entry = (Map.Entry<K,V>)o;
        Object k = maskNull(entry.getKey());
        int hash = hash(k);
        int i = indexFor(hash, table.length);
        Entry<K,V> prev = table[i];
        Entry<K,V> e = prev;

        while (e != null)
        {
            Entry<K,V> next = e.next;
            if (e.hash == hash && validEntry(e) && e.equals(entry))
            {
                if (auto_commit)
                {
                    modCount++;
                    size--;
                    if (prev == e)
                        table[i] = next;
                    else
                        prev.next = next;
                }
                else
                    e.setStatus(Entry.DELETED, getCurrentThreadId());
                return e;
            }
            prev = e;
            e = next;
        }

        return e;
    }

    /**
     * Removes all mappings from this map.
     */
    @Override
    public void clear()
    {
        modCount++;
        Entry<K,V> tab[] = table;
        for (int i = 0; i < tab.length; i++)
            tab[i] = null;
        size = 0;
    }

    /**
     * Returns <tt>true</tt> if this map maps one or more keys to the
     * specified value.
     *
     * @param value value whose presence in this map is to be tested.
     * @return <tt>true</tt> if this map maps one or more keys to the
     *         specified value.
     */
    @Override
    public boolean containsValue(Object value)
    {
        if (value == null)
            return containsNullValue();

        Entry<K,V> tab[] = table;
        for (int i = 0; i < tab.length ; i++)
            for (Entry<K,V> e = tab[i] ; e != null ; e = e.next)
                if (validEntry(e) && value.equals(e.value))
                    return true;
        return false;
    }

    /**
     * Special-case code for containsValue with null argument
     **/
    private boolean containsNullValue()
    {
        Entry<K,V> tab[] = table;
        for (int i = 0; i < tab.length ; i++)
            for (Entry<K,V> e = tab[i] ; e != null ; e = e.next)
                if (validEntry(e) && e.value == null)
                    return true;
        return false;
    }

    private static final class Entry<K,V> implements Map.Entry<K,V>
    {
        final K key;
        V value;
        final int hash;
        Entry<K,V> next;
        private int          transationStatus;
        private String       transactionId;

        private static final int NO_CHANGE = 0;
        private static final int DELETED = 1;
        private static final int ADDED = 2;

        /**
         * Create new entry.
         */
        Entry(int h, K k, V v, Entry<K,V> n)
        {
            value = v;
            next = n;
            key = k;
            hash = h;
            transationStatus   = NO_CHANGE;
            transactionId      = null;
        }

        private void setStatus(final int status, final String id)
        {
            transationStatus = status;
            transactionId = id;
        }

        private boolean is(final int status, final String id)
        {
            if (transactionId == null)
                return status == NO_CHANGE;

            if (id == null || transactionId.equals(id))
                return transationStatus == status;

            return status == NO_CHANGE;
        }

        public K getKey()
        {
            return TransactionalHashMap.<K>unmaskNull(key);
        }

        public V getValue()
        {
            return value;
        }

        public V setValue(Object newValue)
        throws UnsupportedOperationException
        {
            throw new UnsupportedOperationException(
                    "Map.Entry.setValue is not supported");
        }

        @SuppressWarnings("unchecked")
        @Override
        public boolean equals(Object o)
        {
            if (!(o instanceof Map.Entry))
                return false;
            Map.Entry e = (Map.Entry)o;
            Object k1 = getKey();
            Object k2 = e.getKey();
            if (k1 == k2 || (k1 != null && k1.equals(k2)))
            {
                Object v1 = getValue();
                Object v2 = e.getValue();
                if (v1 == v2 || (v1 != null && v1.equals(v2)))
                    return true;
            }
            return false;
        }

        @Override
        public int hashCode()
        {
            return (key==NULL_KEY ? 0 : key.hashCode()) ^
                    (value==null   ? 0 : value.hashCode());
        }

        @Override
        public String toString()
        {
            return getKey() + "=" + getValue();
        }

    }

    /**
     * Add a new entry with the specified key, value and hash code to
     * the specified bucket.  It is the responsibility of this
     * method to resize the table if appropriate.
     *
     * Subclass overrides this to alter the behavior of put method.
     */
    void addEntry(int hash, K key, V value, int bucketIndex)
    {
        table[bucketIndex] = new Entry<K,V>(hash, key, value, table[bucketIndex]);
        if (!auto_commit)
            table[bucketIndex].setStatus(Entry.ADDED, getCurrentThreadId());
        if (size++ >= threshold)
            resize(2 * table.length);
    }

    /**
     * Like addEntry except that this version is used when creating entries
     * as part of Map construction or "pseudo-construction" (cloning,
     * deserialization).  This version needn't worry about resizing the table.
     *
     * Subclass overrides this to alter the behavior of HashMap(Map),
     * clone, and readObject.
     */
    void createEntry(int hash, K key, V value, int bucketIndex)
    {
        table[bucketIndex] = new Entry<K,V>(hash, key, value, table[bucketIndex]);
        size++;
    }

    private abstract class HashIterator<E> implements Iterator<E>
    {
        Entry<K,V> next;                  // next entry to return
        int expectedModCount;        // For fast-fail
        int index;                   // current slot
        Entry<K,V> current;               // current entry

        HashIterator()
        {
            expectedModCount = modCount;
            Entry<K,V>[] t = table;
            int i = t.length;
            Entry<K,V> n = null;
            if (size != 0)
            { // advance to first entry
                while (i > 0 && n == null)
                {
                    Entry<K,V> test = t[--i];
                    while (n==null && test!=null)
                    {
                        if (validEntry(test))
                            n = test;
                        else
                            test = test.next;
                    }
                }
            }
            next = n;
            index = i;
        }

        public boolean hasNext()
        {
            return next != null;
        }

        Entry<K,V> nextEntry()
        {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
            Entry<K,V> e = next;
            if (e == null)
                throw new NoSuchElementException();

            Entry<K,V> n = e.next;
            while (n!=null && !validEntry(n))
                n = n.next;
            Entry<K,V>[] t = table;
            int i = index;
            while (i > 0 && n == null)
            {
                Entry<K,V> test = t[--i];
                while (n==null && test!=null)
                {
                    if (validEntry(test))
                        n = test;
                    else
                        test = test.next;
                }
            }
            index = i;
            next = n;
            return current = e;
        }

        public void remove()
        {
            if (current == null)
                throw new IllegalStateException();
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
            Object k = current.key;
            current = null;
            TransactionalHashMap.this.removeEntryForKey(k);
            if (auto_commit)
                expectedModCount = modCount;
        }

    }

    private class ValueIterator extends HashIterator<V>
    {
        public V next()
        {
            return nextEntry().value;
        }
    }

    private class KeyIterator extends HashIterator<K>
    {
        public K next()
        {
            return nextEntry().getKey();
        }
    }

    private class EntryIterator extends HashIterator<Map.Entry<K, V>>
    {
        public Map.Entry<K, V> next()
        {
            return nextEntry();
        }
    }

    // Subclass overrides these to alter behavior of views' iterator() method
    Iterator<K> newKeyIterator()
    {
        return new KeyIterator();
    }
    Iterator<V> newValueIterator()
    {
        return new ValueIterator();
    }
    Iterator<Map.Entry<K, V>> newEntryIterator()
    {
        return new EntryIterator();
    }

    // Views

    private transient Set<Map.Entry<K,V>> entrySet = null;
    transient volatile Set<K> keySet = null;
    transient volatile Collection<V> values = null;

    /**
     * Returns a set view of the keys contained in this map.  The set is
     * backed by the map, so changes to the map are reflected in the set, and
     * vice-versa.  The set supports element removal, which removes the
     * corresponding mapping from this map, via the <tt>Iterator.remove</tt>,
     * <tt>Set.remove</tt>, <tt>removeAll</tt>, <tt>retainAll</tt>, and
     * <tt>clear</tt> operations.  It does not support the <tt>add</tt> or
     * <tt>addAll</tt> operations.
     *
     * @return a set view of the keys contained in this map.
     */
    @Override
    public Set<K> keySet()
    {
        Set<K> ks = keySet;
        return (ks != null ? ks : (keySet = new KeySet()));
    }

    private class KeySet extends AbstractSet<K>
    {
        @Override
        public Iterator<K> iterator()
        {
            return newKeyIterator();
        }
        @Override
        public int size()
        {
            return TransactionalHashMap.this.size();
        }
        @Override
        public boolean contains(Object o)
        {
            return containsKey(o);
        }
        @Override
        public boolean remove(Object o)
        {
            return TransactionalHashMap.this.removeEntryForKey(o) != null;
        }
        @Override
        public void clear()
        {
            TransactionalHashMap.this.clear();
        }
    }

    /**
     * Returns a collection view of the values contained in this map.  The
     * collection is backed by the map, so changes to the map are reflected in
     * the collection, and vice-versa.  The collection supports element
     * removal, which removes the corresponding mapping from this map, via the
     * <tt>Iterator.remove</tt>, <tt>Collection.remove</tt>,
     * <tt>removeAll</tt>, <tt>retainAll</tt>, and <tt>clear</tt> operations.
     * It does not support the <tt>add</tt> or <tt>addAll</tt> operations.
     *
     * @return a collection view of the values contained in this map.
     */
    @Override
    public Collection<V> values()
    {
        Collection<V> vs = values;
        return (vs != null ? vs : (values = new Values()));
    }

    private class Values extends AbstractCollection<V>
    {
        @Override
        public Iterator<V> iterator()
        {
            return newValueIterator();
        }
        @Override
        public int size()
        {
            return TransactionalHashMap.this.size();
        }
        @Override
        public boolean contains(Object o)
        {
            return containsValue(o);
        }
        @Override
        public void clear()
        {
            TransactionalHashMap.this.clear();
        }
    }

    /**
     * Returns a collection view of the mappings contained in this map.  Each
     * element in the returned collection is a <tt>Map.Entry</tt>.  The
     * collection is backed by the map, so changes to the map are reflected in
     * the collection, and vice-versa.  The collection supports element
     * removal, which removes the corresponding mapping from the map, via the
     * <tt>Iterator.remove</tt>, <tt>Collection.remove</tt>,
     * <tt>removeAll</tt>, <tt>retainAll</tt>, and <tt>clear</tt> operations.
     * It does not support the <tt>add</tt> or <tt>addAll</tt> operations.
     *
     * @return a collection view of the mappings contained in this map.
     * @see Map.Entry
     */
    @Override
    public Set<Map.Entry<K, V>> entrySet()
    {
        Set<Map.Entry<K, V>> es = entrySet;
        return (es != null ? es : (entrySet = new EntrySet()));
    }

    private class EntrySet extends AbstractSet<Map.Entry<K, V>>
    {
        @Override
        public Iterator<Map.Entry<K, V>> iterator()
        {
            return newEntryIterator();
        }
        @SuppressWarnings("unchecked")
        @Override
        public boolean contains(Object o)
        {
            if (!(o instanceof Map.Entry))
                return false;
            Map.Entry<K,V> e = (Map.Entry<K,V>)o;
            Entry<K,V> candidate = getEntry(e.getKey());
            return candidate != null && candidate.equals(e);
        }
        @Override
        public boolean remove(Object o)
        {
            return removeMapping(o) != null;
        }
        @Override
        public int size()
        {
            return TransactionalHashMap.this.size();
        }
        @Override
        public void clear()
        {
            TransactionalHashMap.this.clear();
        }
    }

}


/**
 * The MIT License (MIT)
 *
 * Copyright (c) 2015 Georgy Vlasov
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
package org.tenidwa.collections.utils;

import com.google.common.collect.ForwardingMap;
import com.google.common.collect.ImmutableMap;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;

/**
 * A map that augments another map with more values. For every key that is
 * not in the base map, a key-value pair will be present in the augmented
 * map, with the value generated by the augmentaiton function.
 * @author Georgy Vlasov (suseika@tendiwa.org)
 * @version $Id$
 * @since 0.7.0
 */
public final class AugmentedMap<K, V> extends ForwardingMap<K, V> {
    /**
     * Map delegate.
     */
    private final transient Map<K, V> delegate;

    /**
     * Ctor.
     * @param base Base map to augment.
     * @param keys Keys of the augmented map.
     * @param augmentation Function used to generate the new values.
     */
    public AugmentedMap(
        final Map<K, V> base,
        final Set<? extends K> keys,
        final Function<K, V> augmentation
    ) {
        this.delegate = AugmentedMap.createDelegate(base, keys, augmentation);
    }

    /**
     * Creates map delegate.
     * @param base Base map to augment.
     * @param keys Keys to the augmented map.
     * @param augmentation Function used to generate the new values.
     * @param <K> Key
     * @param <V> Value
     * @return A copy of the {@code base} map augmented with new values.
     */
    private static <K, V> ImmutableMap<K, V> createDelegate(
        final Map<K, V> base,
        final Set<? extends K> keys,
        final Function<K, V> augmentation
    ) {
        final ImmutableMap.Builder<K, V> builder = ImmutableMap.builder();
        builder.putAll(base);
        keys.stream()
            .filter(key -> !base.containsKey(key))
            .forEach(key -> builder.put(key, augmentation.apply(key)));
        return builder.build();
    }

    @Override
    protected Map<K, V> delegate() {
        return this.delegate;
    }
}


/**
 *
 * Copyright 2003-2007 Jive Software.
 *
 * All rights reserved. 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.jivesoftware.smack.util;

import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.List;
import java.util.Random;
import java.util.TimeZone;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.log4j.Level;
import org.apache.log4j.Logger;

/**
 * A collection of utility methods for String objects.
 */
public class StringUtils {
    private static Logger log = Logger.getLogger(StringUtils.class.getName());

    /**
     * Date format as defined in XEP-0082 - XMPP Date and Time Profiles. The
     * time zone is set to UTC.
     *

     * Date formats are not synchronized. Since multiple threads access the
     * format concurrently, it must be synchronized externally or you can use
     * the convenience methods {@link #parseXEP0082Date(String)} and
     * {@link #formatXEP0082Date(Date)}.
     *
     * @deprecated This public version will be removed in favor of using the
     *             methods defined within this class.
     */
    public static final DateFormat XEP_0082_UTC_FORMAT = new SimpleDateFormat(
            "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");

    /*
     * private version to use internally so we don't have to be concerned with
     * thread safety.
     */
    private static final DateFormat dateFormatter = DateFormatType.XEP_0082_DATE_PROFILE
            .createFormatter();
    private static final Pattern datePattern = Pattern
            .compile("^\\d+-\\d+-\\d+$");

    private static final DateFormat timeFormatter = DateFormatType.XEP_0082_TIME_MILLIS_ZONE_PROFILE
            .createFormatter();
    private static final Pattern timePattern = Pattern
            .compile("^(\\d+:){2}\\d+.\\d+(Z|([+-](\\d+:\\d+)))$");
    private static final DateFormat timeNoZoneFormatter = DateFormatType.XEP_0082_TIME_MILLIS_PROFILE
            .createFormatter();
    private static final Pattern timeNoZonePattern = Pattern
            .compile("^(\\d+:){2}\\d+.\\d+$");

    private static final DateFormat timeNoMillisFormatter = DateFormatType.XEP_0082_TIME_ZONE_PROFILE
            .createFormatter();
    private static final Pattern timeNoMillisPattern = Pattern
            .compile("^(\\d+:){2}\\d+(Z|([+-](\\d+:\\d+)))$");
    private static final DateFormat timeNoMillisNoZoneFormatter = DateFormatType.XEP_0082_TIME_PROFILE
            .createFormatter();
    private static final Pattern timeNoMillisNoZonePattern = Pattern
            .compile("^(\\d+:){2}\\d+$");

    private static final DateFormat dateTimeFormatter = DateFormatType.XEP_0082_DATETIME_MILLIS_PROFILE
            .createFormatter();
    private static final Pattern dateTimePattern = Pattern
            .compile("^\\d+(-\\d+){2}+T(\\d+:){2}\\d+.\\d+(Z|([+-](\\d+:\\d+)))?$");
    private static final DateFormat dateTimeNoMillisFormatter = DateFormatType.XEP_0082_DATETIME_PROFILE
            .createFormatter();
    private static final Pattern dateTimeNoMillisPattern = Pattern
            .compile("^\\d+(-\\d+){2}+T(\\d+:){2}\\d+(Z|([+-](\\d+:\\d+)))?$");

    private static final DateFormat xep0091Formatter = new SimpleDateFormat(
            "yyyyMMdd'T'HH:mm:ss");
    private static final DateFormat xep0091Date6DigitFormatter = new SimpleDateFormat(
            "yyyyMd'T'HH:mm:ss");
    private static final DateFormat xep0091Date7Digit1MonthFormatter = new SimpleDateFormat(
            "yyyyMdd'T'HH:mm:ss");
    private static final DateFormat xep0091Date7Digit2MonthFormatter = new SimpleDateFormat(
            "yyyyMMd'T'HH:mm:ss");
    private static final Pattern xep0091Pattern = Pattern
            .compile("^\\d+T\\d+:\\d+:\\d+$");

    private static final List<PatternCouplings> couplings = new ArrayList<PatternCouplings>();

    static {
        TimeZone utc = TimeZone.getTimeZone("UTC");
        XEP_0082_UTC_FORMAT.setTimeZone(utc);
        dateFormatter.setTimeZone(utc);
        timeFormatter.setTimeZone(utc);
        timeNoZoneFormatter.setTimeZone(utc);
        timeNoMillisFormatter.setTimeZone(utc);
        timeNoMillisNoZoneFormatter.setTimeZone(utc);
        dateTimeFormatter.setTimeZone(utc);
        dateTimeNoMillisFormatter.setTimeZone(utc);

        xep0091Formatter.setTimeZone(utc);
        xep0091Date6DigitFormatter.setTimeZone(utc);
        xep0091Date7Digit1MonthFormatter.setTimeZone(utc);
        xep0091Date7Digit1MonthFormatter.setLenient(false);
        xep0091Date7Digit2MonthFormatter.setTimeZone(utc);
        xep0091Date7Digit2MonthFormatter.setLenient(false);

        couplings.add(new PatternCouplings(datePattern, dateFormatter));
        couplings.add(new PatternCouplings(dateTimePattern, dateTimeFormatter,
                true));
        couplings.add(new PatternCouplings(dateTimeNoMillisPattern,
                dateTimeNoMillisFormatter, true));
        couplings.add(new PatternCouplings(timePattern, timeFormatter, true));
        couplings.add(new PatternCouplings(timeNoZonePattern,
                timeNoZoneFormatter));
        couplings.add(new PatternCouplings(timeNoMillisPattern,
                timeNoMillisFormatter, true));
        couplings.add(new PatternCouplings(timeNoMillisNoZonePattern,
                timeNoMillisNoZoneFormatter));
    }

    private static final char[] QUOTE_ENCODE = """.toCharArray();
    private static final char[] APOS_ENCODE = "'".toCharArray();
    private static final char[] AMP_ENCODE = "&".toCharArray();
    private static final char[] LT_ENCODE = "<".toCharArray();
    private static final char[] GT_ENCODE = ">".toCharArray();

    /**
     * Parses the given date string in the XEP-0082 - XMPP Date and
     * Time Profiles.
     *
     * @param dateString
     *            the date string to parse
     * @return the parsed Date
     * @throws ParseException
     *             if the specified string cannot be parsed
     * @deprecated Use {@link #parseDate(String)} instead.
     *
     */
    public static Date parseXEP0082Date(String dateString)
            throws ParseException {
        return parseDate(dateString);
    }

    /**
     * Parses the given date string in either of the three profiles of XEP-0082 - XMPP Date and
     * Time Profiles or XEP-0091 - Legacy Delayed
     * Delivery format.
     *

     * This method uses internal date formatters and is thus threadsafe.
     *
     * @param dateString
     *            the date string to parse
     * @return the parsed Date
     * @throws ParseException
     *             if the specified string cannot be parsed
     */
    public static Date parseDate(String dateString) throws ParseException {
        Matcher matcher = xep0091Pattern.matcher(dateString);

        /*
         * if date is in XEP-0091 format handle ambiguous dates missing the
         * leading zero in month and day
         */
        if (matcher.matches()) {
            int length = dateString.split("T")[0].length();

            if (length < 8) {
                Date date = handleDateWithMissingLeadingZeros(dateString,
                        length);

                if (date != null)
                    return date;
            } else {
                synchronized (xep0091Formatter) {
                    return xep0091Formatter.parse(dateString);
                }
            }
        } else {
            for (PatternCouplings coupling : couplings) {
                matcher = coupling.pattern.matcher(dateString);

                if (matcher.matches()) {
                    if (coupling.needToConvertTimeZone) {
                        dateString = coupling.convertTime(dateString);
                    }

                    synchronized (coupling.formatter) {
                        return coupling.formatter.parse(dateString);
                    }
                }
            }
        }

        /*
         * We assume it is the XEP-0082 DateTime profile with no milliseconds at
         * this point. If it isn't, is is just not parseable, then we attempt to
         * parse it regardless and let it throw the ParseException.
         */
        synchronized (dateTimeNoMillisFormatter) {
            return dateTimeNoMillisFormatter.parse(dateString);
        }
    }

    /**
     * Parses the given date string in different ways and returns the date that
     * lies in the past and/or is nearest to the current date-time.
     *
     * @param stampString
     *            date in string representation
     * @param dateLength
     * @param noFuture
     * @return the parsed date
     * @throws ParseException
     *             The date string was of an unknown format
     */
    private static Date handleDateWithMissingLeadingZeros(String stampString,
            int dateLength) throws ParseException {
        if (dateLength == 6) {
            synchronized (xep0091Date6DigitFormatter) {
                return xep0091Date6DigitFormatter.parse(stampString);
            }
        }
        Calendar now = Calendar.getInstance();

        Calendar oneDigitMonth = parseXEP91Date(stampString,
                xep0091Date7Digit1MonthFormatter);
        Calendar twoDigitMonth = parseXEP91Date(stampString,
                xep0091Date7Digit2MonthFormatter);

        List<Calendar> dates = filterDatesBefore(now, oneDigitMonth,
                twoDigitMonth);

        if (!dates.isEmpty()) {
            return determineNearestDate(now, dates).getTime();
        }
        return null;
    }

    private static Calendar parseXEP91Date(String stampString,
            DateFormat dateFormat) {
        try {
            synchronized (dateFormat) {
                dateFormat.parse(stampString);
                return dateFormat.getCalendar();
            }
        } catch (ParseException e) {
            return null;
        }
    }

    private static List<Calendar> filterDatesBefore(Calendar now,
            Calendar... dates) {
        List<Calendar> result = new ArrayList<Calendar>();

        for (Calendar calendar : dates) {
            if (calendar != null && calendar.before(now)) {
                result.add(calendar);
            }
        }

        return result;
    }

    private static Calendar determineNearestDate(final Calendar now,
            List<Calendar> dates) {

        Collections.sort(dates, new Comparator<Calendar>() {

            public int compare(Calendar o1, Calendar o2) {
                Long diff1 = new Long(now.getTimeInMillis()
                        - o1.getTimeInMillis());
                Long diff2 = new Long(now.getTimeInMillis()
                        - o2.getTimeInMillis());
                return diff1.compareTo(diff2);
            }

        });

        return dates.get(0);
    }

    /**
     * Formats a Date into a XEP-0082 - XMPP Date and Time Profiles string.
     *
     * @param date
     *            the time value to be formatted into a time string
     * @return the formatted time string in XEP-0082 format
     */
    public static String formatXEP0082Date(Date date) {
        synchronized (dateTimeFormatter) {
            return dateTimeFormatter.format(date);
        }
    }

    public static String formatDate(Date toFormat, DateFormatType type) {
        return null;
    }

    /**
     * Returns the name portion of a XMPP address. For example, for the address
     * "matt@jivesoftware.com/Smack", "matt" would be returned. If no username
     * is present in the address, the empty string will be returned.
     *
     * @param XMPPAddress
     *            the XMPP address.
     * @return the name portion of the XMPP address.
     */
    public static String parseName(String XMPPAddress) {
        if (XMPPAddress == null) {
            return null;
        }
        int atIndex = XMPPAddress.lastIndexOf("@");
        if (atIndex <= 0) {
            return "";
        } else {
            return XMPPAddress.substring(0, atIndex);
        }
    }

    /**
     * Returns the server portion of a XMPP address. For example, for the
     * address "matt@jivesoftware.com/Smack", "jivesoftware.com" would be
     * returned. If no server is present in the address, the empty string will
     * be returned.
     *
     * @param XMPPAddress
     *            the XMPP address.
     * @return the server portion of the XMPP address.
     */
    public static String parseServer(String XMPPAddress) {
        if (XMPPAddress == null) {
            return null;
        }
        int atIndex = XMPPAddress.lastIndexOf("@");
        // If the String ends with '@', return the empty string.
        if (atIndex + 1 > XMPPAddress.length()) {
            return "";
        }
        int slashIndex = XMPPAddress.indexOf("/");
        if (slashIndex > 0 && slashIndex > atIndex) {
            return XMPPAddress.substring(atIndex + 1, slashIndex);
        } else {
            return XMPPAddress.substring(atIndex + 1);
        }
    }

    /**
     * Returns the resource portion of a XMPP address. For example, for the
     * address "matt@jivesoftware.com/Smack", "Smack" would be returned. If no
     * resource is present in the address, the empty string will be returned.
     *
     * @param XMPPAddress
     *            the XMPP address.
     * @return the resource portion of the XMPP address.
     */
    public static String parseResource(String XMPPAddress) {
        if (XMPPAddress == null) {
            return null;
        }
        int slashIndex = XMPPAddress.indexOf("/");
        if (slashIndex + 1 > XMPPAddress.length() || slashIndex < 0) {
            return "";
        } else {
            return XMPPAddress.substring(slashIndex + 1);
        }
    }

    /**
     * Returns the XMPP address with any resource information removed. For
     * example, for the address "matt@jivesoftware.com/Smack",
     * "matt@jivesoftware.com" would be returned.
     *
     * @param XMPPAddress
     *            the XMPP address.
     * @return the bare XMPP address without resource information.
     */
    public static String parseBareAddress(String XMPPAddress) {
        if (XMPPAddress == null) {
            return null;
        }
        int slashIndex = XMPPAddress.indexOf("/");
        if (slashIndex < 0) {
            return XMPPAddress;
        } else if (slashIndex == 0) {
            return "";
        } else {
            return XMPPAddress.substring(0, slashIndex);
        }
    }

    /**
     * Returns true if jid is a full JID (i.e. a JID with resource part).
     *
     * @param jid
     * @return true if full JID, false otherwise
     */
    public static boolean isFullJID(String jid) {
        if (parseName(jid).length() <= 0 || parseServer(jid).length() <= 0
                || parseResource(jid).length() <= 0) {
            return false;
        }
        return true;
    }

    /**
     * Escapes the node portion of a JID according to "JID Escaping" (JEP-0106).
     * Escaping replaces characters prohibited by node-prep with escape
     * sequences, as follows:
     *

     *
     *

     *

     * Unescaped Character
     * Encoded Sequence
     *

     *

     * <space>
     * \20
     *

     *

     * "
     * \22
     *

     *

     * &
     * \26
     *

     *

     * '
     * \27
     *

     *

     * /
     * \2f
     *

     *

     * :
     * \3a
     *

     *

     * <
     * \3c
     *

     *

     * >
     * \3e
     *

     *

     * @
     * \40
     *

     *

     * \
     * \5c
     *

     *

     *

     *
     * This process is useful when the node comes from an external source that
     * doesn't conform to nodeprep. For example, a username in LDAP may be
     * "Joe Smith". Because the <space> character isn't a valid part of a
     * node, the username should be escaped to "Joe\20Smith" before being made
     * into a JID (e.g. "joe\20smith@example.com" after case-folding, etc. has
     * been applied).
     *

     *
     * All node escaping and un-escaping must be performed manually at the
     * appropriate time; the JID class will not escape or un-escape
     * automatically.
     *
     * @param node
     *            the node.
     * @return the escaped version of the node.
     */
    public static String escapeNode(String node) {
        if (node == null) {
            return null;
        }
        StringBuilder buf = new StringBuilder(node.length() + 8);
        for (int i = 0, n = node.length(); i < n; i++) {
            char c = node.charAt(i);
            switch (c) {
            case '"':
                buf.append("\\22");
                break;
            case '&':
                buf.append("\\26");
                break;
            case '\'':
                buf.append("\\27");
                break;
            case '/':
                buf.append("\\2f");
                break;
            case ':':
                buf.append("\\3a");
                break;
            case '<':
                buf.append("\\3c");
                break;
            case '>':
                buf.append("\\3e");
                break;
            case '@':
                buf.append("\\40");
                break;
            case '\\':
                buf.append("\\5c");
                break;
            default: {
                if (Character.isWhitespace(c)) {
                    buf.append("\\20");
                } else {
                    buf.append(c);
                }
            }
            }
        }
        return buf.toString();
    }

    /**
     * Un-escapes the node portion of a JID according to "JID Escaping"
     * (JEP-0106).
     *

     * Escaping replaces characters prohibited by node-prep with escape
     * sequences, as follows:
     *

     *
     *

     *

     * Unescaped Character
     * Encoded Sequence
     *

     *

     * <space>
     * \20
     *

     *

     * "
     * \22
     *

     *

     * &
     * \26
     *

     *

     * '
     * \27
     *

     *

     * /
     * \2f
     *

     *

     * :
     * \3a
     *

     *

     * <
     * \3c
     *

     *

     * >
     * \3e
     *

     *

     * @
     * \40
     *

     *

     * \
     * \5c
     *

     *

     *

     *
     * This process is useful when the node comes from an external source that
     * doesn't conform to nodeprep. For example, a username in LDAP may be
     * "Joe Smith". Because the <space> character isn't a valid part of a
     * node, the username should be escaped to "Joe\20Smith" before being made
     * into a JID (e.g. "joe\20smith@example.com" after case-folding, etc. has
     * been applied).
     *

     *
     * All node escaping and un-escaping must be performed manually at the
     * appropriate time; the JID class will not escape or un-escape
     * automatically.
     *
     * @param node
     *            the escaped version of the node.
     * @return the un-escaped version of the node.
     */
    public static String unescapeNode(String node) {
        if (node == null) {
            return null;
        }
        char[] nodeChars = node.toCharArray();
        StringBuilder buf = new StringBuilder(nodeChars.length);
        for (int i = 0, n = nodeChars.length; i < n; i++) {
            compare: {
                char c = node.charAt(i);
                if (c == '\\' && i + 2 < n) {
                    char c2 = nodeChars[i + 1];
                    char c3 = nodeChars[i + 2];
                    if (c2 == '2') {
                        switch (c3) {
                        case '0':
                            buf.append(' ');
                            i += 2;
                            break compare;
                        case '2':
                            buf.append('"');
                            i += 2;
                            break compare;
                        case '6':
                            buf.append('&');
                            i += 2;
                            break compare;
                        case '7':
                            buf.append('\'');
                            i += 2;
                            break compare;
                        case 'f':
                            buf.append('/');
                            i += 2;
                            break compare;
                        }
                    } else if (c2 == '3') {
                        switch (c3) {
                        case 'a':
                            buf.append(':');
                            i += 2;
                            break compare;
                        case 'c':
                            buf.append('<');
                            i += 2;
                            break compare;
                        case 'e':
                            buf.append('>');
                            i += 2;
                            break compare;
                        }
                    } else if (c2 == '4') {
                        if (c3 == '0') {
                            buf.append("@");
                            i += 2;
                            break compare;
                        }
                    } else if (c2 == '5') {
                        if (c3 == 'c') {
                            buf.append("\\");
                            i += 2;
                            break compare;
                        }
                    }
                }
                buf.append(c);
            }
        }
        return buf.toString();
    }

    /**
     * Encodes a string for use in an XML attribute by escaping characters with
     * a special meaning. In particular, white spaces are encoded as character
     * references, such that they are not replaced by ' ' on parsing.
     */
    private static String xmlAttribEncodeBinary(String value) {
        StringBuilder s = new StringBuilder();
        char buf[] = value.toCharArray();
        for (char c : buf) {
            switch (c) {
            case '<':
                s.append("<");
                break;
            case '>':
                s.append(">");
                break;
            case '&':
                s.append("&");
                break;
            case '"':
                s.append(""");
                break;
            case '\'':
                s.append("'");
                break;
            default:
                if (c <= 0x1f || (0x7f <= c && c <= 0x9f)) { // includes \t, \n,
                                                             // \r
                    s.append("&#x");
                    s.append(String.format("%X", (int) c));
                    s.append(';');
                } else {
                    s.append(c);
                }
            }
        }
        return s.toString();
    }

    /**
     * Returns a string representing a XML attribute. The value parameter is
     * escaped as necessary. In particular, white spaces are encoded as
     * character references, such that they are not replaced by ' ' on parsing.
     *
     * @param name
     *            name of the XML attribute
     * @param value
     *            value of the XML attribute
     */
    public static String xmlAttrib(String name, String value) {
        return name + "=\"" + xmlAttribEncodeBinary(value) + "\"";
    }

    /**
     * Escapes all necessary characters in the String so that it can be used in
     * an XML doc.
     *
     * Warning: This method does not escape unicode character
     * references (i.e. references of the from ë)
     *
     * @param string
     *            the string to escape.
     * @return the string with appropriate characters escaped.
     */
    public static String escapeForXML(String string) {
        if (string == null) {
            return null;
        }
        char ch;
        int i = 0;
        int last = 0;
        char[] input = string.toCharArray();
        int len = input.length;
        StringBuilder out = new StringBuilder((int) (len * 1.3));
        for (; i < len; i++) {
            ch = input[i];
            if (ch > '>') {
            } else if (ch == '<') {
                if (i > last) {
                    out.append(input, last, i - last);
                }
                last = i + 1;
                out.append(LT_ENCODE);
            } else if (ch == '>') {
                if (i > last) {
                    out.append(input, last, i - last);
                }
                last = i + 1;
                out.append(GT_ENCODE);
            }

            else if (ch == '&') {
                if (i > last) {
                    out.append(input, last, i - last);
                }
                // Do nothing if the string is of the form ë (unicode
                // value)
                if (!(len > i + 5 && input[i + 1] == '#'
                        && Character.isDigit(input[i + 2])
                        && Character.isDigit(input[i + 3])
                        && Character.isDigit(input[i + 4]) && input[i + 5] == ';')) {
                    last = i + 1;
                    out.append(AMP_ENCODE);
                }
            } else if (ch == '"') {
                if (i > last) {
                    out.append(input, last, i - last);
                }
                last = i + 1;
                out.append(QUOTE_ENCODE);
            } else if (ch == '\'') {
                if (i > last) {
                    out.append(input, last, i - last);
                }
                last = i + 1;
                out.append(APOS_ENCODE);
            }
        }
        if (last == 0) {
            return string;
        }
        if (i > last) {
            out.append(input, last, i - last);
        }
        return out.toString();
    }

    /**
     * Used by the hash method.
     */
    private static MessageDigest digest = null;

    /**
     * Hashes a String using the SHA-1 algorithm and returns the result as a
     * String of hexadecimal numbers. This method is synchronized to avoid
     * excessive MessageDigest object creation. If calling this method becomes a
     * bottleneck in your code, you may wish to maintain a pool of MessageDigest
     * objects instead of using this method.
     *

     * A hash is a one-way function -- that is, given an input, an output is
     * easily computed. However, given the output, the input is almost
     * impossible to compute. This is useful for passwords since we can store
     * the hash and a hacker will then have a very hard time determining the
     * original password.
     *
     * @param data
     *            the String to compute the hash of.
     * @return a hashed version of the passed-in String
     */
    public synchronized static String hash(String data) {
        if (digest == null) {
            try {
                digest = MessageDigest.getInstance("SHA-1");
            } catch (NoSuchAlgorithmException nsae) {
                log.log(Level.ERROR,
                        "Failed to load the SHA-1 MessageDigest. Smack will be unable to function normally.",
                        nsae);
            }
        }
        // Now, compute hash.
        try {
            digest.update(data.getBytes("UTF-8"));
        } catch (UnsupportedEncodingException e) {
            log.log(Level.ERROR, "Error computing hash", e);
        }
        return encodeHex(digest.digest());
    }

    /**
     * Encodes an array of bytes as String representation of hexadecimal.
     *
     * @param bytes
     *            an array of bytes to convert to a hex string.
     * @return generated hex string.
     */
    public static String encodeHex(byte[] bytes) {
        StringBuilder hex = new StringBuilder(bytes.length * 2);

        for (byte aByte : bytes) {
            if (((int) aByte & 0xff) < 0x10) {
                hex.append("0");
            }
            hex.append(Integer.toString((int) aByte & 0xff, 16));
        }

        return hex.toString();
    }

    /**
     * Encodes a String as a base64 String.
     *
     * @param data
     *            a String to encode.
     * @return a base64 encoded String.
     */
    public static String encodeBase64(String data) {
        byte[] bytes = null;
        try {
            bytes = data.getBytes("ISO-8859-1");
        } catch (UnsupportedEncodingException uee) {
            throw new IllegalStateException(uee);
        }
        return encodeBase64(bytes);
    }

    /**
     * Encodes a byte array into a base64 String.
     *
     * @param data
     *            a byte array to encode.
     * @return a base64 encode String.
     */
    public static String encodeBase64(byte[] data) {
        return encodeBase64(data, false);
    }

    /**
     * Encodes a byte array into a bse64 String.
     *
     * @param data
     *            The byte arry to encode.
     * @param lineBreaks
     *            True if the encoding should contain line breaks and false if
     *            it should not.
     * @return A base64 encoded String.
     */
    public static String encodeBase64(byte[] data, boolean lineBreaks) {
        return encodeBase64(data, 0, data.length, lineBreaks);
    }

    /**
     * Encodes a byte array into a bse64 String.
     *
     * @param data
     *            The byte arry to encode.
     * @param offset
     *            the offset of the bytearray to begin encoding at.
     * @param len
     *            the length of bytes to encode.
     * @param lineBreaks
     *            True if the encoding should contain line breaks and false if
     *            it should not.
     * @return A base64 encoded String.
     */
    public static String encodeBase64(byte[] data, int offset, int len,
            boolean lineBreaks) {
        return Base64.encodeBytes(data, offset, len,
                (lineBreaks ? Base64.NO_OPTIONS : Base64.DONT_BREAK_LINES));
    }

    /**
     * Decodes a base64 String. Unlike Base64.decode() this method does not try
     * to detect and decompress a gzip-compressed input.
     *
     * @param data
     *            a base64 encoded String to decode.
     * @return the decoded String.
     */
    public static byte[] decodeBase64(String data) {
        byte[] bytes;
        try {
            bytes = data.getBytes("UTF-8");
        } catch (java.io.UnsupportedEncodingException uee) {
            bytes = data.getBytes();
        }

        bytes = Base64.decode(bytes, 0, bytes.length, Base64.NO_OPTIONS);
        return bytes;
    }

    /**
     * Pseudo-random number generator object for use with randomString(). The
     * Random class is not considered to be cryptographically secure, so only
     * use these random Strings for low to medium security applications.
     */
    private static Random randGen = new Random();

    /**
     * Array of numbers and letters of mixed case. Numbers appear in the list
     * twice so that there is a more equal chance that a number will be picked.
     * We can use the array to get a random number or letter by picking a random
     * array index.
     */
    private static char[] numbersAndLetters = ("0123456789abcdefghijklmnopqrstuvwxyz"
            + "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ").toCharArray();

    /**
     * Returns a random String of numbers and letters (lower and upper case) of
     * the specified length. The method uses the Random class that is built-in
     * to Java which is suitable for low to medium grade security uses. This
     * means that the output is only pseudo random, i.e., each number is
     * mathematically generated so is not truly random.
     *

     *
     * The specified length must be at least one. If not, the method will return
     * null.
     *
     * @param length
     *            the desired length of the random String to return.
     * @return a random String of numbers and letters of the specified length.
     */
    public static String randomString(int length) {
        if (length < 1) {
            return null;
        }
        // Create a char buffer to put random letters and numbers in.
        char[] randBuffer = new char[length];
        for (int i = 0; i < randBuffer.length; i++) {
            randBuffer[i] = numbersAndLetters[randGen.nextInt(71)];
        }
        return new String(randBuffer);
    }

    private StringUtils() {
        // Not instantiable.
    }

    private static class PatternCouplings {
        Pattern pattern;
        DateFormat formatter;
        boolean needToConvertTimeZone = false;

        public PatternCouplings(Pattern datePattern, DateFormat dateFormat) {
            pattern = datePattern;
            formatter = dateFormat;
        }

        public PatternCouplings(Pattern datePattern, DateFormat dateFormat,
                boolean shouldConvertToRFC822) {
            pattern = datePattern;
            formatter = dateFormat;
            needToConvertTimeZone = shouldConvertToRFC822;
        }

        public String convertTime(String dateString) {
            if (dateString.charAt(dateString.length() - 1) == 'Z') {
                return dateString.replace("Z", "+0000");
            } else {
                // If the time zone wasn't specified with 'Z', then it's in
                // ISO8601 format (i.e. '(+|-)HH:mm')
                // RFC822 needs a similar format just without the colon (i.e.
                // '(+|-)HHmm)'), so remove it
                return dateString.replaceAll("([\\+\\-]\\d\\d):(\\d\\d)",
                        "$1$2");
            }
        }
    }

}