/*
 * Copyright (c) 2008, 2010, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 * This code 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.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code 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 in the LICENSE file that
 * accompanied this code).
 *
 * 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 Oracle, 500 Oracle Parkway, Redwood Shores
 * CA 94065 USA or visit www.oracle.com if you need additional information or
 * have any questions.
 */

package com.codename1.ui.util;

import com.codename1.ui.Display;
import com.codename1.designer.ResourceEditorView;
import com.codename1.designer.DataEditor;
import com.codename1.designer.FontEditor;
import com.codename1.designer.ImageMultiEditor;
import com.codename1.designer.ImageRGBEditor;
import com.codename1.designer.L10nEditor;
import com.codename1.designer.MultiImageSVGEditor;
import com.codename1.designer.ThemeEditor;
import com.codename1.designer.TimelineEditor;
import com.codename1.designer.UserInterfaceEditor;
import com.codename1.ui.EditorFont;
import com.codename1.ui.EditorTTFFont;
import com.codename1.ui.EncodedImage;
import com.codename1.ui.Image;
import com.codename1.ui.CodenameOneAccessor;
import com.codename1.ui.animations.AnimationAccessor;
import com.codename1.ui.animations.AnimationObject;
import com.codename1.ui.animations.Motion;
import com.codename1.ui.animations.Timeline;
import com.codename1.impl.javase.SVG;
import com.codename1.ui.plaf.Border;
import com.codename1.ui.plaf.Accessor;
import com.codename1.ui.plaf.Style;
import com.codename1.designer.ResourceEditorApp;
import com.codename1.impl.javase.JavaSEPortWithSVGSupport;
import com.codename1.ui.Form;
import com.codename1.ui.plaf.CSSBorder;
import com.codename1.ui.plaf.RoundBorder;
import com.codename1.ui.plaf.RoundRectBorder;
import com.codename1.ui.util.xml.Data;
import com.codename1.ui.util.xml.Entry;
import com.codename1.ui.util.xml.L10n;
import com.codename1.ui.util.xml.Lang;
import com.codename1.ui.util.xml.LegacyFont;
import com.codename1.ui.util.xml.ResourceFileXML;
import com.codename1.ui.util.xml.Theme;
import com.codename1.ui.util.xml.Ui;
import com.codename1.ui.util.xml.Val;
import com.codename1.ui.util.xml.comps.ComponentEntry;
import com.codename1.util.StringUtil;
import java.awt.Frame;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;
import java.io.BufferedWriter;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.UnsupportedEncodingException;
import java.io.Writer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.imageio.ImageIO;
import javax.swing.Icon;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JPasswordField;
import javax.swing.event.TreeModelEvent;
import javax.swing.event.TreeModelListener;
import javax.swing.tree.TreeModel;
import javax.swing.tree.TreePath;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;

/**
 * This class enhances the resources class by inheriting it and using package
 * friendly accessor methods.
 *
 * @author Shai Almog
 */
public class EditableResources extends Resources implements TreeModel {
    private static final short MINOR_VERSION = 11;
    private static final short MAJOR_VERSION = 1;

    private boolean modified;
    private boolean loadingMode = false;
    private boolean xmlUI;
    
    private boolean ignoreSVGMode;
    private boolean ignorePNGMode;

    private EditableResources overrideResource;
    private File overrideFile;
    private EditableResources parentResource;
    private static boolean xmlEnabled;
    
    private HashSet themeLoadingErrors;
    
    public static void setXMLEnabled(boolean b) {
        xmlEnabled = b;
    }
    
    public void setOverrideMode(EditableResources overrideResource, File overrideFile) {
        this.overrideResource = overrideResource;
        this.overrideFile = overrideFile;
        if(overrideResource != null) {
            overrideResource.parentResource = this;
            overrideResource.onChange = onChange;
        }
    }
    
    /**
     * Copies the value from the base to the override resource as a starting point
     * @param name the name of the resource
     */
    public void overrideResource(String name) {
        overrideResource.setResource(name, getResourceType(name), getResourceObject(name));
    }

    public boolean isOverrideMode() {
        return overrideResource != null;
    }
    
    public boolean isOverridenResource(String id) {
        if(overrideResource == null) {
            return true;
        }
        return overrideResource.getResourceObject(id) != null;
    }
    
    private void writeImageAsPNG(Image image, int type, DataOutputStream output) throws IOException {
        BufferedImage buffer = new BufferedImage(image.getWidth(), image.getHeight(), type);
        buffer.setRGB(0, 0, image.getWidth(), image.getHeight(), image.getRGB(), 0, image.getWidth());
        ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
        ImageIO.write(buffer, "png", byteOut);
        byte[] data = byteOut.toByteArray();
        output.writeInt(data.length);
        output.write(data);
        output.writeInt(buffer.getWidth());
        output.writeInt(buffer.getHeight());
        output.writeBoolean(EncodedImage.create(data).isOpaque());
    }

    /**
     * @return the ignoreSVGMode
     */
    public boolean isIgnoreSVGMode() {
        return ignoreSVGMode;
    }

    /**
     * @param ignoreSVGMode the ignoreSVGMode to set
     */
    public void setIgnoreSVGMode(boolean ignoreSVGMode) {
        this.ignoreSVGMode = ignoreSVGMode;
    }

    /**
     * @return the ignorePNGMode
     */
    public boolean isIgnorePNGMode() {
        return ignorePNGMode;
    }

    /**
     * @param ignorePNGMode the ignorePNGMode to set
     */
    public void setIgnorePNGMode(boolean ignorePNGMode) {
        this.ignorePNGMode = ignorePNGMode;
    }

    private abstract class UndoableEdit {
        private boolean previouslyModified;
        
        public final String doAction() {
            previouslyModified = modified;
            String selection = performAction();
            modified = true;
            updateModified();
            if(onChange != null) {
                onChange.run();
            }
            return selection;
        }
        
        public final String undoAction() {
            String selection = performUndo();
            modified = previouslyModified;
            updateModified();
            if(onChange != null) {
                onChange.run();
            }
            return selection;
        }
        
        protected abstract String performAction();
        protected abstract String performUndo();
    }
    
    private List<UndoableEdit> undoQueue = new ArrayList<UndoableEdit>();
    private List<UndoableEdit> redoQueue = new ArrayList<UndoableEdit>();
    
    private Runnable onChange;
    
    /**
     * Create an empty resource file
     */
    public EditableResources() {
        super();
    }
    
    EditableResources(InputStream input) throws IOException {
        super();
        openFile(input);
    }

    public static void setResourcesClassLoader(Class cls) {
        Resources.setClassLoader(cls);
    }

