/*
* Copyright (C) 2010-2016 JPEXS, All rights reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3.0 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library.
*/
package com.jpexs.decompiler.flash.exporters.swf;
import com.jpexs.decompiler.flash.SWF;
import com.jpexs.decompiler.flash.configuration.Configuration;
import com.jpexs.decompiler.flash.helpers.CodeFormatting;
import com.jpexs.decompiler.flash.helpers.FileTextWriter;
import com.jpexs.decompiler.flash.helpers.GraphTextWriter;
import com.jpexs.decompiler.flash.helpers.LazyObject;
import com.jpexs.decompiler.flash.tags.Tag;
import com.jpexs.decompiler.flash.types.annotations.Internal;
import com.jpexs.helpers.ByteArrayRange;
import com.jpexs.helpers.Helper;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
*
* @author JPEXS
*/
public class SwfJavaExporter {
private static final String javaIndentString = " ";
private static final String[] allowedSubTypes = new String[]{"List", "String", "ByteArrayRange", "RECT", "MATRIX", "CXFORMWITHALPHA",
"CXFORM", "CLIPEVENTFLAGS", "CLIPACTIONRECORD", "CLIPACTIONS", "COLORMATRIXFILTER", "RGBA", "ARGB", "RGB",
"CONVOLUTIONFILTER", "BLURFILTER", "DROPSHADOWFILTER", "GLOWFILTER", "BEVELFILTER", "GRADIENTGLOWFILTER", "GRADIENTBEVELFILTER",
"FILTERLIST", "FILTER", "BUTTONRECORD", "BUTTONCONDACTION", "GRADRECORD", "GRADIENT", "FOCALGRADIENT", "FILLSTYLE",
"FILLSTYLEARRAY", "LINESTYLE", "LINESTYLE2", "LINESTYLEARRAY", "SHAPERECORD", "SHAPE", "SHAPEWITHSTYLE", "SHAPERECORDS",
"SOUNDINFO", "SOUNDENVELOPE", "GLYPHENTRY", "TEXTRECORD", "MORPHGRADRECORD", "MORPHGRADIENT", "MORPHFOCALGRADIENT",
"MORPHFILLSTYLE", "MORPHFILLSTYLEARRAY", "MORPHLINESTYLE", "MORPHLINESTYLE2", "MORPHLINESTYLEARRAY", "KERNINGRECORD",
"LANGCODE", "ZONERECORD", "ZONEDATA", "PIX15", "PIX24", "COLORMAPDATA", "BITMAPDATA", "ALPHABITMAPDATA", "ALPHACOLORMAPDATA"};
public List<File> exportJavaCode(SWF swf, String outdir) throws IOException {
final File file = new File(outdir + File.separator + Helper.makeFileName("SwfFile.java"));
CodeFormatting codeFormatting = Configuration.getCodeFormatting();
codeFormatting.indentString = javaIndentString;
try (FileTextWriter writer = new FileTextWriter(codeFormatting, new FileOutputStream(file))) {
exportJavaCode(swf, writer);
}
List<File> ret = new ArrayList<>();
ret.add(file);
return ret;
}
public void exportJavaCode(SWF swf, GraphTextWriter writer) throws IOException {
Map<String, Integer> objectNames = new HashMap<>();
writer.append("package com.jpexs.decompiler.flash.exporters.swf;").newLine();
writer.newLine();
writer.append("import com.jpexs.decompiler.flash.SWF;").newLine();
writer.append("import com.jpexs.decompiler.flash.SWFCompression;").newLine();
writer.append("import com.jpexs.decompiler.flash.tags.*;").newLine();
writer.append("import com.jpexs.decompiler.flash.types.*;").newLine();
writer.append("import com.jpexs.decompiler.flash.types.filters.*;").newLine();
writer.append("import com.jpexs.decompiler.flash.types.shaperecords.*;").newLine();
writer.append("import com.jpexs.helpers.ByteArrayRange;").newLine();
writer.append("import java.io.FileOutputStream;").newLine();
writer.append("import java.io.IOException;").newLine();
writer.append("import java.util.ArrayList;").newLine();
writer.append("import java.util.List;").newLine();
writer.newLine();
writer.append("@SuppressWarnings(\"unchecked\")").newLine();
writer.append("public class SwfFile {").newLine();
writer.newLine();
writer.indent();
IndentedStringBuilder sb = new IndentedStringBuilder(javaIndentString);
generateJavaCode(writer, sb, objectNames, swf, 0);
writer.unindent();
writer.append(" public SWF getSwf() {").newLine();
writer.append(" SWF swf = swf();").newLine();
writer.append(" swf.updateCharacters();").newLine();
writer.append(" return swf;").newLine();
writer.append(" }").newLine();
writer.newLine();
writer.append(" public void saveTo(String fileName) throws IOException {").newLine();
writer.append(" SWF swf = getSwf();").newLine();
writer.append(" swf.clearModified();").newLine();
writer.append(" try (FileOutputStream fos = new FileOutputStream(fileName)) {").newLine();
writer.append(" swf.saveTo(fos, SWFCompression.ZLIB);").newLine();
writer.append(" }").newLine();
writer.append(" }").newLine();
writer.append("}").newLine();
}
private static String getNextId(Map<String, Integer> objectNames, String type) {
Integer nextId = objectNames.get(type);
if (nextId == null) {
nextId = 0;
} else {
nextId++;
}
objectNames.put(type, nextId);
return type + nextId;
}
private static String getIndent(int indent, String indentStr) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < indent; i++) {
sb.append(indentStr);
}
return sb.toString();
}
private static Object generateJavaCode(GraphTextWriter writer, IndentedStringBuilder sb, Map<String, Integer> objectNames, Object obj, int level) {
if (obj == null) {
return null;
}
Class cls = obj.getClass();
Object value = null;
if (cls == Byte.class || cls == byte.class
|| cls == Short.class || cls == short.class
|| cls == Integer.class || cls == int.class
|| cls == Long.class || cls == long.class
|| cls == Float.class || cls == float.class
|| cls == Double.class || cls == double.class
|| cls == Boolean.class || cls == boolean.class
|| cls == Character.class || cls == char.class
|| cls == String.class) {
value = obj;
if (value instanceof Float) {
value = value + "f";
} else if (value instanceof String) {
value = "\"" + Helper.escapeJavaString((String) value) + "\"";
}
} else if (cls.isEnum()) {
value = cls.getSimpleName() + "." + obj;
} else if (obj instanceof ByteArrayRange) {
ByteArrayRange range = (ByteArrayRange) obj;
String className = "ByteArrayRange";
String tagObjName = getNextId(objectNames, "objByteArrayRange");
byte[] data = range.getRangeData();
StringBuilder sb2 = new StringBuilder();
final int maxBytePerString = 32767;
boolean isLong = data.length > maxBytePerString;
if (isLong) {
// string should be splitted to avoid "constant string too long" compile error
sb2.append("String.join(\"\", ");
}
int stringCount = (int) Math.ceil(data.length / (double) maxBytePerString);
for (int i = 0; i < stringCount; i++) {
if (i != 0) {
sb2.append(", ");
}
sb2.append("\"");
int from = i * maxBytePerString;
int to = Math.min(from + maxBytePerString, data.length);
for (int j = from; j < to; j++) {
sb2.append(Helper.byteToHex(data[j]));
}
sb2.append("\"");
}
if (isLong) {
sb2.append(")");
}
sb.appendLine(className + " " + tagObjName + " = new ByteArrayRange(" + sb2.toString() + ");");
value = tagObjName;
} else if (List.class.isAssignableFrom(cls)) {
List list = (List) obj;
String tagObjName = getNextId(objectNames, "objList");
sb.appendLine("List " + tagObjName + " = new ArrayList();");
for (int i = 0; i < list.size(); i++) {
Object val = generateJavaCode(writer, sb, objectNames, list.get(i), level + 1);
sb.appendLine(tagObjName + ".add(" + val + ");");
}
value = tagObjName;
} else if (cls.isArray()) {
String tagObjName = getNextId(objectNames, "objArray");
String arrayType = cls.getComponentType().getSimpleName();
int length = Array.getLength(obj);
sb.appendLine(arrayType + "[] " + tagObjName + " = new " + arrayType + "[" + length + "];");
for (int i = 0; i < length; i++) {
Object val = generateJavaCode(writer, sb, objectNames, Array.get(obj, i), level + 1);
sb.appendLine(tagObjName + "[" + i + "] = " + val + ";");
}
value = tagObjName;
} else {
if (obj instanceof LazyObject) {
((LazyObject) obj).load();
}
String className = obj.getClass().getSimpleName();
boolean isSwf = level == 0;
String resultName = isSwf ? "swf" : "result";
String tagObjName = isSwf ? "swf" : getNextId(objectNames, "obj" + className);
IndentedStringBuilder sb2 = new IndentedStringBuilder(javaIndentString);
sb2.indent();
sb2.indent();
String indent = getIndent(writer.getIndent() + 1, javaIndentString);
Field[] fields = obj.getClass().getFields();
for (Field f : fields) {
if (Modifier.isStatic(f.getModifiers())) {
continue;
}
Internal inter = f.getAnnotation(Internal.class);
if (inter != null) {
continue;
}
try {
f.setAccessible(true);
Object value2 = generateJavaCode(writer, sb2, objectNames, f.get(obj), level + 1);
if (value2 != null) {
sb2.appendLine(resultName + "." + f.getName() + " = " + value2 + ";");
}
} catch (IllegalArgumentException | IllegalAccessException ex) {
Logger.getLogger(SwfJavaExporter.class.getName()).log(Level.SEVERE, null, ex);
}
}
writer.append("private ").append(className).append(" ").append(tagObjName).append("(").append(isSwf ? "" : "SWF swf").append(") {").newLine();
writer.indent();
writer.append(className).append(" ").append(resultName).append(" = new ").append(className).append("(").append(obj instanceof Tag ? "swf" : "").append(");").newLine();
writer.append(sb2.toString());
writer.append(indent).append("return ").append(resultName).append(";").newLine();
writer.unindent();
writer.append("}").newLine().newLine();
value = tagObjName + "(swf)";
}
return value;
}
}