/*
 * @(#)GTKParser.java	1.88 04/08/10
 *
 * Copyright 2004 Sun Microsystems, Inc. All rights reserved.
 * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 */

package com.sun.java.swing.plaf.gtk;

import java.util.*;
import java.io.*;
import java.awt.*;
import java.util.regex.PatternSyntaxException;
import javax.swing.plaf.ColorUIResource;
import java.security.AccessController;
import sun.security.action.GetPropertyAction;
import javax.swing.plaf.synth.SynthConstants;

/**
 * @author  Shannon Hickey
 * @version 1.88 08/10/04
 */
class GTKParser {
    
    private ArrayList freeScanners = new ArrayList();
    
    private HashMap namedStyles = new HashMap();
    
    private ArrayList assignments = new ArrayList();

    private HashMap settings = new HashMap();

    private File[] pixmapPaths = null;
    
    private ArrayList dirStack = new ArrayList();
    
    private HashMap engineParsers = new HashMap();

    // Register parsers here for now. Later we can add methods to register
    // new parser classes.
    {
        engineParsers.put("pixmap", "com.sun.java.swing.plaf.gtk.PixmapEngineParser");
        engineParsers.put("bluecurve", "com.sun.java.swing.plaf.gtk.BluecurveEngineParser");
        engineParsers.put("wonderland", "com.sun.java.swing.plaf.gtk.BluecurveEngineParser");
        engineParsers.put("blueprint", "com.sun.java.swing.plaf.gtk.BlueprintEngineParser");
    }
    
    private GTKScanner scanner;

    private final String CWD = (String)AccessController.doPrivileged(
                                           new GetPropertyAction("user.dir"));

    static class Symbol {
        
        public String name;
        public int val;
        
        public Symbol(String name, int val) {
            this.name = name;
            this.val = val;
        }
    }

    private static final Symbol SYMBOL_INVALID = new Symbol("invalid", GTKScanner.TOKEN_LAST);
    private static final Symbol SYMBOL_INCLUDE = new Symbol("include", SYMBOL_INVALID.val + 1);
    private static final Symbol SYMBOL_NORMAL = new Symbol("NORMAL", SYMBOL_INCLUDE.val + 1);
    private static final Symbol SYMBOL_ACTIVE = new Symbol("ACTIVE", SYMBOL_NORMAL.val + 1);
    private static final Symbol SYMBOL_PRELIGHT = new Symbol("PRELIGHT", SYMBOL_ACTIVE.val + 1);
    private static final Symbol SYMBOL_SELECTED = new Symbol("SELECTED", SYMBOL_PRELIGHT.val + 1);
    private static final Symbol SYMBOL_INSENSITIVE = new Symbol("INSENSITIVE", SYMBOL_SELECTED.val + 1);
    private static final Symbol SYMBOL_FG = new Symbol("fg", SYMBOL_INSENSITIVE.val + 1);
    private static final Symbol SYMBOL_BG = new Symbol("bg", SYMBOL_FG.val + 1);
    private static final Symbol SYMBOL_TEXT = new Symbol("text", SYMBOL_BG.val + 1);
    private static final Symbol SYMBOL_BASE = new Symbol("base", SYMBOL_TEXT.val + 1);
    private static final Symbol SYMBOL_XTHICKNESS = new Symbol("xthickness", SYMBOL_BASE.val + 1);
    private static final Symbol SYMBOL_YTHICKNESS = new Symbol("ythickness", SYMBOL_XTHICKNESS.val + 1);
    private static final Symbol SYMBOL_FONT = new Symbol("font", SYMBOL_YTHICKNESS.val + 1);
    private static final Symbol SYMBOL_FONTSET = new Symbol("fontset", SYMBOL_FONT.val + 1);
    private static final Symbol SYMBOL_FONT_NAME = new Symbol("font_name", SYMBOL_FONTSET.val + 1);
    private static final Symbol SYMBOL_BG_PIXMAP = new Symbol("bg_pixmap", SYMBOL_FONT_NAME.val + 1);
    private static final Symbol SYMBOL_PIXMAP_PATH = new Symbol("pixmap_path", SYMBOL_BG_PIXMAP.val + 1);
    private static final Symbol SYMBOL_STYLE = new Symbol("style", SYMBOL_PIXMAP_PATH.val + 1);
    private static final Symbol SYMBOL_BINDING = new Symbol("binding", SYMBOL_STYLE.val + 1);
    private static final Symbol SYMBOL_BIND = new Symbol("bind", SYMBOL_BINDING.val + 1);
    private static final Symbol SYMBOL_WIDGET = new Symbol("widget", SYMBOL_BIND.val + 1);
    private static final Symbol SYMBOL_WIDGET_CLASS = new Symbol("widget_class", SYMBOL_WIDGET.val + 1);
    private static final Symbol SYMBOL_CLASS = new Symbol("class", SYMBOL_WIDGET_CLASS.val + 1);
    private static final Symbol SYMBOL_LOWEST = new Symbol("lowest", SYMBOL_CLASS.val + 1);
    private static final Symbol SYMBOL_GTK = new Symbol("gtk", SYMBOL_LOWEST.val + 1);
    private static final Symbol SYMBOL_APPLICATION = new Symbol("application", SYMBOL_GTK.val + 1);
    private static final Symbol SYMBOL_THEME = new Symbol("theme", SYMBOL_APPLICATION.val + 1);
    private static final Symbol SYMBOL_RC = new Symbol("rc", SYMBOL_THEME.val + 1);
    private static final Symbol SYMBOL_HIGHEST = new Symbol("highest", SYMBOL_RC.val + 1);
    private static final Symbol SYMBOL_ENGINE = new Symbol("engine", SYMBOL_HIGHEST.val + 1);
    private static final Symbol SYMBOL_MODULE_PATH = new Symbol("module_path", SYMBOL_ENGINE.val + 1);
    private static final Symbol SYMBOL_IM_MODULE_PATH = new Symbol("im_module_path", SYMBOL_MODULE_PATH.val + 1);
    private static final Symbol SYMBOL_IM_MODULE_FILE = new Symbol("im_module_file", SYMBOL_IM_MODULE_PATH.val + 1);
    private static final Symbol SYMBOL_STOCK = new Symbol("stock", SYMBOL_IM_MODULE_FILE.val + 1);
    private static final Symbol SYMBOL_LTR = new Symbol("LTR", SYMBOL_STOCK.val + 1);
    private static final Symbol SYMBOL_RTL = new Symbol("RTL", SYMBOL_LTR.val + 1);
    private static final Symbol SYMBOL_LAST = new Symbol("last", SYMBOL_RTL.val + 1);
    
    private static final Symbol[] symbols = {
        SYMBOL_INCLUDE, SYMBOL_NORMAL, SYMBOL_ACTIVE, SYMBOL_PRELIGHT,
        SYMBOL_SELECTED, SYMBOL_INSENSITIVE, SYMBOL_FG, SYMBOL_BG,
        SYMBOL_TEXT, SYMBOL_BASE, SYMBOL_XTHICKNESS, SYMBOL_YTHICKNESS,
        SYMBOL_FONT, SYMBOL_FONTSET, SYMBOL_FONT_NAME, SYMBOL_BG_PIXMAP,
        SYMBOL_PIXMAP_PATH, SYMBOL_STYLE, SYMBOL_BINDING, SYMBOL_BIND,
        SYMBOL_WIDGET, SYMBOL_WIDGET_CLASS, SYMBOL_CLASS, SYMBOL_LOWEST,
        SYMBOL_GTK, SYMBOL_APPLICATION, SYMBOL_THEME, SYMBOL_RC,
        SYMBOL_HIGHEST, SYMBOL_ENGINE, SYMBOL_MODULE_PATH,
        SYMBOL_IM_MODULE_FILE, SYMBOL_STOCK, SYMBOL_LTR, SYMBOL_RTL
    };
    
