/*
 *   
 *
 * 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. 
 *
 */


/**
 * This tool takes XML file describing skin as input and produces Java
 * and C files with romized skin's properties values. Those files are
 * intended to be compiled with the rest of Chameleon source files.
 *
 */

package com.sun.midp.skinromization;

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

import javax.xml.parsers.*;
import org.w3c.dom.*;

import java.awt.image.*;
import java.awt.*;
import com.sun.midp.imageutil.*;
import java.lang.reflect.*;
import com.sun.midp.chameleon.skins.resources.*;

/**
 * Represents romization job
 */
class RomizationJob {
    /** XML file name to get skin properties from */
    public String skinXMLFileName = "";

    /** Skin images directory name */
    public String skinImagesDirName = "";

    /** Output file name for RomizedSkin class */
    public String outBinFileName = "";

    /** Output file name for romized images data */
    public String outCFileName = "";

    /** For QA purposes: overrides images romization settings from XML */
    public String imageRomOverride = "";
}


/**
 * Main tool class
 */
public class SkinRomizationTool {
    /** Romizer that does actual romization */
    private static SkinRomizer romizer = null;

    /** Romization job to perform */
    private static RomizationJob romizationJob = null;

    /** Print debug output while running */
    private static boolean debug = false;
    
    /** Print usage info and exit */
    private static boolean printHelp = false;

    /**
     * Main method
     *
     * @param args Command line arguments
     */
    public static void main(String[] args) {
        try {
            parseArgs(args);
            if (printHelp) {
                printHelp();
                return;
            }
            
            if (!validateParsedArgs()) {
                System.exit(1);
            }
            
            romizer = new SkinRomizer(debug);
            romizer.romize(romizationJob);
        } catch (Exception e) {
            e.printStackTrace();
            System.exit(1);
        }
    }

    /**
     * Parses command line arguments
     *
     * @param args command line arguments
     */
    private static void parseArgs(String[] args)
    {
        romizationJob = new RomizationJob();

        for (int i = 0; i < args.length; ++i) {
            String arg = args[i];
            if (arg.equals("-xml")) {
                romizationJob.skinXMLFileName = args[++i];
            } else if (arg.equals("-outbin")) {
                romizationJob.outBinFileName = args[++i];
            } else if (arg.equals("-imagedir")) {
                romizationJob.skinImagesDirName = args[++i];
            } else if (arg.equals("-outc")) {
                romizationJob.outCFileName = args[++i];
            // this option is for QA purposes only and therefore 
            // hidden, undocumented and unsupported
            } else if (arg.equals("-qaimagerom")) {
                romizationJob.imageRomOverride = args[++i];
            } else if (arg.equals("-debug")) {
                debug = true;
            } else {
                throw new IllegalArgumentException("invalid option \"" 
                        + args[i] + "\"");
            }
        }
    }

    /**
     * Validates parsed arguments, printing error message
     * if some arg is invalid.
     *
     * @return true, if all arguments are valid
     */
    private static boolean validateParsedArgs() {
        File f = new File(romizationJob.skinXMLFileName);
        if (!f.isFile()) {
            System.err.println(
                    "SkinRomizationTool: Couldn't find input XML file: "
                    + '"' + romizationJob.skinXMLFileName + '"');

            return false;
        }

        f = new File(romizationJob.skinImagesDirName);
        if (!f.isDirectory()) {
            System.err.println("SkinRomizationTool: " + 
                    '"' + romizationJob.skinImagesDirName + '"' 
                    + " isn't a directory");

            return false;
        }
        
        return true;
    }
    
    /**
     * Prints usage information
     */
    private static void printHelp() {
        /**
         * Following options are recognized:
         * -xml:        XML file describing skin.
         * -out:        Output file. If empty, output will be to stdout.
         * -help:       Print usage information
         * -debug:      Be verbose: print some debug info while running. 
         *
         */
        System.err.println("Usage: java -jar "
            + "com.sun.midp.SkinRomizationTool "
            + "-xml <localXMLFile> " 
            + "-imagedir <skinImagesDirName> "
            + "-outbin <localOutputBinFile> "
            + "-outc <localOutputCFile> "
            + "[-debug] "
            + "[-help]");
    }
}

/**
 * Base class for all skin properties
 */
abstract class SkinPropertyBase {
    
    /** Print some debug info */
    static boolean debug = false;
    
    /** Name of property ID */
    String idName;

    /** Integer number used as property ID */
    int id;

    /** Offset of the property value(s) in romized values array */
    int valueOffset;
    
    /**
     * Constructor
     *
     * @param idName name of the property's ID
     * @param id property's ID
     * @exception IllegalArgumentException if some of parameters 
     * are invalid
     */
    SkinPropertyBase(String idName, String id) {
        this.idName = idName;

        try {
            this.id = Integer.parseInt(id);
        } catch (NumberFormatException e) {
            throw new IllegalArgumentException(
                    "Skin property " + '"' + idName + '"' +
                    " has illegal ID value: " + '"' + id + '"');
        }

        // offset will be set later
        this.valueOffset = -1;
    }

    /**
     * Tests if two properties have the same value
     *
     * @param prop property to compare value with
     * @return true if properties have the same value
     */
    abstract boolean isEqualValue(SkinPropertyBase prop);

    /**
     * How many entries in values array is required for 
     * storing this property value
     *
     * @return number of array entries required for storing
     * the value of this property
     */
    abstract int getValueOffsetDelta();

    /**
     * Prints values array entries for this property's value
     *
     * @param writer where to print entries
     * @param indent indentation string for each entry
     */
    abstract void outputValue(BinaryOutputStream out) 
        throws java.io.IOException;

