package net.hamnaberg.json;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;

import net.hamnaberg.json.util.Iterables;

public final class Data implements Iterable<Property> {

    private List<Property> properties;

    public Data(Iterable<Property> props) {
        properties = Optional.ofNullable(props)
                             .map(x -> StreamSupport.stream(x.spliterator(), false).collect(Collectors.toList()))
                             .orElseThrow(() -> new IllegalArgumentException("Properties in Data may not be null"));
    }

    public boolean isEmpty() {
        return properties.isEmpty();
    }

    public Map<String, Property> getDataAsMap() {
        return Collections.unmodifiableMap(properties.stream().collect(Collectors.toMap(Property::getName, Function.<Property>identity())));
    }

    public Optional<Property> findProperty(Predicate<Property> predicate) {
        return properties.stream().filter(predicate).findFirst();
    }

    public Optional<Property> propertyByName(final String name) {
        return findProperty(input -> name.equals(input.getName()));
    }

    public Optional<Property> get(int index) {
        int count = 0;
        for (Property property : properties) {
            if (index == count) {
                return Optional.of(property);
            }
            count++;
        }
        return Optional.empty();
    }

    /**
     * Replaces all properties with the same name as the supplied properties.
     *
     * @param replacement property to replace with
     * @return a new copy of the template
     */
    public Data replace(Iterable<Property> replacement) {
        if (Iterables.isEmpty(replacement)) {
            return this;
        }
        Map<String, Property> map = new Data(replacement).getDataAsMap();
        List<Property> props = new ArrayList<Property>(this.properties.size());
        for (Property current : this.properties) {
            Property property = map.get(current.getName());
            if (property != null) {
                props.add(property);
            } else {
                props.add(current);
            }
        }
        return new Data(props);
    }

    /**
     * Replaces all properties with the same name as the supplied property
     *
     * @param property property to replace with
     * @return a new copy of the template, or this if nothing was modified.
     */
    public Data replace(Property property) {
        return replace(Arrays.asList(property));
    }

    /**
     * Adds a property to the data.
     *
     * @param property the property to add
     * @return a new copy of the template.
     */
    public Data add(Property property) {
        return addAll(Arrays.asList(property));
    }

    /**
     * Adds properties to the data.
     *
     * @param toAdd the properties to add
     * @return a new copy of the template.
     */
    public Data addAll(Iterable<Property> toAdd) {
        return !Iterables.isEmpty(toAdd)
               ? new Data(Stream.concat(properties.stream(), StreamSupport.stream(toAdd.spliterator(), false)).collect(Collectors.toList()))
               : this;
    }

    /**
     * Replaces all properties.
     *
     * @param props the property to add
     * @return a new copy of the template.
     */
    public Data set(Iterable<Property> props) {
        return !Iterables.isEmpty(props)
               ? new Data(props)
               : this;
    }

    @Override
    public String toString() {
        return properties.toString();
    }

    @Override
    public Iterator<Property> iterator() {
        return properties.iterator();
    }
}

@@ -26,61 +26,8 @@
 n;%0A%0A
-import java.util.ArrayList;%0Aimport java.util.Arrays;%0A
 impo
@@ -52,16 +52,16 @@
 ctions;%0A
+
 import j
@@ -444,16 +444,22 @@
 private 
+final 
 List%3CPro
@@ -478,16 +478,336 @@
 rties;%0A%0A
+    public static Map%3CString, Property%3E getDataAsMap(Iterable%3CProperty%3E properties) %7B%0A        return Collections.unmodifiableMap(%0A                StreamSupport.stream(properties.spliterator(), false).%0A                        collect(Collectors.toMap(Property::getName, Function.%3CProperty%3Eidentity()))%0A        );%0A    %7D%0A%0A%0A
     publ
@@ -1272,131 +1272,31 @@
 urn 
-Collections.unmodifiableMap(properties.stream().collect(Collectors.toMap(Property::getName, Function.%3CProperty%3Eidentity()))
+getDataAsMap(properties
 );%0A 
@@ -2242,24 +2242,28 @@
 %3E map = 
-new Data
+getDataAsMap
 (replace
@@ -2271,24 +2271,10 @@
 ent)
-.getDataAsMap()
 ;
+%0A
 %0A   
@@ -2305,321 +2305,129 @@
 s = 
-new ArrayList%3CProperty%3E(this.properties.size());%0A        for (Property current : this.properties) %7B%0A            Property property = map.get(current.getName());%0A            if (property != null) %7B%0A                props.add(property);%0A            %7D else %7B%0A                props.add(current);%0A            %7D%0A        %7D
+this.stream().%0A                map(f -%3E map.getOrDefault(f.getName(), f)).%0A                collect(Collectors.toList());%0A
 %0A   
@@ -2752,25 +2752,37 @@
 replace(
-Arrays.as
+Collections.singleton
 List(pro
@@ -3009,17 +3009,29 @@
 All(
-Arrays.as
+Collections.singleton
 List
@@ -3932,16 +3932,108 @@
 ator();%0A
-
     %7D
+%0A%0A    public Stream%3CProperty%3E stream() %7Breturn StreamSupport.stream(spliterator(), false); %7D
 %0A%7D%0A

Simplify replace