    private static class StyleInfo {
        String name;
        
        static final int NUM_STATES = 5;
        
        static final int NORMAL = 0;
        static final int PRELIGHT = 1;
        static final int ACTIVE = 2;
        static final int INSENSITIVE = 3;
        static final int SELECTED = 4;
        
        Color[] fg = new Color[NUM_STATES];
        Color[] bg = new Color[NUM_STATES];
        Color[] text = new Color[NUM_STATES];
        Color[] base = new Color[NUM_STATES];
        String[] bgPixmapName = new String[NUM_STATES];
        
        Font font = null;
        
        int xThickness = GTKStyle.UNDEFINED_THICKNESS;
        int yThickness = GTKStyle.UNDEFINED_THICKNESS;

        // An array of HashMaps. The first HashMap is for stock
        // icons defined in this style. The other elements are
        // those inherited from parent.
        ArrayList stocks = null;
        
        CircularIdentityList props = null;
        
        EngineInfo engineInfo = null;

        private GTKStyle cachedStyle = null;
        private static GTKStyle EMPTY_STYLE = new GTKStyle();
        
        StyleInfo(String name) {
            this.name = name;
        }
        
        private void initStocksIfNecessary() {
            if (stocks == null) {
                stocks = new ArrayList();
                // for stock icons defined in this style
                stocks.add(new HashMap());
            }
        }

        void addStockItem(String id, GTKStyle.GTKIconSource[] sources) {
            initStocksIfNecessary();
            
            GTKStyle.GTKStockIconInfo iconInfo = new GTKStyle.GTKStockIconInfo(id, sources);
            
            HashMap map = (HashMap)stocks.get(0);
            map.put(id, iconInfo);                
        }
        
        void addProperty(String klass, String prop, Object value) {
            if (props == null) {
                props = new CircularIdentityList();
            }
            
            CircularIdentityList subList = (CircularIdentityList)props.get(klass);
            
            if (subList == null) {
                subList = new CircularIdentityList();
                props.set(klass, subList);
            }
            
            subList.set(prop, value);
        }
        
        void copyDataFrom(StyleInfo other) {
            for (int i = 0; i < NUM_STATES; i++) {
                fg[i] = other.fg[i];
                bg[i] = other.bg[i];
                text[i] = other.text[i];
                base[i] = other.base[i];
                bgPixmapName[i] = other.bgPixmapName[i];
            }

            xThickness = other.xThickness;
            yThickness = other.yThickness;
            font = other.font;
            
            if (other.stocks != null) {
                initStocksIfNecessary();
                stocks.addAll(other.stocks);
            }
            
            if (props == null) {
                props = GTKStyle.cloneClassSpecificValues(other.props);
            } else {
                GTKStyle.addClassSpecificValues(other.props, props);
            }
        }
        
        GTKStyle toGTKStyle() {
            if (cachedStyle != null) {
                return cachedStyle;
            }
            
            ArrayList stateInfos = new ArrayList();
            
            for (int i = 0; i < NUM_STATES; i++) {
                Color[] colors = null;

                if (fg[i] != null
                        || bg[i] != null
                        || text[i] != null
                        || base[i] != null) {
                    colors = new Color[GTKColorType.MAX_COUNT];
                    colors[GTKColorType.FOREGROUND.getID()] = fg[i];
                    colors[GTKColorType.BACKGROUND.getID()] = bg[i];
                    colors[GTKColorType.TEXT_FOREGROUND.getID()] = text[i];
                    colors[GTKColorType.TEXT_BACKGROUND.getID()] = base[i];
                }
                
                if (colors != null || bgPixmapName[i] != null) {
                    GTKStyle.GTKStateInfo stateInfo =
                        new GTKStyle.GTKStateInfo(toSynthState(i),
                                                  null, colors, bgPixmapName[i]);
                    stateInfos.add(stateInfo);
                }
            }
            
            GTKStyle.GTKStateInfo[] infoArray = null;
            if (stateInfos.size() != 0) {
                infoArray = new GTKStyle.GTKStateInfo[stateInfos.size()];
                infoArray = (GTKStyle.GTKStateInfo[])stateInfos.toArray(infoArray);
            }

            GTKStyle.GTKStockIconInfo[] stockArray = stocksToArray();

            // if this style has engine information, delegate the creation
            if (engineInfo != null) {
                cachedStyle = engineInfo.constructGTKStyle(infoArray,
                                                           props,
                                                           font,
                                                           xThickness,
                                                           yThickness,
                                                           stockArray);
            // otherwise, create a regular GTKStyle
            } else if (infoArray != null
                           || stockArray != null
                           || props != null
                           || font != null
                           || xThickness != GTKStyle.UNDEFINED_THICKNESS
                           || yThickness != GTKStyle.UNDEFINED_THICKNESS) {
                cachedStyle = new GTKStyle(infoArray,
                                           props,
                                           font,
                                           xThickness,
                                           yThickness,
                                           stockArray);
            } else {
                cachedStyle = EMPTY_STYLE;
            }

            return cachedStyle;
        }

        private GTKStyle.GTKStockIconInfo[] stocksToArray() {
            if (stocks == null) {
                return null;
            }
            
            ArrayList tmpList = new ArrayList();
            
            HashMap[] maps = new HashMap[stocks.size()];
            maps = (HashMap[])stocks.toArray(maps);
            
            for (int i = 0; i < maps.length; i++) {
                tmpList.addAll(maps[i].values());
            }
            
            GTKStyle.GTKStockIconInfo[] retVal = new GTKStyle.GTKStockIconInfo[tmpList.size()];
            retVal = (GTKStyle.GTKStockIconInfo[])tmpList.toArray(retVal);
            
            return retVal;
        }

        private static int toSynthState(int ourState) {
            switch(ourState) {
                case NORMAL: return SynthConstants.ENABLED;
                case PRELIGHT: return SynthConstants.MOUSE_OVER;
                case ACTIVE: return SynthConstants.PRESSED;
                case INSENSITIVE: return SynthConstants.DISABLED;
                case SELECTED: return SynthConstants.SELECTED;
            }
            
            // should not happen
            return SynthConstants.ENABLED;
        }
    }

    static abstract class EngineInfo {
        private String engineName;
        
        abstract GTKStyle constructGTKStyle(GTKStyle.GTKStateInfo[] infoArray,
                                            CircularIdentityList props,
                                            Font font,
                                            int xThickness,
                                            int yThickness,
                                            GTKStyle.GTKStockIconInfo[] stockArray);
    }

    private static class Assignment {
        int type;
        String pattern;
        StyleInfo info;
        
        Assignment(int type, String pattern, StyleInfo info) {
            this.type = type;
            this.pattern = pattern;
            this.info = info;
        }
        
        public String toString() {
            String sVal = "";
            
            switch(type) {
                case GTKStyleFactory.WIDGET: sVal = "widget, "; break;
                case GTKStyleFactory.WIDGET_CLASS: sVal = "widget_class, "; break;
                case GTKStyleFactory.CLASS: sVal = "class, "; break;
            }
            
            sVal += pattern + ", ";
            sVal += info.name;
            
            return sVal;
        }
    }