    /**
     * Factory method: constructs SkinProperty from DOM node that
     * corresponds to the property description in XML file.`
     *
     * @param n DOM node that corresponds to the property 
     *          description in XML file
     * @return SkinPropertyBase object constructed from DOM node
     * @exception IllegalArgumentException if DOM node is invalid
     */
    static SkinPropertyBase valueOf(Node n) 
        throws IllegalArgumentException {
            
        if (debug) {
            System.err.println("Property node: " + n.getNodeName());
        }

        SkinPropertyBase p = null;
            
        String idName = getAttributeValue(n, "Key");
        String id = getAttributeValue(n, "KeyValue");
        String value = getAttributeValue(n, "Value");
         
        if (debug) {
            System.err.println("\tID name: " + idName);
            System.err.println("\tID: " + id);
            System.err.println("\tValue: " + value);
            System.err.println("");
        }

        String nodeName = n.getNodeName();

        if (nodeName.equals("integer")) {
            p = new IntSkinProperty(idName, id, value);
        } else if (nodeName.equals("integer_seq")) {
            p = new IntSeqSkinProperty(idName, id, value);
        } else if (nodeName.equals("string")) {
            p = new StringSkinProperty(idName, id, value);
        } else if (nodeName.equals("font")) {
            p = new FontSkinProperty(idName, id, value);
        } else if (nodeName.equals("image")) {
            String isRomized = getAttributeValue(n, "Romized");
            p = new ImageSkinProperty(idName, id, value, isRomized);
        } else if (nodeName.equals("composite_image")) {
            String totalPieces = getAttributeValue(n, "Pieces");
            String isRomized = getAttributeValue(n, "Romized");
            p = new CompositeImageSkinProperty(idName, id, value,
                    totalPieces, isRomized);
        } else {
            throw new IllegalArgumentException(
                    "Illegal skin property node: " + '"' + nodeName + '"');
        }

        return p;
    }

    /**
     * Helper method: gets attribute value from node with some
     * errors checking.
     *
     * @param n DOM node to get attribute value from
     * @param attrName attribute name
     * @return requested attribute value
     */
    private static String getAttributeValue(Node n, String attrName) {
        Element e = (Element)n;
        String attrValue = e.getAttribute(attrName);

        return attrValue.trim();
    }

    /**
     * Helper method for renaming symbolic property value coming
     * from XML file (constant name) into another constant name.
     * Needed for backward compatibility.
     *
     * @param value original value
     * @return renamed value
     */
    protected static String renameSymbolicValue(String value) {
        /**
         * Value can be in form of "Graphics.VALUE" where "VALUE" is 
         * the name of one of the constants defined in Graphics class.
         * Convert such value into "SkinResourcesConstants.VALUE".
         * The reason why "SkinResourcesConstants.VALUE" is not used 
         * directly in XML file is backward compatibility.
         */
        if (value.startsWith("Graphics.")) {
            int dotIdx = value.indexOf(".");
            value = "SkinResourcesConstants" + value.substring(dotIdx);
        }

        /**
         * Value can be in form of "ScrollIndSkin.VALUE" where "VALUE" is 
         * the name of one of the constants defined in 
         * ScrollIndResourcesConstants class.
         * Convert such value into "ScrollIndResourcesConstants.VALUE".
         * The reason why "ScrollIndResourcesConstants.VALUE" is not 
         * used directly in XML file is backward compatibility.
         */
        if (value.startsWith("ScrollIndSkin.")) {
            int dotIdx = value.indexOf(".");
            value = "ScrollIndResourcesConstants" + value.substring(dotIdx);
        }

        /**
         * Value can be in form of "FontResources.VALUE" where "VALUE" is 
         * the name of one of the constants defined in 
         * FontResourcesConstants class.
         * Convert such value into "FontResourcesConstants.VALUE".
         * The reason why "FontResourcesConstants.VALUE" is not 
         * used directly in XML file is backward compatibility.
         */
        if (value.startsWith("FontResources.")) {
            int dotIdx = value.indexOf(".");
            value = "FontResourcesConstants" + value.substring(dotIdx);
        }

        return value;
    }

    /**
     * Helper method for convertic symbolic property value into int value.
     * Basically, it converts integer constant name into constant value.
     *
     * @param value symbolic value
     * @return integer value
     */
    protected static int symbolicValueToInt(String value) {
        int dotIdx = value.indexOf(".");
        if (dotIdx == -1 || dotIdx == value.length() - 1) {
            throw new IllegalArgumentException();
        }

        String className = value.substring(0, dotIdx);
        if (className.length() == 0) {
            throw new IllegalArgumentException(); 
        }

        className = "com.sun.midp.chameleon.skins.resources." + className;
        Class c = null;
        try {
            c = Class.forName(className);
        } catch (Throwable t) {
            throw new IllegalArgumentException();
        }

        String fieldName = value.substring(dotIdx + 1);
        if (fieldName.length() == 0) {
            throw new IllegalArgumentException();
        }

        Field f = null;
        try {
            f = c.getField(fieldName);
        } catch (Throwable t) {
            throw new IllegalArgumentException();
        }

        int val = 0;
        try {
            val = f.getInt(null);
        } catch (Throwable t) {
            throw new IllegalArgumentException();
        }

        return val;
    }

    /**
     * Property value can be primitive arithmetic expression. 
     * This helper method evaluates such expression. Operators
     * precedence is simple, left to right.
     *
     * @param exp expression to evaluate
     * @return integer expression value
     */
    protected static int evalValueExpression(String exp) {
        int result = 0;
        char op = '=';

        StringTokenizer st = new StringTokenizer(exp);
        while (true) {
            String v = renameSymbolicValue(st.nextToken());

            int intVal = 0;
            try {
                int radix = 10;
                if (v.startsWith("0x")) {
                    v = v.substring(2);
                    radix = 16;
                }
                intVal = Integer.parseInt(v, radix);
            } catch (NumberFormatException e) {
                intVal = symbolicValueToInt(v);
            }

            switch (op) {
                case '=':
                    result = intVal;
                    break;

                case '|':
                    result |= intVal;
                    break;

                default:
                    throw new IllegalArgumentException();
            }

            if (!st.hasMoreTokens()) {
                break;
            }

            String opStr = st.nextToken();
            if (opStr.length() > 1) {
                throw new IllegalArgumentException(); 
            }
            op = (opStr.toCharArray())[0];
        }

        return result;
    }
}

/**
 * Integer property
 */
class IntSkinProperty extends SkinPropertyBase {
    
    /**
     * Property's value: integer number 
     */
    int value;

    /**
     * Constructor
     * 
     * @param idName name of the property's ID
     * @param id property's ID
     * @param value integer value as string
     */
    IntSkinProperty(String idName, String id, String value) {
        super(idName, id);

        String v = value;
        try {
            this.value = evalValueExpression(v);
        } catch (Throwable t) {
            throw new IllegalArgumentException(
                    "Invalid int property value: " + value);
        }
    }

    /**
     * Tests if two properties have the same value
     *
     * @param prop property to compare value with
     * @return true if properties have the same value
     */
    boolean isEqualValue(SkinPropertyBase prop) {
        if (!(prop instanceof IntSkinProperty)) {
            return false;
        }
        IntSkinProperty p = (IntSkinProperty)prop;

        return (p.value == value);
    }

