Skip to content
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions sdk/core/azure-core-experimental/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

## 1.0.0-beta.7 (2020-10-08)

- Added API `fromObject()` in `BinaryData` which uses `JsonSerializer` present in the classpath.
- Added APIs to `JsonPatchDocument` which accept pre-serialized JSON.
- Updated `azure-core` dependency to released version.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@

import com.azure.core.util.FluxUtil;
import com.azure.core.util.logging.ClientLogger;
import com.azure.core.util.serializer.JsonSerializerProviders;
import com.azure.core.util.serializer.ObjectSerializer;
import com.azure.core.util.serializer.JsonSerializer;
import com.azure.core.util.serializer.TypeReference;
import static com.azure.core.util.FluxUtil.monoError;

Expand All @@ -18,18 +20,26 @@
import java.io.InputStream;
import java.io.UncheckedIOException;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Objects;

/**
* This class is an abstraction over many different ways that binary data can be represented. The {@link BinaryData}
* can be created from {@link InputStream}, {@link Flux} of {@link ByteBuffer}, {@link String}, {@link Object} and byte
* array. The data is collected from provided sources and stored into a byte array.
* This class is an abstraction over many different ways that binary data can be represented. The data represented by
* {@link BinaryData} is immutable. The {@link BinaryData} can be created from {@link InputStream}, {@link Flux} of

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"The data represented by {@link BinaryData} is immutable" is an odd statement to make? It is taken into BinaryData and presumably copied, but when given back to the user is mutable again. Perhaps clarify what you mean by this statement.

* {@link ByteBuffer}, {@link String}, {@link Object}, or byte array.
* <p>
* It also provides a way to serialize and deserialize an {@link Object} into {@link BinaryData} given an
* {@link ObjectSerializer}. Code samples are explained below.
* It provides a way to serialize {@link Object} into {@link BinaryData} using API
* {@link BinaryData#fromObject(Object, ObjectSerializer)} where you can provide your {@link ObjectSerializer}.
* <p>
* It provides a way to de-serialize {@link BinaryData} into specified {@link Object} using API
* {@link BinaryData#toObject(Class, ObjectSerializer)} where you can provide object type and your
* {@link ObjectSerializer}.
* <p>
* It provides API to use default json serializer which is available in classpath. The serializer must implement
* {@link JsonSerializer} interface.
* <p>
* Code samples are explained below.
Comment thread
hemanttanwar marked this conversation as resolved.
Outdated
*
* <p><strong>Create an instance from Bytes</strong></p>
* {@codesnippet com.azure.core.experimental.util.BinaryDocument.from#bytes}
Expand All @@ -43,19 +53,32 @@
* <p><strong>Get an Object from {@link BinaryData}</strong></p>
* {@codesnippet com.azure.core.experimental.util.BinaryDocument.to#ObjectAsync}
*
* <p><strong>Create an instance from Object</strong></p>
* {@codesnippet com.azure.core.experimental.util.BinaryDocument.from#Object}
*
* @see ObjectSerializer
Comment thread
hemanttanwar marked this conversation as resolved.
*/
public final class BinaryData {
private static final ClientLogger LOGGER = new ClientLogger(BinaryData.class);
private static final byte[] EMPTY_BYTES = new byte[0];
private static final BinaryData EMPTY_DATA = new BinaryData(new byte[0]);

private static final Object LOCK = new Object();

private final byte[] data;

private static volatile JsonSerializer defaultJsonSerializer;
Comment thread
hemanttanwar marked this conversation as resolved.

/**
* Create instance of {@link BinaryData} given the data.
* Create an instance of {@link BinaryData} from the given data. If {@code null} value is provided , it will be
* converted into empty byte array.
*
* @param data to represent as bytes.
* @throws NullPointerException If {@code data} is null.
*/
BinaryData(byte[] data) {
Objects.requireNonNull(data, "'data' cannot be null.");
if (Objects.isNull(data) || data.length == 0) {
data = EMPTY_BYTES;
}
this.data = Arrays.copyOf(data, data.length);
}

Expand All @@ -72,24 +95,15 @@ public InputStream toStream() {
}

/**
* Provides {@link Mono} of {@link InputStream} for the data represented by this {@link BinaryData} object.
*
* @return {@link InputStream} representation of the {@link BinaryData}.
*/
public Mono<InputStream> toStreamAsync() {
return Mono.fromCallable(() -> toStream());
}

/**
* Create {@link BinaryData} instance with given {@link InputStream} as source of data. The {@link InputStream} is
* not closed by this function.
* Creates a {@link BinaryData} instance with given {@link InputStream} as source of data. The {@link InputStream}
* is not closed by this function.
*
* <p><strong>Create an instance from InputStream</strong></p>
* {@codesnippet com.azure.core.experimental.util.BinaryDocument.from#Stream}
*
* @param inputStream to read bytes from.
* @throws UncheckedIOException If any error in reading from {@link InputStream}.
* @throws NullPointerException if {@code inputStream} is null.
* @throws NullPointerException If {@code inputStream} is null.
Comment thread
hemanttanwar marked this conversation as resolved.
* @return {@link BinaryData} representing the binary data.
*/
public static BinaryData fromStream(InputStream inputStream) {
Expand All @@ -111,11 +125,11 @@ public static BinaryData fromStream(InputStream inputStream) {
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are we not able to handle this in a more efficient manner? As it stands this means that the entire input stream is read into a buffer, converted into a byte array, and then copied into a new byte array? At the very least you can avoid the byte array copy.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed the last byte array and same optimization in other from..() API as well.


/**
* Asynchronously create {@link BinaryData} instance with given {@link InputStream} as source of data. The
* Asynchronously create a {@link BinaryData} instance with given {@link InputStream} as source of data. The
* {@link InputStream} is not closed by this function.
*
* @param inputStream to read bytes from.
* @throws NullPointerException if {@code inputStream} is null.
* @throws NullPointerException If {@code inputStream} is null.
* @return {@link Mono} of {@link BinaryData} representing the binary data.
*/
public static Mono<BinaryData> fromStreamAsync(InputStream inputStream) {
Expand All @@ -125,14 +139,14 @@ public static Mono<BinaryData> fromStreamAsync(InputStream inputStream) {
}

/**
* Create {@link BinaryData} instance with given {@link Flux} of {@link ByteBuffer} as source of data. It will
* Creates a {@link BinaryData} instance with given {@link Flux} of {@link ByteBuffer} as source of data. It will
* collect all the bytes from {@link ByteBuffer} into {@link BinaryData}.
*
* <p><strong>Create an instance from String</strong></p>
* {@codesnippet com.azure.core.experimental.util.BinaryDocument.from#Flux}
*
* @param data to use.
* @throws NullPointerException if {@code inputStream} is null.
* @throws NullPointerException If {@code data} is null.
* @return {@link Mono} of {@link BinaryData} representing binary data.
*/
public static Mono<BinaryData> fromFlux(Flux<ByteBuffer> data) {
Expand All @@ -145,38 +159,24 @@ public static Mono<BinaryData> fromFlux(Flux<ByteBuffer> data) {
}

/**
* Create {@link BinaryData} instance with given data and character set.
*
* <p><strong>Create an instance from String</strong></p>
* {@codesnippet com.azure.core.experimental.util.BinaryDocument.from#String}
* Creates a {@link BinaryData} instance with given data. The {@link String} is converted into bytes using UTF_8
* character set. If the String is {@code null}, an empty {@link BinaryData} will be returned.
*
* @param data to use.
* @param charSet to use.
* @throws NullPointerException if {@code inputStream} is null.
* @return {@link BinaryData} representing the binary data.
*/
public static BinaryData fromString(String data, Charset charSet) {
Objects.requireNonNull(data, "'data' cannot be null.");

return new BinaryData(data.getBytes(charSet));
}

/**
* Create {@link BinaryData} instance with given data. The {@link String} is converted into bytes using
* {@link StandardCharsets#UTF_8} character set.
*
* @param data to use.
* @throws NullPointerException if {@code inputStream} is null.
* @return {@link BinaryData} representing binary data.
*/
public static BinaryData fromString(String data) {
Objects.requireNonNull(data, "'data' cannot be null.");
if (Objects.isNull(data) || data.length() == 0) {
return EMPTY_DATA;
} else {
return new BinaryData(data.getBytes(StandardCharsets.UTF_8));
Comment thread
hemanttanwar marked this conversation as resolved.
Outdated
}

return new BinaryData(data.getBytes(StandardCharsets.UTF_8));
}

/**
* Create {@link BinaryData} instance with given byte array data.
* Creates a {@link BinaryData} instance with given byte array data. If the byte array is {@code null}, an empty
* {@link BinaryData} will be returned.
*
* @param data to use.
* @return {@link BinaryData} representing the binary data.
Expand All @@ -185,16 +185,44 @@ public static BinaryData fromBytes(byte[] data) {
return new BinaryData(data);
}

/**
* Serialize the given {@link Object} into {@link BinaryData} using json serializer which is available on classpath.
* The serializer on classpath must implement {@link JsonSerializer} interface. If the given Object is {@code null},
* an empty {@link BinaryData} will be returned.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This documentation should link through an aka.ms link to the appropriate json serializer documentation (which will soon move to docs.microsoft.com, which is why in the meanwhile we will use an aka.ms link)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

*
* @param data The {@link Object} which needs to be serialized into bytes.
* @throws IllegalStateException If a {@link JsonSerializer} cannot be found on the classpath.
* @return {@link BinaryData} representing the JSON serialized object.
*
* @see JsonSerializer
*/
public static BinaryData fromObject(Object data) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given this API is converting into JSON we may want to call it fromObjectJson.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we change it, we need to sync with .Net. I copied this API name from them.

if (Objects.isNull(data)) {
return EMPTY_DATA;
}
final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
getDefaultSerializer().serialize(outputStream, data);

return new BinaryData(outputStream.toByteArray());
}

/**
* Serialize the given {@link Object} into {@link BinaryData} using the provided {@link ObjectSerializer}.
* If the Object is {@code null}, an empty {@link BinaryData} will be returned.
*
* <p><strong>Create an instance from Object</strong></p>
* {@codesnippet com.azure.core.experimental.util.BinaryDocument.from#Object}
*
* @param data The {@link Object} which needs to be serialized into bytes.
* @param serializer to use for serializing the object.
* @throws NullPointerException if {@code inputStream} or {@code serializer} is null.
* @throws NullPointerException If {@code serializer} is null.
* @return {@link BinaryData} representing binary data.
*/
public static BinaryData fromObject(Object data, ObjectSerializer serializer) {
Objects.requireNonNull(data, "'data' cannot be null.");
if (Objects.isNull(data)) {
return EMPTY_DATA;
}

Objects.requireNonNull(serializer, "'serializer' cannot be null.");

final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
Expand All @@ -204,19 +232,19 @@ public static BinaryData fromObject(Object data, ObjectSerializer serializer) {

/**
* Serialize the given {@link Object} into {@link Mono} {@link BinaryData} using the provided
* {@link ObjectSerializer}.
* {@link ObjectSerializer}. If the Object is {@code null}, an empty {@link BinaryData} will be returned.
*
* @param data The {@link Object} which needs to be serialized into bytes.
* @param serializer to use for serializing the object.
* @throws NullPointerException if {@code inputStream} or {@code serializer} is null.
* @throws NullPointerException If {@code serializer} is null.
* @return {@link Mono} of {@link BinaryData} representing the binary data.
*/
public static Mono<BinaryData> fromObjectAsync(Object data, ObjectSerializer serializer) {
Objects.requireNonNull(data, "'data' cannot be null.");
Objects.requireNonNull(serializer, "'serializer' cannot be null.");

if (Objects.isNull(serializer)) {
return monoError(LOGGER, new NullPointerException("'serializer' cannot be null."));
}
return Mono.fromCallable(() -> fromObject(data, serializer));

}

/**
Expand All @@ -230,31 +258,22 @@ public byte[] toBytes() {

/**
* Provides {@link String} representation of this {@link BinaryData} object. The bytes are converted into
* {@link String} using {@link StandardCharsets#UTF_8} character set.
* {@link String} using the UTF-8 character set.
*
* @return {@link String} representation of the data.
*/
public String toString() {
return new String(this.data, StandardCharsets.UTF_8);
}

/**
* Provides {@link String} representation of this {@link BinaryData} object given a character set.
*
* @param charSet to use to convert bytes into {@link String}.
* @return {@link String} representation of the the binary data.
*/
public String toString(Charset charSet) {
return new String(this.data, charSet);
}

/**
* Deserialize the bytes into the {@link Object} of given type by applying the provided {@link ObjectSerializer} on
* the data.
*
* @param clazz representing the type of the Object.
* @param serializer to use deserialize data into type.
* @param <T> Generic type that the data is deserialized into.
* @throws NullPointerException If {@code serializer} or {@code clazz} is null.
* @return The {@link Object} of given type after deserializing the bytes.
*/
public <T> T toObject(Class<T> clazz, ObjectSerializer serializer) {
Expand All @@ -276,10 +295,65 @@ public <T> T toObject(Class<T> clazz, ObjectSerializer serializer) {
* @param clazz representing the type of the Object.
* @param serializer to use deserialize data into type.
* @param <T> Generic type that the data is deserialized into.
* @throws NullPointerException if {@code clazz} or {@code serializer} is null.
* @throws NullPointerException If {@code clazz} or {@code serializer} is null.
* @return The {@link Object} of given type after deserializing the bytes.
*/
public <T> Mono<T> toObjectAsync(Class<T> clazz, ObjectSerializer serializer) {

if (Objects.isNull(clazz)) {
return monoError(LOGGER, new NullPointerException("'clazz' cannot be null."));
} else if (Objects.isNull(serializer)) {
return monoError(LOGGER, new NullPointerException("'serializer' cannot be null."));
Comment thread
hemanttanwar marked this conversation as resolved.
}
return Mono.fromCallable(() -> toObject(clazz, serializer));
}

/**
* Deserialize the bytes into the {@link Object} of given type by using json serializer which is available in
* classpath. The serializer must implement {@link JsonSerializer} interface. A singleton instance of
* {@link JsonSerializer} is kept for this class to use.
*
* @param clazz representing the type of the Object.
* @param <T> Generic type that the data is deserialized into.
* @throws NullPointerException If {@code clazz} is null.
* @return The {@link Object} of given type after deserializing the bytes.
*/
public <T> T toObject(Class<T> clazz) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we rename this API to something JSON specific? toJsonObject?

@JonathanGiles @srnagar

@hemanttanwar hemanttanwar Oct 21, 2020

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This might force us, in future, to add other API for other formats.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

JsonObject has a different meaning. So, I think toObject() is fine as we will return a strongly-typed user-defined object here.

Objects.requireNonNull(clazz, "'clazz' cannot be null.");

TypeReference<T> ref = TypeReference.createInstance(clazz);
InputStream jsonStream = new ByteArrayInputStream(this.data);
return getDefaultSerializer().deserialize(jsonStream, ref);
}

/**
* Return a {@link Mono} by deserialize the bytes into the {@link Object} of given type after applying the Json
Comment thread
hemanttanwar marked this conversation as resolved.
Outdated
* serializer found on classpath.
*
* <p><strong>Gets the specified object</strong></p>
* {@codesnippet com.azure.core.experimental.util.BinaryDocument.to#ObjectAsync}
*
* @param clazz representing the type of the Object.
* @param <T> Generic type that the data is deserialized into.
* @throws NullPointerException If {@code clazz} is null.
* @return The {@link Object} of given type after deserializing the bytes.
*/
public <T> Mono<T> toObjectAsync(Class<T> clazz) {
if (Objects.isNull(clazz)) {
return monoError(LOGGER, new NullPointerException("'clazz' cannot be null."));
}
return Mono.fromCallable(() -> toObject(clazz));
}

/* This will ensure lazy instantiation to avoid hard dependency on Json Serializer. */
private static JsonSerializer getDefaultSerializer() {
if (defaultJsonSerializer == null) {
synchronized (LOCK) {
if (defaultJsonSerializer == null) {
defaultJsonSerializer = JsonSerializerProviders.createInstance();
}
}
}
return defaultJsonSerializer;
}
}
Loading