    private static Symbol getSymbol(int symbol) {
        if (symbol > SYMBOL_INVALID.val && symbol < SYMBOL_LAST.val) {
            for (int i = 0; i < symbols.length; i++) {
                if (symbols[i].val == symbol) {
                    return symbols[i];
                }
            }
        }
        
        return null;
    }

    public GTKParser() {
        freeScanners.add(createScanner());
    }
    
    public void parseString(String str) throws IOException {
        StringReader reader = new StringReader(str);
        parseReader(reader, "-");
    }
    
    public void parseFile(File file, String name) throws IOException {
        if (!file.canRead() || !file.isFile()) {
            return;
        }
        
        File parent = file.getParentFile();
        if (parent == null) {
            parent = new File(CWD);
        }
        
        dirStack.add(parent);

        try {
            BufferedReader reader = new BufferedReader(new FileReader(file));
            parseReader(reader, name);
        } finally {
            dirStack.remove(dirStack.size() - 1);
        }

        // PENDING(shannonh) - This is where we should look up and parse
        //                     the locale-specific version of the file.
    }

    private void parseReader(Reader reader, String name) throws IOException {
        int len = freeScanners.size();
        
        if (len == 0) {
            scanner = createScanner();
        } else {
            scanner = (GTKScanner)freeScanners.remove(len - 1);
        }
        
        scanner.scanReader(reader, name);
        
        try {
            parseCurrent();
        } finally {
            scanner.clearScanner();
            freeScanners.add(scanner);
        }
    }
    
    private static GTKScanner createScanner() {
        GTKScanner scanner = new GTKScanner();

        // configure scanner for GTK rc files
        scanner.caseSensitive = true;
        scanner.scanBinary = true;
        scanner.scanHexDollar = true;
        scanner.symbol2Token = true;
        
        for (int i = 0; i < symbols.length; i++) {
            scanner.addSymbol(symbols[i].name, symbols[i].val);
        }
        
        return scanner;
    }
    
    public void loadStylesInto(GTKStyleFactory factory) {
        Assignment[] assigns = new Assignment[assignments.size()];
        assigns = (Assignment[])assignments.toArray(assigns);
        
        for (int i = 0; i < assigns.length; i++) {
            Assignment assign = assigns[i];
            GTKStyle style = assign.info.toGTKStyle();
            
            if (style != StyleInfo.EMPTY_STYLE) {
                try {
                    factory.addStyle(style, assign.pattern, assign.type);
                } catch (PatternSyntaxException pse) {
                    // should not happen
                }
            }
        }
    }

    public HashMap getGTKSettings() {
        return settings;
    }

    public void clearParser() {
        namedStyles.clear();
        settings.clear();
        assignments.clear();
        dirStack.clear();
        pixmapPaths = null;
    }



//------------------------- Parsing Methods ------------------------------//

    private void parseCurrent() throws IOException {
        while (true) {
            if (scanner.peekNextToken() == GTKScanner.TOKEN_EOF) {
                break;
            }

            int expected = parseStatement();

            if (expected != GTKScanner.TOKEN_NONE) {
                String symbolName = null;
                String msg = null;

                if (scanner.currScope == 0) {
                    Symbol lookup;

                    lookup = getSymbol(expected);
                    if (lookup != null) {
                        msg = "e.g. `" + lookup.name + "'";
                    }

                    lookup = getSymbol(scanner.currToken);
                    if (lookup != null) {
                        symbolName = lookup.name;
                    }
                }

                scanner.unexpectedToken(expected, symbolName, msg, true);
                break;
            }
        }
    }
    
    private int parseStatement() throws IOException {
        int token;
        
        token = scanner.peekNextToken();
        if (token == SYMBOL_INCLUDE.val) {
            return parseInclude();
        } else if (token == SYMBOL_STYLE.val) {
            return parseStyle();
        } else if (token == SYMBOL_BINDING.val) {
            return parseBinding();
        } else if (token == SYMBOL_PIXMAP_PATH.val) {
            return parsePixmapPath();
        } else if (token == SYMBOL_WIDGET.val
                       || token == SYMBOL_WIDGET_CLASS.val
                       || token == SYMBOL_CLASS.val) {
            return parseAssignment(token);
        } else if (token == SYMBOL_MODULE_PATH.val) {
            return parseModulePath();
        } else if (token == SYMBOL_IM_MODULE_FILE.val) {
            return parseIMModuleFile();
        } else if (token == GTKScanner.TOKEN_IDENTIFIER) {
            return parseIdentifier();
        }

        scanner.getToken();
        return SYMBOL_STYLE.val;
    }
    
    private int parseInclude() throws IOException {
        int token;
        
        token = scanner.getToken();
        if (token != SYMBOL_INCLUDE.val) {
            return SYMBOL_INCLUDE.val;
        }
        
        token = scanner.getToken();
        if (token != GTKScanner.TOKEN_STRING) {
            return GTKScanner.TOKEN_STRING;
        }
        
        File parseFile = null;
        
        String name = scanner.currValue.stringVal;
        File file = new File(name);

        if (file.isAbsolute()) {
            parseFile = file;
        } else {
            File[] dirs = new File[dirStack.size()];
            dirs = (File[])dirStack.toArray(dirs);
            
            for (int i = dirs.length - 1; i >= 0; i--) {
                file = new File(dirs[i], name);
                if (file.exists()) {
                    parseFile = file;
                    break;
                }
            }
        }
        
        if (parseFile == null) {
            scanner.printMessage("Unable to find include file: \"" + name + "\"", false);
        } else {
            // save the current scanner and recurse
            GTKScanner savedScanner = scanner;

            try {
                parseFile(file, name);
            } catch (IOException ioe) {
                savedScanner.printMessage("(" + ioe.toString()
                                              + ") while parsing include file: \""
                                              + name
                                              + "\"", false);
            }

            // restore the scanner
            scanner = savedScanner;
        }
        
        return GTKScanner.TOKEN_NONE;
    }
    
    private int parseStyle() throws IOException {
        int token;
        
        token = scanner.getToken();
        if (token != SYMBOL_STYLE.val) {
            return SYMBOL_STYLE.val;
        }
        
        token = scanner.getToken();
        if (token != GTKScanner.TOKEN_STRING) {
            return GTKScanner.TOKEN_STRING;
        }
        
        StyleInfo info = (StyleInfo)namedStyles.get(scanner.currValue.stringVal);
        
        if (info == null) {
            info = new StyleInfo(scanner.currValue.stringVal);
        }
        
        token = scanner.peekNextToken();
        if (token == GTKScanner.TOKEN_EQUAL_SIGN) {
            token = scanner.getToken();
            token = scanner.getToken();
            
            if (token != GTKScanner.TOKEN_STRING) {
                return GTKScanner.TOKEN_STRING;
            }
            
            StyleInfo parent = (StyleInfo)namedStyles.get(scanner.currValue.stringVal);
            if (parent != null) {
                info.copyDataFrom(parent);
            }
        }
        
        token = scanner.getToken();
        if (token != GTKScanner.TOKEN_LEFT_CURLY) {
            return GTKScanner.TOKEN_LEFT_CURLY;
        }

        token = scanner.peekNextToken();
        while (token != GTKScanner.TOKEN_RIGHT_CURLY) {
            if (token == SYMBOL_FG.val
                    || token == SYMBOL_BG.val
                    || token == SYMBOL_TEXT.val
                    || token == SYMBOL_BASE.val) {
                token = parseColorSetting(token, info);
            } else if (token == SYMBOL_XTHICKNESS.val
                           || token == SYMBOL_YTHICKNESS.val) {
                token = parseThickness(token, info);
            } else if (token == SYMBOL_BG_PIXMAP.val) {
                token = parseBGPixmap(info);
            } else if (token == SYMBOL_FONT.val
                           || token == SYMBOL_FONTSET.val
                           || token == SYMBOL_FONT_NAME.val) {
                token = parseFont(token, info);
            } else if (token == SYMBOL_ENGINE.val) {
                token = parseEngine(info);
            } else if (token == SYMBOL_STOCK.val) {
                token = parseStock(info);
            } else if (token == GTKScanner.TOKEN_IDENTIFIER) {
                token = parseIdentifierInStyle(info);
            } else {
                scanner.getToken();
                token = GTKScanner.TOKEN_RIGHT_CURLY;
            }
            
            if (token != GTKScanner.TOKEN_NONE) {
                return token;
            }
            
            token = scanner.peekNextToken();
        }
        
        token = scanner.getToken();
        if (token != GTKScanner.TOKEN_RIGHT_CURLY) {
            return GTKScanner.TOKEN_RIGHT_CURLY;
        }
        
        namedStyles.put(info.name, info);
        
        return GTKScanner.TOKEN_NONE;
    }
    