    /**
     * How many entries in values array is required for 
     * storing this property value
     *
     * @return number of array entries required for storing
     * the value of this property
     */
    int getValueOffsetDelta() {
        // For performance optimization, integers values aren't stored in
        // separate values array as in case for other properties. So there
        // is no offset delta for integer value.
        return 0;
    }

    /**
     * Prints values array entries for this property's value
     *
     * @param writer where to print entries
     * @param indent indentation string for each entry
     */
    void outputValue(BinaryOutputStream out) 
        throws java.io.IOException {

        out.writeInt(value);
    }
}

/**
 * Integers sequence property
 */
class IntSeqSkinProperty extends SkinPropertyBase {

    /**
     * Propety's value: array of integers 
     */
    int[] value;

    /**
     * Constructor
     *
     * @param idName name of the property's ID
     * @param id property's ID
     * @param value comma separated integer values as string
     */
    IntSeqSkinProperty(String idName, String id, String value) {
        super(idName, id); 
        
        // find number of integers in sequence
        int seqLen = 0;
        char[] chars = value.toCharArray();
        int numStart = 0;
        for (int i = 0; i <= chars.length; ++i) {
            if (i == chars.length || chars[i] == ',') {
                seqLen++;
                numStart = i + 1;
            }
        }

        this.value = new int[seqLen];
        
        // fill values array
        int idx = 0;
        numStart = 0;
        for (int i = 0; i <= chars.length; ++i) {
            if (i == chars.length || chars[i] == ',') {
                String v = (value.substring(numStart, i)).trim();
                int intVal = 0;
                try {
                    intVal = evalValueExpression(v);
                } catch (Throwable t) {
                    throw new IllegalArgumentException(
                        "Invalid integer sequence property value: " + value);
                }
                
                this.value[idx++] = intVal;
                numStart = i + 1;
            }
        }
    }

    /**
     * Tests if two properties have the same value
     *
     * @param prop property to compare value with
     * @return true if properties have the same value
     */
    boolean isEqualValue(SkinPropertyBase prop) {
        if (!(prop instanceof IntSeqSkinProperty)) {
            return false;
        }
        IntSeqSkinProperty p = (IntSeqSkinProperty)prop;
        
        if (p.value.length != value.length) {
            return false;
        }
        
        for (int i = 0; i < value.length; ++i) {
            if (p.value[i] != value[i]) {
                return false;
            }
        }

        return true;
    }

    /**
     * How many entries in values array is required for 
     * storing this property value
     *
     * @return number of array entries required for storing
     * the value of this property
     */
    int getValueOffsetDelta() {
        // integers sequence value is stored as length of 
        // the sequence followed by sequence integers
        return (1 + value.length);
    }

    /**
     * Prints values array entries for this property's value
     *
     * @param writer where to print entries
     * @param indent indentation string for each entry
     */
    void outputValue(BinaryOutputStream out) 
        throws java.io.IOException {

        // out sequence length
        out.writeInt(value.length);

        // out sequence integers
        for (int i = 0; i < value.length; ++i) {
            out.writeInt(value[i]);
        }
    }
}

/**
 * String property
 */
class StringSkinProperty extends SkinPropertyBase {

    /**
     * Property's value: string
     */
    String value;

    /**
     * Constructor
     *
     * @param idName name of the property's ID
     * @param id property's ID
     * @param value string value
     */
    StringSkinProperty(String idName, String id, String value) {
        super(idName, id);
        this.value = value;
    }

    /**
     * Tests if two properties have the same value
     *
     * @param prop property to compare value with
     * @return true if properties have the same value
     */
    boolean isEqualValue(SkinPropertyBase prop) {
        if (!(prop instanceof StringSkinProperty)) {
            return false;
        }
        StringSkinProperty p = (StringSkinProperty)prop;

        return p.value.equals(value);
    }

    /**
     * How many entries in values array is required for 
     * storing this property value
     *
     * @return number of array entries required for storing
     * the value of this property
     */
    int getValueOffsetDelta() {
        return 1;
    }

    /**
     * Prints values array entries for this property's value
     *
     * @param writer where to print entries
     * @param indent indentation string for each entry
     */
    void outputValue(BinaryOutputStream out) 
        throws java.io.IOException {

        out.writeString(value);
    }
}

/**
 * Font property
 */
class FontSkinProperty extends SkinPropertyBase {
    
    /**
     * Property's value: integer font type 
     */
    int value;

    /**
     * Constructor
     *
     * @param idName name of the property's ID
     * @param id property's ID
     * @param value integer font ID as string
     */
    FontSkinProperty(String idName, String id, String value) {
        super(idName, id);

        String v = value;
        try {
            this.value = evalValueExpression(v);
        } catch (Throwable t) {
            throw new IllegalArgumentException(
                    "Invalid font property value: " + value);
        }
    }

    /**
     * Tests if two properties have the same value
     *
     * @param prop property to compare value with
     * @return true if properties have the same value
     */
    boolean isEqualValue(SkinPropertyBase prop) {
        if (!(prop instanceof FontSkinProperty)) {
            return false;
        }
        FontSkinProperty p = (FontSkinProperty)prop;

        return (p.value == value);
    }

    /**
     * How many entries in values array is required for 
     * storing this property value
     *
     * @return number of array entries required for storing
     * the value of this property
     */
    int getValueOffsetDelta() {
        return 1;
    }

    /**
     * Prints values array entries for this property's value
     *
     * @param writer where to print entries
     * @param indent indentation string for each entry
     */
    void outputValue(BinaryOutputStream out) 
        throws java.io.IOException {

        out.writeInt(value);
    }
}

/**
 * Image property
 */
class ImageSkinProperty extends SkinPropertyBase {

    /**
     * Property's value: image file name without extension
     */
    String value;

    /**
     * Romize this image
     */
    boolean isRomized;

    /**
     * True, if image value has been specified,
     * i.e. it is not an empty string
     */
    boolean hasValue;

    /** total unique images for all properties */
    static int totalImages;
    
    /**
     * Constructor
     *
     * @param idName name of the property's ID
     * @param id property's ID
     * @param value image file name without extension
     * @param isRomized boolean value as string
     * @exception IllegalArgumentException if some of parameters
     * are invalid
     */
    ImageSkinProperty(String idName, String id, String value, 
            String isRomized) 
        throws IllegalArgumentException {
            
        super(idName, id);
        this.value = value;

        if (isRomized.equals("true")) {
            this.isRomized = true; 
        } else if (isRomized.equals("false")) {
            this.isRomized = false;
        } else {
            throw new IllegalArgumentException(
                    "Skin property " + '"' + idName + '"' +
                    " has illegal romization value: " + 
                    '"' + isRomized + '"');
        }

        hasValue = true;
        if (value.equals("")) {
            hasValue = false;
        }       
    }

