public static final String ARRAY_END_SIGN = "]"; public static final String ARRAY_START_SIGN = "["; public static final String EMPTY_STRING = ""; public static final String NEW_LINE_SIGN = ","; public static final JsonNullReferenceType NULL_OBJECT = new JsonNullReferenceType(); static void mergeObjectIfPossible(AbstractJsonType oldJsonElement, AbstractJsonType elementToAdd, PathMetadata currentPathMetadata) {
    MergableObject oldObject = (MergableObject) oldJsonElement;
    if (oldObject.getClass().isAssignableFrom(elementToAdd.getClass())) {
        oldObject.merge(elementToAdd, currentPathMetadata);
    } else {
        throw new MergeObjectException(oldJsonElement, elementToAdd, currentPathMetadata);
    }
} @Getter
public class PropertyArrayHelper {

    private List<Integer> dimensionalIndexes;
    private String arrayFieldName;

    public PropertyArrayHelper(String field) {
        arrayFieldName = getNameFromArray(field);
        dimensionalIndexes = getIndexesFromArrayField(field);
    }

    public static String getNameFromArray(String fieldName) {
        return fieldName.replaceFirst(INDEXES_PATTERN + "$", EMPTY_STRING);
    }

    public static List<Integer> getIndexesFromArrayField(String fieldName) {
        String indexesAsText = fieldName.replace(getNameFromArray(fieldName), EMPTY_STRING);
        String[] indexesAsTextArray = indexesAsText
            .replace(ARRAY_START_SIGN, EMPTY_STRING)
            .replace(ARRAY_END_SIGN, SIMPLE_ARRAY_DELIMITER)
            .replaceAll("\\s", EMPTY_STRING)
            .split(SIMPLE_ARRAY_DELIMITER);
        List<Integer> indexes = new ArrayList<>();
        for (String indexAsText : indexesAsTextArray) {
            indexes.add(Integer.valueOf(indexAsText));
        }
        return indexes;
    }
} @Data
public class PathMetadata {

    private static final String NUMBER_PATTERN = "([1-9]\\d*)|0";
    public static final String INDEXES_PATTERN = "\\s*(\\[\\s*((" + NUMBER_PATTERN + ")|\\*)\\s*]\\s*)+";

    private static final String WORD_PATTERN = "(.)*";

    private final String originalPropertyKey;
    private PathMetadata parent;
    private String fieldName;
    private String originalFieldName;
    private PathMetadata child;
    private PropertyArrayHelper propertyArrayHelper;
    private Object rawValue;
    private AbstractJsonType jsonValue;

    public boolean isLeaf() {
        return child == null;
    }

    public boolean isRoot() {
        return parent == null;
    }

    public String getCurrentFullPath() {
        return parent == null ? originalFieldName : parent.getCurrentFullPath() + NORMAL_DOT + originalFieldName;
    }

    public PathMetadata getLeaf() {
        PathMetadata current = this;
        while (current.getChild() != null) {
            current = current.getChild();
        }
        return current;
    }

    public void setFieldName(String fieldName) {
        this.fieldName = fieldName;
        if (fieldName.matches(WORD_PATTERN + INDEXES_PATTERN)) {
            propertyArrayHelper = new PropertyArrayHelper(fieldName);
            this.fieldName = propertyArrayHelper.getArrayFieldName();
        }
    }

    public void setRawValue(Object rawValue) {
        if (!isLeaf()) {
            throw new NotLeafValueException("Cannot set value for not leaf: " + getCurrentFullPath());
        }
        this.rawValue = rawValue;
    }

    public String getOriginalPropertyKey() {
        return originalPropertyKey;
    }

    public PathMetadata getRoot() {
        PathMetadata current = this;
        while (current.getParent() != null) {
            current = current.getParent();
        }
        return current;
    }

    public String getCurrentFullPathWithoutIndexes() {
        String parentFullPath = isRoot() ? EMPTY_STRING : getParent().getCurrentFullPath() + NORMAL_DOT;
        return parentFullPath + getFieldName();
    }

    public AbstractJsonType getJsonValue() {
        return jsonValue;
    }

    public void setJsonValue(AbstractJsonType jsonValue) {
        if (!isLeaf()) {
            throw new NotLeafValueException("Cannot set value for not leaf: " + getCurrentFullPath());
        }
        this.jsonValue = jsonValue;
    }