    private int parseBinding() throws IOException {
        int token;
        
        token = scanner.getToken();
        if (token != SYMBOL_BINDING.val) {
            return SYMBOL_BINDING.val;
        }
        
        token = scanner.getToken();
        if (token != GTKScanner.TOKEN_STRING) {
            return GTKScanner.TOKEN_STRING;
        }
        
        token = ignoreBlock();
        if (token != GTKScanner.TOKEN_NONE) {
            return token;
        }
        
        scanner.printMessage("Binding specification is unsupported, ignoring", false);
        
        return GTKScanner.TOKEN_NONE;
    }
    
    private int parsePixmapPath() throws IOException {
        int token;
        
        token = scanner.getToken();
        if (token != SYMBOL_PIXMAP_PATH.val) {
            return SYMBOL_PIXMAP_PATH.val;
        }
        
        token = scanner.getToken();
        if (token != GTKScanner.TOKEN_STRING) {
            return GTKScanner.TOKEN_STRING;
        }
        
        pixmapPaths = null;
        
        ArrayList tempPaths = new ArrayList();
        
        StringTokenizer tok = new StringTokenizer(scanner.currValue.stringVal, File.pathSeparator);
        while (tok.hasMoreTokens()) {
            String path = tok.nextToken();
            File file = new File(path);
            if (file.isAbsolute()) {
                tempPaths.add(file);
            } else {
                scanner.printMessage("Pixmap path element: \"" + path + "\" must be absolute", false);
            }
        }
        
        if (tempPaths.size() > 0) {
            pixmapPaths = new File[tempPaths.size()];
            pixmapPaths = (File[])tempPaths.toArray(pixmapPaths);
        }
        
        return GTKScanner.TOKEN_NONE;
    }
    
    private int parseAssignment(int expVal) throws IOException {
        int token;
        
        token = scanner.getToken();
        if (token != expVal) {
            return expVal;
        }
        
        int type;
        String pattern;
        StyleInfo info;
        
        boolean isBinding;
        
        if (token == SYMBOL_WIDGET.val) {
            type = GTKStyleFactory.WIDGET;
        } else if (token == SYMBOL_WIDGET_CLASS.val) {
            type = GTKStyleFactory.WIDGET_CLASS;
        } else if (token == SYMBOL_CLASS.val) {
            type = GTKStyleFactory.CLASS;
        } else {
            return SYMBOL_WIDGET_CLASS.val;
        }
        
        token = scanner.getToken();
        if (token != GTKScanner.TOKEN_STRING) {
            return GTKScanner.TOKEN_STRING;
        }
        
        pattern = scanner.currValue.stringVal;
        
        token = scanner.getToken();
        if (token == SYMBOL_STYLE.val) {
            isBinding = false;
        } else if (token == SYMBOL_BINDING.val) {
            isBinding = true;
        } else {
            return SYMBOL_STYLE.val;
        }
        
        token = scanner.peekNextToken();
        if (token == ':') {
            token = scanner.getToken();

            token = scanner.getToken();
            if (token != SYMBOL_LOWEST.val
                    && token != SYMBOL_GTK.val
                    && token != SYMBOL_APPLICATION.val
                    && token != SYMBOL_THEME.val
                    && token != SYMBOL_RC.val
                    && token != SYMBOL_HIGHEST.val) {
                return SYMBOL_APPLICATION.val;
            }
            
            scanner.printMessage("Priority specification is unsupported, ignoring", false);
        }
        
        token = scanner.getToken();
        if (token != GTKScanner.TOKEN_STRING) {
            return GTKScanner.TOKEN_STRING;
        }

        // PENDING(shannonh) - When we start handling priority, the information will
        //                     probably be stored as part of an Assignment here
        if (isBinding) {
            // PENDING(shannonh) - Key binding support
            scanner.printMessage("Binding assignment is unsupported, ignoring", false);
        } else {
            info = (StyleInfo)namedStyles.get(scanner.currValue.stringVal);
            if (info == null) {
                return GTKScanner.TOKEN_STRING;
            }
            
            Assignment assignment = new Assignment(type, pattern, info);
            assignments.add(assignment);
        }
        
        return GTKScanner.TOKEN_NONE;
    }
    
    private int parseModulePath() throws IOException {
        int token;
        
        token = scanner.getToken();
        if (token != SYMBOL_MODULE_PATH.val) {
            return SYMBOL_MODULE_PATH.val;
        }
        
        token = scanner.getToken();
        if (token != GTKScanner.TOKEN_STRING) {
            return GTKScanner.TOKEN_STRING;
        }
        
        scanner.printMessage("module_path directive is now ignored", false);
        
        return GTKScanner.TOKEN_NONE;
    }
    
    private int parseIMModuleFile() throws IOException {
        int token;
        
        token = scanner.getToken();
        if (token != SYMBOL_IM_MODULE_FILE.val) {
            return SYMBOL_IM_MODULE_FILE.val;
        }
        
        token = scanner.getToken();
        if (token != GTKScanner.TOKEN_STRING) {
            return GTKScanner.TOKEN_STRING;
        }

        scanner.printMessage("im_module_file directive is unsupported, ignoring", false);
        
        return GTKScanner.TOKEN_NONE;
    }
    
    private int parseIdentifier() throws IOException {
        int token;
        
        token = scanner.getToken();
        if (token != GTKScanner.TOKEN_IDENTIFIER) {
            return GTKScanner.TOKEN_IDENTIFIER;
        }
        
        String prop;
        Object[] value = new Object[1];

        StringBuffer buf = new StringBuffer(scanner.currValue.stringVal);

        String validChars = GTKScanner.CHARS_A_2_Z
                                + GTKScanner.CHARS_a_2_z
                                + GTKScanner.CHARS_DIGITS
                                + "-";

        // some weird logic that GTK does
        int len = buf.length();
        for (int i = 0; i < len; i++) {
            if (validChars.indexOf(buf.charAt(i)) == -1) {
                buf.setCharAt(i, '-');
            }
        }
        
        prop = buf.toString().intern();
        
        token = parsePropertyAssignment(value);
        if (token != GTKScanner.TOKEN_NONE) {
            return token;
        }

        settings.put(prop, value[0]);

        return GTKScanner.TOKEN_NONE;
    }
    
