/*
 * LZMAEncoderNormal
 *
 * Authors: Lasse Collin <lasse.collin@tukaani.org>
 *          Igor Pavlov <http://7-zip.org/>
 *
 * This file has been put into the public domain.
 * You can do whatever you want with this file.
 */

package org.tukaani.xz.lzma;

import org.tukaani.xz.ArrayCache;
import org.tukaani.xz.lz.LZEncoder;
import org.tukaani.xz.lz.Matches;
import org.tukaani.xz.rangecoder.RangeEncoder;

final class LZMAEncoderNormal extends LZMAEncoder {
    private static final int OPTS = 4096;

    private static final int EXTRA_SIZE_BEFORE = OPTS;
    private static final int EXTRA_SIZE_AFTER = OPTS;

    private final Optimum[] opts = new Optimum[OPTS];
    private int optCur = 0;
    private int optEnd = 0;

    private Matches matches;

    // These are fields solely to avoid allocating the objects again and
    // again on each function call.
    private final int[] repLens = new int[REPS];
    private final State nextState = new State();

    static int getMemoryUsage(int dictSize, int extraSizeBefore, int mf) {
        return LZEncoder.getMemoryUsage(dictSize,
                   Math.max(extraSizeBefore, EXTRA_SIZE_BEFORE),
                   EXTRA_SIZE_AFTER, MATCH_LEN_MAX, mf)
               + OPTS * 64 / 1024;
    }

    LZMAEncoderNormal(RangeEncoder rc, int lc, int lp, int pb,
                             int dictSize, int extraSizeBefore,
                             int niceLen, int mf, int depthLimit,
                             ArrayCache arrayCache) {
        super(rc, LZEncoder.getInstance(dictSize,
                                        Math.max(extraSizeBefore,
                                                 EXTRA_SIZE_BEFORE),
                                        EXTRA_SIZE_AFTER,
                                        niceLen, MATCH_LEN_MAX,
                                        mf, depthLimit, arrayCache),
              lc, lp, pb, dictSize, niceLen);

        for (int i = 0; i < OPTS; ++i)
            opts[i] = new Optimum();
    }

    public void reset() {
        optCur = 0;
        optEnd = 0;
        super.reset();
    }

    /**
     * Converts the opts array from backward indexes to forward indexes.
     * Then it will be simple to get the next symbol from the array
     * in later calls to getNextSymbol().
     */
    private int convertOpts() {
        optEnd = optCur;

        int optPrev = opts[optCur].optPrev;

        do {
            Optimum opt = opts[optCur];

            if (opt.prev1IsLiteral) {
                opts[optPrev].optPrev = optCur;
                opts[optPrev].backPrev = -1;
                optCur = optPrev--;

                if (opt.hasPrev2) {
                    opts[optPrev].optPrev = optPrev + 1;
                    opts[optPrev].backPrev = opt.backPrev2;
                    optCur = optPrev;
                    optPrev = opt.optPrev2;
                }
            }

            int temp = opts[optPrev].optPrev;
            opts[optPrev].optPrev = optCur;
            optCur = optPrev;
            optPrev = temp;
        } while (optCur > 0);

        optCur = opts[0].optPrev;
        back = opts[optCur].backPrev;
        return optCur;
    }

    int getNextSymbol() {
        // If there are pending symbols from an earlier call to this
        // function, return those symbols first.
        if (optCur < optEnd) {
            int len = opts[optCur].optPrev - optCur;
            optCur = opts[optCur].optPrev;
            back = opts[optCur].backPrev;
            return len;
        }

        assert optCur == optEnd;
        optCur = 0;
        optEnd = 0;
        back = -1;

        if (readAhead == -1)
            matches = getMatches();

        // Get the number of bytes available in the dictionary, but
        // not more than the maximum match length. If there aren't
        // enough bytes remaining to encode a match at all, return
        // immediately to encode this byte as a literal.
        int avail = Math.min(lz.getAvail(), MATCH_LEN_MAX);
        if (avail < MATCH_LEN_MIN)
            return 1;

        // Get the lengths of repeated matches.
        int repBest = 0;
        for (int rep = 0; rep < REPS; ++rep) {
            repLens[rep] = lz.getMatchLen(reps[rep], avail);

            if (repLens[rep] < MATCH_LEN_MIN) {
                repLens[rep] = 0;
                continue;
            }

            if (repLens[rep] > repLens[repBest])
                repBest = rep;
        }

        // Return if the best repeated match is at least niceLen bytes long.
        if (repLens[repBest] >= niceLen) {
            back = repBest;
            skip(repLens[repBest] - 1);
            return repLens[repBest];
        }

        // Initialize mainLen and mainDist to the longest match found
        // by the match finder.
        int mainLen = 0;
        int mainDist = 0;
        if (matches.count > 0) {
            mainLen = matches.len[matches.count - 1];
            mainDist = matches.dist[matches.count - 1];

            // Return if it is at least niceLen bytes long.
            if (mainLen >= niceLen) {
                back = mainDist + REPS;
                skip(mainLen - 1);
                return mainLen;
            }
        }

        int curByte = lz.getByte(0);
        int matchByte = lz.getByte(reps[0] + 1);

        // If the match finder found no matches and this byte cannot be
        // encoded as a repeated match (short or long), we must be return
        // to have the byte encoded as a literal.
        if (mainLen < MATCH_LEN_MIN && curByte != matchByte
                && repLens[repBest] < MATCH_LEN_MIN)
            return 1;

        int pos = lz.getPos();
        int posState = pos & posMask;

        // Calculate the price of encoding the current byte as a literal.
        {
            int prevByte = lz.getByte(1);
            int literalPrice = literalEncoder.getPrice(curByte, matchByte,
                                                       prevByte, pos, state);
            opts[1].set1(literalPrice, 0, -1);
        }

        int anyMatchPrice = getAnyMatchPrice(state, posState);
        int anyRepPrice = getAnyRepPrice(anyMatchPrice, state);

        // If it is possible to encode this byte as a short rep, see if
        // it is cheaper than encoding it as a literal.
        if (matchByte == curByte) {
            int shortRepPrice = getShortRepPrice(anyRepPrice,
                                                 state, posState);
            if (shortRepPrice < opts[1].price)
                opts[1].set1(shortRepPrice, 0, 0);
        }

        // Return if there is neither normal nor long repeated match. Use
        // a short match instead of a literal if is is possible and cheaper.
        optEnd = Math.max(mainLen, repLens[repBest]);
        if (optEnd < MATCH_LEN_MIN) {
            assert optEnd == 0 : optEnd;
            back = opts[1].backPrev;
            return 1;
        }

        // Update the lookup tables for distances and lengths before using
        // those price calculation functions. (The price function above
        // don't need these tables.)
        updatePrices();

        // Initialize the state and reps of this position in opts[].
        // updateOptStateAndReps() will need these to get the new
        // state and reps for the next byte.
        opts[0].state.set(state);
        System.arraycopy(reps, 0, opts[0].reps, 0, REPS);

        // Initialize the prices for latter opts that will be used below.
        for (int i = optEnd; i >= MATCH_LEN_MIN; --i)
            opts[i].reset();

        // Calculate the prices of repeated matches of all lengths.
        for (int rep = 0; rep < REPS; ++rep) {
            int repLen = repLens[rep];
            if (repLen < MATCH_LEN_MIN)
                continue;

            int longRepPrice = getLongRepPrice(anyRepPrice, rep,
                                               state, posState);
            do {
                int price = longRepPrice + repLenEncoder.getPrice(repLen,
                                                                  posState);
                if (price < opts[repLen].price)
                    opts[repLen].set1(price, 0, rep);
            } while (--repLen >= MATCH_LEN_MIN);
        }

        // Calculate the prices of normal matches that are longer than rep0.
        {
            int len = Math.max(repLens[0] + 1, MATCH_LEN_MIN);
            if (len <= mainLen) {
                int normalMatchPrice = getNormalMatchPrice(anyMatchPrice,
                                                           state);

                // Set i to the index of the shortest match that is
                // at least len bytes long.
                int i = 0;
                while (len > matches.len[i])
                    ++i;

                while (true) {
                    int dist = matches.dist[i];
                    int price = getMatchAndLenPrice(normalMatchPrice,
                                                    dist, len, posState);
                    if (price < opts[len].price)
                        opts[len].set1(price, 0, dist + REPS);

                    if (len == matches.len[i])
                        if (++i == matches.count)
                            break;

                    ++len;
                }
            }
        }

        avail = Math.min(lz.getAvail(), OPTS - 1);

        // Get matches for later bytes and optimize the use of LZMA symbols
        // by calculating the prices and picking the cheapest symbol
        // combinations.
        while (++optCur < optEnd) {
            matches = getMatches();
            if (matches.count > 0
                    && matches.len[matches.count - 1] >= niceLen)
                break;

            --avail;
            ++pos;
            posState = pos & posMask;

            updateOptStateAndReps();
            anyMatchPrice = opts[optCur].price
                            + getAnyMatchPrice(opts[optCur].state, posState);
            anyRepPrice = getAnyRepPrice(anyMatchPrice, opts[optCur].state);

            calc1BytePrices(pos, posState, avail, anyRepPrice);

            if (avail >= MATCH_LEN_MIN) {
                int startLen = calcLongRepPrices(pos, posState,
                                                 avail, anyRepPrice);
                if (matches.count > 0)
                    calcNormalMatchPrices(pos, posState, avail,
                                          anyMatchPrice, startLen);
            }
        }

        return convertOpts();
    }