    /**
     * Tests if two properties have the same value
     *
     * @param prop property to compare value with
     * @return true if properties have the same value
     */
    boolean isEqualValue(SkinPropertyBase prop) {
        if (!(prop instanceof ImageSkinProperty)) {
            return false;
        }
        ImageSkinProperty p = (ImageSkinProperty)prop;

        return p.value.equals(value);
    }

    /**
     * How many entries in values array is required for 
     * storing this property value
     *
     * @return number of array entries required for storing
     * the value of this property
     */
    int getValueOffsetDelta() {
        return 1;
    }

    /**
     * Prints values array entries for this property's value
     *
     * @param writer where to print entries
     * @param indent indentation string for each entry
     */
    void outputValue(BinaryOutputStream out) 
        throws java.io.IOException {

        out.writeString(value);
    }
}

/**
 * Composite image property. Composite image is an image that consists
 * of several pieces, where each piece is some image file.
 */
class CompositeImageSkinProperty extends SkinPropertyBase {
    /**
     * Property's value: pieces files names without extension.
     * File name for each piece is constructed from base prefix by
     * addition of piece number, i.e. if the prefix is "image" and 
     * number of pieces is 2, then file name for first piece is 
     * "image0" and for the second piece is "image1".
     */
    String[] value;

    /**
     * Number of pieces
     */
    int totalPieces;

    /**
     * Romize this image
     */
    boolean isRomized;

    /**
     * True, if image value has been specified,
     * i.e. it is not an empty string
     */
    boolean hasValue;
    
    /** total unique images for all properties */
    static int totalImages;

    /**
     * Constructor
     *
     * @param idName name of the property's ID
     * @param id property's ID
     * @param value base prefix for pieces files names
     * @param totalPieces number of pieces in composite image
     * @param isRomized boolean value as string
     * @exception IllegalArgumentException if some of parameters
     * are invalid
     */
    CompositeImageSkinProperty(String idName, String id, String value, 
            String totalPieces, String isRomized) 
        throws IllegalArgumentException {
        
        super(idName, id);
        

        try {
            this.totalPieces = Integer.parseInt(totalPieces);
        } catch (NumberFormatException e) {
            throw new IllegalArgumentException(
                    "Composite image skin property " + '"' + idName + '"' +
                    " has illegal total pieces value: " + '"' + 
                    totalPieces + '"');
        }


        this.value = new String[this.totalPieces];
        for (int i = 0; i < this.totalPieces; ++i) {
            String v = "";
            if (!value.equals("")) {
                v = value + i;
            }
            
            this.value[i] = v;
        }

        if (isRomized.equals("true")) {
            this.isRomized = true; 
        } else if (isRomized.equals("false")) {
            this.isRomized = false;
        } else {
            throw new IllegalArgumentException(
                    "Skin property " + '"' + idName + '"' +
                    " has illegal romization value: " + 
                    '"' + isRomized + '"');
        }

        hasValue = true;
        if (value.equals("")) {
            hasValue = false;
        }
    }

    /**
     * Tests if two properties have the same value
     *
     * @param prop property to compare value with
     * @return true if properties have the same value
     */
    boolean isEqualValue(SkinPropertyBase prop) {
        if (!(prop instanceof CompositeImageSkinProperty)) {
            return false;
        }
        CompositeImageSkinProperty p = (CompositeImageSkinProperty)prop;

        if (p.value.length != value.length) {
            return false;
        }
        
        boolean equal = true;
        for (int i = 0; i < value.length; ++i) {
            if (!(p.value[i].equals(value[i]))) {
                equal = false;
                break;
            }
        }

        return equal;
    }

    /**
     * How many entries in values array is required for 
     * storing this property value
     *
     * @return number of array entries required for storing
     * the value of this property
     */
    int getValueOffsetDelta() {
        // for composite image, values array contain 
        // file names of image pieces
        return value.length;
    }

    /**
     * Prints values array entries for this property's value
     *
     * @param writer where to print entries
     * @param indent indentation string for each entry
     */
    void outputValue(BinaryOutputStream out) 
        throws java.io.IOException {

        // output pieces file names
        for (int i = 0; i < value.length; ++i) {
            out.writeString(value[i]);
        }
    }
}

/**
 * Represents romized image
 */
final class RomizedImage {
    /** romized image data */
    byte[] imageData;

    /** romized image index */
    int imageIndex;
    
    /**
     * Constructor
     *
     * @param imageData romized image data
     * @param imageIndex romized image index
     */
    RomizedImage(byte imageData[], int imageIndex) {
        this.imageData = imageData;
        this.imageIndex = imageIndex;
    }

    /**
     * Prints romized image data as C array
     *
     * @param writer where to print
     * @param indent indent string for each row
     * @param maxColumns max number of columns
     */
    void printDataArray(PrintWriter writer, String indent, int maxColumns) {
        int len = imageData.length;

        writer.print(indent);
        for (int i = 0; i < len; i++) {
            writer.print(toHex(imageData[i]));
            if (i != len - 1) {
                writer.print(", ");
            
                if ((i > 0) && ((i+1) % maxColumns == 0)) {
                    writer.println("");
                    writer.print(indent);
                }
            }
        }
    }

    /**
     * Converts byte to a hex string
     *
     * @param b byte value to convert
     * @return hex representation of byte
     */
    private static String toHex(byte b) {
        Integer I = new Integer((((int)b) << 24) >>> 24);
        int i = I.intValue();

        if (i < (byte)16) {
            return "0x0" + Integer.toString(i, 16);
        } else {
            return "0x" + Integer.toString(i, 16);
        }
    }     
}

/**
 * Creates RomizedImage instances
 */
final class RomizedImageFactory {
    /** for converting image into raw format */
    ImageToRawConverter converter;
    
    /** romized images counter */
    int romizedImageCounter = 0;
    
    /**
     * Constructor
     *
     * @param converter converter to use for converting images
     * into raw format
     */
    private RomizedImageFactory(ImageToRawConverter converter) {
        this.converter = converter;
    }
    
