/*
 * Copyright The Sett Ltd, 2005 to 2014.
 *
 * 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.thesett.common.util.queues;

import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;

import com.thesett.common.error.NotImplementedException;
import com.thesett.common.tx.GlobalWriteLockWithWriteBehindTxMethod;
import com.thesett.common.tx.Transactional;
import com.thesett.common.tx.TxManager;
import com.thesett.common.tx.TxMethod;
import com.thesett.common.tx.TxOperation;
import com.thesett.common.util.Sizeable;
import com.thesett.common.util.SizeableReQueue;
import com.thesett.common.util.concurrent.Signalable;
import com.thesett.common.util.concurrent.SizeableBlockingQueue;

/**
 * WrapperQueue is a wrapper for queue implementations that is capable of making queues synchronized, transactional,
 * sizeable or atomically counted, or any combination of the above.
 *
 *
Transactional wrapped queues have the transaction isolation level 'ReadUncommitted'. Enqueue operations do not
 * become visible to dequeueing transactions until they are committed which might suggest that the level should be
 * 'ReadCommitted'. However, dequeues are write operations, in the sense that they alter the queue, although they do
 * read data from it too. Multiple transactions that are simultaneously dequeueing from the same queue will 'see' the
 * effect of each others dequeues immediately, before they are committed. One way to prevent this would be enforce
 * serializability by only allowing one consumer transaction to be active on the queue at any one time, with resultant
 * performance penalties.
 *
 *

CRC Card

 *
ResponsibilitiesCollaborations
 *
Make queues sizable.
 *
Make queues atomically counted.
 *
Make queues transactional.
 *
Provide fake transactional queues for transactional code running in a non-transactonal context.
 *

 *
 * @author Rupert Smith
 * @todo   It may be a bad idea to use queues that can reject offers, such as bounded buffers. Linked lists will be
 *         fine. Ideally don't want transactions to fail to commit or rollback if at all possible. Need a way to query
 *         the queue in advance to see if all of a sequence of offers will be accepted, or else there is the risk of
 *         only partly committing a transaction. The correct failure free behaviour for a commit to a bounded queue,
 *         that does not have sufficient space on it to accept the commit, should be to block until enough space becomes
 *         free.
 * @todo   Transactional efficiency. Note that dequeue is a tx write operation, as it alters the queue. Acquire can be
 *         thought of as purely a read operation, and it simply read-locks an element (although it sets a flag so that
 *         the read makes the record invisibile to other normal consumers). Now only accept is a write operation and is
 *         carried out on an already invisible element. This should be very good news for transactional parallelism, as
 *         commiting the tx should be a case of upgrading the read locks that only that tx holds to write locks. There
 *         may be consumers on the queue that can see these invisible elements, e.g., queue monitors and these may stall
 *         tx completion. One possibility is to write queue browsers, or monitors, in such a way that they can be
 *         notified of the intention to upgrade a read-lock, and are required to release their read-locks in the event.
 */
public class WrapperQueue<E> implements SizeableReQueue<E>, SizeableBlockingQueue<E>
{
    /**
     * AcquireState is used to signal the acquired state of elements in the queue, when using a requeue. Elements may be
     * tentatively acquired by at most one 'owner' prior to being removed or placed back into the queue. Acquire/Release
     * operations are not transactional, but when polling in a transactional context it is helpfull to be able to
     * distinguish between elements that a transaction has acquired, and elements that the transaction has already
     * consumed. Upon rollback, acquisition is not undone, but the consumed elements from the rolled back transaction,
     * should be presented for consumption again. The 'PendingAccept' state allows this to be distinguished from the
     * 'Acquired' state.
     */
    private enum AcquireState
    {
        /** Indicates that an element is not acquired. */
        Free,

        /** Indicates that an element has been acquired by an 'owner'. */
        Acquired,

        /** Indicates that an element has been acquired by an 'owner' and has a pending transaction accept operation. */
        Accepted
    }

    /** Flag to indicate that this wrapper should provide transactional access. */
    private final boolean transactional;

    /** Flag to indicate that this wrapper should monitor the queues size. */
    private final boolean sizeable;

    /** Flag to indicate that this wrapper should atomically count the number of elements in this queue. */
    private final boolean atomicallyCounted;

    /** Holds the underlying queue that is wrapped. */
    private final java.util.Queue<E> queue;

    /** Flag that indicates that the underlying queue implements java.util.concurrent.BlockingQueue. */
    private boolean isBlockingQueue;

    /** Holds the queue of requeued items, in the event of roll-backs. */
    private final Collection<RequeueElementWrapper<E>> requeue;

    /** Holds a mapping from elements to the requeue wrapped versions. */
    private final Map<E, RequeueElementWrapper<E>> requeuedElementMap = new HashMap<E, RequeueElementWrapper<E>>();

    /** Used to keep track of the size of the queue. */
    private final AtomicLong dataSize = new AtomicLong(0L);

    /** Used to keep track of the count of elements in the queue. */
    private final AtomicInteger count = new AtomicInteger(0);

    /** Holds the transactional method implementation strategy for transactional queues. */
    private final TxMethod txMethod;

    /** Holds the signalable resource to notify when the size of the queue changes. */
    private Signalable signalable;

    /** Holds the high water size threshold to wake up one waiting thread on. */
    private long highWaterSizeThreshold;

    /** Holds the low water size threshold to wake up one waiting thread on. */
    private long lowWaterSizeThreshold;

    /**
     * Creates a wrapped queue that provides the requested extended behaviour.
     *
     * @param queue             The queue to wrap.
     * @param requeue           The requeue buffer to use.
     * @param transactional     <tt>true</tt> to make the queue transactional.
     * @param sizeable          <tt>true</tt> to monitor the queue data size.
     * @param atomicallyCounted <tt>true</tt> to make the queue atomically counted.
     */
    public WrapperQueue(java.util.Queue<E> queue, Collection requeue, boolean transactional, boolean sizeable,
        boolean atomicallyCounted)
    {
        this.queue = queue;
        this.transactional = transactional;
        this.sizeable = sizeable;
        this.atomicallyCounted = atomicallyCounted;

        this.requeue = requeue;

        // Set up the transactional method depending on whether on not transactions are to be used.
        txMethod = new GlobalWriteLockWithWriteBehindTxMethod();

        if (transactional)
        {
            txMethod.setIsolationLevel(Transactional.IsolationLevel.ReadUncommitted);
        }
        else
        {
            txMethod.setIsolationLevel(Transactional.IsolationLevel.None);
        }

        // Set the blocking queue flag if the queue is a blocking one.
        isBlockingQueue = (queue instanceof java.util.concurrent.BlockingQueue);
    }

    /** {@inheritDoc} */
    public long sizeof()
    {
        return dataSize.get();
    }

    /** {@inheritDoc} */
    public boolean offer(E o)
    {
        if (transactional)
        {
            // Delegate the offer operation to the wrapped queue.
            txMethod.requestWriteOperation(new EnqueueRecord(o));

            // return success;
            return true;
        }
        else
        {
            boolean success = queue.offer(o);

            // Update the queue size if the offer was succesfull.
            if (success)
            {
                incrementSizeAndCount(o);
            }

            return success;
        }
    }

    /** {@inheritDoc} */
    public E poll()
    {
        Object owner;

        if (transactional)
        {
            owner = TxManager.getCurrentSession();
        }
        else
        {
            owner = null;
        }

        // Attempt to acquire an available element from the queue for the current transaction.
        return pollAccept(owner);
    }

    /** {@inheritDoc} */
    public E remove()
    {
        E result = poll();

        if (result == null)
        {
            throw new NoSuchElementException("The queue is empty.");
        }

        return result;
    }

    /** {@inheritDoc} */
    public E peek()
    {
        return queue.peek();
    }

    /** {@inheritDoc} */
    public E element()
    {
        return queue.element();
    }

    /** {@inheritDoc} */
    public int size()
    {
        // Use the atomic count for atomic counted queues.
        if (atomicallyCounted)
        {
            return count.get();
        }

        // Delegate to the wrapped queue for non-atomic counted queues.
        else
        {
            return queue.size();
        }
    }

    /** {@inheritDoc} */
    public boolean isEmpty()
    {
        return queue.isEmpty();
    }

    /** {@inheritDoc} */
    public boolean contains(Object o)
    {
        return queue.contains(o);
    }

    /** {@inheritDoc} */
    public Iterator<E> iterator()
    {
        // Iterate over the requeue till exhausted. Then start pulling stuff off the queue and putting it onto the
        // requeue, until exhausted.
        return new Iterator<E>()
            {
                Iterator<RequeueElementWrapper<E>> requeueIterator = requeue.iterator();
                boolean usingRequeue = true;

                public boolean hasNext()
                {
                    boolean result = false;

                    if (usingRequeue)
                    {
                        result = requeueIterator.hasNext();

                        if (!result)
                        {
                            usingRequeue = false;
                        }
                    }

                    if (!usingRequeue)
                    {
                        result = !queue.isEmpty();
                    }

                    return result;
                }

                public E next()
                {
                    E result;

                    if (usingRequeue)
                    {
                        result = requeueIterator.next().element;
                    }
                    else
                    {
                        result = queue.poll();
                        requeue(result);
                    }

                    return result;
                }

                public void remove()
                {
                    throw new NotImplementedException();
                }
            };
    }

    /** {@inheritDoc} */
    public Object[] toArray()
    {
        return queue.toArray();
    }

    /** {@inheritDoc} */
    public <T> T[] toArray(T[] a)
    {
        return queue.toArray(a);
    }

    /** {@inheritDoc} */
    public boolean add(E o)
    {
        return queue.add(o);
    }

    /** {@inheritDoc} */
    public boolean remove(Object o)
    {
        return queue.remove(o);
    }

    /** {@inheritDoc} */
    public boolean containsAll(Collection<?> c)
    {
        return queue.containsAll(c);
    }