    /**
     * Updates the state and reps for the current byte in the opts array.
     */
    private void updateOptStateAndReps() {
        int optPrev = opts[optCur].optPrev;
        assert optPrev < optCur;

        if (opts[optCur].prev1IsLiteral) {
            --optPrev;

            if (opts[optCur].hasPrev2) {
                opts[optCur].state.set(opts[opts[optCur].optPrev2].state);
                if (opts[optCur].backPrev2 < REPS)
                    opts[optCur].state.updateLongRep();
                else
                    opts[optCur].state.updateMatch();
            } else {
                opts[optCur].state.set(opts[optPrev].state);
            }

            opts[optCur].state.updateLiteral();
        } else {
            opts[optCur].state.set(opts[optPrev].state);
        }

        if (optPrev == optCur - 1) {
            // Must be either a short rep or a literal.
            assert opts[optCur].backPrev == 0 || opts[optCur].backPrev == -1;

            if (opts[optCur].backPrev == 0)
                opts[optCur].state.updateShortRep();
            else
                opts[optCur].state.updateLiteral();

            System.arraycopy(opts[optPrev].reps, 0,
                             opts[optCur].reps, 0, REPS);
        } else {
            int back;
            if (opts[optCur].prev1IsLiteral && opts[optCur].hasPrev2) {
                optPrev = opts[optCur].optPrev2;
                back = opts[optCur].backPrev2;
                opts[optCur].state.updateLongRep();
            } else {
                back = opts[optCur].backPrev;
                if (back < REPS)
                    opts[optCur].state.updateLongRep();
                else
                    opts[optCur].state.updateMatch();
            }

            if (back < REPS) {
                opts[optCur].reps[0] = opts[optPrev].reps[back];

                int rep;
                for (rep = 1; rep <= back; ++rep)
                    opts[optCur].reps[rep] = opts[optPrev].reps[rep - 1];

                for (; rep < REPS; ++rep)
                    opts[optCur].reps[rep] = opts[optPrev].reps[rep];
            } else {
                opts[optCur].reps[0] = back - REPS;
                System.arraycopy(opts[optPrev].reps, 0,
                                 opts[optCur].reps, 1, REPS - 1);
            }
        }
    }

    /**
     * Calculates prices of a literal, a short rep, and literal + rep0.
     */
    private void calc1BytePrices(int pos, int posState,
                                 int avail, int anyRepPrice) {
        // This will be set to true if using a literal or a short rep.
        boolean nextIsByte = false;

        int curByte = lz.getByte(0);
        int matchByte = lz.getByte(opts[optCur].reps[0] + 1);

        // Try a literal.
        int literalPrice = opts[optCur].price
                + literalEncoder.getPrice(curByte, matchByte, lz.getByte(1),
                                          pos, opts[optCur].state);
        if (literalPrice < opts[optCur + 1].price) {
            opts[optCur + 1].set1(literalPrice, optCur, -1);
            nextIsByte = true;
        }

        // Try a short rep.
        if (matchByte == curByte && (opts[optCur + 1].optPrev == optCur
                                      || opts[optCur + 1].backPrev != 0)) {
            int shortRepPrice = getShortRepPrice(anyRepPrice,
                                                 opts[optCur].state,
                                                 posState);
            if (shortRepPrice <= opts[optCur + 1].price) {
                opts[optCur + 1].set1(shortRepPrice, optCur, 0);
                nextIsByte = true;
            }
        }

        // If neither a literal nor a short rep was the cheapest choice,
        // try literal + long rep0.
        if (!nextIsByte && matchByte != curByte && avail > MATCH_LEN_MIN) {
            int lenLimit = Math.min(niceLen, avail - 1);
            int len = lz.getMatchLen(1, opts[optCur].reps[0], lenLimit);

            if (len >= MATCH_LEN_MIN) {
                nextState.set(opts[optCur].state);
                nextState.updateLiteral();
                int nextPosState = (pos + 1) & posMask;
                int price = literalPrice
                            + getLongRepAndLenPrice(0, len,
                                                    nextState, nextPosState);

                int i = optCur + 1 + len;
                while (optEnd < i)
                    opts[++optEnd].reset();

                if (price < opts[i].price)
                    opts[i].set2(price, optCur, 0);
            }
        }
    }

    /**
     * Calculates prices of long rep and long rep + literal + rep0.
     */
    private int calcLongRepPrices(int pos, int posState,
                                  int avail, int anyRepPrice) {
        int startLen = MATCH_LEN_MIN;
        int lenLimit = Math.min(avail, niceLen);

        for (int rep = 0; rep < REPS; ++rep) {
            int len = lz.getMatchLen(opts[optCur].reps[rep], lenLimit);
            if (len < MATCH_LEN_MIN)
                continue;

            while (optEnd < optCur + len)
                opts[++optEnd].reset();

            int longRepPrice = getLongRepPrice(anyRepPrice, rep,
                                               opts[optCur].state, posState);

            for (int i = len; i >= MATCH_LEN_MIN; --i) {
                int price = longRepPrice
                            + repLenEncoder.getPrice(i, posState);
                if (price < opts[optCur + i].price)
                    opts[optCur + i].set1(price, optCur, rep);
            }

            if (rep == 0)
                startLen = len + 1;

            int len2Limit = Math.min(niceLen, avail - len - 1);
            int len2 = lz.getMatchLen(len + 1, opts[optCur].reps[rep],
                                      len2Limit);

            if (len2 >= MATCH_LEN_MIN) {
                // Rep
                int price = longRepPrice
                            + repLenEncoder.getPrice(len, posState);
                nextState.set(opts[optCur].state);
                nextState.updateLongRep();

                // Literal
                int curByte = lz.getByte(len, 0);
                int matchByte = lz.getByte(0); // lz.getByte(len, len)
                int prevByte = lz.getByte(len, 1);
                price += literalEncoder.getPrice(curByte, matchByte, prevByte,
                                                 pos + len, nextState);
                nextState.updateLiteral();

                // Rep0
                int nextPosState = (pos + len + 1) & posMask;
                price += getLongRepAndLenPrice(0, len2,
                                               nextState, nextPosState);

                int i = optCur + len + 1 + len2;
                while (optEnd < i)
                    opts[++optEnd].reset();

                if (price < opts[i].price)
                    opts[i].set3(price, optCur, rep, len, 0);
            }
        }

        return startLen;
    }

    /**
     * Calculates prices of a normal match and normal match + literal + rep0.
     */
    private void calcNormalMatchPrices(int pos, int posState, int avail,
                                       int anyMatchPrice, int startLen) {
        // If the longest match is so long that it would not fit into
        // the opts array, shorten the matches.
        if (matches.len[matches.count - 1] > avail) {
            matches.count = 0;
            while (matches.len[matches.count] < avail)
                ++matches.count;

            matches.len[matches.count++] = avail;
        }

        if (matches.len[matches.count - 1] < startLen)
            return;

        while (optEnd < optCur + matches.len[matches.count - 1])
            opts[++optEnd].reset();

        int normalMatchPrice = getNormalMatchPrice(anyMatchPrice,
                                                   opts[optCur].state);

        int match = 0;
        while (startLen > matches.len[match])
            ++match;

        for (int len = startLen; ; ++len) {
            int dist = matches.dist[match];

            // Calculate the price of a match of len bytes from the nearest
            // possible distance.
            int matchAndLenPrice = getMatchAndLenPrice(normalMatchPrice,
                                                       dist, len, posState);
            if (matchAndLenPrice < opts[optCur + len].price)
                opts[optCur + len].set1(matchAndLenPrice,
                                        optCur, dist + REPS);

            if (len != matches.len[match])
                continue;

            // Try match + literal + rep0. First get the length of the rep0.
            int len2Limit = Math.min(niceLen, avail - len - 1);
            int len2 = lz.getMatchLen(len + 1, dist, len2Limit);

            if (len2 >= MATCH_LEN_MIN) {
                nextState.set(opts[optCur].state);
                nextState.updateMatch();

                // Literal
                int curByte = lz.getByte(len, 0);
                int matchByte = lz.getByte(0); // lz.getByte(len, len)
                int prevByte = lz.getByte(len, 1);
                int price = matchAndLenPrice
                        + literalEncoder.getPrice(curByte, matchByte,
                                                  prevByte, pos + len,
                                                  nextState);
                nextState.updateLiteral();

                // Rep0
                int nextPosState = (pos + len + 1) & posMask;
                price += getLongRepAndLenPrice(0, len2,
                                               nextState, nextPosState);

                int i = optCur + len + 1 + len2;
                while (optEnd < i)
                    opts[++optEnd].reset();

                if (price < opts[i].price)
                    opts[i].set3(price, optCur, dist + REPS, len, 0);
            }

            if (++match == matches.count)
                break;
        }
    }
}


/*
 * LZMA2InputStream
 *
 * Authors: Lasse Collin <lasse.collin@tukaani.org>
 *          Igor Pavlov <http://7-zip.org/>
 *
 * This file has been put into the public domain.
 * You can do whatever you want with this file.
 */

package org.tukaani.xz;

import java.io.InputStream;
import java.io.DataInputStream;
import java.io.IOException;
import org.tukaani.xz.lz.LZDecoder;
import org.tukaani.xz.rangecoder.RangeDecoderFromBuffer;
import org.tukaani.xz.lzma.LZMADecoder;

/**
 * Decompresses a raw LZMA2 stream (no XZ headers).
 */
public class LZMA2InputStream extends InputStream {
    /**
     * Smallest valid LZMA2 dictionary size.
     *

     * Very tiny dictionaries would be a performance problem, so
     * the minimum is 4 KiB.
     */
    public static final int DICT_SIZE_MIN = 4096;