    /**
     * Creates RomizedImage from BufferedImage object
     *
     * @param image image to create romized image from
     * @param imageIndex romized image index
     * @return created RomizedImage
     */
    RomizedImage createFromBufferedImage(BufferedImage image, 
            int imageIndex) {

        int width = image.getWidth(null);
        int height = image.getHeight(null);
        boolean hasAlpha = image.getColorModel().hasAlpha();
        int[] imageData = getBufferedImageData(image);
        
        byte[] rawData = converter.convertToRaw(imageData, width, height, 
                hasAlpha);

        return new RomizedImage(rawData, romizedImageCounter++);
    }
    
    /**
     * Utility method. Gets BuffredImage data as in array.
     *
     * @param image BufferedImage to return data for
     * @return image data as byte array
     */
    private static int[] getBufferedImageData(BufferedImage image) {
        int width = image.getWidth(null);
        int height = image.getHeight(null);
        BufferedImage bi = new BufferedImage(width, height, 
                BufferedImage.TYPE_INT_ARGB);
        
        Graphics g = bi.getGraphics(); 
        try {
            g.drawImage(image, 0, 0, width, height, null);
        } finally {
            g.dispose();
        }
        
        DataBuffer srcBuf = bi.getData().getDataBuffer();
        int[] buf = ((DataBufferInt)srcBuf).getData();

        return buf;
    }

    /**
     * Returns factory class instance
     *
     * @param rawFormat format of raw file
     * @param colorFormat format of pixel in raw file
     * @param endian romized image data endianess
     * @return RomizedImage factory
     */
    static RomizedImageFactory getFactory(int rawFormat, int colorFormat, 
            int endian) {

        ImageToRawConverter converter = new ImageToRawConverter(rawFormat, 
                colorFormat, endian);

        return new RomizedImageFactory(converter);
    }
}

/**
 * Binary output stream capable of writing data 
 * in big/little endian format.
 */
final class BinaryOutputStream {
    /** Underlying stream for writing bytes into */ 
    private DataOutputStream outputStream = null;

    /** true for big endian format, false for little */
    private boolean isBigEndian = false;

    /**
     * Constructor
     *
     * @param out underlying output stream for writing bytes into
     * @param isBigEndian true for big endian format, false for little
     */
    BinaryOutputStream(OutputStream out, boolean isBigEndian) {
        this.outputStream = new DataOutputStream(out);
        this.isBigEndian = isBigEndian;
    }

    /**
     * Writes byte value into stream
     *
     * @param value byte value to write
     */
    public void writeByte(int value) 
        throws java.io.IOException {

        outputStream.writeByte(value);
    }

    /**
     * Writes integer value into stream
     *
     * @param value integer value to write
     */
    public void writeInt(int value) 
        throws java.io.IOException {

        if (isBigEndian) {
            outputStream.writeByte((value >> 24) & 0xFF);
            outputStream.writeByte((value >> 16) & 0xFF);
            outputStream.writeByte((value >> 8) & 0xFF);
            outputStream.writeByte(value & 0xFF);
        } else { 
            outputStream.writeByte(value & 0xFF);
            outputStream.writeByte((value >> 8) & 0xFF);
            outputStream.writeByte((value >> 16) & 0xFF);
            outputStream.writeByte((value >> 24) & 0xFF);
        }
    }

    /**
     * Writes string into stream. The string data is written 
     * in follwoing order:
     * - Number of bytes for string chars
     * - Encoding (US ASCII or UTF8)
     * - String chars as bytes
     * 
     * The number of bytes for string chars is written as 
     * single byte, so it can't exceed 255.
     *
     * @param value String value to write into stream
     */
    public void writeString(String value) 
        throws java.io.IOException {

        byte[] chars = value.getBytes("UTF8");
        int length = chars.length;

        // determine what encoding to use
        int encoding = SkinResourcesConstants.STRING_ENCODING_USASCII;
        for (int i = 0; i < length; ++i) {
            int ch = chars[i] & 0xFF;
            if (ch >= 128) {
                encoding = SkinResourcesConstants.STRING_ENCODING_UTF8;
                break;
            }
        }

        if (encoding == SkinResourcesConstants.STRING_ENCODING_UTF8) {
            System.err.println("UTF8: " + value);
            // for '\0' at the end of the string
            length += 1;
        }

        // write string data length
        if (length > 255) {
            throw new IllegalArgumentException(
                    "String data length exceeds 255 bytes");
        }
        outputStream.writeByte(length);

        // write string encoding
        outputStream.writeByte(encoding);

        // write string data
        for (int i = 0; i < chars.length; ++i) {
            outputStream.writeByte(chars[i] & 0xFF);
        }

        if (encoding == SkinResourcesConstants.STRING_ENCODING_UTF8) {
            // '\0' at the end of the string
            outputStream.writeByte(0);
        }
    }

    /**
     * Closes stream
     */
    public void close() 
        throws java.io.IOException {

        outputStream.close();
    }
}

/**
 * Perform the romization
 */
class SkinRomizer {
    /** current romization job */
    RomizationJob romizationJob;
    
    /** XML with skin properties as Document */
    private Document domDoc = null;

    /** Be verbose: print some debug info while running */
    private boolean debug = false;

    /** All skin properties */
    Vector allProps = null;

    /** All integers sequence skin properties */
    Vector intSeqProps = null;

    /** All string skin properties */
    Vector stringProps = null;

    /** All integers font skin properties */
    Vector fontProps = null;

    /** All image skin properties */
    Vector imageProps = null;

    /** All composite image skin properties */
    Vector compImageProps = null;

    /** All romized images */
    Vector romizedImages = null;

    /** Romized image factory */
    RomizedImageFactory romizedImageFactory;

    /** Character output file writer */
    PrintWriter writer = null;

    /** Binary output file stream */
    BinaryOutputStream outputStream = null;
    
    /** raw image file format */
    int rawFormat = ImageToRawConverter.FORMAT_INVALID;

    /** raw image color format */
    int colorFormat = ImageToRawConverter.FORMAT_INVALID;

    /** raw image data endianess */
    int endianFormat = ImageToRawConverter.FORMAT_INVALID;

    /** array of strings used in XML file to specify raw image format */
    static String[] imageFormatStrings = {
        // raw file formats 
        "Putpixel",
        "ARGB",
        // endianess
        "Little",
        "Big",
        // supported pixel formats
        "565",
        "888",
    };

    /** array of constants corresponding to strings above */
    static int[] imageFormats = {
        // raw file formats 
        ImageToRawConverter.RAW_FORMAT_PP,
        ImageToRawConverter.RAW_FORMAT_ARGB,
        // endianess
        ImageToRawConverter.INT_FORMAT_LITTLE_ENDIAN,
        ImageToRawConverter.INT_FORMAT_BIG_ENDIAN,
        // supported pixel formats        
        ImageToRawConverter.COLOR_FORMAT_565,
        ImageToRawConverter.COLOR_FORMAT_888
    };
    