    /** {@inheritDoc} */
    public boolean addAll(Collection<? extends E> c)
    {
        return queue.addAll(c);
    }

    /** {@inheritDoc} */
    public boolean removeAll(Collection<?> c)
    {
        return queue.removeAll(c);
    }

    /** {@inheritDoc} */
    public boolean retainAll(Collection<?> c)
    {
        return queue.retainAll(c);
    }

    /** {@inheritDoc} */
    public void clear()
    {
        queue.clear();
    }

    /** {@inheritDoc} */
    public E pollAccept(Object owner)
    {
        if (transactional)
        {
            E element = null;
            RequeueElementWrapper<E> record = null;

            // Find an element on the requeue that is free, or has already been acquired by the owner but not accepted.
            // Mark the element as acquired by the owner as necessary.
            if (!requeue.isEmpty())
            {
                for (RequeueElementWrapper<E> nextRecord : requeue)
                {
                    if (AcquireState.Free.equals(nextRecord.state))
                    {
                        record = nextRecord;
                        record.state = AcquireState.Acquired;
                        record.owner = owner;

                        element = record.element;

                        break;
                    }
                    else if (AcquireState.Acquired.equals(nextRecord.state) && owner.equals(nextRecord.owner))
                    {
                        record = nextRecord;
                        element = record.element;

                        break;
                    }
                }
            }

            // If an element cannot be found on the requeue, poll an element from the main queue, and place it onto the
            // requeue.
            if (record == null)
            {
                element = queue.poll();
            }

            // If no element at all can be found return null.
            if (element == null)
            {
                return element;
            }

            // Check that an element was actually available on the queue before creating a new acquired record for it
            // on the requeue.
            if (record == null)
            {
                record = requeue(element, owner, AcquireState.Acquired);
            }

            // Accept the element and create a transaction operation to remove it upon commit or unnaccept it upon
            // rollback.
            record.state = AcquireState.Accepted;
            txMethod.requestWriteOperation(new AcceptRecord(record));

            return record.element;
        }
        else
        {
            E element;

            // Find an element on the requeue that is free. Remove it and return it.
            if (!requeue.isEmpty())
            {
                for (RequeueElementWrapper<E> nextRecord : requeue)
                {
                    if (AcquireState.Free.equals(nextRecord.state))
                    {
                        requeue.remove(nextRecord);
                        requeuedElementMap.remove(nextRecord.element);

                        return nextRecord.element;
                    }
                }
            }

            // Or poll an element from the main queue and return it.
            element = queue.poll();

            if (element != null)
            {
                decrementSizeAndCount(element);
            }

            return element;
        }
    }

    /** {@inheritDoc} */
    public E pollAcquire(Object owner)
    {
        E element;

        // Find an element on the requeue that is free and return it.
        if (!requeue.isEmpty())
        {
            for (RequeueElementWrapper<E> nextRecord : requeue)
            {
                if (AcquireState.Free.equals(nextRecord.state))
                {
                    nextRecord.state = AcquireState.Acquired;
                    nextRecord.owner = owner;

                    return nextRecord.element;
                }
            }
        }

        // Nothing could be found on the requeue, so attempt to poll an element off the main queue and acquire
        // it on the requeue.
        element = queue.poll();

        if (element != null)
        {
            requeue(element, owner, AcquireState.Acquired);
        }

        return element;
    }

    /** {@inheritDoc} */
    public boolean acquire(Object owner, Object o)
    {
        // Look up the element wrapper record for the element to be accepted.
        RequeueElementWrapper<E> record = requeuedElementMap.get(o);

        // Check if the element is currently free, and acquire it if so.
        if (AcquireState.Free.equals(record.state))
        {
            record.state = AcquireState.Acquired;
            record.owner = owner;

            return true;
        }

        return false;
    }

    /** {@inheritDoc} */
    public void release(Object owner, Object o)
    {
        // Look up the element wrapper record for the element to be released, and release it.
        RequeueElementWrapper<E> record = requeuedElementMap.get(o);

        if (record != null)
        {
            record.state = AcquireState.Free;
            record.owner = null;
        }
    }

    /** {@inheritDoc} */
    public void accept(Object owner, Object o)
    {
        // Look up the element wrapper record for the element to be accepted.
        RequeueElementWrapper<E> record = requeuedElementMap.get(o);

        if (record != null)
        {
            // If running in a transaction, create an accept operation to accept the item only upon commit of the
            // transaction.
            if (transactional)
            {
                record.state = AcquireState.Accepted;
                txMethod.requestWriteOperation(new AcceptRecord(record));
            }
            else
            {
                requeuedElementMap.remove(o);
                requeue.remove(record);
            }
        }
    }

    /**
     * Prints the contents of this queue as a string for debugging purposes.
     *
     * @return The contents of this queue as a string.
     */
    public String toString()
    {
        return "WrapperQueue: [ dataSize = " + dataSize + ", count = " + count + "]";
    }

    /**
     * Equality based on object reference.
     *
     * @param  o The object to compare to.
     *
     * @return <tt>true</tt> if the comparator is this.
     */
    public boolean equals(Object o)
    {
        return (this == o);
    }

    /**
     * Default hash code based on object reference.
     *
     * @return The default hash code.
     */
    public int hashCode()
    {
        return super.hashCode();
    }

    /** {@inheritDoc} */
    public E poll(long timeout, TimeUnit unit) throws InterruptedException
    {
        if (!isBlockingQueue)
        {
            throw new UnsupportedOperationException("This operation is only supported on blocking queues.");
        }

        return ((java.util.concurrent.BlockingQueue<E>) queue).poll(timeout, unit);
    }

    /** {@inheritDoc} */
    public E take() throws InterruptedException
    {
        if (!isBlockingQueue)
        {
            throw new UnsupportedOperationException("This operation is only supported on blocking queues.");
        }

        return ((java.util.concurrent.BlockingQueue<E>) queue).take();
    }

    /** {@inheritDoc} */
    public int drainTo(Collection<? super E> c)
    {
        if (!isBlockingQueue)
        {
            throw new UnsupportedOperationException("This operation is only supported on blocking queues.");
        }

        return ((java.util.concurrent.BlockingQueue<E>) queue).drainTo(c);
    }

    /** {@inheritDoc} */
    public int drainTo(Collection<? super E> c, int maxElements)
    {
        if (!isBlockingQueue)
        {
            throw new UnsupportedOperationException("This operation is only supported on blocking queues.");
        }

        return ((java.util.concurrent.BlockingQueue<E>) queue).drainTo(c, maxElements);
    }

    /** {@inheritDoc} */
    public boolean offer(E o, long timeout, TimeUnit unit) throws InterruptedException
    {
        if (!isBlockingQueue)
        {
            throw new UnsupportedOperationException("This operation is only supported on blocking queues.");
        }

        return ((java.util.concurrent.BlockingQueue<E>) queue).offer(o, timeout, unit);
    }

    /** {@inheritDoc} */
    public void put(E o) throws InterruptedException
    {
        if (!isBlockingQueue)
        {
            throw new UnsupportedOperationException("This operation is only supported on blocking queues.");
        }

        ((java.util.concurrent.BlockingQueue<E>) queue).put(o);
    }

    /** {@inheritDoc} */
    public int remainingCapacity()
    {
        if (!isBlockingQueue)
        {
            throw new UnsupportedOperationException("This operation is only supported on blocking queues.");
        }

        return ((java.util.concurrent.BlockingQueue<E>) queue).remainingCapacity();
    }

    /** {@inheritDoc} */
    public void setSignalableResource(Signalable signalable)
    {
        this.signalable = signalable;
    }

    /** {@inheritDoc} */
    public void setHighWaterThreshold(long size)
    {
        this.highWaterSizeThreshold = size;
    }

    /** {@inheritDoc} */
    public void setLowWaterThreshold(long size)
    {
        this.lowWaterSizeThreshold = size;
    }

    /**
     * Places an element onto the requeue buffer.
     *
     * @param element The element to place onto the requeue buffer.
     */
    private void requeue(E element)
    {
        RequeueElementWrapper<E> record = new RequeueElementWrapper<E>(element);
        requeue.add(record);
        requeuedElementMap.put(element, record);
    }

    /**
     * Places an element onto the requeue buffer, in the acquired state by the specified owner.
     *
     * @param  element  The element to place onto the requeue buffer.
     * @param  owner    The owner of the acquired element.
     * @param  acquired The acquired state to set on the element in the requeue.
     *
     * @return The requeue element wrapper for the requeued element.
     */
    private RequeueElementWrapper<E> requeue(E element, Object owner, AcquireState acquired)
    {
        RequeueElementWrapper<E> record = new RequeueElementWrapper<E>(element);
        record.state = acquired;
        record.owner = owner;
        requeue.add(record);
        requeuedElementMap.put(element, record);

        return record;
    }

    /**
     * Atomically adds to the size and count, if the queue is running in atomic counting mode, or sizeable mode and the
     * element is sizeable.
     *
     * @param record The record to update the size and count for.
     */
    private void incrementSizeAndCount(E record)
    {
        // Update the count for atomically counted queues.
        if (atomicallyCounted)
        {
            count.incrementAndGet();
        }

        // Update the size for sizeable elements and sizeable queues.
        if (sizeable && (record instanceof Sizeable))
        {
            dataSize.addAndGet(((Sizeable) record).sizeof());
        }
        else if (sizeable)
        {
            dataSize.incrementAndGet();
        }
    }

    /**
     * Atomically subtracts from the size and count, if the queue is running in atomic counting mode, or sizeable mode
     * and the element is sizeable.
     *
     * @param record The record to update the size and count for.
     */
    private void decrementSizeAndCount(E record)
    {
        // Update the count for atomically counted queues.
        if (atomicallyCounted)
        {
            count.decrementAndGet();
        }

        // Update the size for sizeable elements and sizeable queues.
        if (sizeable && (record instanceof Sizeable))
        {
            long recordSize = -((Sizeable) record).sizeof();
            long oldSize = dataSize.getAndAdd(recordSize);
            long newSize = oldSize + recordSize;

            signalOnSizeThresholdCrossing(oldSize, newSize);
        }
        else if (sizeable)
        {
            long oldSize = dataSize.getAndDecrement();
            long newSize = oldSize - 1;

            signalOnSizeThresholdCrossing(oldSize, newSize);
        }
    }