    public static void setCurrentPassword(String password) {
        currentPassword = password;
        if(currentPassword.length() == 0) {
            currentPassword = null;
            key = null;
        } else {
            setPassword(currentPassword);
            try {
                key = currentPassword.getBytes("UTF-8");
            } catch (UnsupportedEncodingException ex) {
                ex.printStackTrace();
            }
        }
    }

    private static byte[] key;
    private static String currentPassword;
    void checkKey(String id) {
        JPasswordField password = new JPasswordField();
        if(currentPassword != null) {
            password.setText(currentPassword);
        }
        int v = JOptionPane.showConfirmDialog(java.awt.Frame.getFrames()[0], password, "Enter Password", JOptionPane.OK_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE);
        if(v == JOptionPane.OK_OPTION) {
            currentPassword = password.getText();
            setPassword(currentPassword);
            try {
                key = currentPassword.getBytes("UTF-8");
            } catch (UnsupportedEncodingException ex) {
                ex.printStackTrace();
            }
            char l = (char)encode(id.charAt(0));
            char w = (char)encode(id.charAt(1));
            //keyOffset = 0;
            if(l != 'l' || w != 'w') {
                // incorrect password!
                JOptionPane.showMessageDialog(java.awt.Frame.getFrames()[0],
                        "Incorrect Password!", "Error", JOptionPane.ERROR_MESSAGE);
                throw new IllegalStateException("Incorrect password");
            }
            return;
        }
        super.checkKey(id);
    }

    private int encode(int val) {
        val = key[keyOffset] ^ val;
        keyOffset++;
        if(keyOffset == key.length) {
            keyOffset = 0;
        }
        return val;
    }

    
    public void setOnChange(Runnable run) {
        onChange = run;
        if(overrideResource != null) {
            overrideResource.onChange = run;
        }
    }
    
    void setResource(final String id, final byte type, final Object value) {
        /*if(!SwingUtilities.isEventDispatchThread()) {
            try {
                SwingUtilities.invokeAndWait(new Runnable() {
                    public void run() {
                        setResource(id, type, value);
                    }
                });
            } catch (Exception ex) {
                ex.printStackTrace();
            } 
            return;
        }*/
        if(overrideResource != null) {
            overrideResource.setResource(id, type, value);
            return;
        }
        boolean exists = false;
        int index = -1;
        if(value != null) {
            exists = getResourceObject(id) != null;
        } else {
            index = getIndexOfChild(getParent(type), id);
        }

        Object superValue = value;
        if(multiPending != null) {
            if(superValue instanceof com.codename1.ui.EncodedImage) {
                superValue = multiPending;
            }
            multiPending = null;
        }

        super.setResource(id, type, superValue);
        if(superValue != null) {
            index = getIndexOfChild(getParent(type), id);
            if(exists) {
                fireTreeNodeChanged(id, index);
            } else {
                fireTreeNodeAdded(id, index);
            }
        } else {
            fireTreeNodeRemoved(id, type, index);
        }
    }

    public void clear() {
        if(overrideResource != null) {
            overrideResource.clear();
            return;
        }
        super.clear();
        modified = false;
        for(String name : getResourceNames()) {
            setResource(name, getResourceType(name), null);
        }
        updateModified();
    }
    
    private byte[] readFile(File parent, String name, boolean normalize) throws IOException {
        if(normalize) {
            name = normalizeFileName(name);
        }
        File f = new File(parent, name);
        byte[] data = new byte[(int)f.length()];
        DataInputStream fi = new DataInputStream(new FileInputStream(f));
        fi.readFully(data);
        fi.close();
        return data;
    }

    private byte[] readFileNoNormal(File f) throws IOException {
        byte[] data = new byte[(int)f.length()];
        DataInputStream fi = new DataInputStream(new FileInputStream(f));
        fi.readFully(data);
        fi.close();
        return data;
    }

    /**
     * Converts a file name to a normalized name so it can be saved on a windows filesystem
     */
    private String normalizeFileName(String fName) {
        return fName.replace("*", "_").replace("?", "_").replace(":", "_");
    }
    
