/* * Copyright (C) 2010-2016 JPEXS * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package com.jpexs.decompiler.flash.gui; import com.jpexs.decompiler.flash.SWF; import com.jpexs.decompiler.flash.amf.amf3.Amf3Value; import com.jpexs.decompiler.flash.gui.generictageditors.Amf3ValueEditor; import com.jpexs.decompiler.flash.gui.generictageditors.BinaryDataEditor; import com.jpexs.decompiler.flash.gui.generictageditors.BooleanEditor; import com.jpexs.decompiler.flash.gui.generictageditors.ColorEditor; import com.jpexs.decompiler.flash.gui.generictageditors.EnumEditor; import com.jpexs.decompiler.flash.gui.generictageditors.FullSized; import com.jpexs.decompiler.flash.gui.generictageditors.GenericTagEditor; import com.jpexs.decompiler.flash.gui.generictageditors.NumberEditor; import com.jpexs.decompiler.flash.gui.generictageditors.StringEditor; import com.jpexs.decompiler.flash.tags.Tag; import com.jpexs.decompiler.flash.tags.base.ASMSource; import com.jpexs.decompiler.flash.types.ARGB; import com.jpexs.decompiler.flash.types.BasicType; import com.jpexs.decompiler.flash.types.RGB; import com.jpexs.decompiler.flash.types.RGBA; import com.jpexs.decompiler.flash.types.annotations.Conditional; import com.jpexs.decompiler.flash.types.annotations.EnumValue; import com.jpexs.decompiler.flash.types.annotations.EnumValues; import com.jpexs.decompiler.flash.types.annotations.HideInRawEdit; import com.jpexs.decompiler.flash.types.annotations.Internal; import com.jpexs.decompiler.flash.types.annotations.Multiline; import com.jpexs.decompiler.flash.types.annotations.SWFArray; import com.jpexs.decompiler.flash.types.annotations.SWFType; import com.jpexs.decompiler.flash.types.annotations.Table; import com.jpexs.decompiler.flash.types.annotations.parser.AnnotationParseException; import com.jpexs.decompiler.flash.types.annotations.parser.ConditionEvaluator; import com.jpexs.helpers.ByteArrayRange; import com.jpexs.helpers.ConcreteClasses; import com.jpexs.helpers.ReflectionTools; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Component; import java.awt.Dimension; import java.awt.FlowLayout; import java.awt.Graphics; import java.awt.Rectangle; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; 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.EventObject; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.logging.Level; import java.util.logging.Logger; import javax.swing.AbstractCellEditor; import javax.swing.JComponent; import javax.swing.JLabel; import javax.swing.JMenu; import javax.swing.JMenuItem; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JPopupMenu; import javax.swing.JScrollPane; import javax.swing.JTree; import javax.swing.UIManager; import javax.swing.event.TreeModelListener; import javax.swing.plaf.basic.BasicLabelUI; import javax.swing.plaf.basic.BasicTreeUI; import javax.swing.tree.DefaultMutableTreeNode; import javax.swing.tree.DefaultTreeCellRenderer; import javax.swing.tree.DefaultTreeModel; import javax.swing.tree.TreeCellEditor; import javax.swing.tree.TreeModel; import javax.swing.tree.TreePath; /** * * @author JPEXS */ public class GenericTagTreePanel extends GenericTagPanel { private static final Logger logger = Logger.getLogger(GenericTagTreePanel.class.getName()); private JTree tree; private Tag editedTag; private static final Map<Class, List<Field>> fieldCache = new HashMap<>(); private static final int FIELD_INDEX = 0; private class MyTree extends JTree { public MyTree() { setBackground(Color.white); setUI(new BasicTreeUI() { @Override public void paint(Graphics g, JComponent c) { setHashColor(Color.gray); super.paint(g, c); } }); setCellRenderer(new MyTreeCellRenderer()); setCellEditor(new MyTreeCellEditor(this)); setInvokesStopCellEditing(true); } } private class MyTreeCellEditor extends AbstractCellEditor implements TreeCellEditor { private List<GenericTagEditor> editors = null; private final JTree tree; private FieldNode fnode; public MyTreeCellEditor(JTree tree) { this.tree = tree; } @Override public Component getTreeCellEditorComponent(JTree tree, Object value, boolean isSelected, boolean expanded, boolean leaf, int row) { Rectangle cellRect = tree.getPathBounds(tree.getPathForRow(row)); Rectangle treeVisibleRect = tree.getVisibleRect(); int scrollBarSize = ((Integer) UIManager.get("ScrollBar.width")).intValue(); Rectangle cellMaxVisibleRect = new Rectangle(cellRect.x, cellRect.y, treeVisibleRect.width - cellRect.x - tree.getInsets().left - tree.getInsets().right - scrollBarSize, cellRect.height); if (value instanceof FieldNode) { fnode = (FieldNode) value; JPanel panSum = new JPanel(new FlowLayout(FlowLayout.LEFT, 0, 0)); panSum.setOpaque(false); for (int i = 0; i < fnode.fieldSet.size(); i++) { Field field = fnode.fieldSet.get(i);//fnode.fieldSet.get(FIELD_INDEX); int index = fnode.index; Object obj = fnode.obj; Class<?> type; boolean isByteArray = field.getType().equals(byte[].class); try { type = isByteArray ? byte[].class : ReflectionTools.getValue(obj, field, index).getClass(); } catch (IllegalArgumentException | IllegalAccessException ex) { logger.log(Level.SEVERE, "Fixing characters order failed, recursion detected."); return null; } GenericTagEditor editor = null; SWFType swfType = field.getAnnotation(SWFType.class); Multiline multiline = field.getAnnotation(Multiline.class); EnumValues enumValues = field.getAnnotation(EnumValues.class); if (enumValues != null && (type.equals(int.class) || type.equals(Integer.class))) { Map<Integer, String> values = new HashMap<>(); for (EnumValue enumValue : enumValues.value()) { values.put(enumValue.value(), enumValue.text()); } editor = new EnumEditor(field.getName(), obj, field, index, type, swfType, values); } else if (type.equals(int.class) || type.equals(Integer.class) || type.equals(short.class) || type.equals(Short.class) || type.equals(long.class) || type.equals(Long.class) || type.equals(double.class) || type.equals(Double.class) || type.equals(float.class) || type.equals(Float.class)) { editor = new NumberEditor(field.getName(), obj, field, index, type, swfType); } else if (type.equals(boolean.class) || type.equals(Boolean.class)) { editor = new BooleanEditor(field.getName(), obj, field, index, type); } else if (type.equals(String.class)) { editor = new StringEditor(field.getName(), obj, field, index, type, multiline != null); } else if (type.equals(RGB.class) || type.equals(RGBA.class) || type.equals(ARGB.class)) { editor = new ColorEditor(field.getName(), obj, field, index, type); } else if (type.equals(byte[].class) || type.equals(ByteArrayRange.class)) { editor = new BinaryDataEditor(mainPanel, field.getName(), obj, field, index, type); } else if (type.equals(Amf3Value.class)) { editor = new Amf3ValueEditor(field.getName(), obj, field, index, type); } if (editor != null) { if (editors == null) { editors = new ArrayList<>(); } editors.add(editor); } JPanel pan = new JPanel(); FlowLayout fl = new FlowLayout(FlowLayout.LEFT, 0, 0); fl.setAlignOnBaseline(true); pan.setLayout(fl); JLabel nameLabel = new JLabel(fnode.getNameType(i) + " = ") { @Override public BaselineResizeBehavior getBaselineResizeBehavior() { return Component.BaselineResizeBehavior.CONSTANT_ASCENT; } @Override public int getBaseline(int width, int height) { return 0; } }; pan.setOpaque(false); nameLabel.setAlignmentY(TOP_ALIGNMENT); pan.add(nameLabel); JComponent editorComponent = (JComponent) editor; if (editorComponent != null) { nameLabel.setSize(nameLabel.getWidth(), editorComponent.getHeight()); editorComponent.setAlignmentY(TOP_ALIGNMENT); pan.add(editorComponent); if (editorComponent instanceof FullSized) { editorComponent.setPreferredSize(new Dimension(cellMaxVisibleRect.width - (int) nameLabel.getPreferredSize().getWidth() - 5, editorComponent.getPreferredSize().height)); } if (editorComponent instanceof GenericTagEditor) { ((GenericTagEditor) editorComponent).added(); } pan.setPreferredSize(new Dimension((int) nameLabel.getPreferredSize().getWidth() + 5 + (int) editorComponent.getPreferredSize().getWidth(), (int) editorComponent.getPreferredSize().getHeight())); } else { pan.setPreferredSize(new Dimension((int) nameLabel.getPreferredSize().getWidth(), (int) nameLabel.getPreferredSize().getHeight())); } panSum.add(pan); } panSum.setPreferredSize(new Dimension(cellMaxVisibleRect.width, panSum.getPreferredSize().height)); return panSum; } return null; } @Override public Object getCellEditorValue() { List<Object> ret = new ArrayList<>(); if (editors != null) { for (GenericTagEditor editor : editors) { ret.add(editor.getChangedValue()); } } return ret; } @Override public boolean isCellEditable(EventObject e) { if (!(e instanceof MouseEvent)) { return false; } MouseEvent me = (MouseEvent) e; TreePath path = tree.getPathForLocation(me.getX(), me.getY()); if (path == null) { return false; } Object obj = path.getLastPathComponent(); boolean ret = super.isCellEditable(e) && tree.getModel().isLeaf(obj); return ret; } @Override public boolean stopCellEditing() { if (editors != null) { for (GenericTagEditor editor : editors) { try { editor.validateValue(); editor.save(); } catch (IllegalArgumentException iex) { return false; } } } super.stopCellEditing(); editors = null; TreePath sp = tree.getSelectionPath(); if (sp != null) { ((MyTreeModel) tree.getModel()).vchanged(sp); } refreshTree(); return true; } } public GenericTagTreePanel(MainPanel mainPanel) { super(mainPanel); setLayout(new BorderLayout()); tree = new MyTree(); add(new JScrollPane(tree), BorderLayout.CENTER); tree.addMouseListener(new MouseAdapter() { @Override public void mousePressed(MouseEvent e) { if (!tree.isEditable()) { return; } int selRow = tree.getRowForLocation(e.getX(), e.getY()); TreePath selPath = tree.getPathForLocation(e.getX(), e.getY()); if (selRow != -1 && selPath != null) { if (e.getClickCount() == 1) { if (e.getButton() == MouseEvent.BUTTON3) { //right click Object selObject = selPath.getLastPathComponent(); if (selObject instanceof FieldNode) { final FieldNode fnode = (FieldNode) selObject; Field field = fnode.fieldSet.get(FIELD_INDEX); if (ReflectionTools.needsIndex(field)) { SWFArray swfArray = fnode.fieldSet.get(FIELD_INDEX).getAnnotation(SWFArray.class); String itemStr = ""; if (swfArray != null) { itemStr = swfArray.value(); } if (fnode.fieldSet.itemName != null && !fnode.fieldSet.itemName.isEmpty()) { itemStr = fnode.fieldSet.itemName; } if (itemStr.isEmpty()) { itemStr = AppStrings.translate("generictag.array.item"); } boolean canAdd = true; if (!ReflectionTools.canAddToField(fnode.obj, fnode.fieldSet.get(FIELD_INDEX))) { canAdd = false; } JPopupMenu p = new JPopupMenu(); JMenuItem mi; Class<?> subtype = ReflectionTools.getFieldSubType(fnode.obj, fnode.fieldSet.get(FIELD_INDEX)); if (!canAdd && subtype.isAnnotationPresent(ConcreteClasses.class)) { Class<?>[] availableClasses = subtype.getAnnotation(ConcreteClasses.class).value(); JMenu mBegin = new JMenu(AppStrings.translate("generictag.array.insertbeginning").replace("%item%", itemStr)); p.add(mBegin); JMenu mBefore = new JMenu(AppStrings.translate("generictag.array.insertbefore").replace("%item%", itemStr)); p.add(mBefore); mi = new JMenuItem(AppStrings.translate("generictag.array.remove").replace("%item%", itemStr)); mi.addActionListener((ActionEvent e1) -> { removeItem(fnode.obj, fnode.fieldSet.get(FIELD_INDEX), fnode.index); }); p.add(mi); JMenu mAfter = new JMenu(AppStrings.translate("generictag.array.insertafter").replace("%item%", itemStr)); p.add(mAfter); JMenu mEnd = new JMenu(AppStrings.translate("generictag.array.insertend").replace("%item%", itemStr)); p.add(mEnd); for (Class<?> c : availableClasses) { mi = new JMenuItem(c.getSimpleName()); mi.addActionListener((ActionEvent e1) -> { addItem(fnode.obj, fnode.fieldSet.get(FIELD_INDEX), 0, c); }); mBegin.add(mi); mi = new JMenuItem(c.getSimpleName()); mi.addActionListener((ActionEvent e1) -> { addItem(fnode.obj, fnode.fieldSet.get(FIELD_INDEX), fnode.index, c); }); mBefore.add(mi); mi = new JMenuItem(c.getSimpleName()); mi.addActionListener((ActionEvent e1) -> { addItem(fnode.obj, fnode.fieldSet.get(FIELD_INDEX), fnode.index + 1, c); }); mAfter.add(mi); mi = new JMenuItem(c.getSimpleName()); mi.addActionListener((ActionEvent e1) -> { addItem(fnode.obj, fnode.fieldSet.get(FIELD_INDEX), ReflectionTools.getFieldSubSize(fnode.obj, fnode.fieldSet.get(FIELD_INDEX)), c); }); mEnd.add(mi); } } else { mi = new JMenuItem(AppStrings.translate("generictag.array.insertbeginning").replace("%item%", itemStr)); mi.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { addItem(fnode.obj, fnode.fieldSet.get(FIELD_INDEX), 0, null); } }); if (!canAdd) { mi.setEnabled(false); } p.add(mi); if (fnode.index > -1) { mi = new JMenuItem(AppStrings.translate("generictag.array.insertbefore").replace("%item%", itemStr)); mi.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { addItem(fnode.obj, fnode.fieldSet.get(FIELD_INDEX), fnode.index, null); } }); if (!canAdd) { mi.setEnabled(false); } p.add(mi); mi = new JMenuItem(AppStrings.translate("generictag.array.remove").replace("%item%", itemStr)); mi.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { removeItem(fnode.obj, fnode.fieldSet.get(FIELD_INDEX), fnode.index); } }); p.add(mi); mi = new JMenuItem(AppStrings.translate("generictag.array.insertafter").replace("%item%", itemStr)); mi.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { addItem(fnode.obj, fnode.fieldSet.get(FIELD_INDEX), fnode.index + 1, null); } }); if (!canAdd) { mi.setEnabled(false); } p.add(mi); } mi = new JMenuItem(AppStrings.translate("generictag.array.insertend").replace("%item%", itemStr)); mi.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { addItem(fnode.obj, fnode.fieldSet.get(FIELD_INDEX), ReflectionTools.getFieldSubSize(fnode.obj, fnode.fieldSet.get(FIELD_INDEX)), null); } }); if (!canAdd) { mi.setEnabled(false); } p.add(mi); //} } p.show(tree, e.getX(), e.getY()); } } } //} else if (e.getClickCount() == 2) { // myDoubleClick(selRow, selPath); } } } }); } private Tag tag; public class MyTreeCellRenderer extends DefaultTreeCellRenderer { public MyTreeCellRenderer() { setUI(new BasicLabelUI()); setOpaque(false); setBackgroundNonSelectionColor(Color.white); } } @Override public void clear() { } private static final class TableFieldNodes extends DefaultMutableTreeNode { List<FieldNode> subnodes; public TableFieldNodes(List<FieldNode> subnodes) { this.subnodes = subnodes; } } private static final class FieldNode extends DefaultMutableTreeNode { private Object obj; private FieldSet fieldSet; private int index; public FieldNode(Object obj, FieldSet fieldSet, int index) { this.obj = obj; this.fieldSet = fieldSet; this.index = index; for (int i = 0; i < fieldSet.size(); i++) { if (getValue(i) == null) { try { if (List.class.isAssignableFrom(fieldSet.get(i).getType())) { ReflectionTools.setValue(obj, fieldSet.get(i), new ArrayList<>()); } else if (fieldSet.get(i).getType().isArray()) { ReflectionTools.setValue(obj, fieldSet.get(i), Array.newInstance(fieldSet.get(i).getType().getComponentType(), 0)); } } catch (IllegalArgumentException | IllegalAccessException ex) { logger.log(Level.SEVERE, null, ex); } } } } @Override public void setUserObject(Object userObject) { } /* */ @Override public String toString() { StringBuilder ret = new StringBuilder(); if (index > -1) { for (int i = 0; i < fieldSet.size(); i++) { if (i > 0) { ret.append(", "); } ret.append(toString(i)); } return ret.toString(); } if (fieldSet.size() == 1) { ret.append(toString(0)); } else { ret.append(fieldSet.name); SWFArray t = fieldSet.get(0).getAnnotation(SWFArray.class); if (t != null) { ret.append(" [").append(t.countField()).append("]"); } else { ret.append(" []"); } } ret.insert(0, "<html>").append("</html>"); return ret.toString(); } public String toString(int fieldIndex) { String valStr = ""; Field field = fieldSet.get(fieldIndex); if (ReflectionTools.needsIndex(field) && (index == -1)) { valStr += ""; } else if (hasEditor(obj, field, index)) { Object val = getValue(fieldIndex); Color color = null; String colorAdd = ""; if (val instanceof RGB) { //Note: Can be RGBA too color = ((RGB) val).toColor(); } if (val instanceof ARGB) { color = ((ARGB) val).toColor(); } if (color != null) { colorAdd = "<cite style=\"color:rgb(" + color.getRed() + "," + color.getGreen() + "," + color.getBlue() + ");\">\u25cf</cite> "; } EnumValues enumValues = field.getAnnotation(EnumValues.class); String enumAdd = ""; if (enumValues != null && val instanceof Integer) { Map<Integer, String> values = new HashMap<>(); for (EnumValue enumValue : enumValues.value()) { values.put(enumValue.value(), enumValue.text()); } enumAdd = " - " + values.get(val); } if (val instanceof byte[]) { valStr += " = " + ((byte[]) val).length + " byte"; } else if (val instanceof ByteArrayRange) { valStr += " = " + ((ByteArrayRange) val).getLength() + " byte"; } else { valStr += " = " + colorAdd + val.toString() + enumAdd; } } return getNameType(fieldIndex) + valStr; } public String getType(int fieldIndex) { SWFType swfType = fieldSet.get(fieldIndex).getAnnotation(SWFType.class); SWFArray swfArray = fieldSet.get(fieldIndex).getAnnotation(SWFArray.class); Class<?> declaredType = fieldSet.get(fieldIndex).getType(); boolean isArray = ReflectionTools.needsIndex(fieldSet.get(fieldIndex)) || swfArray != null; boolean isArrayParent = isArray && index == -1; Class<?> declaredSubType = isArray ? ReflectionTools.getFieldSubType(obj, fieldSet.get(fieldIndex)) : null; Class<?> type = declaredType; if (declaredSubType != null) { type = declaredSubType; } if (isArray && !isArrayParent) { //get real value object type try { Object val = ReflectionTools.getValue(obj, fieldSet.get(fieldIndex), index); if (val != null) { type = val.getClass(); } } catch (IllegalArgumentException | IllegalAccessException ex) { //ignore } } String typeStr = type.getSimpleName(); if (swfType != null && swfType.value() != BasicType.OTHER) { typeStr = "" + swfType.value(); if (swfType.count() > 0) { typeStr += "[" + swfType.count(); if (swfType.countAdd() > 0) { typeStr += " + " + swfType.countAdd(); } typeStr += "]"; } else if (!swfType.countField().isEmpty()) { typeStr += "[" + swfType.countField(); if (swfType.countAdd() > 0) { typeStr += " + " + swfType.countAdd(); } typeStr += "]"; } } String arrayBrackets = ""; if (isArrayParent) { if (swfArray != null) { if (swfArray.count() > 0) { arrayBrackets = "[" + swfArray.count() + "]"; } else if (!swfArray.countField().isEmpty()) { arrayBrackets = "[" + swfArray.countField() + "]"; } else { arrayBrackets = "[]"; } } else { arrayBrackets = "[]"; } } typeStr += arrayBrackets; return typeStr; } public String getNameType(int fieldIndex) { String typeStr = getType(fieldIndex); return getName(fieldIndex) + (typeStr != null ? " : " + typeStr : ""); } public String getName(int fieldIndex) { SWFArray swfArray = fieldSet.get(fieldIndex).getAnnotation(SWFArray.class); boolean isArray = ReflectionTools.needsIndex(fieldSet.get(fieldIndex)) || swfArray != null; boolean isArrayParent = isArray && index == -1; String name = ""; if (!isArray || isArrayParent) { name = fieldSet.get(fieldIndex).getName(); } else if (swfArray != null && !isArrayParent) { name = swfArray.value(); } if (!isArrayParent && isArray) { name += "[" + index + "]"; } return name; } public Object getValue(int fieldIndex) { try { if (ReflectionTools.needsIndex(fieldSet.get(fieldIndex)) && (index == -1)) { return ReflectionTools.getValue(obj, fieldSet.get(fieldIndex)); } Object val = ReflectionTools.getValue(obj, fieldSet.get(fieldIndex), index); if (val == null) { try { val = ReflectionTools.newInstanceOf(fieldSet.get(fieldIndex).getType()); ReflectionTools.setValue(obj, fieldSet.get(fieldIndex), index, val); } catch (InstantiationException | IllegalAccessException ex) { logger.log(Level.SEVERE, null, ex); return null; } } return val; } catch (IllegalArgumentException | IllegalAccessException ex) { return null; } } @Override public int hashCode() { int hash = 7; hash = 11 * hash + Objects.hashCode(this.obj); hash = 11 * hash + Objects.hashCode(this.fieldSet.get(FIELD_INDEX)); hash = 11 * hash + this.index; return hash; } @Override public boolean equals(Object obj) { if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } final FieldNode other = (FieldNode) obj; if (!Objects.equals(this.obj, other.obj)) { return false; } if (!Objects.equals(this.fieldSet.get(FIELD_INDEX), other.fieldSet.get(FIELD_INDEX))) { return false; } return this.index == other.index; } } private static class MyTreeModel extends DefaultTreeModel { private final Object mtroot; private final List<TreeModelListener> listeners = new ArrayList<>(); private final Map<String, Object> nodeCache = new HashMap<>(); // it is much faster to store the reverse mappings, too private final Map<Object, String> nodeCacheReverse = new HashMap<>(); private Object getNodeByPath(String path) { if (nodeCache.containsKey(path)) { return nodeCache.get(path); } return null; } public String getNodePathName(Object find) { if (nodeCacheReverse.containsKey(find)) { return nodeCacheReverse.get(find); } return null; } public List<FieldNode> getDependentFields(FieldNode fnode) { List<FieldNode> ret = new ArrayList<>(); getDependentFields(getNodePathName(fnode), mtroot.getClass().getSimpleName(), mtroot, ret); return ret; } public void getDependentFields(String dependence, String currentPath, Object node, List<FieldNode> ret) { if (node instanceof FieldNode) { FieldNode fnode = (FieldNode) node; Conditional cond = fnode.fieldSet.get(FIELD_INDEX).getAnnotation(Conditional.class); if (cond != null) { ConditionEvaluator ev = new ConditionEvaluator(cond); String parentPath = currentPath.indexOf('.') == -1 ? "" : currentPath.substring(0, currentPath.lastIndexOf('.')); try { for (String cname : ev.getFields()) { String fullParh = parentPath + "." + cname; if (fullParh.equals(dependence)) { ret.add(fnode); break; } } } catch (AnnotationParseException ex) { logger.log(Level.SEVERE, null, ex); } } } int count = getChildCount(node); for (int i = 0; i < count; i++) { FieldNode f = (FieldNode) getChild(node, i); getDependentFields(dependence, currentPath + "." + f.getName(FIELD_INDEX), f, ret); } } public MyTreeModel(Tag root) { super(new DefaultMutableTreeNode(root)); this.mtroot = root; buildCache(root, ""); } private void buildCache(Object obj, String parentPath) { if (!"".equals(parentPath)) { parentPath += "."; } if (obj instanceof FieldNode) { FieldNode fn = (FieldNode) obj; parentPath += fn.getName(FIELD_INDEX); } else { parentPath += obj.getClass().getSimpleName(); } nodeCache.put(parentPath, obj); nodeCacheReverse.put(obj, parentPath); int count = getChildCount(obj, false); for (int i = 0; i < count; i++) { buildCache(getChild(obj, i, false), parentPath); } } @Override public Object getRoot() { return mtroot; } private Object getChild(Object parent, int index, boolean limited) { if (parent == mtroot) { return new FieldNode(mtroot, filterFields(this, mtroot.getClass().getSimpleName(), mtroot.getClass(), limited).get(index), -1); } FieldNode fnode = (FieldNode) parent; Field field = fnode.fieldSet.get(FIELD_INDEX); if (ReflectionTools.needsIndex(field) && (fnode.index == -1)) { //Arrays ot Lists return new FieldNode(fnode.obj, fnode.fieldSet, index); } parent = fnode.getValue(FIELD_INDEX); return new FieldNode(parent, filterFields(this, getNodePathName(fnode), parent.getClass(), limited).get(index), -1); } @Override public Object getChild(Object parent, int index) { return getChild(parent, index, true); } @Override public int getChildCount(Object parent) { return getChildCount(parent, true); } private int getChildCount(Object parent, boolean limited) { if (parent == mtroot) { return filterFields(this, mtroot.getClass().getSimpleName(), mtroot.getClass(), limited).size(); } FieldNode fnode = (FieldNode) parent; if (isLeaf(fnode)) { return 0; } Field field = fnode.fieldSet.get(FIELD_INDEX); if (ReflectionTools.needsIndex(field) && (fnode.index == -1)) { //Arrays or Lists try { if (field.get(fnode.obj) == null) { // todo: instanciate the (Array)List or Array to allow adding items to it return 0; } } catch (IllegalArgumentException | IllegalAccessException ex) { return 0; } return ReflectionTools.getFieldSubSize(fnode.obj, field); } parent = fnode.getValue(FIELD_INDEX); return filterFields(this, getNodePathName(fnode), parent.getClass(), limited).size(); } @Override public boolean isLeaf(Object node) { if (node == mtroot) { return false; } FieldNode fnode = (FieldNode) node; Field field = fnode.fieldSet.get(FIELD_INDEX); boolean isByteArray = field.getType().equals(byte[].class); if (!isByteArray && ReflectionTools.needsIndex(field) && fnode.index == -1) { return false; } boolean r = hasEditor(fnode.obj, field, fnode.index); return r; } public void vchanged(TreePath path) { fireTreeNodesChanged(this, path.getPath(), null, null); } @Override public int getIndexOfChild(Object parent, Object child) { int cnt = getChildCount(parent); for (int i = 0; i < cnt; i++) { if (getChild(parent, i).equals(child)) { return i; } } return -1; } } private TreeModel getModel() { return new MyTreeModel(editedTag); } @Override public void setEditMode(boolean edit, Tag tag) { if (tag == null) { tag = this.tag; } this.tag = tag; try { editedTag = tag.cloneTag(); } catch (InterruptedException ex) { } catch (IOException ex) { logger.log(Level.SEVERE, null, ex); } if (!edit && tree.isEditing()) { tree.stopEditing(); } tree.setEditable(edit); refreshTree(); } @Override public boolean save() { if (tree.isEditing() && !tree.stopEditing()) { return false; } SWF swf = tag.getSwf(); assignTag(tag, editedTag); tag.setModified(true); tag.setSwf(swf); return true; } private void assignTag(Tag t, Tag assigned) { if (t.getClass() != assigned.getClass()) { return; } for (Field f : getAvailableFields(t.getClass())) { try { f.set(t, f.get(assigned)); } catch (IllegalArgumentException | IllegalAccessException ex) { logger.log(Level.SEVERE, null, ex); } } } @Override public Tag getTag() { return tag; } /*public static String swfTypeToString(Class<?> type, SWFType swfType, SWFArray swfArray, boolean arrayHeader) { S }*/ private static boolean hasEditor(Object obj, Field field, int index) { boolean isByteArray = field.getType().equals(byte[].class); if (!isByteArray && ReflectionTools.needsIndex(field) && index == -1) { return false; } Class<?> type; try { Object val = ReflectionTools.getValue(obj, field, index); if (val == null) { return false; } type = val.getClass(); } catch (IllegalArgumentException | IllegalAccessException ex) { return false; } SWFType swfType = field.getAnnotation(SWFType.class); Multiline multiline = field.getAnnotation(Multiline.class); if (type.equals(int.class) || type.equals(Integer.class) || type.equals(short.class) || type.equals(Short.class) || type.equals(long.class) || type.equals(Long.class) || type.equals(double.class) || type.equals(Double.class) || type.equals(float.class) || type.equals(Float.class)) { return true; } else if (type.equals(boolean.class) || type.equals(Boolean.class)) { return true; } else if (type.equals(String.class)) { return true; } else if (type.equals(RGB.class) || type.equals(RGBA.class) || type.equals(ARGB.class)) { return true; } else if (isByteArray || type.equals(ByteArrayRange.class)) { return true; } else if (type.equals(Amf3Value.class)) { return true; } else { return false; } } private static class FieldSet { public List<Field> fields; public String name; public String itemName; public FieldSet(Field field) { fields = new ArrayList<>(); fields.add(field); name = field.getName(); } public FieldSet(List<Field> fields, String name, String itemName) { this.fields = fields; this.name = name; this.itemName = itemName; } public Field get(int index) { return fields.get(index); } public int size() { return fields.size(); } } private static List<FieldSet> filterFields(MyTreeModel mod, String parentPath, Class<?> cls, boolean limited) { List<FieldSet> ret = new ArrayList<>(); List<Field> fields = getAvailableFields(cls); Map<String, List<Field>> tables = new HashMap<>(); for (Field f : fields) { if (limited) { Conditional cond = f.getAnnotation(Conditional.class); if (cond != null) { ConditionEvaluator ev = new ConditionEvaluator(cond); try { Map<String, Boolean> fieldMap = new HashMap<>(); for (String sf : ev.getFields()) { String fulldf = parentPath + "." + sf; FieldNode condnode = (FieldNode) (mod).getNodeByPath(fulldf); if (condnode != null) { Object value = ReflectionTools.getValue(condnode.obj, condnode.fieldSet.get(FIELD_INDEX), condnode.index); if (value instanceof Boolean) { fieldMap.put(sf, (Boolean) value); } else if (value instanceof Integer) { int intValue = (Integer) value; boolean found = false; for (int i : cond.options()) { if (i == intValue) { found = true; } } fieldMap.put(sf, found); } } else { fieldMap.put(sf, true); } } if (!ev.eval(fieldMap)) { continue; } } catch (AnnotationParseException | IllegalArgumentException | IllegalAccessException ex) { logger.log(Level.SEVERE, null, ex); } } } Table t = f.getAnnotation(Table.class); List<Field> ret1; if (t != null) { String tableName = t.value(); if (!tables.containsKey(tableName)) { ret1 = new ArrayList<>(); tables.put(tableName, ret1); ret.add(new FieldSet(ret1, tableName, t.itemName())); } tables.get(tableName).add(f); } else { ret.add(new FieldSet(f)); } } return ret; } private static List<Field> getAvailableFields(Class<?> cls) { List<Field> ret = fieldCache.get(cls); if (ret == null) { ret = new ArrayList<>(); Field[] fields = cls.getFields(); for (Field f : fields) { if (Modifier.isStatic(f.getModifiers())) { continue; } f.setAccessible(true); Internal inter = f.getAnnotation(Internal.class); if (inter != null) { continue; } HideInRawEdit hide = f.getAnnotation(HideInRawEdit.class); if (hide != null) { continue; } ret.add(f); } fieldCache.put(cls, ret); } return ret; } private void addItem(Object obj, Field field, int index, Class<?> cls) { SWFArray swfArray = field.getAnnotation(SWFArray.class); if (swfArray != null && !swfArray.countField().isEmpty()) { //Fields with same countField must be enlarged too Field[] fields = obj.getClass().getDeclaredFields(); List<Integer> sameFlds = new ArrayList<>(); for (int f = 0; f < fields.length; f++) { SWFArray fieldSwfArray = fields[f].getAnnotation(SWFArray.class); if (fieldSwfArray != null && fieldSwfArray.countField().equals(swfArray.countField())) { sameFlds.add(f); if (cls == null && !ReflectionTools.canAddToField(obj, fields[f])) { JOptionPane.showMessageDialog(this, "This field is abstract, cannot be instantiated, sorry."); //TODO!!! return; } } } for (int f : sameFlds) { ReflectionTools.addToField(obj, fields[f], index, true, cls); try { Object v = ReflectionTools.getValue(obj, fields[f], index); if (v instanceof ASMSource) { ASMSource asv = (ASMSource) v; asv.setSourceTag(editedTag); } } catch (IllegalArgumentException | IllegalAccessException ex) { //ignore } } try { //If countField exists, increment, otherwise do nothing Field countField = obj.getClass().getDeclaredField(swfArray.countField()); int cnt = countField.getInt(obj); cnt++; countField.setInt(obj, cnt); } catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException ex) { //ignored } } else { if (cls == null && !ReflectionTools.canAddToField(obj, field)) { JOptionPane.showMessageDialog(this, "This field is abstract, cannot be instantiated, sorry."); //TODO!!! return; } ReflectionTools.addToField(obj, field, index, true, cls); try { Object v = ReflectionTools.getValue(obj, field, index); if (v instanceof ASMSource) { ASMSource asv = (ASMSource) v; asv.setSourceTag(editedTag); } } catch (IllegalArgumentException | IllegalAccessException ex) { //ignore } } refreshTree(); } public void refreshTree() { View.refreshTree(tree, getModel()); revalidate(); repaint(); } private void removeItem(Object obj, Field field, int index) { SWFArray swfArray = field.getAnnotation(SWFArray.class); if (swfArray != null && !swfArray.countField().isEmpty()) { //Fields with same countField must be removed from too Field[] fields = obj.getClass().getDeclaredFields(); for (int f = 0; f < fields.length; f++) { SWFArray fieldSwfArray = fields[f].getAnnotation(SWFArray.class); if (fieldSwfArray != null && fieldSwfArray.countField().equals(swfArray.countField())) { ReflectionTools.removeFromField(obj, fields[f], index); } } try { //If countField exists, decrement, otherwise do nothing Field countField = obj.getClass().getDeclaredField(swfArray.countField()); int cnt = countField.getInt(obj); cnt--; countField.setInt(obj, cnt); } catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException ex) { //ignored } } else { ReflectionTools.removeFromField(obj, field, index); } refreshTree(); } }