    /**
     * Signals the signallable resource if the size crosses a threshold boundary in a downward direction.
     *
     * @param oldSize The old size.
     * @param newSize The new size.
     */
    private void signalOnSizeThresholdCrossing(long oldSize, long newSize)
    {
        if (signalable != null)
        {
            if ((oldSize >= lowWaterSizeThreshold) && (newSize < lowWaterSizeThreshold))
            {
                signalable.signalAll();
            }
            else if ((oldSize >= highWaterSizeThreshold) && (newSize < highWaterSizeThreshold))
            {
                signalable.signal();
            }
        }
    }

    /**
     * Wraps elements in the requeue buffer, to add ownership fields to them.
     */
    private class RequeueElementWrapper<E> implements Comparable
    {
        /** Holds the acquisition state of this element. */
        public AcquireState state = AcquireState.Free;

        /** Holds the unique owner of this element when it has been acquired. */
        public Object owner;

        /** Holds the element data. */
        public E element;

        /**
         * Creates a new requeue element.
         *
         * @param element The data element to wrap as a requeue element.
         */
        public RequeueElementWrapper(E element)
        {
            this.element = element;
        }

        /**
         * Compares this requeue element to another. They are equal when their data elements are equal.
         *
         * @param  o The element to compare to.
         *
         * @return <tt>true</tt> if the comparator is a RequeueElementWrapper with data element that is equal to this
         *         ones data element.
         */
        public boolean equals(Object o)
        {
            return (o instanceof RequeueElementWrapper) ? element.equals(((RequeueElementWrapper) o).element)
                                                        : element.equals(o);
        }

        /**
         * Computes a hash code based on the data element to be compatable with the equals method.
         *
         * @return A hash code based on the data element.
         */
        public int hashCode()
        {
            return ((element != null) ? element.hashCode() : 0);
        }

        /**
         * Compares this requeue element to another if the data element implements Comparable.
         *
         * @param  o The element to compare to.
         *
         * @return The result of comparing the data element of the comparator with this ones data element, if the
         *         element implements Comparable. If the element type does not implement Comparable an exception will be
         *         thrown.
         *
         * @throws UnsupportedOperationException If the element type does not implement Comparable.
         */
        public int compareTo(Object o)
        {
            if (element instanceof Comparable)
            {
                Comparable compElement = (Comparable) element;

                return (o instanceof RequeueElementWrapper) ? compElement.compareTo(
                    ((RequeueElementWrapper) o).element) : compElement.compareTo(o);
            }
            else
            {
                throw new UnsupportedOperationException();
            }
        }
    }

    /**
     * Encapsulates an enqueue as a transactional write-behind operation.
     */
    public class EnqueueRecord extends TxOperation
    {
        /** Holds the record to be added to the queue. */
        E record;

        /**
         * Creates an enqueue operation that is delayed until the transaction is committed.
         *
         * @param record The record to add to the queue.
         */
        public EnqueueRecord(E record)
        {
            this.record = record;
        }

        /**
         * {@inheritDoc}
         *
         *
This operation must not fail (other than critical error conditions like out of memory). If the underlying
         * queue is bounded, or cannot accept the enqueue for any reason this method will raise an
         * IllegalStateException. Use bounded wrapper queues for blocking commits when there is insufficient space on
         * the queue.
         */
        public void execute()
        {
            boolean success = queue.offer(record);

            // Update the queue size of the offer was succesfull.
            if (success)
            {
                incrementSizeAndCount(record);
            }

            // Raise an illegal state exception if the offer was refused.
            else
            {
                throw new IllegalStateException(
                    "Wrapped queues must accept all 'enqueues' when running transactionally.");
            }
        }
    }

    /**
     * Encapsulates an accept as a transactional write-behind operation.
     */
    public class AcceptRecord extends TxOperation
    {
        /** Holds the record to be removed from the queue. */
        RequeueElementWrapper<E> record;

        /**
         * Creates an accept operation that is delayed until the transaction is committed.
         *
         * @param record The record to accept from the queue.
         */
        public AcceptRecord(RequeueElementWrapper<E> record)
        {
            this.record = record;
        }

        /**
         * {@inheritDoc}
         *
         *
There is nothing to do at commit time for a dequeue, other than to update the queues size, as the data
         * item will already have been taken from the queue, and placed in this operation. This operation can safely be
         * deleted after the commit.
         */
        public void execute()
        {
            // Update the size and count on commit only.
            decrementSizeAndCount(record.element);

            requeuedElementMap.remove(record.element);
            requeue.remove(record);
        }

        /** {@inheritDoc} */
        public boolean cancel(boolean mayInterruptIfRunning)
        {
            //release(TxManager.getTxIdFromThread(), record);
            record.state = AcquireState.Acquired;

            return true;
        }
    }
}


/*
 * Copyright The Sett Ltd, 2005 to 2014.
 *
 * 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.thesett.common.jndi;

import java.util.Enumeration;
import java.util.Hashtable;

import javax.naming.Binding;
import javax.naming.CompositeName;
import javax.naming.Context;
import javax.naming.InvalidNameException;
import javax.naming.Name;
import javax.naming.NameAlreadyBoundException;
import javax.naming.NameClassPair;
import javax.naming.NameNotFoundException;
import javax.naming.NameParser;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.NotContextException;
import javax.naming.OperationNotSupportedException;

/**
 * SimpleContext is a simple JNDI service provider. It implements a flat namespace (with no federation support).
 *
 *
An instance of this context is bound directly as the initial context. The corresponding InitialContextFactory
 * definition can be found in {@link SimpleInitialContextFactory}.
 *
 *

CRC Card

 *
ResponsibilitiesCollaborations
 *

 *
 * @author Rupert Smith
 */
public class SimpleContext implements Context
{
    /** Holds a reference to the name parser. */
    static final NameParser myParser = new SimpleNameParser();

    /** Holds the JNDI environment. */
    Hashtable myEnv;

    /** Holds the bindings of names to objects. */
    private Hashtable bindings = new Hashtable(11);

    /**
     * Creates a new SimpleContext.
     *
     * @param environment The JNDI environment for the context.
     */
    SimpleContext(Hashtable environment)
    {
        myEnv = (environment != null) ? (Hashtable) (environment.clone()) : null;
    }

    /**
     * Retrieves the named object. If name is empty, returns a new instance of this context (which represents the same
     * naming context as this context, but its environment may be modified independently and it may be accessed
     * concurrently).
     *
     * @param  name The name to look up.
     *
     * @return The object associated with the name, if there is one.
     *
     * @throws NamingException If the name cannot be found in the context.
     */
    public Object lookup(String name) throws NamingException
    {
        if ("".equals(name))
        {
            // Asking to look up this context itself.  Create and return
            // a new instance with its own independent environment.
            return (new SimpleContext(myEnv));
        }

        Object answer = bindings.get(name);

        if (answer == null)
        {
            throw new NameNotFoundException(name + " not found");
        }

        return answer;
    }

    /**
     * Retrieves the named object. If name is empty, returns a new instance of this context (which represents the same
     * naming context as this context, but its environment may be modified independently and it may be accessed
     * concurrently).
     *
     * @param  name The name to look up.
     *
     * @return The object associated with the name, if there is one.
     *
     * @throws NamingException If the name cannot be found in the context.
     */
    public Object lookup(Name name) throws NamingException
    {
        // Flat namespace; no federation; just call string version
        return lookup(name.toString());
    }

    /**
     * Binds a name to an object. All intermediate contexts and the target context (that named by all but terminal
     * atomic component of the name) must already exist.
     *
     * @param  name The name to bind; may not be empty.
     * @param  obj  The object to bind; possibly null.
     *
     * @throws NamingException If the name already exists.
     */
    public void bind(String name, Object obj) throws NamingException
    {
        if ("".equals(name))
        {
            throw new InvalidNameException("Cannot bind empty name");
        }

        if (bindings.get(name) != null)
        {
            throw new NameAlreadyBoundException("Use rebind to override");
        }

        bindings.put(name, obj);
    }

    /**
     * Binds a name to an object. All intermediate contexts and the target context (that named by all but terminal
     * atomic component of the name) must already exist.
     *
     * @param  name The name to bind; may not be empty.
     * @param  obj  The object to bind; possibly null.
     *
     * @throws NamingException If the name already exists or is illegal.
     */
    public void bind(Name name, Object obj) throws NamingException
    {
        // Flat namespace; no federation; just call string version
        bind(name.toString(), obj);
    }

    /**
     * Binds a name to an object, overwriting any existing binding. All intermediate contexts and the target context
     * (that named by all but terminal atomic component of the name) must already exist.
     *
     *
If the object is a DirContext, any existing attributes associated with the name are replaced with those of
     * the object. Otherwise, any existing attributes associated with the name remain unchanged.
     *
     * @param  name The name to bind; may not be empty.
     * @param  obj  The object to bind; possibly null.
     *
     * @throws NamingException If the name is illegal.
     */
    public void rebind(String name, Object obj) throws NamingException
    {
        if ("".equals(name))
        {
            throw new InvalidNameException("Cannot bind empty name");
        }

        bindings.put(name, obj);
    }

    /**
     * Binds a name to an object, overwriting any existing binding. All intermediate contexts and the target context
     * (that named by all but terminal atomic component of the name) must already exist.
     *
     *
If the object is a DirContext, any existing attributes associated with the name are replaced with those of
     * the object. Otherwise, any existing attributes associated with the name remain unchanged.
     *
     * @param  name The name to bind; may not be empty.
     * @param  obj  The object to bind; possibly null.
     *
     * @throws NamingException If the name is illegal.
     */
    public void rebind(Name name, Object obj) throws NamingException
    {
        // Flat namespace; no federation; just call string version
        rebind(name.toString(), obj);
    }