    public void openFileWithXMLSupport(File f) throws IOException {
        if(xmlEnabled && f.getParentFile().getName().equals("src")) {
            File res = new File(f.getParentFile().getParentFile(), "res");
            if(res.exists()) {
                File xml = new File(res, f.getName().substring(0, f.getName().length() - 3) + "xml");
                if(xml.exists()) {
                    loadingMode = true;
                    com.codename1.ui.Font.clearBitmapCache();
                    clear();
                    try {
                        File resDir = new File(res, f.getName().substring(0, f.getName().length() - 4));
                        
                        // open the XML file...
                        JAXBContext ctx = JAXBContext.newInstance(ResourceFileXML.class);
                        ResourceFileXML xmlData = (ResourceFileXML)ctx.createUnmarshaller().unmarshal(xml);
                        boolean normalize = xmlData.getMajorVersion() > 1 || xmlData.getMinorVersion() > 5;
                        
                        majorVersion = (short)xmlData.getMajorVersion();
                        minorVersion = (short)xmlData.getMinorVersion();
                        xmlUI = xmlData.isUseXmlUI();
                        if(xmlData.getData() != null) {
                            for(Data d : xmlData.getData()) {
                                setResource(d.getName(), MAGIC_DATA, readFile(resDir, d.getName(), normalize));
                            }
                        }
                                                
                        if(xmlData.getLegacyFont() != null) {
                            for(LegacyFont d : xmlData.getLegacyFont()) {
                                String name = d.getName();
                                if(normalize) {
                                    name = normalizeFileName(name);
                                }
                                DataInputStream fi = new DataInputStream(new FileInputStream(new File(resDir, name)));
                                setResource(d.getName(), MAGIC_FONT, loadFont(fi, d.getName(), false));
                                fi.close();
                            }
                        }
                        
                        if(xmlData.getImage() != null) {
                            for(com.codename1.ui.util.xml.Image d : xmlData.getImage()) {
                                if(d.getType() == null) {
                                    // standara JPG or PNG
                                    String name = d.getName();
                                    if(normalize) {
                                        name = normalizeFileName(name);
                                    }
                                    FileInputStream fi = new FileInputStream(new File(resDir, name));
                                    EncodedImage e = EncodedImage.create(fi);
                                    fi.close();
                                    setResource(d.getName(), MAGIC_IMAGE, e);
                                    continue;
                                }

                                if("svg".equals(d.getType())) {
                                    setResource(d.getName(), MAGIC_IMAGE, Image.createSVG(d.getType(), false, readFile(resDir, d.getName(), normalize)));
                                    continue;
                                }

                                if("timeline".equals(d.getType())) {
                                    String name = d.getName();
                                    if(normalize) {
                                        name = normalizeFileName(name);
                                    }
                                    DataInputStream fi = new DataInputStream(new FileInputStream(new File(resDir, name)));
                                    setResource(d.getName(), MAGIC_IMAGE, readTimeline(fi));
                                    fi.close();
                                    continue;
                                }
                                
                                if("multi".equals(d.getType())) {
                                    String name = d.getName();
                                    if(normalize) {
                                        name = normalizeFileName(name);
                                    }
                                    File multiImageDir = new File(resDir, name);
                                    File hd4k = new File(multiImageDir, "4k.png");
                                    File hd2 = new File(multiImageDir, "2hd.png");
                                    File hd560 = new File(multiImageDir, "560.png");
                                    File hd = new File(multiImageDir, "hd.png");
                                    File veryhigh = new File(multiImageDir, "veryhigh.png");
                                    File high = new File(multiImageDir, "high.png");
                                    File medium = new File(multiImageDir, "medium.png");
                                    File low = new File(multiImageDir, "low.png");
                                    File veryLow = new File(multiImageDir, "verylow.png");
                                    
                                    Map<Integer, EncodedImage> images = new HashMap<Integer, EncodedImage>();
                                    if(hd4k.exists()) {
                                        images.put(new Integer(Display.DENSITY_4K), EncodedImage.create(readFileNoNormal(hd4k)));
                                    }
                                    if(hd2.exists()) {
                                        images.put(new Integer(Display.DENSITY_2HD), EncodedImage.create(readFileNoNormal(hd2)));
                                    }
                                    if(hd560.exists()) {
                                        images.put(new Integer(Display.DENSITY_560), EncodedImage.create(readFileNoNormal(hd560)));
                                    }
                                    if(hd.exists()) {
                                        images.put(new Integer(Display.DENSITY_HD), EncodedImage.create(readFileNoNormal(hd)));
                                    }
                                    if(veryhigh.exists()) {
                                        images.put(new Integer(Display.DENSITY_VERY_HIGH), EncodedImage.create(readFileNoNormal(veryhigh)));
                                    }
                                    if(high.exists()) {
                                        images.put(new Integer(Display.DENSITY_HIGH), EncodedImage.create(readFileNoNormal(high)));
                                    }
                                    if(medium.exists()) {
                                        images.put(new Integer(Display.DENSITY_MEDIUM), EncodedImage.create(readFileNoNormal(medium)));
                                    }
                                    if(low.exists()) {
                                        images.put(new Integer(Display.DENSITY_LOW), EncodedImage.create(readFileNoNormal(low)));
                                    }
                                    if(veryLow.exists()) {
                                        images.put(new Integer(Display.DENSITY_VERY_LOW), EncodedImage.create(readFileNoNormal(veryLow)));
                                    }
                                    
                                    int[] dpis = new int[images.size()];
                                    EncodedImage[] imageArray = new EncodedImage[images.size()];
                                    
                                    int count = 0;
                                    for(Map.Entry<Integer, EncodedImage> m : images.entrySet()) {
                                        dpis[count] = m.getKey().intValue();
                                        imageArray[count] = m.getValue();
                                        count++;
                                    }
                                    
                                    MultiImage result = new MultiImage();
                                    result.setDpi(dpis);
                                    result.setInternalImages(imageArray);
                                    setResource(d.getName(), MAGIC_IMAGE, result);
                                    continue;
                                }
                            }
                        }

                        if(xmlData.getL10n() != null) {
                            for(L10n d : xmlData.getL10n()) {
                                Hashtable<String, Hashtable<String, String>> l10n = new Hashtable<String, Hashtable<String, String>>();
                                
                                for(Lang l : d.getLang()) {
                                    Hashtable<String, String> language = new Hashtable<String, String>();
                                    
                                    if(l != null && l.getEntry() != null) {
                                        for(Entry e : l.getEntry()) {
                                            language.put(e.getKey(), e.getValue());
                                        }
                                    }
                                    
                                    l10n.put(l.getName(), language);
                                }
                                
                                setResource(d.getName(), MAGIC_L10N, l10n);
                            }
                        }
                        
                        if(xmlData.getTheme() != null) {
                            for(Theme d : xmlData.getTheme()) {
                                Hashtable<String, Object> theme = new Hashtable<String, Object>();
                                theme.put("uninitialized", Boolean.TRUE);
                                
                                if(d.getVal() != null) {
                                    for(Val v : d.getVal()) {
                                        String key = v.getKey();
                                    
                                        if(key.endsWith("align") || key.endsWith("textDecoration")) {
                                            theme.put(key, Integer.valueOf(v.getValue()));
                                            continue;
                                        }
                                        if(key.endsWith(Style.BACKGROUND_TYPE) || key.endsWith(Style.BACKGROUND_ALIGNMENT)) {
                                            theme.put(key, Byte.valueOf(v.getValue()));
                                            continue;
                                        }
                                        // padding and or margin type
                                        if(key.endsWith("Unit")) {
                                            String[] s = v.getValue().split(",");
                                            theme.put(key, new byte[] {Byte.parseByte(s[0]), Byte.parseByte(s[1]), Byte.parseByte(s[2]), Byte.parseByte(s[3])}); 
                                            continue;
                                        }
                                        theme.put(key, v.getValue());
                                    }
                                }
                                
                                if(d.getBorder() != null) {
                                    for(com.codename1.ui.util.xml.Border b : d.getBorder()) {
                                        if("empty".equals(b.getType())) {
                                            theme.put(b.getKey(), Border.createEmpty());
                                            continue;
                                        }

                                        if("round".equals(b.getType())) {
                                            RoundBorder rb = RoundBorder.create();
                                            rb = rb.opacity(b.getOpacity());
                                            rb = rb.color(b.getRoundBorderColor());
                                            rb = rb.rectangle(b.isRectangle());
                                            rb = rb.shadowBlur(b.getShadowBlur());
                                            rb = rb.shadowOpacity(b.getShadowOpacity());
                                            rb = rb.shadowSpread((int)b.getShadowSpread(), b.isShadowMM());
                                            rb = rb.shadowX(b.getShadowX());
                                            rb = rb.shadowY(b.getShadowY());
                                            rb = rb.stroke(b.getStrokeThickness(), b.isStrokeMM());
                                            rb = rb.strokeColor(b.getStrokeColor());
                                            rb = rb.strokeOpacity(b.getStrokeOpacity());
                                            
                                            theme.put(b.getKey(), rb);
                                            continue;
                                        }
                                        if("css".equals(b.getType())) {
                                            CSSBorder cb = new CSSBorder(this, b.getCSS());
                                            theme.put(b.getKey(), cb);
                                            continue;
                                        }
                                        
                                        if("roundRect".equals(b.getType())) {
                                            RoundRectBorder rb = RoundRectBorder.create();
                                            rb = rb.shadowBlur(b.getShadowBlur());
                                            rb = rb.shadowOpacity(b.getShadowOpacity());
                                            rb = rb.shadowSpread(b.getShadowSpread());
                                            rb = rb.shadowX(b.getShadowX());
                                            rb = rb.shadowY(b.getShadowY());
                                            rb = rb.stroke(b.getStrokeThickness(), b.isStrokeMM());
                                            rb = rb.strokeColor(b.getStrokeColor());
                                            rb = rb.strokeOpacity(b.getStrokeOpacity());
                                            rb = rb.bezierCorners(b.isBezierCorners());
                                            if (b.isTopOnlyMode()) {
                                                rb.topOnlyMode(true);
                                            } else if (b.isBottomOnlyMode()) {
                                                rb.bottomOnlyMode(true);
                                            }
                                            
                                            rb = rb.cornerRadius(b.getCornerRadius());
                                            theme.put(b.getKey(), rb);
                                            continue;
                                        }

                                        if("line".equals(b.getType())) {
                                            if(b.getColor() == null) {
                                                if(b.isMillimeters()) {
                                                    theme.put(b.getKey(), Border.createLineBorder(b.getThickness().floatValue()));
                                                } else {
                                                    theme.put(b.getKey(), Border.createLineBorder(b.getThickness().intValue()));
                                                }
                                            } else {
                                                if(b.isMillimeters()) {
                                                    theme.put(b.getKey(), Border.createLineBorder(b.getThickness().floatValue(), b.getColor().intValue()));
                                                } else {
                                                    theme.put(b.getKey(), Border.createLineBorder(b.getThickness().intValue(), b.getColor().intValue()));
                                                }
                                            }
                                            continue;
                                        }

                                        if("underline".equals(b.getType())) {
                                            if(b.getColor() == null) {
                                                theme.put(b.getKey(), Border.createUndelineBorder(b.getThickness().intValue()));
                                            } else {
                                                theme.put(b.getKey(), Border.createUnderlineBorder(b.getThickness().intValue(), b.getColor().intValue()));
                                            }
                                            continue;
                                        }

                                        if("rounded".equals(b.getType())) {
                                            if(b.getColor() == null) {
                                                theme.put(b.getKey(), Border.createRoundBorder(b.getArcW().intValue(), b.getArcH().intValue()));
                                            } else {
                                                theme.put(b.getKey(), Border.createRoundBorder(b.getArcW().intValue(), b.getArcH().intValue(), b.getColor().intValue()));
                                            }
                                            continue;
                                        }

                                        if("etchedRaised".equals(b.getType())) {
                                            if(b.getColor() == null) {
                                                theme.put(b.getKey(), Border.createEtchedRaised());
                                            } else {
                                                theme.put(b.getKey(), Border.createEtchedRaised(b.getColor().intValue(), b.getColorB().intValue()));
                                            }
                                            continue;
                                        }

                                        if("etchedLowered".equals(b.getType())) {
                                            if(b.getColor() == null) {
                                                theme.put(b.getKey(), Border.createEtchedLowered());
                                            } else {
                                                theme.put(b.getKey(), Border.createEtchedLowered(b.getColor().intValue(), b.getColorB().intValue()));
                                            }
                                            continue;
                                        }
                                        if("bevelLowered".equals(b.getType())) {
                                            if(b.getColor() == null) {
                                                theme.put(b.getKey(), Border.createBevelLowered());
                                            } else {
                                                theme.put(b.getKey(), Border.createBevelLowered(b.getColor().intValue(), b.getColorB().intValue(), b.getColorC().intValue(), b.getColorD().intValue()));
                                            }
                                            continue;
                                        }
                                        if("bevelRaised".equals(b.getType())) {
                                            if(b.getColor() == null) {
                                                theme.put(b.getKey(), Border.createBevelRaised());
                                            } else {
                                                theme.put(b.getKey(), Border.createBevelRaised(b.getColor().intValue(), b.getColorB().intValue(), b.getColorC().intValue(), b.getColorD().intValue()));
                                            }
                                            continue;
                                        }
                                        if("image".equals(b.getType())) {
                                            int imageCount = 2;
                                            if(b.getI9() != null) {
                                                imageCount = 9;
                                            } else {
                                                if(b.getI8() != null) {
                                                    imageCount = 8;
                                                } else {
                                                    if(b.getI3() != null) {
                                                        imageCount = 3;
                                                    }
                                                }
                                            }

                                            String[] borderInstance;
                                            switch(imageCount) {
                                                case 2:
                                                    borderInstance = new String[] {b.getI1(), b.getI2()};
                                                    break;
                                                case 3:
                                                    borderInstance = new String[] {b.getI1(), b.getI2(), b.getI3()};
                                                    break;
                                                case 8:
                                                    borderInstance = new String[] {b.getI1(), b.getI2(), b.getI3(), b.getI4(), b.getI5(), b.getI6(), b.getI7(), b.getI8()};
                                                    break;
                                                default:
                                                    borderInstance = new String[] {b.getI1(), b.getI2(), b.getI3(), b.getI4(), b.getI5(), b.getI6(), b.getI7(), b.getI8(), b.getI9()};
                                                    break;
                                            }

                                            theme.put(b.getKey(), borderInstance);
                                            continue;
                                        }
                                        if("imageH".equals(b.getType())) {
                                            theme.put(b.getKey(), new String[] {"h", b.getI1(), b.getI2(), b.getI3()} );
                                            continue;
                                        }
                                        if("imageV".equals(b.getType())) {
                                            theme.put(b.getKey(), new String[] {"v", b.getI1(), b.getI2(), b.getI3()} );
                                            continue;
                                        }
                                    }
                                }
                                
                                if(d.getFont() != null) {
                                    for(com.codename1.ui.util.xml.Font b : d.getFont()) {
                                        if("ttf".equals(b.getType())) {
                                            com.codename1.ui.Font system = com.codename1.ui.Font.createSystemFont(b.getFace().intValue(), b.getStyle().intValue(), b.getSize().intValue());
                                            EditorTTFFont t;
                                            if(b.getName().startsWith("native:")) {
                                                t = new EditorTTFFont(b.getName(), b.getSizeSettings().intValue(), b.getActualSize().floatValue(), system);
                                            } else {
                                                t = new EditorTTFFont(new File(f.getParentFile(), b.getName()), b.getSizeSettings().intValue(), b.getActualSize().floatValue(), system);
                                            }
                                            theme.put(b.getKey(), t);
                                            continue;
                                        }
                                        if("system".equals(b.getType())) {
                                            com.codename1.ui.Font system = com.codename1.ui.Font.createSystemFont(b.getFace().intValue(), b.getStyle().intValue(), b.getSize().intValue());
                                            theme.put(b.getKey(), system);
                                            continue;
                                        }
                                        // bitmap fonts aren't supported right now
                                    }
                                }

                                if(d.getGradient() != null) {
                                    for(com.codename1.ui.util.xml.Gradient b : d.getGradient()) {
                                        theme.put(b.getKey(), new Object[] {
                                            b.getColor1(), b.getColor2(), b.getPosX(), b.getPosY(), b.getRadius()
                                        });                                    
                                    }
                                }

                                setResource(d.getName(), MAGIC_THEME, theme);
                            }
                        }
                        
                        // we load the UI last since it might depend on images or other elements in the future
                        if(xmlData.getUi() != null) {
                            if(xmlData.isUseXmlUI()) {
                                ArrayList<ComponentEntry> guiElements = new ArrayList<ComponentEntry>();
                                
                                // place renderers first
                                final ArrayList<String> renderers = new ArrayList<String>();
                                for(Ui d : xmlData.getUi()) {
                                    JAXBContext componentContext = JAXBContext.newInstance(ComponentEntry.class);
                                    File uiFile = new File(resDir, normalizeFileName(d.getName()) + ".ui");
                                    ComponentEntry uiXMLData = (ComponentEntry)componentContext.createUnmarshaller().unmarshal(uiFile);                                    
                                    guiElements.add(uiXMLData);
                                    uiXMLData.findRendererers(renderers);
                                }
                                
                                Collections.sort(guiElements, new Comparator<ComponentEntry>() {
                                    private final ArrayList<String> entries1 = new ArrayList<String>();
                                    private final ArrayList<String> entries2 = new ArrayList<String>();
                                    @Override
                                    public int compare(ComponentEntry o1, ComponentEntry o2) {
                                        if(renderers.contains(o1.getName())) {
                                            return -1;
                                        }
                                        if(renderers.contains(o2.getName())) {
                                            return 1;
                                        }
                                       
                                        entries1.clear();
                                        entries2.clear();
                                       
                                        o1.findEmbeddedDependencies(entries1);
                                        o2.findEmbeddedDependencies(entries2);
                                        if(entries1.size() == 0) {
                                            if(entries2.size() == 0) {
                                                return 0;
                                            }
                                            return -1;
                                        } else {
                                            if(entries2.size() == 0) {
                                                return 1;
                                            }
                                        }
                                        for(String e : entries1) {
                                            if(e.equals(o2.getName())) {
                                                return 1;
                                            }
                                        }
                                        for(String e : entries2) {
                                            if(e.equals(o1.getName())) {
                                                return -1;
                                            }
                                        }
                                        return 0;
                                    }
                                });
                                for(ComponentEntry uiXMLData : guiElements) {
                                    UIBuilderOverride uib = new UIBuilderOverride();
                                    com.codename1.ui.Container cnt = uib.createInstance(uiXMLData, this);
                                    
                                    // encountered an error loading the component fallback to loading with the binary types
                                    if(cnt == null) {
                                        for(Ui ui : xmlData.getUi()) {
                                            setResource(uiXMLData.getName(), MAGIC_UI, readFile(resDir, ui.getName(), normalize));
                                        }
                                        break;
                                    } else {
                                        byte[] data = UserInterfaceEditor.persistContainer(cnt, this);
                                        setResource(uiXMLData.getName(), MAGIC_UI, data);
                                    }
                                }
                            } else {
                                for(Ui d : xmlData.getUi()) {
                                    setResource(d.getName(), MAGIC_UI, readFile(resDir, d.getName(), normalize));
                                }
                            }
                        }
                        
                        loadingMode = false;
                        modified = false;
                        updateModified();

                        // can occure when a resource file is opened via the constructor
                        if(undoQueue != null) {
                            undoQueue.clear();
                            redoQueue.clear();
                        }
                        
                        return;
                    } catch(JAXBException err) {
                        err.printStackTrace();
                    }
                }
            }
        } 
        openFile(new FileInputStream(f));
    }
    