    /**
     * Largest dictionary size supported by this implementation.
     *

     * The LZMA2 algorithm allows dictionaries up to one byte less than 4 GiB.
     * This implementation supports only 16 bytes less than 2 GiB for raw
     * LZMA2 streams, and for .xz files the maximum is 1.5 GiB. This
     * limitation is due to Java using signed 32-bit integers for array
     * indexing. The limitation shouldn't matter much in practice since so
     * huge dictionaries are not normally used.
     */
    public static final int DICT_SIZE_MAX = Integer.MAX_VALUE & ~15;

    private static final int COMPRESSED_SIZE_MAX = 1 << 16;

    private final ArrayCache arrayCache;
    private DataInputStream in;

    private LZDecoder lz;
    private RangeDecoderFromBuffer rc;
    private LZMADecoder lzma;

    private int uncompressedSize = 0;
    private boolean isLZMAChunk = false;

    private boolean needDictReset = true;
    private boolean needProps = true;
    private boolean endReached = false;

    private IOException exception = null;

    private final byte[] tempBuf = new byte[1];

    /**
     * Gets approximate decompressor memory requirements as kibibytes for
     * the given dictionary size.
     *
     * @param       dictSize    LZMA2 dictionary size as bytes, must be
     *                          in the range [DICT_SIZE_MIN,
     *                          DICT_SIZE_MAX]
     *
     * @return      approximate memory requirements as kibibytes (KiB)
     */
    public static int getMemoryUsage(int dictSize) {
        // The base state is around 30-40 KiB (probabilities etc.),
        // range decoder needs COMPRESSED_SIZE_MAX bytes for buffering,
        // and LZ decoder needs a dictionary buffer.
        return 40 + COMPRESSED_SIZE_MAX / 1024 + getDictSize(dictSize) / 1024;
    }

    private static int getDictSize(int dictSize) {
        if (dictSize < DICT_SIZE_MIN || dictSize > DICT_SIZE_MAX)
            throw new IllegalArgumentException(
                    "Unsupported dictionary size " + dictSize);

        // Round dictionary size upward to a multiple of 16. This way LZMA
        // can use LZDecoder.getPos() for calculating LZMA's posMask.
        // Note that this check is needed only for raw LZMA2 streams; it is
        // redundant with .xz.
        return (dictSize + 15) & ~15;
    }

    /**
     * Creates a new input stream that decompresses raw LZMA2 data
     * from in.
     *

     * The caller needs to know the dictionary size used when compressing;
     * the dictionary size isn't stored as part of a raw LZMA2 stream.
     *

     * Specifying a too small dictionary size will prevent decompressing
     * the stream. Specifying a too big dictionary is waste of memory but
     * decompression will work.
     *

     * There is no need to specify a dictionary bigger than
     * the uncompressed size of the data even if a bigger dictionary
     * was used when compressing. If you know the uncompressed size
     * of the data, this might allow saving some memory.
     *
     * @param       in          input stream from which LZMA2-compressed
     *                          data is read
     *
     * @param       dictSize    LZMA2 dictionary size as bytes, must be
     *                          in the range [DICT_SIZE_MIN,
     *                          DICT_SIZE_MAX]
     */
    public LZMA2InputStream(InputStream in, int dictSize) {
        this(in, dictSize, null);
    }

    /**
     * Creates a new LZMA2 decompressor using a preset dictionary.
     *

     * This is like LZMA2InputStream(InputStream, int) except
     * that the dictionary may be initialized using a preset dictionary.
     * If a preset dictionary was used when compressing the data, the
     * same preset dictionary must be provided when decompressing.
     *
     * @param       in          input stream from which LZMA2-compressed
     *                          data is read
     *
     * @param       dictSize    LZMA2 dictionary size as bytes, must be
     *                          in the range [DICT_SIZE_MIN,
     *                          DICT_SIZE_MAX]
     *
     * @param       presetDict  preset dictionary or null
     *                          to use no preset dictionary
     */
    public LZMA2InputStream(InputStream in, int dictSize, byte[] presetDict) {
        this(in, dictSize, presetDict, ArrayCache.getDefaultCache());
    }

    /**
     * Creates a new LZMA2 decompressor using a preset dictionary
     * and array cache.
     *

     * This is like LZMA2InputStream(InputStream, int, byte[])
     * except that this also takes the arrayCache argument.
     *
     * @param       in          input stream from which LZMA2-compressed
     *                          data is read
     *
     * @param       dictSize    LZMA2 dictionary size as bytes, must be
     *                          in the range [DICT_SIZE_MIN,
     *                          DICT_SIZE_MAX]
     *
     * @param       presetDict  preset dictionary or null
     *                          to use no preset dictionary
     *
     * @param       arrayCache  cache to be used for allocating large arrays
     *
     * @since 1.7
     */
    LZMA2InputStream(InputStream in, int dictSize, byte[] presetDict,
                     ArrayCache arrayCache) {
        // Check for null because otherwise null isn't detect
        // in this constructor.
        if (in == null)
            throw new NullPointerException();

        this.arrayCache = arrayCache;
        this.in = new DataInputStream(in);
        this.rc = new RangeDecoderFromBuffer(COMPRESSED_SIZE_MAX, arrayCache);
        this.lz = new LZDecoder(getDictSize(dictSize), presetDict, arrayCache);

        if (presetDict != null && presetDict.length > 0)
            needDictReset = false;
    }

    /**
     * Decompresses the next byte from this input stream.
     *

     * Reading lots of data with read() from this input stream
     * may be inefficient. Wrap it in java.io.BufferedInputStream
     * if you need to read lots of data one byte at a time.
     *
     * @return      the next decompressed byte, or -1
     *              to indicate the end of the compressed stream
     *
     * @throws      CorruptedInputException
     *
     * @throws      XZIOException if the stream has been closed
     *
     * @throws      EOFException
     *                          compressed input is truncated or corrupt
     *
     * @throws      IOException may be thrown by in
     */
    public int read() throws IOException {
        return read(tempBuf, 0, 1) == -1 ? -1 : (tempBuf[0] & 0xFF);
    }

    /**
     * Decompresses into an array of bytes.
     *

     * If len is zero, no bytes are read and 0
     * is returned. Otherwise this will block until len
     * bytes have been decompressed, the end of the LZMA2 stream is reached,
     * or an exception is thrown.
     *
     * @param       buf         target buffer for uncompressed data
     * @param       off         start offset in buf
     * @param       len         maximum number of uncompressed bytes to read
     *
     * @return      number of bytes read, or -1 to indicate
     *              the end of the compressed stream
     *
     * @throws      CorruptedInputException
     *
     * @throws      XZIOException if the stream has been closed
     *
     * @throws      EOFException
     *                          compressed input is truncated or corrupt
     *
     * @throws      IOException may be thrown by in
     */
    public int read(byte[] buf, int off, int len) throws IOException {
        if (off < 0 || len < 0 || off + len < 0 || off + len > buf.length)
            throw new IndexOutOfBoundsException();

        if (len == 0)
            return 0;

        if (in == null)
            throw new XZIOException("Stream closed");

        if (exception != null)
            throw exception;

        if (endReached)
            return -1;

        try {
            int size = 0;

            while (len > 0) {
                if (uncompressedSize == 0) {
                    decodeChunkHeader();
                    if (endReached)
                        return size == 0 ? -1 : size;
                }

                int copySizeMax = Math.min(uncompressedSize, len);

                if (!isLZMAChunk) {
                    lz.copyUncompressed(in, copySizeMax);
                } else {
                    lz.setLimit(copySizeMax);
                    lzma.decode();
                }

                int copiedSize = lz.flush(buf, off);
                off += copiedSize;
                len -= copiedSize;
                size += copiedSize;
                uncompressedSize -= copiedSize;

                if (uncompressedSize == 0)
                    if (!rc.isFinished() || lz.hasPending())
                        throw new CorruptedInputException();
            }

            return size;

        } catch (IOException e) {
            exception = e;
            throw e;
        }
    }

    private void decodeChunkHeader() throws IOException {
        int control = in.readUnsignedByte();

        if (control == 0x00) {
            endReached = true;
            putArraysToCache();
            return;
        }

        if (control >= 0xE0 || control == 0x01) {
            needProps = true;
            needDictReset = false;
            lz.reset();
        } else if (needDictReset) {
            throw new CorruptedInputException();
        }

        if (control >= 0x80) {
            isLZMAChunk = true;

            uncompressedSize = (control & 0x1F) << 16;
            uncompressedSize += in.readUnsignedShort() + 1;

            int compressedSize = in.readUnsignedShort() + 1;

            if (control >= 0xC0) {
                needProps = false;
                decodeProps();

            } else if (needProps) {
                throw new CorruptedInputException();

            } else if (control >= 0xA0) {
                lzma.reset();
            }

            rc.prepareInputBuffer(in, compressedSize);

        } else if (control > 0x02) {
            throw new CorruptedInputException();

        } else {
            isLZMAChunk = false;
            uncompressedSize = in.readUnsignedShort() + 1;
        }
    }

    private void decodeProps() throws IOException {
        int props = in.readUnsignedByte();

        if (props > (4 * 5 + 4) * 9 + 8)
            throw new CorruptedInputException();

        int pb = props / (9 * 5);
        props -= pb * 9 * 5;
        int lp = props / 9;
        int lc = props - lp * 9;

        if (lc + lp > 4)
            throw new CorruptedInputException();

        lzma = new LZMADecoder(lz, rc, lc, lp, pb);
    }