    /**
     * Unbinds the named object. Removes the terminal atomic name in name from the target context--that named by all but
     * the terminal atomic part of name.
     *
     *
This method is idempotent. It succeeds even if the terminal atomic name is not bound in the target context,
     * but throws NameNotFoundException if any of the intermediate contexts do not exist.
     *
     *
Any attributes associated with the name are removed. Intermediate contexts are not changed.
     *
     * @param  name The name to unbind.
     *
     * @throws NamingException If the name is illegal, or intermediate contexts do not exist.
     */
    public void unbind(String name) throws NamingException
    {
        if ("".equals(name))
        {
            throw new InvalidNameException("Cannot unbind empty name");
        }

        bindings.remove(name);
    }

    /**
     * Unbinds the named object. Removes the terminal atomic name in name from the target context--that named by all but
     * the terminal atomic part of name.
     *
     *
This method is idempotent. It succeeds even if the terminal atomic name is not bound in the target context,
     * but throws NameNotFoundException if any of the intermediate contexts do not exist.
     *
     *
Any attributes associated with the name are removed. Intermediate contexts are not changed.
     *
     * @param  name The name to unbind.
     *
     * @throws NamingException If the name is illegal, or intermediate contexts do not exist.
     */
    public void unbind(Name name) throws NamingException
    {
        // Flat namespace; no federation; just call string version
        unbind(name.toString());
    }

    /**
     * Binds a new name to the object bound to an old name, and unbinds the old name. Both names are relative to this
     * context. Any attributes associated with the old name become associated with the new name. Intermediate contexts
     * of the old name are not changed.
     *
     * @param  oldname The name of the existing binding; may not be empty.
     * @param  newname The new name of the binding; may not be empty.
     *
     * @throws NamingException If newname is bound, oldname cannot be found or one of the names is illegal.
     */
    public void rename(String oldname, String newname) throws NamingException
    {
        if ("".equals(oldname) || "".equals(newname))
        {
            throw new InvalidNameException("Cannot rename empty name");
        }

        // Check if new name exists
        if (bindings.get(newname) != null)
        {
            throw new NameAlreadyBoundException(newname + " is already bound");
        }

        // Check if old name is bound
        Object oldBinding = bindings.remove(oldname);

        if (oldBinding == null)
        {
            throw new NameNotFoundException(oldname + " not bound");
        }

        bindings.put(newname, oldBinding);
    }

    /**
     * Binds a new name to the object bound to an old name, and unbinds the old name. Both names are relative to this
     * context. Any attributes associated with the old name become associated with the new name. Intermediate contexts
     * of the old name are not changed.
     *
     * @param  oldname The name of the existing binding; may not be empty.
     * @param  newname The new name of the binding; may not be empty.
     *
     * @throws NamingException If newname is bound, oldname cannot be found or one of the names is illegal.
     */
    public void rename(Name oldname, Name newname) throws NamingException
    {
        // Flat namespace; no federation; just call string version
        rename(oldname.toString(), newname.toString());
    }

    /**
     * Enumerates the names bound in the named context, along with the class names of objects bound to them. The
     * contents of any subcontexts are not included. If a binding is added to or removed from this context, its effect
     * on an enumeration previously returned is undefined.
     *
     * @param  name The name of the context to list.
     *
     * @return An enumeration of the names and class names of the bindings in this context. Each element of the
     *         enumeration is of type NameClassPair.
     *
     * @throws NamingException If the context is not known.
     */
    public NamingEnumeration list(String name) throws NamingException
    {
        if ("".equals(name))
        {
            // listing this context
            return new FlatNames(bindings.keys());
        }

        // Perhaps `name' names a context
        Object target = lookup(name);

        if (target instanceof Context)
        {
            return ((Context) target).list("");
        }

        throw new NotContextException(name + " cannot be listed");
    }

    /**
     * Enumerates the names bound in the named context, along with the class names of objects bound to them. The
     * contents of any subcontexts are not included. If a binding is added to or removed from this context, its effect
     * on an enumeration previously returned is undefined.
     *
     * @param  name The name of the context to list.
     *
     * @return An enumeration of the names and class names of the bindings in this context. Each element of the
     *         enumeration is of type NameClassPair.
     *
     * @throws NamingException If the context is not known.
     */
    public NamingEnumeration list(Name name) throws NamingException
    {
        // Flat namespace; no federation; just call string version
        return list(name.toString());
    }

    /**
     * Enumerates the names bound in the named context, along with the objects bound to them. The contents of any
     * subcontexts are not included.
     *
     *
If a binding is added to or removed from this context, its effect on an enumeration previously returned is
     * undefined.
     *
     * @param  name The name of the context to list.
     *
     * @return An enumeration of the bindings in this context. Each element of the enumeration is of type Binding.
     *
     * @throws NamingException If the context is not known.
     */
    public NamingEnumeration listBindings(String name) throws NamingException
    {
        if ("".equals(name))
        {
            // listing this context
            return new FlatBindings(bindings.keys());
        }

        // Perhaps `name' names a context
        Object target = lookup(name);

        if (target instanceof Context)
        {
            return ((Context) target).listBindings("");
        }

        throw new NotContextException(name + " cannot be listed");
    }

    /**
     * Enumerates the names bound in the named context, along with the objects bound to them. The contents of any
     * subcontexts are not included.
     *
     *
If a binding is added to or removed from this context, its effect on an enumeration previously returned is
     * undefined.
     *
     * @param  name The name of the context to list.
     *
     * @return An enumeration of the bindings in this context. Each element of the enumeration is of type Binding.
     *
     * @throws NamingException If the context is not known.
     */
    public NamingEnumeration listBindings(Name name) throws NamingException
    {
        // Flat namespace; no federation; just call string version
        return listBindings(name.toString());
    }

    /**
     * Destroys the named context and removes it from the namespace. Any attributes associated with the name are also
     * removed. Intermediate contexts are not destroyed.
     *
     *
This method is idempotent. It succeeds even if the terminal atomic name is not bound in the target context,
     * but throws NameNotFoundException if any of the intermediate contexts do not exist.
     *
     *
In a federated naming system, a context from one naming system may be bound to a name in another. One can
     * subsequently look up and perform operations on the foreign context using a composite name. However, an attempt
     * destroy the context using this composite name will fail with NotContextException, because the foreign context is
     * not a "subcontext" of the context in which it is bound. Instead, use unbind() to remove the binding of the
     * foreign context. Destroying the foreign context requires that the destroySubcontext() be performed on a context
     * from the foreign context's "native" naming system.
     *
     * @param  name The name of the context to be destroyed; may not be empty.
     *
     * @throws NamingException Always throws OperationNotSupportedException.
     */
    public void destroySubcontext(String name) throws NamingException
    {
        throw new OperationNotSupportedException("SimpleContext does not support subcontexts");
    }

    /**
     * Destroys the named context and removes it from the namespace. Any attributes associated with the name are also
     * removed. Intermediate contexts are not destroyed.
     *
     *
This method is idempotent. It succeeds even if the terminal atomic name is not bound in the target context,
     * but throws NameNotFoundException if any of the intermediate contexts do not exist.
     *
     *
In a federated naming system, a context from one naming system may be bound to a name in another. One can
     * subsequently look up and perform operations on the foreign context using a composite name. However, an attempt
     * destroy the context using this composite name will fail with NotContextException, because the foreign context is
     * not a "subcontext" of the context in which it is bound. Instead, use unbind() to remove the binding of the
     * foreign context. Destroying the foreign context requires that the destroySubcontext() be performed on a context
     * from the foreign context's "native" naming system.
     *
     * @param  name The name of the context to be destroyed; may not be empty.
     *
     * @throws NamingException Always throws OperationNotSupportedException.
     */
    public void destroySubcontext(Name name) throws NamingException
    {
        // Flat namespace; no federation; just call string version
        destroySubcontext(name.toString());
    }

    /**
     * Creates and binds a new context. Creates a new context with the given name and binds it in the target context
     * (that named by all but terminal atomic component of the name). All intermediate contexts and the target context
     * must already exist. This operation does nothing on simple context.
     *
     * @param  name The name of the context to create; may not be empty.
     *
     * @return The newly created context.
     */
    public Context createSubcontext(String name)
    {
        return null;
    }

    /**
     * Creates and binds a new context. Creates a new context with the given name and binds it in the target context
     * (that named by all but terminal atomic component of the name). All intermediate contexts and the target context
     * must already exist.
     *
     * @param  name The name of the context to create; may not be empty.
     *
     * @return The newly created context.
     *
     * @throws NamingException Always throws OperationNotSupportedException.
     */
    public Context createSubcontext(Name name) throws NamingException
    {
        // Flat namespace; no federation; just call string version
        return createSubcontext(name.toString());
    }

    /**
     * Retrieves the named object, following links except for the terminal atomic component of the name. If the object
     * bound to name is not a link, returns the object itself.
     *
     * @param  name The name of the object to look up.
     *
     * @return The object bound to name, not following the terminal link (if any).
     *
     * @throws NamingException If the name is illegal or not bound.
     */
    public Object lookupLink(String name) throws NamingException
    {
        // This flat context does not treat links specially
        return lookup(name);
    }

    /**
     * Retrieves the named object, following links except for the terminal atomic component of the name. If the object
     * bound to name is not a link, returns the object itself.
     *
     * @param  name The name of the object to look up.
     *
     * @return The object bound to name, not following the terminal link (if any).
     *
     * @throws NamingException If the name is illegal or not bound.
     */
    public Object lookupLink(Name name) throws NamingException
    {
        // Flat namespace; no federation; just call string version
        return lookupLink(name.toString());
    }

