From af7eebbd17cbe6137975851d8810251b2f0e1644 Mon Sep 17 00:00:00 2001 From: salyh Date: Tue, 29 Jul 2014 23:05:24 +0200 Subject: [PATCH] FLEECE-10 Signed-off-by: salyh --- .../fleece/core/EscapedStringAwareJsonParser.java | 25 - .../apache/fleece/core/JsonArrayBuilderImpl.java | 85 ++- .../java/org/apache/fleece/core/JsonArrayImpl.java | 102 +-- .../apache/fleece/core/JsonBuilderFactoryImpl.java | 26 +- .../java/org/apache/fleece/core/JsonChars.java | 6 +- .../org/apache/fleece/core/JsonDoubleImpl.java | 12 +- .../apache/fleece/core/JsonGeneratorFacade.java | 186 ------ .../fleece/core/JsonGeneratorFactoryImpl.java | 89 ++- .../org/apache/fleece/core/JsonGeneratorImpl.java | 734 ++++++++++++++++++--- .../org/apache/fleece/core/JsonInMemoryParser.java | 29 +- .../org/apache/fleece/core/JsonLocationImpl.java | 8 +- .../java/org/apache/fleece/core/JsonLongImpl.java | 7 +- .../org/apache/fleece/core/JsonNumberImpl.java | 19 +- .../apache/fleece/core/JsonObjectBuilderImpl.java | 63 +- .../org/apache/fleece/core/JsonObjectImpl.java | 61 +- .../apache/fleece/core/JsonParserFactoryImpl.java | 62 +- .../fleece/core/JsonPrettyGeneratorImpl.java | 306 +++++++-- .../org/apache/fleece/core/JsonProviderImpl.java | 31 +- .../apache/fleece/core/JsonReaderFactoryImpl.java | 26 +- .../org/apache/fleece/core/JsonReaderImpl.java | 239 ++----- .../org/apache/fleece/core/JsonReaderListener.java | 43 -- .../fleece/core/JsonReaderListenerFactory.java | 25 - .../apache/fleece/core/JsonStreamParserImpl.java | 20 +- .../org/apache/fleece/core/JsonStringImpl.java | 28 +- .../apache/fleece/core/JsonWriterFactoryImpl.java | 38 +- .../org/apache/fleece/core/JsonWriterImpl.java | 32 +- .../fleece/core/RFC4627AwareInputStreamReader.java | 30 +- .../main/java/org/apache/fleece/core/Strings.java | 13 +- .../org/apache/fleece/core/JsonArrayImplTest.java | 33 +- .../apache/fleece/core/JsonGeneratorImplTest.java | 126 ++++ .../org/apache/fleece/core/JsonObjectImplTest.java | 9 +- .../org/apache/fleece/core/JsonParserTest.java | 63 +- .../org/apache/fleece/core/JsonReaderImplTest.java | 15 + .../org/apache/fleece/core/JsonWriterImplTest.java | 7 +- .../java/org/apache/fleece/core/LocationTest.java | 49 +- .../main/java/org/apache/fleece/mapper/Mapper.java | 12 +- .../java/org/apache/fleece/mapper/MapperTest.java | 150 ++++- 37 files changed, 1828 insertions(+), 981 deletions(-) delete mode 100644 fleece-core/src/main/java/org/apache/fleece/core/EscapedStringAwareJsonParser.java delete mode 100644 fleece-core/src/main/java/org/apache/fleece/core/JsonGeneratorFacade.java delete mode 100644 fleece-core/src/main/java/org/apache/fleece/core/JsonReaderListener.java delete mode 100644 fleece-core/src/main/java/org/apache/fleece/core/JsonReaderListenerFactory.java diff --git a/fleece-core/src/main/java/org/apache/fleece/core/EscapedStringAwareJsonParser.java b/fleece-core/src/main/java/org/apache/fleece/core/EscapedStringAwareJsonParser.java deleted file mode 100644 index 56cb7a5..0000000 --- a/fleece-core/src/main/java/org/apache/fleece/core/EscapedStringAwareJsonParser.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you 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 org.apache.fleece.core; - -import javax.json.stream.JsonParser; - -public interface EscapedStringAwareJsonParser extends JsonParser { - String getEscapedString(); -} diff --git a/fleece-core/src/main/java/org/apache/fleece/core/JsonArrayBuilderImpl.java b/fleece-core/src/main/java/org/apache/fleece/core/JsonArrayBuilderImpl.java index d5620fd..a92eede 100644 --- a/fleece-core/src/main/java/org/apache/fleece/core/JsonArrayBuilderImpl.java +++ b/fleece-core/src/main/java/org/apache/fleece/core/JsonArrayBuilderImpl.java @@ -18,114 +18,113 @@ */ package org.apache.fleece.core; +import java.io.Serializable; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + import javax.json.JsonArray; import javax.json.JsonArrayBuilder; import javax.json.JsonObjectBuilder; import javax.json.JsonValue; -import java.io.Serializable; -import java.math.BigDecimal; -import java.math.BigInteger; -public class JsonArrayBuilderImpl implements JsonArrayBuilder, Serializable { - private final JsonArrayImpl array = new JsonArrayImpl(); +class JsonArrayBuilderImpl implements JsonArrayBuilder, Serializable { + private List tmpList; @Override public JsonArrayBuilder add(final JsonValue value) { - if (value == null) { - throw npe(); - } - array.addInternal(value); + addValue(value); return this; } @Override public JsonArrayBuilder add(final String value) { - if (value == null) { - throw npe(); - } - array.addInternal(new JsonStringImpl(value)); + addValue(new JsonStringImpl(value)); return this; } @Override public JsonArrayBuilder add(final BigDecimal value) { - if (value == null) { - throw npe(); - } - array.addInternal(new JsonNumberImpl(value)); + addValue(new JsonNumberImpl(value)); return this; } @Override public JsonArrayBuilder add(final BigInteger value) { - if (value == null) { - throw npe(); - } - array.addInternal(new JsonNumberImpl(new BigDecimal(value))); + addValue(new JsonNumberImpl(new BigDecimal(value))); return this; } @Override public JsonArrayBuilder add(final int value) { - array.addInternal(new JsonLongImpl(value)); + addValue(new JsonLongImpl(value)); return this; } @Override public JsonArrayBuilder add(final long value) { - array.addInternal(new JsonLongImpl(value)); + addValue(new JsonLongImpl(value)); return this; } @Override public JsonArrayBuilder add(final double value) { - final Double valueObject = Double.valueOf(value); - if (valueObject.isInfinite()) { - throw new NumberFormatException("value must not be infinite"); - } - if (valueObject.isNaN()) { - throw new NumberFormatException("value must not be NaN"); - } - array.addInternal(new JsonDoubleImpl(value)); + addValue(new JsonDoubleImpl(value)); return this; } @Override public JsonArrayBuilder add(final boolean value) { - array.addInternal(value ? JsonValue.TRUE : JsonValue.FALSE); + addValue(value ? JsonValue.TRUE : JsonValue.FALSE); return this; } @Override public JsonArrayBuilder addNull() { - array.addInternal(JsonValue.NULL); + addValue(JsonValue.NULL); return this; } @Override public JsonArrayBuilder add(final JsonObjectBuilder builder) { - if (builder == null) { - throw new NullPointerException("builder must not be null"); - } - array.addInternal(builder.build()); + addValue(builder.build()); return this; } @Override public JsonArrayBuilder add(final JsonArrayBuilder builder) { - if (builder == null) { - throw new NullPointerException("builder must not be null"); - } - array.addInternal(builder.build()); + addValue(builder.build()); return this; } + + private void addValue(JsonValue value){ + if (value == null) { + throw npe(); + } + + if(tmpList==null){ + tmpList=new ArrayList(); + } + + tmpList.add(value); + } @Override public JsonArray build() { - return array; + + if(tmpList == null) { + return new JsonArrayImpl(Collections.EMPTY_LIST); + } else { + List dump = (Collections.unmodifiableList(tmpList)); + tmpList=null; + return new JsonArrayImpl(dump); + } + } private static NullPointerException npe() { - throw new NullPointerException("value must not be null"); + throw new NullPointerException("value/builder must not be null"); } } diff --git a/fleece-core/src/main/java/org/apache/fleece/core/JsonArrayImpl.java b/fleece-core/src/main/java/org/apache/fleece/core/JsonArrayImpl.java index afb6b63..fa1e230 100644 --- a/fleece-core/src/main/java/org/apache/fleece/core/JsonArrayImpl.java +++ b/fleece-core/src/main/java/org/apache/fleece/core/JsonArrayImpl.java @@ -18,26 +18,36 @@ */ package org.apache.fleece.core; +import java.io.Serializable; +import java.util.AbstractList; +import java.util.Iterator; +import java.util.List; + import javax.json.JsonArray; import javax.json.JsonNumber; import javax.json.JsonObject; import javax.json.JsonString; import javax.json.JsonValue; -import java.io.Serializable; -import java.util.Collection; -import java.util.Iterator; -import java.util.LinkedList; -import java.util.List; -public class JsonArrayImpl extends LinkedList implements JsonArray, Serializable { +class JsonArrayImpl extends AbstractList implements JsonArray, Serializable { private Integer hashCode = null; + private final List unmodifieableBackingList; + + JsonArrayImpl(List backingList) { + super(); + this.unmodifieableBackingList = backingList; + } + + private T value(final int idx, final Class type) { - if (idx > size()) { - throw new IndexOutOfBoundsException(idx + "/" + size()); + if (idx > unmodifieableBackingList.size()) { + throw new IndexOutOfBoundsException(idx + "/" + unmodifieableBackingList.size()); } - return type.cast(get(idx)); + return type.cast(unmodifieableBackingList.get(idx)); } + + @Override public JsonObject getJsonObject(final int index) { @@ -61,7 +71,7 @@ public class JsonArrayImpl extends LinkedList implements JsonArray, S @Override public List getValuesAs(final Class clazz) { - return (List) this; + return (List) unmodifieableBackingList; } @Override @@ -119,12 +129,12 @@ public class JsonArrayImpl extends LinkedList implements JsonArray, S @Override public String toString() { final StringBuilder builder = new StringBuilder("["); - final Iterator it = iterator(); + final Iterator it = unmodifieableBackingList.iterator(); boolean hasNext = it.hasNext(); while (hasNext) { final JsonValue jsonValue = it.next(); if (JsonString.class.isInstance(jsonValue)) { - builder.append(JsonChars.QUOTE).append(jsonValue.toString()).append(JsonChars.QUOTE); + builder.append(jsonValue.toString()); } else { builder.append(jsonValue != JsonValue.NULL ? jsonValue.toString() : JsonChars.NULL); } @@ -138,69 +148,27 @@ public class JsonArrayImpl extends LinkedList implements JsonArray, S @Override public boolean equals(final Object obj) { - return JsonArray.class.isInstance(obj) && super.equals(obj); - } - - //make protected if class is supposed to be subclassed - //make package private otherwise - protected void addInternal(final JsonValue value) { - super.add(value); - } - - @Override - public boolean add(final JsonValue element) { - throw immutable(); - } - - @Override - public boolean addAll(final int index, final Collection c) { - throw immutable(); - } - - @Override - public boolean remove(final Object o) { - throw immutable(); - } - - @Override - public JsonValue remove(final int index) { - throw immutable(); - } - - @Override - public void add(final int index, final JsonValue element) { - throw immutable(); - } - - @Override - public void clear() { - throw immutable(); + return JsonArrayImpl.class.isInstance(obj) && unmodifieableBackingList.equals(JsonArrayImpl.class.cast(obj).unmodifieableBackingList); } + @Override - public boolean retainAll(final Collection c) { - throw immutable(); - } - - @Override - public boolean removeAll(final Collection c) { - throw immutable(); + public int hashCode() { + Integer h=hashCode; + if (h == null) { + h = unmodifieableBackingList.hashCode(); + h=hashCode; + } + return h; } @Override - public JsonValue set(final int index, final JsonValue element) { - throw immutable(); - } - - private static UnsupportedOperationException immutable() { - throw new UnsupportedOperationException("JsonArray is immutable. You can create another one thanks to JsonArrayBuilder"); + public JsonValue get(int index) { + return unmodifieableBackingList.get(index); } @Override - public int hashCode() { - if (hashCode == null) { - hashCode = super.hashCode(); - } - return hashCode; + public int size() { + return unmodifieableBackingList.size(); } } diff --git a/fleece-core/src/main/java/org/apache/fleece/core/JsonBuilderFactoryImpl.java b/fleece-core/src/main/java/org/apache/fleece/core/JsonBuilderFactoryImpl.java index 5e59640..76b0455 100644 --- a/fleece-core/src/main/java/org/apache/fleece/core/JsonBuilderFactoryImpl.java +++ b/fleece-core/src/main/java/org/apache/fleece/core/JsonBuilderFactoryImpl.java @@ -18,18 +18,30 @@ */ package org.apache.fleece.core; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + import javax.json.JsonArrayBuilder; import javax.json.JsonBuilderFactory; import javax.json.JsonObjectBuilder; -import java.util.Collections; -import java.util.Map; +class JsonBuilderFactoryImpl implements JsonBuilderFactory { + private final Map internalConfig = new HashMap(); + private static final String[] SUPPORTED_CONFIG_KEYS = new String[] { + //nothing yet + + }; -public class JsonBuilderFactoryImpl implements JsonBuilderFactory { - private final Map config; + JsonBuilderFactoryImpl(final Map config) { + if (config != null) { - public JsonBuilderFactoryImpl(final Map config) { - this.config = config; + for (final String configKey : SUPPORTED_CONFIG_KEYS) { + if (config.containsKey(configKey)) { + internalConfig.put(configKey, config.get(configKey)); + } + } + } } @Override @@ -44,6 +56,6 @@ public class JsonBuilderFactoryImpl implements JsonBuilderFactory { @Override public Map getConfigInUse() { - return Collections.unmodifiableMap(config); + return Collections.unmodifiableMap(internalConfig); } } diff --git a/fleece-core/src/main/java/org/apache/fleece/core/JsonChars.java b/fleece-core/src/main/java/org/apache/fleece/core/JsonChars.java index 3a92c9b..b135886 100644 --- a/fleece-core/src/main/java/org/apache/fleece/core/JsonChars.java +++ b/fleece-core/src/main/java/org/apache/fleece/core/JsonChars.java @@ -27,8 +27,8 @@ public interface JsonChars { char END_OBJECT_CHAR = '}'; char START_ARRAY_CHAR = '['; char END_ARRAY_CHAR = ']'; - char QUOTE = '"'; - char COMMA = ','; + char QUOTE_CHAR = '"'; + char COMMA_CHAR = ','; char KEY_SEPARATOR = ':'; char EOL = '\n'; @@ -61,7 +61,7 @@ public interface JsonChars { char FORMFEED = '\f'; char CR = '\r'; - String NULL = "null"; + String NULL = "null".intern(); static final byte START_ARRAY = (byte) Event.START_ARRAY.ordinal(); static final byte START_OBJECT = (byte) Event.START_OBJECT.ordinal(); diff --git a/fleece-core/src/main/java/org/apache/fleece/core/JsonDoubleImpl.java b/fleece-core/src/main/java/org/apache/fleece/core/JsonDoubleImpl.java index 5b63d41..4ff90f9 100644 --- a/fleece-core/src/main/java/org/apache/fleece/core/JsonDoubleImpl.java +++ b/fleece-core/src/main/java/org/apache/fleece/core/JsonDoubleImpl.java @@ -18,14 +18,20 @@ */ package org.apache.fleece.core; -import javax.json.JsonNumber; import java.math.BigDecimal; import java.math.BigInteger; -public class JsonDoubleImpl implements JsonNumber { +import javax.json.JsonNumber; + +final class JsonDoubleImpl implements JsonNumber { private final double value; - public JsonDoubleImpl(final double value) { + JsonDoubleImpl(final double value) { + + if(Double.isInfinite(value) || Double.isNaN(value)) { + throw new NumberFormatException("double value must to be NaN or Infinite"); + } + this.value = value; } diff --git a/fleece-core/src/main/java/org/apache/fleece/core/JsonGeneratorFacade.java b/fleece-core/src/main/java/org/apache/fleece/core/JsonGeneratorFacade.java deleted file mode 100644 index bf1245a..0000000 --- a/fleece-core/src/main/java/org/apache/fleece/core/JsonGeneratorFacade.java +++ /dev/null @@ -1,186 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you 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 org.apache.fleece.core; - -import javax.json.JsonValue; -import javax.json.stream.JsonGenerator; -import java.io.Serializable; -import java.math.BigDecimal; -import java.math.BigInteger; - -// basically a proxy but java.lang.reflect.Proxy would be too slow for JSon -// -// just a facade allowing to reuse the same pointer (instance) -// without using a fluent API -public class JsonGeneratorFacade implements JsonGenerator, Serializable { - private JsonGenerator delegate; - - public JsonGeneratorFacade(final JsonGenerator delegate) { - this.delegate = delegate; - } - - @Override - public JsonGenerator writeStartObject() { - delegate = delegate.writeStartObject(); - return this; - } - - @Override - public JsonGenerator writeStartObject(final String name) { - delegate = delegate.writeStartObject(name); - return this; - } - - @Override - public JsonGenerator writeStartArray() { - delegate = delegate.writeStartArray(); - return this; - } - - @Override - public JsonGenerator writeStartArray(final String name) { - delegate = delegate.writeStartArray(name); - return this; - } - - @Override - public JsonGenerator write(final String name, final JsonValue value) { - delegate = delegate.write(name, value); - return this; - } - - @Override - public JsonGenerator write(final String name, final String value) { - delegate = delegate.write(name, value); - return this; - } - - @Override - public JsonGenerator write(final String name, final BigInteger value) { - delegate = delegate.write(name, value); - return this; - } - - @Override - public JsonGenerator write(final String name, final BigDecimal value) { - delegate = delegate.write(name, value); - return this; - } - - @Override - public JsonGenerator write(final String name, final int value) { - delegate = delegate.write(name, value); - return this; - } - - @Override - public JsonGenerator write(final String name, final long value) { - delegate = delegate.write(name, value); - return this; - } - - @Override - public JsonGenerator write(final String name, final double value) { - delegate = delegate.write(name, value); - return this; - } - - @Override - public JsonGenerator write(final String name, final boolean value) { - delegate = delegate.write(name, value); - return this; - } - - @Override - public JsonGenerator writeNull(final String name) { - delegate = delegate.writeNull(name); - return this; - } - - @Override - public JsonGenerator writeEnd() { - delegate = delegate.writeEnd(); - return this; - } - - @Override - public JsonGenerator write(final JsonValue value) { - delegate = delegate.write(value); - return this; - } - - @Override - public JsonGenerator write(final String value) { - delegate = delegate.write(value); - return this; - } - - @Override - public JsonGenerator write(final BigDecimal value) { - delegate = delegate.write(value); - return this; - } - - @Override - public JsonGenerator write(final BigInteger value) { - delegate = delegate.write(value); - return this; - } - - @Override - public JsonGenerator write(final int value) { - delegate = delegate.write(value); - return this; - } - - @Override - public JsonGenerator write(final long value) { - delegate = delegate.write(value); - return this; - } - - @Override - public JsonGenerator write(final double value) { - delegate = delegate.write(value); - return this; - } - - @Override - public JsonGenerator write(final boolean value) { - delegate = delegate.write(value); - return this; - } - - @Override - public JsonGenerator writeNull() { - delegate = delegate.writeNull(); - return this; - } - - @Override - public void close() { - delegate.close(); - - } - - @Override - public void flush() { - delegate.flush(); - } -} diff --git a/fleece-core/src/main/java/org/apache/fleece/core/JsonGeneratorFactoryImpl.java b/fleece-core/src/main/java/org/apache/fleece/core/JsonGeneratorFactoryImpl.java index a320a14..573ca70 100644 --- a/fleece-core/src/main/java/org/apache/fleece/core/JsonGeneratorFactoryImpl.java +++ b/fleece-core/src/main/java/org/apache/fleece/core/JsonGeneratorFactoryImpl.java @@ -18,53 +18,104 @@ */ package org.apache.fleece.core; -import javax.json.stream.JsonGenerator; -import javax.json.stream.JsonGeneratorFactory; - import java.io.OutputStream; -import java.io.OutputStreamWriter; import java.io.Serializable; import java.io.Writer; import java.nio.charset.Charset; import java.util.Collections; +import java.util.HashMap; +import java.util.Locale; import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; -public class JsonGeneratorFactoryImpl implements JsonGeneratorFactory, Serializable { - private final Map config; - private final ConcurrentMap cache = new ConcurrentHashMap(); +import javax.json.stream.JsonGenerator; +import javax.json.stream.JsonGeneratorFactory; + +public class JsonGeneratorFactoryImpl implements JsonGeneratorFactory, Serializable { + public static final String BUFFER_LENGTH = "org.apache.fleece.default-char-buffer-generator"; + public static final int DEFAULT_BUFFER_LENGTH = Integer.getInteger(BUFFER_LENGTH, 1024); //TODO check default string length/buffer size + private final Map internalConfig = new HashMap(); + private static final String[] SUPPORTED_CONFIG_KEYS = new String[] { + + JsonGenerator.PRETTY_PRINTING, BUFFER_LENGTH, JsonParserFactoryImpl.BUFFER_STRATEGY + + }; + //key caching currently disabled + private final ConcurrentMap cache = null;//new ConcurrentHashMap(); private final boolean pretty; + private final BufferStrategy.BufferProvider bufferProvider; public JsonGeneratorFactoryImpl(final Map config) { - this.config = config; - this.pretty = Boolean.TRUE.equals(config.get(JsonGenerator.PRETTY_PRINTING)) || "true".equals(config.get(JsonGenerator.PRETTY_PRINTING)); + + if(config != null) { + + for (String configKey : SUPPORTED_CONFIG_KEYS) { + if(config.containsKey(configKey)) { + internalConfig.put(configKey, config.get(configKey)); + } + } + } + + if(internalConfig.containsKey(JsonGenerator.PRETTY_PRINTING)) { + this.pretty = Boolean.TRUE.equals(internalConfig.get(JsonGenerator.PRETTY_PRINTING)) || "true".equals(internalConfig.get(JsonGenerator.PRETTY_PRINTING)); + } else { + this.pretty = false; + } + + final int bufferSize = getInt(BUFFER_LENGTH); + if (bufferSize <= 0) { + throw new IllegalArgumentException("buffer length must be greater than zero"); + } + + this.bufferProvider = getBufferProvider().newCharProvider(bufferSize); + } + + private BufferStrategy getBufferProvider() { + final Object name = internalConfig.get(JsonParserFactoryImpl.BUFFER_STRATEGY); + if (name != null) { + return BufferStrategy.valueOf(name.toString().toUpperCase(Locale.ENGLISH)); + } + return BufferStrategy.QUEUE; } - @Override - public JsonGenerator createGenerator(final Writer writer) { - return new JsonGeneratorFacade(newJsonGeneratorImpl(writer)); + private int getInt(final String key) { + final Object maxStringSize = internalConfig.get(key); + if (maxStringSize == null) { + return DEFAULT_BUFFER_LENGTH; + } else if (Number.class.isInstance(maxStringSize)) { + return Number.class.cast(maxStringSize).intValue(); + } + return Integer.parseInt(maxStringSize.toString()); } - private JsonGenerator newJsonGeneratorImpl(final Writer writer) { + @Override + public JsonGenerator createGenerator(final Writer writer) { if (pretty) { - return new JsonPrettyGeneratorImpl(writer, cache); + return new JsonPrettyGeneratorImpl(writer, bufferProvider, cache); } - return new JsonGeneratorImpl>(writer, cache); + return new JsonGeneratorImpl(writer, bufferProvider, cache); } + + @Override public JsonGenerator createGenerator(final OutputStream out) { - return createGenerator(new OutputStreamWriter(out)); + if (pretty) { + return new JsonPrettyGeneratorImpl(out, bufferProvider, cache); + } + return new JsonGeneratorImpl(out, bufferProvider, cache); } @Override public JsonGenerator createGenerator(final OutputStream out, final Charset charset) { - return createGenerator(new OutputStreamWriter(out, charset)); + if (pretty) { + return new JsonPrettyGeneratorImpl(out,charset, bufferProvider, cache); + } + return new JsonGeneratorImpl(out,charset, bufferProvider, cache); } @Override public Map getConfigInUse() { - return Collections.unmodifiableMap(config); + return Collections.unmodifiableMap(internalConfig); } } diff --git a/fleece-core/src/main/java/org/apache/fleece/core/JsonGeneratorImpl.java b/fleece-core/src/main/java/org/apache/fleece/core/JsonGeneratorImpl.java index 7a77a11..a1d0a53 100644 --- a/fleece-core/src/main/java/org/apache/fleece/core/JsonGeneratorImpl.java +++ b/fleece-core/src/main/java/org/apache/fleece/core/JsonGeneratorImpl.java @@ -18,144 +18,371 @@ */ package org.apache.fleece.core; -import javax.json.JsonString; -import javax.json.JsonValue; -import javax.json.stream.JsonGenerationException; -import javax.json.stream.JsonGenerator; import java.io.IOException; +import java.io.OutputStream; +import java.io.OutputStreamWriter; import java.io.Serializable; import java.io.Writer; import java.math.BigDecimal; import java.math.BigInteger; +import java.nio.charset.Charset; +import java.util.Iterator; +import java.util.Map; import java.util.concurrent.ConcurrentMap; -public class JsonGeneratorImpl

> implements JsonGenerator, JsonChars, Serializable { - protected static final String START_ARRAY = "["; - protected static final String END_ARRAY = "]"; - protected static final String END_OBJECT = "}"; - protected static final String START_OBJECT = "{"; - protected static final String NULL = "null"; - protected static final String NULL_KEY = "null:"; +import javax.json.JsonArray; +import javax.json.JsonException; +import javax.json.JsonNumber; +import javax.json.JsonObject; +import javax.json.JsonString; +import javax.json.JsonStructure; +import javax.json.JsonValue; +import javax.json.stream.JsonGenerationException; +import javax.json.stream.JsonGenerator; - protected final Writer writer; - protected final P parent; - protected final boolean array; - protected final ConcurrentMap cache; +class JsonGeneratorImpl implements JsonGenerator, JsonChars, Serializable { + private static final Charset UTF8_CHARSET = Charset.forName("UTF-8"); + private final Writer writer; + private final BufferStrategy.BufferProvider bufferProvider; + private final char[] buffer; + private int bufferPos = 0; + //private final ConcurrentMap cache; protected boolean needComma = false; - public JsonGeneratorImpl(final Writer writer, final ConcurrentMap cache) { - this(writer, null, false, cache); + private StructureElement currentStructureElement = null; + private boolean valid = false; + protected int depth = 0; + + //minimal stack implementation + private static final class StructureElement { + final StructureElement previous; + final boolean isArray; + + StructureElement(final StructureElement previous, final boolean isArray) { + super(); + this.previous = previous; + this.isArray = isArray; + } } - public JsonGeneratorImpl(final Writer writer, final P parent, final boolean array, - final ConcurrentMap cache) { + JsonGeneratorImpl(final Writer writer, final BufferStrategy.BufferProvider bufferProvider, + final ConcurrentMap cache) { this.writer = writer; - this.parent = parent; - this.array = array; - this.cache = cache; + //this.cache = cache; + this.buffer = bufferProvider.newBuffer(); + this.bufferProvider = bufferProvider; + } + + JsonGeneratorImpl(final OutputStream out, final BufferStrategy.BufferProvider bufferProvider, + final ConcurrentMap cache) { + this(new OutputStreamWriter(out, UTF8_CHARSET), bufferProvider, cache); } - private void addCommaIfNeeded() { + JsonGeneratorImpl(final OutputStream out, final Charset encoding, final BufferStrategy.BufferProvider bufferProvider, + final ConcurrentMap cache) { + this(new OutputStreamWriter(out, encoding), bufferProvider, cache); + } + + protected void addCommaIfNeeded() { if (needComma) { - try { - writer.write(','); - } catch (final IOException e) { - throw new JsonGenerationException(e.getMessage(), e); - } + justWrite(COMMA_CHAR); needComma = false; } - } - protected JsonGenerator newJsonGenerator(final Writer writer, final P parent, final boolean array) { - return new JsonGeneratorImpl

(writer, parent, array, cache); } - // we cache key only since they are generally fixed - private String key(final String name) { - if (name == null) { - return NULL_KEY; - } - String k = cache.get(name); + //caching currently disabled + //two problems: + // 1) not easy to get the escaped value efficiently wen its streamed and the buffer is full and needs to be flushed + // 2) we have to use a kind of bounded threadsafe map to let the cache not grow indefinitely + private void writeCachedOrEscape(final String name) { + /* String k = cache.get(name); + if (k == null) { - k = '"' + Strings.escape(name) + "\":"; - cache.putIfAbsent(name, k); + + justWrite(QUOTE_CHAR); + int start = bufferPos; + writeEscaped0(name); + int end = bufferPos; + String escaped= get from buffer + --- + //FIXME if buffer is flushed this will not work here + cache.putIfAbsent(name, escaped); + justWrite(QUOTE_CHAR); + justWrite(KEY_SEPARATOR); + }else*/ + { + justWrite(QUOTE_CHAR); + writeEscaped0(name); + justWrite(QUOTE_CHAR); + justWrite(KEY_SEPARATOR); } - return k; + } @Override public JsonGenerator writeStartObject() { - noCheckWriteAndForceComma(START_OBJECT); - return newJsonGenerator(writer, (P) this, false); + + if (currentStructureElement == null && valid) { + throw new JsonGenerationException("Method must not be called more than once in no context"); + } + + if (currentStructureElement != null && !currentStructureElement.isArray) { + throw new JsonGenerationException("Method must not be called within an object context"); + } + + //push upon the stack + if (currentStructureElement == null) { + currentStructureElement = new StructureElement(null, false); + } else { + final StructureElement localStructureElement = new StructureElement(currentStructureElement, false); + currentStructureElement = localStructureElement; + } + + if (!valid) { + valid = true; + } + + noCheckWrite(START_OBJECT_CHAR); + depth++; + return this; } @Override public JsonGenerator writeStartObject(final String name) { - noCheckWriteAndForceComma(key(name) + START_OBJECT); - return newJsonGenerator(writer, (P) this, false); + if (currentStructureElement == null || currentStructureElement.isArray) { + throw new JsonGenerationException("Method must not be called within an array context"); + } + + //push upon the stack + if (currentStructureElement == null) { + currentStructureElement = new StructureElement(null, false); + } else { + final StructureElement localStructureElement = new StructureElement(currentStructureElement, false); + currentStructureElement = localStructureElement; + } + + addCommaIfNeeded(); + writeCachedOrEscape(name); + noCheckWrite(START_OBJECT_CHAR); + depth++; + return this; } @Override public JsonGenerator writeStartArray() { - noCheckWriteAndForceComma(START_ARRAY); - return newJsonGenerator(writer, (P) this, true); + if (currentStructureElement == null && valid) { + throw new JsonGenerationException("Method must not be called more than once in no context"); + } + + if (currentStructureElement != null && !currentStructureElement.isArray) { + throw new JsonGenerationException("Method must not be called within an object context"); + } + + //push upon the stack + if (currentStructureElement == null) { + currentStructureElement = new StructureElement(null, true); + } else { + final StructureElement localStructureElement = new StructureElement(currentStructureElement, true); + currentStructureElement = localStructureElement; + } + + if (!valid) { + valid = true; + } + + noCheckWrite(START_ARRAY_CHAR); + depth++; + return this; } @Override public JsonGenerator writeStartArray(final String name) { - noCheckWriteAndForceComma(key(name) + START_ARRAY); - return newJsonGenerator(writer, (P) this, true); + if (currentStructureElement == null || currentStructureElement.isArray) { + throw new JsonGenerationException("Method must not be called within an array context"); + } + + //push upon the stack + if (currentStructureElement == null) { + currentStructureElement = new StructureElement(null, true); + } else { + final StructureElement localStructureElement = new StructureElement(currentStructureElement, true); + currentStructureElement = localStructureElement; + } + + addCommaIfNeeded(); + writeCachedOrEscape(name); + noCheckWrite(START_ARRAY_CHAR); + depth++; + return this; + } + + private void writeJsonValue(final String name, final JsonValue value) { + if (currentStructureElement != null) { + checkObject(); + } + //TODO check null handling + switch (value.getValueType()) { + case ARRAY: + writeStartArray(name); + final JsonArray array = JsonArray.class.cast(value); + final Iterator ait = array.iterator(); + while (ait.hasNext()) { + write(ait.next()); + } + writeEnd(); + + break; + case OBJECT: + writeStartObject(name); + final JsonObject object = JsonObject.class.cast(value); + final Iterator> oit = object.entrySet().iterator(); + while (oit.hasNext()) { + final Map.Entry keyval = oit.next(); + write(keyval.getKey(), keyval.getValue()); + } + writeEnd(); + + break; + case STRING: + write(name, JsonString.class.cast(value).getString()); + break; + case NUMBER: + //TODO optimize + final JsonNumber number = JsonNumber.class.cast(value); + if (number.isIntegral()) { + write(name, number.longValueExact()); + } else { + write(name, number.bigDecimalValue()); + } + break; + case TRUE: + write(name, true); + break; + case FALSE: + write(name, false); + break; + case NULL: + writeNull(name); + break; + default: + throw new JsonGenerationException("Unknown JsonValue type"); + } + } + + private void writeJsonValue(final JsonValue value) { + if (currentStructureElement != null) { + checkArray(); + } + //TODO check null handling + switch (value.getValueType()) { + case ARRAY: + writeStartArray(); + final JsonArray array = JsonArray.class.cast(value); + final Iterator ait = array.iterator(); + while (ait.hasNext()) { + write(ait.next()); + } + writeEnd(); + + break; + case OBJECT: + writeStartObject(); + final JsonObject object = JsonObject.class.cast(value); + final Iterator> oit = object.entrySet().iterator(); + while (oit.hasNext()) { + final Map.Entry keyval = oit.next(); + write(keyval.getKey(), keyval.getValue()); + } + writeEnd(); + + break; + case STRING: + write(JsonString.class.cast(value).getString()); + break; + case NUMBER: + //TODO optimize + final JsonNumber number = JsonNumber.class.cast(value); + if (number.isIntegral()) { + write(number.longValueExact()); + } else { + write(number.bigDecimalValue()); + } + break; + case TRUE: + write(true); + break; + case FALSE: + write(false); + break; + case NULL: + writeNull(); + break; + default: + throw new JsonGenerationException("Unknown JsonValue type"); + } } @Override public JsonGenerator write(final String name, final JsonValue value) { - if (JsonString.class.isInstance(value)) { - return write(name, value == null ? null : value.toString()); - } - checkObject(); - noCheckWriteAndForceComma(key(name)); - justWrite(value == null ? NULL : value.toString()); + writeJsonValue(name, value); return this; } + @Override public JsonGenerator write(final String name, final String value) { checkObject(); - noCheckWriteAndForceComma(key(name)); - justWrite(value == null ? NULL : "\"" + Strings.escape(value) + "\""); + + addCommaIfNeeded(); + writeCachedOrEscape(name); + + addCommaIfNeeded(); + justWrite(QUOTE_CHAR); + writeEscaped0(value); + justWrite(QUOTE_CHAR); + needComma = true; return this; } @Override public JsonGenerator write(final String name, final BigInteger value) { checkObject(); - noCheckWriteAndForceComma(key(name)); - justWrite(value == null ? NULL : value.toString()); + addCommaIfNeeded(); + writeCachedOrEscape(name); + noCheckWriteAndForceComma(String.valueOf(value)); return this; } @Override public JsonGenerator write(final String name, final BigDecimal value) { checkObject(); - noCheckWriteAndForceComma(key(name)); - justWrite(value == null ? NULL : value.toString()); + addCommaIfNeeded(); + writeCachedOrEscape(name); + noCheckWriteAndForceComma(String.valueOf(value)); return this; } @Override public JsonGenerator write(final String name, final int value) { checkObject(); - noCheckWriteAndForceComma(key(name)); - justWrite(String.valueOf(value)); + addCommaIfNeeded(); + writeCachedOrEscape(name); + addCommaIfNeeded(); + writeInt0(value); + needComma = true; return this; } @Override public JsonGenerator write(final String name, final long value) { checkObject(); - noCheckWriteAndForceComma(key(name)); - justWrite(String.valueOf(value)); + addCommaIfNeeded(); + writeCachedOrEscape(name); + addCommaIfNeeded(); + writeLong0(value); + needComma = true; return this; } @@ -163,47 +390,67 @@ public class JsonGeneratorImpl

> implements JsonGe public JsonGenerator write(final String name, final double value) { checkObject(); checkDoubleRange(value); - noCheckWriteAndForceComma(key(name)); - justWrite(String.valueOf(value)); + addCommaIfNeeded(); + writeCachedOrEscape(name); + noCheckWriteAndForceComma(String.valueOf(value)); return this; } @Override public JsonGenerator write(final String name, final boolean value) { checkObject(); - noCheckWriteAndForceComma(key(name)); - justWrite(String.valueOf(value)); + addCommaIfNeeded(); + writeCachedOrEscape(name); + noCheckWriteAndForceComma(String.valueOf(value)); return this; } @Override public JsonGenerator writeNull(final String name) { checkObject(); - noCheckWriteAndForceComma(key(name)); - justWrite(NULL); + addCommaIfNeeded(); + writeCachedOrEscape(name); + noCheckWriteAndForceComma(NULL); return this; } @Override public JsonGenerator writeEnd() { - needComma = false; - noCheckWriteAndForceComma(array ? END_ARRAY : END_OBJECT); - return parent != null ? parent : this; + if (currentStructureElement == null) { + throw new JsonGenerationException("Method must not be called in no context"); + } + + writeEnd(currentStructureElement.isArray ? END_ARRAY_CHAR : END_OBJECT_CHAR); + + //pop from stack + currentStructureElement = currentStructureElement.previous; + depth--; + + return this; } @Override public JsonGenerator write(final JsonValue value) { - noCheckWriteAndForceComma(String.valueOf(value)); + writeJsonValue(value); + + if (JsonStructure.class.isInstance(value)) { + valid = true; + } return this; } @Override public JsonGenerator write(final String value) { checkArray(); - noCheckWriteAndForceComma(QUOTE+Strings.escape(value)+QUOTE); + addCommaIfNeeded(); + justWrite(QUOTE_CHAR); + writeEscaped0(value); + justWrite(QUOTE_CHAR); + needComma = true; return this; } + @Override public JsonGenerator write(final BigDecimal value) { checkArray(); @@ -223,7 +470,8 @@ public class JsonGeneratorImpl

> implements JsonGe @Override public JsonGenerator write(final int value) { checkArray(); - noCheckWrite(Integer.toString(value)); + addCommaIfNeeded(); + writeInt0(value); needComma = true; return this; } @@ -231,7 +479,8 @@ public class JsonGeneratorImpl

> implements JsonGe @Override public JsonGenerator write(final long value) { checkArray(); - noCheckWrite(Long.toString(value)); + addCommaIfNeeded(); + writeLong0(value); needComma = true; return this; } @@ -263,56 +512,359 @@ public class JsonGeneratorImpl

> implements JsonGe @Override public void close() { + try { - writer.close(); - } catch (final IOException e) { - throw new JsonGenerationException(e.getMessage(), e); + if (currentStructureElement != null || !valid) { + + throw new JsonGenerationException("Invalid json " + currentStructureElement + " " + valid); + } + } finally { + + flushBuffer(); + + try { + writer.close(); + } catch (final IOException e) { + throw new JsonException(e.getMessage(), e); + } + + bufferProvider.release(buffer); } } @Override public void flush() { + + flushBuffer(); + try { writer.flush(); } catch (final IOException e) { - throw new JsonGenerationException(e.getMessage(), e); + throw new JsonException(e.getMessage(), e); } } - protected JsonGenerator noCheckWriteAndForceComma(final String value) { + private JsonGenerator noCheckWriteAndForceComma(final String value) { noCheckWrite(value); needComma = true; return this; } - private void noCheckWrite(String value) { + protected JsonGenerator writeEnd(final char value) { + justWrite(value); + needComma = true; + return this; + } + + protected void noCheckWrite(final String value) { addCommaIfNeeded(); justWrite(value); } - private void justWrite(final String value) { - try { - writer.write(value); - } catch (final IOException e) { - throw new JsonGenerationException(e.getMessage(), e); + protected void noCheckWrite(final char value) { + addCommaIfNeeded(); + justWrite(value); + } + + private void flushBuffer() { + + if (bufferPos > 0) { + + try { + writer.write(buffer, 0, bufferPos); + bufferPos = 0; + } catch (final IOException e) { + throw new JsonException(e.getMessage(), e); + } + + } + } + + private void writeEscaped0(final String value) { + int len = 0; + if (value == null || (len = value.length()) == 0) { + return; + } + + for (int i = 0; i < value.length(); i++) { + char c = value.charAt(i); + + while (c != ESCAPE_CHAR && c != QUOTE_CHAR && c >= SPACE) { + + //read fast + justWrite(c); + + if (i >= len - 1) { + return; + } + + i++; + c = value.charAt(i); + } + + switch (c) { + case QUOTE_CHAR: + case ESCAPE_CHAR: + justWrite(ESCAPE_CHAR); + justWrite(c); + break; + default: + if (c < SPACE) { + switch (c) { + case EOL: + justWrite("\\n"); + break; + case '\r': + justWrite("\\r"); + break; + case '\t': + justWrite("\\t"); + break; + case '\b': + justWrite("\\b"); + break; + case '\f': + justWrite("\\f"); + break; + default: + justWrite(toUnicode(c)); + } + } else if ((c >= '\u0080' && c < '\u00a0') || (c >= '\u2000' && c < '\u2100')) { + justWrite(toUnicode(c)); + } else { + justWrite(c); + } + } + } + + } + + private static final String UNICODE_PREFIX = "\\u"; + private static final String UNICODE_PREFIX_HELPER = "000"; + + private static String toUnicode(final char c) { + + final String hex = UNICODE_PREFIX_HELPER + Integer.toHexString(c); + final String s = UNICODE_PREFIX + hex.substring(hex.length() - 4); + + return s; + } + + protected void justWrite(final char[] chars) { + + if (bufferPos + chars.length >= buffer.length) { + + int start = 0; + int len = buffer.length - bufferPos; + + while (true) { + int end = start + len; + if (end > chars.length) { + end = chars.length; + } + + System.arraycopy(chars, start, buffer, bufferPos, end - start); + + bufferPos += (end - start); + start += (len); + + if (start >= chars.length) { + return; + } + + if (bufferPos >= buffer.length) { + flushBuffer(); + len = buffer.length; + } + + } + + } else { + //fits completely into the buffer + System.arraycopy(chars, 0, buffer, bufferPos, chars.length); + bufferPos += chars.length; } + } + protected void justWrite(final String value) { + final int valueLength = value.length(); + + if (bufferPos + valueLength >= buffer.length) { + + int start = 0; + int len = buffer.length - bufferPos; + + while (true) { + int end = start + len; + if (end > valueLength) { + end = valueLength; + } + + value.getChars(start, end, buffer, bufferPos); + + bufferPos += (end - start); + start += (len); + + if (start >= valueLength) { + return; + } + + if (bufferPos >= buffer.length) { + flushBuffer(); + len = buffer.length; + } + + } + + } else { + //fits completely into the buffer + value.getChars(0, valueLength, buffer, bufferPos); + bufferPos += valueLength; + } + + } + + protected void justWrite(final char value) { + + if (bufferPos >= buffer.length) { + flushBuffer(); + } + + buffer[bufferPos++] = value; + + } + private void checkObject() { - if (array) { + if (currentStructureElement == null || currentStructureElement.isArray) { throw new JsonGenerationException("write(name, param) is only valid in objects"); } } private void checkArray() { - if (!array) { + if (currentStructureElement == null || !currentStructureElement.isArray) { throw new JsonGenerationException("write(param) is only valid in arrays"); } } private static void checkDoubleRange(final double value) { if (Double.isInfinite(value) || Double.isNaN(value)) { - throw new NumberFormatException("double can't be infinite and NaN"); + throw new NumberFormatException("double can't be infinite or NaN"); + } + } + + + //unopitimized, see below + private void writeLong0(final long i) { + + justWrite(String.valueOf(i)); + } + + //unopitimized, see below + private void writeInt0(final int i) { + + justWrite(String.valueOf(i)); + } + + //optimized number optimizations +/* + private void writeLong0(final long i) { + if (i == Long.MIN_VALUE) { + justWrite("-9223372036854775808"); + return; + } + final int size = (i < 0) ? stringSize(-i) + 1 : stringSize(i); + final char[] buf = new char[size]; + getChars(i, size, buf); + justWrite(buf); + } + + private void writeInt0(final int i) { + if (i == Integer.MIN_VALUE) { + justWrite("-2147483648"); + return; + } + final int size = (i < 0) ? stringSize(-i) + 1 : stringSize(i); + final char[] buf = new char[size]; + getChars(i, size, buf); + justWrite(buf); + } + + private final static char[] DIGIT_TENS = { '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '1', '1', '1', '1', '1', '1', '1', '1', + '1', '1', '2', '2', '2', '2', '2', '2', '2', '2', '2', '2', '3', '3', '3', '3', '3', '3', '3', '3', '3', '3', '4', '4', '4', + '4', '4', '4', '4', '4', '4', '4', '5', '5', '5', '5', '5', '5', '5', '5', '5', '5', '6', '6', '6', '6', '6', '6', '6', '6', + '6', '6', '7', '7', '7', '7', '7', '7', '7', '7', '7', '7', '8', '8', '8', '8', '8', '8', '8', '8', '8', '8', '9', '9', '9', + '9', '9', '9', '9', '9', '9', '9', }; + + private final static char[] DIGIT_ONES = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', + '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', + '3', '4', '5', '6', '7', '8', '9', }; + + private final static char[] DIGITS = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', + 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z' }; + + // Requires positive x + private static int stringSize(final long x) { + long p = 10; + for (int i = 1; i < 19; i++) { + if (x < p) { + return i; + } + p = 10 * p; + } + return 19; + } + + private static void getChars(long i, final int index, final char[] buf) { + long q; + int r; + int charPos = index; + char sign = 0; + + if (i < 0) { + sign = '-'; + i = -i; + } + + // Get 2 digits/iteration using longs until quotient fits into an int + while (i > Integer.MAX_VALUE) { + q = i / 100; + // really: r = i - (q * 100); + r = (int) (i - ((q << 6) + (q << 5) + (q << 2))); + i = q; + buf[--charPos] = DIGIT_ONES[r]; + buf[--charPos] = DIGIT_TENS[r]; + } + + // Get 2 digits/iteration using ints + int q2; + int i2 = (int) i; + while (i2 >= 65536) { + q2 = i2 / 100; + // really: r = i2 - (q * 100); + r = i2 - ((q2 << 6) + (q2 << 5) + (q2 << 2)); + i2 = q2; + buf[--charPos] = DIGIT_ONES[r]; + buf[--charPos] = DIGIT_TENS[r]; + } + + // Fall thru to fast mode for smaller numbers + // assert(i2 <= 65536, i2); + for (;;) { + q2 = (i2 * 52429) >>> (16 + 3); + r = i2 - ((q2 << 3) + (q2 << 1)); // r = i2-(q2*10) ... + buf[--charPos] = DIGITS[r]; + i2 = q2; + if (i2 == 0) { + break; + } + } + if (sign != 0) { + buf[--charPos] = sign; } } +*/ + } diff --git a/fleece-core/src/main/java/org/apache/fleece/core/JsonInMemoryParser.java b/fleece-core/src/main/java/org/apache/fleece/core/JsonInMemoryParser.java index 76f01d6..a6b8276 100644 --- a/fleece-core/src/main/java/org/apache/fleece/core/JsonInMemoryParser.java +++ b/fleece-core/src/main/java/org/apache/fleece/core/JsonInMemoryParser.java @@ -18,31 +18,33 @@ */ package org.apache.fleece.core; +import java.math.BigDecimal; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + import javax.json.JsonArray; import javax.json.JsonNumber; import javax.json.JsonObject; import javax.json.JsonString; import javax.json.JsonValue; import javax.json.stream.JsonLocation; -import java.math.BigDecimal; -import java.util.Iterator; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; +import javax.json.stream.JsonParser; // we don't use visitor pattern to ensure we work with other impl of JsonObject and JsonArray -public class JsonInMemoryParser implements EscapedStringAwareJsonParser { +class JsonInMemoryParser implements JsonParser { private final Iterator iterator; private Entry next = null; - public JsonInMemoryParser(final JsonObject object) { + JsonInMemoryParser(final JsonObject object) { final List events = new LinkedList(); generateObjectEvents(events, object); iterator = events.iterator(); } - public JsonInMemoryParser(final JsonArray array) { + JsonInMemoryParser(final JsonArray array) { final List events = new LinkedList(); generateArrayEvents(events, array); iterator = events.iterator(); @@ -51,7 +53,7 @@ public class JsonInMemoryParser implements EscapedStringAwareJsonParser { private static void generateObjectEvents(final List events, final JsonObject object) { events.add(new Entry(Event.START_OBJECT, object)); for (final Map.Entry entry : object.entrySet()) { - events.add(new Entry(Event.KEY_NAME, new JsonStringImpl(entry.getKey()))); + events.add(new Entry(Event.KEY_NAME, new JsonStringImpl(entry.getKey()))); //TODO check perf final JsonValue value = entry.getValue(); addValueEvents(events, value); } @@ -102,7 +104,7 @@ public class JsonInMemoryParser implements EscapedStringAwareJsonParser { if (JsonObject.class.isInstance(next.value) || JsonArray.class.isInstance(next.value)) { throw new IllegalStateException("String is for numbers and strings"); } - return getEscapedString(); + return JsonString.class.cast(next.value).getString(); } @Override @@ -139,12 +141,7 @@ public class JsonInMemoryParser implements EscapedStringAwareJsonParser { @Override public JsonLocation getLocation() { // no location for in memory parsers - return new JsonLocationImpl(-1, -1, -1); - } - - @Override - public String getEscapedString() { - return JsonValue.class.cast(next.value).toString(); + return JsonLocationImpl.UNKNOW_LOCATION; } @Override diff --git a/fleece-core/src/main/java/org/apache/fleece/core/JsonLocationImpl.java b/fleece-core/src/main/java/org/apache/fleece/core/JsonLocationImpl.java index c1c094e..5c36d58 100644 --- a/fleece-core/src/main/java/org/apache/fleece/core/JsonLocationImpl.java +++ b/fleece-core/src/main/java/org/apache/fleece/core/JsonLocationImpl.java @@ -18,11 +18,11 @@ */ package org.apache.fleece.core; -import javax.json.stream.JsonLocation; - import java.io.Serializable; -public class JsonLocationImpl implements JsonLocation, Serializable { +import javax.json.stream.JsonLocation; + +final class JsonLocationImpl implements JsonLocation, Serializable { public static final JsonLocation UNKNOW_LOCATION = new JsonLocationImpl(-1, -1, -1); @@ -30,7 +30,7 @@ public class JsonLocationImpl implements JsonLocation, Serializable { private final long columnNumber; private final long streamOffset; - public JsonLocationImpl(final long lineNumber, final long columnNumber, final long streamOffset) { + JsonLocationImpl(final long lineNumber, final long columnNumber, final long streamOffset) { this.lineNumber = lineNumber; this.columnNumber = columnNumber; this.streamOffset = streamOffset; diff --git a/fleece-core/src/main/java/org/apache/fleece/core/JsonLongImpl.java b/fleece-core/src/main/java/org/apache/fleece/core/JsonLongImpl.java index b15a8b3..9928a42 100644 --- a/fleece-core/src/main/java/org/apache/fleece/core/JsonLongImpl.java +++ b/fleece-core/src/main/java/org/apache/fleece/core/JsonLongImpl.java @@ -18,14 +18,15 @@ */ package org.apache.fleece.core; -import javax.json.JsonNumber; import java.math.BigDecimal; import java.math.BigInteger; -public class JsonLongImpl implements JsonNumber { +import javax.json.JsonNumber; + +final class JsonLongImpl implements JsonNumber { private final long value; - public JsonLongImpl(final long value) { + JsonLongImpl(final long value) { this.value = value; } diff --git a/fleece-core/src/main/java/org/apache/fleece/core/JsonNumberImpl.java b/fleece-core/src/main/java/org/apache/fleece/core/JsonNumberImpl.java index c992a9f..f1b388a 100644 --- a/fleece-core/src/main/java/org/apache/fleece/core/JsonNumberImpl.java +++ b/fleece-core/src/main/java/org/apache/fleece/core/JsonNumberImpl.java @@ -18,15 +18,20 @@ */ package org.apache.fleece.core; -import javax.json.JsonNumber; import java.math.BigDecimal; import java.math.BigInteger; -public class JsonNumberImpl implements JsonNumber { +import javax.json.JsonNumber; + +final class JsonNumberImpl implements JsonNumber { private final BigDecimal value; private Integer hashCode = null; - public JsonNumberImpl(final BigDecimal decimal) { + JsonNumberImpl(final BigDecimal decimal) { + if(decimal == null) { + throw new NullPointerException("decimal must not be null"); + } + this.value = decimal; } @@ -87,10 +92,12 @@ public class JsonNumberImpl implements JsonNumber { @Override public int hashCode() { - if (hashCode == null) { - hashCode = value.hashCode(); + Integer h=hashCode; + if (h == null) { + h = value.hashCode(); + hashCode=h; } - return hashCode; + return h; } @Override diff --git a/fleece-core/src/main/java/org/apache/fleece/core/JsonObjectBuilderImpl.java b/fleece-core/src/main/java/org/apache/fleece/core/JsonObjectBuilderImpl.java index f76c26e..a61c9a3 100644 --- a/fleece-core/src/main/java/org/apache/fleece/core/JsonObjectBuilderImpl.java +++ b/fleece-core/src/main/java/org/apache/fleece/core/JsonObjectBuilderImpl.java @@ -18,85 +18,114 @@ */ package org.apache.fleece.core; +import java.io.Serializable; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.Map; + import javax.json.JsonArrayBuilder; import javax.json.JsonObject; import javax.json.JsonObjectBuilder; import javax.json.JsonValue; -import java.io.Serializable; -import java.math.BigDecimal; -import java.math.BigInteger; -public class JsonObjectBuilderImpl implements JsonObjectBuilder, Serializable { - private final JsonObjectImpl delegate = new JsonObjectImpl(); +class JsonObjectBuilderImpl implements JsonObjectBuilder, Serializable { + private Map tmpMap; @Override public JsonObjectBuilder add(final String name, final JsonValue value) { - delegate.putInternal(name, value); + putValue(name, value); return this; } @Override public JsonObjectBuilder add(final String name, final String value) { - delegate.putInternal(name, new JsonStringImpl(value)); + putValue(name, new JsonStringImpl(value)); return this; } @Override public JsonObjectBuilder add(final String name, final BigInteger value) { - delegate.putInternal(name, new JsonNumberImpl(new BigDecimal(value))); + putValue(name, new JsonNumberImpl(new BigDecimal(value))); return this; } @Override public JsonObjectBuilder add(final String name, final BigDecimal value) { - delegate.putInternal(name, new JsonNumberImpl(value)); + putValue(name, new JsonNumberImpl(value)); return this; } @Override public JsonObjectBuilder add(final String name, final int value) { - delegate.putInternal(name, new JsonLongImpl(value)); + putValue(name, new JsonLongImpl(value)); return this; } @Override public JsonObjectBuilder add(final String name, final long value) { - delegate.putInternal(name, new JsonLongImpl(value)); + putValue(name, new JsonLongImpl(value)); return this; } @Override public JsonObjectBuilder add(final String name, final double value) { - delegate.putInternal(name, new JsonDoubleImpl(value)); + putValue(name, new JsonDoubleImpl(value)); return this; } @Override public JsonObjectBuilder add(final String name, final boolean value) { - delegate.putInternal(name, value ? JsonValue.TRUE : JsonValue.FALSE); + putValue(name, value ? JsonValue.TRUE : JsonValue.FALSE); return this; } @Override public JsonObjectBuilder addNull(final String name) { - delegate.putInternal(name, JsonValue.NULL); + putValue(name, JsonValue.NULL); return this; } @Override public JsonObjectBuilder add(final String name, final JsonObjectBuilder builder) { - delegate.putInternal(name, builder.build()); + putValue(name, builder.build()); return this; } @Override public JsonObjectBuilder add(final String name, final JsonArrayBuilder builder) { - delegate.putInternal(name, builder.build()); + putValue(name, builder.build()); return this; } + + private void putValue(String name, JsonValue value){ + if(name == null || value == null) { + throw npe(); + } + + if(tmpMap==null){ + tmpMap=new LinkedHashMap(); + } + + tmpMap.put(name, value); + } + + private static NullPointerException npe() { + return new NullPointerException("name or value/builder must not be null"); + } @Override public JsonObject build() { - return delegate; + + if(tmpMap==null) { + return new JsonObjectImpl(Collections.EMPTY_MAP); + } else { + Map dump = (Collections.unmodifiableMap(tmpMap)); + tmpMap=null; + return new JsonObjectImpl(dump); + } + + } } diff --git a/fleece-core/src/main/java/org/apache/fleece/core/JsonObjectImpl.java b/fleece-core/src/main/java/org/apache/fleece/core/JsonObjectImpl.java index c08f523..a71e42c 100644 --- a/fleece-core/src/main/java/org/apache/fleece/core/JsonObjectImpl.java +++ b/fleece-core/src/main/java/org/apache/fleece/core/JsonObjectImpl.java @@ -18,26 +18,35 @@ */ package org.apache.fleece.core; +import java.io.Serializable; +import java.util.AbstractMap; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; + import javax.json.JsonArray; import javax.json.JsonNumber; import javax.json.JsonObject; import javax.json.JsonString; import javax.json.JsonValue; -import java.io.Serializable; -import java.util.Iterator; -import java.util.LinkedHashMap; -import java.util.Map; -public class JsonObjectImpl extends LinkedHashMap implements JsonObject, Serializable { +public final class JsonObjectImpl extends AbstractMap implements JsonObject, Serializable { private Integer hashCode = null; + private final Map unmodifieableBackingMap; private T value(final String name, final Class clazz) { - final Object v = get(name); + final Object v = unmodifieableBackingMap.get(name); if (v != null) { return clazz.cast(v); } throw new NullPointerException("no mapping for " + name); } + + public JsonObjectImpl(Map backingMap) { + super(); + this.unmodifieableBackingMap = backingMap; + } + @Override public JsonArray getJsonArray(final String name) { @@ -115,7 +124,7 @@ public class JsonObjectImpl extends LinkedHashMap implements @Override public String toString() { final StringBuilder builder = new StringBuilder("{"); - final Iterator> it = entrySet().iterator(); + final Iterator> it = unmodifieableBackingMap.entrySet().iterator(); boolean hasNext = it.hasNext(); while (hasNext) { final Map.Entry entry = it.next(); @@ -124,7 +133,7 @@ public class JsonObjectImpl extends LinkedHashMap implements final JsonValue value = entry.getValue(); if (JsonString.class.isInstance(value)) { - builder.append(JsonChars.QUOTE).append(value.toString()).append(JsonChars.QUOTE); + builder.append(value.toString()); } else { builder.append(value != JsonValue.NULL ? value.toString() : JsonChars.NULL); } @@ -139,42 +148,20 @@ public class JsonObjectImpl extends LinkedHashMap implements @Override public boolean equals(final Object obj) { - return JsonObject.class.isInstance(obj) && super.equals(obj); + return JsonObjectImpl.class.isInstance(obj) && unmodifieableBackingMap.equals(JsonObjectImpl.class.cast(obj).unmodifieableBackingMap); } - @Override - public void clear() { - throw immutable(); - } - - @Override - public JsonValue put(String key, JsonValue value) { - throw immutable(); - } - - @Override - public void putAll(Map jsonObject) { - throw immutable(); - } - - @Override - public JsonValue remove(Object key) { - throw immutable(); - } - - private static UnsupportedOperationException immutable() { - throw new UnsupportedOperationException("JsonObject is immutable. You can create another one thanks to JsonObjectBuilder"); - } - - public void putInternal(final String name, final JsonValue value) { - super.put(name, value); - } @Override public int hashCode() { if (hashCode == null) { - hashCode = super.hashCode(); + hashCode = unmodifieableBackingMap.hashCode(); } return hashCode; } + + @Override + public Set> entrySet() { + return unmodifieableBackingMap.entrySet(); + } } diff --git a/fleece-core/src/main/java/org/apache/fleece/core/JsonParserFactoryImpl.java b/fleece-core/src/main/java/org/apache/fleece/core/JsonParserFactoryImpl.java index 505542e..f43ceee 100644 --- a/fleece-core/src/main/java/org/apache/fleece/core/JsonParserFactoryImpl.java +++ b/fleece-core/src/main/java/org/apache/fleece/core/JsonParserFactoryImpl.java @@ -18,31 +18,49 @@ */ package org.apache.fleece.core; -import javax.json.JsonArray; -import javax.json.JsonObject; -import javax.json.stream.JsonParser; -import javax.json.stream.JsonParserFactory; import java.io.InputStream; import java.io.Reader; import java.io.Serializable; import java.nio.charset.Charset; import java.util.Collections; +import java.util.HashMap; import java.util.Locale; import java.util.Map; -public class JsonParserFactoryImpl implements JsonParserFactory, Serializable { +import javax.json.JsonArray; +import javax.json.JsonObject; +import javax.json.stream.JsonParser; +import javax.json.stream.JsonParserFactory; + +class JsonParserFactoryImpl implements JsonParserFactory, Serializable { public static final String BUFFER_STRATEGY = "org.apache.fleece.buffer-strategy"; public static final String MAX_STRING_LENGTH = "org.apache.fleece.max-string-length"; public static final String BUFFER_LENGTH = "org.apache.fleece.default-char-buffer"; - public static final int DEFAULT_MAX_SIZE = Integer.getInteger(MAX_STRING_LENGTH, 8192*32); - - private final Map config; + public static final int DEFAULT_MAX_SIZE = Integer.getInteger(MAX_STRING_LENGTH, 8192*32); //TODO check default string length/buffer size + + private final Map internalConfig = new HashMap(); + private static final String[] SUPPORTED_CONFIG_KEYS = new String[] { + + BUFFER_STRATEGY, MAX_STRING_LENGTH, BUFFER_LENGTH + + }; + private final int maxSize; private final BufferStrategy.BufferProvider bufferProvider; private final BufferStrategy.BufferProvider valueBufferProvider; - public JsonParserFactoryImpl(final Map config) { - this.config = config; + JsonParserFactoryImpl(final Map config) { + + + if(config != null) { + + for (String configKey : SUPPORTED_CONFIG_KEYS) { + if(config.containsKey(configKey)) { + internalConfig.put(configKey, config.get(configKey)); + } + } + } + final int bufferSize = getInt(BUFFER_LENGTH); if (bufferSize <= 0) { @@ -55,10 +73,7 @@ public class JsonParserFactoryImpl implements JsonParserFactory, Serializable { } private BufferStrategy getBufferProvider() { - if(config==null) { - return BufferStrategy.QUEUE; - } - final Object name = config.get(BUFFER_STRATEGY); + final Object name = internalConfig.get(BUFFER_STRATEGY); if (name != null) { return BufferStrategy.valueOf(name.toString().toUpperCase(Locale.ENGLISH)); } @@ -66,10 +81,7 @@ public class JsonParserFactoryImpl implements JsonParserFactory, Serializable { } private int getInt(final String key) { - if(config==null) { - return DEFAULT_MAX_SIZE; - } - final Object maxStringSize = config.get(key); + final Object maxStringSize = internalConfig.get(key); if (maxStringSize == null) { return DEFAULT_MAX_SIZE; } else if (Number.class.isInstance(maxStringSize)) { @@ -78,17 +90,17 @@ public class JsonParserFactoryImpl implements JsonParserFactory, Serializable { return Integer.parseInt(maxStringSize.toString()); } - private EscapedStringAwareJsonParser getDefaultJsonParserImpl(final InputStream in) { + private JsonParser getDefaultJsonParserImpl(final InputStream in) { //UTF Auto detection RFC 4627 return new JsonStreamParserImpl(in, maxSize, bufferProvider, valueBufferProvider); } - private EscapedStringAwareJsonParser getDefaultJsonParserImpl(final InputStream in, final Charset charset) { + private JsonParser getDefaultJsonParserImpl(final InputStream in, final Charset charset) { //use provided charset return new JsonStreamParserImpl(in, charset, maxSize, bufferProvider, valueBufferProvider); } - private EscapedStringAwareJsonParser getDefaultJsonParserImpl(final Reader in) { + private JsonParser getDefaultJsonParserImpl(final Reader in) { //no charset necessary return new JsonStreamParserImpl(in, maxSize, bufferProvider, valueBufferProvider); } @@ -120,18 +132,18 @@ public class JsonParserFactoryImpl implements JsonParserFactory, Serializable { @Override public Map getConfigInUse() { - return Collections.unmodifiableMap(config); + return Collections.unmodifiableMap(internalConfig); } - public EscapedStringAwareJsonParser createInternalParser(final InputStream in) { + public JsonParser createInternalParser(final InputStream in) { return getDefaultJsonParserImpl(in); } - public EscapedStringAwareJsonParser createInternalParser(final InputStream in, final Charset charset) { + public JsonParser createInternalParser(final InputStream in, final Charset charset) { return getDefaultJsonParserImpl(in, charset); } - public EscapedStringAwareJsonParser createInternalParser(final Reader reader) { + public JsonParser createInternalParser(final Reader reader) { return getDefaultJsonParserImpl(reader); } } diff --git a/fleece-core/src/main/java/org/apache/fleece/core/JsonPrettyGeneratorImpl.java b/fleece-core/src/main/java/org/apache/fleece/core/JsonPrettyGeneratorImpl.java index ff965bd..2e72f20 100644 --- a/fleece-core/src/main/java/org/apache/fleece/core/JsonPrettyGeneratorImpl.java +++ b/fleece-core/src/main/java/org/apache/fleece/core/JsonPrettyGeneratorImpl.java @@ -18,108 +18,288 @@ */ package org.apache.fleece.core; -import javax.json.stream.JsonGenerationException; -import javax.json.stream.JsonGenerator; -import java.io.IOException; +import java.io.OutputStream; import java.io.Writer; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.nio.charset.Charset; import java.util.concurrent.ConcurrentMap; -public class JsonPrettyGeneratorImpl extends JsonGeneratorImpl { - private static final String DEFAULT_INDENTATION = " "; +import javax.json.JsonValue; +import javax.json.stream.JsonGenerator; - private String indent; // should be final but writeEnd needs it not final, we could change write() to support indent +final class JsonPrettyGeneratorImpl extends JsonGeneratorImpl { + private static final String DEFAULT_INDENTATION = " "; + private final String indent; - public JsonPrettyGeneratorImpl(final Writer writer, final ConcurrentMap cache) { - this(writer, null, false, "", cache); + public JsonPrettyGeneratorImpl(final Writer writer, final BufferStrategy.BufferProvider bufferProvider, + final ConcurrentMap cache) { + super(writer, bufferProvider, cache); + indent = DEFAULT_INDENTATION; } - public JsonPrettyGeneratorImpl(final Writer writer, final JsonPrettyGeneratorImpl parent, - final boolean array, final String prefix, - final ConcurrentMap cache) { - super(writer, parent, array, cache); - this.indent = prefix; + public JsonPrettyGeneratorImpl(final OutputStream out, final Charset encoding, + final BufferStrategy.BufferProvider bufferProvider, final ConcurrentMap cache) { + super(out, encoding, bufferProvider, cache); + indent = DEFAULT_INDENTATION; } - private void addCommaIfNeeded() { - if (needComma) { - try { - writer.write(','); - ln(); - } catch (final IOException e) { - throw new JsonGenerationException(e.getMessage(), e); - } - needComma = false; - } + public JsonPrettyGeneratorImpl(final OutputStream out, final BufferStrategy.BufferProvider bufferProvider, + final ConcurrentMap cache) { + super(out, bufferProvider, cache); + indent = DEFAULT_INDENTATION; } - private void ln() { - try { - writer.write('\n'); - } catch (final IOException e) { - throw new JsonGenerationException(e.getMessage(), e); - } + private void writeEOL() { + justWrite(EOL); } - private String nextIndent() { - return indent != null ? indent + DEFAULT_INDENTATION : null; + private void writeIndent(final int correctionOffset) { + for (int i = 0; i < depth + correctionOffset; i++) { + justWrite(indent); + } } @Override - protected JsonGenerator newJsonGenerator(final Writer writer, final JsonPrettyGeneratorImpl parent, final boolean array) { - return new JsonPrettyGeneratorImpl(writer, parent, array, nextIndent(), cache); + protected void addCommaIfNeeded() { + if (needComma) { + justWrite(COMMA_CHAR); + writeEOL(); + writeIndent(0); + needComma = false; + } + } @Override public JsonGenerator writeStartObject() { - final JsonGenerator generator = super.writeStartObject(); - ln(); - return generator; + if (depth > 0 && !needComma) { + writeEOL(); + writeIndent(0); + } + return super.writeStartObject(); + } @Override public JsonGenerator writeStartObject(final String name) { - final JsonGenerator generator = super.writeStartObject(name); - ln(); - return generator; + if (!needComma) { + writeEOL(); + writeIndent(0); + } + return super.writeStartObject(name); + } @Override public JsonGenerator writeStartArray() { - final JsonGenerator generator = super.writeStartArray(); - ln(); - return generator; + if (depth > 0 && !needComma) { + writeEOL(); + writeIndent(0); + } + return super.writeStartArray(); + } @Override public JsonGenerator writeStartArray(final String name) { - final JsonGenerator generator = super.writeStartArray(name); - ln(); - return generator; + if (!needComma) { + writeEOL(); + writeIndent(0); + } + return super.writeStartArray(name); + } + //end + @Override public JsonGenerator writeEnd() { - ln(); - needComma = false; - final String thisIndent = indent; - indent = parent != null ? parent.indent : indent; - noCheckWriteAndForceComma(array ? "]" : "}"); - indent = thisIndent; - return parent != null ? parent : this; + writeEOL(); + writeIndent(-1); + return super.writeEnd(); + + } + + //normal + + @Override + public JsonGenerator write(final String name, final JsonValue value) { + if (!needComma) { + writeEOL(); + writeIndent(0); + } + + return super.write(name, value); + + } + + @Override + public JsonGenerator write(final String name, final String value) { + if (!needComma) { + writeEOL(); + writeIndent(0); + } + return super.write(name, value); + } + + @Override + public JsonGenerator write(final String name, final BigInteger value) { + if (!needComma) { + writeEOL(); + writeIndent(0); + } + return super.write(name, value); + + } + + @Override + public JsonGenerator write(final String name, final BigDecimal value) { + if (!needComma) { + writeEOL(); + writeIndent(0); + } + return super.write(name, value); + + } + + @Override + public JsonGenerator write(final String name, final int value) { + if (!needComma) { + writeEOL(); + writeIndent(0); + } + return super.write(name, value); + + } + + @Override + public JsonGenerator write(final String name, final long value) { + if (!needComma) { + writeEOL(); + writeIndent(0); + } + return super.write(name, value); + + } + + @Override + public JsonGenerator write(final String name, final double value) { + if (!needComma) { + writeEOL(); + writeIndent(0); + } + return super.write(name, value); + + } + + @Override + public JsonGenerator write(final String name, final boolean value) { + if (!needComma) { + writeEOL(); + writeIndent(0); + } + return super.write(name, value); + + } + + @Override + public JsonGenerator writeNull(final String name) { + if (!needComma) { + writeEOL(); + writeIndent(0); + } + return super.writeNull(name); + } @Override - protected JsonGenerator noCheckWriteAndForceComma(final String value) { - addCommaIfNeeded(); - try { - if (indent != null) { - writer.write(indent); - } - writer.write(value); - } catch (final IOException e) { - throw new JsonGenerationException(e.getMessage(), e); + public JsonGenerator write(final JsonValue value) { + if (!needComma) { + writeEOL(); + writeIndent(0); } - needComma = true; - return this; + return super.write(value); + + } + + @Override + public JsonGenerator write(final String value) { + if (!needComma) { + writeEOL(); + writeIndent(0); + } + return super.write(value); + + } + + @Override + public JsonGenerator write(final BigDecimal value) { + if (!needComma) { + writeEOL(); + writeIndent(0); + } + return super.write(value); + } + + @Override + public JsonGenerator write(final BigInteger value) { + if (!needComma) { + writeEOL(); + writeIndent(0); + } + return super.write(value); + + } + + @Override + public JsonGenerator write(final int value) { + if (!needComma) { + writeEOL(); + writeIndent(0); + } + return super.write(value); + + } + + @Override + public JsonGenerator write(final long value) { + if (!needComma) { + writeEOL(); + writeIndent(0); + } + return super.write(value); + + } + + @Override + public JsonGenerator write(final double value) { + if (!needComma) { + writeEOL(); + writeIndent(0); + } + return super.write(value); + + } + + @Override + public JsonGenerator write(final boolean value) { + if (!needComma) { + writeEOL(); + writeIndent(0); + } + return super.write(value); + + } + + @Override + public JsonGenerator writeNull() { + if (!needComma) { + writeEOL(); + writeIndent(0); + } + return super.writeNull(); + } + } diff --git a/fleece-core/src/main/java/org/apache/fleece/core/JsonProviderImpl.java b/fleece-core/src/main/java/org/apache/fleece/core/JsonProviderImpl.java index 1e432ce..6245030 100644 --- a/fleece-core/src/main/java/org/apache/fleece/core/JsonProviderImpl.java +++ b/fleece-core/src/main/java/org/apache/fleece/core/JsonProviderImpl.java @@ -18,6 +18,16 @@ */ package org.apache.fleece.core; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.Reader; +import java.io.Serializable; +import java.io.Writer; +import java.nio.charset.Charset; +import java.util.Collections; +import java.util.Map; + import javax.json.JsonArrayBuilder; import javax.json.JsonBuilderFactory; import javax.json.JsonObjectBuilder; @@ -30,18 +40,10 @@ import javax.json.stream.JsonGenerator; import javax.json.stream.JsonGeneratorFactory; import javax.json.stream.JsonParser; import javax.json.stream.JsonParserFactory; -import java.io.InputStream; -import java.io.OutputStream; -import java.io.OutputStreamWriter; -import java.io.Reader; -import java.io.Serializable; -import java.io.Writer; -import java.util.Collections; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; public class JsonProviderImpl extends JsonProvider implements Serializable { private static final JsonProvider DELEGATE = new JsonProviderDelegate(); + @Override public JsonParser createParser(final Reader reader) { @@ -118,9 +120,11 @@ public class JsonProviderImpl extends JsonProvider implements Serializable { return DELEGATE.createBuilderFactory(stringMap); } - private static class JsonProviderDelegate extends JsonProvider { + static class JsonProviderDelegate extends JsonProvider { private final JsonReaderFactory readerFactory = new JsonReaderFactoryImpl(Collections.emptyMap()); private final JsonParserFactory parserFactory = new JsonParserFactoryImpl(Collections.emptyMap()); + private final JsonGeneratorFactory generatorFactory = new JsonGeneratorFactoryImpl(Collections.emptyMap()); + private static final Charset UTF8_CHARSET = Charset.forName("UTF-8"); @Override public JsonParser createParser(final InputStream in) { @@ -152,14 +156,15 @@ public class JsonProviderImpl extends JsonProvider implements Serializable { return new JsonReaderFactoryImpl(config); } + @SuppressWarnings("unchecked") @Override public JsonGenerator createGenerator(final Writer writer) { - return new JsonGeneratorFacade(new JsonGeneratorImpl(writer, new ConcurrentHashMap())); + return generatorFactory.createGenerator(writer); } @Override public JsonGenerator createGenerator(final OutputStream out) { - return createGenerator(new OutputStreamWriter(out)); + return generatorFactory.createGenerator(out); } @Override @@ -174,7 +179,7 @@ public class JsonProviderImpl extends JsonProvider implements Serializable { @Override public JsonWriter createWriter(final OutputStream out) { - return createWriter(new OutputStreamWriter(out)); + return createWriter(new OutputStreamWriter(out, UTF8_CHARSET)); } @Override diff --git a/fleece-core/src/main/java/org/apache/fleece/core/JsonReaderFactoryImpl.java b/fleece-core/src/main/java/org/apache/fleece/core/JsonReaderFactoryImpl.java index 1d04f26..6f0e063 100644 --- a/fleece-core/src/main/java/org/apache/fleece/core/JsonReaderFactoryImpl.java +++ b/fleece-core/src/main/java/org/apache/fleece/core/JsonReaderFactoryImpl.java @@ -23,19 +23,31 @@ import java.io.Reader; import java.io.Serializable; import java.nio.charset.Charset; import java.util.Collections; +import java.util.HashMap; import java.util.Map; import javax.json.JsonReader; import javax.json.JsonReaderFactory; -@SuppressWarnings("unused") -public class JsonReaderFactoryImpl implements JsonReaderFactory, Serializable { - private final Map config; +class JsonReaderFactoryImpl implements JsonReaderFactory, Serializable { + private final Map internalConfig = new HashMap(); + private static final String[] SUPPORTED_CONFIG_KEYS = new String[] { + JsonParserFactoryImpl.BUFFER_STRATEGY, JsonParserFactoryImpl.MAX_STRING_LENGTH, JsonParserFactoryImpl.BUFFER_LENGTH + }; private final JsonParserFactoryImpl parserFactory; - public JsonReaderFactoryImpl(final Map config) { - this.config = config; - this.parserFactory = new JsonParserFactoryImpl(config); + JsonReaderFactoryImpl(final Map config) { + + if(config != null) { + + for (String configKey : SUPPORTED_CONFIG_KEYS) { + if(config.containsKey(configKey)) { + internalConfig.put(configKey, config.get(configKey)); + } + } + } + + this.parserFactory = new JsonParserFactoryImpl(internalConfig); } @Override @@ -55,6 +67,6 @@ public class JsonReaderFactoryImpl implements JsonReaderFactory, Serializable { @Override public Map getConfigInUse() { - return Collections.unmodifiableMap(config); + return Collections.unmodifiableMap(internalConfig); } } diff --git a/fleece-core/src/main/java/org/apache/fleece/core/JsonReaderImpl.java b/fleece-core/src/main/java/org/apache/fleece/core/JsonReaderImpl.java index 0bc3850..3586bc5 100644 --- a/fleece-core/src/main/java/org/apache/fleece/core/JsonReaderImpl.java +++ b/fleece-core/src/main/java/org/apache/fleece/core/JsonReaderImpl.java @@ -19,53 +19,52 @@ package org.apache.fleece.core; import javax.json.JsonArray; +import javax.json.JsonArrayBuilder; import javax.json.JsonObject; +import javax.json.JsonObjectBuilder; import javax.json.JsonReader; import javax.json.JsonStructure; -import javax.json.JsonValue; import javax.json.stream.JsonParser; import javax.json.stream.JsonParsingException; -import java.math.BigDecimal; +class JsonReaderImpl implements JsonReader { + private final JsonParser parser; + private boolean closed = false; -public class JsonReaderImpl implements JsonReader { - private final EscapedStringAwareJsonParser parser; - private final JsonReaderListenerFactory listenerFactory; - - public JsonReaderImpl(final EscapedStringAwareJsonParser parser) { - this(parser, new JsonListenerFactory()); - } - - public JsonReaderImpl(final EscapedStringAwareJsonParser parser, final JsonReaderListenerFactory listenerFactory) { + JsonReaderImpl(final JsonParser parser) { this.parser = parser; - this.listenerFactory = listenerFactory; } @Override public JsonStructure read() { + + checkClosed(); + if (!parser.hasNext()) { throw new IllegalStateException("Nothing to read"); } switch (parser.next()) { case START_OBJECT: - final JsonReaderListener subObject = listenerFactory.subObject(); - parseObject(subObject); + final JsonObjectBuilder objectBuilder = new JsonObjectBuilderImpl(); + parseObject(objectBuilder); if (parser.hasNext()) { throw new JsonParsingException("Expected end of file", parser.getLocation()); } - return JsonObject.class.cast(subObject.getObject()); + close(); + return objectBuilder.build(); case START_ARRAY: - final JsonReaderListener subArray = listenerFactory.subArray(); - parseArray(subArray); + final JsonArrayBuilder arrayBuilder = new JsonArrayBuilderImpl(); + parseArray(arrayBuilder); if (parser.hasNext()) { throw new JsonParsingException("Expected end of file", parser.getLocation()); } - return JsonArray.class.cast(subArray.getObject()); + close(); + return arrayBuilder.build(); default: + close(); throw new JsonParsingException("Unknown structure: " + parser.next(), parser.getLocation()); } - - + } @Override @@ -80,188 +79,57 @@ public class JsonReaderImpl implements JsonReader { @Override public void close() { - parser.close(); - } - private static class JsonListenerFactory implements JsonReaderListenerFactory { - @Override - public JsonReaderListener subObject() { - return new JsonObjectListener(); + if (!closed) { + closed = true; + parser.close(); } - @Override - public JsonReaderListener subArray() { - return new JsonArrayListener(); - } } - private static class JsonObjectListener implements JsonReaderListener { - private JsonObjectImpl object = new JsonObjectImpl(); - private String key = null; - - @Override - public Object getObject() { - return object; - } - - @Override - public void onKey(final String string) { - key = string; - } - - @Override - public void onValue(final String string, final String escaped) { - final JsonStringImpl value = new JsonStringImpl(string, escaped); - object.putInternal(key, value); - } - - @Override - public void onLong(final long aLong) { - final JsonLongImpl value = new JsonLongImpl(aLong); - object.putInternal(key, value); - } - - @Override - public void onBigDecimal(final BigDecimal bigDecimal) { - final JsonNumberImpl value = new JsonNumberImpl(bigDecimal); - object.putInternal(key, value); - } - - @Override - public void onNull() { - final JsonValue value = JsonValue.NULL; - object.putInternal(key, value); - } - - @Override - public void onTrue() { - final JsonValue value = JsonValue.TRUE; - object.putInternal(key, value); - } - - @Override - public void onFalse() { - final JsonValue value = JsonValue.FALSE; - object.putInternal(key, value); - } - - @Override - public void onObject(final Object obj) { - final JsonObject jsonObject = JsonObject.class.cast(obj); - object.putInternal(key, jsonObject); - } - - @Override - public void onArray(final Object arr) { - final JsonArray jsonArry = JsonArray.class.cast(arr); - object.putInternal(key, jsonArry); - } - } - - private static class JsonArrayListener implements JsonReaderListener { - private JsonArrayImpl array = new JsonArrayImpl(); - - @Override - public Object getObject() { - return array; - } - - @Override - public void onKey(final String string) { - throw new UnsupportedOperationException(); - } - - @Override - public void onValue(final String string, final String escaped) { - final JsonStringImpl value = new JsonStringImpl(string, escaped); - array.addInternal(value); - } - - @Override - public void onLong(final long aLong) { - final JsonLongImpl value = new JsonLongImpl(aLong); - array.addInternal(value); - } - - @Override - public void onBigDecimal(final BigDecimal bigDecimal) { - final JsonNumberImpl value = new JsonNumberImpl(bigDecimal); - array.addInternal(value); - } - - @Override - public void onNull() { - final JsonValue value = JsonValue.NULL; - array.addInternal(value); - } - - @Override - public void onTrue() { - final JsonValue value = JsonValue.TRUE; - array.addInternal(value); - } - - @Override - public void onFalse() { - final JsonValue value = JsonValue.FALSE; - array.addInternal(value); - } - - @Override - public void onObject(final Object obj) { - final JsonObject jsonObject = JsonObject.class.cast(obj); - array.addInternal(jsonObject); - } - - @Override - public void onArray(final Object arr) { - final JsonArray jsonArry = JsonArray.class.cast(arr); - array.addInternal(jsonArry); - } - } - - private void parseObject(final JsonReaderListener listener) { + private void parseObject(final JsonObjectBuilder builder) { + String key = null; while (parser.hasNext()) { final JsonParser.Event next = parser.next(); switch (next) { case KEY_NAME: - listener.onKey(parser.getString()); + key = parser.getString(); break; case VALUE_STRING: - listener.onValue(parser.getString(), parser.getEscapedString()); + builder.add(key, new JsonStringImpl(parser.getString())); break; case START_OBJECT: - final JsonReaderListener subListenerObject = listenerFactory.subObject(); - parseObject(subListenerObject); - listener.onObject(subListenerObject.getObject()); + JsonObjectBuilder subObject = null; + parseObject(subObject = new JsonObjectBuilderImpl()); + builder.add(key, subObject); break; case START_ARRAY: - final JsonReaderListener subListenerArray = listenerFactory.subArray(); - parseArray(subListenerArray); - listener.onArray(subListenerArray.getObject()); + JsonArrayBuilder subArray = null; + parseArray(subArray = new JsonArrayBuilderImpl()); + builder.add(key, subArray); break; case VALUE_NUMBER: if (parser.isIntegralNumber()) { - listener.onLong(parser.getLong()); + builder.add(key, new JsonLongImpl(parser.getLong())); } else { - listener.onBigDecimal(parser.getBigDecimal()); + builder.add(key, new JsonNumberImpl(parser.getBigDecimal())); } break; case VALUE_NULL: - listener.onNull(); + builder.addNull(key); break; case VALUE_TRUE: - listener.onTrue(); + builder.add(key, true); break; case VALUE_FALSE: - listener.onFalse(); + builder.add(key, false); break; case END_OBJECT: @@ -276,47 +144,47 @@ public class JsonReaderImpl implements JsonReader { } } - private void parseArray(final JsonReaderListener listener) { + private void parseArray(final JsonArrayBuilder builder) { while (parser.hasNext()) { final JsonParser.Event next = parser.next(); switch (next) { case VALUE_STRING: - listener.onValue(parser.getString(), parser.getEscapedString()); + builder.add(new JsonStringImpl(parser.getString())); break; case VALUE_NUMBER: if (parser.isIntegralNumber()) { - listener.onLong(parser.getLong()); + builder.add(new JsonLongImpl(parser.getLong())); } else { - listener.onBigDecimal(parser.getBigDecimal()); + builder.add(new JsonNumberImpl(parser.getBigDecimal())); } break; case START_OBJECT: - final JsonReaderListener subListenerObject = listenerFactory.subObject(); - parseObject(subListenerObject); - listener.onObject(subListenerObject.getObject()); + JsonObjectBuilder subObject = null; + parseObject(subObject = new JsonObjectBuilderImpl()); + builder.add(subObject); break; case START_ARRAY: - final JsonReaderListener subListenerArray = listenerFactory.subArray(); - parseArray(subListenerArray); - listener.onArray(subListenerArray.getObject()); + JsonArrayBuilder subArray = null; + parseArray(subArray = new JsonArrayBuilderImpl()); + builder.add(subArray); break; case END_ARRAY: return; case VALUE_NULL: - listener.onNull(); + builder.addNull(); break; case VALUE_TRUE: - listener.onTrue(); + builder.add(true); break; case VALUE_FALSE: - listener.onFalse(); + builder.add(false); break; case KEY_NAME: @@ -330,4 +198,11 @@ public class JsonReaderImpl implements JsonReader { } } } + + private void checkClosed() { + if (closed) { + throw new IllegalStateException("read(), readObject(), readArray() or close() method was already called"); + } + + } } diff --git a/fleece-core/src/main/java/org/apache/fleece/core/JsonReaderListener.java b/fleece-core/src/main/java/org/apache/fleece/core/JsonReaderListener.java deleted file mode 100644 index b3ba294..0000000 --- a/fleece-core/src/main/java/org/apache/fleece/core/JsonReaderListener.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you 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 org.apache.fleece.core; - -import java.math.BigDecimal; - -public interface JsonReaderListener { - Object getObject(); - - void onKey(String string); - - void onValue(String string, String escaped); - - void onLong(long aLong); - - void onBigDecimal(BigDecimal bigDecimal); - - void onNull(); - - void onTrue(); - - void onFalse(); - - void onObject(Object object); - - void onArray(Object array); -} diff --git a/fleece-core/src/main/java/org/apache/fleece/core/JsonReaderListenerFactory.java b/fleece-core/src/main/java/org/apache/fleece/core/JsonReaderListenerFactory.java deleted file mode 100644 index 653145d..0000000 --- a/fleece-core/src/main/java/org/apache/fleece/core/JsonReaderListenerFactory.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you 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 org.apache.fleece.core; - -public interface JsonReaderListenerFactory { - JsonReaderListener subObject(); - - JsonReaderListener subArray(); -} diff --git a/fleece-core/src/main/java/org/apache/fleece/core/JsonStreamParserImpl.java b/fleece-core/src/main/java/org/apache/fleece/core/JsonStreamParserImpl.java index b6e1e32..8a69195 100644 --- a/fleece-core/src/main/java/org/apache/fleece/core/JsonStreamParserImpl.java +++ b/fleece-core/src/main/java/org/apache/fleece/core/JsonStreamParserImpl.java @@ -28,10 +28,11 @@ import java.util.NoSuchElementException; import javax.json.JsonException; import javax.json.stream.JsonLocation; +import javax.json.stream.JsonParser; import javax.json.stream.JsonParsingException; //This class represents either the Json tokenizer and the Json parser. -public class JsonStreamParserImpl implements JsonChars, EscapedStringAwareJsonParser { +public class JsonStreamParserImpl implements JsonChars, JsonParser{ //the main buffer where the stream will be buffered private final char[] buffer; @@ -327,7 +328,7 @@ public class JsonStreamParserImpl implements JsonChars, EscapedStringAwareJsonPa final char c = readNextNonWhitespaceChar(); - if (c == COMMA) { + if (c == COMMA_CHAR) { //last event must one of the following-> " ] } LITERAL if (previousEvent == START_ARRAY || previousEvent == START_OBJECT || previousEvent == COMMA_EVENT || previousEvent == KEY_NAME) { @@ -384,7 +385,7 @@ public class JsonStreamParserImpl implements JsonChars, EscapedStringAwareJsonPa return handleEndArray(); - case QUOTE: + case QUOTE_CHAR: return handleQuote(); @@ -494,7 +495,7 @@ public class JsonStreamParserImpl implements JsonChars, EscapedStringAwareJsonPa //when first called n its first char after the starting quote //after that its the next character after the while loop below - if (n == QUOTE) { + if (n == QUOTE_CHAR) { endOfValueInBuffer = startOfValueInBuffer = bufferPos; //->"" case return; } else if (n == EOL) { @@ -527,13 +528,13 @@ public class JsonStreamParserImpl implements JsonChars, EscapedStringAwareJsonPa startOfValueInBuffer = bufferPos; endOfValueInBuffer = -1; - while ((n = readNextChar()) > '\u001F' && n != ESCAPE_CHAR && n != EOL && n != QUOTE) { + while ((n = readNextChar()) > '\u001F' && n != ESCAPE_CHAR && n != EOL && n != QUOTE_CHAR) { //read fast } endOfValueInBuffer = bufferPos; - if (n == QUOTE) { + if (n == QUOTE_CHAR) { if (fallBackCopyBufferLength > 0) { copyCurrentValue(); @@ -708,7 +709,7 @@ public class JsonStreamParserImpl implements JsonChars, EscapedStringAwareJsonPa endOfValueInBuffer = bufferPos; - if (y == COMMA || y == END_ARRAY_CHAR || y == END_OBJECT_CHAR || y == EOL || y == SPACE || y == TAB || y == CR) { + if (y == COMMA_CHAR || y == END_ARRAY_CHAR || y == END_OBJECT_CHAR || y == EOL || y == SPACE || y == TAB || y == CR) { bufferPos--;//unread one char @@ -895,11 +896,6 @@ public class JsonStreamParserImpl implements JsonChars, EscapedStringAwareJsonPa } } - @Override - public String getEscapedString() { - return Strings.escape(getString()); - } - //parse a char[] to long while checking overflow //if overflowed return null //no additional checks since we are sure here that there are no non digits in the array diff --git a/fleece-core/src/main/java/org/apache/fleece/core/JsonStringImpl.java b/fleece-core/src/main/java/org/apache/fleece/core/JsonStringImpl.java index f4552d1..fe16ce1 100644 --- a/fleece-core/src/main/java/org/apache/fleece/core/JsonStringImpl.java +++ b/fleece-core/src/main/java/org/apache/fleece/core/JsonStringImpl.java @@ -20,18 +20,18 @@ package org.apache.fleece.core; import javax.json.JsonString; -public class JsonStringImpl implements JsonString { +final class JsonStringImpl implements JsonString { private final String value; private String escape; private Integer hashCode = null; - public JsonStringImpl(final String value) { - this(value, null); - } - public JsonStringImpl(final String value, final String escaped) { + JsonStringImpl(final String value) { + if(value == null) { + throw new NullPointerException("value must not be null"); + } + this.value = value; - this.escape = escaped; } @Override @@ -51,18 +51,22 @@ public class JsonStringImpl implements JsonString { @Override public String toString() { - if (escape == null) { - escape = Strings.escape(value); + String s = escape; + if (s == null) { + s = JsonChars.QUOTE_CHAR+Strings.escape(value)+JsonChars.QUOTE_CHAR; + escape=s; } - return escape; + return s; } @Override public int hashCode() { - if (hashCode == null) { - hashCode = value.hashCode(); + Integer h = hashCode; + if (h == null) { + h = value.hashCode(); + hashCode=h; } - return hashCode; + return h; } @Override diff --git a/fleece-core/src/main/java/org/apache/fleece/core/JsonWriterFactoryImpl.java b/fleece-core/src/main/java/org/apache/fleece/core/JsonWriterFactoryImpl.java index 3d52cdb..67a4b66 100644 --- a/fleece-core/src/main/java/org/apache/fleece/core/JsonWriterFactoryImpl.java +++ b/fleece-core/src/main/java/org/apache/fleece/core/JsonWriterFactoryImpl.java @@ -18,23 +18,41 @@ */ package org.apache.fleece.core; -import javax.json.JsonWriter; -import javax.json.JsonWriterFactory; -import javax.json.stream.JsonGeneratorFactory; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.Serializable; import java.io.Writer; import java.nio.charset.Charset; +import java.util.Collections; +import java.util.HashMap; import java.util.Map; -public class JsonWriterFactoryImpl implements JsonWriterFactory,Serializable { - private final Map config; +import javax.json.JsonWriter; +import javax.json.JsonWriterFactory; +import javax.json.stream.JsonGenerator; +import javax.json.stream.JsonGeneratorFactory; + +class JsonWriterFactoryImpl implements JsonWriterFactory, Serializable { + private static final Charset UTF8_CHARSET = Charset.forName("UTF-8"); + private final Map internalConfig = new HashMap(); + private static final String[] SUPPORTED_CONFIG_KEYS = new String[] { + + JsonGenerator.PRETTY_PRINTING + + }; private final JsonGeneratorFactory factory; - public JsonWriterFactoryImpl(final Map config) { - this.config = config; - this.factory = new JsonGeneratorFactoryImpl(config); + JsonWriterFactoryImpl(final Map config) { + if (config != null) { + + for (final String configKey : SUPPORTED_CONFIG_KEYS) { + if (config.containsKey(configKey)) { + internalConfig.put(configKey, config.get(configKey)); + } + } + } + + this.factory = new JsonGeneratorFactoryImpl(internalConfig); } @Override @@ -44,7 +62,7 @@ public class JsonWriterFactoryImpl implements JsonWriterFactory,Serializable { @Override public JsonWriter createWriter(final OutputStream out) { - return createWriter(new OutputStreamWriter(out)); + return createWriter(new OutputStreamWriter(out, UTF8_CHARSET)); } @Override @@ -54,6 +72,6 @@ public class JsonWriterFactoryImpl implements JsonWriterFactory,Serializable { @Override public Map getConfigInUse() { - return config; + return Collections.unmodifiableMap(internalConfig); } } diff --git a/fleece-core/src/main/java/org/apache/fleece/core/JsonWriterImpl.java b/fleece-core/src/main/java/org/apache/fleece/core/JsonWriterImpl.java index 9474779..ef063b0 100644 --- a/fleece-core/src/main/java/org/apache/fleece/core/JsonWriterImpl.java +++ b/fleece-core/src/main/java/org/apache/fleece/core/JsonWriterImpl.java @@ -18,44 +18,56 @@ */ package org.apache.fleece.core; +import java.io.Serializable; + import javax.json.JsonArray; import javax.json.JsonObject; import javax.json.JsonStructure; import javax.json.JsonWriter; import javax.json.stream.JsonGenerator; -import java.io.Flushable; -import java.io.IOException; -import java.io.Serializable; -public class JsonWriterImpl implements JsonWriter, Serializable, Flushable { +class JsonWriterImpl implements JsonWriter, Serializable{ private final JsonGenerator generator; + private boolean closed = false; - public JsonWriterImpl(final JsonGenerator generator) { + JsonWriterImpl(final JsonGenerator generator) { this.generator = generator; } @Override public void writeArray(final JsonArray array) { + checkClosed(); generator.write(array); + close(); } @Override public void writeObject(final JsonObject object) { + checkClosed(); generator.write(object); + close(); } @Override public void write(final JsonStructure value) { + checkClosed(); generator.write(value); + close(); } @Override public void close() { - generator.close(); + + if(!closed) { + closed = true; + generator.close(); + } } - - @Override - public void flush() throws IOException { - generator.flush(); + + private void checkClosed() { + if(closed) { + throw new IllegalStateException("writeArray(), writeObject(), write() or close() method was already called"); + } + } } diff --git a/fleece-core/src/main/java/org/apache/fleece/core/RFC4627AwareInputStreamReader.java b/fleece-core/src/main/java/org/apache/fleece/core/RFC4627AwareInputStreamReader.java index 78d3f57..4abac6e 100644 --- a/fleece-core/src/main/java/org/apache/fleece/core/RFC4627AwareInputStreamReader.java +++ b/fleece-core/src/main/java/org/apache/fleece/core/RFC4627AwareInputStreamReader.java @@ -28,7 +28,7 @@ import javax.json.JsonException; final class RFC4627AwareInputStreamReader extends InputStreamReader { - public RFC4627AwareInputStreamReader(final InputStream in) { + RFC4627AwareInputStreamReader(final InputStream in) { this(new PushbackInputStream(in,4)); } @@ -60,7 +60,7 @@ final class RFC4627AwareInputStreamReader extends InputStreamReader { private static Charset getCharset(final PushbackInputStream inputStream) { Charset charset = Charset.forName("UTF-8"); final byte[] utfBytes = new byte[4]; - + int bomLength=0; try { final int read = inputStream.read(utfBytes); if (read < 2) { @@ -77,27 +77,47 @@ final class RFC4627AwareInputStreamReader extends InputStreamReader { charset = (third == 0x00) ? Charset.forName("UTF-32LE") : Charset.forName("UTF-16LE"); } else { - //check BOM + /*check BOM + + Encoding hex byte order mark + UTF-8 EF BB BF + UTF-16 (BE) FE FF + UTF-16 (LE) FF FE + UTF-32 (BE) 00 00 FE FF + UTF-32 (LE) FF FE 00 00 + */ + + + + if(first == 0xFE && second == 0xFF) { charset = Charset.forName("UTF-16BE"); + bomLength=2; } else if(read > 3 && first == 0x00 && second == 0x00 && (utfBytes[2]&0xff) == 0xFE && (utfBytes[3]&0xff) == 0xFF){ charset = Charset.forName("UTF-32BE"); + bomLength=4; } else if(first == 0xFF && second == 0xFE) { if(read > 3 && (utfBytes[2]&0xff) == 0x00 && (utfBytes[3]&0xff) == 0x00) { charset = Charset.forName("UTF-32LE"); + bomLength=4; }else { charset = Charset.forName("UTF-16LE"); + bomLength=2; } - } + } + + //assume UTF8 } } + if(bomLength < 4) { + inputStream.unread(utfBytes,bomLength==2?2:0,read-bomLength); + } - inputStream.unread(utfBytes); } catch (final IOException e) { diff --git a/fleece-core/src/main/java/org/apache/fleece/core/Strings.java b/fleece-core/src/main/java/org/apache/fleece/core/Strings.java index 71e8820..e0c6c73 100644 --- a/fleece-core/src/main/java/org/apache/fleece/core/Strings.java +++ b/fleece-core/src/main/java/org/apache/fleece/core/Strings.java @@ -23,7 +23,7 @@ import java.util.concurrent.ConcurrentMap; import javax.json.stream.JsonParsingException; -public class Strings implements JsonChars { +class Strings implements JsonChars { private static final BufferStrategy.BufferProvider BUILDER_CACHE = BufferStrategy.valueOf(System.getProperty("fleece.string-builder.strategy", "QUEUE")) .newStringBuilderProvider(Integer.getInteger("org.apache.fleece.default-string-builder", 1024)); @@ -32,7 +32,7 @@ public class Strings implements JsonChars { private static final String UNICODE_PREFIX_HELPER = "000"; private static final ConcurrentMap UNICODE_CACHE = new ConcurrentHashMap(); - public static char asEscapedChar(final char current) { + static char asEscapedChar(final char current) { switch (current) { case 'r': return '\r'; @@ -60,13 +60,18 @@ public class Strings implements JsonChars { } - public static String escape(final String value) { + static String escape(final String value) { + + if(value == null || value.length()==0) { + return value; + } + final StringBuilder builder = BUILDER_CACHE.newBuffer(); try { for (int i = 0; i < value.length(); i++) { final char c = value.charAt(i); switch (c) { - case QUOTE: + case QUOTE_CHAR: case ESCAPE_CHAR: builder.append(ESCAPE_CHAR).append(c); break; diff --git a/fleece-core/src/test/java/org/apache/fleece/core/JsonArrayImplTest.java b/fleece-core/src/test/java/org/apache/fleece/core/JsonArrayImplTest.java index 59b74d2..c8863d4 100644 --- a/fleece-core/src/test/java/org/apache/fleece/core/JsonArrayImplTest.java +++ b/fleece-core/src/test/java/org/apache/fleece/core/JsonArrayImplTest.java @@ -22,36 +22,39 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; +import javax.json.Json; import javax.json.JsonArray; +import javax.json.JsonArrayBuilder; import org.junit.Test; public class JsonArrayImplTest { @Test - public void arrayToString() { - final JsonArrayImpl object = new JsonArrayImpl(); - object.addInternal(new JsonStringImpl("a")); - object.addInternal(new JsonStringImpl("b")); - assertEquals("[\"a\",\"b\"]", object.toString()); + public void arrayToString() { + JsonArrayBuilder ab = Json.createArrayBuilder(); + + ab.add(new JsonStringImpl("a")); + ab.add(new JsonStringImpl("b")); + assertEquals("[\"a\",\"b\"]", ab.build().toString()); } @Test public void arrayIndex() { - final JsonArrayImpl object = new JsonArrayImpl(); - object.addInternal(new JsonStringImpl("a")); - object.addInternal(new JsonStringImpl("b")); - object.addInternal(new JsonLongImpl(5)); - final JsonArray array = (JsonArray) object; + JsonArrayBuilder ab = Json.createArrayBuilder(); + ab.add(new JsonStringImpl("a")); + ab.add(new JsonStringImpl("b")); + ab.add(new JsonLongImpl(5)); + final JsonArray array = (JsonArray) ab.build(); assertFalse(array.isEmpty()); - assertEquals("a", object.getJsonString(0).getString()); - assertEquals("b", object.getJsonString(1).getString()); - assertEquals(5, object.getJsonNumber(2).longValue()); - assertEquals("[\"a\",\"b\",5]", object.toString()); + assertEquals("a", array.getJsonString(0).getString()); + assertEquals("b", array.getJsonString(1).getString()); + assertEquals(5, array.getJsonNumber(2).longValue()); + assertEquals("[\"a\",\"b\",5]", array.toString()); } @Test public void emptyArray() { - final JsonArray array = new JsonArrayImpl(); + final JsonArray array = Json.createArrayBuilder().build(); assertTrue(array.isEmpty()); assertEquals("[]", array.toString()); } diff --git a/fleece-core/src/test/java/org/apache/fleece/core/JsonGeneratorImplTest.java b/fleece-core/src/test/java/org/apache/fleece/core/JsonGeneratorImplTest.java index 374526f..6f501f6 100644 --- a/fleece-core/src/test/java/org/apache/fleece/core/JsonGeneratorImplTest.java +++ b/fleece-core/src/test/java/org/apache/fleece/core/JsonGeneratorImplTest.java @@ -21,10 +21,13 @@ package org.apache.fleece.core; import static org.junit.Assert.assertEquals; import java.io.ByteArrayOutputStream; +import java.math.BigDecimal; +import java.math.BigInteger; import java.util.HashMap; import javax.json.Json; import javax.json.JsonValue; +import javax.json.stream.JsonGenerationException; import javax.json.stream.JsonGenerator; import org.junit.Test; @@ -98,6 +101,129 @@ public class JsonGeneratorImplTest { Json.createGenerator(baos).writeStartArray().write(JsonValue.FALSE).write(JsonValue.TRUE).writeEnd().close(); assertEquals("[false,true]", new String(baos.toByteArray())); } + + @Test(expected=JsonGenerationException.class) + public void fail1() { + final ByteArrayOutputStream baos = new ByteArrayOutputStream(); + Json.createGenerator(baos) + .writeStartArray("test"); + } + + @Test(expected=JsonGenerationException.class) + public void fail2() { + final ByteArrayOutputStream baos = new ByteArrayOutputStream(); + Json.createGenerator(baos) + .write("test",1); + } + + @Test(expected=JsonGenerationException.class) + public void fail3() { + final ByteArrayOutputStream baos = new ByteArrayOutputStream(); + Json.createGenerator(baos) + .writeStartObject() + .writeStartObject(); + } + + @Test(expected=JsonGenerationException.class) + public void fail4() { + final ByteArrayOutputStream baos = new ByteArrayOutputStream(); + Json.createGenerator(baos) + .writeEnd(); + } + + @Test(expected=JsonGenerationException.class) + public void fail5() { + final ByteArrayOutputStream baos = new ByteArrayOutputStream(); + Json.createGenerator(baos) + .close(); + } + + @Test(expected=JsonGenerationException.class) + public void fail6() { + final ByteArrayOutputStream baos = new ByteArrayOutputStream(); + Json.createGenerator(baos) + .writeStartArray() + .writeStartObject("test"); + } + + @Test(expected=JsonGenerationException.class) + public void fail7() { + final ByteArrayOutputStream baos = new ByteArrayOutputStream(); + Json.createGenerator(baos) + .writeStartArray() + .writeNull() + .writeStartObject() + .write("a", new BigDecimal("123.123")) + .write("b", true) + .write("c", new BigInteger("3312")) + .write("d", new JsonStringImpl("mystring")) + .writeEnd() + .close(); + + } + + @Test(expected=JsonGenerationException.class) + public void fail9() { + final ByteArrayOutputStream baos = new ByteArrayOutputStream(); + Json.createGenerator(baos) + .writeStartObject() + .write("a", new BigDecimal("123.123")) + .write("b", true) + .write("c", new BigInteger("3312")) + .write("d", new JsonStringImpl("mystring")) + .writeEnd() + .writeStartObject() + .close(); + + } + + @Test + public void numbers() { + final ByteArrayOutputStream baos = new ByteArrayOutputStream(); + Json.createGenerator(baos) + .writeStartArray() + .writeNull() + .writeStartObject() + .write("a", new BigDecimal("123.123")) + .write("b", true) + .write("c", new BigInteger("3312")) + .write("d", new JsonStringImpl("Mystring")) + .writeEnd() + .writeEnd() + .close(); + assertEquals("[null,{\"a\":123.123,\"b\":true,\"c\":3312,\"d\":\"Mystring\"}]", new String(baos.toByteArray())); + } + + @Test + public void numbers2() { + final ByteArrayOutputStream baos = new ByteArrayOutputStream(); + Json.createGenerator(baos) + .writeStartArray() + .writeNull() + .writeStartObject() + .write("a", 999999999L) + .write("b", 123) + .write("c", -444444444L) + .write("d",-123) + .writeEnd() + .writeEnd() + .close(); + assertEquals("[null,{\"a\":999999999,\"b\":123,\"c\":-444444444,\"d\":-123}]", new String(baos.toByteArray())); + } + + @Test + public void arrayInArray() { + final ByteArrayOutputStream baos = new ByteArrayOutputStream(); + Json.createGenerator(baos) + .writeStartArray() + .writeStartArray() + .writeNull() + .writeEnd() + .writeEnd() + .close(); + assertEquals("[[null]]", new String(baos.toByteArray())); + } + @Test public void generate() { diff --git a/fleece-core/src/test/java/org/apache/fleece/core/JsonObjectImplTest.java b/fleece-core/src/test/java/org/apache/fleece/core/JsonObjectImplTest.java index 6ebb3e5..459ee57 100644 --- a/fleece-core/src/test/java/org/apache/fleece/core/JsonObjectImplTest.java +++ b/fleece-core/src/test/java/org/apache/fleece/core/JsonObjectImplTest.java @@ -20,13 +20,16 @@ package org.apache.fleece.core; import static org.junit.Assert.assertEquals; +import javax.json.Json; +import javax.json.JsonObjectBuilder; + import org.junit.Test; public class JsonObjectImplTest { @Test public void objectToString() { - final JsonObjectImpl object = new JsonObjectImpl(); - object.putInternal("a", new JsonStringImpl("b")); - assertEquals("{\"a\":\"b\"}", object.toString()); + final JsonObjectBuilder ob = Json.createObjectBuilder(); + ob.add("a", new JsonStringImpl("b")); + assertEquals("{\"a\":\"b\"}", ob.build().toString()); } } diff --git a/fleece-core/src/test/java/org/apache/fleece/core/JsonParserTest.java b/fleece-core/src/test/java/org/apache/fleece/core/JsonParserTest.java index a5027b5..8113b0d 100644 --- a/fleece-core/src/test/java/org/apache/fleece/core/JsonParserTest.java +++ b/fleece-core/src/test/java/org/apache/fleece/core/JsonParserTest.java @@ -36,11 +36,12 @@ import java.util.NoSuchElementException; import javax.json.Json; import javax.json.JsonArray; +import javax.json.JsonArrayBuilder; import javax.json.JsonException; +import javax.json.JsonObjectBuilder; import javax.json.JsonReader; import javax.json.stream.JsonParser; import javax.json.stream.JsonParser.Event; -import javax.json.stream.JsonParserFactory; import javax.json.stream.JsonParsingException; import org.junit.Test; @@ -219,15 +220,16 @@ public class JsonParserTest { @Test public void simpleInMemory() { - final JsonObjectImpl simple = new JsonObjectImpl(); - simple.putInternal("a", new JsonStringImpl("b")); - simple.putInternal("c", new JsonNumberImpl(new BigDecimal(4))); - final JsonArrayImpl array = new JsonArrayImpl(); - array.addInternal(new JsonNumberImpl(new BigDecimal(1))); - array.addInternal(new JsonNumberImpl(new BigDecimal(-2))); - simple.putInternal("d", array); + final JsonObjectBuilder ob = Json.createObjectBuilder(); + ob.add("a", new JsonStringImpl("b")); + ob.add("c", new JsonNumberImpl(new BigDecimal(4))); + JsonArrayBuilder ab = Json.createArrayBuilder(); + ab.add(new JsonNumberImpl(new BigDecimal(1))); + ab.add(new JsonNumberImpl(new BigDecimal(-2))); + + ob.add("d", ab); - final JsonParser parser = Json.createParserFactory(Collections.emptyMap()).createParser(simple); + final JsonParser parser = Json.createParserFactory(Collections.emptyMap()).createParser(ob.build()); assertNotNull(parser); assertSimple(parser); } @@ -241,7 +243,8 @@ public class JsonParserTest { @Test public void simpleUTF16LE() { - final JsonParser parser = Json.createParserFactory(null).createParser(Thread.currentThread().getContextClassLoader().getResourceAsStream("json/simple_utf16le.json"),UTF_16LE); + final JsonParser parser = Json.createParserFactory(null).createParser(Thread.currentThread() + .getContextClassLoader().getResourceAsStream("json/simple_utf16le.json"),UTF_16LE); assertNotNull(parser); assertSimple(parser); } @@ -642,12 +645,13 @@ public class JsonParserTest { @Test public void escapedStringAwareParser() { - final EscapedStringAwareJsonParser parser = (EscapedStringAwareJsonParser) Json.createParser(Thread.currentThread().getContextClassLoader().getResourceAsStream("json/stringescape.json")); + final JsonParser parser = Json.createParser(Thread.currentThread() + .getContextClassLoader().getResourceAsStream("json/stringescape.json")); parser.next(); parser.next(); parser.next(); assertEquals("s\"mit\"", parser.getString()); - assertEquals("s\\\"mit\\\"", parser.getEscapedString()); + assertEquals("\"s\\\"mit\\\"\"", new JsonStringImpl(parser.getString()).toString()); parser.close(); } @@ -939,6 +943,7 @@ public class JsonParserTest { parser.next(); fail("Should have thrown a NoSuchElementException"); } catch (NoSuchElementException ne) { + //expected } } @@ -1377,4 +1382,38 @@ public class JsonParserTest { } + @Test + public void stringescapeVariousBufferSizesBogus() { + + + StringBuilder sb = new StringBuilder(); + sb.append("\t\"special-\":" + "\"" + "\\\\f\\n\\r\\t\\u6565\uDC00\uD800" + "\",\n"); + sb.append("\t\"unicode-\\u0000- \":\"\\u5656\uDC00\uD800\"\n"); + String s = "{"+sb.toString()+"}"; + + for (int i = 1; i < s.length()+100; i++) { + final String value = String.valueOf(i); + + final JsonParser parser = Json.createParserFactory(new HashMap() { + { + put("org.apache.fleece.default-char-buffer", value); + } + }).createParser(new ByteArrayInputStream(s.getBytes())); + assertNotNull(parser); + + while(parser.hasNext()) { + Event e = parser.next(); + if(e==null) { + fail(); + } + } + + assertTrue(!parser.hasNext()); + parser.close(); + + } + } + + + } diff --git a/fleece-core/src/test/java/org/apache/fleece/core/JsonReaderImplTest.java b/fleece-core/src/test/java/org/apache/fleece/core/JsonReaderImplTest.java index abad642..a9ecac7 100644 --- a/fleece-core/src/test/java/org/apache/fleece/core/JsonReaderImplTest.java +++ b/fleece-core/src/test/java/org/apache/fleece/core/JsonReaderImplTest.java @@ -169,6 +169,21 @@ public class JsonReaderImplTest { assertEquals("hallo\u20acö\uffff \u08a5 থ?ß§$%&´'`*+#\udbff\udfff", object.getString("নa")); reader.close(); } + + @Test + public void specialKeysWithStringAsByteArrayInputStream() { + final String s = "{\"\\\"a\":\"\u0055\",\"\u0055\":\"test2\"}"; + System.out.println(s); + final JsonReader reader = Json.createReaderFactory(getFactoryConfig()).createReader( + new ByteArrayInputStream(s.getBytes(utf8Charset)), utf8Charset); + assertNotNull(reader); + final JsonObject object = reader.readObject(); + assertNotNull(object); + assertEquals(2, object.size()); + assertEquals("U", object.getString("\"a")); + assertEquals("test2", object.getString("U")); + reader.close(); + } @Test public void specialWithStringReader() { diff --git a/fleece-core/src/test/java/org/apache/fleece/core/JsonWriterImplTest.java b/fleece-core/src/test/java/org/apache/fleece/core/JsonWriterImplTest.java index 8887570..cf5d18c 100644 --- a/fleece-core/src/test/java/org/apache/fleece/core/JsonWriterImplTest.java +++ b/fleece-core/src/test/java/org/apache/fleece/core/JsonWriterImplTest.java @@ -23,6 +23,7 @@ import static org.junit.Assert.assertEquals; import java.io.ByteArrayOutputStream; import javax.json.Json; +import javax.json.JsonObjectBuilder; import javax.json.JsonWriter; import org.junit.Test; @@ -32,9 +33,9 @@ public class JsonWriterImplTest { public void writer() { final ByteArrayOutputStream out = new ByteArrayOutputStream(); final JsonWriter writer = Json.createWriter(out); - final JsonObjectImpl value = new JsonObjectImpl(); - value.putInternal("a", new JsonStringImpl("b")); - writer.write(value); + final JsonObjectBuilder ob = Json.createObjectBuilder(); + ob.add("a", new JsonStringImpl("b")); + writer.write(ob.build()); writer.close(); assertEquals("{\"a\":\"b\"}", new String(out.toByteArray())); } diff --git a/fleece-core/src/test/java/org/apache/fleece/core/LocationTest.java b/fleece-core/src/test/java/org/apache/fleece/core/LocationTest.java index 1d73d3d..313d56e 100644 --- a/fleece-core/src/test/java/org/apache/fleece/core/LocationTest.java +++ b/fleece-core/src/test/java/org/apache/fleece/core/LocationTest.java @@ -19,17 +19,12 @@ package org.apache.fleece.core; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; import java.io.StringReader; import java.util.HashMap; import javax.json.Json; -import javax.json.JsonObject; import javax.json.JsonReader; -import javax.json.JsonWriter; import javax.json.stream.JsonLocation; import javax.json.stream.JsonParser; import javax.json.stream.JsonParsingException; @@ -193,4 +188,48 @@ public class LocationTest { } + + + + @Test + public void testLocationOnParsingException() { + //line number, column, offset (measured in chars) + //line number and column start at 1 + //offset start at 0 + assertJsonLocation("a", new JsonLocationImpl(1, 2, 1)); + assertJsonLocation("aa", new JsonLocationImpl(1, 2, 1)); + assertJsonLocation("asa", new JsonLocationImpl(1, 2, 1)); + assertJsonLocation("{]", new JsonLocationImpl(1, 3, 2)); + assertJsonLocation("[}", new JsonLocationImpl(1, 3, 2)); + assertJsonLocation("[a", new JsonLocationImpl(1, 3, 2)); + assertJsonLocation("[nuLl]", new JsonLocationImpl(1, 5, 4)); + assertJsonLocation("[falsE]", new JsonLocationImpl(1, 7, 6)); + assertJsonLocation("[][]", new JsonLocationImpl(1, 4, 3)); + assertJsonLocation("[1234L]", new JsonLocationImpl(1, 7, 6)); + assertJsonLocation("[null\n}", new JsonLocationImpl(2, 2, 7)); + assertJsonLocation("[null\r\n}", new JsonLocationImpl(2, 2, 8)); + assertJsonLocation("[null\n, null\n}", new JsonLocationImpl(3, 2, 14)); + assertJsonLocation("[null\r\n, null\r\n}", new JsonLocationImpl(3, 2, 16)); + } + + + private void assertJsonLocation(String jsonString, JsonLocation expectedLocation) { + JsonParser parser = Json.createParser(new StringReader(jsonString)); + try { + while(parser.hasNext()) { + parser.next(); + } + Assert.fail("Expected to throw JsonParsingException for "+jsonString); + } catch(JsonParsingException je) { + // Expected + if (expectedLocation != null) { + JsonLocation loc = je.getLocation(); + assertEquals(expectedLocation.getLineNumber(), loc.getLineNumber()); + assertEquals(expectedLocation.getColumnNumber(), loc.getColumnNumber()); + assertEquals(expectedLocation.getStreamOffset(), loc.getStreamOffset()); + } + } finally { + parser.close(); + } + } } diff --git a/fleece-mapper/src/main/java/org/apache/fleece/mapper/Mapper.java b/fleece-mapper/src/main/java/org/apache/fleece/mapper/Mapper.java index 6398218..967d597 100644 --- a/fleece-mapper/src/main/java/org/apache/fleece/mapper/Mapper.java +++ b/fleece-mapper/src/main/java/org/apache/fleece/mapper/Mapper.java @@ -18,16 +18,18 @@ */ package org.apache.fleece.mapper; -import org.apache.fleece.core.JsonObjectImpl; import org.apache.fleece.mapper.converter.EnumConverter; import org.apache.fleece.mapper.reflection.Mappings; +import javax.json.Json; import javax.json.JsonArray; import javax.json.JsonNumber; import javax.json.JsonObject; import javax.json.JsonReader; import javax.json.JsonReaderFactory; +import javax.json.JsonString; import javax.json.JsonValue; +import javax.json.JsonValue.ValueType; import javax.json.stream.JsonGenerator; import javax.json.stream.JsonGeneratorFactory; @@ -63,7 +65,7 @@ import java.util.concurrent.ConcurrentMap; import static java.util.Arrays.asList; public class Mapper { - protected static final JsonObjectImpl EMPTY_OBJECT = new JsonObjectImpl(); + protected static final JsonObject EMPTY_OBJECT = Json.createObjectBuilder().build(); private static final Converter FALLBACK_CONVERTER = new FallbackConverter(); protected final Mappings mappings; @@ -500,7 +502,9 @@ public class Mapper { final Mappings.Setter value = setter.getValue(); final Method setterMethod = value.setter; final Object convertedValue = value.converter == null? - toObject(jsonValue, value.paramType) : value.converter.fromString(jsonValue.toString()); + toObject(jsonValue, value.paramType) : jsonValue.getValueType() == ValueType.STRING ? + value.converter.fromString(JsonString.class.cast(jsonValue).getString()): + value.converter.fromString(jsonValue.toString()); if (convertedValue != null) { try { @@ -521,6 +525,8 @@ public class Mapper { convertedValue = buildObject(type, JsonObject.class.cast(jsonValue)); } else if (JsonArray.class.isInstance(jsonValue)) { convertedValue = buildArray(type, JsonArray.class.cast(jsonValue)); + } else if (JsonString.class.isInstance(jsonValue)) { + convertedValue = JsonString.class.cast(jsonValue).getString(); } else if (jsonValue != null && JsonValue.NULL != jsonValue) { if (JsonNumber.class.isInstance(jsonValue)) { final JsonNumber number = JsonNumber.class.cast(jsonValue); diff --git a/fleece-mapper/src/test/java/org/apache/fleece/mapper/MapperTest.java b/fleece-mapper/src/test/java/org/apache/fleece/mapper/MapperTest.java index fed9c2f..bcba569 100644 --- a/fleece-mapper/src/test/java/org/apache/fleece/mapper/MapperTest.java +++ b/fleece-mapper/src/test/java/org/apache/fleece/mapper/MapperTest.java @@ -19,11 +19,13 @@ package org.apache.fleece.mapper; import junit.framework.AssertionFailedError; + import org.junit.Test; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.StringWriter; +import java.util.Arrays; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -71,7 +73,7 @@ public class MapperTest { "]," + "\"primitives\":[1,2,3,4,5]," + "\"collectionWrapper\":[1,2,3,4,5]," + - "\"map\":{\"uno\":\"true\",\"duos\":false}" + + "\"map\":{\"uno\":true,\"duos\":false}" + "}"; @Test @@ -118,11 +120,82 @@ public class MapperTest { //Assert fail with oracle java 1.7.0_45, works well with apple java 1.6.0_65 //assertTrue(serialized.contains("\"map\":{\"uno\":true,\"duos\":false}")); - assertTrue(serialized.contains("\"map\":{")); assertTrue(serialized.contains("\"uno\":true")); assertTrue(serialized.contains("\"duos\":false")); + + TheObject instance2 = new MapperBuilder().build() + .readObject(new ByteArrayInputStream(serialized.getBytes()), TheObject.class); // suppose reader writes but this is tested + + assertEquals(instance, instance2); + } + + static class Bool{ + boolean bool; + + public boolean isBool() { + return bool; + } + + public void setBool(boolean bool) { + this.bool = bool; + } + + + } + + static class Bool2{ + Map map; + + public Map getMap() { + return map; + } + + public void setMap(Map map) { + this.map = map; + } + + + + + } + + @Test + public void literal() { + + + + final Bool instance = new MapperBuilder().build() + .readObject(new ByteArrayInputStream("{\"bool\":true}".getBytes()), Bool.class); + + assertTrue(instance.bool); + + final StringWriter writer = new StringWriter(); + new MapperBuilder().build().writeObject(instance, writer); + final String serialized = writer.toString(); + assertEquals("{\"bool\":true}", serialized); + + } + + @Test(expected= IllegalArgumentException.class) + public void literalFail() { + + final Bool instance = new MapperBuilder().build() + .readObject(new ByteArrayInputStream("{\"bool\":\"true\"}".getBytes()), Bool.class); + + assertTrue(instance.bool); + } + + /*@Test(expected= IllegalArgumentException.class) + public void literalFail2() { + + final Bool2 instance = new MapperBuilder().build() + .readObject(new ByteArrayInputStream("{\"map\":{\"key\":\"true\"}}".getBytes()), Bool2.class); + + + + }*/ @Test public void writeArray() { @@ -311,6 +384,79 @@ public class MapperTest { public void setMap(final Map map) { this.map = map; } + + @Override + public String toString() { + return "TheObject [name=" + name + ", integer=" + integer + ", longnumber=" + longnumber + ", bool=" + bool + ", nested=" + + nested + ", array=" + Arrays.toString(array) + ", list=" + list + ", primitives=" + Arrays.toString(primitives) + + ", collectionWrapper=" + collectionWrapper + ", map=" + map + "]"; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + Arrays.hashCode(array); + result = prime * result + (bool ? 1231 : 1237); + result = prime * result + ((collectionWrapper == null) ? 0 : collectionWrapper.hashCode()); + result = prime * result + integer; + result = prime * result + ((list == null) ? 0 : list.hashCode()); + result = prime * result + (int) (longnumber ^ (longnumber >>> 32)); + result = prime * result + ((map == null) ? 0 : map.hashCode()); + result = prime * result + ((name == null) ? 0 : name.hashCode()); + result = prime * result + ((nested == null) ? 0 : nested.hashCode()); + result = prime * result + Arrays.hashCode(primitives); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + TheObject other = (TheObject) obj; + if (!Arrays.equals(array, other.array)) + return false; + if (bool != other.bool) + return false; + if (collectionWrapper == null) { + if (other.collectionWrapper != null) + return false; + } else if (!collectionWrapper.equals(other.collectionWrapper)) + return false; + if (integer != other.integer) + return false; + if (list == null) { + if (other.list != null) + return false; + } else if (!list.equals(other.list)) + return false; + if (longnumber != other.longnumber) + return false; + if (map == null) { + if (other.map != null) + return false; + } else if (!map.equals(other.map)) + return false; + if (name == null) { + if (other.name != null) + return false; + } else if (!name.equals(other.name)) + return false; + if (nested == null) { + if (other.nested != null) + return false; + } else if (!nested.equals(other.nested)) + return false; + if (!Arrays.equals(primitives, other.primitives)) + return false; + return true; + } + + } public static class Pair { -- 1.8.5.2 (Apple Git-48)