    /**
     * Returns the number of uncompressed bytes that can be read
     * without blocking. The value is returned with an assumption
     * that the compressed input data will be valid. If the compressed
     * data is corrupt, CorruptedInputException may get
     * thrown before the number of bytes claimed to be available have
     * been read from this input stream.
     *

     * In LZMA2InputStream, the return value will be non-zero when the
     * decompressor is in the middle of an LZMA2 chunk. The return value
     * will then be the number of uncompressed bytes remaining from that
     * chunk. The return value can also be non-zero in the middle of
     * an uncompressed chunk, but then the return value depends also on
     * the available() method of the underlying InputStream.
     *
     * @return      the number of uncompressed bytes that can be read
     *              without blocking
     */
    public int available() throws IOException {
        if (in == null)
            throw new XZIOException("Stream closed");

        if (exception != null)
            throw exception;

        return isLZMAChunk ? uncompressedSize
                           : Math.min(uncompressedSize, in.available());
    }

    private void putArraysToCache() {
        if (lz != null) {
            lz.putArraysToCache(arrayCache);
            lz = null;

            rc.putArraysToCache(arrayCache);
            rc = null;
        }
    }

    /**
     * Closes the stream and calls in.close().
     * If the stream was already closed, this does nothing.
     *
     * @throws  IOException if thrown by in.close()
     */
    public void close() throws IOException {
        if (in != null) {
            putArraysToCache();

            try {
                in.close();
            } finally {
                in = null;
            }
        }
    }
}


/*******************************************************************************
 * Copyright (c) 2014, 2015 IBH SYSTEMS GmbH.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     IBH SYSTEMS GmbH - initial API and implementation
 *******************************************************************************/
package org.eclipse.packagedrone.repo.channel.web.internal;

import org.eclipse.packagedrone.repo.aspect.ChannelAspectProcessor;
import org.eclipse.packagedrone.repo.aspect.recipe.RecipeProcessor;
import org.eclipse.packagedrone.repo.generator.GeneratorProcessor;
import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;

public class Activator implements BundleActivator
{

    private static Activator INSTANCE;

    private ChannelAspectProcessor aspects;

    private RecipeProcessor recipes;

    private GeneratorProcessor generatorProcessor;

    /*
     * (non-Javadoc)
     * @see org.osgi.framework.BundleActivator#start(org.osgi.framework.BundleContext)
     */
    @Override
    public void start ( final BundleContext bundleContext ) throws Exception
    {
        Activator.INSTANCE = this;
        this.aspects = new ChannelAspectProcessor ( bundleContext );
        this.recipes = new RecipeProcessor ( bundleContext );
        this.generatorProcessor = new GeneratorProcessor ( bundleContext );
        this.generatorProcessor.open ();
    }

    /*
     * (non-Javadoc)
     * @see org.osgi.framework.BundleActivator#stop(org.osgi.framework.BundleContext)
     */
    @Override
    public void stop ( final BundleContext bundleContext ) throws Exception
    {
        this.aspects.close ();
        this.recipes.dispose ();
        this.generatorProcessor.close ();

        Activator.INSTANCE = null;
    }

    public static ChannelAspectProcessor getAspects ()
    {
        return INSTANCE.aspects;
    }

    public static RecipeProcessor getRecipes ()
    {
        return INSTANCE.recipes;
    }

    public static GeneratorProcessor getGeneratorProcessor ()
    {
        return INSTANCE.generatorProcessor;
    }
}


/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright (c) 1997-2010 Oracle and/or its affiliates. All rights reserved.
 *
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common Development
 * and Distribution License("CDDL") (collectively, the "License").  You
 * may not use this file except in compliance with the License.  You can
 * obtain a copy of the License at
 * https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html
 * or packager/legal/LICENSE.txt.  See the License for the specific
 * language governing permissions and limitations under the License.
 *
 * When distributing the software, include this License Header Notice in each
 * file and include the License file at packager/legal/LICENSE.txt.
 *
 * GPL Classpath Exception:
 * Oracle designates this particular file as subject to the "Classpath"
 * exception as provided by Oracle in the GPL Version 2 section of the License
 * file that accompanied this code.
 *
 * Modifications:
 * If applicable, add the following below the License Header, with the fields
 * enclosed by brackets [] replaced by your own identifying information:
 * "Portions Copyright [year] [name of copyright owner]"
 *
 * Contributor(s):
 * If you wish your version of this file to be governed by only the CDDL or
 * only the GPL Version 2, indicate your decision by adding "[Contributor]
 * elects to include this software in this distribution under the [CDDL or GPL
 * Version 2] license."  If you don't indicate a single choice of license, a
 * recipient has the option to distribute your version of this file under
 * either the CDDL, the GPL Version 2 or to extend the choice of license to
 * its licensees as provided above.  However, if you add GPL Version 2 code
 * and therefore, elected the GPL Version 2 license, then the option applies
 * only if the new code is made subject to such option by the copyright
 * holder.
 *
 *
 * This file incorporates work covered by the following copyright and
 * permission notice:
 *
 * Copyright 2004 The Apache Software Foundation
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.jasper.xmlparser;

import java.io.InputStream;
import java.io.IOException;
import java.io.Reader;
import java.io.UTFDataFormatException;
import org.apache.jasper.compiler.Localizer;

/**
 * @author Andy Clark, IBM
 *
 * @version $Id: UTF8Reader.java,v 1.4 2006/10/03 02:25:27 jluehe Exp $
 */