    private void writeToFile(byte[] data, File f) throws IOException {
        FileOutputStream o = new FileOutputStream(f);
        o.write(data);
        o.close();
    }
    
    /**
     * Converts a String to XML body string
     * @param s the string to convert
     * @return XMLized string with entity escapes
     */
    public static String xmlize(String s) {
        s = s.replaceAll("&", "&amp;");
        s = s.replaceAll("<", "&lt;");
        s = s.replaceAll(">", "&gt;");
        s = s.replaceAll("\"", "&quot;");
        int charCount = s.length();
        for(int iter = 0 ; iter < charCount ; iter++) {
            char c = s.charAt(iter);
            if(c > 127) {
                // we need to localize the string...
                StringBuilder b = new StringBuilder();
                for(int counter = 0 ; counter < charCount ; counter++) {
                    c = s.charAt(counter);
                    if(c > 127) {
                        b.append("&#x");
                        b.append(Integer.toHexString(c));
                        b.append(";");
                    } else {
                        b.append(c);
                    }
                }
                return b.toString();
            }
        }
        return s;
    }
    
    
    private void saveXMLFile(File xml, File resourcesDir) throws IOException {
        // disable override for the duration of the save so stuff from the override doesn't
        // get into the main resource file
        File overrideFileBackup = overrideFile;
        EditableResources overrideResourceBackup = overrideResource;
        overrideResource = null;
        overrideFile = null;
        try {
            BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(xml), "UTF-8"));
            String[] resourceNames = getResourceNames();
            Arrays.sort(resourceNames, String.CASE_INSENSITIVE_ORDER);

