diff --git a/jodd-json/src/main/java/jodd/json/JsonParser.java b/jodd-json/src/main/java/jodd/json/JsonParser.java index 943f5c747..32200d6b0 100644 --- a/jodd-json/src/main/java/jodd/json/JsonParser.java +++ b/jodd-json/src/main/java/jodd/json/JsonParser.java @@ -122,6 +122,16 @@ public JsonParser looseMode(final boolean looseMode) { return this; } + /** + * Defines how JSON parser works. In non-lazy mode, the whole JSON is parsed as it is. + * In the lazy mode, not everything is parsed, but some things are left lazy. + * This way we gain some performances, especially on partial usage of the whole JSON. + */ + public JsonParser lazy(final boolean lazy) { + this.mapSupplier = lazy ? LAZYMAP_SUPPLIER : HASMAP_SUPPLIER; + return this; + } + // ---------------------------------------------------------------- mappings protected Map mappings; diff --git a/jodd-json/src/main/java/jodd/json/JsonParserBase.java b/jodd-json/src/main/java/jodd/json/JsonParserBase.java index 552c4470b..db86876e0 100644 --- a/jodd-json/src/main/java/jodd/json/JsonParserBase.java +++ b/jodd-json/src/main/java/jodd/json/JsonParserBase.java @@ -39,6 +39,7 @@ import java.util.List; import java.util.Map; import java.util.Set; +import java.util.function.Supplier; /** * Just a base class of {@link jodd.json.JsonParser} that contains @@ -46,6 +47,11 @@ */ public abstract class JsonParserBase { + protected static final Supplier HASMAP_SUPPLIER = HashMap::new; + protected static final Supplier LAZYMAP_SUPPLIER = LazyMap::new; + + protected Supplier mapSupplier = HASMAP_SUPPLIER; + /** * Creates new instance of {@link jodd.json.MapToBean}. */ @@ -88,7 +94,7 @@ protected Object newObjectInstance(final Class targetType) { if (targetType == null || targetType == Map.class) { - return new HashMap(); + return mapSupplier.get(); } ClassDescriptor cd = ClassIntrospector.get().lookup(targetType); @@ -99,6 +105,7 @@ protected Object newObjectInstance(final Class targetType) { } try { +// return ClassUtil.newInstance(targetType); return ctorDescriptor.getConstructor().newInstance(); } catch (Exception e) { throw new JsonException(e); diff --git a/jodd-json/src/main/java/jodd/json/LazyMap.java b/jodd-json/src/main/java/jodd/json/LazyMap.java new file mode 100644 index 000000000..942dd074a --- /dev/null +++ b/jodd-json/src/main/java/jodd/json/LazyMap.java @@ -0,0 +1,299 @@ +// Copyright 2015 Richard Hightower +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package jodd.json; + +import jodd.util.collection.MapEntry; + +import java.lang.reflect.Array; +import java.util.AbstractMap; +import java.util.AbstractSet; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +/** + * This maps only builds once you ask for a key for the first time. + * It is designed to not incur the overhead of creating a map unless needed. + * + * Taken from Boon project (https://github.com/boonproject/boon) + */ +public class LazyMap extends AbstractMap { + + private final boolean delayMap; + private Map map; + private int size; + private Object[] keys; + private Object[] values; + + public LazyMap() { + keys = new Object[5]; + values = new Object[5]; + this.delayMap = false; + } + + public LazyMap(final int initialSize) { + keys = new Object[initialSize]; + values = new Object[initialSize]; + this.delayMap = false; + } + + public LazyMap(final int initialSize, final boolean delayMap) { + keys = new Object[initialSize]; + values = new Object[initialSize]; + this.delayMap = delayMap; + } + + public LazyMap(final List keys, final List values, final boolean delayMap) { + this.keys = array(Object.class, keys); + this.values = array(Object.class, values); + this.size = this.keys.length; + this.delayMap = delayMap; + + } + + @Override + public Object put(final Object key, final Object value) { + if (map == null) { + keys[size] = key; + values[size] = value; + size++; + if (size == keys.length) { + keys = grow(keys); + values = grow(values); + } + return null; + } + return map.put(key, value); + } + + @Override + public Set> entrySet() { + if (map != null) map.entrySet(); + + if (delayMap) { + + return new FakeMapEntrySet(size, keys, values); + } + buildIfNeeded(); + return map.entrySet(); + } + + @Override + public int size() { + if (map == null) { + return size; + } + return map.size(); + } + + @Override + public boolean isEmpty() { + if (map == null) { + return size == 0; + } + return map.isEmpty(); + } + + @Override + public boolean containsValue(final Object value) { + if (map == null) { + throw new RuntimeException("wrong type of map"); + } + return map.containsValue(value); + } + + @Override + public boolean containsKey(final Object key) { + buildIfNeeded(); + return map.containsKey(key); + } + + @Override + public Object get(final Object key) { + buildIfNeeded(); + return map.get(key); + } + + private void buildIfNeeded() { + if (map == null) { + + map = new HashMap<>(); + + for (int index = 0; index < size; index++) { + map.put(keys[index], values[index]); + } + this.keys = null; + this.values = null; + } + } + + @Override + public Object remove(final Object key) { + + buildIfNeeded(); + return map.remove(key); + + } + + @Override + public void putAll(final Map m) { + buildIfNeeded(); + map.putAll(m); + } + + @Override + public void clear() { + if (map == null) { + size = 0; + } else { + map.clear(); + } + } + + @Override + public Set keySet() { + if (map == null) { + return set(size, keys); + } + return map.keySet(); + + } + + @Override + public Collection values() { + if (map == null) { + return Arrays.asList(values); + } + return map.values(); + + } + + @Override + public boolean equals(final Object o) { + buildIfNeeded(); + return map.equals(o); + } + + @Override + public int hashCode() { + buildIfNeeded(); + return map.hashCode(); + } + + @Override + public String toString() { + buildIfNeeded(); + return map.toString(); + } + + @Override + protected Object clone() { + if (map == null) { + return null; + } + if (map instanceof LinkedHashMap) { + return ((LinkedHashMap) map).clone(); + } + return copy(this); + } + + public LazyMap clearAndCopy() { + final LazyMap map = new LazyMap(size); + for (int index = 0; index < size; index++) { + map.put(keys[index], values[index]); + } + size = 0; + return map; + } + + public static Map copy(final Map map) { + if (map instanceof LinkedHashMap) { + return new LinkedHashMap<>(map); + } + if (map instanceof ConcurrentHashMap) { + return new ConcurrentHashMap<>(map); + } + return new HashMap<>(map); + } + + + // ---------------------------------------------------------------- utils + + private static Set set(final int size, final V... array) { + int index = 0; + final Set set = new HashSet<>(); + + for (final V v : array) { + set.add(v); + index++; + if (index == size) { + break; + } + } + return set; + } + + private static V[] array(final Class cls, final Collection collection) { + final Object newInstance = Array.newInstance(cls, collection.size()); + return collection.toArray((V[]) newInstance); + } + + private static V[] grow(final V[] array) { + final Object newArray = Array.newInstance(array.getClass().getComponentType(), array.length * 2); + System.arraycopy(array, 0, newArray, 0, array.length); + return (V[]) newArray; + } + + private static List list(final V... array) { + final int length = array.length; + final List list = new ArrayList<>(); + for (int index = 0; index < length; index++) { + list.add((V) Array.get(array, index)); + } + return list; + } + + private static class FakeMapEntrySet extends AbstractSet> { + Map.Entry[] array; + + public FakeMapEntrySet(final int size, final Object[] keys, final Object[] values) { + array = new Map.Entry[size]; + + for (int index = 0; index < size; index++) { + array[index] = new MapEntry(keys[index], values[index]); + } + } + + @Override + public Iterator> iterator() { + return list(this.array).iterator(); + } + + @Override + public int size() { + return array.length; + } + + } + +} \ No newline at end of file