public class UTF8Reader
    extends Reader {

    //
    // Constants
    //

    /** Default byte buffer size (2048). */
    public static final int DEFAULT_BUFFER_SIZE = 2048;

    //
    // Data
    //

    /** Input stream. */
    protected InputStream fInputStream;

    /** Byte buffer. */
    protected byte[] fBuffer;

    /** Offset into buffer. */
    protected int fOffset;

    /** Surrogate character. */
    private int fSurrogate = -1;

    //
    // Constructors
    //

    /**
     * Constructs a UTF-8 reader from the specified input stream,
     * buffer size and MessageFormatter.
     *
     * @param inputStream The input stream.
     * @param size        The initial buffer size.
     */
    public UTF8Reader(InputStream inputStream, int size) {
        fInputStream = inputStream;
        fBuffer = new byte[size];
    }

    //
    // Reader methods
    //

    /**
     * Read a single character.  This method will block until a character is
     * available, an I/O error occurs, or the end of the stream is reached.
     *
     * <p> Subclasses that intend to support efficient single-character input
     * should override this method.
     *
     * @return     The character read, as an integer in the range 0 to 16383
     *             (<tt>0x00-0xffff</tt>), or -1 if the end of the stream has
     *             been reached
     *
     * @exception  IOException  If an I/O error occurs
     */
    public int read() throws IOException {

        // decode character
        int c = fSurrogate;
        if (fSurrogate == -1) {
            // NOTE: We use the index into the buffer if there are remaining
            //       bytes from the last block read. -Ac
            int index = 0;

            // get first byte
            int b0 = index == fOffset
                   ? fInputStream.read() : fBuffer[index++] & 0x00FF;
            if (b0 == -1) {
                return -1;
            }

            // UTF-8:   [0xxx xxxx]
            // Unicode: [0000 0000] [0xxx xxxx]
            if (b0 < 0x80) {
                c = (char)b0;
            }

            // UTF-8:   [110y yyyy] [10xx xxxx]
            // Unicode: [0000 0yyy] [yyxx xxxx]
            else if ((b0 & 0xE0) == 0xC0) {
                int b1 = index == fOffset
                       ? fInputStream.read() : fBuffer[index++] & 0x00FF;
                if (b1 == -1) {
                    expectedByte(2, 2);
                }
                if ((b1 & 0xC0) != 0x80) {
                    invalidByte(2, 2, b1);
                }
                c = ((b0 << 6) & 0x07C0) | (b1 & 0x003F);
            }

            // UTF-8:   [1110 zzzz] [10yy yyyy] [10xx xxxx]
            // Unicode: [zzzz yyyy] [yyxx xxxx]
            else if ((b0 & 0xF0) == 0xE0) {
                int b1 = index == fOffset
                       ? fInputStream.read() : fBuffer[index++] & 0x00FF;
                if (b1 == -1) {
                    expectedByte(2, 3);
                }
                if ((b1 & 0xC0) != 0x80) {
                    invalidByte(2, 3, b1);
                }
                int b2 = index == fOffset
                       ? fInputStream.read() : fBuffer[index++] & 0x00FF;
                if (b2 == -1) {
                    expectedByte(3, 3);
                }
                if ((b2 & 0xC0) != 0x80) {
                    invalidByte(3, 3, b2);
                }
                c = ((b0 << 12) & 0xF000) | ((b1 << 6) & 0x0FC0) |
                    (b2 & 0x003F);
            }

            // UTF-8:   [1111 0uuu] [10uu zzzz] [10yy yyyy] [10xx xxxx]*
            // Unicode: [1101 10ww] [wwzz zzyy] (high surrogate)
            //          [1101 11yy] [yyxx xxxx] (low surrogate)
            //          * uuuuu = wwww + 1
            else if ((b0 & 0xF8) == 0xF0) {
                int b1 = index == fOffset
                       ? fInputStream.read() : fBuffer[index++] & 0x00FF;
                if (b1 == -1) {
                    expectedByte(2, 4);
                }
                if ((b1 & 0xC0) != 0x80) {
                    invalidByte(2, 3, b1);
                }
                int b2 = index == fOffset
                       ? fInputStream.read() : fBuffer[index++] & 0x00FF;
                if (b2 == -1) {
                    expectedByte(3, 4);
                }
                if ((b2 & 0xC0) != 0x80) {
                    invalidByte(3, 3, b2);
                }
                int b3 = index == fOffset
                       ? fInputStream.read() : fBuffer[index++] & 0x00FF;
                if (b3 == -1) {
                    expectedByte(4, 4);
                }
                if ((b3 & 0xC0) != 0x80) {
                    invalidByte(4, 4, b3);
                }
                int uuuuu = ((b0 << 2) & 0x001C) | ((b1 >> 4) & 0x0003);
                if (uuuuu > 0x10) {
                    invalidSurrogate(uuuuu);
                }
                int wwww = uuuuu - 1;
                int hs = 0xD800 |
                         ((wwww << 6) & 0x03C0) | ((b1 << 2) & 0x003C) |
                         ((b2 >> 4) & 0x0003);
                int ls = 0xDC00 | ((b2 << 6) & 0x03C0) | (b3 & 0x003F);
                c = hs;
                fSurrogate = ls;
            }

            // error
            else {
                invalidByte(1, 1, b0);
            }
        }

        // use surrogate
        else {
            fSurrogate = -1;
        }

        // return character
        return c;

    } // read():int

    /**
     * Read characters into a portion of an array.  This method will block
     * until some input is available, an I/O error occurs, or the end of the
     * stream is reached.
     *
     * @param      ch     Destination buffer
     * @param      offset Offset at which to start storing characters
     * @param      length Maximum number of characters to read
     *
     * @return     The number of characters read, or -1 if the end of the
     *             stream has been reached
     *
     * @exception  IOException  If an I/O error occurs
     */
    public int read(char ch[], int offset, int length) throws IOException {

        // handle surrogate
        int out = offset;
        if (fSurrogate != -1) {
            ch[offset + 1] = (char)fSurrogate;
            fSurrogate = -1;
            length--;
            out++;
        }

        // read bytes
        int count = 0;
        if (fOffset == 0) {
            // adjust length to read
            if (length > fBuffer.length) {
                length = fBuffer.length;
            }

            // perform read operation
            count = fInputStream.read(fBuffer, 0, length);
            if (count == -1) {
                return -1;
            }
            count += out - offset;
        }

        // skip read; last character was in error
        // NOTE: Having an offset value other than zero means that there was
        //       an error in the last character read. In this case, we have
        //       skipped the read so we don't consume any bytes past the
        //       error. By signalling the error on the next block read we
        //       allow the method to return the most valid characters that
        //       it can on the previous block read. -Ac
        else {
            count = fOffset;
            fOffset = 0;
        }

        // convert bytes to characters
        final int total = count;
        for (int in = 0; in < total; in++) {
            int b0 = fBuffer[in] & 0x00FF;

            // UTF-8:   [0xxx xxxx]
            // Unicode: [0000 0000] [0xxx xxxx]
            if (b0 < 0x80) {
                ch[out++] = (char)b0;
                continue;
            }

            // UTF-8:   [110y yyyy] [10xx xxxx]
            // Unicode: [0000 0yyy] [yyxx xxxx]
            if ((b0 & 0xE0) == 0xC0) {
                int b1 = -1;
                if (++in < total) {
                    b1 = fBuffer[in] & 0x00FF;
                }
                else {
                    b1 = fInputStream.read();
                    if (b1 == -1) {
                        if (out > offset) {
                            fBuffer[0] = (byte)b0;
                            fOffset = 1;
                            return out - offset;
                        }
                        expectedByte(2, 2);
                    }
                    count++;
                }
                if ((b1 & 0xC0) != 0x80) {
                    if (out > offset) {
                        fBuffer[0] = (byte)b0;
                        fBuffer[1] = (byte)b1;
                        fOffset = 2;
                        return out - offset;
                    }
                    invalidByte(2, 2, b1);
                }
                int c = ((b0 << 6) & 0x07C0) | (b1 & 0x003F);
                ch[out++] = (char)c;
                count -= 1;
                continue;
            }

            // UTF-8:   [1110 zzzz] [10yy yyyy] [10xx xxxx]
            // Unicode: [zzzz yyyy] [yyxx xxxx]
            if ((b0 & 0xF0) == 0xE0) {
                int b1 = -1;
                if (++in < total) {
                    b1 = fBuffer[in] & 0x00FF;
                }
                else {
                    b1 = fInputStream.read();
                    if (b1 == -1) {
                        if (out > offset) {
                            fBuffer[0] = (byte)b0;
                            fOffset = 1;
                            return out - offset;
                        }
                        expectedByte(2, 3);
                    }
                    count++;
                }
                if ((b1 & 0xC0) != 0x80) {
                    if (out > offset) {
                        fBuffer[0] = (byte)b0;
                        fBuffer[1] = (byte)b1;
                        fOffset = 2;
                        return out - offset;
                    }
                    invalidByte(2, 3, b1);
                }
                int b2 = -1;
                if (++in < total) {
                    b2 = fBuffer[in] & 0x00FF;
                }
                else {
                    b2 = fInputStream.read();
                    if (b2 == -1) {
                        if (out > offset) {
                            fBuffer[0] = (byte)b0;
                            fBuffer[1] = (byte)b1;
                            fOffset = 2;
                            return out - offset;
                        }
                        expectedByte(3, 3);
                    }
                    count++;
                }
                if ((b2 & 0xC0) != 0x80) {
                    if (out > offset) {
                        fBuffer[0] = (byte)b0;
                        fBuffer[1] = (byte)b1;
                        fBuffer[2] = (byte)b2;
                        fOffset = 3;
                        return out - offset;
                    }
                    invalidByte(3, 3, b2);
                }
                int c = ((b0 << 12) & 0xF000) | ((b1 << 6) & 0x0FC0) |
                        (b2 & 0x003F);
                ch[out++] = (char)c;
                count -= 2;
                continue;
            }

            // UTF-8:   [1111 0uuu] [10uu zzzz] [10yy yyyy] [10xx xxxx]*
            // Unicode: [1101 10ww] [wwzz zzyy] (high surrogate)
            //          [1101 11yy] [yyxx xxxx] (low surrogate)
            //          * uuuuu = wwww + 1
            if ((b0 & 0xF8) == 0xF0) {
                int b1 = -1;
                if (++in < total) {
                    b1 = fBuffer[in] & 0x00FF;
                }
                else {
                    b1 = fInputStream.read();
                    if (b1 == -1) {
                        if (out > offset) {
                            fBuffer[0] = (byte)b0;
                            fOffset = 1;
                            return out - offset;
                        }
                        expectedByte(2, 4);
                    }
                    count++;
                }
                if ((b1 & 0xC0) != 0x80) {
                    if (out > offset) {
                        fBuffer[0] = (byte)b0;
                        fBuffer[1] = (byte)b1;
                        fOffset = 2;
                        return out - offset;
                    }
                    invalidByte(2, 4, b1);
                }
                int b2 = -1;
                if (++in < total) {
                    b2 = fBuffer[in] & 0x00FF;
                }
                else {
                    b2 = fInputStream.read();
                    if (b2 == -1) {
                        if (out > offset) {
                            fBuffer[0] = (byte)b0;
                            fBuffer[1] = (byte)b1;
                            fOffset = 2;
                            return out - offset;
                        }
                        expectedByte(3, 4);
                    }
                    count++;
                }
                if ((b2 & 0xC0) != 0x80) {
                    if (out > offset) {
                        fBuffer[0] = (byte)b0;
                        fBuffer[1] = (byte)b1;
                        fBuffer[2] = (byte)b2;
                        fOffset = 3;
                        return out - offset;
                    }
                    invalidByte(3, 4, b2);
                }
                int b3 = -1;
                if (++in < total) {
                    b3 = fBuffer[in] & 0x00FF;
                }
                else {
                    b3 = fInputStream.read();
                    if (b3 == -1) {
                        if (out > offset) {
                            fBuffer[0] = (byte)b0;
                            fBuffer[1] = (byte)b1;
                            fBuffer[2] = (byte)b2;
                            fOffset = 3;
                            return out - offset;
                        }
                        expectedByte(4, 4);
                    }
                    count++;
                }
                if ((b3 & 0xC0) != 0x80) {
                    if (out > offset) {
                        fBuffer[0] = (byte)b0;
                        fBuffer[1] = (byte)b1;
                        fBuffer[2] = (byte)b2;
                        fBuffer[3] = (byte)b3;
                        fOffset = 4;
                        return out - offset;
                    }
                    invalidByte(4, 4, b2);
                }

                // decode bytes into surrogate characters
                int uuuuu = ((b0 << 2) & 0x001C) | ((b1 >> 4) & 0x0003);
                if (uuuuu > 0x10) {
                    invalidSurrogate(uuuuu);
                }
                int wwww = uuuuu - 1;
                int zzzz = b1 & 0x000F;
                int yyyyyy = b2 & 0x003F;
                int xxxxxx = b3 & 0x003F;
                int hs = 0xD800 | ((wwww << 6) & 0x03C0) | (zzzz << 2) | (yyyyyy >> 4);
                int ls = 0xDC00 | ((yyyyyy << 6) & 0x03C0) | xxxxxx;

                // set characters
                ch[out++] = (char)hs;
                ch[out++] = (char)ls;
                count -= 2;
                continue;
            }

            // error
            if (out > offset) {
                fBuffer[0] = (byte)b0;
                fOffset = 1;
                return out - offset;
            }
            invalidByte(1, 1, b0);
        }

        // return number of characters converted
        return count;

    } // read(char[],int,int)

    /**
     * Skip characters.  This method will block until some characters are
     * available, an I/O error occurs, or the end of the stream is reached.
     *
     * @param  n  The number of characters to skip
     *
     * @return    The number of characters actually skipped
     *
     * @exception  IOException  If an I/O error occurs
     */
    public long skip(long n) throws IOException {

        long remaining = n;
        final char[] ch = new char[fBuffer.length];
        do {
            int length = ch.length < remaining ? ch.length : (int)remaining;
            int count = read(ch, 0, length);
            if (count > 0) {
                remaining -= count;
            }
            else {
                break;
            }
        } while (remaining > 0);

        long skipped = n - remaining;
        return skipped;

    } // skip(long):long

    /**
     * Tell whether this stream is ready to be read.
     *
     * @return True if the next read() is guaranteed not to block for input,
     * false otherwise.  Note that returning false does not guarantee that the
     * next read will block.
     *
     * @exception  IOException  If an I/O error occurs
     */
    public boolean ready() throws IOException {
	    return false;
    } // ready()

    /**
     * Tell whether this stream supports the mark() operation.
     */
    public boolean markSupported() {
	    return false;
    } // markSupported()

    /**
     * Mark the present position in the stream.  Subsequent calls to reset()
     * will attempt to reposition the stream to this point.  Not all
     * character-input streams support the mark() operation.
     *
     * @param  readAheadLimit  Limit on the number of characters that may be
     *                         read while still preserving the mark.  After
     *                         reading this many characters, attempting to
     *                         reset the stream may fail.
     *
     * @exception  IOException  If the stream does not support mark(),
     *                          or if some other I/O error occurs
     */
    public void mark(int readAheadLimit) throws IOException {
	throw new IOException(
                Localizer.getMessage("jsp.error.xml.operationNotSupported",
				     "mark()", "UTF-8"));
    }

    /**
     * Reset the stream.  If the stream has been marked, then attempt to
     * reposition it at the mark.  If the stream has not been marked, then
     * attempt to reset it in some way appropriate to the particular stream,
     * for example by repositioning it to its starting point.  Not all
     * character-input streams support the reset() operation, and some support
     * reset() without supporting mark().
     *
     * @exception  IOException  If the stream has not been marked,
     *                          or if the mark has been invalidated,
     *                          or if the stream does not support reset(),
     *                          or if some other I/O error occurs
     */
    public void reset() throws IOException {
        fOffset = 0;
        fSurrogate = -1;
    } // reset()

    /**
     * Close the stream.  Once a stream has been closed, further read(),
     * ready(), mark(), or reset() invocations will throw an IOException.
     * Closing a previously-closed stream, however, has no effect.
     *
     * @exception  IOException  If an I/O error occurs
     */
    public void close() throws IOException {
        fInputStream.close();
    } // close()

    //
    // Private methods
    //

    /** Throws an exception for expected byte. */
    private void expectedByte(int position, int count)
        throws UTFDataFormatException {

        throw new UTFDataFormatException(
                Localizer.getMessage("jsp.error.xml.expectedByte",
				     Integer.toString(position),
				     Integer.toString(count)));

    } // expectedByte(int,int,int)

    /** Throws an exception for invalid byte. */
    private void invalidByte(int position, int count, int c)
        throws UTFDataFormatException {

        throw new UTFDataFormatException(
                Localizer.getMessage("jsp.error.xml.invalidByte",
				     Integer.toString(position),
				     Integer.toString(count)));
    } // invalidByte(int,int,int,int)

    /** Throws an exception for invalid surrogate bits. */
    private void invalidSurrogate(int uuuuu) throws UTFDataFormatException {

        throw new UTFDataFormatException(
                Localizer.getMessage("jsp.error.xml.invalidHighSurrogate",
				     Integer.toHexString(uuuuu)));
    } // invalidSurrogate(int)

} // class UTF8Reader