    @Override
    public String toString() {
        return "field='" + fieldName + '\''
            + ", rawValue=" + rawValue
            + ", fullPath='" + getCurrentFullPath() + '}';
    }

    public boolean isArrayField() {
        return propertyArrayHelper != null;
    }
} public class PrimitiveJsonTypesResolver extends JsonTypeResolver {

    private final List<TextToConcreteObjectResolver<?>> toObjectsResolvers;
    private final JsonTypeResolversHierarchyResolver resolversHierarchyResolver;
    private final Boolean skipNulls;
    private final NullToJsonTypeConverter nullToJsonTypeConverter;

    public PrimitiveJsonTypesResolver(List<TextToConcreteObjectResolver<?>> toObjectsResolvers,
        List<ObjectToJsonTypeConverter<?>> toJsonResolvers,
        Boolean skipNulls,
        NullToJsonTypeConverter nullToJsonTypeConverter) {
        this.toObjectsResolvers = ImmutableList.copyOf(toObjectsResolvers);
        this.resolversHierarchyResolver = new JsonTypeResolversHierarchyResolver(toJsonResolvers);
        this.skipNulls = skipNulls;
        this.nullToJsonTypeConverter = nullToJsonTypeConverter;
    }

    @Override
    public ObjectJsonType traverse(PathMetadata currentPathMetaData) {
        addPrimitiveFieldWhenIsValid(currentPathMetaData);
        return null;
    }

    private void addPrimitiveFieldWhenIsValid(PathMetadata currentPathMetaData) {
        JsonObjectFieldsValidator.checkThatFieldCanBeSet(currentObjectJsonType, currentPathMetaData, propertyKey);
        addPrimitiveFieldToCurrentJsonObject(currentPathMetaData);
    }

    private void addPrimitiveFieldToCurrentJsonObject(PathMetadata currentPathMetaData) {
        String field = currentPathMetaData.getFieldName();
        if (currentPathMetaData.isArrayField()) {
            addFieldToArray(currentPathMetaData);
        } else {
            if (currentObjectJsonType.containsField(field) && isArrayJson(currentObjectJsonType.getField(field))) {
                AbstractJsonType abstractJsonType = currentPathMetaData.getJsonValue();
                ArrayJsonType currentArrayInObject = currentObjectJsonType.getJsonArray(field);
                if (isArrayJson(abstractJsonType)) {
                    ArrayJsonType newArray = (ArrayJsonType) abstractJsonType;
                    List<AbstractJsonType> abstractJsonTypes = newArray.convertToListWithoutRealNull();
                    for (int i = 0; i < abstractJsonTypes.size(); i++) {
                        currentArrayInObject.addElement(i, abstractJsonTypes.get(i), currentPathMetaData);
                    }
                } else {
                    throw new CannotOverrideFieldException(currentPathMetaData.getCurrentFullPath(), currentArrayInObject, propertyKey);
                }
            } else {
                currentObjectJsonType.addField(field, currentPathMetaData.getJsonValue(), currentPathMetaData);
            }
        }
    }

    public Object getResolvedObject(String propertyValue, String propertyKey) {
        Optional<?> objectOptional = Optional.empty();
        for (TextToConcreteObjectResolver primitiveResolver : toObjectsResolvers) {
            if (!objectOptional.isPresent()) {
                objectOptional = primitiveResolver.returnConvertedValueForClearedText(this, propertyValue, propertyKey);
            }
        }
        return objectOptional.orElse(null);
    }

    public AbstractJsonType resolvePrimitiveTypeAndReturn(Object propertyValue, String propertyKey) {
        AbstractJsonType result;
        if (propertyValue == null) {
            result = nullToJsonTypeConverter.convertToJsonTypeOrEmpty(this, NULL_OBJECT, propertyKey).get();
        } else {
            result = resolversHierarchyResolver.returnConcreteJsonTypeObject(this, propertyValue, propertyKey);
        }

        if (Boolean.TRUE.equals(skipNulls) && result instanceof JsonNullReferenceType) {
            result = SkipJsonField.SKIP_JSON_FIELD;
        }

        return result;
    }

    protected void addFieldToArray(PathMetadata currentPathMetaData) {
        if (arrayWithGivenFieldNameExist(currentPathMetaData.getFieldName())) {
            fetchArrayAndAddElement(currentPathMetaData);
        } else {
            createArrayAndAddElement(currentPathMetaData);
        }
    }

    private boolean arrayWithGivenFieldNameExist(String field) {
        return currentObjectJsonType.containsField(field);
    }

    private void createArrayAndAddElement(PathMetadata currentPathMetaData) {
        ArrayJsonType arrayJsonTypeObject = new ArrayJsonType();
        addElementToArray(currentPathMetaData, arrayJsonTypeObject);
        currentObjectJsonType.addField(currentPathMetaData.getFieldName(), arrayJsonTypeObject, currentPathMetaData);
    }

    private void fetchArrayAndAddElement(PathMetadata currentPathMetaData) {
        ArrayJsonType arrayJsonType = getArrayJsonWhenIsValid(currentPathMetaData);
        addElementToArray(currentPathMetaData, arrayJsonType);
    }

    private void addElementToArray(PathMetadata currentPathMetaData, ArrayJsonType arrayJsonTypeObject) {
        arrayJsonTypeObject.addElement(currentPathMetaData.getPropertyArrayHelper(), currentPathMetaData.getJsonValue(), currentPathMetaData);
    }

} public class CannotOverrideFieldException extends RuntimeException {

    private static final long serialVersionUID = 1L;

    private static final String CANNOT_OVERRIDE_VALUE = "Cannot override value at path: '%s', current value is: '%s', problematic property key: '%s'";

    public CannotOverrideFieldException(String currentPath, AbstractJsonType currentValue, String propertyKey) {
        this(currentPath, currentValue.toString(), propertyKey);
    }

    @VisibleForTesting
    public CannotOverrideFieldException(String currentPath, String currentValue, String propertyKey) {
        super(String.format(CANNOT_OVERRIDE_VALUE, currentPath, currentValue, propertyKey));
    }
}
import static pl.jalokim.propertiestojson.Constants.ARRAY_END_SIGN;
import static pl.jalokim.propertiestojson.Constants.ARRAY_START_SIGN;
import static pl.jalokim.propertiestojson.Constants.EMPTY_STRING;
import static pl.jalokim.propertiestojson.Constants.NEW_LINE_SIGN;
import static pl.jalokim.propertiestojson.object.JsonNullReferenceType.NULL_OBJECT;
import static pl.jalokim.propertiestojson.object.MergableObject.mergeObjectIfPossible;
import static pl.jalokim.utils.collection.CollectionUtils.getLastIndex;
import static pl.jalokim.utils.collection.CollectionUtils.isLastIndex;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import pl.jalokim.propertiestojson.PropertyArrayHelper;
import pl.jalokim.propertiestojson.path.PathMetadata;
import pl.jalokim.propertiestojson.resolvers.PrimitiveJsonTypesResolver;
import pl.jalokim.propertiestojson.util.exception.CannotOverrideFieldException;
package pl.jalokim.propertiestojson.object;



