/*
 * Copyright (C) 2007 The Android Open Source Project
 *
 * 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.android.dx.cf.direct;

import com.android.dx.cf.iface.Attribute;
import com.android.dx.cf.iface.ParseException;
import com.android.dx.cf.iface.ParseObserver;
import com.android.dx.cf.iface.StdAttributeList;
import com.android.dx.util.ByteArray;
import com.android.dx.util.Hex;

/**
 * Parser for lists of attributes.
 */
final /*package*/ class AttributeListParser {
    /** {@code non-null;} the class file to parse from */
    private final DirectClassFile cf;

    /** attribute parsing context */
    private final int context;

    /** offset in the byte array of the classfile to the start of the list */
    private final int offset;

    /** {@code non-null;} attribute factory to use */
    private final AttributeFactory attributeFactory;

    /** {@code non-null;} list of parsed attributes */
    private final StdAttributeList list;

    /** {@code >= -1;} the end offset of this list in the byte array of the
     * classfile, or {@code -1} if not yet parsed */
    private int endOffset;

    /** {@code null-ok;} parse observer, if any */
    private ParseObserver observer;

    /**
     * Constructs an instance.
     *
     * @param cf {@code non-null;} class file to parse from
     * @param context attribute parsing context (see {@link AttributeFactory})
     * @param offset offset in {@code bytes} to the start of the list
     * @param attributeFactory {@code non-null;} attribute factory to use
     */
    public AttributeListParser(DirectClassFile cf, int context, int offset,
                               AttributeFactory attributeFactory) {
        if (cf == null) {
            throw new NullPointerException("cf == null");
        }

        if (attributeFactory == null) {
            throw new NullPointerException("attributeFactory == null");
        }

        int size = cf.getBytes().getUnsignedShort(offset);

        this.cf = cf;
        this.context = context;
        this.offset = offset;
        this.attributeFactory = attributeFactory;
        this.list = new StdAttributeList(size);
        this.endOffset = -1;
    }

    /**
     * Sets the parse observer for this instance.
     *
     * @param observer {@code null-ok;} the observer
     */
    public void setObserver(ParseObserver observer) {
        this.observer = observer;
    }

    /**
     * Gets the end offset of this constant pool in the {@code byte[]}
     * which it came from.
     *
     * @return {@code >= 0;} the end offset
     */
    public int getEndOffset() {
        parseIfNecessary();
        return endOffset;
    }

    /**
     * Gets the parsed list.
     *
     * @return {@code non-null;} the list
     */
    public StdAttributeList getList() {
        parseIfNecessary();
        return list;
    }

    /**
     * Runs {@link #parse} if it has not yet been run successfully.
     */
    private void parseIfNecessary() {
        if (endOffset < 0) {
            parse();
        }
    }

    /**
     * Does the actual parsing.
     */
    private void parse() {
        int sz = list.size();
        int at = offset + 2; // Skip the count.

        ByteArray bytes = cf.getBytes();

        if (observer != null) {
            observer.parsed(bytes, offset, 2,
                            "attributes_count: " + Hex.u2(sz));
        }

        for (int i = 0; i < sz; i++) {
            try {
                if (observer != null) {
                    observer.parsed(bytes, at, 0,
                                    "\nattributes[" + i + "]:\n");
                    observer.changeIndent(1);
                }

                Attribute attrib =
                    attributeFactory.parse(cf, context, at, observer);

                at += attrib.byteLength();
                list.set(i, attrib);

                if (observer != null) {
                    observer.changeIndent(-1);
                    observer.parsed(bytes, at, 0,
                                    "end attributes[" + i + "]\n");
                }
            } catch (ParseException ex) {
                ex.addContext("...while parsing attributes[" + i + "]");
                throw ex;
            } catch (RuntimeException ex) {
                ParseException pe = new ParseException(ex);
                pe.addContext("...while parsing attributes[" + i + "]");
                throw pe;
            }
        }

        endOffset = at;
    }
}