/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright (c) 1997-2010 Oracle and/or its affiliates. All rights reserved.
 *
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common Development
 * and Distribution License("CDDL") (collectively, the "License").  You
 * may not use this file except in compliance with the License.  You can
 * obtain a copy of the License at
 * https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html
 * or packager/legal/LICENSE.txt.  See the License for the specific
 * language governing permissions and limitations under the License.
 *
 * When distributing the software, include this License Header Notice in each
 * file and include the License file at packager/legal/LICENSE.txt.
 *
 * GPL Classpath Exception:
 * Oracle designates this particular file as subject to the "Classpath"
 * exception as provided by Oracle in the GPL Version 2 section of the License
 * file that accompanied this code.
 *
 * Modifications:
 * If applicable, add the following below the License Header, with the fields
 * enclosed by brackets [] replaced by your own identifying information:
 * "Portions Copyright [year] [name of copyright owner]"
 *
 * Contributor(s):
 * If you wish your version of this file to be governed by only the CDDL or
 * only the GPL Version 2, indicate your decision by adding "[Contributor]
 * elects to include this software in this distribution under the [CDDL or GPL
 * Version 2] license."  If you don't indicate a single choice of license, a
 * recipient has the option to distribute your version of this file under
 * either the CDDL, the GPL Version 2 or to extend the choice of license to
 * its licensees as provided above.  However, if you add GPL Version 2 code
 * and therefore, elected the GPL Version 2 license, then the option applies
 * only if the new code is made subject to such option by the copyright
 * holder.
 *
 *
 * This file incorporates work covered by the following copyright and
 * permission notice:
 *
 * Copyright 2004 The Apache Software Foundation
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.jasper.runtime;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.io.IOException;
import java.net.JarURLConnection;
import java.net.URI;
import java.net.URL;
import java.net.URLClassLoader;
import java.net.URLConnection;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.ArrayList;
import java.util.concurrent.ConcurrentHashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.jar.Manifest;
import java.util.logging.Logger;
import java.util.logging.Level;

import javax.servlet.ServletContext;
import javax.servlet.ServletRegistration;
import javax.servlet.ServletContainerInitializer;
import javax.servlet.ServletException;
import javax.servlet.descriptor.JspPropertyGroupDescriptor;
import javax.servlet.descriptor.TaglibDescriptor;
import javax.servlet.descriptor.JspConfigDescriptor;

import org.apache.jasper.Constants;
import org.apache.jasper.JasperException;
import org.apache.jasper.Options;
import org.apache.jasper.xmlparser.ParserUtils;
import org.apache.jasper.xmlparser.TreeNode;
import org.apache.jasper.compiler.Localizer;

/**
 * A container for all tag libraries that are defined "globally"
 * for the web application.
 *
 * Tag Libraries can be defined globally in one of two ways:
 *   1. Via <taglib> elements in web.xml:
 *      the uri and location of the tag-library are specified in
 *      the <taglib> element.
 *   2. Via packaged jar files that contain .tld files
 *      within the META-INF directory, or some subdirectory
 *      of it. The taglib is 'global' if it has the <uri>
 *      element defined.
 *
 * A mapping between the taglib URI and its associated TaglibraryInfoImpl
 * is maintained in this container.
 * Actually, that's what we'd like to do. However, because of the
 * way the classes TagLibraryInfo and TagInfo have been defined,
 * it is not currently possible to share an instance of TagLibraryInfo
 * across page invocations. A bug has been submitted to the spec lead.
 * In the mean time, all we do is save the 'location' where the
 * TLD associated with a taglib URI can be found.
 *
 * When a JSP page has a taglib directive, the mappings in this container
 * are first searched (see method getLocation()).
 * If a mapping is found, then the location of the TLD is returned.
 * If no mapping is found, then the uri specified
 * in the taglib directive is to be interpreted as the location for
 * the TLD of this tag library.
 *
 * @author Pierre Delisle
 * @author Jan Luehe
 * @author Kin-man Chung servlet 3.0 JSP plugin, tld cache etc
 */

public class TldScanner implements ServletContainerInitializer {

    // Logger
    private static Logger log =
            Logger.getLogger(TldScanner.class.getName());

    /**
     * The types of URI one may specify for a tag library
     */
    public static final int ABS_URI = 0;
    public static final int ROOT_REL_URI = 1;
    public static final int NOROOT_REL_URI = 2;

    private static final String WEB_XML = "/WEB-INF/web.xml";
    private static final String FILE_PROTOCOL = "file:";
    private static final String JAR_FILE_SUFFIX = ".jar";

    // Names of system Uri's that are ignored if referred in WEB-INF/web.xml
    private static HashSet<String> systemUris = new HashSet<String>();
    private static HashSet<String> systemUrisJsf = new HashSet<String>();

    // A Cache is used for system jar files.
    // The key is the name of the jar file, the value is an array of
    // TldInfo, one for each of the TLD in the jar file
    private static Map<String, TldInfo[]> jarTldCache =
        new ConcurrentHashMap<String, TldInfo[]>();

    private static final String EAR_LIB_CLASSLOADER =
        "org.glassfish.javaee.full.deployment.EarLibClassLoader";

    private static final String IS_STANDALONE_ATTRIBUTE_NAME =
        "org.glassfish.jsp.isStandaloneWebapp";

    /**
     * The mapping of the 'global' tag library URI (as defined in the tld) to
     * the location (resource path) of the TLD associated with that tag library.
     * The location is returned as a String array:
     *    [0] The location of the tld file or the jar file that contains the tld
     *    [1] If the location is a jar file, this is the location of the tld.
     */
    private HashMap<String, String[]> mappings;

    /**
     * A local cache for keeping track which jars have been scanned.
     */
    private Map<String, TldInfo[]> jarTldCacheLocal =
        new HashMap<String, TldInfo[]>();

    private ServletContext ctxt;
    private boolean isValidationEnabled;
    private boolean useMyFaces = false;
    private boolean scanListeners;  // true if scan tlds for listeners
    private boolean doneScanning;   // true if all tld scanning done

    //*********************************************************************
    // Constructor and Initilizations

    /*
     * Initializes the set of JARs that are known not to contain any TLDs
     */
    static {
        systemUrisJsf.add("http://java.sun.com/jsf/core");
        systemUrisJsf.add("http://java.sun.com/jsf/html");
    }

    /**
     * Default Constructor.
     * This is only used for implementing ServletContainerInitializer.
     * ServletContext will be supplied in the method onStartUp;
     */
    public TldScanner() {
    }

