diff --git a/java/src/org/openqa/selenium/json/Input.java b/java/src/org/openqa/selenium/json/Input.java
index 5c2c3dfa642b8..c64b4ebe42f13 100644
--- a/java/src/org/openqa/selenium/json/Input.java
+++ b/java/src/org/openqa/selenium/json/Input.java
@@ -24,25 +24,31 @@
/**
* Similar to a {@link Reader} but with the ability to peek a single character ahead.
- *
- *
For the sake of providing a useful {@link #toString()} implementation, keeps the most recently
+ *
+ * For the sake of providing a useful {@link #toString()} implementation, keeps the most recently
* read characters in the input buffer.
*/
class Input {
- public static final char EOF = (char) -1;
- // the number of chars to buffer
+ /** end-of-file indicator (0xFFFD) */
+ public static final char EOF = (char) -1; // NOTE: Produces Unicode replacement character (0xFFFD)
+ /** the number of chars to buffer */
private static final int BUFFER_SIZE = 4096;
- // the number of chars to remember, safe to set to 0
+ /** the number of chars to remember, safe to set to 0 */
private static final int MEMORY_SIZE = 128;
private final Reader source;
- // a buffer used to minimize read calls and to keep the chars to remember
+ /** a buffer used to minimize read calls and to keep the chars to remember */
private final char[] buffer;
- // the filled area in the buffer
+ /** the filled area in the buffer */
private int filled;
- // the last position read in the buffer
+ /** the last position read in the buffer */
private int position;
+ /**
+ * Initialize a new instance of the {@link Input} class with the specified source.
+ *
+ * @param source {@link Reader} object that supplies the input to be processed
+ */
public Input(Reader source) {
this.source = Require.nonNull("Source", source);
this.buffer = new char[BUFFER_SIZE + MEMORY_SIZE];
@@ -50,14 +56,29 @@ public Input(Reader source) {
this.position = -1;
}
+ /**
+ * Extract the next character from the input without consuming it.
+ *
+ * @return the next input character; {@link #EOF} if input is exhausted
+ */
public char peek() {
return fill() ? buffer[position + 1] : EOF;
}
+ /**
+ * Read and consume the next character from the input.
+ *
+ * @return the next input character; {@link #EOF} if input is exhausted
+ */
public char read() {
return fill() ? buffer[++position] : EOF;
}
+ /**
+ * Return a string containing the most recently consumed input characters.
+ *
+ * @return {@link String} with up to 128 consumed input characters
+ */
@Override
public String toString() {
int offset;
@@ -74,6 +95,13 @@ public String toString() {
return "Last " + length + " characters read: " + new String(buffer, offset, length);
}
+ /**
+ * If all buffered input has been consumed, read the next chunk into the buffer.
+ * NOTE : The last 128 character of consumed input is retained for debug output.
+ *
+ * @return {@code true} if new input is available; {@code false} if input is exhausted
+ * @throws UncheckedIOException if an I/O exception is encountered
+ */
private boolean fill() {
// do we need to fill the buffer?
while (filled == position + 1) {
diff --git a/java/src/org/openqa/selenium/json/Json.java b/java/src/org/openqa/selenium/json/Json.java
index cc7fc978d2aae..5deb7b31d3bb1 100644
--- a/java/src/org/openqa/selenium/json/Json.java
+++ b/java/src/org/openqa/selenium/json/Json.java
@@ -27,23 +27,101 @@
import java.util.List;
import java.util.Map;
+/**
+ * The Json class is the entrypoint for the JSON processing features of the Selenium API.
+ * These features include:
+ *
+ * Built-in JSON deserialization to primitives and collections from the standard types shown below.
+ * Facilities to deserialize JSON to custom data types:
+ *
+ * Classes that declare a {@code fromJson(T)} static method, where T is any of the standard
+ * types shown below.
+ * Classes that declare a {@code fromJson(JsonInput)} static method.
+ * NOTE : Objects deserialized via a {@code fromJson} static method can be immutable.
+ * Classes that declare setter methods adhering to the
+ * JavaBean
+ * specification.
+ * NOTE : Deserialized {@code JavaBean} objects are mutable, which may be undesirable.
+ *
+ *
+ * Built-in JSON serialization from primitives and collections from the standard types shown below.
+ * Facilities to serialize custom data types to JSON:
+ *
+ * Classes that declare a {@code toJson()} method returning a primitive or collection from
+ * the standard types shown below.
+ * Classes that declare getter methods adhering to the {@code JavaBean} specification.
+ *
+ *
+ *
+ *
+ * The standard types supported by built-in processing:
+ *
+ * Numeric Types :
+ * {@link java.lang.Byte Byte}, {@link java.lang.Double Double}, {@link java.lang.Float Float},
+ * {@link java.lang.Integer Integer}, {@link java.lang.Long Long}, {@link java.lang.Short Short}
+ *
+ * Collection Types :
+ * {@link java.util.List List}, {@link java.util.Set Set}
+ *
+ * Standard Java Types :
+ * {@link java.util.Map Map}, {@link java.lang.Boolean Boolean}, {@link java.lang.String String},
+ * {@link java.lang.Enum Enum}, {@link java.net.URI URI}, {@link java.net.URL URL},
+ * {@link java.util.UUID UUID}, {@link java.time.Instant Instant}, {@link java.lang.Object Object}
+ *
+ *
+ *
+ * You can serialize objects for which no explicit coercer has been specified, and the Json API will use a
+ * generic process to provide best-effort JSON output. For the most predictable results, though, it's best to
+ * provide a {@code toJson()} method for the Json API to use for serialization. This is especially beneficial
+ * for objects that contain transient properties that should be omitted from the JSON output.
+ *
+ * You can deserialize objects for which no explicit handling has been defined. Note that the data type of the
+ * result will be {@code Map}, which means that you'll need to perform type checking and casting every
+ * time you extract an entry value from the result. For this reason, it's best to declare a type-specific
+ * {@code fromJson()} method in every type you need to deserialize.
+ *
+ * @see JsonTypeCoercer
+ * @see JsonInput
+ * @see JsonOutput
+ */
public class Json {
+ /** The value of {@code Content-Type} headers for HTTP requests and
+ * responses with JSON entities */
public static final String JSON_UTF_8 = "application/json; charset=utf-8";
+ /** Specifier for {@code List} input/output type */
public static final Type LIST_OF_MAPS_TYPE =
- new TypeToken>>() {}.getType();
+ new TypeToken>>() {}.getType();
+ /** Specifier for {@code Map} input/output type */
public static final Type MAP_TYPE = new TypeToken>() {}.getType();
+ /** Specifier for {@code Object} input/output type */
public static final Type OBJECT_TYPE = new TypeToken() {}.getType();
private final JsonTypeCoercer fromJson = new JsonTypeCoercer();
+ /**
+ * Serialize the specified object to JSON string representation.
+ * NOTE : This method limits traversal of nested objects to the default
+ * {@link JsonOutput#MAX_DEPTH maximum depth}.
+ *
+ * @param toConvert the object to be serialized
+ * @return JSON string representing the specified object
+ */
public String toJson(Object toConvert) {
return toJson(toConvert, JsonOutput.MAX_DEPTH);
}
+ /**
+ * Serialize the specified object to JSON string representation.
+ *
+ * @param toConvert the object to be serialized
+ * @param maxDepth maximum depth of nested object traversal
+ * @return JSON string representing the specified object
+ * @throws JsonException if an I/O exception is encountered
+ */
public String toJson(Object toConvert, int maxDepth) {
try (Writer writer = new StringWriter();
- JsonOutput jsonOutput = newOutput(writer)) {
+ JsonOutput jsonOutput = newOutput(writer)) {
jsonOutput.write(toConvert, maxDepth);
return writer.toString();
} catch (IOException e) {
@@ -51,10 +129,31 @@ public String toJson(Object toConvert, int maxDepth) {
}
}
+ /**
+ * Deserialize the specified JSON string into an object of the specified type.
+ * NOTE : This method uses the {@link PropertySetting#BY_NAME BY_NAME} strategy to assign values to properties
+ * in the deserialized object.
+ *
+ * @param source serialized source as JSON string
+ * @param typeOfT data type for deserialization (class or {@link TypeToken})
+ * @return object of the specified type deserialized from [source]
+ * @param result type (as specified by [typeOfT])
+ * @throws JsonException if an I/O exception is encountered
+ */
public T toType(String source, Type typeOfT) {
return toType(source, typeOfT, PropertySetting.BY_NAME);
}
+ /**
+ * Deserialize the specified JSON string into an object of the specified type.
+ *
+ * @param source serialized source as JSON string
+ * @param typeOfT data type for deserialization (class or {@link TypeToken})
+ * @param setter strategy used to assign values during deserialization
+ * @return object of the specified type deserialized from [source]
+ * @param result type (as specified by [typeOfT])
+ * @throws JsonException if an I/O exception is encountered
+ */
public T toType(String source, Type typeOfT, PropertySetting setter) {
try (StringReader reader = new StringReader(source)) {
return toType(reader, typeOfT, setter);
@@ -63,10 +162,31 @@ public T toType(String source, Type typeOfT, PropertySetting setter) {
}
}
+ /**
+ * Deserialize the JSON string supplied by the specified {@code Reader} into an object of the specified type.
+ * NOTE : This method uses the {@link PropertySetting#BY_NAME BY_NAME} strategy to assign values to properties
+ * in the deserialized object.
+ *
+ * @param source {@link Reader} that supplies a serialized JSON string
+ * @param typeOfT data type for deserialization (class or {@link TypeToken})
+ * @return object of the specified type deserialized from [source]
+ * @param result type (as specified by [typeOfT])
+ * @throws JsonException if an I/O exception is encountered
+ */
public T toType(Reader source, Type typeOfT) {
return toType(source, typeOfT, PropertySetting.BY_NAME);
}
+ /**
+ * Deserialize the JSON string supplied by the specified {@code Reader} into an object of the specified type.
+ *
+ * @param source {@link Reader} that supplies a serialized JSON string
+ * @param typeOfT data type for deserialization (class or {@link TypeToken})
+ * @param setter strategy used to assign values during deserialization
+ * @return object of the specified type deserialized from [source]
+ * @param result type (as specified by [typeOfT])
+ * @throws JsonException if an I/O exception is encountered
+ */
public T toType(Reader source, Type typeOfT, PropertySetting setter) {
if (setter == null) {
throw new JsonException("Mechanism for setting properties must be set");
@@ -77,10 +197,26 @@ public T toType(Reader source, Type typeOfT, PropertySetting setter) {
}
}
+ /**
+ * Create a new {@code JsonInput} object to traverse the JSON string supplied the specified {@code Reader}.
+ * NOTE : The {@code JsonInput} object returned by this method uses the {@link PropertySetting#BY_NAME BY_NAME}
+ * strategy to assign values to properties objects it deserializes.
+ *
+ * @param from {@link Reader} that supplies a serialized JSON string
+ * @return {@link JsonInput} object to traverse the JSON string supplied by [from]
+ * @throws UncheckedIOException if an I/O exception occurs
+ */
public JsonInput newInput(Reader from) throws UncheckedIOException {
return new JsonInput(from, fromJson, PropertySetting.BY_NAME);
}
+ /**
+ * Create a new {@code JsonOutput} object to produce a serialized JSON string in the specified {@code Appendable}.
+ *
+ * @param to {@link Appendable} that consumes a serialized JSON string
+ * @return {@link JsonOutput} object to product a JSON string in [to]
+ * @throws UncheckedIOException if an I/O exception occurs
+ */
public JsonOutput newOutput(Appendable to) throws UncheckedIOException {
return new JsonOutput(to);
}
diff --git a/java/src/org/openqa/selenium/json/JsonInput.java b/java/src/org/openqa/selenium/json/JsonInput.java
index 808f2bb6607ee..e138ec73f6ed3 100644
--- a/java/src/org/openqa/selenium/json/JsonInput.java
+++ b/java/src/org/openqa/selenium/json/JsonInput.java
@@ -30,17 +30,30 @@
import java.util.function.Function;
import org.openqa.selenium.internal.Require;
+/**
+ * The JsonInput class defines the operations used to deserialize JSON strings into Java objects.
+ */
public class JsonInput implements Closeable {
private final Reader source;
+ // FIXME: This flag is never set
private volatile boolean readPerformed = false;
private JsonTypeCoercer coercer;
private PropertySetting setter;
- private Input input;
+ private final Input input;
// Used when reading maps and collections so that we handle de-nesting and
// figuring out whether we're expecting a NAME properly.
- private Deque stack = new ArrayDeque<>();
+ private final Deque stack = new ArrayDeque<>();
+ /**
+ * This package-private constructor initializes new instances of the {@code JsonInput} class.
+ * This class declares methods that enable structured traversal of the JSON string supplied by the
+ * {@code Reader} object specified by [source].
+ *
+ * @param source {@link Reader} object that supplies the JSON string to be processed
+ * @param coercer {@link JsonTypeCoercer} that encapsulates the defined type-specific deserializers
+ * @param setter strategy used to assign values during deserialization
+ */
JsonInput(Reader source, JsonTypeCoercer coercer, PropertySetting setter) {
this.source = Require.nonNull("Source", source);
this.coercer = Require.nonNull("Coercer", coercer);
@@ -60,10 +73,24 @@ public PropertySetting propertySetting(PropertySetting setter) {
return previous;
}
+ /**
+ * Add the specified type coercers to the set installed in the JSON coercion manager.
+ *
+ * @param coercers array of zero or more {@link TypeCoercer} objects
+ * @return this {@link JsonInput} object with added type coercers
+ * @throws JsonException if this {@code JsonInput} has already begun processing its input
+ */
public JsonInput addCoercers(TypeCoercer>... coercers) {
return addCoercers(Arrays.asList(coercers));
}
+ /**
+ * Add the specified type coercers to the set installed in the JSON coercion manager.
+ *
+ * @param coercers iterable collection of {@link TypeCoercer} objects
+ * @return this {@link JsonInput} object with added type coercers
+ * @throws JsonException if this {@code JsonInput} has already begun processing its input
+ */
public JsonInput addCoercers(Iterable> coercers) {
synchronized (this) {
if (readPerformed) {
@@ -76,6 +103,11 @@ public JsonInput addCoercers(Iterable> coercers) {
return this;
}
+ /**
+ * {@inheritDoc}
+ *
+ * @throws UncheckedIOException if an I/O exception is encountered
+ */
@Override
public void close() {
try {
@@ -85,6 +117,13 @@ public void close() {
}
}
+ /**
+ * Peek at the next input string character to determine the pending JSON element type.
+ *
+ * @return {@link JsonType} indicating the pending JSON element type
+ * @throws JsonException if unable to determine the type of the pending element
+ * @throws UncheckedIOException if an I/O exception is encountered
+ */
public JsonType peek() {
skipWhitespace(input);
@@ -134,11 +173,25 @@ public JsonType peek() {
}
}
+ /**
+ * Read the next element of the JSON input stream as a boolean value.
+ *
+ * @return {@code true} or {@code false}
+ * @throws JsonException if the next element isn't the expected boolean
+ * @throws UncheckedIOException if an I/O exception is encountered
+ */
public boolean nextBoolean() {
expect(JsonType.BOOLEAN);
return read(input.peek() == 't' ? "true" : "false", Boolean::valueOf);
}
+ /**
+ * Read the next element of the JSON input stream as an object property name.
+ *
+ * @return JSON object property name
+ * @throws JsonException if the next element isn't a string followed by a colon
+ * @throws UncheckedIOException if an I/O exception is encountered
+ */
public String nextName() {
expect(JsonType.NAME);
@@ -147,16 +200,30 @@ public String nextName() {
char read = input.read();
if (read != ':') {
throw new JsonException(
- "Unable to read name. Expected colon separator, but saw '" + read + "'");
+ "Unable to read name. Expected colon separator, but saw '" + read + "'");
}
return name;
}
+ /**
+ * Read the next element of the JSON input stream as a {@code null} object.
+ *
+ * @return {@code null} object
+ * @throws JsonException if the next element isn't a {@code null}
+ * @throws UncheckedIOException if an I/O exception is encountered
+ */
public Object nextNull() {
expect(JsonType.NULL);
return read("null", str -> null);
}
+ /**
+ * Read the next element of the JSON input stream as a number.
+ *
+ * @return {@link Number} object
+ * @throws JsonException if the next element isn't a number
+ * @throws UncheckedIOException if an I/O exception is encountered
+ */
public Number nextNumber() {
expect(JsonType.NUMBER);
StringBuilder builder = new StringBuilder();
@@ -165,19 +232,17 @@ public Number nextNumber() {
do {
char read = input.peek();
if (Character.isDigit(read)
- || read == '+'
- || read == '-'
- || read == 'e'
- || read == 'E'
- || read == '.') {
+ || read == '+'
+ || read == '-'
+ || read == 'e'
+ || read == 'E'
+ || read == '.') {
builder.append(input.read());
} else {
break;
}
- if (read == '.') {
- fractionalPart = true;
- }
+ fractionalPart |= (read == '.');
} while (true);
try {
@@ -191,20 +256,41 @@ public Number nextNumber() {
}
}
+ /**
+ * Read the next element of the JSON input stream as a string.
+ *
+ * @return {@link String} object
+ * @throws JsonException if the next element isn't a string
+ * @throws UncheckedIOException if an I/O exception is encountered
+ */
public String nextString() {
expect(JsonType.STRING);
return readString();
}
+ /**
+ * Read the next element of the JSON input stream as an instant.
+ *
+ * @return {@link Instant} object
+ * @throws JsonException if the next element isn't a {@code Long}
+ * @throws UncheckedIOException if an I/O exception is encountered
+ */
public Instant nextInstant() {
Long time = read(Long.class);
return (null != time) ? Instant.ofEpochSecond(time) : null;
}
+ /**
+ * Determine whether an element is pending for the current container from the JSON input stream.
+ *
+ * @return {@code true} if an element is pending; otherwise {@code false}
+ * @throws JsonException if no container is open
+ * @throws UncheckedIOException if an I/O exception is encountered
+ */
public boolean hasNext() {
if (stack.isEmpty()) {
throw new JsonException(
- "Unable to determine if an item has next when not in a container type. " + input);
+ "Unable to determine if an item has next when not in a container type. " + input);
}
skipWhitespace(input);
@@ -217,29 +303,49 @@ public boolean hasNext() {
return type != JsonType.END_COLLECTION && type != JsonType.END_MAP;
}
+ /**
+ * Process the opening square bracket of a JSON array.
+ *
+ * @throws UncheckedIOException if an I/O exception is encountered
+ */
public void beginArray() {
expect(JsonType.START_COLLECTION);
stack.addFirst(Container.COLLECTION);
input.read();
}
+ /**
+ * Process the closing square bracket of a JSON array.
+ *
+ * @throws UncheckedIOException if an I/O exception is encountered
+ */
public void endArray() {
expect(JsonType.END_COLLECTION);
Container expectation = stack.removeFirst();
if (expectation != Container.COLLECTION) {
// The only other thing we could be closing is a map
throw new JsonException(
- "Attempt to close a JSON List, but a JSON Object was expected. " + input);
+ "Attempt to close a JSON List, but a JSON Object was expected. " + input);
}
input.read();
}
+ /**
+ * Process the opening curly brace of a JSON object.
+ *
+ * @throws UncheckedIOException if an I/O exception is encountered
+ */
public void beginObject() {
expect(JsonType.START_MAP);
stack.addFirst(Container.MAP_NAME);
input.read();
}
+ /**
+ * Process the closing curly brace of a JSON object.
+ *
+ * @throws UncheckedIOException if an I/O exception is encountered
+ */
public void endObject() {
expect(JsonType.END_MAP);
Container expectation = stack.removeFirst();
@@ -250,6 +356,14 @@ public void endObject() {
input.read();
}
+ /**
+ * Discard the pending JSON property value.
+ *
+ * @throws JsonException if the pending element isn't a value type
+ * @throws UncheckedIOException if an I/O exception is encountered
+ */
+ // FIXME: This method doesn't verify that the prior element was a property name.
+ // FIXME: This method doesn't enforce a depth limit when processing container types.
public void skipValue() {
switch (peek()) {
case BOOLEAN:
@@ -294,6 +408,16 @@ public void skipValue() {
}
}
+ /**
+ * Read the next element from the JSON input stream as the specified type.
+ *
+ * @param type data type for deserialization (class or {@link TypeToken})
+ * @return object of the specified type deserialized from the JSON input stream
+ * NOTE : Returns {@code null} if the input string is exhausted.
+ * @param result type (as specified by [type])
+ * @throws JsonException if coercion of the next element to the specified type fails
+ * @throws UncheckedIOException if an I/O exception is encountered
+ */
public T read(Type type) {
skipWhitespace(input);
@@ -305,14 +429,26 @@ public T read(Type type) {
return coercer.coerce(this, type, setter);
}
+ /**
+ * Determine if awaiting a JSON object property name.
+ *
+ * @return {@code true} is awaiting a property name; otherwise {@code false}
+ */
private boolean isReadingName() {
return stack.peekFirst() == Container.MAP_NAME;
}
+ /**
+ * Verify that the type of the pending JSON element matches the specified type.
+ *
+ * @param type expected JSON element type
+ * @throws JsonException if the pending element is not of the expected type
+ * @throws UncheckedIOException if an I/O exception is encountered
+ */
private void expect(JsonType type) {
if (peek() != type) {
throw new JsonException(
- "Expected to read a " + type + " but instead have: " + peek() + ". " + input);
+ "Expected to read a " + type + " but instead have: " + peek() + ". " + input);
}
// Special map handling. Woo!
@@ -337,6 +473,15 @@ private void expect(JsonType type) {
}
}
+ /**
+ * Read the next element from the JSON input stream, converting with the supplied mapper if it's the expected string.
+ *
+ * @param toCompare expected element string
+ * @param mapper function to convert the element string to its corresponding type
+ * @return value produced by the supplied mapper
+ * @param data type returned by the supplied mapper
+ * @throws UncheckedIOException if an I/O exception is encountered
+ */
private X read(String toCompare, Function mapper) {
skipWhitespace(input);
@@ -344,14 +489,21 @@ private X read(String toCompare, Function mapper) {
char read = input.read();
if (read != toCompare.charAt(i)) {
throw new JsonException(
- String.format(
- "Unable to read %s. Saw %s at position %d. %s", toCompare, read, i, input));
+ String.format(
+ "Unable to read %s. Saw %s at position %d. %s", toCompare, read, i, input));
}
}
return mapper.apply(toCompare);
}
+ /**
+ * Read the next element from the JSON input stream as a string, converting escaped characters.
+ *
+ * @return {@link String} object
+ * @throws JsonException if input stream ends without finding a closing quote
+ * @throws UncheckedIOException if an I/O exception is encountered
+ */
private String readString() {
input.read(); // Skip leading quote
@@ -373,6 +525,15 @@ private String readString() {
}
}
+ /**
+ * Convert the escape sequence at the current JSON input stream position, appending the result to the provided
+ * builder.
+ *
+ * @param builder {@link StringBuilder}
+ * @throws JsonException if an unsupported escape sequence is found
+ * @throws UncheckedIOException if an I/O exception is encountered
+ */
+ // FIXME: This function doesn't appear to support UTF-8 or UTF-32.
private void readEscape(StringBuilder builder) {
char read = input.read();
@@ -424,15 +585,28 @@ private void readEscape(StringBuilder builder) {
}
}
+ /**
+ * Consume whitespace characters from the head of the specified input object.
+ *
+ * @param input {@link Input} object
+ * @throws UncheckedIOException if an I/O exception is encountered
+ */
private void skipWhitespace(Input input) {
while (input.peek() != Input.EOF && Character.isWhitespace(input.peek())) {
input.read();
}
}
+ /**
+ * Used to track the current container processing state.
+ */
private enum Container {
+
+ /** Processing a JSON array */
COLLECTION,
+ /** Processing a JSON object property name */
MAP_NAME,
+ /** Processing a JSON object property value */
MAP_VALUE,
}
}
diff --git a/java/src/org/openqa/selenium/json/JsonOutput.java b/java/src/org/openqa/selenium/json/JsonOutput.java
index acc6f21d9ee53..d580acfa7f9df 100644
--- a/java/src/org/openqa/selenium/json/JsonOutput.java
+++ b/java/src/org/openqa/selenium/json/JsonOutput.java
@@ -22,6 +22,7 @@
import java.io.Closeable;
import java.io.File;
import java.io.IOException;
+import java.io.UncheckedIOException;
import java.lang.reflect.Method;
import java.net.URI;
import java.net.URL;
@@ -46,9 +47,12 @@
import org.openqa.selenium.internal.Require;
import org.openqa.selenium.logging.LogLevelMapping;
+/**
+ *
+ */
public class JsonOutput implements Closeable {
private static final Logger LOG = Logger.getLogger(JsonOutput.class.getName());
- static final int MAX_DEPTH = 100;
+ static final int MAX_DEPTH = 10;
private static final Predicate> GSON_ELEMENT;
@@ -103,7 +107,7 @@ public class JsonOutput implements Closeable {
private final Map>, DepthAwareConsumer> converters;
private final Appendable appendable;
private final Consumer appender;
- private Deque stack;
+ private final Deque stack;
private String indent = "";
private String lineSeparator = "\n";
private String indentBy = " ";
@@ -113,13 +117,13 @@ public class JsonOutput implements Closeable {
this.appendable = Require.nonNull("Underlying appendable", appendable);
this.appender =
- str -> {
- try {
- appendable.append(str);
- } catch (IOException e) {
- throw new JsonException("Unable to write to underlying appendable", e);
- }
- };
+ str -> {
+ try {
+ appendable.append(str);
+ } catch (IOException e) {
+ throw new JsonException("Unable to write to underlying appendable", e);
+ }
+ };
this.stack = new ArrayDeque<>();
this.stack.addFirst(new Empty());
@@ -129,147 +133,161 @@ public class JsonOutput implements Closeable {
Map>, DepthAwareConsumer> builder = new LinkedHashMap<>();
builder.put(Objects::isNull, (obj, maxDepth, depthRemaining) -> append("null"));
builder.put(
- CharSequence.class::isAssignableFrom,
- (obj, maxDepth, depthRemaining) -> append(asString(obj)));
+ CharSequence.class::isAssignableFrom,
+ (obj, maxDepth, depthRemaining) -> append(asString(obj)));
builder.put(
- Number.class::isAssignableFrom, (obj, maxDepth, depthRemaining) -> append(obj.toString()));
+ Number.class::isAssignableFrom, (obj, maxDepth, depthRemaining) -> append(obj.toString()));
builder.put(
- Boolean.class::isAssignableFrom,
- (obj, maxDepth, depthRemaining) -> append((Boolean) obj ? "true" : "false"));
+ Boolean.class::isAssignableFrom,
+ (obj, maxDepth, depthRemaining) -> append((Boolean) obj ? "true" : "false"));
builder.put(
- Date.class::isAssignableFrom,
- (obj, maxDepth, depthRemaining) ->
- append(String.valueOf(MILLISECONDS.toSeconds(((Date) obj).getTime()))));
+ Date.class::isAssignableFrom,
+ (obj, maxDepth, depthRemaining) ->
+ append(String.valueOf(MILLISECONDS.toSeconds(((Date) obj).getTime()))));
builder.put(
- Instant.class::isAssignableFrom,
- (obj, maxDepth, depthRemaining) ->
- append(asString(DateTimeFormatter.ISO_INSTANT.format((Instant) obj))));
+ Instant.class::isAssignableFrom,
+ (obj, maxDepth, depthRemaining) ->
+ append(asString(DateTimeFormatter.ISO_INSTANT.format((Instant) obj))));
builder.put(
- Enum.class::isAssignableFrom, (obj, maxDepth, depthRemaining) -> append(asString(obj)));
+ Enum.class::isAssignableFrom, (obj, maxDepth, depthRemaining) -> append(asString(obj)));
builder.put(
- File.class::isAssignableFrom,
- (obj, maxDepth, depthRemaining) -> append(((File) obj).getAbsolutePath()));
+ File.class::isAssignableFrom,
+ (obj, maxDepth, depthRemaining) -> append(((File) obj).getAbsolutePath()));
builder.put(
- URI.class::isAssignableFrom,
- (obj, maxDepth, depthRemaining) -> append(asString((obj).toString())));
+ URI.class::isAssignableFrom,
+ (obj, maxDepth, depthRemaining) -> append(asString((obj).toString())));
builder.put(
- URL.class::isAssignableFrom,
- (obj, maxDepth, depthRemaining) -> append(asString(((URL) obj).toExternalForm())));
+ URL.class::isAssignableFrom,
+ (obj, maxDepth, depthRemaining) -> append(asString(((URL) obj).toExternalForm())));
builder.put(
- UUID.class::isAssignableFrom,
- (obj, maxDepth, depthRemaining) -> append(asString(obj.toString())));
+ UUID.class::isAssignableFrom,
+ (obj, maxDepth, depthRemaining) -> append(asString(obj.toString())));
builder.put(
- Level.class::isAssignableFrom,
- (obj, maxDepth, depthRemaining) -> append(asString(LogLevelMapping.getName((Level) obj))));
+ Level.class::isAssignableFrom,
+ (obj, maxDepth, depthRemaining) -> append(asString(LogLevelMapping.getName((Level) obj))));
builder.put(
- GSON_ELEMENT,
- (obj, maxDepth, depthRemaining) -> {
- LOG.log(
- Level.WARNING,
- "Attempt to convert JsonElement from GSON. This functionality is deprecated. "
- + "Diagnostic stacktrace follows",
- new JsonException("Stack trace to determine cause of warning"));
- append(obj.toString());
- });
+ GSON_ELEMENT,
+ (obj, maxDepth, depthRemaining) -> {
+ LOG.log(
+ Level.WARNING,
+ "Attempt to convert JsonElement from GSON. This functionality is deprecated. "
+ + "Diagnostic stacktrace follows",
+ new JsonException("Stack trace to determine cause of warning"));
+ append(obj.toString());
+ });
// Special handling of asMap and toJson
builder.put(
- cls -> getMethod(cls, "toJson") != null,
- (obj, maxDepth, depthRemaining) ->
- convertUsingMethod("toJson", obj, maxDepth, depthRemaining));
+ cls -> getMethod(cls, "toJson") != null,
+ (obj, maxDepth, depthRemaining) ->
+ convertUsingMethod("toJson", obj, maxDepth, depthRemaining));
builder.put(
- cls -> getMethod(cls, "asMap") != null,
- (obj, maxDepth, depthRemaining) ->
- convertUsingMethod("asMap", obj, maxDepth, depthRemaining));
+ cls -> getMethod(cls, "asMap") != null,
+ (obj, maxDepth, depthRemaining) ->
+ convertUsingMethod("asMap", obj, maxDepth, depthRemaining));
builder.put(
- cls -> getMethod(cls, "toMap") != null,
- (obj, maxDepth, depthRemaining) ->
- convertUsingMethod("toMap", obj, maxDepth, depthRemaining));
+ cls -> getMethod(cls, "toMap") != null,
+ (obj, maxDepth, depthRemaining) ->
+ convertUsingMethod("toMap", obj, maxDepth, depthRemaining));
// And then the collection types
builder.put(
- Collection.class::isAssignableFrom,
- (obj, maxDepth, depthRemaining) -> {
- if (depthRemaining < 1) {
- throw new JsonException(
- "Reached the maximum depth of " + maxDepth + " while writing JSON");
- }
- beginArray();
- ((Collection>) obj)
- .stream()
- .filter(o -> (!(o instanceof Optional) || ((Optional>) o).isPresent()))
- .forEach(o -> write0(o, maxDepth, depthRemaining - 1));
- endArray();
- });
+ Collection.class::isAssignableFrom,
+ (obj, maxDepth, depthRemaining) -> {
+ if (depthRemaining < 1) {
+ throw new JsonException(
+ "Reached the maximum depth of " + maxDepth + " while writing JSON");
+ }
+ beginArray();
+ ((Collection>) obj)
+ .stream()
+ .filter(o -> (!(o instanceof Optional) || ((Optional>) o).isPresent()))
+ .forEach(o -> write0(o, maxDepth, depthRemaining - 1));
+ endArray();
+ });
builder.put(
- Map.class::isAssignableFrom,
- (obj, maxDepth, depthRemaining) -> {
- if (depthRemaining < 1) {
- throw new JsonException(
- "Reached the maximum depth of " + maxDepth + " while writing JSON");
- }
- beginObject();
- ((Map, ?>) obj)
- .forEach(
- (key, value) -> {
- if (value instanceof Optional && !((Optional) value).isPresent()) {
- return;
- }
- name(String.valueOf(key)).write0(value, maxDepth, depthRemaining - 1);
- });
- endObject();
- });
+ Map.class::isAssignableFrom,
+ (obj, maxDepth, depthRemaining) -> {
+ if (depthRemaining < 1) {
+ throw new JsonException(
+ "Reached the maximum depth of " + maxDepth + " while writing JSON");
+ }
+ beginObject();
+ ((Map, ?>) obj)
+ .forEach(
+ (key, value) -> {
+ if (value instanceof Optional && !((Optional) value).isPresent()) {
+ return;
+ }
+ name(String.valueOf(key)).write0(value, maxDepth, depthRemaining - 1);
+ });
+ endObject();
+ });
builder.put(
- Class::isArray,
- (obj, maxDepth, depthRemaining) -> {
- if (depthRemaining < 1) {
- throw new JsonException(
- "Reached the maximum depth of " + maxDepth + " while writing JSON");
- }
- beginArray();
- Stream.of((Object[]) obj)
- .filter(o -> (!(o instanceof Optional) || ((Optional>) o).isPresent()))
- .forEach(o -> write0(o, maxDepth, depthRemaining - 1));
- endArray();
- });
+ Class::isArray,
+ (obj, maxDepth, depthRemaining) -> {
+ if (depthRemaining < 1) {
+ throw new JsonException(
+ "Reached the maximum depth of " + maxDepth + " while writing JSON");
+ }
+ beginArray();
+ Stream.of((Object[]) obj)
+ .filter(o -> (!(o instanceof Optional) || ((Optional>) o).isPresent()))
+ .forEach(o -> write0(o, maxDepth, depthRemaining - 1));
+ endArray();
+ });
builder.put(
- Optional.class::isAssignableFrom,
- (obj, maxDepth, depthRemaining) -> {
- Optional> optional = (Optional>) obj;
- if (!optional.isPresent()) {
- append("null");
- return;
- }
+ Optional.class::isAssignableFrom,
+ (obj, maxDepth, depthRemaining) -> {
+ Optional> optional = (Optional>) obj;
+ if (!optional.isPresent()) {
+ append("null");
+ return;
+ }
- write0(optional.get(), maxDepth, depthRemaining);
- });
+ write0(optional.get(), maxDepth, depthRemaining);
+ });
// Finally, attempt to convert as an object
builder.put(
- cls -> true,
- (obj, maxDepth, depthRemaining) -> {
- if (depthRemaining < 1) {
- throw new JsonException(
- "Reached the maximum depth of " + maxDepth + " while writing JSON");
- }
- mapObject(obj, maxDepth, depthRemaining - 1);
- });
+ cls -> true,
+ (obj, maxDepth, depthRemaining) -> {
+ if (depthRemaining < 1) {
+ throw new JsonException(
+ "Reached the maximum depth of " + maxDepth + " while writing JSON");
+ }
+ mapObject(obj, maxDepth, depthRemaining - 1);
+ });
this.converters = Collections.unmodifiableMap(builder);
}
+ /**
+ *
+ * @param enablePrettyPrinting
+ * @return
+ */
public JsonOutput setPrettyPrint(boolean enablePrettyPrinting) {
this.lineSeparator = enablePrettyPrinting ? "\n" : "";
this.indentBy = enablePrettyPrinting ? " " : "";
return this;
}
+ /**
+ *
+ * @param writeClassName
+ * @return
+ */
public JsonOutput writeClassName(boolean writeClassName) {
this.writeClassName = writeClassName;
return this;
}
+ /**
+ *
+ * @return
+ */
public JsonOutput beginObject() {
stack.getFirst().write("{" + lineSeparator);
indent += indentBy;
@@ -277,6 +295,11 @@ public JsonOutput beginObject() {
return this;
}
+ /**
+ *
+ * @param name
+ * @return
+ */
public JsonOutput name(String name) {
if (!(stack.getFirst() instanceof JsonObject)) {
throw new JsonException("Attempt to write name, but not writing a json object: " + name);
@@ -285,6 +308,10 @@ public JsonOutput name(String name) {
return this;
}
+ /**
+ *
+ * @return
+ */
public JsonOutput endObject() {
Node topOfStack = stack.getFirst();
if (!(topOfStack instanceof JsonObject)) {
@@ -301,6 +328,10 @@ public JsonOutput endObject() {
return this;
}
+ /**
+ *
+ * @return
+ */
public JsonOutput beginArray() {
append("[" + lineSeparator);
indent += indentBy;
@@ -308,6 +339,10 @@ public JsonOutput beginArray() {
return this;
}
+ /**
+ *
+ * @return
+ */
public JsonOutput endArray() {
Node topOfStack = stack.getFirst();
if (!(topOfStack instanceof JsonCollection)) {
@@ -324,25 +359,48 @@ public JsonOutput endArray() {
return this;
}
+ /**
+ *
+ * @param value
+ * @return
+ */
public JsonOutput write(Object value) {
return write(value, MAX_DEPTH);
}
+ /**
+ *
+ * @param value
+ * @param maxDepth
+ * @return
+ */
public JsonOutput write(Object value, int maxDepth) {
return write0(value, maxDepth, maxDepth);
}
+ /**
+ *
+ * @param input
+ * @param maxDepth
+ * @param depthRemaining
+ * @return
+ */
private JsonOutput write0(Object input, int maxDepth, int depthRemaining) {
converters.entrySet().stream()
- .filter(entry -> entry.getKey().test(input == null ? null : input.getClass()))
- .findFirst()
- .map(Map.Entry::getValue)
- .orElseThrow(() -> new JsonException("Unable to write " + input))
- .consume(input, maxDepth, depthRemaining);
+ .filter(entry -> entry.getKey().test(input == null ? null : input.getClass()))
+ .findFirst()
+ .map(Map.Entry::getValue)
+ .orElseThrow(() -> new JsonException("Unable to write " + input))
+ .consume(input, maxDepth, depthRemaining);
return this;
}
+ /**
+ * {@inheritDoc}
+ *
+ * @throws JsonException if JSON stream isn't empty or an I/O exception is encountered
+ */
@Override
public void close() {
if (appendable instanceof Closeable) {
@@ -358,31 +416,47 @@ public void close() {
}
}
+ /**
+ *
+ * @param text
+ * @return
+ */
private JsonOutput append(String text) {
stack.getFirst().write(text);
return this;
}
+ /**
+ *
+ * @param obj
+ * @return
+ */
private String asString(Object obj) {
StringBuilder toReturn = new StringBuilder("\"");
String.valueOf(obj)
- .chars()
- .forEach(
- i -> {
- String escaped = ESCAPES.get(i);
- if (escaped != null) {
- toReturn.append(escaped);
- } else {
- toReturn.append((char) i);
- }
- });
+ .chars()
+ .forEach(
+ i -> {
+ String escaped = ESCAPES.get(i);
+ if (escaped != null) {
+ toReturn.append(escaped);
+ } else {
+ toReturn.append((char) i);
+ }
+ });
toReturn.append('"');
return toReturn.toString();
}
+ /**
+ *
+ * @param clazz
+ * @param methodName
+ * @return
+ */
private Method getMethod(Class> clazz, String methodName) {
if (Object.class.equals(clazz)) {
return null;
@@ -396,17 +470,25 @@ private Method getMethod(Class> clazz, String methodName) {
return getMethod(clazz.getSuperclass(), methodName);
} catch (SecurityException e) {
throw new JsonException(
- "Unable to find the method because of a security constraint: " + methodName, e);
+ "Unable to find the method because of a security constraint: " + methodName, e);
}
}
+ /**
+ *
+ * @param methodName
+ * @param toConvert
+ * @param maxDepth
+ * @param depthRemaining
+ * @return
+ */
private JsonOutput convertUsingMethod(
- String methodName, Object toConvert, int maxDepth, int depthRemaining) {
+ String methodName, Object toConvert, int maxDepth, int depthRemaining) {
try {
Method method = getMethod(toConvert.getClass(), methodName);
if (method == null) {
throw new JsonException(
- String.format("Unable to read object %s using method %s", toConvert, methodName));
+ String.format("Unable to read object %s using method %s", toConvert, methodName));
}
Object value = method.invoke(toConvert);
@@ -416,6 +498,12 @@ private JsonOutput convertUsingMethod(
}
}
+ /**
+ *
+ * @param toConvert
+ * @param maxDepth
+ * @param depthRemaining
+ */
private void mapObject(Object toConvert, int maxDepth, int depthRemaining) {
if (toConvert instanceof Class) {
write(((Class>) toConvert).getName());
@@ -425,7 +513,7 @@ private void mapObject(Object toConvert, int maxDepth, int depthRemaining) {
// Raw object via reflection? Nope, not needed
beginObject();
for (SimplePropertyDescriptor pd :
- SimplePropertyDescriptor.getPropertyDescriptors(toConvert.getClass())) {
+ SimplePropertyDescriptor.getPropertyDescriptors(toConvert.getClass())) {
// Only include methods not on java.lang.Object to stop things being super-noisy
Function readMethod = pd.getReadMethod();
@@ -446,9 +534,16 @@ private void mapObject(Object toConvert, int maxDepth, int depthRemaining) {
endObject();
}
+ /**
+ *
+ */
private class Node {
protected boolean isEmpty = true;
+ /**
+ *
+ * @param text
+ */
public void write(String text) {
if (isEmpty) {
isEmpty = false;
@@ -461,6 +556,9 @@ public void write(String text) {
}
}
+ /**
+ *
+ */
private class Empty extends Node {
@Override
@@ -473,11 +571,21 @@ public void write(String text) {
}
}
+ /**
+ *
+ */
private class JsonCollection extends Node {}
+ /**
+ *
+ */
private class JsonObject extends Node {
private boolean isNameNext = true;
+ /**
+ *
+ * @param name
+ */
public void name(String name) {
if (!isNameNext) {
throw new JsonException("Unexpected attempt to set name of json object: " + name);
@@ -498,8 +606,17 @@ public void write(String text) {
}
}
+ /**
+ *
+ */
@FunctionalInterface
private interface DepthAwareConsumer {
+
+ /**
+ * @param object
+ * @param maxDepth
+ * @param depthRemaining
+ */
void consume(Object object, int maxDepth, int depthRemaining);
}
}
diff --git a/java/src/org/openqa/selenium/json/JsonType.java b/java/src/org/openqa/selenium/json/JsonType.java
index db391ebe49fd1..c38bac047a74b 100644
--- a/java/src/org/openqa/selenium/json/JsonType.java
+++ b/java/src/org/openqa/selenium/json/JsonType.java
@@ -17,15 +17,28 @@
package org.openqa.selenium.json;
+/**
+ * Used to specify the pending JSON element type.
+ */
public enum JsonType {
+ /** Boolean value */
BOOLEAN,
+ /** property name */
NAME,
+ /** {@code null} value */
NULL,
+ /** numeric value */
NUMBER,
+ /** start of object */
START_MAP,
+ /** end of object */
END_MAP,
+ /** start of array */
START_COLLECTION,
+ /** end of array */
END_COLLECTION,
+ /** string value */
STRING,
+ /** end of input */
END
}
diff --git a/java/src/org/openqa/selenium/json/JsonTypeCoercer.java b/java/src/org/openqa/selenium/json/JsonTypeCoercer.java
index 246e91dcecd5f..910e40c71cc6a 100644
--- a/java/src/org/openqa/selenium/json/JsonTypeCoercer.java
+++ b/java/src/org/openqa/selenium/json/JsonTypeCoercer.java
@@ -42,27 +42,42 @@
import org.openqa.selenium.MutableCapabilities;
import org.openqa.selenium.internal.Require;
+/**
+ *
+ */
class JsonTypeCoercer {
private final Set> additionalCoercers;
private final Set> coercers;
private final Map> knownCoercers =
- new ConcurrentHashMap<>();
+ new ConcurrentHashMap<>();
+ /**
+ *
+ */
JsonTypeCoercer() {
this(Stream.of());
}
+ /**
+ *
+ * @param coercer
+ * @param coercers
+ */
JsonTypeCoercer(JsonTypeCoercer coercer, Iterable> coercers) {
this(
- Stream.concat(
- StreamSupport.stream(coercers.spliterator(), false),
- coercer.additionalCoercers.stream()));
+ Stream.concat(
+ StreamSupport.stream(coercers.spliterator(), false),
+ coercer.additionalCoercers.stream()));
}
+ /**
+ *
+ * @param coercers
+ */
private JsonTypeCoercer(Stream> coercers) {
this.additionalCoercers =
- coercers.collect(collectingAndThen(toSet(), Collections::unmodifiableSet));
+ coercers.collect(collectingAndThen(toSet(), Collections::unmodifiableSet));
// Note: we call out when ordering matters.
Set> builder = new LinkedHashSet<>(additionalCoercers);
@@ -77,15 +92,15 @@ private JsonTypeCoercer(Stream> coercers) {
builder.add(new NumberCoercer<>(Integer.class, Number::intValue));
builder.add(new NumberCoercer<>(Long.class, Number::longValue));
builder.add(
- new NumberCoercer<>(
- Number.class,
- num -> {
- double doubleValue = num.doubleValue();
- if (doubleValue % 1 != 0 || doubleValue > Long.MAX_VALUE) {
- return doubleValue;
- }
- return num.longValue();
- }));
+ new NumberCoercer<>(
+ Number.class,
+ num -> {
+ double doubleValue = num.doubleValue();
+ if (doubleValue % 1 != 0 || doubleValue > Long.MAX_VALUE) {
+ return doubleValue;
+ }
+ return num.longValue();
+ }));
builder.add(new NumberCoercer<>(Short.class, Number::shortValue));
builder.add(new StringCoercer());
builder.add(new EnumCoercer<>());
@@ -96,14 +111,14 @@ private JsonTypeCoercer(Stream> coercers) {
// From Selenium
builder.add(
- new MapCoercer<>(
- Capabilities.class,
- this,
- Collector.of(
- MutableCapabilities::new,
- (caps, entry) -> caps.setCapability((String) entry.getKey(), entry.getValue()),
- MutableCapabilities::merge,
- UNORDERED)));
+ new MapCoercer<>(
+ Capabilities.class,
+ this,
+ Collector.of(
+ MutableCapabilities::new,
+ (caps, entry) -> caps.setCapability((String) entry.getKey(), entry.getValue()),
+ MutableCapabilities::merge,
+ UNORDERED)));
// Container types
//noinspection unchecked
@@ -114,18 +129,18 @@ private JsonTypeCoercer(Stream> coercers) {
builder.add(new StaticInitializerCoercer());
builder.add(
- new MapCoercer<>(
- Map.class,
- this,
- Collector.of(
- LinkedHashMap::new,
- (map, entry) -> map.put(entry.getKey(), entry.getValue()),
- (l, r) -> {
- l.putAll(r);
- return l;
- },
- UNORDERED,
- CONCURRENT)));
+ new MapCoercer<>(
+ Map.class,
+ this,
+ Collector.of(
+ LinkedHashMap::new,
+ (map, entry) -> map.put(entry.getKey(), entry.getValue()),
+ (l, r) -> {
+ l.putAll(r);
+ return l;
+ },
+ UNORDERED,
+ CONCURRENT)));
// If the requested type is exactly "Object", do some guess work
builder.add(new ObjectCoercer(this));
@@ -136,9 +151,18 @@ private JsonTypeCoercer(Stream> coercers) {
this.coercers = Collections.unmodifiableSet(builder);
}
+ /**
+ * Deserialize the next JSON element as an object of the specified type.
+ *
+ * @param json serialized source as JSON string
+ * @param typeOfT data type for deserialization (class or {@link TypeToken})
+ * @param setter strategy used to assign values during deserialization
+ * @return object of the specified type deserialized from [source]
+ * @param result type (as specified by [typeOfT])
+ */
T coerce(JsonInput json, Type typeOfT, PropertySetting setter) {
BiFunction coercer =
- knownCoercers.computeIfAbsent(typeOfT, this::buildCoercer);
+ knownCoercers.computeIfAbsent(typeOfT, this::buildCoercer);
// We need to keep null checkers happy, apparently.
@SuppressWarnings("unchecked")
@@ -147,21 +171,26 @@ T coerce(JsonInput json, Type typeOfT, PropertySetting setter) {
return result;
}
+ /**
+ *
+ * @param type
+ * @return
+ */
private BiFunction buildCoercer(Type type) {
return coercers.stream()
- .filter(coercer -> coercer.test(narrow(type)))
- .findFirst()
- .map(coercer -> coercer.apply(type))
- .map(
- func ->
- (BiFunction)
- (jsonInput, setter) -> {
- if (jsonInput.peek() == JsonType.NULL) {
- return jsonInput.nextNull();
- }
-
- return func.apply(jsonInput, setter);
- })
- .orElseThrow(() -> new JsonException("Unable to find type coercer for " + type));
+ .filter(coercer -> coercer.test(narrow(type)))
+ .findFirst()
+ .map(coercer -> coercer.apply(type))
+ .map(
+ func ->
+ (BiFunction)
+ (jsonInput, setter) -> {
+ if (jsonInput.peek() == JsonType.NULL) {
+ return jsonInput.nextNull();
+ }
+
+ return func.apply(jsonInput, setter);
+ })
+ .orElseThrow(() -> new JsonException("Unable to find type coercer for " + type));
}
}
diff --git a/java/src/org/openqa/selenium/json/PropertySetting.java b/java/src/org/openqa/selenium/json/PropertySetting.java
index 4a6d9188479b5..60724594220b1 100644
--- a/java/src/org/openqa/selenium/json/PropertySetting.java
+++ b/java/src/org/openqa/selenium/json/PropertySetting.java
@@ -17,7 +17,18 @@
package org.openqa.selenium.json;
+import java.io.Reader;
+import java.lang.reflect.Type;
+
+/**
+ * Used to specify the strategy used to assign values during deserialization.
+ *
+ * @see org.openqa.selenium.json.Json#toType(Reader, Type, PropertySetting)
+ * @see org.openqa.selenium.json.Json#toType(String, Type, PropertySetting)
+ */
public enum PropertySetting {
+ /** Values are stored via the corresponding Bean setter methods. */
BY_NAME,
+ /** Values are stored in fields with the indicated names. */
BY_FIELD
}