    /**
     * Retrieves the parser associated with the named context. In a federation of namespaces, different naming systems
     * will parse names differently. This method allows an application to get a parser for parsing names into their
     * atomic components using the naming convention of a particular naming system. Within any single naming system,
     * NameParser objects returned by this method must be equal (using the equals() test).
     *
     * @param  name The name of the context from which to get the parser. Ignored.
     *
     * @return The instance of {@link SimpleNameParser}.
     */
    public NameParser getNameParser(String name)
    {
        return myParser;
    }

    /**
     * Retrieves the parser associated with the named context. In a federation of namespaces, different naming systems
     * will parse names differently. This method allows an application to get a parser for parsing names into their
     * atomic components using the naming convention of a particular naming system. Within any single naming system,
     * NameParser objects returned by this method must be equal (using the equals() test).
     *
     * @param  name The name of the context from which to get the parser. Ignored.
     *
     * @return The instance of {@link SimpleNameParser}.
     */
    public NameParser getNameParser(Name name)
    {
        // Flat namespace; no federation; just call string version
        return getNameParser(name.toString());
    }

    /**
     * Composes the name of this context with a name relative to this context. Given a name (name) relative to this
     * context, and the name (prefix) of this context relative to one of its ancestors, this method returns the
     * composition of the two names using the syntax appropriate for the naming system(s) involved. That is, if name
     * names an object relative to this context, the result is the name of the same object, but relative to the ancestor
     * context. None of the names may be null.
     *
     *
For example, if this context is named "wiz.com" relative to the initial context, then
     *
     *

     *         composeName("east", "wiz.com")
     *

     *
     *
might return "east.wiz.com". If instead this context is named "org/research", then
     *
     *

     *         composeName("user/jane", "org/research")
     *

     *
     *
might return "org/research/user/jane" while
     *
     *

     *         composeName("user/jane", "research")
     *

     *
     *
returns "research/user/jane".
     *
     * @param  name   A name relative to this context.
     * @param  prefix The name of this context relative to one of its ancestors.
     *
     * @return The composition of prefix and name.
     *
     * @throws NamingException If a naming exception is encountered.
     */
    public String composeName(String name, String prefix) throws NamingException
    {
        Name result = composeName(new CompositeName(name), new CompositeName(prefix));

        return result.toString();
    }

    /**
     * Composes the name of this context with a name relative to this context. Given a name (name) relative to this
     * context, and the name (prefix) of this context relative to one of its ancestors, this method returns the
     * composition of the two names using the syntax appropriate for the naming system(s) involved. That is, if name
     * names an object relative to this context, the result is the name of the same object, but relative to the ancestor
     * context. None of the names may be null.
     *
     * @param  name   A name relative to this context.
     * @param  prefix The name of this context relative to one of its ancestors.
     *
     * @return The composition of prefix and name.
     *
     * @throws NamingException If a naming exception is encountered.
     */
    public Name composeName(Name name, Name prefix) throws NamingException
    {
        Name result = (Name) (prefix.clone());
        result.addAll(name);

        return result;
    }

    /**
     * Adds a new environment property to the environment of this context. If the property already exists, its value is
     * overwritten. See class description for more details on environment properties.
     *
     * @param  propName The name of the property.
     * @param  propVal  The value of the property.
     *
     * @return The previous value of the property, or null if the property was not in the environment before.
     */
    public Object addToEnvironment(String propName, Object propVal)
    {
        if (myEnv == null)
        {
            myEnv = new Hashtable(5, 0.75f);
        }

        return myEnv.put(propName, propVal);
    }

    /**
     * Removes an environment property from the environment of this context. See class description for more details on
     * environment properties.
     *
     * @param  propName The name of the property to remove.
     *
     * @return The previous value of the property, or null if the property was not in the environment before.
     */
    public Object removeFromEnvironment(String propName)
    {
        if (myEnv == null)
        {
            return null;
        }

        return myEnv.remove(propName);
    }

    /**
     * Retrieves the environment in effect for this context. See class description for more details on environment
     * properties.
     *
     *
The caller should not make any changes to the object returned: their effect on the context is undefined. The
     * environment of this context may be changed using addToEnvironment() and removeFromEnvironment().
     *
     * @return The environment of this context; never null.
     */
    public Hashtable getEnvironment()
    {
        if (myEnv == null)
        {
            // Must return non-null
            return new Hashtable(3, 0.75f);
        }
        else
        {
            return (Hashtable) myEnv.clone();
        }
    }

    /**
     * Retrieves the full name of this context within its own namespace.
     *
     *
Many naming services have a notion of a "full name" for objects in their respective namespaces. For example,
     * an LDAP entry has a distinguished name, and a DNS record has a fully qualified name. This method allows the
     * client application to retrieve this name. The string returned by this method is not a JNDI composite name and
     * should not be passed directly to context methods. In naming systems for which the notion of full name does not
     * make sense, OperationNotSupportedException is thrown.
     *
     * @return This context's name in its own namespace; never null.
     */
    public String getNameInNamespace()
    {
        return "";
    }

    /**
     * Closes this context. This method releases this context's resources immediately, instead of waiting for them to be
     * released automatically by the garbage collector.
     *
     *
This method is idempotent: invoking it on a context that has already been closed has no effect. Invoking any
     * other method on a closed context is not allowed, and results in undefined behaviour.
     */
    public void close()
    {
        myEnv = null;
        bindings = null;
    }

    /**
     * Class for enumerating name/class pairs.
     */
    class FlatNames implements NamingEnumeration
    {
        /** Holds an enumeration of the names. */
        Enumeration names;

        /**
         * Creates an enumeration of names.
         *
         * @param names The enumeration of names.
         */
        FlatNames(Enumeration names)
        {
            this.names = names;
        }

        /**
         * Detects whether or not this enumeration contains more names.
         *
         * @return <tt>true</tt> if it contains more names.
         */
        public boolean hasMoreElements()
        {
            return names.hasMoreElements();
        }

        /**
         * Detects whether or not this enumeration contains more names.
         *
         * @return <tt>true</tt> if it contains more names.
         */
        public boolean hasMore()
        {
            return hasMoreElements();
        }

        /**
         * Gets the next element from this enumeration.
         *
         * @return The next element from this enumeration.
         */
        public Object nextElement()
        {
            String name = (String) names.nextElement();
            String className = bindings.get(name).getClass().getName();

            return new NameClassPair(name, className);
        }

        /**
         * Gets the next element from this enumeration.
         *
         * @return The next element from this enumeration.
         */
        public Object next()
        {
            return nextElement();
        }

        /** Does nothing. */
        public void close()
        {
        }
    }

    /**
     * Class for enumerating bindings.
     */
    class FlatBindings implements NamingEnumeration
    {
        /** Holds an enumeration of the names. */
        Enumeration names;

        /**
         * Creates an enumeration of names.
         *
         * @param names The enumeration of names.
         */
        FlatBindings(Enumeration names)
        {
            this.names = names;
        }

        /**
         * Detects whether or not this enumeration contains more names.
         *
         * @return <tt>true</tt> if it contains more names.
         */
        public boolean hasMoreElements()
        {
            return names.hasMoreElements();
        }

        /**
         * Detects whether or not this enumeration contains more names.
         *
         * @return <tt>true</tt> if it contains more names.
         */
        public boolean hasMore()
        {
            return hasMoreElements();
        }

        /**
         * Gets the next element from this enumeration.
         *
         * @return The next element from this enumeration.
         */
        public Object nextElement()
        {
            String name = (String) names.nextElement();

            return new Binding(name, bindings.get(name));
        }

        /**
         * Gets the next element from this enumeration.
         *
         * @return The next element from this enumeration.
         */
        public Object next()
        {
            return nextElement();
        }

        /** Does nothing. */
        public void close()
        {
        }
    }
}


/*
 * Copyright The Sett Ltd, 2005 to 2014.
 *
 * 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.thesett.common.util;

/**
 * Sizeof provides an alternative mechanism to the C sizeof() method for Java. Java does not provide a sizeof() method
 * so getting an exact memeory size of a data structure is not possible using the standard library.
 *
 *
The Sizeof class requires a quiescent JVM so that the heap activity is only due to object allocations and garbage
 * collections requested by the measuring thread. It also requires a large number of identical object instances. This
 * does not work when you want to size a single large object.
 *
 *

CRC Card

 *
ResponsibilitiesCollaborations
 *
Calculates how much memory the virtual machine is currently using.
 *

 *
 * @author Rupert Smith
 */
public class Sizeof
{
    /** Used to hold a reference to the Runtime class for querying memory and triggering garbage collection. */
    private static final Runtime RUNTIME = Runtime.getRuntime();

    /**
     * Repeatedly runs the garbage collector and finalization method of the JVM runtime system until the used memory
     * count becomes stable or 500 iterations occur, whichever happens soonest. If other threads are active then this
     * method is not likely to work as the used memory count will continually be changing.
     */
    private static void runGCTillStable()
    {
        // Possibly add another iteration in here to run this whole method 3 or 4 times.

        long usedMem1 = usedMemory();
        long usedMem2 = Long.MAX_VALUE;

        // Repeatedly garbage collection until the used memory count becomes stable, or 500 iterations occur.
        for (int i = 0; (usedMem1 < usedMem2) && (i < 500); i++)
        {
            // Force finalisation of all object pending finalisation.
            RUNTIME.runFinalization();

            // Return unused memory to the heap.
            RUNTIME.gc();

            // Allow other threads to run.
            Thread.currentThread().yield();

            // Keep the old used memory count from the last iteration and get a fresh reading.
            usedMem2 = usedMem1;
            usedMem1 = usedMemory();
        }
    }

    /**
     * Calculates the total amount of memory used as total memory available minus the amount that is free.
     *
     * @return The total amount of memory used.
     */
    private static long usedMemory()
    {
        runGCTillStable();

        return RUNTIME.totalMemory() - RUNTIME.freeMemory();
    }
}