    private int parseColorSetting(int expVal, StyleInfo info) throws IOException {
        int token;
        
        token = scanner.getToken();
        if (token != expVal) {
            return expVal;
        }
        
        Color[] cols = null;
        
        if (token == SYMBOL_FG.val) {
            cols = info.fg;
        } else if (token == SYMBOL_BG.val) {
            cols = info.bg;
        } else if (token == SYMBOL_TEXT.val) {
            cols = info.text;
        } else if (token == SYMBOL_BASE.val) {
            cols = info.base;
        } else {
            return SYMBOL_FG.val;
        }
        
        int[] state = new int[1];
        token = parseState(state);
        
        if (token != GTKScanner.TOKEN_NONE) {
            return token;
        }
        
        token = scanner.getToken();
        if (token != GTKScanner.TOKEN_EQUAL_SIGN) {
            return GTKScanner.TOKEN_EQUAL_SIGN;
        }
        
        return parseColor(scanner, cols, state[0]);
    }
    
    private int parseState(int[] retVal) throws IOException {
        int token;
        
        token = scanner.getToken();
        if (token != GTKScanner.TOKEN_LEFT_BRACE) {
            return GTKScanner.TOKEN_LEFT_BRACE;
        }
        
        token = scanner.getToken();
        if (token == SYMBOL_NORMAL.val) {
            retVal[0] = StyleInfo.NORMAL;
        } else if (token == SYMBOL_ACTIVE.val) {
            retVal[0] = StyleInfo.ACTIVE;
        } else if (token == SYMBOL_PRELIGHT.val) {
            retVal[0] = StyleInfo.PRELIGHT;
        } else if (token == SYMBOL_SELECTED.val) {
            retVal[0] = StyleInfo.SELECTED;
        } else if (token == SYMBOL_INSENSITIVE.val) {
            retVal[0] = StyleInfo.INSENSITIVE;
        } else {
            return SYMBOL_NORMAL.val;
        }
        
        token = scanner.getToken();
        if (token != GTKScanner.TOKEN_RIGHT_BRACE) {
            return GTKScanner.TOKEN_RIGHT_BRACE;
        }
        
        return GTKScanner.TOKEN_NONE;
    }

    // this is static, and takes a scanner as a parameter, so that it can
    // be used in other places that need to parse colors
    static int parseColor(GTKScanner scanner, Color[] colors, int index)
                                                        throws IOException {
        int token;
        
        long lVal;
        double dVal;
        
        float red;
        float green;
        float blue;
        
        token = scanner.getToken();
        
        switch(token) {
            case GTKScanner.TOKEN_LEFT_CURLY:
                token = scanner.getToken();
                if (token == GTKScanner.TOKEN_INT) {
                    red = javaColorVal(scanner.currValue.longVal);
                } else if (token == GTKScanner.TOKEN_FLOAT) {
                    red = javaColorVal(scanner.currValue.doubleVal);
                } else {
                    return GTKScanner.TOKEN_FLOAT;
                }
                
                token = scanner.getToken();
                if (token != GTKScanner.TOKEN_COMMA) {
                    return GTKScanner.TOKEN_COMMA;
                }
                
                token = scanner.getToken();
                if (token == GTKScanner.TOKEN_INT) {
                    green = javaColorVal(scanner.currValue.longVal);
                } else if (token == GTKScanner.TOKEN_FLOAT) {
                    green = javaColorVal(scanner.currValue.doubleVal);
                } else {
                    return GTKScanner.TOKEN_FLOAT;
                }
                
                token = scanner.getToken();
                if (token != GTKScanner.TOKEN_COMMA) {
                    return GTKScanner.TOKEN_COMMA;
                }
                
                token = scanner.getToken();
                if (token == GTKScanner.TOKEN_INT) {
                    blue = javaColorVal(scanner.currValue.longVal);
                } else if (token == GTKScanner.TOKEN_FLOAT) {
                    blue = javaColorVal(scanner.currValue.doubleVal);
                } else {
                    return GTKScanner.TOKEN_FLOAT;
                }
                
                token = scanner.getToken();
                if (token != GTKScanner.TOKEN_RIGHT_CURLY) {
                    return GTKScanner.TOKEN_RIGHT_CURLY;
                }
                
                colors[index] = new ColorUIResource(red, green, blue);
                
                break;
            case GTKScanner.TOKEN_STRING:
                Color color = parseColorString(scanner.currValue.stringVal);

                if (color == null) {
                    scanner.printMessage("Invalid color constant '" +
                                              scanner.currValue.stringVal
                                              + "'", false);
                    return GTKScanner.TOKEN_STRING;
                }

                colors[index] = color;
                
                break;
            default:
                return GTKScanner.TOKEN_STRING;
        }
        
        return GTKScanner.TOKEN_NONE;
    }
    
    static Color parseColorString(String str) {
        if (str.charAt(0) == '#') {
            str = str.substring(1);
            
            int i = str.length();
            
            if (i < 3 || i > 12 || (i % 3) != 0) {
                return null;
            }
            
            i /= 3;
            
            int r;
            int g;
            int b;
            
            try {
                r = Integer.parseInt(str.substring(0, i), 16);
                g = Integer.parseInt(str.substring(i, i * 2), 16);
                b = Integer.parseInt(str.substring(i * 2, i * 3), 16);
            } catch (NumberFormatException nfe) {
                return null;
            }
            
            if (i == 4) {
                return new ColorUIResource(r / 65535.0f, g / 65535.0f, b / 65535.0f);
            } else if (i == 1) {
                return new ColorUIResource(r / 15.0f, g / 15.0f, b / 15.0f);
            } else if (i == 2) {
                return new ColorUIResource(r, g, b);
            } else {
                return new ColorUIResource(r / 4095.0f, g / 4095.0f, b / 4095.0f);
            }
        } else {
            return XColors.lookupColor(str);
        }
    }
    
    private static float javaColorVal(long col) {
        int color = (int)Math.max(Math.min(col, 65535), 0);
        return color / 65535.0f;
    }
    
    private static float javaColorVal(double col) {
        float color = (float)Math.max(Math.min(col, 1.0f), 0.0f);
        return color;
    }

    private int parseThickness(int expVal, StyleInfo info) throws IOException {
        int token;
        boolean isXThickness;

        token = scanner.getToken();
        if (token != expVal) {
            return expVal;
        }

        if (token == SYMBOL_XTHICKNESS.val) {
            isXThickness = true;
        } else if (token == SYMBOL_YTHICKNESS.val) {
            isXThickness = false;
        } else {
            return SYMBOL_XTHICKNESS.val;
        }

        token = scanner.getToken();
        if (token != GTKScanner.TOKEN_EQUAL_SIGN) {
            return GTKScanner.TOKEN_EQUAL_SIGN;
        }

        token = scanner.getToken();
        if (token != GTKScanner.TOKEN_INT) {
            return GTKScanner.TOKEN_INT;
        }

        int thickness = (int)scanner.currValue.longVal;

        if (isXThickness) {
            info.xThickness = thickness;
        } else {
            info.yThickness = thickness;
        }

        return GTKScanner.TOKEN_NONE;
    }

