private void removeItem(Object obj, Field field, int index) {
    final JScrollBar sb = genericTagPropertiesEditPanelScrollPanel.getVerticalScrollBar();
    final int val = sb.getValue(); // save scroll top
    SWFType swfType = field.getAnnotation(SWFType.class);
    if (swfType != null
        && !swfType
            .countField()
            .isEmpty()) { // Fields with same countField must be removed from too
      Field[] fields = obj.getClass().getDeclaredFields();
      for (int f = 0; f < fields.length; f++) {
        SWFType fieldSwfType = fields[f].getAnnotation(SWFType.class);
        if (fieldSwfType != null && fieldSwfType.countField().equals(swfType.countField())) {
          ReflectionTools.removeFromField(obj, fields[f], index);
        }
      }
      try {
        // If countField exists, decrement, otherwise do nothing
        Field countField = obj.getClass().getDeclaredField(swfType.countField());
        int cnt = countField.getInt(obj);
        cnt--;
        countField.setInt(obj, cnt);
      } catch (NoSuchFieldException
          | SecurityException
          | IllegalArgumentException
          | IllegalAccessException ex) {
        // ignored
      }
    } else {
      ReflectionTools.removeFromField(obj, field, index);
    }

    generateEditControls(editedTag, false);

    // Restore scroll top after some time. TODO: Handle this better. I don't know how :-(.
    new Thread() {

      @Override
      public void run() {
        try {
          Thread.sleep(500);
        } catch (InterruptedException ex) {
          logger.log(Level.SEVERE, null, ex);
        }

        View.execInEventDispatch(
            () -> {
              genericTagPropertiesEditPanelScrollPanel.getVerticalScrollBar().setValue(val);
            });
      }
    }.start();
    revalidate();
    repaint();
  }
  public BinaryDataEditor(
      MainPanel mainPanel, String fieldName, Object obj, Field field, int index, Class<?> type) {
    super();
    this.mainPanel = mainPanel;
    this.obj = obj;
    this.field = field;
    this.index = index;
    this.type = type;
    this.fieldName = fieldName;
    setText(AppStrings.translate("button.replace"));
    addActionListener(this::buttonActionPerformed);

    try {
      Object val = ReflectionTools.getValue(obj, field, index);
      if (val instanceof byte[]) {
        byte[] ba = (byte[]) val;
        setToolTipText(ba.length + " bytes");
      } else if (val instanceof ByteArrayRange) {
        ByteArrayRange bar = (ByteArrayRange) val;
        setToolTipText(bar.getLength() + " bytes");
      }
      value = val;
    } catch (IllegalArgumentException | IllegalAccessException ex) {
      // ignore
    }
  }
 @Override
 public void save() {
   try {
     ReflectionTools.setValue(obj, field, index, value);
   } catch (IllegalArgumentException | IllegalAccessException ex) {
     // ignore
   }
 }
  @Override
  public void change(GenericTagEditor ed) {
    for (String key : editors.keySet()) {
      GenericTagEditor dependentEditor = editors.get(key);
      Component dependentLabel = labels.get(key);
      Component dependentTypeLabel = types.get(key);
      List<Field> path = fieldPaths.get(key);
      List<Integer> indices = fieldIndices.get(key);
      String p = "";
      boolean conditionMet = true;
      for (int i = 0; i < path.size(); i++) {
        Field f = path.get(i);
        int index = indices.get(i);
        String par = p;
        if (!p.isEmpty()) {
          p += ".";
        }
        p += f.getName();
        if (ReflectionTools.needsIndex(f)) {
          p += "[" + index + "]";
        }
        Conditional cond = f.getAnnotation(Conditional.class);
        if (cond != null) {
          ConditionEvaluator ev = new ConditionEvaluator(cond);

          try {
            Set<String> fieldNames = ev.getFields();
            Map<String, Boolean> fields = new HashMap<>();
            for (String fld : fieldNames) {
              String ckey = "";
              if (!par.isEmpty()) {
                ckey = par + ".";
              }
              ckey += fld;
              if (editors.containsKey(ckey)) {
                GenericTagEditor editor = editors.get(ckey);
                Object val = editor.getChangedValue();
                fields.put(fld, true);
                if (val instanceof Boolean) {
                  fields.put(fld, (Boolean) val);
                }
              }
            }
            boolean ok = ev.eval(fields);
            if (conditionMet) {
              conditionMet = ok;
            }
            ((Component) dependentEditor).setVisible(conditionMet);
            dependentLabel.setVisible(conditionMet);
            dependentTypeLabel.setVisible(conditionMet);
          } catch (AnnotationParseException ex) {
            logger.log(Level.SEVERE, "Invalid condition", ex);
          }
        }
        if (!conditionMet) {
          break;
        }
      }
    }
    genericTagPropertiesEditPanel.removeAll();
    genericTagPropertiesEditPanel.setSize(0, 0);
    int propCount = 0;
    for (String key : keys) {

      Component dependentEditor;
      if (addKeys.contains(key)) {
        dependentEditor = addButtons.get(key);
      } else if (removeButtons.containsKey(key)) { // It's array/list, add remove button
        JPanel editRemPanel = new JPanel(new BorderLayout());
        editRemPanel.add((Component) editors.get(key), BorderLayout.CENTER);
        editRemPanel.add(removeButtons.get(key), BorderLayout.EAST);
        dependentEditor = editRemPanel;
      } else {
        dependentEditor = (Component) editors.get(key);
      }
      Component dependentLabel = labels.get(key);
      Component dependentTypeLabel = types.get(key);
      if (dependentEditor.isVisible()) {
        genericTagPropertiesEditPanel.add(dependentLabel);
        genericTagPropertiesEditPanel.add(((Component) dependentEditor));
        genericTagPropertiesEditPanel.add(dependentTypeLabel);
        propCount++;
      }
    }
    /*genericTagPropertiesEditPanel.add(new JPanel());
    genericTagPropertiesEditPanel.add(new JPanel());
    genericTagPropertiesEditPanel.add(new JPanel());*/
    relayout(propCount /*+ 1*/);
  }
  private int addEditor(
      String name,
      Object obj,
      Field field,
      int index,
      Class<?> type,
      Object value,
      List<Field> parentList,
      List<Integer> parentIndices,
      boolean readonly)
      throws IllegalArgumentException, IllegalAccessException {
    Calculated calculated = field.getAnnotation(Calculated.class);
    if (calculated != null) {
      return 0;
    }
    List<Field> parList = new ArrayList<>(parentList);
    parList.add(field);

    List<Integer> parIndices = new ArrayList<>(parentIndices);
    parIndices.add(index);
    Internal inter = field.getAnnotation(Internal.class);
    if (inter != null) {
      return 0;
    }
    SWFType swfType = field.getAnnotation(SWFType.class);
    Multiline multiline = field.getAnnotation(Multiline.class);
    Component editor;
    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(name, obj, field, index, type, swfType);
    } else if (type.equals(boolean.class) || type.equals(Boolean.class)) {
      editor = new BooleanEditor(name, obj, field, index, type);
    } else if (type.equals(String.class)) {
      editor = new StringEditor(name, obj, field, index, type, multiline != null);
    } else if (type.equals(RGB.class) || type.equals(RGBA.class) || type.equals(ARGB.class)) {
      editor = new ColorEditor(name, obj, field, index, type);
    } else if (type.equals(ByteArrayRange.class)) {
      editor = new BinaryDataEditor(mainPanel, name, obj, field, index, type);
    } else if (type.equals(Amf3Value.class)) {
      editor = new Amf3ValueEditor(name, obj, field, index, type);
    } else {
      if (value == null) {
        if (readonly) {
          return 0;
        }
        Optional opt = field.getAnnotation(Optional.class);
        if (opt == null) {
          try {
            value = ReflectionTools.newInstanceOf(field.getType());
            field.set(obj, value);
          } catch (InstantiationException | IllegalAccessException ex) {
            logger.log(Level.SEVERE, null, ex);
            return 0;
          }
        } else {
          return 0;
        }
      }
      return generateEditControlsRecursive(value, name + ".", parList, parIndices, readonly);
    }
    if (editor instanceof GenericTagEditor) {
      GenericTagEditor ce = (GenericTagEditor) editor;
      ce.addChangeListener(this);
      editors.put(name, ce);
      fieldPaths.put(name, parList);
      fieldIndices.put(name, parIndices);
      addRow(name, editor, field);

      ce.added();
    }
    return 1;
  }
  private int generateEditControlsRecursive(
      final Object obj,
      String parent,
      List<Field> parentFields,
      List<Integer> parentIndices,
      boolean readonly) {
    if (obj == null) {
      return 0;
    }
    Field[] fields = obj.getClass().getDeclaredFields();
    int propCount = 0;
    for (final Field field : fields) {
      try {
        if (Modifier.isStatic(field.getModifiers())) {
          continue;
        }
        field.setAccessible(true);
        String name = parent + field.getName();
        final Object value = field.get(obj);
        if (List.class.isAssignableFrom(field.getType())) {
          if (value != null) {
            int i = 0;
            for (Object obj1 : (Iterable) value) {
              final String subname = name + "[" + i + "]";
              propCount +=
                  addEditor(
                      subname,
                      obj,
                      field,
                      i,
                      obj1.getClass(),
                      obj1,
                      parentFields,
                      parentIndices,
                      readonly);
              final int fi = i;
              i++;
              JButton removeButton = new JButton(View.getIcon("close16"));
              removeButton.addActionListener(
                  new ActionListener() {

                    @Override
                    public void actionPerformed(ActionEvent e) {
                      removeItem(obj, field, fi);
                    }
                  });
              removeButtons.put(subname, removeButton);
            }
          }
        } else if (field.getType().isArray()) {
          if (value != null) {
            for (int i = 0; i < Array.getLength(value); i++) {
              Object item = Array.get(value, i);
              String subname = name + "[" + i + "]";
              propCount +=
                  addEditor(
                      subname,
                      obj,
                      field,
                      i,
                      item.getClass(),
                      item,
                      parentFields,
                      parentIndices,
                      readonly);
              final int fi = i;
              JButton removeButton = new JButton(View.getIcon("close16"));
              removeButton.addActionListener(
                  new ActionListener() {

                    @Override
                    public void actionPerformed(ActionEvent e) {
                      removeItem(obj, field, fi);
                    }
                  });
              removeButtons.put(subname, removeButton);
            }
          }
        } else {
          propCount +=
              addEditor(
                  name,
                  obj,
                  field,
                  0,
                  field.getType(),
                  value,
                  parentFields,
                  parentIndices,
                  readonly);
        }
        if (ReflectionTools.needsIndex(field)
            && !readonly
            && !field.getName().equals("clipActionRecords")) { // No clip actions, sorry
          JButton addButton = new JButton(View.getIcon("add16"));
          addButton.addActionListener(
              new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                  addItem(obj, field);
                }
              });
          name += "[]";

          List<Field> parList = new ArrayList<>(parentFields);
          parList.add(field);
          fieldPaths.put(name, parList);

          List<Integer> parIndices = new ArrayList<>(parentIndices);
          parIndices.add(0);
          fieldIndices.put(name, parIndices);

          addRow(name, addButton, field);
          addKeys.add(name);
          addButtons.put(name, addButton);
        }
      } catch (IllegalArgumentException | IllegalAccessException ex) {
        logger.log(Level.SEVERE, null, ex);
      }
    }
    return propCount;
  }