public class ArrayJsonType extends AbstractJsonType implements MergableObject<ArrayJsonType> {

    public static final int INIT_SIZE = 100;
    private AbstractJsonType[] elements = new AbstractJsonType[INIT_SIZE];
    private int maxIndex = -1;

    public ArrayJsonType() {
    }

    @SuppressWarnings("PMD.ConstructorCallsOverridableMethod")
    public ArrayJsonType(PrimitiveJsonTypesResolver primitiveJsonTypesResolver, Collection<?> elements, PathMetadata currentPathMetadata, String propertyKey) {
        Iterator<?> iterator = elements.iterator();
        int index = 0;
        while (iterator.hasNext()) {
            Object element = iterator.next();
            addElement(index, primitiveJsonTypesResolver.resolvePrimitiveTypeAndReturn(element, propertyKey), currentPathMetadata);
            index++;
        }
    }

    public static ArrayJsonType createOrGetNextDimensionOfArray(ArrayJsonType currentArray, List<Integer> indexes, int indexToTest,
        PathMetadata currentPathMetadata) {
        if (currentArray.existElementByGivenIndex(indexes.get(indexToTest))) {
            AbstractJsonType element = currentArray.getElement(indexes.get(indexToTest));
            if (element instanceof ArrayJsonType) {
                return (ArrayJsonType) element;
            } else {
                List<Integer> currentIndexes = indexes.subList(0, indexToTest + 1);
                String indexesAsText = currentIndexes.stream()
                    .map(Object::toString)
                    .reduce(EMPTY_STRING, (oldText, index) -> oldText + ARRAY_START_SIGN + index + ARRAY_END_SIGN);
                throw new CannotOverrideFieldException(currentPathMetadata.getCurrentFullPathWithoutIndexes() + indexesAsText, element,