    private int parseBGPixmap(StyleInfo info) throws IOException {
        int token;
        
        token = scanner.getToken();
        if (token != SYMBOL_BG_PIXMAP.val) {
            return SYMBOL_BG_PIXMAP.val;
        }
        
        int[] state = new int[1];
        token = parseState(state);
        
        if (token != GTKScanner.TOKEN_NONE) {
            return token;
        }
        
        token = scanner.getToken();
        if (token != GTKScanner.TOKEN_EQUAL_SIGN) {
            return GTKScanner.TOKEN_EQUAL_SIGN;
        }
        
        token = scanner.getToken();
        if (token != GTKScanner.TOKEN_STRING) {
            return GTKScanner.TOKEN_STRING;
        }
        
        String pixmapStr = null;
        
        String str = scanner.currValue.stringVal;
        
        if (str.equals("<none>") || str.equals("<parent>")) {
            pixmapStr = str.intern();
        } else {
            pixmapStr = resolvePixmapPath(str);
        }
        
        if (pixmapStr == null) {
            scanner.printMessage("Unable to locate image file in pixmap_path: \"" + str + "\"", false);
        } else {
            info.bgPixmapName[state[0]] = pixmapStr;
        }
        
        return GTKScanner.TOKEN_NONE;
    }

    String resolvePixmapPath(String str) {
        // search in pixmap path
        if (pixmapPaths != null) {
            for (int i = 0; i < pixmapPaths.length; i++) {
                File file = new File(pixmapPaths[i], str);
                if (file.canRead()) {
                    return file.getAbsolutePath();
                }
            }
        }
        
        // search in rc directory stack
        File[] dirs = new File[dirStack.size()];
        dirs = (File[])dirStack.toArray(dirs);
        
        for (int i = dirs.length - 1; i >= 0; i--) {
            File file = new File(dirs[i], str);
            if (file.canRead()) {
                return file.getAbsolutePath();
            }
        }
        
        return null;
    }

    private int parseFont(int expVal, StyleInfo info) throws IOException {
        int token;
        boolean isPango;
        
        token = scanner.getToken();
        if (token != expVal) {
            return expVal;
        }
        
        if (token == SYMBOL_FONT_NAME.val) {
            isPango = true;
        } else if (token == SYMBOL_FONT.val
                       || token == SYMBOL_FONTSET.val) {
            isPango = false;
        } else {
            return SYMBOL_FONT_NAME.val;
        }
        
        token = scanner.getToken();
        if (token != GTKScanner.TOKEN_EQUAL_SIGN) {
            return GTKScanner.TOKEN_EQUAL_SIGN;
        }
        
        token = scanner.getToken();
        if (token != GTKScanner.TOKEN_STRING) {
            return GTKScanner.TOKEN_STRING;
        }
        
        // only need to parse pango font names
        if (isPango) {
            String pangoName = scanner.currValue.stringVal;
            
            info.font = PangoFonts.lookupFont(pangoName);
        }
        
        return GTKScanner.TOKEN_NONE;
    }

    private GTKEngineParser getParser(String engineName) {
        Object o = engineParsers.get(engineName);
        
        if (o == null) {
            return null;
        }
        
        if (o instanceof GTKEngineParser) {
            return (GTKEngineParser)o;
        }
        
        GTKEngineParser parser = null;
        
        try {
            parser = (GTKEngineParser)Class.forName((String)o).newInstance();
        } catch (ClassNotFoundException e) {
        } catch (InstantiationException e) {
        } catch (IllegalAccessException e) {
        }
        
        if (parser == null) {
            // no need to keep trying to load it every time
            engineParsers.remove(engineName);
        } else {
            // replace the name with an instance of a parser
            engineParsers.put(engineName, parser);
        }
        
        return parser;
    }

    private int parseEngine(StyleInfo info) throws IOException {
        int token;

        token = scanner.getToken();
        if (token != SYMBOL_ENGINE.val) {
            return SYMBOL_ENGINE.val;
        }

        token = scanner.getToken();
        if (token != GTKScanner.TOKEN_STRING) {
            return GTKScanner.TOKEN_STRING;
        }

        String engineName = scanner.currValue.stringVal;

        // engine "" {} means to use the default engine
        if (engineName.length() == 0) {
            token = scanner.getToken();
            if (token != GTKScanner.TOKEN_LEFT_CURLY) {
                return GTKScanner.TOKEN_LEFT_CURLY;
            }

            token = scanner.getToken();
            if (token != GTKScanner.TOKEN_RIGHT_CURLY) {
                return GTKScanner.TOKEN_RIGHT_CURLY;
            }

            info.engineInfo = null;

            return GTKScanner.TOKEN_NONE;
        }

        GTKEngineParser parser = getParser(engineName);

        if (parser == null) {
            token = ignoreBlock();
            if (token != GTKScanner.TOKEN_NONE) {
                return token;
            }
            
            scanner.printMessage("Engine \"" + engineName + "\" is unsupported, ignoring", false);
        } else {
            token = scanner.getToken();
            if (token != GTKScanner.TOKEN_LEFT_CURLY) {
                return GTKScanner.TOKEN_LEFT_CURLY;
            }

            EngineInfo[] engineInfo = new EngineInfo[1];

            // only pass in the existing engine info if it came from this parser
            if (info.engineInfo != null && engineName.equals(info.engineInfo.engineName)) {
                engineInfo[0] = info.engineInfo;
            }

            token = parser.parse(scanner, this, engineInfo);
            if (token != GTKScanner.TOKEN_NONE) {
                return token;
            }

            // tag the returned engine info with the engine name
            if (engineInfo[0] != null) {
                engineInfo[0].engineName = engineName;
            }

            info.engineInfo = engineInfo[0];
        }
        
        return GTKScanner.TOKEN_NONE;
    }

    private int parseStock(StyleInfo info) throws IOException {
        String id;

        int token;
        
        token = scanner.getToken();
        if (token != SYMBOL_STOCK.val) {
            return SYMBOL_STOCK.val;
        }
        
        token = scanner.getToken();
        if (token != GTKScanner.TOKEN_LEFT_BRACE) {
            return GTKScanner.TOKEN_LEFT_BRACE;
        }
        
        token = scanner.getToken();
        if (token != GTKScanner.TOKEN_STRING) {
            return GTKScanner.TOKEN_STRING;
        }
        
        id = scanner.currValue.stringVal;
        
        token = scanner.getToken();
        if (token != GTKScanner.TOKEN_RIGHT_BRACE) {
            return GTKScanner.TOKEN_RIGHT_BRACE;
        }
        
        token = scanner.getToken();
        if (token != GTKScanner.TOKEN_EQUAL_SIGN) {
            return GTKScanner.TOKEN_EQUAL_SIGN;
        }
        
        token = scanner.getToken();
        if (token != GTKScanner.TOKEN_LEFT_CURLY) {
            return GTKScanner.TOKEN_LEFT_CURLY;
        }
        
        ArrayList iconSources = new ArrayList();

        // This array will be used first to hold the return value from
        // parseIconSource and then the variable will be used in
        // converting iconSources to an array.
        GTKStyle.GTKIconSource[] sources = new GTKStyle.GTKIconSource[1];

        token = scanner.peekNextToken();
        while (token != GTKScanner.TOKEN_RIGHT_CURLY) {
            token = parseIconSource(sources);
            
            if (token != GTKScanner.TOKEN_NONE) {
                return token;
            }
            
            token = scanner.getToken();
            if (token != GTKScanner.TOKEN_COMMA
                    && token != GTKScanner.TOKEN_RIGHT_CURLY) {
                return GTKScanner.TOKEN_RIGHT_CURLY;
            }
            
            if (sources[0] != null) {
                iconSources.add(sources[0]);
            }
        }
        
        if (iconSources.size() != 0) {
            sources = new GTKStyle.GTKIconSource[iconSources.size()];
            sources = (GTKStyle.GTKIconSource[])iconSources.toArray(sources);
            info.addStockItem(id, sources);
        }
        
        return GTKScanner.TOKEN_NONE;
    }
    