            bw.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\n");
            bw.write("<resource majorVersion=\"" + MAJOR_VERSION + "\" minorVersion=\"" + MINOR_VERSION + "\" useXmlUI=\"" + xmlUI + "\">\n");
            
            for(int iter = 0 ; iter < resourceNames.length ; iter++) {
                String xResourceName = xmlize(resourceNames[iter]);
                // write the magic number
                byte magic = getResourceType(resourceNames[iter]);
                switch(magic) {
                    case MAGIC_TIMELINE:
                    case MAGIC_ANIMATION_LEGACY:
                    case MAGIC_IMAGE_LEGACY:
                    case MAGIC_INDEXED_IMAGE_LEGACY:
                        magic = MAGIC_IMAGE;
                        break;
                    case MAGIC_THEME_LEGACY:
                        magic = MAGIC_THEME;
                        break;
                    case MAGIC_FONT_LEGACY:
                        magic = MAGIC_FONT;
                        break;
                }
                
                switch(magic) {
                    case MAGIC_IMAGE:
                        Object o = getResourceObject(resourceNames[iter]);
                        if(!(o instanceof MultiImage)) {
                            o = null;
                        }
                        bw.write("    <image name=\"" + xResourceName + "\" ");
                        com.codename1.ui.Image image = getImage(resourceNames[iter]);
                        MultiImage mi = (MultiImage)o;
                        int rType = getImageType(image, mi);
                        switch(rType) {
                            // PNG file
                            case 0xf1:

                            // JPEG File
                            case 0xf2:
                                if(image instanceof EncodedImage) {
                                    byte[] data = ((EncodedImage)image).getImageData();
                                    writeToFile(data, new File(resourcesDir, normalizeFileName(resourceNames[iter])));
                                } else {
                                    FileOutputStream fo = new FileOutputStream(new File(resourcesDir, normalizeFileName(resourceNames[iter])));
                                    BufferedImage buffer = new BufferedImage(image.getWidth(), image.getHeight(), BufferedImage.TYPE_INT_ARGB);
                                    buffer.setRGB(0, 0, image.getWidth(), image.getHeight(), image.getRGB(), 0, image.getWidth());
                                    ImageIO.write(buffer, "png", fo);
                                    fo.close();
                                }
                                break;

                            // SVG
                            case 0xf5:
                            // multiimage with SVG
                            case 0xf7:
                                SVG s = (SVG)image.getSVGDocument();
                                writeToFile(s.getSvgData(), new File(resourcesDir, normalizeFileName(resourceNames[iter])));

                                if(s.getBaseURL() != null && s.getBaseURL().length() > 0) {
                                    bw.write("baseUrl=\"" + s.getBaseURL() + "\" ");
                                }
                                bw.write("type=\"svg\" ");
                                break;

                            case 0xF6:
                                File multiImageDir = new File(resourcesDir, normalizeFileName(resourceNames[iter]));
                                multiImageDir.mkdirs();
                                for(int imageIter = 0 ; imageIter < mi.getDpi().length ; imageIter++) {
                                    File f = null;
                                    switch(mi.getDpi()[imageIter]) {
                                        case Display.DENSITY_4K:
                                            f = new File(multiImageDir, "4k.png");
                                            break;
                                        case Display.DENSITY_2HD:
                                            f = new File(multiImageDir, "2hd.png");
                                            break;
                                        case Display.DENSITY_560:
                                            f = new File(multiImageDir, "560.png");
                                            break;
                                        case Display.DENSITY_HD:
                                            f = new File(multiImageDir, "hd.png");
                                            break;
                                        case Display.DENSITY_VERY_HIGH:
                                            f = new File(multiImageDir, "veryhigh.png");
                                            break;
                                        case Display.DENSITY_HIGH:
                                            f = new File(multiImageDir, "high.png");
                                            break;
                                        case Display.DENSITY_MEDIUM:
                                            f = new File(multiImageDir, "medium.png");
                                            break;
                                        case Display.DENSITY_LOW:
                                            f = new File(multiImageDir, "low.png");
                                            break;
                                        case Display.DENSITY_VERY_LOW:
                                            f = new File(multiImageDir, "verylow.png");
                                            break;
                                    }
                                    writeToFile(mi.getInternalImages()[imageIter].getImageData(), f);
                                }
                                bw.write("type=\"multi\" ");
                                break;

                            // Timeline
                            case MAGIC_TIMELINE:
                                File timeline = new File(resourcesDir, normalizeFileName(resourceNames[iter]));
                                DataOutputStream timelineOut = new DataOutputStream(new FileOutputStream(timeline));
                                writeTimeline(timelineOut, (Timeline)image);
                                timelineOut.close();
                                bw.write("type=\"timeline\" ");
                                break;

                            // Fail this is the wrong data type
                            default:
                                throw new IOException("Illegal type while creating image: " + Integer.toHexString(rType));
                        }
                        bw.write(" />\n");

                        continue;
                    case MAGIC_THEME:
                        Hashtable<String, Object> theme = getTheme(resourceNames[iter]);
                        theme.remove("name");
                        bw.write("    <theme name=\"" + xResourceName + "\">\n");

                        ArrayList<String> setOfKeys = new ArrayList<String>(theme.keySet());
                        Collections.sort(setOfKeys);
                        for(String key : setOfKeys) {

                            if(key.startsWith("@")) {
                                if(key.endsWith("Image")) {
                                    bw.write("        <val key=\"" + key + "\" value=\"" + findId(theme.get(key), true) + "\" />\n");
                                } else {
                                    bw.write("        <val key=\"" + key + "\" value=\"" + theme.get(key) + "\" />\n");
                                }
                                continue;
                            }

                            // if this is a simple numeric value
                            if(key.endsWith("Color")) {
                                bw.write("        <val key=\"" + key + "\" value=\"" + theme.get(key) + "\" />\n");
                                continue;
                            } 

                            if(key.endsWith("align") || key.endsWith("textDecoration")) {
                                bw.write("        <val key=\"" + key + "\" value=\"" + ((Number)theme.get(key)).shortValue() + "\" />\n");
                                continue;
                            }

                            // if this is a short numeric value
                            if(key.endsWith("transparency")) {
                                bw.write("        <val key=\"" + key + "\" value=\"" + theme.get(key) + "\" />\n");
                                continue;
                            }
                            
                            if(key.endsWith("opacity")) {
                                bw.write("        <val key=\"" + key + "\" value=\"" + theme.get(key) + "\" />\n");
                                continue;
                            } 

                            // if this is a padding or margin then we will have the 4 values as bytes
                            if(key.endsWith("padding") || key.endsWith("margin")) {
                                bw.write("        <val key=\"" + key + "\" value=\"" + theme.get(key) + "\" />\n");
                                continue;
                            }

                            // padding and or margin type
                            if(key.endsWith("Unit")) {
                                byte[] b = (byte[])theme.get(key);
                                bw.write("        <val key=\"" + key + "\" value=\"" + b[0] + "," + b[1] + "," + b[2] + "," + b[3] + "\" />\n");
                                continue;
                            }

                            if(key.endsWith("border")) {                                
                                Border border = (Border)theme.get(key);
                                if(border instanceof RoundBorder) {
                                    RoundBorder rb = (RoundBorder)border;
                                    bw.write("        <border key=\"" + key + "\" type=\"round\" "
                                            + "roundBorderColor=\"" + rb.getColor()+ "\" " 
                                            + "opacity=\"" + rb.getOpacity() + "\" " 
                                            + "strokeColor=\"" + rb.getStrokeColor()+ "\" " 
                                            + "strokeOpacity=\"" + rb.getStrokeOpacity()+ "\" " 
                                            + "strokeThickness=\"" + rb.getStrokeThickness()+ "\" " 
                                            + "strokeMM=\"" + rb.isStrokeMM()+ "\" " 
                                            + "shadowSpread=\"" + rb.getShadowSpread()+ "\" " 
                                            + "shadowOpacity=\"" + rb.getShadowOpacity()+ "\" " 
                                            + "shadowX=\"" + rb.getShadowX()+ "\" " 
                                            + "shadowY=\"" + rb.getShadowY()+ "\" " 
                                            + "shadowBlur=\"" + rb.getShadowBlur()+ "\" " 
                                            + "shadowMM=\"" + rb.isShadowMM()+ "\" " 
                                            + "rectangle=\"" + rb.isRectangle()+ "\" />\n");
                            
                                    continue;
                                }
                                if (border instanceof CSSBorder) {
                                    bw.write("        <border key=\"" + key + "\" type=\"css\" "
                                            + "css=\"" + encodeXML(((CSSBorder)border).toCSSString())+ "\"/>\n");
                                    continue;
                                }
                                if(border instanceof RoundRectBorder) {
                                    RoundRectBorder rb = (RoundRectBorder)border;
                                    bw.write("        <border key=\"" + key + "\" type=\"roundRect\" "
                                            + "strokeColor=\"" + rb.getStrokeColor()+ "\" " 
                                            + "strokeOpacity=\"" + rb.getStrokeOpacity()+ "\" " 
                                            + "strokeThickness=\"" + rb.getStrokeThickness()+ "\" " 
                                            + "strokeMM=\"" + rb.isStrokeMM()+ "\" " 
                                            + "shadowSpread=\"" + rb.getShadowSpread()+ "\" " 
                                            + "shadowOpacity=\"" + rb.getShadowOpacity()+ "\" " 
                                            + "shadowX=\"" + rb.getShadowX()+ "\" " 
                                            + "shadowY=\"" + rb.getShadowY()+ "\" " 
                                            + "shadowBlur=\"" + rb.getShadowBlur()+ "\" " 
                                            + "topOnlyMode=\"" + rb.isTopOnlyMode()+ "\" " 
                                            + "bottomOnlyMode=\"" + rb.isBottomOnlyMode()+ "\" " 
                                            + "cornerRadius=\"" + rb.getCornerRadius()+ "\" " 
                                            + "bezierCorners=\"" + rb.isBezierCorners()+ "\" />\n");
                            
                                    continue;
                                }
                                int type = Accessor.getType(border);
                                switch(type) {
                                    case BORDER_TYPE_EMPTY:
                                        bw.write("        <border key=\"" + key + "\" type=\"empty\" />\n");
                                        continue;
                                    case BORDER_TYPE_LINE:
                                        // use theme colors?
                                        if(Accessor.isThemeColors(border)) {
                                            bw.write("        <border key=\"" + key + "\" type=\"line\" millimeters=\"" + 
                                                    Accessor.isMillimeters(border) +
                                                    "\" thickness=\"" + Accessor.getThickness(border) + "\" />\n");
                                        } else {
                                            bw.write("        <border key=\"" + key + "\" type=\"line\" millimeters=\"" + 
                                                    Accessor.isMillimeters(border) +
                                                    "\" thickness=\"" + Accessor.getThickness(border) + "\" color=\""
                                                    + Accessor.getColorA(border) + "\" />\n");
                                        }
                                        continue;
                                    case BORDER_TYPE_UNDERLINE:
                                        // use theme colors?
                                        if(Accessor.isThemeColors(border)) {
                                            bw.write("        <border key=\"" + key + "\" type=\"underline\" millimeters=\"" + 
                                                    Accessor.isMillimeters(border) +
                                                    "\" thickness=\"" + Accessor.getThickness(border) + "\" />\n");
                                        } else {
                                            bw.write("        <border key=\"" + key + "\" type=\"underline\"  millimeters=\"" + 
                                                    Accessor.isMillimeters(border) +"\" thickness=\"" + 
                                                    Accessor.getThickness(border) + "\" color=\""
                                                    + Accessor.getColorA(border) + "\" />\n");
                                        }
                                        continue;
                                    case BORDER_TYPE_ROUNDED:
                                        if(Accessor.isThemeColors(border)) {
                                            bw.write("        <border key=\"" + key + "\" type=\"rounded\" "
                                                    + "thickness=\"" + Accessor.getThickness(border) + "\" arcW=\""
                                                    + Accessor.getArcWidth(border) + "\" arcH=\""
                                                    + Accessor.getArcHeight(border) + "\" />\n");
                                        } else {
                                            bw.write("        <border key=\"" + key + "\" type=\"rounded\" "
                                                    + "thickness=\"" + Accessor.getThickness(border) + "\" arcW=\""
                                                    + Accessor.getArcWidth(border) + "\" arcH=\""
                                                    + Accessor.getArcHeight(border) + "\" color=\""
                                                    + Accessor.getColorA(border) + "\" />\n");
                                        }
                                        continue;
                                    case BORDER_TYPE_ETCHED_RAISED:
                                        // use theme colors?
                                        if(Accessor.isThemeColors(border)) {
                                            bw.write("        <border key=\"" + key + "\" type=\"etchedRaised\" "
                                                    + "thickness=\"" + Accessor.getThickness(border) + "\" />\n");
                                        } else {
                                            bw.write("        <border key=\"" + key + "\" type=\"etchedRaised\" "
                                                    + "thickness=\"" + Accessor.getThickness(border) + "\" color=\""
                                                    + Accessor.getColorA(border) + "\" colorB=\""
                                                    + Accessor.getColorB(border) + "\" />\n");
                                        }
                                        continue;
                                    case BORDER_TYPE_ETCHED_LOWERED:
                                        // use theme colors?
                                        if(Accessor.isThemeColors(border)) {
                                            bw.write("        <border key=\"" + key + "\" type=\"etchedLowered\" "
                                                    + "thickness=\"" + Accessor.getThickness(border) + "\" />\n");
                                        } else {
                                            bw.write("        <border key=\"" + key + "\" type=\"etchedLowered\" "
                                                    + "thickness=\"" + Accessor.getThickness(border) + "\" color=\""
                                                    + Accessor.getColorA(border) + "\" colorB=\""
                                                    + Accessor.getColorB(border) + "\" />\n");
                                        }
                                        continue;
                                    case BORDER_TYPE_BEVEL_LOWERED:
                                        // use theme colors?
                                        if(Accessor.isThemeColors(border)) {
                                            bw.write("        <border key=\"" + key + "\" type=\"bevelLowered\" "
                                                    + "thickness=\"" + Accessor.getThickness(border) + "\" />\n");
                                        } else {
                                            bw.write("        <border key=\"" + key + "\" type=\"bevelLowered\" "
                                                    + "thickness=\"" + Accessor.getThickness(border) + "\" color=\""
                                                    + Accessor.getColorA(border) + "\" colorB=\""
                                                    + Accessor.getColorB(border) + "\" colorC=\""
                                                    + Accessor.getColorC(border) + "\" colorD=\""
                                                    + Accessor.getColorD(border) + "\" />\n");
                                        }
                                        continue;
                                    case BORDER_TYPE_BEVEL_RAISED:
                                        if(Accessor.isThemeColors(border)) {
                                            bw.write("        <border key=\"" + key + "\" type=\"bevelRaised\" "
                                                    + "thickness=\"" + Accessor.getThickness(border) + "\" />\n");
                                        } else {
                                            bw.write("        <border key=\"" + key + "\" type=\"bevelRaised\" "
                                                    + "thickness=\"" + Accessor.getThickness(border) + "\" color=\""
                                                    + Accessor.getColorA(border) + "\" colorB=\""
                                                    + Accessor.getColorB(border) + "\" colorC=\""
                                                    + Accessor.getColorC(border) + "\" colorD=\""
                                                    + Accessor.getColorD(border) + "\" />\n");
                                        }
                                        continue;
                                    //case BORDER_TYPE_IMAGE_SCALED:
                                    case BORDER_TYPE_IMAGE: {
                                        Image[] images = Accessor.getImages(border);
                                        int resourceCount = 0;
                                        for(int counter = 0 ; counter < images.length ; counter++) {
                                            if(images[counter] != null && findId(images[counter], true) != null) {
                                                resourceCount++;
                                            }
                                        }
                                        if(resourceCount != 2 && resourceCount != 3 && resourceCount != 8 && resourceCount != 9) {
                                            System.out.println("Odd resource count for image border: " + resourceCount);
                                            resourceCount = 2;
                                        }
                                        switch(resourceCount) {
                                            case 2:
                                                bw.write("        <border key=\"" + key + "\" type=\"image\" "
                                                        + "i1=\"" + findId(images[0], true) + "\" "
                                                        + "i2=\"" + findId(images[4], true) + "\" />\n");
                                                break;
                                            case 3:
                                                bw.write("        <border key=\"" + key + "\" type=\"image\" "
                                                        + "i1=\"" + findId(images[0], true) + "\" "
                                                        + "i2=\"" + findId(images[4], true) + "\" "
                                                        + "i3=\"" + findId(images[8], true) + "\" />\n");
                                                break;
                                            case 8:
                                                bw.write("        <border key=\"" + key + "\" type=\"image\" "
                                                        + "i1=\"" + findId(images[0], true) + "\" "
                                                        + "i2=\"" + findId(images[1], true) + "\" "
                                                        + "i3=\"" + findId(images[2], true) + "\" "
                                                        + "i4=\"" + findId(images[3], true) + "\" "
                                                        + "i5=\"" + findId(images[4], true) + "\" "
                                                        + "i6=\"" + findId(images[5], true) + "\" "
                                                        + "i7=\"" + findId(images[6], true) + "\" "
                                                        + "i8=\"" + findId(images[7], true) + "\" />\n");
                                                break;
                                            case 9:
                                                bw.write("        <border key=\"" + key + "\" type=\"image\" "
                                                        + "i1=\"" + findId(images[0], true) + "\" "
                                                        + "i2=\"" + findId(images[1], true) + "\" "
                                                        + "i3=\"" + findId(images[2], true) + "\" "
                                                        + "i4=\"" + findId(images[3], true) + "\" "
                                                        + "i5=\"" + findId(images[4], true) + "\" "
                                                        + "i6=\"" + findId(images[5], true) + "\" "
                                                        + "i7=\"" + findId(images[6], true) + "\" "
                                                        + "i8=\"" + findId(images[7], true) + "\" "
                                                        + "i9=\"" + findId(images[8], true) + "\" />\n");
                                                break;
                                        }
                                        continue;
                                    }
                                    case BORDER_TYPE_IMAGE_HORIZONTAL: {
                                        Image[] images = Accessor.getImages(border);
                                        bw.write("        <border key=\"" + key + "\" type=\"imageH\" "
                                                + "i1=\"" + findId(images[0], true) + "\" "
                                                + "i2=\"" + findId(images[1], true) + "\" "
                                                + "i3=\"" + findId(images[2], true) + "\" />\n");
                                        continue;
                                    }
                                    case BORDER_TYPE_IMAGE_VERTICAL: {
                                        Image[] images = Accessor.getImages(border);
                                        bw.write("        <border key=\"" + key + "\" type=\"imageV\" "
                                                + "i1=\"" + findId(images[0], true) + "\" "
                                                + "i2=\"" + findId(images[1], true) + "\" "
                                                + "i3=\"" + findId(images[2], true) + "\" />\n");
                                        continue;
                                    }
                                }
                                continue;
                            }

                            // if this is a font
                            if(key.endsWith("font")) {
                                com.codename1.ui.Font f = (com.codename1.ui.Font)theme.get(key);

                                // is this a new font?
                                boolean newFont = f instanceof EditorFont;
                                if(newFont) {
                                    bw.write("        <font key=\"" + key + "\" type=\"named\" "
                                            + "name=\"" + findId(f) + "\" />\n");
                                } else {
                                    if(f instanceof EditorTTFFont && (((EditorTTFFont)f).getFontFile() != null || ((EditorTTFFont)f).getNativeFontName() != null)) {
                                        EditorTTFFont ed = (EditorTTFFont)f;
                                        String fname;
                                        String ffName;
                                        if(((EditorTTFFont)f).getNativeFontName() != null) {
                                            fname = ((EditorTTFFont)f).getNativeFontName();
                                            ffName = fname;
                                        } else {
                                            fname = ed.getFontFile().getName();
                                            ffName = ((java.awt.Font)ed.getNativeFont()).getPSName();
                                        }
                                        bw.write("        <font key=\"" + key + "\" type=\"ttf\" "
                                                + "face=\"" + f.getFace() + "\" "
                                                + "style=\"" + f.getStyle() + "\" "
                                                + "size=\"" + f.getSize() + "\" "
                                                + "name=\"" + fname + "\" "
                                                + "family=\"" + ffName+ "\" "
                                                + "sizeSettings=\"" + ed.getSizeSetting(