    /**
     * Constructor
     *
     * @param dbg print some debug output while running
     */
    public SkinRomizer(boolean dbg)
    {
        debug = dbg;
        SkinPropertyBase.debug = dbg;

    }

    /**
     * Does the actual romization
     * 
     * @param romizationJob romization to perform
     * @exception Exception if there was an error during romization
     */
    public void romize(RomizationJob romizationJob) 
        throws Exception {

        this.romizationJob = romizationJob;
            
        allProps = new Vector();
        intSeqProps = new Vector();
        stringProps = new Vector();
        fontProps = new Vector();
        imageProps = new Vector();
        compImageProps = new Vector();

        // load XML file as DOM tree 
        DocumentBuilderFactory domFactory = 
            DocumentBuilderFactory.newInstance();

        // do not validate input XML file,
        // we assume it has been validated before
        domFactory.setValidating(false);

        DocumentBuilder domBuilder = domFactory.newDocumentBuilder();
        domDoc = domBuilder.parse(new File(romizationJob.skinXMLFileName));

        // traverse DOM tree constructed fro input XML and 
        // collect all skin properties described there
        collectSkinProperties(domDoc.getDocumentElement());

        // now, when we know format of romized images,
        // we can create romized images factory
        romizedImageFactory = RomizedImageFactory.getFactory(
                rawFormat, colorFormat, endianFormat);
        
        // assign offsets
        assignPropertiesValuesOffsets(intSeqProps, 0);
        assignPropertiesValuesOffsets(stringProps, 0);
        assignPropertiesValuesOffsets(fontProps, 0);
        
        // values for composite image properties are stored in same
        // array as values for image properties. first, values for 
        // image properties are stored, followed by values for 
        // composite image properties.
        int lastOffset = assignPropertiesValuesOffsets(imageProps, 0); 
        ImageSkinProperty.totalImages = lastOffset - 1;
        lastOffset = assignPropertiesValuesOffsets(compImageProps, lastOffset);
        CompositeImageSkinProperty.totalImages = lastOffset - 1;

        // total unique skin images
        int totalImages = ImageSkinProperty.totalImages + 
            CompositeImageSkinProperty.totalImages;

        // if image isn't romized, then corresponding value will be null
        romizedImages = new Vector(totalImages);
        romizedImages.setSize(totalImages);

        romizeImages();

        // output generated file
        makeDirectoryTree(romizationJob.outBinFileName);

        OutputStream out = new BufferedOutputStream(new FileOutputStream(
                romizationJob.outBinFileName), 8192);
        outputStream = new BinaryOutputStream(out,
                endianFormat == ImageToRawConverter.INT_FORMAT_BIG_ENDIAN);

        writeBinHeader();
        writeRomizedProperties();
        outputStream.close();

        out = new FileOutputStream(romizationJob.outCFileName);
        writer = new PrintWriter(new OutputStreamWriter(out));

        writeCHeader();
        writeRomizedImagesData();
        writeGetMethod();
        writer.close();
    }

    /**
     * Walks the XML tree and collects all skin properties elements
     * 
     * @param n current DOM node in document
     * @exception Exception if there was an error during romization
     */
    private void collectSkinProperties(Node n) 
        throws Exception {
            
        if (n.getNodeName().equals("skin") && (n instanceof Element)) {
            NodeList list = n.getChildNodes();
            for (int i = 0; i < list.getLength(); i++) {
                collectSkinProperties(list.item(i));
            }           
        } else if (n.getNodeName().equals("rawimage") && 
            (n instanceof Element)) {

            collectImageFormat(n);           
        } else if (n.getNodeName().equals("skin_properties") &&
            (n instanceof Element)) {
                
            NodeList list = n.getChildNodes();
            for (int i = 0; i < list.getLength(); i++) {
                collectSkinProperty(list.item(i));
            }
        } else {
            NodeList list = n.getChildNodes();
            for (int i = 0; i < list.getLength(); i++) {
                collectSkinProperties(list.item(i));
            }
        }
    }

    /**
     * Collects raw images format specification
     * @param n current DOM node in document
     * @exception Exception if there was an error during romization
     */
    private void collectImageFormat(Node n) {
        Element e = (Element)n;
        
        String rawFormatStr = e.getAttribute("Format");
        rawFormat = ImageToRawConverter.FORMAT_INVALID; 
        for (int i = 0; i < imageFormatStrings.length; ++i) {
            if (imageFormatStrings[i].equals(rawFormatStr)) {
                rawFormat = imageFormats[i];
                break;
            }
        }
        if (rawFormat == ImageToRawConverter.FORMAT_INVALID) {
            throw new IllegalArgumentException("invalid raw file format " 
                    + '"' + rawFormatStr + '"');
        }
        
        String colorFormatStr = e.getAttribute("Colors");
        colorFormat = ImageToRawConverter.FORMAT_INVALID; 
        for (int i = 0; i < imageFormatStrings.length; ++i) {
            if (imageFormatStrings[i].equals(colorFormatStr)) {
                colorFormat = imageFormats[i];
                break;
            }
        }
        if (colorFormat == ImageToRawConverter.FORMAT_INVALID) {
            throw new IllegalArgumentException("invalid color format " 
                    + '"' + colorFormatStr + '"');
        }

        
        String endianFormatStr = e.getAttribute("Endian");
        endianFormat = ImageToRawConverter.FORMAT_INVALID; 
        for (int i = 0; i < imageFormatStrings.length; ++i) {
            if (imageFormatStrings[i].equals(endianFormatStr)) {
                endianFormat = imageFormats[i];
                break;
            }
        }
        if (endianFormat == ImageToRawConverter.FORMAT_INVALID) {
            throw new IllegalArgumentException("invalid color format " 
                    + '"' + endianFormatStr + '"');
        }

        if (!ImageToRawConverter.isFormatSupported(rawFormat, colorFormat)) {
            throw new IllegalArgumentException(
                    "unsupported romized image format: " 
                    + "raw file " + '"' + rawFormat + '"' 
                    + ", color " + '"' + colorFormat + '"');
        }
    }
    