    private GTKStyle.GTKIconSource createIconSource(String path,
                                                    int direction,
                                                    int state,
                                                    String size) {
        String resolvedPath = resolvePixmapPath(path);

        if (resolvedPath != null) {
            return new GTKStyle.GTKIconSource(resolvedPath, direction, state, size);
        }
        
        return null;
    }
    
    private int parseIconSource(GTKStyle.GTKIconSource[] retVal) throws IOException {
        int token;

        String pixmapStr = null;
        int direction = GTKConstants.UNDEFINED;
        int state = GTKConstants.UNDEFINED;
        String size = null;

        token = scanner.getToken();
        if (token != GTKScanner.TOKEN_LEFT_CURLY) {
            return GTKScanner.TOKEN_LEFT_CURLY;
        }
        
        token = scanner.getToken();
        if (token != GTKScanner.TOKEN_STRING) {
            return GTKScanner.TOKEN_STRING;
        }
        
        pixmapStr = scanner.currValue.stringVal;
        
        token = scanner.getToken();
        if (token == GTKScanner.TOKEN_RIGHT_CURLY) {
            retVal[0] = createIconSource(pixmapStr, direction, state, size);
            return GTKScanner.TOKEN_NONE;
        } else if (token != GTKScanner.TOKEN_COMMA) {
            return GTKScanner.TOKEN_COMMA;
        }
        
        token = scanner.getToken();
        if (token == SYMBOL_RTL.val) {
            direction = GTKConstants.RTL;
        } else if (token == SYMBOL_LTR.val) {
            direction = GTKConstants.LTR;
        } else if (token == '*') {
            // nothing
        } else {
            return SYMBOL_RTL.val;
        }
        
        token = scanner.getToken();
        if (token == GTKScanner.TOKEN_RIGHT_CURLY) {
            retVal[0] = createIconSource(pixmapStr, direction, state, size);
            return GTKScanner.TOKEN_NONE;
        } else if (token != GTKScanner.TOKEN_COMMA) {
            return GTKScanner.TOKEN_COMMA;
        }
        
        token = scanner.getToken();
        if (token == SYMBOL_NORMAL.val) {
            state = SynthConstants.ENABLED;
        } else if (token == SYMBOL_ACTIVE.val) {
            state = SynthConstants.PRESSED;
        } else if (token == SYMBOL_PRELIGHT.val) {
            state = SynthConstants.MOUSE_OVER;
        } else if (token == SYMBOL_SELECTED.val) {
            state = SynthConstants.SELECTED;
        } else if (token == SYMBOL_INSENSITIVE.val) {
            state = SynthConstants.DISABLED;
        } else if (token == '*') {
            // nothing
        } else {
            return SYMBOL_PRELIGHT.val;
        }
        
        token = scanner.getToken();
        if (token == GTKScanner.TOKEN_RIGHT_CURLY) {
            retVal[0] = createIconSource(pixmapStr, direction, state, size);
            return GTKScanner.TOKEN_NONE;
        } else if (token != GTKScanner.TOKEN_COMMA) {
            return GTKScanner.TOKEN_COMMA;
        }
        
        token = scanner.getToken();
        if (token != '*') {
            if (token != GTKScanner.TOKEN_STRING) {
                return GTKScanner.TOKEN_STRING;
            }
            size = scanner.currValue.stringVal;
        }
        
        token = scanner.getToken();
        if (token != GTKScanner.TOKEN_RIGHT_CURLY) {
            return GTKScanner.TOKEN_RIGHT_CURLY;
        }
        
        retVal[0] = createIconSource(pixmapStr, direction, state, size);
        
        return GTKScanner.TOKEN_NONE;
    }

    private int parseIdentifierInStyle(StyleInfo info) throws IOException {
        int token;
        
        token = scanner.getToken();
        if (token != GTKScanner.TOKEN_IDENTIFIER
                || scanner.currValue.stringVal.charAt(0) < 'A'
                || scanner.currValue.stringVal.charAt(0) > 'Z') {
            return GTKScanner.TOKEN_IDENTIFIER;
        }
        
        String klass;
        String prop;
        Object[] value = new Object[1];
        
        klass = scanner.currValue.stringVal.intern();
        
        // check the next two tokens to make sure they're both ':'
        if (scanner.getToken() != ':' || scanner.getToken() != ':') {
            return ':';
        }
        
        token = scanner.getToken();
        if (token != GTKScanner.TOKEN_IDENTIFIER) {
            return GTKScanner.TOKEN_IDENTIFIER;
        }
        
        StringBuffer buf = new StringBuffer(scanner.currValue.stringVal);
        
        String validChars = GTKScanner.CHARS_A_2_Z
                                + GTKScanner.CHARS_a_2_z
                                + GTKScanner.CHARS_DIGITS
                                + "-";

        // some weird logic that GTK does
        int len = buf.length();
        for (int i = 0; i < len; i++) {
            if (validChars.indexOf(buf.charAt(i)) == -1) {
                buf.setCharAt(i, '-');
            }
        }
        
        prop = buf.toString().intern();
        
        token = parsePropertyAssignment(value);
        if (token != GTKScanner.TOKEN_NONE) {
            return token;
        }

        // for Strings or StringBuffers (representing complex values) we check
        // for a parser that might want to parse the value
        if (value[0] instanceof String || value[0] instanceof StringBuffer) {
            Object val = value[0];

            PropertyParser pp = PropertyParser.getParserFor(prop);

            if (pp == null) {
                // just add the property (but convert to String first if StringBuffer)
                info.addProperty(klass, prop,
                        val instanceof String ? val : val.toString());
            } else {
                String toParse;
                if (val instanceof String) {
                    if (pp.needSimpleStringsEscaped()) {
                        toParse = '"' + escapeString((String) val) + '"';
                    } else {
                        toParse = (String)val;
                    }
                } else {
                    toParse = val.toString();
                }

                Object parsedVal = pp.parse(toParse);
                if (parsedVal == null) {
                    scanner.printMessage("Failed to parse property value \"" + toParse + "\" for `"
                            + klass + "::" + prop + "'", false);
                } else {
                    info.addProperty(klass, prop, parsedVal);
                }
            }
        } else {
            info.addProperty(klass, prop, value[0]);
        }

        return GTKScanner.TOKEN_NONE;
    }

