/*
* 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.importers;
import com.jpexs.decompiler.flash.SWF;
import com.jpexs.decompiler.flash.abc.ABC;
import com.jpexs.decompiler.flash.abc.ABCVersion;
import com.jpexs.decompiler.flash.abc.avm2.AVM2ConstantPool;
import com.jpexs.decompiler.flash.abc.types.ABCException;
import com.jpexs.decompiler.flash.abc.types.ClassInfo;
import com.jpexs.decompiler.flash.abc.types.Decimal;
import com.jpexs.decompiler.flash.abc.types.InstanceInfo;
import com.jpexs.decompiler.flash.abc.types.MetadataInfo;
import com.jpexs.decompiler.flash.abc.types.MethodBody;
import com.jpexs.decompiler.flash.abc.types.MethodInfo;
import com.jpexs.decompiler.flash.abc.types.Multiname;
import com.jpexs.decompiler.flash.abc.types.Namespace;
import com.jpexs.decompiler.flash.abc.types.NamespaceSet;
import com.jpexs.decompiler.flash.abc.types.ScriptInfo;
import com.jpexs.decompiler.flash.abc.types.ValueKind;
import com.jpexs.decompiler.flash.abc.types.traits.TraitClass;
import com.jpexs.decompiler.flash.abc.types.traits.TraitFunction;
import com.jpexs.decompiler.flash.abc.types.traits.TraitMethodGetterSetter;
import com.jpexs.decompiler.flash.abc.types.traits.TraitSlotConst;
import com.jpexs.decompiler.flash.abc.types.traits.Traits;
import com.jpexs.decompiler.flash.amf.amf3.Amf3Value;
import com.jpexs.decompiler.flash.tags.Tag;
import com.jpexs.decompiler.flash.tags.TagTypeInfo;
import com.jpexs.decompiler.flash.types.ALPHABITMAPDATA;
import com.jpexs.decompiler.flash.types.ALPHACOLORMAPDATA;
import com.jpexs.decompiler.flash.types.ARGB;
import com.jpexs.decompiler.flash.types.BITMAPDATA;
import com.jpexs.decompiler.flash.types.BUTTONCONDACTION;
import com.jpexs.decompiler.flash.types.BUTTONRECORD;
import com.jpexs.decompiler.flash.types.CLIPACTIONRECORD;
import com.jpexs.decompiler.flash.types.CLIPACTIONS;
import com.jpexs.decompiler.flash.types.CLIPEVENTFLAGS;
import com.jpexs.decompiler.flash.types.COLORMAPDATA;
import com.jpexs.decompiler.flash.types.CXFORM;
import com.jpexs.decompiler.flash.types.CXFORMWITHALPHA;
import com.jpexs.decompiler.flash.types.ColorTransform;
import com.jpexs.decompiler.flash.types.FILLSTYLE;
import com.jpexs.decompiler.flash.types.FILLSTYLEARRAY;
import com.jpexs.decompiler.flash.types.FOCALGRADIENT;
import com.jpexs.decompiler.flash.types.GLYPHENTRY;
import com.jpexs.decompiler.flash.types.GRADIENT;
import com.jpexs.decompiler.flash.types.GRADRECORD;
import com.jpexs.decompiler.flash.types.KERNINGRECORD;
import com.jpexs.decompiler.flash.types.LANGCODE;
import com.jpexs.decompiler.flash.types.LINESTYLE;
import com.jpexs.decompiler.flash.types.LINESTYLE2;
import com.jpexs.decompiler.flash.types.LINESTYLEARRAY;
import com.jpexs.decompiler.flash.types.MATRIX;
import com.jpexs.decompiler.flash.types.MORPHFILLSTYLE;
import com.jpexs.decompiler.flash.types.MORPHFILLSTYLEARRAY;
import com.jpexs.decompiler.flash.types.MORPHFOCALGRADIENT;
import com.jpexs.decompiler.flash.types.MORPHGRADIENT;
import com.jpexs.decompiler.flash.types.MORPHGRADRECORD;
import com.jpexs.decompiler.flash.types.MORPHLINESTYLE;
import com.jpexs.decompiler.flash.types.MORPHLINESTYLE2;
import com.jpexs.decompiler.flash.types.MORPHLINESTYLEARRAY;
import com.jpexs.decompiler.flash.types.PIX15;
import com.jpexs.decompiler.flash.types.PIX24;
import com.jpexs.decompiler.flash.types.RECT;
import com.jpexs.decompiler.flash.types.RGB;
import com.jpexs.decompiler.flash.types.RGBA;
import com.jpexs.decompiler.flash.types.SHAPE;
import com.jpexs.decompiler.flash.types.SHAPEWITHSTYLE;
import com.jpexs.decompiler.flash.types.SOUNDENVELOPE;
import com.jpexs.decompiler.flash.types.SOUNDINFO;
import com.jpexs.decompiler.flash.types.TEXTRECORD;
import com.jpexs.decompiler.flash.types.ZONEDATA;
import com.jpexs.decompiler.flash.types.ZONERECORD;
import com.jpexs.decompiler.flash.types.filters.BEVELFILTER;
import com.jpexs.decompiler.flash.types.filters.BLURFILTER;
import com.jpexs.decompiler.flash.types.filters.COLORMATRIXFILTER;
import com.jpexs.decompiler.flash.types.filters.CONVOLUTIONFILTER;
import com.jpexs.decompiler.flash.types.filters.DROPSHADOWFILTER;
import com.jpexs.decompiler.flash.types.filters.GLOWFILTER;
import com.jpexs.decompiler.flash.types.filters.GRADIENTBEVELFILTER;
import com.jpexs.decompiler.flash.types.filters.GRADIENTGLOWFILTER;
import com.jpexs.decompiler.flash.types.shaperecords.CurvedEdgeRecord;
import com.jpexs.decompiler.flash.types.shaperecords.EndShapeRecord;
import com.jpexs.decompiler.flash.types.shaperecords.StraightEdgeRecord;
import com.jpexs.decompiler.flash.types.shaperecords.StyleChangeRecord;
import com.jpexs.helpers.ByteArrayRange;
import com.jpexs.helpers.HashArrayList;
import com.jpexs.helpers.ReflectionTools;
import java.io.IOException;
import java.io.StringReader;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
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;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.w3c.dom.Attr;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
/**
*
* @author JPEXS
*/
@SuppressWarnings("unchecked")
public class SwfXmlImporter {
private static final Logger logger = Logger.getLogger(SwfXmlImporter.class.getName());
private Map<String, Class> swfTags;
private Map<String, Class> swfObjects;
private Map<String, Class> swfObjectsParam;
public void importSwf(SWF swf, String xml) throws IOException {
DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance();
try {
DocumentBuilder docBuilder = docFactory.newDocumentBuilder();
Document doc = docBuilder.parse(new InputSource(new StringReader(xml)));
processElement(doc.getDocumentElement(), swf, swf, null);
swf.clearAllCache();
} catch (ParserConfigurationException | SAXException ex) {
logger.log(Level.SEVERE, null, ex);
}
}
public Object importObject(String xml, Class requiredType, SWF swf) throws IOException {
DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance();
try {
DocumentBuilder docBuilder = docFactory.newDocumentBuilder();
Document doc = docBuilder.parse(new InputSource(new StringReader(xml)));
return processObject(doc.getDocumentElement(), requiredType, swf, null);
} catch (ParserConfigurationException | SAXException | IllegalArgumentException | IllegalAccessException | NoSuchMethodException | InstantiationException | InvocationTargetException ex) {
Logger.getLogger(SwfXmlImporter.class.getName()).log(Level.SEVERE, null, ex);
}
return null;
}
private Field getField(Class cls, String name) throws NoSuchFieldException {
Field field;
try {
field = cls.getField(name);
} catch (NoSuchFieldException ex) {
field = cls.getDeclaredField(name);
field.setAccessible(true);
}
return field;
}
private static void setFieldValue(Field field, Object obj, Object value) throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException {
Field modifiersField = Field.class.getDeclaredField("modifiers");
modifiersField.setAccessible(true);
//Remove final attribute temporary (For example Multiname.namespace_set_index
int originalModifiers = field.getModifiers();
if ((originalModifiers & Modifier.FINAL) > 0) {
modifiersField.setInt(field, originalModifiers & ~Modifier.FINAL);
}
field.setAccessible(true);
int newModifiers = field.getModifiers();
field.set(obj, value);
//Put final back in
if (originalModifiers != newModifiers) {
modifiersField.setInt(field, originalModifiers);
}
}
private void processElement(Element element, Object obj, SWF swf, Tag tag) {
Class cls = obj.getClass();
for (int i = 0; i < element.getAttributes().getLength(); i++) {
Attr attr = (Attr) element.getAttributes().item(i);
String name = attr.getName();
if (!name.equals("type")) {
try {
Field field = getField(cls, name);
String attrValue = attr.getValue();
setFieldValue(field, obj, getAs(field.getType(), attrValue));
} catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException ex) {
logger.log(Level.SEVERE, null, ex);
}
}
}
for (int i = 0; i < element.getChildNodes().getLength(); i++) {
Node childNode = element.getChildNodes().item(i);
if (childNode instanceof Element) {
Element child = (Element) childNode;
String name = child.getTagName();
try {
Field field = getField(cls, name);
Class childCls = field.getType();
if (List.class.isAssignableFrom(childCls)) {
List list = HashArrayList.class.isAssignableFrom(childCls) ? new HashArrayList() : new ArrayList();
for (int j = 0; j < child.getChildNodes().getLength(); j++) {
Node childChildNode = child.getChildNodes().item(j);
if (childChildNode instanceof Element) {
Element childChild = (Element) child.getChildNodes().item(j);
Object childObj = processObject(childChild, ReflectionTools.getFieldSubType(obj, field), swf, tag);
list.add(childObj);
}
}
setFieldValue(field, obj, list);
} else if (childCls.isArray()) {
List list = new ArrayList();
for (int j = 0; j < child.getChildNodes().getLength(); j++) {
Node childChildNode = child.getChildNodes().item(j);
if (childChildNode instanceof Element) {
Element childChild = (Element) child.getChildNodes().item(j);
Object childObj = processObject(childChild, childCls.getComponentType(), swf, tag);
list.add(childObj);
}
}
Object array = Array.newInstance(childCls.getComponentType(), list.size());
for (int j = 0; j < list.size(); j++) {
Array.set(array, j, list.get(j));
}
setFieldValue(field, obj, array);
} else {
Object childObj = processObject(child, null, swf, tag);
setFieldValue(field, obj, childObj);
}
} catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException | NoSuchMethodException | InstantiationException | InvocationTargetException ex) {
logger.log(Level.SEVERE, "Error while getting val from class " + cls + " field: " + name, ex);
}
}
}
}
private Object processObject(Element element, Class requiredType, SWF swf, Tag tag) throws IllegalArgumentException, IllegalAccessException, NoSuchMethodException, InstantiationException, InvocationTargetException {
String type = element.getAttribute("type");
if ("String".equals(type)) {
return element.getTextContent();
} else if (type != null && !type.isEmpty()) {
Object childObj = createObject(type, swf, tag);
if (childObj instanceof Tag) {
tag = (Tag) childObj;
}
processElement(element, childObj, swf, tag);
return childObj;
} else {
String isNullAttr = element.getAttribute("isNull");
if (Boolean.parseBoolean(isNullAttr)) {
return null;
}
return getAs(requiredType, element.getTextContent());
}
}
private Object createObject(String type, SWF swf, Tag tag) throws NoSuchMethodException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
if (swfTags == null) {
Map<String, Class> tags = new HashMap<>();
Map<Integer, TagTypeInfo> knownTags = Tag.getKnownClasses();
for (Integer key : knownTags.keySet()) {
Class cls = knownTags.get(key).getCls();
if (!ReflectionTools.canInstantiate(cls)) {
System.err.println("Can't instantiate: " + cls.getName());
}
tags.put(cls.getSimpleName(), cls);
}
swfTags = tags;
}
Class cls = swfTags.get(type);
if (cls != null) {
return cls.getConstructor(SWF.class).newInstance(swf);
}
if (swfObjects == null) {
Map<String, Class> objects = new HashMap<>();
Class[] knownObjects = new Class[]{ALPHABITMAPDATA.class, ALPHACOLORMAPDATA.class, ARGB.class, BITMAPDATA.class,
BUTTONCONDACTION.class, BUTTONRECORD.class, CLIPACTIONRECORD.class, CLIPACTIONS.class, CLIPEVENTFLAGS.class,
COLORMAPDATA.class, ColorTransform.class, CXFORM.class, CXFORMWITHALPHA.class,
FILLSTYLE.class, FILLSTYLEARRAY.class, FOCALGRADIENT.class, GLYPHENTRY.class, GRADIENT.class, GRADRECORD.class,
KERNINGRECORD.class, LANGCODE.class, LINESTYLE.class, LINESTYLE2.class, LINESTYLEARRAY.class, MATRIX.class,
MORPHFILLSTYLE.class, MORPHFILLSTYLEARRAY.class, MORPHFOCALGRADIENT.class, MORPHGRADIENT.class,
MORPHGRADRECORD.class, MORPHLINESTYLE.class, MORPHLINESTYLE2.class, MORPHLINESTYLEARRAY.class, PIX15.class,
PIX24.class, RECT.class, RGB.class, RGBA.class, SHAPE.class, SHAPEWITHSTYLE.class, SOUNDENVELOPE.class,
SOUNDINFO.class, TEXTRECORD.class, ZONEDATA.class, ZONERECORD.class,
CurvedEdgeRecord.class, EndShapeRecord.class, StraightEdgeRecord.class, StyleChangeRecord.class,
BEVELFILTER.class, BLURFILTER.class, COLORMATRIXFILTER.class, CONVOLUTIONFILTER.class,
DROPSHADOWFILTER.class, GLOWFILTER.class, GRADIENTBEVELFILTER.class, GRADIENTGLOWFILTER.class,
AVM2ConstantPool.class, Decimal.class, Namespace.class, NamespaceSet.class, Multiname.class, MethodInfo.class, MetadataInfo.class,
ValueKind.class, InstanceInfo.class, Traits.class, TraitClass.class, TraitFunction.class,
TraitMethodGetterSetter.class, TraitSlotConst.class, ClassInfo.class, ScriptInfo.class, MethodBody.class,
ABCException.class, ABCVersion.class, Amf3Value.class};
for (Class cls2 : knownObjects) {
if (!ReflectionTools.canInstantiateDefaultConstructor(cls2)) {
System.err.println("Can't instantiate: " + cls2.getName());
}
objects.put(cls2.getSimpleName(), cls2);
}
swfObjects = objects;
}
cls = swfObjects.get(type);
if (cls != null) {
return cls.getConstructor().newInstance();
}
if (swfObjectsParam == null) {
Map<String, Class> objects = new HashMap<>();
Class[] knownObjects = new Class[]{ABC.class};
for (Class cls2 : knownObjects) {
if (!ReflectionTools.canInstantiate(cls2)) {
System.err.println("Can't instantiate: " + cls2.getName());
}
objects.put(cls2.getSimpleName(), cls2);
}
swfObjectsParam = objects;
}
cls = swfObjectsParam.get(type);
if (cls != null) {
for (Constructor<?> constructor : cls.getConstructors()) {
if (constructor.getParameterCount() == 1) {
Class<?> parameterType = constructor.getParameterTypes()[0];
if (parameterType.isAssignableFrom(tag.getClass())) {
return constructor.newInstance(tag);
}
}
}
}
System.err.println("Type not found: " + type);
return null;
}
private Object getAs(Class cls, String stringValue) throws IllegalArgumentException, IllegalAccessException {
if (cls == Byte.class || cls == byte.class) {
return Byte.parseByte(stringValue);
} else if (cls == Short.class || cls == short.class) {
return Short.parseShort(stringValue);
} else if (cls == Integer.class || cls == int.class) {
return Integer.parseInt(stringValue);
} else if (cls == Long.class || cls == long.class) {
return Long.parseLong(stringValue);
} else if (cls == Float.class || cls == float.class) {
return Float.parseFloat(stringValue);
} else if (cls == Double.class || cls == double.class) {
return Double.parseDouble(stringValue);
} else if (cls == Boolean.class || cls == boolean.class) {
return Boolean.parseBoolean(stringValue);
} else if (cls == Character.class || cls == char.class) {
return stringValue.charAt(0);
} else if (cls == String.class) {
return stringValue;
} else if (cls == ByteArrayRange.class) {
ByteArrayRange range = new ByteArrayRange(stringValue);
return range;
} else if (cls == byte[].class) {
ByteArrayRange range = new ByteArrayRange(stringValue);
return range.getArray();
} else if (cls.isEnum()) {
return Enum.valueOf(cls, stringValue);
} else {
throw new RuntimeException("Unsupported object type.");
}
}
}