http://git-wip-us.apache.org/repos/asf/polygene-java/blob/e4cca11e/core/spi/src/main/java/org/apache/polygene/spi/serialization/AbstractDeserializer.java ---------------------------------------------------------------------- diff --git a/core/spi/src/main/java/org/apache/polygene/spi/serialization/AbstractDeserializer.java b/core/spi/src/main/java/org/apache/polygene/spi/serialization/AbstractDeserializer.java new file mode 100644 index 0000000..17982f3 --- /dev/null +++ b/core/spi/src/main/java/org/apache/polygene/spi/serialization/AbstractDeserializer.java @@ -0,0 +1,155 @@ +/* + * 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.polygene.spi.serialization; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.io.Reader; +import java.io.StringReader; +import java.util.function.Function; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; +import org.apache.polygene.api.entity.EntityReference; +import org.apache.polygene.api.serialization.Deserializer; +import org.apache.polygene.api.structure.ModuleDescriptor; +import org.apache.polygene.api.type.CollectionType; +import org.apache.polygene.api.type.MapType; +import org.apache.polygene.api.type.ValueType; +import org.apache.polygene.spi.module.ModuleSpi; + +public abstract class AbstractDeserializer implements Deserializer +{ + protected static final ValueType ENTITY_REF_LIST_VALUE_TYPE = CollectionType.listOf( EntityReference.class ); + protected static final ValueType ENTITY_REF_MAP_VALUE_TYPE = MapType.of( String.class, EntityReference.class ); + + @Override + public <T> T deserialize( ModuleDescriptor module, ValueType valueType, String state ) + { + return deserialize( module, valueType, new StringReader( state ) ); + } + + @Override + public <T> Function<String, T> deserializeFunction( ModuleDescriptor module, ValueType valueType ) + { + return state -> deserialize( module, valueType, new StringReader( state ) ); + } + + @Override + public <T> Stream<T> deserializeEach( ModuleDescriptor module, ValueType valueType, Iterable<String> states ) + { + return StreamSupport.stream( states.spliterator(), false ) + .map( state -> deserialize( module, valueType, new StringReader( state ) ) ); + } + + @Override + public <T> Stream<T> deserializeEach( ModuleDescriptor module, ValueType valueType, String... states ) + { + return Stream.of( states ).map( state -> deserialize( module, valueType, new StringReader( state ) ) ); + } + + @Override + public <T> T fromBytes( ModuleDescriptor module, ValueType valueType, byte[] bytes ) + { + return deserialize( module, valueType, new ByteArrayInputStream( bytes ) ); + } + + @Override + public <T> Function<byte[], T> fromBytesFunction( ModuleDescriptor module, ValueType valueType ) + { + return bytes -> deserialize( module, valueType, new ByteArrayInputStream( bytes ) ); + } + + @Override + public <T> Stream<T> fromBytesEach( ModuleDescriptor module, ValueType valueType, Iterable<byte[]> states ) + { + return StreamSupport.stream( states.spliterator(), false ) + .map( bytes -> deserialize( module, valueType, new ByteArrayInputStream( bytes ) ) ); + } + + @Override + public <T> Stream<T> fromBytesEach( ModuleDescriptor module, ValueType valueType, byte[]... states ) + { + return Stream.of( states ).map( bytes -> deserialize( module, valueType, new ByteArrayInputStream( bytes ) ) ); + } + + @Override + public <T> T deserialize( ModuleDescriptor module, Class<T> type, InputStream state ) + { + return deserialize( module, valueTypeOf( module, type ), state ); + } + + @Override + public <T> T deserialize( ModuleDescriptor module, Class<T> type, Reader state ) + { + return deserialize( module, valueTypeOf( module, type ), state ); + } + + @Override + public <T> T deserialize( ModuleDescriptor module, Class<T> type, String state ) + { + return deserialize( module, valueTypeOf( module, type ), state ); + } + + @Override + public <T> Function<String, T> deserializeFunction( ModuleDescriptor module, Class<T> type ) + { + return deserializeFunction( module, valueTypeOf( module, type ) ); + } + + @Override + public <T> Stream<T> deserializeEach( ModuleDescriptor module, Class<T> type, Iterable<String> states ) + { + return deserializeEach( module, valueTypeOf( module, type ), states ); + } + + @Override + public <T> Stream<T> deserializeEach( ModuleDescriptor module, Class<T> type, String... states ) + { + return deserializeEach( module, valueTypeOf( module, type ), states ); + } + + @Override + public <T> T fromBytes( ModuleDescriptor module, Class<T> type, byte[] bytes ) + { + return fromBytes( module, valueTypeOf( module, type ), bytes ); + } + + @Override + public <T> Function<byte[], T> fromBytesFunction( ModuleDescriptor module, Class<T> type ) + { + return fromBytesFunction( module, valueTypeOf( module, type ) ); + } + + @Override + public <T> Stream<T> fromBytesEach( ModuleDescriptor module, Class<T> type, Iterable<byte[]> states ) + { + return fromBytesEach( module, valueTypeOf( module, type ), states ); + } + + @Override + public <T> Stream<T> fromBytesEach( ModuleDescriptor module, Class<T> type, byte[]... states ) + { + return fromBytesEach( module, valueTypeOf( module, type ), states ); + } + + private ValueType valueTypeOf( ModuleDescriptor module, Class<?> type ) + { + // TODO Remove (ModuleSpi) cast + return ( (ModuleSpi) module.instance() ).valueTypeFactory().valueTypeOf( module, type ); + } +}
http://git-wip-us.apache.org/repos/asf/polygene-java/blob/e4cca11e/core/spi/src/main/java/org/apache/polygene/spi/serialization/AbstractSerializer.java ---------------------------------------------------------------------- diff --git a/core/spi/src/main/java/org/apache/polygene/spi/serialization/AbstractSerializer.java b/core/spi/src/main/java/org/apache/polygene/spi/serialization/AbstractSerializer.java new file mode 100644 index 0000000..b5f10ff --- /dev/null +++ b/core/spi/src/main/java/org/apache/polygene/spi/serialization/AbstractSerializer.java @@ -0,0 +1,147 @@ +/* + * 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.polygene.spi.serialization; + +import java.io.ByteArrayOutputStream; +import java.io.OutputStream; +import java.io.StringWriter; +import java.io.Writer; +import java.util.function.Function; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; +import org.apache.polygene.api.common.Optional; +import org.apache.polygene.api.serialization.Serializer; + +public abstract class AbstractSerializer implements Serializer +{ + @Override + public void serialize( Writer writer, @Optional Object object ) + { + serialize( Options.DEFAULT, writer, object ); + } + + @Override + public void serialize( OutputStream output, @Optional Object object ) + { + serialize( Options.DEFAULT, output, object ); + } + + @Override + public String serialize( Options options, @Optional Object object ) + { + StringWriter writer = new StringWriter(); + serialize( options, writer, object ); + return writer.toString(); + } + + @Override + public String serialize( @Optional Object object ) + { + return serialize( Options.DEFAULT, object ); + } + + @Override + public <T> Function<T, String> serializeFunction( Options options ) + { + return object -> serialize( options, object ); + } + + @Override + public <T> Function<T, String> serializeFunction() + { + return object -> serialize( Options.DEFAULT, object ); + } + + @Override + public Stream<String> serializeEach( Options options, Iterable<Object> objects ) + { + return StreamSupport.stream( objects.spliterator(), false ) + .map( object -> serialize( options, object ) ); + } + + @Override + public Stream<String> serializeEach( Iterable<Object> objects ) + { + return StreamSupport.stream( objects.spliterator(), false ) + .map( object -> serialize( Options.DEFAULT, object ) ); + } + + @Override + public Stream<String> serializeEach( Options options, Object... objects ) + { + return Stream.of( objects ).map( object -> serialize( options, object ) ); + } + + @Override + public Stream<String> serializeEach( Object... objects ) + { + return Stream.of( objects ).map( object -> serialize( Options.DEFAULT, object ) ); + } + + @Override + public byte[] toBytes( Options options, @Optional Object object ) + { + ByteArrayOutputStream output = new ByteArrayOutputStream(); + serialize( options, output, object ); + return output.toByteArray(); + } + + @Override + public byte[] toBytes( @Optional Object object ) + { + return toBytes( Options.DEFAULT, object ); + } + + @Override + public <T> Function<T, byte[]> toBytesFunction( Options options ) + { + return object -> toBytes( options, object ); + } + + @Override + public <T> Function<T, byte[]> toBytesFunction() + { + return object -> toBytes( Options.DEFAULT, object ); + } + + @Override + public Stream<byte[]> toBytesEach( Options options, Iterable<Object> objects ) + { + return StreamSupport.stream( objects.spliterator(), false ) + .map( object -> toBytes( options, object ) ); + } + + @Override + public Stream<byte[]> toBytesEach( Iterable<Object> objects ) + { + return StreamSupport.stream( objects.spliterator(), false ) + .map( object -> toBytes( Options.DEFAULT, object ) ); + } + + @Override + public Stream<byte[]> toBytesEach( Options options, Object... objects ) + { + return Stream.of( objects ).map( object -> toBytes( options, object ) ); + } + + @Override + public Stream<byte[]> toBytesEach( Object... objects ) + { + return Stream.of( objects ).map( object -> toBytes( Options.DEFAULT, object ) ); + } +} http://git-wip-us.apache.org/repos/asf/polygene-java/blob/e4cca11e/core/spi/src/main/java/org/apache/polygene/spi/serialization/AbstractTextDeserializer.java ---------------------------------------------------------------------- diff --git a/core/spi/src/main/java/org/apache/polygene/spi/serialization/AbstractTextDeserializer.java b/core/spi/src/main/java/org/apache/polygene/spi/serialization/AbstractTextDeserializer.java new file mode 100644 index 0000000..0575489 --- /dev/null +++ b/core/spi/src/main/java/org/apache/polygene/spi/serialization/AbstractTextDeserializer.java @@ -0,0 +1,34 @@ +/* + * 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.polygene.spi.serialization; + +import java.io.InputStream; +import java.io.InputStreamReader; +import org.apache.polygene.api.structure.ModuleDescriptor; +import org.apache.polygene.api.type.ValueType; + +import static java.nio.charset.StandardCharsets.UTF_8; + +public abstract class AbstractTextDeserializer extends AbstractDeserializer +{ + @Override + public <T> T deserialize( ModuleDescriptor module, ValueType valueType, InputStream state ) + { + return deserialize( module, valueType, new InputStreamReader( state, UTF_8 ) ); + } +} http://git-wip-us.apache.org/repos/asf/polygene-java/blob/e4cca11e/core/spi/src/main/java/org/apache/polygene/spi/serialization/AbstractTextSerializer.java ---------------------------------------------------------------------- diff --git a/core/spi/src/main/java/org/apache/polygene/spi/serialization/AbstractTextSerializer.java b/core/spi/src/main/java/org/apache/polygene/spi/serialization/AbstractTextSerializer.java new file mode 100644 index 0000000..3d8bb16 --- /dev/null +++ b/core/spi/src/main/java/org/apache/polygene/spi/serialization/AbstractTextSerializer.java @@ -0,0 +1,48 @@ +/* + * 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.polygene.spi.serialization; + +import java.io.IOException; +import java.io.OutputStream; +import java.io.StringWriter; +import java.io.UncheckedIOException; +import org.apache.polygene.api.common.Optional; + +import static java.nio.charset.StandardCharsets.UTF_8; + +/** + * Base Text Serializer. + * + * Implementations work on Strings, this base serializer encode these strings in UTF-8 to produce bytes. + */ +public abstract class AbstractTextSerializer extends AbstractSerializer +{ + public void serialize( Options options, OutputStream output, @Optional Object object ) + { + try + { + StringWriter writer = new StringWriter(); + serialize( options, writer, object ); + output.write( writer.toString().getBytes( UTF_8 ) ); + } + catch( IOException ex ) + { + throw new UncheckedIOException( ex ); + } + } +} http://git-wip-us.apache.org/repos/asf/polygene-java/blob/e4cca11e/core/spi/src/main/java/org/apache/polygene/spi/serialization/JsonDeserializer.java ---------------------------------------------------------------------- diff --git a/core/spi/src/main/java/org/apache/polygene/spi/serialization/JsonDeserializer.java b/core/spi/src/main/java/org/apache/polygene/spi/serialization/JsonDeserializer.java new file mode 100644 index 0000000..8bf3584 --- /dev/null +++ b/core/spi/src/main/java/org/apache/polygene/spi/serialization/JsonDeserializer.java @@ -0,0 +1,162 @@ +/* + * 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.polygene.spi.serialization; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.Reader; +import java.io.StringReader; +import java.io.UncheckedIOException; +import java.util.function.Function; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; +import javax.json.Json; +import javax.json.JsonObject; +import javax.json.JsonReader; +import javax.json.JsonValue; +import javax.json.stream.JsonParser; +import javax.json.stream.JsonParsingException; +import org.apache.polygene.api.serialization.Deserializer; +import org.apache.polygene.api.structure.ModuleDescriptor; +import org.apache.polygene.api.type.ValueType; +import org.apache.polygene.spi.module.ModuleSpi; + +import static java.util.stream.Collectors.joining; + +public interface JsonDeserializer extends Deserializer +{ + <T> T fromJson( ModuleDescriptor module, ValueType valueType, JsonValue state ); + + default <T> Function<JsonValue, T> fromJsonFunction( ModuleDescriptor module, ValueType valueType ) + { + return state -> fromJson( module, valueType, state ); + } + + default <T> Stream<T> fromJsonEach( ModuleDescriptor module, ValueType valueType, Stream<JsonValue> states ) + { + return states.map( fromJsonFunction( module, valueType ) ); + } + + default <T> Stream<T> fromJsonEach( ModuleDescriptor module, ValueType valueType, Iterable<JsonValue> states ) + { + return fromJsonEach( module, valueType, StreamSupport.stream( states.spliterator(), false ) ); + } + + default <T> Stream<T> fromJsonEach( ModuleDescriptor module, ValueType valueType, JsonValue... states ) + { + return fromJsonEach( module, valueType, Stream.of( states ) ); + } + + default <T> T fromJson( ModuleDescriptor module, Class<T> type, JsonValue state ) + { + // TODO Remove (ModuleSpi) cast + ValueType valueType = ( (ModuleSpi) module.instance() ).valueTypeFactory().valueTypeOf( module, type ); + return fromJson( module, valueType, state ); + } + + default <T> Function<JsonValue, T> fromJson( ModuleDescriptor module, Class<T> type ) + { + return state -> fromJson( module, type, state ); + } + + default <T> Stream<T> fromJsonEach( ModuleDescriptor module, Class<T> valueType, Stream<JsonValue> states ) + { + return states.map( fromJson( module, valueType ) ); + } + + default <T> Stream<T> fromJsonEach( ModuleDescriptor module, Class<T> valueType, Iterable<JsonValue> states ) + { + return fromJsonEach( module, valueType, StreamSupport.stream( states.spliterator(), false ) ); + } + + default <T> Stream<T> fromJsonEach( ModuleDescriptor module, Class<T> valueType, JsonValue... states ) + { + return fromJsonEach( module, valueType, Stream.of( states ) ); + } + + @Override + default <T> T deserialize( ModuleDescriptor module, ValueType valueType, Reader state ) + { + // JSR-353 Does not allow reading "out of structure" values + // See https://www.jcp.org/en/jsr/detail?id=353 + // And commented JsonReader#readValue() method in the javax.json API + // BUT, it will be part of the JsonReader contract in the next version + // See https://www.jcp.org/en/jsr/detail?id=374 + // Implementation by provider is optional though, so we'll always need a default implementation here. + // Fortunately, JsonParser has new methods allowing to read structures while parsing so it will be easy to do. + // In the meantime, a poor man's implementation will do. + // TODO Revisit values out of structure JSON deserialization when JSR-374 is out + String stateString; + try( BufferedReader buffer = new BufferedReader( state ) ) + { + stateString = buffer.lines().collect( joining( "\n" ) ); + } + catch( IOException ex ) + { + throw new UncheckedIOException( ex ); + } + // We want plain Strings, BigDecimals, BigIntegers to be deserialized even when unquoted + Function<String, T> plainValueFunction = string -> + { + String poorMans = "{\"value\":" + string + "}"; + JsonObject poorMansJson = Json.createReader( new StringReader( poorMans ) ).readObject(); + JsonValue value = poorMansJson.get( "value" ); + return fromJson( module, valueType, value ); + }; + Function<String, T> outOfStructureFunction = string -> + { + // Is this an unquoted plain value? + try + { + return plainValueFunction.apply( '"' + string + '"' ); + } + catch( JsonParsingException ex ) + { + return plainValueFunction.apply( string ); + } + }; + try( JsonParser parser = Json.createParser( new StringReader( stateString ) ) ) + { + if( parser.hasNext() ) + { + JsonParser.Event e = parser.next(); + if( e == JsonParser.Event.START_ARRAY || e == JsonParser.Event.START_OBJECT ) + { + // JSON Structure + try( JsonReader reader = Json.createReader( new StringReader( stateString ) ) ) + { + return fromJson( module, valueType, reader.read() ); + } + } + else + { + // JSON Value out of structure + return outOfStructureFunction.apply( stateString ); + } + } + } + catch( JsonParsingException ex ) + { + return outOfStructureFunction.apply( stateString ); + } + // Empty state string? + JsonValue emptyJsonString = Json.createReader( new StringReader( "{\"empty\":\"\"}" ) ) + .readObject().get( "empty" ); + return fromJson( module, valueType, emptyJsonString ); + } +} http://git-wip-us.apache.org/repos/asf/polygene-java/blob/e4cca11e/core/spi/src/main/java/org/apache/polygene/spi/serialization/JsonSerialization.java ---------------------------------------------------------------------- diff --git a/core/spi/src/main/java/org/apache/polygene/spi/serialization/JsonSerialization.java b/core/spi/src/main/java/org/apache/polygene/spi/serialization/JsonSerialization.java new file mode 100644 index 0000000..a98e70f --- /dev/null +++ b/core/spi/src/main/java/org/apache/polygene/spi/serialization/JsonSerialization.java @@ -0,0 +1,24 @@ +/* + * 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.polygene.spi.serialization; + +import org.apache.polygene.api.serialization.Serialization; + +public interface JsonSerialization extends Serialization, JsonSerializer, JsonDeserializer +{ +} http://git-wip-us.apache.org/repos/asf/polygene-java/blob/e4cca11e/core/spi/src/main/java/org/apache/polygene/spi/serialization/JsonSerializer.java ---------------------------------------------------------------------- diff --git a/core/spi/src/main/java/org/apache/polygene/spi/serialization/JsonSerializer.java b/core/spi/src/main/java/org/apache/polygene/spi/serialization/JsonSerializer.java new file mode 100644 index 0000000..b64f240 --- /dev/null +++ b/core/spi/src/main/java/org/apache/polygene/spi/serialization/JsonSerializer.java @@ -0,0 +1,106 @@ +/* + * 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.polygene.spi.serialization; + +import java.io.IOException; +import java.io.UncheckedIOException; +import java.io.Writer; +import java.util.function.Function; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; +import javax.json.JsonString; +import javax.json.JsonValue; +import org.apache.polygene.api.common.Optional; +import org.apache.polygene.api.serialization.Serializer; + +public interface JsonSerializer extends Serializer +{ + <T> Function<T, JsonValue> toJsonFunction( Options options ); + + default <T> Function<T, JsonValue> toJsonFunction() + { + return object -> toJsonFunction( Options.DEFAULT ).apply( object ); + } + + default JsonValue toJson( Options options, @Optional Object object ) + { + return toJsonFunction( options ).apply( object ); + } + + default JsonValue toJson( @Optional Object object ) + { + return toJsonFunction( Options.DEFAULT ).apply( object ); + } + + default <T> Stream<JsonValue> toJsonEach( Options options, Stream<T> objects ) + { + return objects.map( toJsonFunction( options ) ); + } + + default <T> Stream<JsonValue> toJsonEach( Options options, Iterable<T> objects ) + { + return toJsonEach( options, StreamSupport.stream( objects.spliterator(), false ) ); + } + + default <T> Stream<JsonValue> toJsonEach( Options options, Object... objects ) + { + return toJsonEach( options, Stream.of( objects ) ); + } + + default <T> Stream<JsonValue> toJsonEach( Stream<T> objects ) + { + return objects.map( toJsonFunction( Options.DEFAULT ) ); + } + + default <T> Stream<JsonValue> toJsonEach( Iterable<T> objects ) + { + return toJsonEach( Options.DEFAULT, StreamSupport.stream( objects.spliterator(), false ) ); + } + + default <T> Stream<JsonValue> toJsonEach( Object... objects ) + { + return toJsonEach( Options.DEFAULT, Stream.of( objects ) ); + } + + default void serialize( Options options, Writer writer, @Optional Object object ) + { + JsonValue jsonValue = toJson( options, object ); + if( jsonValue == null ) + { + return; + } + try + { + // TODO FIX ThIS SHIT of "out of structure" value (de)serialization + // We want plain Strings to be serialized without quotes which is non JSON compliant + // See https://java.net/jira/browse/JSON_PROCESSING_SPEC-65 + if( jsonValue.getValueType() == JsonValue.ValueType.STRING ) + { + writer.write( ( (JsonString) jsonValue ).getString() ); + } + else + { + writer.write( jsonValue.toString() ); + } + } + catch( IOException ex ) + { + throw new UncheckedIOException( ex ); + } + } +} http://git-wip-us.apache.org/repos/asf/polygene-java/blob/e4cca11e/core/spi/src/main/java/org/apache/polygene/spi/serialization/XmlDeserializer.java ---------------------------------------------------------------------- diff --git a/core/spi/src/main/java/org/apache/polygene/spi/serialization/XmlDeserializer.java b/core/spi/src/main/java/org/apache/polygene/spi/serialization/XmlDeserializer.java new file mode 100644 index 0000000..c7ac42b --- /dev/null +++ b/core/spi/src/main/java/org/apache/polygene/spi/serialization/XmlDeserializer.java @@ -0,0 +1,107 @@ +/* + * 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.polygene.spi.serialization; + +import java.io.IOException; +import java.io.Reader; +import java.util.function.Function; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import org.apache.polygene.api.serialization.Deserializer; +import org.apache.polygene.api.serialization.SerializationException; +import org.apache.polygene.api.structure.ModuleDescriptor; +import org.apache.polygene.api.type.CollectionType; +import org.apache.polygene.api.type.EnumType; +import org.apache.polygene.api.type.MapType; +import org.apache.polygene.api.type.ValueCompositeType; +import org.apache.polygene.api.type.ValueType; +import org.apache.polygene.api.value.ValueDescriptor; +import org.apache.polygene.spi.module.ModuleSpi; +import org.w3c.dom.Document; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; + +public interface XmlDeserializer extends Deserializer +{ + <T> T fromXml( ModuleDescriptor module, ValueType valueType, Document state ); + + default <T> Function<Document, T> fromXmlFunction( ModuleDescriptor module, ValueType valueType ) + { + return state -> fromXml( module, valueType, state ); + } + + default <T> Stream<T> fromXmlEach( ModuleDescriptor module, ValueType valueType, Stream<Document> states ) + { + return states.map( fromXmlFunction( module, valueType ) ); + } + + default <T> Stream<T> fromXmlEach( ModuleDescriptor module, ValueType valueType, Iterable<Document> states ) + { + return fromXmlEach( module, valueType, StreamSupport.stream( states.spliterator(), false ) ); + } + + default <T> Stream<T> fromXmlEach( ModuleDescriptor module, ValueType valueType, Document... states ) + { + return fromXmlEach( module, valueType, Stream.of( states ) ); + } + + default <T> T fromXml( ModuleDescriptor module, Class<T> type, Document state ) + { + // TODO Remove (ModuleSpi) cast + ValueType valueType = ( (ModuleSpi) module.instance() ).valueTypeFactory().valueTypeOf( module, type ); + return fromXml( module, valueType, state ); + } + + default <T> Function<Document, T> fromXml( ModuleDescriptor module, Class<T> type ) + { + return state -> fromXml( module, type, state ); + } + + default <T> Stream<T> fromXmlEach( ModuleDescriptor module, Class<T> valueType, Stream<Document> states ) + { + return states.map( fromXml( module, valueType ) ); + } + + default <T> Stream<T> fromXmlEach( ModuleDescriptor module, Class<T> valueType, Iterable<Document> states ) + { + return fromXmlEach( module, valueType, StreamSupport.stream( states.spliterator(), false ) ); + } + + default <T> Stream<T> fromXmlEach( ModuleDescriptor module, Class<T> valueType, Document... states ) + { + return fromXmlEach( module, valueType, Stream.of( states ) ); + } + + @Override + default <T> T deserialize( ModuleDescriptor module, ValueType valueType, Reader state ) + { + try + { + DocumentBuilder docBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder(); + Document doc = docBuilder.parse( new InputSource( state ) ); + return fromXml( module, valueType, doc ); + } + catch( SAXException | IOException | ParserConfigurationException ex ) + { + throw new SerializationException( "Unable to read XML document", ex ); + } + } +} http://git-wip-us.apache.org/repos/asf/polygene-java/blob/e4cca11e/core/spi/src/main/java/org/apache/polygene/spi/serialization/XmlSerialization.java ---------------------------------------------------------------------- diff --git a/core/spi/src/main/java/org/apache/polygene/spi/serialization/XmlSerialization.java b/core/spi/src/main/java/org/apache/polygene/spi/serialization/XmlSerialization.java new file mode 100644 index 0000000..12fda54 --- /dev/null +++ b/core/spi/src/main/java/org/apache/polygene/spi/serialization/XmlSerialization.java @@ -0,0 +1,24 @@ +/* + * 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.polygene.spi.serialization; + +import org.apache.polygene.api.serialization.Serialization; + +public interface XmlSerialization extends Serialization, XmlSerializer, XmlDeserializer +{ +} http://git-wip-us.apache.org/repos/asf/polygene-java/blob/e4cca11e/core/spi/src/main/java/org/apache/polygene/spi/serialization/XmlSerializer.java ---------------------------------------------------------------------- diff --git a/core/spi/src/main/java/org/apache/polygene/spi/serialization/XmlSerializer.java b/core/spi/src/main/java/org/apache/polygene/spi/serialization/XmlSerializer.java new file mode 100644 index 0000000..e244d63 --- /dev/null +++ b/core/spi/src/main/java/org/apache/polygene/spi/serialization/XmlSerializer.java @@ -0,0 +1,118 @@ +/* + * 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.polygene.spi.serialization; + +import java.io.IOException; +import java.io.UncheckedIOException; +import java.io.Writer; +import java.util.function.Function; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerException; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamResult; +import org.apache.polygene.api.common.Optional; +import org.apache.polygene.api.serialization.SerializationException; +import org.apache.polygene.api.serialization.Serializer; +import org.w3c.dom.Document; +import org.w3c.dom.Node; + +/** + * XML State Serializer. + */ +public interface XmlSerializer extends Serializer +{ + <T> Function<T, Document> toXmlFunction( Options options ); + + default <T> Function<T, Document> toXmlFunction() + { + return object -> toXmlFunction( Options.DEFAULT ).apply( object ); + } + + default Document toXml( Options options, @Optional Object object ) + { + return toXmlFunction( options ).apply( object ); + } + + default Document toXml( @Optional Object object ) + { + return toXmlFunction( Options.DEFAULT ).apply( object ); + } + + default <T> Stream<Document> toXmlEach( Options options, Stream<T> objects ) + { + return objects.map( toXmlFunction( options ) ); + } + + default <T> Stream<Document> toXmlEach( Options options, Iterable<T> objects ) + { + return toXmlEach( options, StreamSupport.stream( objects.spliterator(), false ) ); + } + + default <T> Stream<Document> toXmlEach( Options options, Object... objects ) + { + return toXmlEach( options, Stream.of( objects ) ); + } + + default <T> Stream<Document> toXmlEach( Stream<T> objects ) + { + return objects.map( toXmlFunction( Options.DEFAULT ) ); + } + + default <T> Stream<Document> toXmlEach( Iterable<T> objects ) + { + return toXmlEach( Options.DEFAULT, StreamSupport.stream( objects.spliterator(), false ) ); + } + + default <T> Stream<Document> toXmlEach( Object... objects ) + { + return toXmlEach( Options.DEFAULT, Stream.of( objects ) ); + } + + default void serialize( Options options, Writer writer, @Optional Object object ) + { + Document xmlDocument = toXml( options, object ); + if( xmlDocument == null ) + { + return; + } + try + { + // We want plain Strings to be serialized without quotes + if( xmlDocument.getNodeType() == Node.TEXT_NODE ) + { + writer.write( xmlDocument.getNodeValue() ); + } + else + { + Transformer transformer = TransformerFactory.newInstance().newTransformer(); + transformer.transform( new DOMSource( xmlDocument ), new StreamResult( writer ) ); + } + } + catch( IOException ex ) + { + throw new UncheckedIOException( ex ); + } + catch( TransformerException ex ) + { + throw new SerializationException( "Unable to transform XML Document to String", ex ); + } + } +} http://git-wip-us.apache.org/repos/asf/polygene-java/blob/e4cca11e/core/spi/src/main/java/org/apache/polygene/spi/serialization/package.html ---------------------------------------------------------------------- diff --git a/core/spi/src/main/java/org/apache/polygene/spi/serialization/package.html b/core/spi/src/main/java/org/apache/polygene/spi/serialization/package.html new file mode 100644 index 0000000..2e2f188 --- /dev/null +++ b/core/spi/src/main/java/org/apache/polygene/spi/serialization/package.html @@ -0,0 +1,24 @@ +<!-- + ~ 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. + ~ + ~ + --> +<html> + <body> + <h2>Serialization SPI.</h2> + </body> +</html> http://git-wip-us.apache.org/repos/asf/polygene-java/blob/e4cca11e/core/spi/src/main/java/org/apache/polygene/spi/types/ValueTypeFactory.java ---------------------------------------------------------------------- diff --git a/core/spi/src/main/java/org/apache/polygene/spi/types/ValueTypeFactory.java b/core/spi/src/main/java/org/apache/polygene/spi/types/ValueTypeFactory.java new file mode 100644 index 0000000..efe2c3b --- /dev/null +++ b/core/spi/src/main/java/org/apache/polygene/spi/types/ValueTypeFactory.java @@ -0,0 +1,11 @@ +package org.apache.polygene.spi.types; + +import org.apache.polygene.api.structure.ModuleDescriptor; +import org.apache.polygene.api.type.ValueType; + +public interface ValueTypeFactory +{ + ValueType valueTypeOf( ModuleDescriptor module, Object object ); + + ValueType valueTypeOf( ModuleDescriptor module, Class<?> type ); +} http://git-wip-us.apache.org/repos/asf/polygene-java/blob/e4cca11e/core/spi/src/main/java/org/apache/polygene/spi/value/ValueDeserializerAdapter.java ---------------------------------------------------------------------- diff --git a/core/spi/src/main/java/org/apache/polygene/spi/value/ValueDeserializerAdapter.java b/core/spi/src/main/java/org/apache/polygene/spi/value/ValueDeserializerAdapter.java deleted file mode 100644 index cac2270..0000000 --- a/core/spi/src/main/java/org/apache/polygene/spi/value/ValueDeserializerAdapter.java +++ /dev/null @@ -1,1000 +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.polygene.spi.value; - -import java.io.ByteArrayInputStream; -import java.io.InputStream; -import java.io.ObjectInputStream; -import java.math.BigDecimal; -import java.math.BigInteger; -import java.time.Duration; -import java.time.Instant; -import java.time.LocalDate; -import java.time.LocalDateTime; -import java.time.LocalTime; -import java.time.OffsetDateTime; -import java.time.Period; -import java.time.ZonedDateTime; -import java.util.ArrayList; -import java.util.Base64; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; -import java.util.Scanner; -import java.util.Set; -import java.util.function.Function; -import java.util.stream.Stream; -import java.util.stream.StreamSupport; -import org.apache.polygene.api.entity.EntityReference; -import org.apache.polygene.api.identity.Identity; -import org.apache.polygene.api.identity.StringIdentity; -import org.apache.polygene.api.structure.ModuleDescriptor; -import org.apache.polygene.api.type.CollectionType; -import org.apache.polygene.api.type.EnumType; -import org.apache.polygene.api.type.MapType; -import org.apache.polygene.api.type.Serialization; -import org.apache.polygene.api.type.ValueCompositeType; -import org.apache.polygene.api.type.ValueType; -import org.apache.polygene.api.value.ValueBuilder; -import org.apache.polygene.api.value.ValueDescriptor; -import org.apache.polygene.api.value.ValueDeserializer; -import org.apache.polygene.api.value.ValueSerializationException; - -/** - * Adapter for pull-parsing and tree-parsing capable ValueDeserializers. - * - * <p> - * Among Plain values (see {@link ValueDeserializer}) some are considered primitives to underlying serialization - * mechanisms and by so handed/come without conversion to/from implementations. Primitive values can be one of: - * </p> - * <ul> - * <li>String,</li> - * <li>Character or char,</li> - * <li>Boolean or boolean,</li> - * <li>Integer or int,</li> - * <li>Long or long,</li> - * <li>Short or short,</li> - * <li>Byte or byte,</li> - * <li>Float or float,</li> - * <li>Double or double.</li> - * </ul> - * <p> - * Some other Plain values are expected in given formats: - * </p> - * <ul> - * <li>BigInteger and BigDecimal depends on {@link org.apache.polygene.api.value.ValueSerializer.Options};</li> - * <li>Date as String in ISO-8601, {@literal @millis@} or {@literal /Date(..)} Microsoft format;</li> - * <li>DateTime (JodaTime) as a ISO-8601 String with optional timezone offset;</li> - * <li>LocalDateTime (JodaTime) as whatever {@link LocalDateTime#parse} accept as {@literal instant};</li> - * <li>LocalDate (JodaTime) as whatever {@link LocalDate#parse} accept as {@literal instant};</li> - * </ul> - * - * @param <InputType> Implementor pull-parser type - * @param <InputNodeType> Implementor tree-parser node type - */ -public abstract class ValueDeserializerAdapter<InputType, InputNodeType> - implements ValueDeserializer -{ - public interface ComplexDeserializer<T, InputType, InputNodeType> - { - T deserializePull( InputType input ) - throws Exception; - - T deserializeTree( InputNodeType inputNode ) - throws Exception; - } - - private static final String UTF_8 = "UTF-8"; - private final Map<Class<?>, Function<Object, Object>> deserializers = new HashMap<>( 16 ); - private final Map<Class<?>, ComplexDeserializer<Object, InputType, InputNodeType>> complexDeserializers = new HashMap<>( 2 ); - - /** - * Register a Plain Value type deserialization Function. - * - * @param <T> Plain Value parametrized Type - * @param type Plain Value Type - * @param deserializer Deserialization Function - */ - @SuppressWarnings( "unchecked" ) - protected final <T> void registerDeserializer( Class<T> type, Function<Object, T> deserializer ) - { - deserializers.put( type, (Function<Object, Object>) deserializer ); - } - - @SuppressWarnings( { "UnusedDeclaration", "unchecked" } ) - protected final <T> void registerComplexDeserializer( Class<T> type, - ComplexDeserializer<T, InputType, InputNodeType> deserializer - ) - { - complexDeserializers.put( type, (ComplexDeserializer<Object, InputType, InputNodeType>) deserializer ); - } - - protected ValueDeserializerAdapter() - { - - // Primitive Value types - registerDeserializer( String.class, Object::toString ); - registerDeserializer( Character.class, input -> input.toString().charAt( 0 ) ); - registerDeserializer( Boolean.class, input -> ( input instanceof String ) - ? Boolean.parseBoolean( (String) input ) - : (Boolean) input ); - registerDeserializer( Integer.class, input -> ( input instanceof String ) - ? Integer.parseInt( (String) input ) - : ( (Number) input ).intValue() ); - registerDeserializer( Long.class, input -> ( input instanceof String ) - ? Long.parseLong( (String) input ) - : ( (Number) input ).longValue() ); - registerDeserializer( Short.class, input -> ( input instanceof String ) - ? Short.parseShort( (String) input ) - : ( (Number) input ).shortValue() ); - registerDeserializer( Byte.class, input -> ( input instanceof String ) - ? Byte.parseByte( (String) input ) - : ( (Number) input ).byteValue() ); - registerDeserializer( Float.class, input -> ( input instanceof String ) - ? Float.parseFloat( (String) input ) - : ( (Number) input ).floatValue() ); - registerDeserializer( Double.class, input -> ( input instanceof String ) - ? Double.parseDouble( (String) input ) - : ( (Number) input ).doubleValue() ); - - // Number types - registerDeserializer( BigDecimal.class, input -> new BigDecimal( input.toString() ) ); - registerDeserializer( BigInteger.class, input -> new BigInteger( input.toString() ) ); - registerDeserializer( Identity.class, input -> StringIdentity.fromString( input.toString() ) ); - - // Date types - registerDeserializer( Instant.class, input -> Instant.parse( input.toString() ) ); - registerDeserializer( ZonedDateTime.class, input -> ZonedDateTime.parse( input.toString() ) ); - registerDeserializer( OffsetDateTime.class, input -> OffsetDateTime.parse( input.toString() ) ); - registerDeserializer( LocalDateTime.class, input -> LocalDateTime.parse( input.toString() ) ); - registerDeserializer( LocalDate.class, input -> LocalDate.parse( input.toString() )); - registerDeserializer( LocalTime.class, input -> LocalTime.parse( input.toString() )); - registerDeserializer( Duration.class, input -> Duration.parse( input.toString() )); - registerDeserializer( Period.class, input -> Period.parse( input.toString() )); - - // Other supported types - registerDeserializer( EntityReference.class, input -> EntityReference.parseEntityReference( input.toString() ) ); - } - - @Override - public <T> Function<String, T> deserialize( ModuleDescriptor module, Class<T> type ) - { - if( CollectionType.isCollection( type ) ) - { - ValueType objectValueType = new ValueType( Object.class ); - return deserialize( module, new CollectionType( type, objectValueType ) ); - } - if( MapType.isMap( type ) ) - { - ValueType objectValueType = new ValueType( Object.class ); - return deserialize( module, new MapType( type, objectValueType, objectValueType ) ); - } - return deserialize( module, new ValueType( type ) ); - } - - @Override - public final <T> Function<String, T> deserialize( ModuleDescriptor module, ValueType valueType ) - { - return input -> deserialize( module, valueType, input ); - } - - @Override - public final <T> T deserialize( ModuleDescriptor module, Class<?> type, String input ) - throws ValueSerializationException - { - if( CollectionType.isCollection( type ) ) - { - ValueType objectValueType = new ValueType( Object.class ); - return deserialize( module, new CollectionType( type, objectValueType ), input ); - } - if( MapType.isMap( type ) ) - { - ValueType objectValueType = new ValueType( Object.class ); - return deserialize( module, new MapType( type, objectValueType, objectValueType ), input ); - } - return deserialize( module, new ValueType( type ), input ); - } - - @Override - public final <T> T deserialize( ModuleDescriptor module, ValueType valueType, String input ) - throws ValueSerializationException - { - try - { - return deserializeRoot( module, valueType, new ByteArrayInputStream( input.getBytes( UTF_8 ) ) ); - } - catch( ValueSerializationException ex ) - { - throw ex; - } - catch( Exception ex ) - { - throw new ValueSerializationException( "Could not deserialize value", ex ); - } - } - - @Override - public final <T> T deserialize( ModuleDescriptor module, Class<?> type, InputStream input ) - throws ValueSerializationException - { - if( CollectionType.isCollection( type ) ) - { - ValueType objectValueType = new ValueType( Object.class ); - return deserialize( module, new CollectionType( type, objectValueType ), input ); - } - if( MapType.isMap( type ) ) - { - ValueType objectValueType = new ValueType( Object.class ); - return deserialize( module, new MapType( type, objectValueType, objectValueType ), input ); - } - return deserialize( module, new ValueType( type ), input ); - } - - @Override - public final <T> T deserialize( ModuleDescriptor module, ValueType valueType, InputStream input ) - throws ValueSerializationException - { - try - { - return deserializeRoot( module, valueType, input ); - } - catch( ValueSerializationException ex ) - { - throw ex; - } - catch( Exception ex ) - { - throw new ValueSerializationException( "Could not deserialize value", ex ); - } - } - - @SuppressWarnings( "unchecked" ) - private <T> T deserializeRoot( ModuleDescriptor module, ValueType valueType, InputStream input ) - throws Exception - { - Class<?> type = valueType.types().findFirst().orElse( null ); - - if( Identity.class.isAssignableFrom( type ) ) - { - type = Identity.class; - } - - // Plain ValueType - Function<Object, Object> deserializationFunction = deserializers.get( type ); - if( deserializationFunction != null ) - { - Scanner scanner = new Scanner( input, UTF_8 ).useDelimiter( "\\A" ); - if( !scanner.hasNext() ) - { - return String.class.equals( type ) ? (T) "" : null; - } - String string = scanner.next(); - return (T) deserializationFunction.apply( string ); - } - else // Array ValueType - if( type.isArray() ) - { - Scanner scanner = new Scanner( input, UTF_8 ).useDelimiter( "\\A" ); - if( !scanner.hasNext() ) - { - return null; - } - String string = scanner.next(); - return (T) deserializeBase64Serialized( module, string ); - } - else if( type.isEnum() ) - { - Scanner scanner = new Scanner( input, UTF_8 ).useDelimiter( "\\A" ); - if( !scanner.hasNext() ) - { - return String.class.equals( type ) ? (T) "" : null; - } - String string = scanner.next(); - return (T) Enum.valueOf( (Class) type, string ); - } - else // Complex ValueType - { - InputType adaptedInput = adaptInput( module, input ); - onDeserializationStart( module, valueType, adaptedInput ); - T deserialized = doDeserialize( module, valueType, adaptedInput ); - onDeserializationEnd( module, valueType, adaptedInput ); - return deserialized; - } - } - - @SuppressWarnings( "unchecked" ) - private <T> T doDeserialize( ModuleDescriptor module, ValueType valueType, InputType input ) - throws Exception - { - final Class<?> type = valueType.types().findFirst().orElse( null ); - // Registered deserializers - if( deserializers.get( type ) != null ) - { - Object value = readPlainValue( module, input ); - if( value == null ) - { - return null; - } - return (T) deserializers.get( type ).apply( value ); - } - else if( complexDeserializers.get( type ) != null ) - { - return (T) complexDeserializers.get( type ).deserializePull( input ); - } - else // Explicit ValueComposite - if( ValueCompositeType.class.isAssignableFrom( valueType.getClass() ) ) - { - return (T) deserializeValueComposite( module, valueType, input ); - } - else // Explicit Collections - if( CollectionType.class.isAssignableFrom( valueType.getClass() ) ) - { - return (T) deserializeCollection( module, (CollectionType) valueType, input ); - } - else // Explicit Map - if( MapType.class.isAssignableFrom( valueType.getClass() ) ) - { - return (T) deserializeMap( module, (MapType) valueType, input ); - } - else // Enum - if( EnumType.class.isAssignableFrom( valueType.getClass() ) || type.isEnum() ) - { - return (T) Enum.valueOf( (Class) type, readPlainValue( module, input ).toString() ); - } - else // Array - if( type.isArray() ) - { - return (T) deserializeBase64Serialized( module, readPlainValue( module, input ).toString() ); - } - // Guessed Deserialization - return (T) deserializeGuessed( module, valueType, input ); - } - - private <T> Function<InputType, T> buildDeserializeInputFunction( ModuleDescriptor module, ValueType valueType ) - { - return input -> { - try - { - return doDeserialize( module, valueType, input ); - } - catch( ValueSerializationException ex ) - { - throw ex; - } - catch( Exception ex ) - { - throw new ValueSerializationException( ex ); - } - }; - } - - private <T> Collection<T> deserializeCollection( ModuleDescriptor module, CollectionType collectionType, InputType input ) - throws Exception - { - Collection<T> collection; - Class<?> collectionMainType = collectionType.types().findFirst().orElse( null ); - if( Set.class.equals( collectionMainType ) ) - { - collection = new LinkedHashSet<>(); - } - else - { - collection = new ArrayList<>(); - } - return readArrayInCollection( module, - input, - this.buildDeserializeInputFunction( module, collectionType.collectedType() ), - collection ); - } - - private <K, V> Map<K, V> deserializeMap( ModuleDescriptor module, MapType mapType, InputType input ) - throws Exception - { - return readMapInMap( module, - input, - this.<K>buildDeserializeInputFunction( module, mapType.keyType() ), - this.<V>buildDeserializeInputFunction( module, mapType.valueType() ), - new HashMap<>() ); - } - - private <T> T deserializeValueComposite( ModuleDescriptor module, ValueType valueType, InputType input ) - throws Exception - { - InputNodeType inputNode = readObjectTree( module, input ); - if( inputNode == null ) - { - return null; - } - return deserializeNodeValueComposite( module, valueType, inputNode ); - } - - private <T> T deserializeNodeValueComposite( ModuleDescriptor module, ValueType valueType, InputNodeType inputNode ) - throws Exception - { - ValueCompositeType valueCompositeType = (ValueCompositeType) valueType; - Class<?> valueBuilderType = valueCompositeType.types().findFirst().orElse( null ); - String typeInfo = this.getObjectFieldValue( - module, - inputNode, - "_type", - this.<String>buildDeserializeInputNodeFunction( module, new ValueType( String.class ) ) ); - if( typeInfo != null ) - { - ValueDescriptor valueDescriptor = module.valueDescriptor( typeInfo ); - if( valueDescriptor == null ) - { - throw new ValueSerializationException( "Specified value type could not be resolved: " + typeInfo ); - } - valueCompositeType = valueDescriptor.valueType(); - valueBuilderType = Class.forName( typeInfo ); - } - return deserializeValueComposite( module, valueCompositeType, valueBuilderType, inputNode ); - } - - @SuppressWarnings( "unchecked" ) - private <T> T deserializeValueComposite( ModuleDescriptor module, - ValueCompositeType valueCompositeType, - Class<?> valueBuilderType, - InputNodeType inputNode - ) - throws Exception - { - final Map<String, Object> stateMap = new HashMap<>(); - - // Properties - valueCompositeType.properties().forEach( property -> { - String propertyName = null; - Object value; - try - { - propertyName = property.qualifiedName().name(); - if( objectHasField( module, inputNode, propertyName ) ) - { - value = getObjectFieldValue( - module, - inputNode, - propertyName, - buildDeserializeInputNodeFunction( module, property.valueType() ) ); - if( property.isImmutable() ) - { - if( value instanceof Set ) - { - value = Collections.unmodifiableSet( (Set<?>) value ); - } - else if( value instanceof List ) - { - value = Collections.unmodifiableList( (List<?>) value ); - } - else if( value instanceof Map ) - { - value = Collections.unmodifiableMap( (Map<?, ?>) value ); - } - } - } - else - { - // Serialized object does not contain the field, try to default it - value = property.initialValue( module ); - } - } - catch( Exception e ) - { - throw new ValueSerializationException( "Unable to deserialize property " + property, e ); - } - stateMap.put( propertyName, value ); - } ); - - // Associations - valueCompositeType.associations().forEach( association -> { - try - { - String associationName = association.qualifiedName().name(); - if( objectHasField( module, inputNode, associationName ) ) - { - Object value = getObjectFieldValue( - module, - inputNode, - associationName, - buildDeserializeInputNodeFunction( module, new ValueType( EntityReference.class ) ) ); - stateMap.put( associationName, value ); - } - } - catch( Exception e ) - { - throw new ValueSerializationException( "Unable to deserialize association " + association, e ); - } - } ); - - // ManyAssociations - valueCompositeType.manyAssociations().forEach( manyAssociation -> { - try - { - String manyAssociationName = manyAssociation.qualifiedName().name(); - if( objectHasField( module, inputNode, manyAssociationName ) ) - { - Object value = getObjectFieldValue( - module, - inputNode, - manyAssociationName, - buildDeserializeInputNodeFunction( - module, - new CollectionType( - Collection.class, - new ValueType( EntityReference.class ) ) ) ); - stateMap.put( manyAssociationName, value ); - } - } - catch( Exception e ) - { - throw new ValueSerializationException( "Unable to deserialize manyassociation " + manyAssociation, e ); - } - } ); - - // NamedAssociations - valueCompositeType.namedAssociations().forEach( namedAssociation -> { - try - { - String namedAssociationName = namedAssociation.qualifiedName().name(); - if( objectHasField( module, inputNode, namedAssociationName ) ) - { - Object value = getObjectFieldValue( - module, - inputNode, - namedAssociationName, - buildDeserializeInputNodeFunction( module, MapType.of( String.class, EntityReference.class, Serialization.Variant.object ) ) ); - stateMap.put( namedAssociationName, value ); - } - } - catch( Exception e ) - { - throw new ValueSerializationException( "Unable to deserialize namedassociation " + namedAssociation, e ); - } - } ); - - ValueBuilder<?> valueBuilder = buildNewValueBuilderWithState( module, valueBuilderType, stateMap ); - return (T) valueBuilder.newInstance(); // Unchecked cast because the builder could use a type != T - } - - private <T> Function<InputNodeType, T> buildDeserializeInputNodeFunction( ModuleDescriptor module, final ValueType valueType ) - { - return inputNode -> { - try - { - return doDeserializeInputNodeValue( module, valueType, inputNode ); - } - catch( ValueSerializationException ex ) - { - throw ex; - } - catch( Exception ex ) - { - throw new ValueSerializationException( ex ); - } - }; - } - - @SuppressWarnings( "unchecked" ) - private <T> T doDeserializeInputNodeValue( ModuleDescriptor module, ValueType valueType, InputNodeType inputNode ) - throws Exception - { - if( inputNode == null ) - { - return null; - } - final Class<?> type = valueType.types().findFirst().orElse( null ); - // Registered deserializers - if( deserializers.get( type ) != null ) - { - Object value = asSimpleValue( module, inputNode ); - if( value == null ) - { - return null; - } - return (T) deserializers.get( type ).apply( value ); - } - else if( complexDeserializers.get( type ) != null ) - { - return (T) complexDeserializers.get( type ).deserializeTree( inputNode ); - } - else // Explicit ValueComposite - if( ValueCompositeType.class.isAssignableFrom( valueType.getClass() ) ) - { - return (T) deserializeNodeValueComposite( module, valueType, inputNode ); - } - else // Explicit Collections - if( CollectionType.class.isAssignableFrom( valueType.getClass() ) ) - { - return (T) deserializeNodeCollection( module, (CollectionType) valueType, inputNode ); - } - else // Explicit Map - if( MapType.class.isAssignableFrom( valueType.getClass() ) ) - { - MapType mapType = (MapType) valueType; - if( mapType.variant().equals( Serialization.Variant.entry ) ) - { - return (T) deserializeNodeEntryMap( module, (MapType) valueType, inputNode ); - } - else - { - return (T) deserializeNodeObjectMap( module, (MapType) valueType, inputNode ); - } - } - else // Enum - if( EnumType.class.isAssignableFrom( valueType.getClass() ) || type.isEnum() ) - { - Object value = asSimpleValue( module, inputNode ); - if( value == null ) - { - return null; - } - return (T) Enum.valueOf( (Class) type, value.toString() ); - } - // Guessed deserialization - return (T) deserializeNodeGuessed( module, valueType, inputNode ); - } - - private ValueBuilder<?> buildNewValueBuilderWithState( ModuleDescriptor module, - Class<?> type, - final Map<String, Object> stateMap - ) - { - return module.instance().newValueBuilderWithState( - type, - property -> stateMap.get( property.qualifiedName().name() ), - association -> { - Object entityRef = stateMap.get( association.qualifiedName().name() ); - if( entityRef == null ) - { - return null; - } - return (EntityReference) entityRef; - }, - manyAssociation -> { - Object entityRefs = stateMap.get( manyAssociation.qualifiedName().name() ); - if( entityRefs == null ) - { - return Stream.empty(); - } - //noinspection unchecked - return StreamSupport.stream( ( (Iterable<EntityReference>) entityRefs ).spliterator(), false ); - }, - namedAssociation -> { - Object entityRefs = stateMap.get( namedAssociation.qualifiedName().name() ); - if( entityRefs == null ) - { - return Stream.empty(); - } - //noinspection unchecked - return ( (Map<String, EntityReference>) entityRefs ).entrySet().stream(); - } ); - } - - @SuppressWarnings( "unchecked" ) - private <T> T deserializeGuessed( ModuleDescriptor module, ValueType valueType, InputType input ) - throws Exception - { - InputNodeType inputNode = readObjectTree( module, input ); - if( inputNode == null ) - { - return null; - } - return deserializeNodeGuessed( module, valueType, inputNode ); - } - - private <T> Collection<T> deserializeNodeCollection( ModuleDescriptor module, - CollectionType collectionType, - InputNodeType inputNode - ) - throws Exception - { - Collection<T> collection; - Class<?> collectionMainType = collectionType.types().findFirst().orElse( null ); - if( Set.class.equals( collectionMainType ) ) - { - collection = new LinkedHashSet<>(); - } - else - { - collection = new ArrayList<>(); - } - putArrayNodeInCollection( module, - inputNode, - this.buildDeserializeInputNodeFunction( module, collectionType.collectedType() ), - collection ); - return collection; - } - - private <K, V> Map<K, V> deserializeNodeEntryMap( ModuleDescriptor module, MapType mapType, InputNodeType inputNode ) - throws Exception - { - Map<K, V> map = new HashMap<>(); - putArrayNodeInMap( module, - inputNode, - this.buildDeserializeInputNodeFunction( module, mapType.keyType() ), - this.buildDeserializeInputNodeFunction( module, mapType.valueType() ), - map ); - return map; - } - - private <V> Map<String, V> deserializeNodeObjectMap( ModuleDescriptor module, MapType mapType, InputNodeType inputNode ) - throws Exception - { - Map<String, V> map = new HashMap<>(); - putObjectNodeInMap( module, - inputNode, - this.buildDeserializeInputNodeFunction( module, mapType.valueType() ), - map ); - return map; - } - - @SuppressWarnings( "unchecked" ) - private <T> T deserializeNodeGuessed( ModuleDescriptor module, ValueType valueType, InputNodeType inputNode ) - throws Exception - { - if( isObjectValue( module, inputNode ) ) - { - // Attempt ValueComposite deserialization - ValueCompositeType valueCompositeType; - if( objectHasField( module, inputNode, "_type" ) ) // with _type info - { - String typeInfo = this.getObjectFieldValue( - module, - inputNode, - "_type", - this.<String>buildDeserializeInputNodeFunction( module, new ValueType( String.class ) ) ); - ValueDescriptor valueDescriptor = module.valueDescriptor( typeInfo ); - if( valueDescriptor == null ) - { - throw new ValueSerializationException( "Specified value type could not be resolved: " + typeInfo ); - } - valueCompositeType = valueDescriptor.valueType(); - } - else // without _type info - { - ValueDescriptor valueDescriptor = module.valueDescriptor( valueType.types() - .findFirst() - .get() - .getName() ); - if( valueDescriptor == null ) - { - throw new ValueSerializationException( "Don't know how to deserialize " + inputNode ); - } - valueCompositeType = valueDescriptor.valueType(); - } - Class<?> valueBuilderType = valueCompositeType.types().findFirst().orElse( null ); - return deserializeValueComposite( module, valueCompositeType, valueBuilderType, inputNode ); - } - // Last resort : base64 java deserialization - return (T) deserializeBase64Serialized( module, inputNode ); - } - - @SuppressWarnings( "unchecked" ) - private <T> T deserializeBase64Serialized( ModuleDescriptor module, InputNodeType inputNode ) - throws Exception - { - Object value = asSimpleValue( module, inputNode ); - if( value == null ) - { - return null; - } - String base64 = value.toString(); - return deserializeBase64Serialized( module, base64 ); - } - - @SuppressWarnings( "unchecked" ) - private <T> T deserializeBase64Serialized( ModuleDescriptor module, String inputString ) - throws Exception - { - byte[] bytes = inputString.getBytes( UTF_8 ); - bytes = Base64.getDecoder().decode( bytes ); - Object result; - try (ObjectInputStream oin = new ObjectInputStream( new ByteArrayInputStream( bytes ) )) - { - result = oin.readObject(); - } - return (T) result; - } - - // - // Deserialization Extension Points - // - - /** - * Called by the adapter on deserialization start, after {@link #adaptInput(ModuleDescriptor, java.io.InputStream)}. - * - * @param module Module descriptor - * @param valueType ValueType - * @param input Input - * - * @throws Exception that will be wrapped in a {@link ValueSerializationException} - */ - @SuppressWarnings( "UnusedParameters" ) - protected void onDeserializationStart( ModuleDescriptor module, ValueType valueType, InputType input ) - throws Exception - { - // NOOP - } - - /** - * Called by the adapter on deserialization end. - * - * @param module Module descriptor - * @param valueType ValueType - * @param input Input - * - * @throws Exception that will be wrapped in a {@link ValueSerializationException} - */ - protected void onDeserializationEnd( ModuleDescriptor module, ValueType valueType, InputType input ) - throws Exception - { - // NOOP - } - - // - // Pull Parsing Deserialization - // - - /** - * This method is always called first, this is a chance to wrap the input type. - * - * @param module Module descriptor - * @param input InputStream to adapt - * - * @return Adapted input - * - * @throws Exception that will be wrapped in a {@link ValueSerializationException} - */ - protected abstract InputType adaptInput( ModuleDescriptor module, InputStream input ) - throws Exception; - - /** - * @param module Module descriptor - * @param input Input - * - * @return a Plain Value read from the input - * - * @throws Exception that will be wrapped in a {@link ValueSerializationException} - */ - protected abstract Object readPlainValue( ModuleDescriptor module, InputType input ) - throws Exception; - - /** - * @param module Module descriptor - * @param input Input - * @param deserializer Deserialization function - * @param collection Collection - * @param <T> Parameterized collection type - * - * @return The filled collection or null if no array - * - * @throws Exception that will be wrapped in a {@link ValueSerializationException} - */ - protected abstract <T> Collection<T> readArrayInCollection( ModuleDescriptor module, - InputType input, - Function<InputType, T> deserializer, - Collection<T> collection - ) - throws Exception; - - /** - * A Map<K,V> is serialized in an array of entries objects. - * - * <p>Here is an example in JSON:</p> - * <pre> - * [ - * { "key": "foo", "value": "bar" }, - * { "key": "cathedral", "value": "bazar" } - * ] - * </pre> - * <p>And an empty Map:</p> - * <pre>[]</pre> - * <p> - * This allow to use any type as keys and values while keeping the Map order at the cost of having - * non-predictible order of key/value inside an entry object. - * </p> - * - * @param module Module descriptor - * @param input Input - * @param keyDeserializer Map key deserialization function - * @param valueDeserializer Map value deserialization function - * @param map Map - * @param <K> Parameterized map key type - * @param <V> Parameterized map value type - * - * @return The filled map or null if no array - * - * @throws Exception that will be wrapped in a {@link ValueSerializationException} - */ - protected abstract <K, V> Map<K, V> readMapInMap( ModuleDescriptor module, - InputType input, - Function<InputType, K> keyDeserializer, - Function<InputType, V> valueDeserializer, - Map<K, V> map - ) - throws Exception; - - /** - * @param module Module descriptor - * @param input Input - * - * @return an InputNodeType or null if the value was null - * - * @throws Exception that will be wrapped in a {@link ValueSerializationException} - */ - protected abstract InputNodeType readObjectTree( ModuleDescriptor module, InputType input ) - throws Exception; - - // - // Tree Parsing Deserialization - // - protected abstract Object asSimpleValue( ModuleDescriptor module, InputNodeType inputNode ) - throws Exception; - - protected abstract boolean isObjectValue( ModuleDescriptor module, InputNodeType inputNode ) - throws Exception; - - protected abstract boolean objectHasField( ModuleDescriptor module, InputNodeType inputNode, String key ) - throws Exception; - - /** - * Return null if the field do not exists. - * - * @param module Module descriptor - * @param inputNode Input Node - * @param key Object key - * @param valueDeserializer Deserialization function - * @param <T> Parameterized object field value type - * - * @return The value of the field. - * - * @throws Exception that will be wrapped in a {@link ValueSerializationException} - */ - protected abstract <T> T getObjectFieldValue( ModuleDescriptor module, - InputNodeType inputNode, - String key, - Function<InputNodeType, T> valueDeserializer - ) - throws Exception; - - protected abstract <T> void putArrayNodeInCollection( ModuleDescriptor module, - InputNodeType inputNode, - Function<InputNodeType, T> deserializer, - Collection<T> collection - ) - throws Exception; - - protected abstract <K, V> void putArrayNodeInMap( ModuleDescriptor module, - InputNodeType inputNode, - Function<InputNodeType, K> keyDeserializer, - Function<InputNodeType, V> valueDeserializer, - Map<K, V> map - ) - throws Exception; - - protected abstract <V> void putObjectNodeInMap( ModuleDescriptor module, - InputNodeType inputNode, - Function<InputNodeType, V> valueDeserializer, - Map<String, V> map - ) - throws Exception; -}