    /**
     * Collects single skin property element.
     *
     * @param n DOM node corresponding to skin property description
     * in XML file
     * @exception Exception if there was an error during romization
     */
    private void collectSkinProperty(Node n) 
        throws Exception {

        if (n instanceof Element) {
            SkinPropertyBase p = SkinPropertyBase.valueOf(n);

            // ID of the property is the index into skinProperties vector
            if (allProps.size() < p.id + 1) {
                allProps.setSize(p.id + 1);
            }

            if (allProps.elementAt(p.id) != null) {
                throw new IllegalArgumentException(
                        "Duplicate skin property " + p.idName);
            }
            allProps.setElementAt(p, p.id);

            if (p instanceof IntSeqSkinProperty) {
                intSeqProps.add(p);
            } else if (p instanceof StringSkinProperty) {
                stringProps.add(p);
            } else if (p instanceof FontSkinProperty) {
                fontProps.add(p);
            } else if (p instanceof ImageSkinProperty) {
                ImageSkinProperty ip = (ImageSkinProperty)p;
                if (romizationJob.imageRomOverride.equals("all")) {
                    ip.isRomized = true;
                } else if (romizationJob.imageRomOverride.equals("none")) {
                    ip.isRomized = false;
                }
                
                imageProps.add(p);
            } else if (p instanceof CompositeImageSkinProperty) {
                CompositeImageSkinProperty ip = (CompositeImageSkinProperty)p;
                if (romizationJob.imageRomOverride.equals("all")) {
                    ip.isRomized = true;
                } else if (romizationJob.imageRomOverride.equals("none")) {
                    ip.isRomized = false;
                }
                
                compImageProps.add(p);
            }
        }
    }
    
    /**
     * Assigns offsets into properties values array
     *
     * @param props properties to assign offsets for
     * @param startOffset offset to start from
     * @return offset past the last property value
     */
    private static int assignPropertiesValuesOffsets(Vector props, 
            int startOffset) {

        // current (last) offset in the values array
        int curOffset = startOffset;
        
        for (int i = 0; i < props.size(); ++i) {
            SkinPropertyBase curP = (SkinPropertyBase)props.elementAt(i);

            // properties with same values share same offset,
            // so look if there already was property with same value
            int sameValueOffset = -1;
            for (int j = i - 1; j >= 0; --j) {
                SkinPropertyBase p = (SkinPropertyBase)props.elementAt(j);
                if (p.isEqualValue(curP)) {
                    sameValueOffset = p.valueOffset;
                    break;
                }
            }

            if (sameValueOffset != -1) {
                // use offset from property with same value
                curP.valueOffset = sameValueOffset;
            } else {
                // this value is new, give it current offset 
                curP.valueOffset = curOffset;
                curOffset += curP.getValueOffsetDelta();
            }
        }

        return curOffset;
    }

    /**
     * Romizes images
     *
     * @exception IOException if there was IO error during romization
     */
    private void romizeImages() 
        throws IOException {

        int maxOffset = -1;
        for (int i = 0; i < imageProps.size(); ++i) {
            ImageSkinProperty p = (ImageSkinProperty)imageProps.elementAt(i);
            if (!(p.isRomized) || !p.hasValue) {
                continue;
            }

            // this property has the same value as some other 
            // property seen before, so skip it
            if (p.valueOffset <= maxOffset) {
                continue;
            }

            romizeImage(p.value, p.valueOffset);
            maxOffset = p.valueOffset;
        }

        for (int i = 0; i < compImageProps.size(); ++i) {
            CompositeImageSkinProperty p = 
                (CompositeImageSkinProperty)compImageProps.elementAt(i);
            if (!(p.isRomized) || !p.hasValue) {
                continue;
            }

            // this property has the same value as some other 
            // property seen before, so skip it
            if (p.valueOffset <= maxOffset) {
                continue;
            }

            for (int j = 0; j < p.value.length; ++j) {
                romizeImage(p.value[j], p.valueOffset + j);
            }
            maxOffset = p.valueOffset;
        }

    }

    /**
     * Romizes single image
     *
     * @param imageName of the image to romize without extension
     * @param imageIndex romized image index
     * @exception IOException if there was IO error during romization
     */
    private void romizeImage(String imageName, int imageIndex) 
        throws IOException {

        // we romize png images only
        String imageFileName = imageName + ".png";
        imageFileName = romizationJob.skinImagesDirName + File.separator + 
            imageFileName;

        System.out.println("     " + imageFileName);
        
        // load image
        BufferedImage image = javax.imageio.ImageIO.read(
                new File(imageFileName));

        // and romize it
        RomizedImage ri = romizedImageFactory.createFromBufferedImage(image, 
                imageIndex);

        romizedImages.set(imageIndex, ri);
    }

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

        // write magic sequence
        int magicLength = SkinResourcesConstants.CHAM_BIN_MAGIC.length;
        for (int i = 0; i < magicLength; ++i) {
            byte b = (byte)(SkinResourcesConstants.CHAM_BIN_MAGIC[i] & 0xFF);
            outputStream.writeByte(b);
        }