    /**
     * Constructor used in Jasper
     */
    public TldScanner(ServletContext ctxt, boolean isValidationEnabled) {
        this.ctxt = ctxt;
        this.isValidationEnabled = isValidationEnabled;
        Boolean b = (Boolean) ctxt.getAttribute("com.sun.faces.useMyFaces");
        if (b != null) {
            useMyFaces = b.booleanValue();
        }
    }

    public void onStartup(java.util.Set<java.lang.Class<?>> c,
               ServletContext ctxt) throws ServletException {
        this.ctxt = ctxt;
        Boolean b = (Boolean) ctxt.getAttribute("com.sun.faces.useMyFaces");
        if (b != null) {
            useMyFaces = b.booleanValue();
        }
        ServletRegistration reg = ctxt.getServletRegistration("jsp");
        if (reg == null) {
            return;
        }
        String validating = reg.getInitParameter("validating");
        isValidationEnabled = "true".equals(validating);

        scanListeners = true;
        scanTlds();

        ctxt.setAttribute(Constants.JSP_TLD_URI_TO_LOCATION_MAP, mappings);
    }

    /**
     * Gets the 'location' of the TLD associated with the given taglib 'uri'.
     *
     * Returns null if the uri is not associated with any tag library 'exposed'
     * in the web application. A tag library is 'exposed' either explicitly in
     * web.xml or implicitly via the uri tag in the TLD of a taglib deployed
     * in a jar file (WEB-INF/lib).
     *
     * @param uri The taglib uri
     *
     * @return An array of two Strings: The first element denotes the real
     * path to the TLD. If the path to the TLD points to a jar file, then the
     * second element denotes the name of the TLD entry in the jar file.
     * Returns null if the uri is not associated with any tag library 'exposed'
     * in the web application.
     *
     * This method may be called when the scanning is in one of states:
     * 1. Called from jspc script, then a full tld scan is required.
     * 2. The is the first call after servlet initialization, then system jars
     *    that are knwon to have tlds but not listeners need to be scanned.
     * 3. Sebsequent calls, no need to scans.
     */

    @SuppressWarnings("unchecked")
    public String[] getLocation(String uri) throws JasperException {

        if (mappings == null) {
            // Recovering the map done in onStart.
            mappings = (HashMap<String, String[]>) ctxt.getAttribute(
                            Constants.JSP_TLD_URI_TO_LOCATION_MAP);
        }

        if (mappings != null && mappings.get(uri) != null) {
            // if the uri is in, return that, and dont bother to do full scan
            return mappings.get(uri);
        }

        if (! doneScanning) {
            scanListeners = false;
            scanTlds();
            doneScanning = true;
        }
        if (mappings == null) {
            // Should never happend
            return null;
        }
        return mappings.get(uri);
    }

    @SuppressWarnings("unchecked")
    Map<URI, List<String>> getTldMap() {
        /*
         * System jars with tlds may be passed as a special
         * ServletContext attribute
         * Map key: a JarURI
         * Map value: list of tlds in the jar file
         */
        return (Map<URI, List<String>>)
            ctxt.getAttribute("com.sun.appserv.tld.map");
    }

    @SuppressWarnings("unchecked")
    Map<URI, List<String>> getTldListenerMap() {
        /*
         * System jars with tlds that are known to contain a listener, and
         * may be passed as a special ServletContext attribute
         * Map key: a JarURI
         * Map value: list of tlds in the jar file
         */
        return (Map<URI, List<String>>)
            ctxt.getAttribute("com.sun.appserv.tldlistener.map");
    }

    /**
     * Returns the type of a URI:
     *     ABS_URI
     *     ROOT_REL_URI
     *     NOROOT_REL_URI
     */
    public static int uriType(String uri) {
        if (uri.indexOf(':') != -1) {
            return ABS_URI;
        } else if (uri.startsWith("/")) {
            return ROOT_REL_URI;
        } else {
            return NOROOT_REL_URI;
        }
    }

    /**
     * Scan the all the tlds accessible in the web app.
     * For performance reasons, this is done in two stages.  At servlet
     * initialization time, we only scan the jar files for listeners.  The
     * container passes a list of system jar files that are known to contain
     * tlds with listeners.  The rest of the jar files will be scanned when
     * a JSP page with a tld referenced is compiled.
     */
    private void scanTlds() throws JasperException {

        mappings = new HashMap<String, String[]>();

        // Make a local copy of the system jar cache
        jarTldCacheLocal.putAll(jarTldCache);

        try {
            processWebDotXml();
            scanJars();
            processTldsInFileSystem("/WEB-INF/");
        } catch (JasperException ex) {
            throw ex;
        } catch (Exception ex) {
            throw new JasperException(
                Localizer.getMessage("jsp.error.internal.tldinit"),
                ex);
        }
    }

    /*
     * Populates taglib map described in web.xml.
     */
    private void processWebDotXml() throws Exception {

        // Skip if we are only looking for listeners
        if (scanListeners) {
            return;
        }

        JspConfigDescriptor jspConfig = ctxt.getJspConfigDescriptor();
        if (jspConfig == null) {
            return;
        }

        for (TaglibDescriptor taglib: jspConfig.getTaglibs()) {

            if (taglib == null) {
                continue;
            }
            String tagUri = taglib.getTaglibURI();
            String tagLoc = taglib.getTaglibLocation();
            if (tagUri == null || tagLoc == null) {
                continue;
            }
            // Ignore system tlds in web.xml, for backward compatibility
            if (systemUris.contains(tagUri)
                        || (!useMyFaces && systemUrisJsf.contains(tagUri))) {
                continue;
            }
            // Save this location if appropriate
            if (uriType(tagLoc) == NOROOT_REL_URI)
                    tagLoc = "/WEB-INF/" + tagLoc;
            String tagLoc2 = null;
            if (tagLoc.endsWith(JAR_FILE_SUFFIX)) {
                tagLoc = ctxt.getResource(tagLoc).toString();
                tagLoc2 = "META-INF/taglib.tld";
            }
            if (log.isLoggable(Level.FINE)) {
                log.fine( "Add tld map from web.xml: " + tagUri + "=>" + tagLoc+ "," + tagLoc2);
            }
            mappings.put(tagUri, new String[] { tagLoc, tagLoc2 });
        }
    }

    /**
     * Scans the given JarURLConnection for TLD files located in META-INF
     * (or a subdirectory of it).  If the scanning in is done as part of the
     * ServletContextInitializer, the listeners in the tlds in this jar file
     * are added to the servlet context, and for any  TLD that has a <uri>
     * element, an implicit map entry is added to the taglib map.
     *
     * @param conn The JarURLConnection to the JAR file to scan
     * @param tldNames the list of tld element to scan. The null value
     *         indicates all the tlds in this case.
     * @param isLocal True if the jar file is under WEB-INF
     *         false otherwise
     */
    private void scanJar(JarURLConnection conn, List<String> tldNames,
                         boolean isLocal)
            throws JasperException {

        String resourcePath = conn.getJarFileURL().toString();
        TldInfo[] tldInfos = jarTldCacheLocal.get(resourcePath);

        // Optimize for most common cases: jars known to NOT have tlds
        if (tldInfos != null && tldInfos.length == 0) {
            try {
                conn.getJarFile().close();
            } catch (IOException ex) {
                //ignored
            }
            return;
        }

        // scan the tld if the jar has not been cached.
        if (tldInfos == null) {
            JarFile jarFile = null;
            ArrayList<TldInfo> tldInfoA = new ArrayList<TldInfo>();
            try {
                jarFile = conn.getJarFile();
                if (tldNames != null) {
                    for (String tldName : tldNames) {
                        JarEntry entry = jarFile.getJarEntry(tldName);
                        InputStream stream = jarFile.getInputStream(entry);
                        tldInfoA.add(scanTld(resourcePath, tldName, stream));
                    }
                } else {
                    Enumeration<JarEntry> entries = jarFile.entries();
                    while (entries.hasMoreElements()) {
                        JarEntry entry = entries.nextElement();
                        String name = entry.getName();
                        if (!name.startsWith("META-INF/")) continue;
                        if (!name.endsWith(".tld")) continue;
                        InputStream stream = jarFile.getInputStream(entry);
                        tldInfoA.add(scanTld(resourcePath, name, stream));
                    }
                }
            } catch (IOException ex) {
                if (resourcePath.startsWith(FILE_PROTOCOL) &&
                        !((new File(resourcePath)).exists())) {
                    if (log.isLoggable(Level.WARNING)) {
                        log.log(Level.WARNING,
                            Localizer.getMessage("jsp.warn.nojar",
                                                 resourcePath),
                            ex);
                    }
                } else {
                    throw new JasperException(
                        Localizer.getMessage("jsp.error.jar.io", resourcePath),
                        ex);
                }
            } finally {
                if (jarFile != null) {
                    try {
                        jarFile.close();
                    } catch (Throwable t) {
                        // ignore
                    }
                }
            }
            // Update the jar TLD cache
            tldInfos = tldInfoA.toArray(new TldInfo[tldInfoA.size()]);
            jarTldCacheLocal.put(resourcePath, tldInfos);
            if (!isLocal) {
                // Also update the global cache;
                jarTldCache.put(resourcePath, tldInfos);
            }
        }

        // Iterate over tldinfos to add listeners or to map tldlocations
        for (TldInfo tldInfo: tldInfos) {
            if (scanListeners) {
                addListener(tldInfo, isLocal);
            }
            mapTldLocation(resourcePath, tldInfo, isLocal);
        }
    }