/**
 * Copyright 2010 Bazaarvoice, Inc.
 *
 * 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.
 *
 * @author J. Ryan Stinnett (ryan.stinnett@bazaarvoice.com)
 */

package com.bazaarvoice.jless.parser;

import com.bazaarvoice.jless.ast.node.ArgumentsNode;
import com.bazaarvoice.jless.ast.node.ExpressionGroupNode;
import com.bazaarvoice.jless.ast.node.ExpressionNode;
import com.bazaarvoice.jless.ast.node.ExpressionPhraseNode;
import com.bazaarvoice.jless.ast.node.FilterArgumentNode;
import com.bazaarvoice.jless.ast.node.FunctionNode;
import com.bazaarvoice.jless.ast.node.InternalNode;
import com.bazaarvoice.jless.ast.node.LineBreakNode;
import com.bazaarvoice.jless.ast.node.MediaQueryNode;
import com.bazaarvoice.jless.ast.node.MediaTypeNode;
import com.bazaarvoice.jless.ast.node.MediaTypeRestriction;
import com.bazaarvoice.jless.ast.node.Node;
import com.bazaarvoice.jless.ast.node.ParametersNode;
import com.bazaarvoice.jless.ast.node.PlaceholderNode;
import com.bazaarvoice.jless.ast.node.PropertyNode;
import com.bazaarvoice.jless.ast.node.RuleSetNode;
import com.bazaarvoice.jless.ast.node.ScopeNode;
import com.bazaarvoice.jless.ast.node.SelectorGroupNode;
import com.bazaarvoice.jless.ast.node.SelectorNode;
import com.bazaarvoice.jless.ast.node.SelectorSegmentNode;
import com.bazaarvoice.jless.ast.node.SimpleNode;
import com.bazaarvoice.jless.ast.node.SpacingNode;
import com.bazaarvoice.jless.ast.node.VariableDefinitionNode;
import com.bazaarvoice.jless.ast.node.VariableReferenceNode;
import com.bazaarvoice.jless.ast.node.WhiteSpaceCollectionNode;
import com.bazaarvoice.jless.ast.util.NodeTreeUtils;
import com.bazaarvoice.jless.exception.UndefinedMixinException;
import com.bazaarvoice.jless.exception.UndefinedVariableException;
import org.parboiled.BaseParser;
import org.parboiled.Context;
import org.parboiled.Rule;
import org.parboiled.annotations.MemoMismatches;
import org.parboiled.support.Var;

import java.util.List;

/**
 * Initially transcribed into Parboiled from the
 * LESS Treetop grammar
 * that was created by Alexis Sellier as part of the LESS Ruby implementation.
 *
 * From there, the parser has been modified to more closely support Parboiled style and a modified set of requirements.
 * Parsing differences from LESS Ruby include:
 *

 *
Selector parsing was rewritten using the CSS 3 selector grammar

 *
Numbers in attribute selectors must be quoted (as per the CSS spec)

 *
Case-sensitivity was removed

 *
Line breaks can be used in all places where spaces are accepted

 *

 *
 * This parser attempts to track line breaks so that the input and output files can have the same number of lines
 * (which is helpful when referencing styles via browser tools like Firebug).
 *
 * This list only notes changes in the parsing stage. See {@link com.bazaarvoice.jless.LessProcessor} for details on any changes
 * to the translation stage.
 *
 * @see com.bazaarvoice.jless.LessProcessor
 */
public class Parser extends BaseParser<Node> {

    private boolean _parserTranslationEnabled;

    public Parser() {
        this(true);
    }

    public Parser(Boolean parserTranslationEnabled) {
        _parserTranslationEnabled = parserTranslationEnabled;
    }

    protected boolean isParserTranslationEnabled() {
        return _parserTranslationEnabled;
    }

    // ********** Document **********

    public Rule Document() {
        return Sequence(Scope(), EOI);
    }

    /**
     * This is the high-level rule at some scope (either the root document or within a rule set / mixin).
     * Future: Imports
     */
    Rule Scope() {
        return Sequence(
                push(new ScopeNode()),
                ZeroOrMore(
                        FirstOf(
                                Declaration(),
                                MediaQuery(),
                                RuleSet(),
                                MixinReference(),
                                Sequence(push(new WhiteSpaceCollectionNode()), Sp1Nodes())
                        ),
                        peek(1).addChild(pop())
                )
        );
    }

    // ********** CSS Rule Sets & Mixins **********

    /**
     * "@media" Ws0 "only "? MediaType
     */
    Rule MediaQuery() {
        return Sequence(
            push(new MediaQueryNode()),
            MediaQueryDefinition(), peek(1).addChild(pop()), peek().addChild(new SimpleNode(" ")),
            Ws0(),
            Optional(OnlyIndicator()),
            OneOrMore(MediaType()), Ws0(),
            '{', Scope(), '}',
            peek(1).addChild(pop()),
            Ws0Nodes()
        );
    }

    /**
     * media type name Optional( and "(" Css property ")")
     */
    Rule MediaType() {
        return Sequence(
            push(new MediaTypeNode()),
            MediaTypeName(),
            peek(1).addChild(pop()),
            FirstOf(
                Sequence(
                    MediaTypeRestriction(),
                    peek(1).addChild(pop())
                ),
                Optional(Ws0(), push(new SpacingNode(" ")), peek(1).addChild(pop()))
            ),
            peek(1).addChild(pop())
        );
    }

    /**
    * simple media type name
    */
    Rule MediaTypeName() {
        return Sequence(
              OneOrMore(NameCharacter()),
              push(new SimpleNode(match()))
        );
    }

    /**
     * Sequence ( Whitespace "and" Whitespace "(" Css Property ")" )
     */
    Rule MediaTypeRestriction() {
        return Sequence(
            push(new MediaTypeRestriction()),
            Ws0(), "and", Ws0(), '(',
            Sequence(
                    Ident(), peek().addChild(new SimpleNode(match())),
                    ':', peek().addChild(new SimpleNode(":")),
                    Optional(Ws0()),
                    peek().addChild(new SpacingNode(" ")),
                    ExpressionPhrase()
            ),
            ')',
            peek(1).addChild(pop())
        );
    }

    /**
     * "only" Whitespace
     */
    Rule OnlyIndicator() {
        return Sequence(
                "only",
                peek().addChild(new SimpleNode(match())), peek().addChild(new SimpleNode(" ")),
                Ws0()
        );
    }

    /**
     * (Selectors '{' Ws0 / Class Ws0 Parameters Ws0 '{' Ws0) Scope Ws0 '}' Ws0
     *
     * Ex: div, .class, body > p {...}
     *
     * Future: Hidden
     */
    Rule RuleSet() {
        return Sequence(
                FirstOf(
                        // Standard CSS rule set
                        Sequence(
                                SelectorGroup(), push(new RuleSetNode(pop())),
                                '{',
                                Scope(), peek(1).addChild(pop()), Ws0()
                        ),
                        // Mixin rule set, with possible arguments
                        Sequence(
                                ClassSelectorGroup(), push(new RuleSetNode(pop())), Ws0(),
                                Parameters(), Ws0(),
                                '{',
                                Scope(), peek(1).addChild(pop()), peek(1).addChild(pop()), Ws0()
                        )
                ),
                '}', Ws0Nodes()
        );
    }

    /**
     * '(' Parameter Ws0 (',' Ws0 Parameter)* ')'
     */
    Rule Parameters() {
        return Sequence(
                '(',
                Parameter(), push(new ParametersNode(pop())), Ws0(),
                ZeroOrMore(
                        ',', Ws0(),
                        Parameter(), peek(1).addChild(pop())
                ),
                ')',
                push(new ScopeNode(pop()))
        );
    }

    /**
     * Variable Ws0 ':' Ws0 ExpressionPhrase
     */
    Rule Parameter() {
        return Sequence(
                Variable(), push(new VariableDefinitionNode(match())), peek().setVisible(!isParserTranslationEnabled()), Ws0(),
                ':', Ws0(),
                ExpressionPhrase(), peek(1).addChild(new ExpressionGroupNode(pop()))
        );
    }

    /**
     * SelectorGroup ';' Ws0
     * / Class Arguments ';' Ws0
     */
    @MemoMismatches
    Rule MixinReference() {
        Var<String> name = new Var<String>();
        return FirstOf(
                // No arguments, reference an existing rule set's properties
                Sequence(
                        SelectorGroup(), ';',
                        resolveMixinReference(NodeTreeUtils.getFirstChild((InternalNode) pop(), SelectorNode.class).toString(), null),
                        Ws0Nodes()
                ),
                // Call a mixin, passing along some arguments
                Sequence(
                        Class(), name.set(match()), Arguments(), Ws0(), ';',
                        resolveMixinReference(name.get(), (ArgumentsNode) pop()),
                        Ws0Nodes()
                )
        );
    }

    // ********** CSS Selectors **********

    /**
     * Selector Ws0 (',' Ws0 Selector)* Ws0
     */
    @MemoMismatches
    Rule SelectorGroup() {
        return Sequence(
                Selector(), push(new SelectorGroupNode(pop())), Ws0Nodes(),
                ZeroOrMore(
                        ',', Ws0Nodes(),
                        Selector(), peek(1).addChild(pop())
                ),
                Ws0Nodes()
        );
    }

    /**
     * Special case rule that builds a selector for only a single class
     */
    Rule ClassSelectorGroup() {
        return Sequence(
                Class(),
                push(new SelectorGroupNode(new SelectorNode(new SelectorSegmentNode("", match()))))
        );
    }