        // write version info as an array 
        outputStream.writeInt(1); // array size
        outputStream.writeInt(SkinResourcesConstants.CHAM_BIN_FORMAT_VERSION);
    }

    /**
     *  Writes romized images C file header
     */
    private void writeCHeader() {
        writeCopyright();

        pl("");
        pl("#include <string.h>");
        pl("#include <kni.h>");
        pl("#include <midpError.h>");
        pl("#include <midpMalloc.h>");
        pl("#include <midpServices.h>\n");
    }
    
    /**
     * Writes romized properties data
     * @exception Exception if there was an error during output
     */
    private void writeRomizedProperties() 
        throws Exception {
            
        outputStream.writeInt(allProps.size());
        for (int i = 0; i < allProps.size(); ++i) {
            SkinPropertyBase p = (SkinPropertyBase)allProps.elementAt(i);
            if (p instanceof IntSkinProperty) {
                IntSkinProperty intP = (IntSkinProperty)p;
                // for integer property the array holds actual property's 
                // value, not an offset into values array
                p.outputValue(outputStream);
            } else {
                // for all other properties the array holds an offset into
                // values array, in which actual values are stored
                outputStream.writeInt(p.valueOffset);
            }
        }

        writePropertiesValues(intSeqProps);
        writePropertiesValues(stringProps);
        writePropertiesValues(fontProps);

        Vector v = new Vector(imageProps);
        v.addAll(compImageProps);
        writePropertiesValues(v);

        writeRomizedImagesIndexes();
    }
    
    /**
     * Writes properties values array entries
     *
     * @param props properties to write entries for 
     */
    private void writePropertiesValues(Vector props) 
        throws java.io.IOException {

        int maxOffset = -1;
        int totalValues = 0;
        for (int i = 0; i < props.size(); ++i) {
            SkinPropertyBase p = (SkinPropertyBase)props.elementAt(i);

            // this property has the same value as some other 
            // property printed before, so skip it
            if (p.valueOffset <= maxOffset) {
                continue;
            }

            totalValues += p.getValueOffsetDelta();
            maxOffset = p.valueOffset;
        }
        outputStream.writeInt(totalValues);

        maxOffset = -1;
        for (int i = 0; i < props.size(); ++i) {
            SkinPropertyBase p = (SkinPropertyBase)props.elementAt(i);

            // this property has the same value as some other 
            // property outputed before, so skip it
            if (p.valueOffset <= maxOffset) {
                continue;
            }

            p.outputValue(outputStream);
            maxOffset = p.valueOffset;
        }
    }
    
    /**
     * Writes indexes for romized images. 
     */
    private void writeRomizedImagesIndexes() 
        throws java.io.IOException {

        outputStream.writeInt(romizedImages.size());
        for (int i = 0; i < romizedImages.size(); ++i) {
            Object o = romizedImages.elementAt(i);
            // image with index = i isn't romized
            if (o == null) {
                outputStream.writeInt(-1);
            } else {
                RomizedImage ri = (RomizedImage)o;
                outputStream.writeInt(ri.imageIndex);
            }
        }
    }
    
    /**
     *  Writes romized images data
     */
    private void writeRomizedImagesData() {
        int totalRomizedImages = 0;
        for (int i = 0; i < romizedImages.size(); ++i) {
            if (romizedImages.elementAt(i) != null) {
                ++totalRomizedImages;
            }
        }
        
        pl("");
        pl("static const int NUM_ROM_IMAGES = " + totalRomizedImages + ";");
        
        if (totalRomizedImages != 0) {
            // output a structure declaration used for storing 
            // romized images data in it
            pl("");
            pl("struct romized_images_data {");
            for (int i = 0; i < romizedImages.size(); ++i) {
                Object o = romizedImages.elementAt(i);
                if (o != null) {
                    RomizedImage ri = (RomizedImage)o;

                    // this field ensures proper alignment of 
                    // subsequent data array
                    pl("    " + "const int align_" + ri.imageIndex + ";");

                    String dataArrayName = "romized_image" + ri.imageIndex;
                    int dataArrayLength = ri.imageData.length;
                    pl("    " + "const unsigned char " + dataArrayName + 
                            "[" + dataArrayLength + "];");
                }
            }
            pl("};");

            pl("");
            pl("static const struct romized_images_data " + 
                    "romized_images_data = {");
            for (int i = 0; i < romizedImages.size(); ++i) {
                Object o = romizedImages.elementAt(i);
                if (o != null) {
                    // alignemnt field
                    pl("    " + "0,");

                    RomizedImage ri = (RomizedImage)o;
                    String dataArrayName = "romized_image" + ri.imageIndex;

                    // romized image data field
                    pl("    " + "/* " + dataArrayName + " */");
                    pl("    " +"{");
                    ri.printDataArray(writer, "        ", 11);
                    pl(" },");
                }
            }
            pl("};");
        }

        pl("");
        pl("static const unsigned char* image_cache[] = {");
        // if there are no romized images, print dummy value
        // to make array non empty and keep compilers happy
        if (totalRomizedImages == 0) {
            pl("    NULL");
        } else {
            for (int i = 0; i < romizedImages.size(); ++i) {
                Object o = romizedImages.elementAt(i);
                if (o != null) {
                    RomizedImage ri = (RomizedImage)o;
                    String dataArrayName = 
                        "romized_images_data.romized_image" + ri.imageIndex;
                    pl("    " + dataArrayName + ",");
                }
            }
        }
        pl("};");

        pl("");
        pl("static const int image_size[] = {"); 
        // if there are no romized images, print dummy value
        // to make array non empty and keep compilers happy
        if (totalRomizedImages == 0) {
            pl("    0");
        } else {
            for (int i = 0; i < romizedImages.size(); ++i) {
                Object o = romizedImages.elementAt(i);
                if (o != null) {
                    RomizedImage ri = (RomizedImage)o;
                    String dataArrayName = 
                        "romized_images_data.romized_image" + ri.imageIndex;
                    pl("    sizeof(" + dataArrayName + "),");
                }
            }
        }
        pl("};");
    }
    
    /**
     * Writes get method for obtaining romized image data
     */
    void writeGetMethod() {
        pl("");
        pl("/**");
        pl(" * Loads a native image from rom, if present."); 
        pl(" *");
        pl(" * @param imageId    The image id");
        pl(" * @param **bufPtr   Pointer where a buffer will be "
		    + "allocated and data stored");
        pl(" * @return           -1 if failed, else length of buffer");
        pl(" */");
        pl("int lfj_load_image_from_rom(int imageId, "
		    + "unsigned char** bufPtr) {\n");
        pl("    int len = -1;");
        pl("    if ((imageId < 0) || (imageId > NUM_ROM_IMAGES)) {");
        pl("        REPORT_WARN1(LC_LOWUI,"); 
        pl("            \"Warning: could not load romized" +
		    "image for index %d; \", imageId); ");
        pl("        return len;");
        pl("    }\n");
        pl("    *bufPtr = (unsigned char*)image_cache[imageId];");
        pl("    len = image_size[imageId];");
        pl("    return len;");
        pl("}");
    }

    
    /**
     * Creates a directory structure.
     *
     * @param fullFileName Full path to the file to be created. If directory
     * in which file is to be created doesn't exists, it will be created
     * @exception IOException is thrown if directory couldn't be created 
     */
    private void makeDirectoryTree(String fullFileName) 
        throws IOException {

        if (debug == true) {
            System.out.println("mkdir: " + fullFileName);
        }
        int index = fullFileName.lastIndexOf(File.separatorChar);
        if (index == -1) {
            // To be compatible with MKS-hosted build on win32, which
            // does not translate / to \.
            index = fullFileName.lastIndexOf('/');
        }
        File outputDirectory = new File(fullFileName.substring(0, index));

        if (!(outputDirectory).exists()) {
            if (!(outputDirectory).mkdirs()) {
                throw new IOException("failed to create output directory");
            }
        }

    }

    /**
     * Short-hand for printint a line into the output file
     *
     * @param s line to print
     */
    void pl(String s) {
        writer.println(s);
    }
}