    private void addListener(TldInfo tldInfo, boolean isLocal) {
        String uri = tldInfo.getUri();
        if (!systemUrisJsf.contains(uri)
                    || (isLocal && useMyFaces)
                    || (!isLocal && !useMyFaces)) {
            for (String listenerClassName: tldInfo.getListeners()) {
                if (log.isLoggable(Level.FINE)) {
                    log.fine( "Add tld listener " + listenerClassName);
                }
                ctxt.addListener(listenerClassName);
            }
        }
    }

    private void mapTldLocation(String resourcePath, TldInfo tldInfo,
                                boolean isLocal) {

        String uri = tldInfo.getUri();
        if (uri == null) {
            return;
        }

        if ((isLocal
                // Local tld files override the tlds in the jar files,
                // unless it is in a system jar (except when using myfaces)
                && mappings.get(uri) == null
                && !systemUris.contains(uri)
                && (!systemUrisJsf.contains(uri) || useMyFaces)
            ) ||
            (!isLocal
                // Jars are scanned bottom up, so jars in WEB-INF override
                // thos in the system (except when using myfaces)
                && (mappings.get(uri) == null
                    || systemUris.contains(uri)
                    || (systemUrisJsf.contains(uri) && !useMyFaces)
                   )
            )
           ) {
            String entryName = tldInfo.getEntryName();
            if (log.isLoggable(Level.FINE)) {
                log.fine("Add tld map from tld in " +
                    (isLocal? "WEB-INF": "jar: ") + uri + "=>" +
                    resourcePath + "," + entryName);
            }
            mappings.put(uri, new String[] {resourcePath, entryName});
        }
    }

    /*
     * Searches the filesystem under /WEB-INF for any TLD files, and scans
     * them for <uri> and <listener> elements.
     */
    private void processTldsInFileSystem(String startPath)
            throws JasperException {

        Set dirList = ctxt.getResourcePaths(startPath);
        if (dirList != null) {
            Iterator it = dirList.iterator();
            while (it.hasNext()) {
                String path = (String) it.next();
                if (path.endsWith("/")) {
                    processTldsInFileSystem(path);
                }
                if (!path.endsWith(".tld")) {
                    continue;
                }
                if (path.startsWith("/WEB-INF/tags/")
                        && !path.endsWith("implicit.tld")) {
                    throw new JasperException(
                        Localizer.getMessage(
                                "jsp.error.tldinit.tldInWebInfTags",
                                path));
                }
                InputStream stream = ctxt.getResourceAsStream(path);
                TldInfo tldInfo = scanTld(path, null, stream);
                // Add listeners or to map tldlocations for this TLD
                if (scanListeners) {
                    addListener(tldInfo, true);
                }
                mapTldLocation(path, tldInfo, true);
            }
        }
    }

    /**
     * Scan the given TLD for uri and listeners elements.
     *
     * @param resourcePath the resource path for the jar file or the tld file.
     * @param entryName If the resource path is a jar file, then the name of
     *        the tld file in the jar, else should be null.
     * @param stream The input stream for the tld
     * @return The TldInfo for this tld
     */
    private TldInfo scanTld(String resourcePath, String entryName,
                         InputStream stream)
                throws JasperException {
        try {
            // Parse the tag library descriptor at the specified resource path
            TreeNode tld = new ParserUtils().parseXMLDocument(
                                resourcePath, stream, isValidationEnabled);

            String uri = null;
            TreeNode uriNode = tld.findChild("uri");
            if (uriNode != null) {
                uri = uriNode.getBody();
            }

            ArrayList<String> listeners = new ArrayList<String>();

            Iterator<TreeNode>listenerNodes = tld.findChildren("listener");
            while (listenerNodes.hasNext()) {
                TreeNode listener = listenerNodes.next();
                TreeNode listenerClass = listener.findChild("listener-class");
                if (listenerClass != null) {
                    String listenerClassName = listenerClass.getBody();
                    if (listenerClassName != null) {
                        listeners.add(listenerClassName);
                    }
                }
            }

            return new TldInfo(uri, entryName,
                               listeners.toArray(new String[listeners.size()]));

        } finally {
            if (stream != null) {
                try {
                    stream.close();
                } catch (Throwable t) {
                    // do nothing
                }
            }
        }
    }

    /*
     * Scans all JARs accessible to the webapp's classloader and its
     * parent classloaders for TLDs.
     *
     * The list of JARs always includes the JARs under WEB-INF/lib, as well as
     * all shared JARs in the classloader delegation chain of the webapp's
     * classloader.
     *
     * Considering JARs in the classloader delegation chain constitutes a
     * Tomcat-specific extension to the TLD search
     * order defined in the JSP spec. It allows tag libraries packaged as JAR
     * files to be shared by web applications by simply dropping them in a
     * location that all web applications have access to (e.g.,
     * <CATALINA_HOME>/common/lib).
     */
    private void scanJars() throws Exception {

        ClassLoader webappLoader =
            Thread.currentThread().getContextClassLoader();
        ClassLoader loader = webappLoader;

        Map<URI, List<String>> tldMap;
        if (scanListeners) {
            tldMap = getTldListenerMap();
        } else {
            tldMap= getTldMap();
        }

        Boolean isStandalone = (Boolean)
            ctxt.getAttribute(IS_STANDALONE_ATTRIBUTE_NAME);

        while (loader != null) {
            if (loader instanceof URLClassLoader) {
                boolean isLocal = (loader == webappLoader);
                URL[] urls = ((URLClassLoader) loader).getURLs();
                List<String> extraJars = new ArrayList<String>();

                for (int i=0; i<urls.length; i++) {
                    URLConnection conn = urls[i].openConnection();
                    JarURLConnection jconn = null;
                    if (conn instanceof JarURLConnection) {
                        jconn = (JarURLConnection) conn;
                    } else {
                        String urlStr = urls[i].toString();
                        if (urlStr.startsWith(FILE_PROTOCOL)
                                && urlStr.endsWith(JAR_FILE_SUFFIX)) {
                            URL jarURL = new URL("jar:" + urlStr + "!/");
                            jconn = (JarURLConnection) jarURL.openConnection();
                        }
                    }
                    if (jconn != null) {
                        jconn.setUseCaches(false);
                        if (isLocal) {
                            // For local jars, collect the jar files in the
                            // Manifest Class-Path, to be scanned later.
                            addManifestClassPath(null, extraJars, jconn);
                        }
                        scanJar(jconn, null, isLocal);
                    }
                }

                // Scan the jars collected from manifest class-path.  Expand
                // the list to include jar files from their manifest classpath.
                if (extraJars.size() > 0) {
                    List<String> newJars;
                    do {
                        newJars = new ArrayList<String>();
                        for (String jar: extraJars) {
                            URL jarURL = new URL("jar:" + jar + "!/");
                            JarURLConnection jconn =
                                    (JarURLConnection) jarURL.openConnection();
                            jconn.setUseCaches(false);
                            if (addManifestClassPath(extraJars,newJars,jconn)){
                                scanJar(jconn, null, true);
                            }
                        }
                        extraJars.addAll(newJars);
                    } while (newJars.size() != 0);
                }
            }

            if (tldMap != null && isStandalone != null) {
                if (isStandalone.booleanValue()) {
                    break;
                } else {
                    if (EAR_LIB_CLASSLOADER.equals(
                            loader.getClass().getName())) {
                        // Do not walk up classloader delegation chain beyond
                        // EarLibClassLoader
                        break;
                    }
                }
            }

            loader = loader.getParent();
        }

        if (tldMap != null) {
            for (URI uri : tldMap.keySet()) {
                URL jarURL = new URL("jar:" + uri.toString() + "!/");
                scanJar((JarURLConnection)jarURL.openConnection(),
                        tldMap.get(uri), false);
            }
        }
    }

    /*
     * Add the jars in the manifest Class-Path to the list "jars"
     * @param scannedJars List of jars that has been previously scanned
     * @param newJars List of jars from Manifest Class-Path
     * @return true is the jar file exists
     */
    private boolean addManifestClassPath(List<String> scannedJars,
                                         List<String> newJars,
                                         JarURLConnection jconn){

        Manifest manifest;
        try {
            manifest = jconn.getManifest();
        } catch (IOException ex) {
            // Maybe non existing jar, ignored
            return false;
        }

        String file = jconn.getJarFileURL().toString();
        if (! file.contains("WEB-INF")) {
            // Only jar in WEB-INF is considered here
            return true;
        }

        if (manifest == null)
            return true;

        java.util.jar.Attributes attrs = manifest.getMainAttributes();
        String cp = (String) attrs.getValue("Class-Path");
        if (cp == null)
            return true;

        String[] paths = cp.split(" ");
        int lastIndex = file.lastIndexOf('/');
        if (lastIndex < 0) {
            lastIndex = file.lastIndexOf('\\');
        }
        String baseDir = "";
        if (lastIndex > 0) {
            baseDir = file.substring(0, lastIndex+1);
        }
        for (String path: paths) {
            String p;
            if (path.startsWith("/") || path.startsWith("\\")){
                p = "file:" + path;
            } else {
                p = baseDir + path;
            }
            if ((scannedJars == null || !scannedJars.contains(p)) &&
                !newJars.contains(p) ){
                     newJars.add(p);
            }
        }
        return true;
    }

    static class TldInfo {
        private String entryName;        // The name of the tld file
        private String uri;              // The uri name for the tld
        private String[] listeners;      // The listeners in the tld

        public TldInfo(String uri, String entryName, String[] listeners) {
            this.uri = uri;
            this.entryName = entryName;
            this.listeners = listeners;
        }

        public String getEntryName() {
            return entryName;
        }

        public String getUri() {
            return uri;
        }

        public String[] getListeners() {
            return listeners;
        }
    }
}