    /**
     * SimpleSelector (Combinator SimpleSelector)*
     */
    Rule Selector() {
        Var<SelectorSegmentNode> selectorSegmentNode = new Var<SelectorSegmentNode>();
        return Sequence(
                push(new SelectorNode()),
                // First selector segment may have a combinator (with nested rule sets)
                Optional(SymbolCombinator()),
                selectorSegmentNode.set(new SelectorSegmentNode(match())),
                SimpleSelector(selectorSegmentNode),
                selectorSegmentNode.get().setSimpleSelector(match()),
                peek().addChild(selectorSegmentNode.getAndClear()),
                // Additional selector segments must have a combinator
                ZeroOrMore(
                        Combinator(),
                        selectorSegmentNode.set(new SelectorSegmentNode(match())),
                        SimpleSelector(selectorSegmentNode),
                        selectorSegmentNode.get().setSimpleSelector(match()),
                        peek().addChild(selectorSegmentNode.getAndClear())
                )
        );
    }

    /**
     * Ws0 [+>~] Ws0 / Ws1
     */
    Rule Combinator() {
        return FirstOf(
                Sequence(Ws0(), SymbolCombinator()),
                DescendantCombinator()
        );
    }

    Rule SymbolCombinator() {
        return Sequence(AnyOf("+>~"), Ws0());
    }

    Rule DescendantCombinator() {
        return Ws1();
    }

    Rule SimpleSelector(Var<SelectorSegmentNode> selectorSegmentNode) {
        return FirstOf(
                Sequence(
                        FirstOf(
                                ID(), Class(), Attribute(), Negation(), ElementName(),
                                Sequence(
                                        FirstOf(
                                                UniversalHtml(),
                                                Universal()
                                        ),
                                        selectorSegmentNode.get().setUniversal(true)
                                ),
                                Sequence(
                                        Pseudo(),
                                        selectorSegmentNode.get().setSubElementSelector(true)
                                )
                        ),
                        ZeroOrMore(FirstOf(ID(), Class(), Attribute(), Negation(), Pseudo()))
                ),
                "@media",
                "@font-face"
        );
    }

    Rule Attribute() {
        return Sequence(
                '[', Ws0(),
                Ident(), Ws0(),
                Optional(
                        Optional(AnyOf("~|^$*")),
                        '=', Ws0(),
                        FirstOf(Ident(), String()), Ws0()
                ),
                ']'
        );
    }

    Rule Pseudo() {
        return Sequence(':', Optional(':' ), FirstOf(FunctionalPseudo(), Ident()));
    }

    Rule FunctionalPseudo() {
        return Sequence(Ident(), '(', Ws0(), PseudoExpression(), ')');
    }

    Rule PseudoExpression() {
        return OneOrMore(FirstOf(AnyOf("+-"), Dimension(), Digit(), String(), Ident()), Ws0());
    }

    Rule Negation() {
        return Sequence(":not(", Ws0(), NegationArgument(), Ws0(), ')');
    }

    Rule NegationArgument() {
        return FirstOf(ElementName(), Universal(), ID(), Class(), Attribute(), Pseudo());
    }

    // ********** Variables & Expressions **********

    /**
     * (Ident / Variable) Ws0 ':' Ws0 ExpressionPhrase (Ws0 ',' Ws0 ExpressionPhrase)* Sp0 (';' / Ws0 &'}')
     * / Ident Ws0 ':' Ws0 ';'
     *
     * Ex: @my-var: 12px; height: 100%;
     */
    Rule Declaration() {
        return FirstOf(
                Sequence(
                        FirstOf(
                                Sequence(PropertyName(), push(new PropertyNode(match()))),
                                Sequence(Variable(), push(new VariableDefinitionNode(match())), peek().setVisible(!isParserTranslationEnabled()))
                        ), Ws0(),
                        ':', Ws0(),
                        push(new ExpressionGroupNode()),
                        ExpressionPhrase(), peek(1).addChild(pop()),
                        ZeroOrMore(
                                Ws0(), ',', Ws0Nodes(),
                                ExpressionPhrase(), peek(1).addChild(pop())
                        ),
                        Sp0(), FirstOf(';', Sequence(Ws0(), Test('}' ))),
                        peek(1).addChild(pop())
                ),
                // Empty rules are ignored
                Sequence(
                        Ident(), push(new PlaceholderNode()), Ws0(),
                        ':', Ws0(),
                        ';'
                )
        );
    }

    Rule Variable() {
        return Sequence('@', Ident());
    }

    /**
     * Expression (Operator Expression)+ Future: Operations
     * / Expression (Ws1 Expression)* Important?
     */
    Rule ExpressionPhrase() {
        // Space-separated expressions
        return Sequence(
                Expression(), push(new ExpressionPhraseNode(pop())),
                ZeroOrMore(Ws1(), Expression(), peek(1).addChild(pop())),
                Optional(Ws0(), Important(), peek(1).addChild(pop()))
        );
    }

    /**
     * '(' Ws0 ExpressionPhrase Ws0 ')' Future: Operations
     * / Entity
     */
    Rule Expression() {
        return Sequence(Value(), push(new ExpressionNode(pop())));
    }

    /**
     * '!' Ws0 'important'
     */
    Rule Important() {
        return Sequence('!', Ws0(), "important", push(new SimpleNode("!important")));
    }

    // ********** Browser Hack Workarounds **********

    Rule PropertyName() {
        return Sequence(Optional(AnyOf("*_")), Ident());
    }

    Rule UniversalHtml() {
        return Sequence(Universal(), Ws0(), "html");
    }

    // ********** CSS Entities **********

    Rule ElementName() {
        return Ident();
    }

    Rule Universal() {
        return Ch('*' );
    }

    Rule ID() {
        return Sequence('#', Name());
    }

    @MemoMismatches
    Rule Class() {
        return Sequence('.', Ident());
    }

    // ********** Functions & Arguments **********

    /**
     * Ident Arguments
     */
    Rule Function() {
        return Sequence(
                Ident(), push(new FunctionNode(match())),
                Arguments(), peek(1).addChild(pop())
        );
    }

    /**
     * '(' Ws0 ExpressionPhrase Ws0 (',' Ws0 ExpressionPhrase Ws0)* ')' / '(' Ws0 ')'
     */
    Rule Arguments() {
        return FirstOf(
                Sequence(
                        Ws0(), '(', Ws0(),
                        ExpressionPhrase(), push(new ArgumentsNode(new ExpressionGroupNode(pop()))),
                        Ws0(),
                        ZeroOrMore(
                                ',', Ws0(),
                                ExpressionPhrase(), peek(1).addChild(new ExpressionGroupNode(pop())),
                                Ws0()
                        ),
                        ')'
                ),
                Sequence(Ws0(), '(', Ws0(), ')', push(new ArgumentsNode()))
        );
    }

    /**
     * Ex: progid:DXImageTransform.Microsoft.gradient(startColorstr=@lightColor, endColorstr=@darkColor)
     */
    Rule FilterFunction() {
        return Sequence(
                Sequence(
                        "progid:",
                        OneOrMore(FirstOf('.', Ident()))
                ),
                push(new FunctionNode(match())),
                FilterArguments(),
                peek(1).addChild(pop())
        );
    }

    /**
     * '(' Ws0 FilterArgument Ws0 (',' Ws0 FilterArgument Ws0)* ')' / '(' Ws0 ')'
     */
    Rule FilterArguments() {
        return FirstOf(
                Sequence(
                        '(', Ws0(),
                        FilterArgument(), push(new ArgumentsNode(pop())),
                        Ws0(),
                        ZeroOrMore(
                                ',', Ws0(),
                                FilterArgument(), peek(1).addChild(pop()),
                                Ws0()
                        ),
                        ')'
                ),
                Sequence('(', Ws0(), ')', push(new ArgumentsNode()))
        );
    }

    /**
     * Ex: startColorstr=@lightColor
     */
    Rule FilterArgument() {
        return Sequence(
                Ident(), push(new FilterArgumentNode(match())), Ws0(),
                '=', Ws0(),
                Value(), peek(1).addChild(pop())
        );
    }

    // ********** LESS Entities **********

    /**
     * Any token used as a value in an expression
     * Future: Accessors
     */
    Rule Value() {
        return FirstOf(
                Keyword(),
                Literal(),
                Function(),
                VariableReference(),
                URL(),
                Font(),
                AlphaFilter(),
                ExpressionFunction(),
                FilterFunction()
        );
    }

    @MemoMismatches
    Rule VariableReference() {
        return Sequence(
                Variable(),
                pushVariableReference(match())
        );
    }

    Rule MediaQueryDefinition() {
        return Sequence(
                "@media",
                push(new SimpleNode(match()))
        );
    }

    /**
     * 'url(' (String / [-_%$/.&=:;#+?Alphanumeric]+) ')'
     */
    Rule URL() {
        return Sequence(
                Sequence(
                        "url(", Ws0(),
                        FirstOf(
                                String(),
                                OneOrMore(FirstOf(AnyOf("-_%$/.&=:;#+?"), Alphanumeric()))
                        ), Ws0(),
                        ')'
                ),
                push(new SimpleNode(match()))
        );
    }

    /**
     * 'alpha(' Ws0 'opacity' Ws0 '=' Ws0 Digit1 Ws0 ')'
     */
    Rule AlphaFilter() {
        return Sequence(
                Sequence(
                        "alpha(", Ws0(),
                        "opacity", Ws0(),
                        '=', Ws0(),
                        Digit1(), Ws0(),
                        ')'
                ),
                push(new SimpleNode(match()))
        );
    }

    /**
     * 'expression(' (!(')' Ws0 [;}]) .)* ');'
     */
    Rule ExpressionFunction() {
        return Sequence(
                Sequence(
                        "expression(",
                        ZeroOrMore(
                                TestNot(
                                        ')',
                                        Ws0(),
                                        AnyOf(";}")
                                ),
                                ANY
                        ),
                        ')'
                ),
                push(new SimpleNode(match()))
        );
    }

    /**
     * Ident &Delimiter
     *
     * Ex: blue, small, normal
     */
    Rule Keyword() {
        return Sequence(
                Sequence(Ident(), Test(Delimiter())),
                push(new SimpleNode(match()))
        );
    }

