/*
 *   
 *
 * Copyright  1990-2006 Sun Microsystems, Inc. All Rights Reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
 * 
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License version
 * 2 only, as published by the Free Software Foundation. 
 * 
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * General Public License version 2 for more details (a copy is
 * included at /legal/license.txt). 
 * 
 * You should have received a copy of the GNU General Public License
 * version 2 along with this work; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA 
 * 
 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
 * Clara, CA 95054 or visit www.sun.com if you need additional
 * information or have any questions. 
 */

package com.sun.midp.installer;

import java.io.*;
import java.util.*;

/**
 * Handles the properties in a JAR manifest. Each property is know as a header
 * in the syntax definition. A line that begins with a space is a continuation
 * of the previous line, and the first space ignored. To save code this
 * manifest parser uses lets its super class parse the key and value
 * of property once it has read the entire line, including continuation lines.
 * This means that this parser is lax on reporting illegal characters.
 * <p>
 * The BNF for the manifest syntax included below is extended as follows:
 * <pre>
 *    ":":separates the name and value of a rule
 *    +: 1 or more
 *    *: 0 or more
 *    {}: encloses a list of alternatives
 *    ;: comment
 *    terminals can also be represented in UPPER CASE
 *    non-terminals are not encluded in angle brackets
 * </pre><p>
 * Syntax definition from the manifest spec:
 * <p>
 * In most cases, information contained within the manifest file or
 * signature files is represented as so-called "name: value" pairs
 * inspired by the RFC822 standard.
 * <p>
 * Groups of name-value pairs are known as a "section". Sections are
 * separated from other sections by empty lines. 
 * <p>
 * Binary data of any form is represented as base64. Continuations are
 * required for binary data which causes line length to exceed 72
 * bytes. Examples of binary data are digests and signatures.
 * <p>
 * Implementations shall support header values of up to 65535 bytes.
 * <pre>
 *  section: *header +newline
 *  nonempty-section: +header +newline
 *  newline: CR LF | LF | CR (not followed by LF)
 *
 *  ; That 'not followed by LF' probably requires some minor
 *  ; ugliness in the parser. Sorry.
 *
 *  header: alphanum *headerchar ":" SPACE *otherchar newline
 *          *continuation
 *
 *  continuation: SPACE *otherchar newline
 *
 *  ; RFC822 has +(SPACE | TAB), but this way continuation lines 
 *  ; never get mangled.
 *
 *  alphanum: {"A"-"Z"} | {"a"-"z"} | {"0"-"9"}
 *
 *  headerchar: alphanum | "-" | "_"
 *
 *  otherchar: any Unicode character except NUL, CR and LF
 *
 *  ; Also: To prevent mangling of files sent via straight e-mail, no 
 *  ; header will start with the four letters "From".
 *
 *  ; When version numbering is used:
 *
 *  number: {"0"-"9"}+
 *
 *  ; The number needn't be, e.g. 1.11 is considered to be later
 *  ; than 1.9. Both major and minor versions must be 3 digits or less.
 *</pre>
 */
public class ManifestProperties extends JadProperties {
    /** Signals that there is no remainder. */
    protected static final int NO_REMAINDER = -2;

    /** Holds the remainder from the last look ahead operation. */
    protected int remainder = NO_REMAINDER;

    /**
     * Constructor - creates an empty property list.
     */
    public ManifestProperties() {
    }
    /**
    * Read a portion of the manifest.
    * @param inStream the current data source for the manifest
    * @param enc the character set encoding of the manifest 
    * @param propertiesToLoad the names of the properties to load
    * @exception IOException is thrown if an error occurs reading the 
    * manifest data stream.
    * @exception  InvalidJadException if the JAD is not formatted correctly.
    */
    public void partialLoad(InputStream inStream, String enc,
                             int propertiesToLoad) throws IOException,
            InvalidJadException {
        // reset any leftover remainder
        remainder = NO_REMAINDER;
        super.partialLoad(inStream, enc, propertiesToLoad);
    }

    /**
     * Reads one line using a given reader. CR, LF, or CR + LF end a line.
     * However lines may be continued by beginning the next line with a space.
     * The end of line and end of file characters and continuation space are
     * dropped.
     * @param in reader for a JAD
     * @return one line of the JAD or null at the end of the JAD
     * @exception IOException thrown by the reader
     */
    protected String readLine(Reader in) throws IOException {
        int lastChar = 0;
	int room;
	int offset = 0;
	int c;
        char[] temp;

	room = lineBuffer.length;

        if (remainder != NO_REMAINDER) {
            c = remainder;
            remainder = NO_REMAINDER;
        } else {
            c = in.read();
        }

        for (; c != -1; lastChar = c, c = in.read()) {
            /*
             * if we read the end of the line last time and the next line
             * does not begin with a space we are done. But save this character
             * for next time. CR | LF | CR LF ends a line.
             */
            if (lastChar == LF) {
                if (c == SP) {
                    // Marks a continuation line, throw away the space
                    continue;
                }
                    
                remainder = c;
                break;
            }

            if (lastChar == CR) {
                if (c == SP) {
                    // Marks a continuation line, throw away the space
                    continue;
                }

                if (c != LF) {
                    remainder = c;
                    break;
                }
            }

            /*
             * do not include the end of line characters and the end
             * of file character.
             */
            if (c == CR || c == LF || c == EOF) {
                continue;
            }

            if (--room < 0) {
                temp = new char[offset + 128];
                room = temp.length - offset - 1;
                System.arraycopy(lineBuffer, 0, temp, 0, offset);
                lineBuffer = temp;
            }

            lineBuffer[offset++] = (char) c;
	}

	if ((c == -1) && (offset <= 0)) {
	    return null;
	}

        return new String(lineBuffer, 0, offset);
    }

    /**
     * Check to see if all the chars in the key of a property are valid.
     *
     * @param key key to check
     *
     * @return false if a character is not valid for a key
     */
    protected boolean checkKeyChars(String key) {
        char[] temp = key.toCharArray();
        int len = temp.length;

        for (int i = 0; i < len; i++) {
            char current = temp[i];

            if (current >= 'A' && current <= 'Z') {
                continue;
            }

            if (current >= 'a' && current <= 'z') {
                continue;
            }

            if (current >= '0' && current <= '9') {
                continue;
            }

            if (i > 0 && (current == '-' || current == '_')) {
                continue;
            }

            return false;
        }

        return true;
    }

    /**
     * Check to see if all the chars in the value of a property are valid.
     *
     * @param value value to check
     *
     * @return false if a character is not valid for a value
     */
    protected boolean checkValueChars(String value) {
        char[] temp = value.toCharArray();
        int len = temp.length;

        // assume whitespace and new lines are trimmed
        for (int i = 0; i < len; i++) {
            if (temp[i] == 0) {
                return false;
            }
        }

        return true;
    }
}
