public Array<String> readFile(FileHandle fileHandle) throws Exception {
        Array<String> warnings = new Array<>();
        
        projectData.setChangesSaved(false);
        
        //read drawables from texture atlas file
        FileHandle atlasHandle = fileHandle.sibling(fileHandle.nameWithoutExtension() + ".atlas");
        if (atlasHandle.exists()) {
            projectData.getAtlasData().readAtlas(atlasHandle);
        } else {
            warnings.add("[RED]ERROR:[] Atlas file [BLACK]" + atlasHandle.name() + "[] does not exist.");
        }

        //folder for critical files to be copied to
        FileHandle saveFile = projectData.getSaveFile();
        FileHandle targetDirectory;
        if (saveFile != null) {
            targetDirectory = saveFile.sibling(saveFile.nameWithoutExtension() + "_data");
        } else {
            targetDirectory = Main.appFolder.child("temp/" + projectData.getId() + "_data");
        }

        //read json file and create styles
        JsonReader reader = new JsonReader();
        JsonValue val = reader.parse(fileHandle.reader("UTF-8"));

        for (JsonValue child : val.iterator()) {
            //fonts
            if (child.name().equals(BitmapFont.class.getName()) || child.name().equals(BitmapFont.class.getSimpleName())) {
                for (JsonValue font : child.iterator()) {
                    if (font.get("file") != null) {
                        FileHandle fontFile = fileHandle.sibling(font.getString("file"));
                        if (!fontFile.exists()) {
                            warnings.add("[RED]ERROR:[] Font file [BLACK]" + fontFile.name() + "[] does not exist.");
                            return warnings;
                        }
                        FileHandle fontCopy = targetDirectory.child(font.getString("file"));
                        if (!fontCopy.parent().equals(fontFile.parent())) {
                            fontFile.copyTo(fontCopy);
                        }
                        FontData fontData = new FontData(font.name(), font.getInt("scaledSize", -1), font.getBoolean("markupEnabled", false), font.getBoolean("flip", false), fontCopy);

                        //delete fonts with the same name
                        for (FontData originalData : new Array<>(fonts)) {
                            if (originalData.getName().equals(fontData.getName())) {
                                fonts.removeValue(originalData, true);
                            }
                        }

                        fonts.add(fontData);

                        BitmapFont.BitmapFontData bitmapFontData = new BitmapFont.BitmapFontData(fontCopy, false);
                        for (String path : bitmapFontData.imagePaths) {
                            FileHandle file = new FileHandle(path);
                            
                            var drawable = projectData.getAtlasData().getDrawable(file.nameWithoutExtension());
                            if(drawable == null) {
                                drawable = projectData.getAtlasData().getFontDrawable(file.nameWithoutExtension());
                                if (drawable == null) {
                                    Gdx.app.error(getClass().getName(), "The Drawable for a BitmapFont was missing.");
                                    warnings.add("[RED]In font: [BLACK]" + bitmapFontData.name + "[], with path: [BLACK]" + path + "[], drawable: [BLACK]" + file.nameWithoutExtension() + "[] was not found!");
                                    continue; // ignores this drawable rather than throwing an NPE
                                }
                            }
                            drawable.type = DrawableType.FONT;
                            
                            projectData.getAtlasData().getDrawables().removeValue(drawable, false);
                            projectData.getAtlasData().getFontDrawables().add(drawable);
                        }
                    }
                }
            } //FreeType fonts
            else if (child.name().equals(FreeTypeFontGenerator.class.getName())) {
                for (JsonValue font : child.iterator()) {
                    if (font.get("font") != null) {
                        FreeTypeFontData data = new FreeTypeFontData();
                        data.name = font.name;
                        data.previewTTF = font.getString("previewTTF", null);
                        data.useCustomSerializer= font.getBoolean("useCustomSerializer", true);
                        data.size = font.getInt("size", 16);
                        data.mono = font.getBoolean("mono", false);
                        data.hinting = font.getString("hinting", "AutoMedium");
                        data.color = font.getString("color", null);
                        data.gamma = font.getFloat("gamma", 1.8f);
                        data.renderCount = font.getInt("renderCount", 2);
                        data.borderWidth = font.getFloat("borderWidth", 0);
                        data.borderColor = font.getString("borderColor", null);
                        data.borderStraight = font.getBoolean("borderStraight", false);
                        data.borderGamma = font.getFloat("borderGamma", 1.8f);
                        data.shadowOffsetX = font.getInt("shadowOffsetX", 0);
                        data.shadowOffsetY = font.getInt("shadowOffsetY", 0);
                        data.shadowColor = font.getString("shadowColor", null);
                        data.spaceX = font.getInt("spaceX", 0);
                        data.spaceY = font.getInt("spaceY", 0);
                        data.characters = font.getString("characters", "");
                        data.kerning = font.getBoolean("kerning", true);
                        data.flip = font.getBoolean("flip", false);
                        data.genMipMaps = font.getBoolean("genMipMaps", false);
                        data.minFilter = font.getString("minFilter", "Nearest");
                        data.magFilter = font.getString("magFilter", "Nearest");
                        data.incremental = font.getBoolean("bitmapFont", false);

                        FileHandle fontFile = fileHandle.sibling(font.getString("font"));
                        if (!fontFile.exists()) {
                            warnings.add("[RED]ERROR:[] Font file [BLACK]" + fontFile.name() + "[] does not exist.");
                            return warnings;
                        }
                        FileHandle fontCopy = targetDirectory.child(font.getString("font"));
                        if (!fontCopy.parent().equals(fontFile.parent()) && !fontCopy.exists()) {
                            fontFile.copyTo(fontCopy);
                        }
                        data.file = fontCopy;
                        data.createBitmapFont();

                        if (data.bitmapFont != null) {
                            //delete fonts with the same name
                            for (FontData duplicate : new Array<>(fonts)) {
                                if (duplicate.getName().equals(data.name)) {
                                    fonts.removeValue(duplicate, false);
                                }
                            }

                            for (FreeTypeFontData duplicate : new Array<>(freeTypeFonts)) {
                                if (duplicate.name.equals(data.name)) {
                                    freeTypeFonts.removeValue(duplicate, false);
                                }
                            }

                            freeTypeFonts.add(data);
                        }
                    }
                }
            } //colors
            else if (child.name().equals(Color.class.getName()) || child.name().equals(Color.class.getSimpleName())) {
                for (JsonValue color : child.iterator()) {
                    var colorValue = new Color();
                    if (color.has("hex")) {
                        colorValue.set(Color.valueOf(color.getString("hex")));
                    } else {
                        colorValue.set(color.getFloat("r", 1.0f), color.getFloat("g", 1.0f), color.getFloat("b", 1.0f), color.getFloat("a", 1.0f));
                    }
                    ColorData colorData = new ColorData(color.name, colorValue);
                    
                    //delete colors with the same name
                    for (ColorData originalData : new Array<>(colors)) {
                        if (originalData.getName().equals(colorData.getName())) {
                            colors.removeValue(originalData, true);
                        }
                    }
                    
                    colors.add(colorData);
                }
            }
            //drawables
            else if (child.name().equals(TextureRegionDrawable.class.getName()) || child.name().equals(TextureRegionDrawable.class.getSimpleName()) || child.name().equals(NinePatchDrawable.class.getName()) || child.name().equals(NinePatchDrawable.class.getSimpleName())) {
                for (JsonValue jsonValue : child.iterator()) {
                    DrawableData drawableData = projectData.getAtlasData().getDrawable(jsonValue.name);
                    if (jsonValue.has("minWidth")) drawableData.minWidth = jsonValue.getFloat("minWidth");
                    if (jsonValue.has("minHeight")) drawableData.minHeight = jsonValue.getFloat("minHeight");
                }
            } //tiled drawables
            else if (child.name().equals(TiledDrawable.class.getName()) || child.name().equals(TiledDrawable.class.getSimpleName())) {
                for (JsonValue tiledDrawable : child.iterator()) {
                    DrawableData drawableData = new DrawableData(projectData.getAtlasData().getDrawable(tiledDrawable.getString("region")).file);
                    drawableData.type = DrawableType.TILED;
                    drawableData.name = tiledDrawable.name;
                    
                    drawableData.tiled = true;
                    drawableData.tintName = tiledDrawable.getString("color");
                    drawableData.minWidth = tiledDrawable.getFloat("minWidth", 0.0f);
                    drawableData.minHeight = tiledDrawable.getFloat("minHeight", 0.0f);
   
                    //delete drawables with the same name
                    for (DrawableData originalData : new Array<>(projectData.getAtlasData().getDrawables())) {
                        if (originalData.name.equals(drawableData.name)) {
                            projectData.getAtlasData().getDrawables().removeValue(originalData, true);
                        }
                    }
                    
                    projectData.getAtlasData().getDrawables().add(drawableData);
                }
            } //tinted drawables
            else if (child.name().equals(TintedDrawable.class.getName()) || child.name().equals(TintedDrawable.class.getSimpleName())) {
                for (JsonValue tintedDrawable : child.iterator()) {
                    DrawableData drawableData = new DrawableData(projectData.getAtlasData().getDrawable(tintedDrawable.getString("name")).file);
                    drawableData.name = tintedDrawable.name;
                    
                    if (!tintedDrawable.get("color").isString()) {
                        drawableData.type = TINTED;
                        drawableData.tint = new Color();
                        if (tintedDrawable.get("color").has("hex")) {
                            drawableData.tint.set(Color.valueOf(tintedDrawable.get("color").getString("hex")));
                        } else {
                            drawableData.tint.set(tintedDrawable.get("color").getFloat("r", 1.0f), tintedDrawable.get("color").getFloat("g", 1.0f), tintedDrawable.get("color").getFloat("b", 1.0f), tintedDrawable.get("color").getFloat("a", 1.0f));
                        }
                    } else {
                        drawableData.type = TINTED_FROM_COLOR_DATA;
                        drawableData.tintName = tintedDrawable.getString("color");
                    }
    
                    if (tintedDrawable.has("minWidth")) drawableData.minWidth = tintedDrawable.getFloat("minWidth");
                    if (tintedDrawable.has("minHeight")) drawableData.minHeight = tintedDrawable.getFloat("minHeight");
                    
                    //delete drawables with the same name
                    for (DrawableData originalData : new Array<>(projectData.getAtlasData().getDrawables())) {
                        if (originalData.name.equals(drawableData.name)) {
                            projectData.getAtlasData().getDrawables().removeValue(originalData, true);
                        }
                    }
                    
                    projectData.getAtlasData().getDrawables().add(drawableData);
                }
            } //ten patch drawables
            else if (child.name().equals(TenPatchDrawable.class.getName()) || child.name().equals(TenPatchDrawable.class.getSimpleName())) {
                for (JsonValue value : child.iterator()) {
                    DrawableData drawableData = new DrawableData(projectData.getAtlasData().getDrawable(value.getString("region")).file);
                    drawableData.type = DrawableType.TENPATCH;
                    drawableData.name = value.name();
                    drawableData.tenPatchData = new DialogTenPatch.TenPatchData();
                    drawableData.tenPatchData.colorName = value.getString("color", null);
                    drawableData.tenPatchData.horizontalStretchAreas = new IntArray(value.get("horizontalStretchAreas").asIntArray());
                    drawableData.tenPatchData.verticalStretchAreas = new IntArray(value.get("verticalStretchAreas").asIntArray());
                    drawableData.tenPatchData.tile = value.getBoolean("tiling");
                    drawableData.minWidth = value.getFloat("minWidth", 0f);
                    drawableData.minHeight = value.getFloat("minHeight", 0f);
                    drawableData.tenPatchData.contentRight = value.getInt("rightWidth", 0);
                    drawableData.tenPatchData.contentLeft = value.getInt("leftWidth", 0);
                    drawableData.tenPatchData.contentBottom = value.getInt("bottomHeight", 0);
                    drawableData.tenPatchData.contentTop = value.getInt("topHeight", 0);
                    drawableData.tenPatchData.color1Name = value.getString("color1", null);
                    drawableData.tenPatchData.color2Name = value.getString("color2", null);
                    drawableData.tenPatchData.color3Name = value.getString("color3", null);
                    drawableData.tenPatchData.color4Name = value.getString("color4", null);
                    drawableData.tenPatchData.offsetX = value.getInt("offsetX", 0);
                    drawableData.tenPatchData.offsetY = value.getInt("offsetY", 0);
                    drawableData.tenPatchData.offsetXspeed = value.getInt("offsetXspeed", 0);
                    drawableData.tenPatchData.offsetYspeed = value.getInt("offsetYspeed", 0);
                    drawableData.tenPatchData.frameDuration = value.getFloat("frameDuration", 0);
                    drawableData.tenPatchData.regionNames = new Array<>(value.get("regions").asStringArray());
                    drawableData.tenPatchData.playMode = value.getInt("playMode", TenPatchDrawable.PlayMode.LOOP);
                    drawableData.tenPatchData.crushMode = value.getInt("crushMode", CrushMode.SHRINK);
    
                    //delete drawables with the same name
                    for (DrawableData originalData : new Array<>(projectData.getAtlasData().getDrawables())) {
                        if (originalData.name.equals(drawableData.name)) {
                            projectData.getAtlasData().getDrawables().removeValue(originalData, true);
                        }
                    }
    
                    projectData.getAtlasData().getDrawables().add(drawableData);
                }
            } //TVG drawables
            else if (child.name().equals(TinyVGDrawable.class.getName()) || child.name().equals(TinyVGDrawable.class.getSimpleName())) {
                for (JsonValue value : child.iterator()) {
                    DrawableData drawableData = new DrawableData();
                    
                    FileHandle tvgFile = fileHandle.sibling(value.getString("file"));
                    System.out.println("tvgFile.path() = " + tvgFile.path());
                    if (!tvgFile.exists()) {
                        warnings.add("[RED]ERROR:[] TVG file [BLACK]" + tvgFile.name() + "[] does not exist.");
                        return warnings;
                    }
                    FileHandle tvgCopy = targetDirectory.child(value.getString("file"));
                    if (!tvgCopy.parent().equals(tvgFile.parent()) && !tvgCopy.exists()) {
                        tvgFile.copyTo(tvgCopy);
                    }
                    drawableData.file = tvgCopy;
                    
                    drawableData.type = TVG;
                    drawableData.name = value.name();
                    drawableData.tvgData = new TvgData();
                    drawableData.tvgData.clipBasedOnTVGsize = value.getBoolean("clipBasedOnTVGsize");
        
                    //delete drawables with the same name
                    for (DrawableData originalData : new Array<>(projectData.getAtlasData().getDrawables())) {
                        if (originalData.name.equals(drawableData.name)) {
                            projectData.getAtlasData().getDrawables().removeValue(originalData, true);
                        }
                    }
        
                    projectData.getAtlasData().getDrawables().add(drawableData);
                }
            } //styles
            else {
                int classIndex = 0;
                
                Class matchClass = findStyleClassByName(child.name);
                
                if (matchClass != null) {
                    for (Class clazz : Main.STYLE_CLASSES) {
                        if (clazz.equals(matchClass)) {
                            break;
                        } else {
                            classIndex++;
                        }
                    }

                    Class clazz = Main.BASIC_CLASSES[classIndex];
                    for (JsonValue style : child.iterator()) {
                        StyleData data = newStyle(clazz, style.name);
                        for (JsonValue property : style.iterator()) {
                            if (property.name.equals("parent")) {
                                data.parent = property.asString();
                            } else {
                                StyleProperty styleProperty = data.properties.get(property.name);
                                if (styleProperty.type.equals(Float.TYPE)) {
                                    styleProperty.value = (double) property.asFloat();
                                } else if (styleProperty.type.equals(Color.class)) {
                                    if (property.isString()) {
                                        styleProperty.value = property.asString();
                                    } else {
                                        Gdx.app.error(getClass().getName(), "Can't import JSON files that do not use predefined colors.");
                                        warnings.add("Property [BLACK]" + styleProperty.name + "[] value cleared for [BLACK]" + clazz.getSimpleName() + ": " + data.name + "[] (Unsupported color definition)");
                                    }
                                } else {
                                    if (property.isString()) {
                                        styleProperty.value = property.asString();
                                    } else {
                                        Gdx.app.error(getClass().getName(), "Can't import JSON files that do not use String names for field values.");
                                        warnings.add("Property [BLACK]" + styleProperty.name + "[] value cleared for [BLACK]" + clazz.getSimpleName() + ": " + data.name + "[] (Unsupported property value)");
                                    }
                                }
                            }
                        }
                    }
                } else { //custom classes
                    CustomClass customClass = new CustomClass(child.name, child.name.replaceFirst(".*(\\.|\\$)", ""));
                    
                    CustomClass existingClass = getCustomClass(customClass.getDisplayName());
                    if (existingClass != null) {
                        customClasses.removeValue(existingClass, true);
                    }
                    
                    customClasses.add(customClass);
                    for (JsonValue style : child.iterator()) {
                        CustomStyle customStyle = new CustomStyle(style.name);
                        customStyle.setParentClass(customClass);
                        
                        CustomStyle existingStyle = customClass.getStyle(style.name);
                        if (existingStyle != null) {
                            customClass.getStyles().removeValue(existingStyle, true);
                        }
                        
                        if (customStyle.getName().equals("default")) {
                            customStyle.setDeletable(false);
                        }
                        
                        customClass.getStyles().add(customStyle);
                        
                        for (JsonValue property : style.iterator()) {
                            CustomProperty customProperty = new CustomProperty();
                            customProperty.setName(property.name);
                            customProperty.setParentStyle(customStyle);
                            
                            CustomProperty existingProperty = customStyle.getProperty(property.name);
                            if (existingProperty != null) {
                                customStyle.getProperties().removeValue(existingProperty, true);
                            }
                            
                            
                            if (property.isNumber()) {
                                customProperty.setType(PropertyType.NUMBER);
                                customProperty.setValue(property.asDouble());
                            } else if (property.isString()) {
                                customProperty.setType(PropertyType.TEXT);
                                customProperty.setValue(property.asString());
                            } else if (property.isBoolean()) {
                                customProperty.setType(PropertyType.BOOL);
                                customProperty.setValue(property.asBoolean());
                            } else if (property.isObject()) {
                                customProperty.setType(PropertyType.RAW_TEXT);
                                customProperty.setValue(property.toJson(OutputType.minimal));
                            } else if (property.isArray()) {
                                customProperty.setType(PropertyType.RAW_TEXT);
                                customProperty.setValue(property.toJson(OutputType.minimal));
                            } else {
                                customProperty = null;
                            }
                            
                            if (customProperty != null) {
                                customStyle.getProperties().add(customProperty);

                                //add to template style as necessary
                                if (customClass.getTemplateStyle().getProperty(customProperty.getName()) == null) {
                                    CustomProperty dupeProperty = customProperty.copy();
                                    dupeProperty.setValue(null);
                                    customClass.getTemplateStyle().getProperties().add(dupeProperty);
                                }
                            }
                        }
                    }
                    
                    //ensure default style has all the template styles.
                    for (CustomStyle style : customClass.getStyles()) {
                        if (style.getName().equals("default")) {
                            for (CustomProperty templateProperty : customClass.getTemplateStyle().getProperties()) {
                                boolean hasProperty = false;
                                for (CustomProperty customProperty : style.getProperties()) {
                                    if (customProperty.getName().equals(templateProperty.getName())) {
                                        hasProperty = true;
                                        break;
                                    }
                                }
                                
                                if (!hasProperty) {
                                    style.getProperties().add(templateProperty.copy());
                                }
                            }
                            
                            break;
                        }
                    }
                }
            }
        }
        
        return warnings;
    }
Imports skin data from a JSON file. Supports skins from libGDX 1.9.10
@param fileHandle
@return
@throws Exception
core/src/com/ray3k/skincomposer/data/JsonData.java