    /**
     * Tokens that don't need to evaluated
     */
    Rule Literal() {
        return Sequence(
                FirstOf(Color(), MultiDimension(), Dimension(), String()),
                push(new SimpleNode(match()))
        );
    }

    /**
     * Alpha [-Alphanumeric]* &Delimiter / String
     */
    Rule Font() {
        return Sequence(
                FirstOf(
                        Sequence(Alpha(), ZeroOrMore(FirstOf('-', Alphanumeric())), Test(Delimiter())),
                        String()
                ),
                push(new SimpleNode(match()))
        );
    }

    /**
     * Ex: 'hello' / "hello"
     */
    Rule String() {
        return FirstOf(
                Sequence('\'', ZeroOrMore(TestNot('\'' ), ANY), '\'' ),
                Sequence('"', ZeroOrMore(TestNot('"' ), ANY), '"' )
        );
    }

    /**
     * Some CSS properties allow multiple dimensions separated by '/'
     *
     * (Dimension / Ident) '/' Dimension
     */
    Rule MultiDimension() {
        return Sequence(
                FirstOf(
                        Dimension(),
                        Ident()
                ),
                '/',
                Dimension()
        );
    }

    /**
     * Number Unit
     */
    @MemoMismatches
    Rule Dimension() {
        return Sequence(Number(), Unit());
    }

    /**
     * '-'? Digit* '.' Digit+ / '-'? Digit+
     */
    Rule Number() {
        return FirstOf(
                Sequence(Optional('-'), Digit0(), '.', Digit1()),
                Sequence(Optional('-'), Digit1())
        );
    }

    Rule Unit() {
        return Optional(FirstOf('%', Ident()));
    }

    /**
     * '#' RGB
     */
    Rule Color() {
        return Sequence('#', RGB());
    }

    /**
     * Ex: 0099dd / 09d
     */
    Rule RGB() {
        return FirstOf(
                Sequence(Hex(), Hex(), Hex(), Hex(), Hex(), Hex()),
                Sequence(Hex(), Hex(), Hex())
        );
    }

    // ********** Comments **********

    Rule Comment() {
        return FirstOf(MultipleLineComment(), SingleLineComment());
    }

    /**
     * '//' (!LB .)* LB Ws0
     */
    Rule SingleLineComment() {
        return Sequence(
                "//", ZeroOrMore(TestNot(AnyOf("\r\n")), ANY),
                FirstOf('\n', "\r\n"), Ws0()
        );
    }

    /**
     * '/*' (!'*\/' .)* '*\/' Ws0
     */
    Rule MultipleLineComment() {
        return Sequence(
                "/*", ZeroOrMore(TestNot("*/"), ANY),
                "*/", Ws0()
        );
    }

    // ********** Characters & Simple Character Groups **********

    Rule Alphanumeric() {
        return FirstOf(Alpha(), Digit());
    }

    Rule Alpha() {
        return FirstOf(CharRange('a', 'z' ), CharRange('A', 'Z' ));
    }

    Rule Digit0() {
        return ZeroOrMore(Digit());
    }

    Rule Digit1() {
        return OneOrMore(Digit());
    }

    Rule Digit() {
        return CharRange('0', '9' );
    }

    Rule Hex() {
        return FirstOf(CharRange('a', 'f' ), CharRange('A', 'F' ), Digit());
    }

    Rule Ws0() {
        return ZeroOrMore(Whitespace());
    }

    Rule Ws0Nodes() {
        return ZeroOrMore(WhitespaceNode());
    }

    Rule Ws1() {
        return OneOrMore(Whitespace());
    }

    Rule Whitespace() {
        return AnyOf(" \r\n\t\f");
    }

    Rule WhitespaceNode() {
        return FirstOf(
                Sequence(OneOrMore(AnyOf(" \t")), peek().addChild(new SpacingNode(match()))),
                Sequence(FirstOf('\n', "\r\n", '\r', '\f' ), peek().addChild(new LineBreakNode(1)))
        );
    }

    Rule Sp0() {
        return ZeroOrMore(Spacing());
    }

    Rule Sp1() {
        return OneOrMore(Spacing());
    }

    Rule Sp1Nodes() {
        return OneOrMore(SpacingNode());
    }

    Rule Spacing() {
        return FirstOf(Whitespace(), Comment());
    }

    Rule SpacingNode() {
        return FirstOf(
                WhitespaceNode(),
                Sequence(Comment(), peek().addChild(new SpacingNode(match())))
        );
    }

    Rule Delimiter() {
        return FirstOf(AnyOf(";,!})"), Whitespace());
    }

    Rule NameStart() {
        return FirstOf('_', Alpha(), NonAscii(), Escape());
    }

    Rule NameCharacter() {
        return FirstOf(AnyOf("-_"), Alphanumeric(), NonAscii(), Escape());
    }

    /**
     * nonascii [^\0-\177]
     */
    Rule NonAscii() {
        return Sequence(TestNot(CharRange((char) 0, (char) 127)), ANY);
    }

    /**
     * '\' [0-9a-fA-F]{1,6} wc?
     */
    Rule Unicode() {
        return Sequence(
                '\\',
                Hex(), Optional(Hex(), Optional(Hex(), Optional(Hex(), Optional(Hex(), Optional(Hex()))))),
                Optional(Whitespace())
        );
    }

    /**
     * unicode | '\' [^\x0-\x1F]
     */
    Rule Escape() {
        return Sequence(
                Test('\\'), // for performance
                FirstOf(
                        Unicode(),
                        Sequence('\\', Sequence(TestNot(CharRange((char) 0, (char) 31)), ANY))
                )
        );
    }

    @MemoMismatches
    Rule Ident() {
        return Sequence(Optional('-'), NameStart(), ZeroOrMore(NameCharacter()));
    }

    Rule Name() {
        return OneOrMore(NameCharacter());
    }

    // ********** Translation Actions **********

    /**
     * Locates the referenced mixin in one of the scope nodes on the stack. If found, the mixin's
     * scope is cloned and placed onto the stack in place of the mixin reference. Additionally,
     * any arguments are applied to the mixin's scope.
     */
    boolean resolveMixinReference(String name, ArgumentsNode arguments) {
        if (!isParserTranslationEnabled()) {
            return push(new PlaceholderNode(new SimpleNode(name)));
        }

        // Walk down the stack, looking for a scope node that knows about a given rule set
        for (Node node : getContext().getValueStack()) {
            if (!(node instanceof ScopeNode)) {
                continue;
            }

            ScopeNode scope = (ScopeNode) node;
            RuleSetNode ruleSet = scope.getRuleSet(name);

            if (ruleSet == null) {
                continue;
            }

            // Get the scope of the rule set we located and call it as a mixin
            ScopeNode ruleSetScope = NodeTreeUtils.getFirstChild(ruleSet, ScopeNode.class).callMixin(name, arguments);

            return push(ruleSetScope);
        }

        // Record error location
        throw new UndefinedMixinException(name);
    }

    /**
     * Looks for a variable definition that matches the reference in the scope nodes on the stack.
     * If found, a reference node that can repeat this lookup later is placed on the stack, not the
     * current value itself. This is done because the value may change if the variable reference is
     * inside a mixin.
     */
    boolean pushVariableReference(String name) {
        if (!isParserTranslationEnabled()) {
            return push(new SimpleNode(name));
        }

        // Walk down the stack, looking for a scope node that knows about a given variable
        for (Node node : getContext().getValueStack()) {
            if (!(node instanceof ScopeNode)) {
                continue;
            }

            // Ensure that the variable exists
            ScopeNode scope = (ScopeNode) node;
            if (!scope.isVariableDefined(name)) {
                continue;
            }

            return push(new VariableReferenceNode(name));
        }

        // Record error location
        throw new UndefinedVariableException(name);
    }

    // ********** Debugging **********

    boolean debug(Context context) {
        return true;
    }
}


package com.hibegin.common.util;

import com.hibegin.http.server.util.PathUtil;

import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.logging.FileHandler;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.logging.SimpleFormatter;

public class LoggerUtil {

    private static final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
    private static final String LOG_FOLDER_NAME = "log";
    private static final String LOG_FILE_SUFFIX = ".log";
    private static FileHandler fileHandler;
    private static final Logger LOGGER = LoggerUtil.getLogger(LoggerUtil.class);

    static {
        try {
            fileHandler = new FileHandler(getLogFilePath(), true);
            fileHandler.setFormatter(new SimpleFormatter());
        } catch (IOException e) {
            LOGGER.log(Level.SEVERE, "", e);
        }
    }

    private LoggerUtil() {
    }

    public static Logger getLogger(Class clazz) {
        Logger logger = Logger.getLogger(clazz.getName());
        try {
            if (fileHandler != null) {
                logger.addHandler(fileHandler);
            }
            logger.setLevel(Level.ALL);
        } catch (Exception e) {
            LOGGER.log(Level.SEVERE, recordStackTraceMsg(e));
        }
        return logger;
    }

    private synchronized static String getLogFilePath() {
        StringBuilder logFilePath = new StringBuilder();
        logFilePath.append(PathUtil.getRootPath());
        logFilePath.append(File.separatorChar);
        logFilePath.append(LOG_FOLDER_NAME);

        File file = new File(logFilePath.toString());
        if (!file.exists()) {
            file.mkdir();
        }

        logFilePath.append(File.separatorChar);
        logFilePath.append(sdf.format(new Date()));
        logFilePath.append(LOG_FILE_SUFFIX);

        return logFilePath.toString();
    }

    /**
     * 记录完善的异常日志信息(包括堆栈信息)
     *
     * @param e Exception
     */
    public static String recordStackTraceMsg(Exception e) {
        StringWriter stringWriter = new StringWriter();
        PrintWriter writer = new PrintWriter(stringWriter);
        e.printStackTrace(writer);
        StringBuffer buffer = stringWriter.getBuffer();
        return buffer.toString();
    }
}