    private int parsePropertyAssignment(Object[] retVal) throws IOException {
        int token;

        token = scanner.getToken();
        if (token != '=') {
            return '=';
        }

        // save the scanner mode
        boolean scanIdentifier = scanner.scanIdentifier;
        boolean scanSymbols = scanner.scanSymbols;
        boolean identifier2String = scanner.identifier2String;
        boolean char2Token = scanner.char2Token;
        boolean scanIdentifierNULL = scanner.scanIdentifierNULL;
        boolean numbers2Int = scanner.numbers2Int;

        // modify the scanner mode for our purposes
        scanner.scanIdentifier = true;
        scanner.scanSymbols = false;
        scanner.identifier2String = false;
        scanner.char2Token = true;
        scanner.scanIdentifierNULL = false;
        scanner.numbers2Int = true;

        boolean negate = false;
        
        if (scanner.peekNextToken() == '-') {
            scanner.getToken();
            negate = true;
        }
        
        token = scanner.peekNextToken();
        switch(token) {
            case GTKScanner.TOKEN_INT:
                scanner.getToken();
                retVal[0] = new Long(negate ? -scanner.currValue.longVal : scanner.currValue.longVal);
                token = GTKScanner.TOKEN_NONE;
                break;
            case GTKScanner.TOKEN_FLOAT:
                scanner.getToken();
                retVal[0] = new Double(negate ? -scanner.currValue.doubleVal : scanner.currValue.doubleVal);
                token = GTKScanner.TOKEN_NONE;
                break;
            case GTKScanner.TOKEN_STRING:
                scanner.getToken();
                if (negate) {
                    token = GTKScanner.TOKEN_INT;
                } else {
                    retVal[0] = scanner.currValue.stringVal;
                    token = GTKScanner.TOKEN_NONE;
                }
                break;
            case GTKScanner.TOKEN_IDENTIFIER:
            case GTKScanner.TOKEN_LEFT_PAREN:
            case GTKScanner.TOKEN_LEFT_CURLY:
            case GTKScanner.TOKEN_LEFT_BRACE:
                if (negate) {
                    token = GTKScanner.TOKEN_INT;
                } else {
                    StringBuffer result = new StringBuffer();

                    token = parseComplexPropVal(result, GTKScanner.TOKEN_EOF);
                    if (token == GTKScanner.TOKEN_NONE) {
                        result.append(' ');
                        // return the StringBuffer directly to indicate a complex value
                        retVal[0] = result;
                    }
                }
                break;
            default:
                scanner.getToken();
                token = GTKScanner.TOKEN_INT;
                break;
        }
        
        // restore the scanner mode
        scanner.scanIdentifier = scanIdentifier;
        scanner.scanSymbols = scanSymbols;
        scanner.identifier2String = identifier2String;
        scanner.char2Token = char2Token;
        scanner.scanIdentifierNULL = scanIdentifierNULL;
        scanner.numbers2Int = numbers2Int;
        
        return token;
    }
    
    private int parseComplexPropVal(StringBuffer into, int delim) throws IOException {
        int token;
        
        token = scanner.getToken();
        switch(token) {
            case GTKScanner.TOKEN_INT:
                into.append(" 0x");
                into.append(Long.toHexString(scanner.currValue.longVal));
                break;
            case GTKScanner.TOKEN_FLOAT:
                into.append(' ');
                into.append(scanner.currValue.doubleVal);
                break;
            case GTKScanner.TOKEN_STRING:
                into.append(" \"");
                into.append(escapeString(scanner.currValue.stringVal));
                into.append('"');
                break;
            case GTKScanner.TOKEN_IDENTIFIER:
                into.append(' ');
                into.append(scanner.currValue.stringVal);
                break;
            case GTKScanner.TOKEN_LEFT_PAREN:
                into.append(' ');
                into.append((char)token);
                token = parseComplexPropVal(into, GTKScanner.TOKEN_RIGHT_PAREN);
                if (token != GTKScanner.TOKEN_NONE) {
                    return token;
                }
                break;
            case GTKScanner.TOKEN_LEFT_CURLY:
                into.append(' ');
                into.append((char)token);
                token = parseComplexPropVal(into, GTKScanner.TOKEN_RIGHT_CURLY);
                if (token != GTKScanner.TOKEN_NONE) {
                    return token;
                }
                break;
            case GTKScanner.TOKEN_LEFT_BRACE:
                into.append(' ');
                into.append((char)token);
                token = parseComplexPropVal(into, GTKScanner.TOKEN_RIGHT_BRACE);
                if (token != GTKScanner.TOKEN_NONE) {
                    return token;
                }
                break;
            default:
                if (token >= GTKScanner.TOKEN_NONE || token <= GTKScanner.TOKEN_EOF) {
                    return delim != GTKScanner.TOKEN_EOF ? delim : GTKScanner.TOKEN_STRING;
                }
                into.append(' ');
                into.append((char)token);
                if (token == delim) {
                    return GTKScanner.TOKEN_NONE;
                }
        }
        
        if (delim == GTKScanner.TOKEN_EOF) {
            return GTKScanner.TOKEN_NONE;
        } else {
            return parseComplexPropVal(into, delim);
        }
    }

    private String escapeString(String source) {
        int len = source.length();
        
        StringBuffer result = new StringBuffer(len * 4);
        
        for (int i = 0; i < len; i++) {
            char ch = source.charAt(i);
            
            switch(ch) {
                case '\b':
                    result.append("\\b");
                    break;
                case '\f':
                    result.append("\\f");
                    break;
                case '\n':
                    result.append("\\n");
                    break;
                case '\r':
                    result.append("\\r");
                    break;
                case '\t':
                    result.append("\\t");
                    break;
                case '\\':
                    result.append("\\\\");
                    break;
                case '"':
                    result.append("\\\"");
                    break;
                default:
                    if (ch < ' ' || ch > '~') {
                        result.append('\\');
                        result.append(Integer.toOctalString(ch));
                    } else {
                        result.append((char)ch);
                    }
                    break;
            }
        }

        return result.toString();
    }

    private int ignoreBlock() throws IOException {
        int token;

        token = scanner.getToken();
        if (token != GTKScanner.TOKEN_LEFT_CURLY) {
            return GTKScanner.TOKEN_LEFT_CURLY;
        }

        int curlys = 1;

        while (curlys > 0) {
            token = scanner.getToken();
            switch(token) {
                case GTKScanner.TOKEN_EOF:
                    return GTKScanner.TOKEN_RIGHT_CURLY;
                case GTKScanner.TOKEN_LEFT_CURLY:
                    curlys++;
                    break;
                case GTKScanner.TOKEN_RIGHT_CURLY:
                    curlys--;
                    break;
                default:
                    // ignore
            }
        }

        return GTKScanner.TOKEN_NONE;
    }

    // for testing
    public static void main(String[] args) {
        if (args.length == 0) {
            System.err.println("Usage: java GTKParser <gtkrc file> <gtkrc file>....");
            System.exit(1);
        }
        
        GTKParser parser = new GTKParser();
        
        try {
            for (int i = 0; i < args.length; i++) {
                parser.parseFile(new File(args[i]), args[i]);
            }
        } catch (IOException ioe) {
            ioe.printStackTrace();
        }
        
        parser.printNamedStyles();
        System.out.println();
        parser.printSettings();
        System.out.println();
        parser.printAssignments();
    }

    // for testing
    private void printNamedStyles() {
        System.out.println("===== Named Styles =====");
        
        StyleInfo[] infos = new StyleInfo[namedStyles.size()];
        infos = (StyleInfo[])namedStyles.values().toArray(infos);
        
        for (int i = 0; i < infos.length; i++) {
            StyleInfo info = infos[i];
            
            System.out.println("NAME: " + info.name);
            GTKStyle style = info.toGTKStyle();
            System.out.println(style == StyleInfo.EMPTY_STYLE ? "EMPTY_STYLE" : style.toString());
            System.out.println("---------------------------");
        }
    }

    // for testing
    private void printSettings() {
        System.out.println("===== GTK Settings =====");

        Iterator iter = settings.entrySet().iterator();
        while (iter.hasNext()) {
            Map.Entry entry = (Map.Entry)iter.next();
            System.out.println(entry.getKey() + "=" + entry.getValue());
        }
    }

    // for testing    
    private void printAssignments() {
        System.out.println("===== Assignments =====");
        
        Assignment[] assigns = new Assignment[assignments.size()];
        assigns = (Assignment[])assignments.toArray(assigns);
        
        for (int i = 0; i < assigns.length; i++) {
            System.out.println(assigns[i]);
        }
    }

}
