Skip to content

Commit d8aab61

Browse files
committed
Add valueAt() method and text encoder/decoder.
1 parent b580b92 commit d8aab61

File tree

13 files changed

+318
-21
lines changed

13 files changed

+318
-21
lines changed

README.md

Lines changed: 36 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ Classes provided by the HTTP-RPC framework include:
2727
* [WebServiceProxy](#webserviceproxy) - client-side invocation proxy for web services
2828
* [JSONEncoder and JSONDecoder](#jsonencoder-and-jsondecoder) - encodes/decodes an object hierarchy to/from JSON
2929
* [CSVEncoder and CSVDecoder](#csvencoder-and-csvdecoder) - encodes/decodes an iterable sequence of values to/from CSV
30+
* [TextEncoder and TextDecoder](#textencoder-and-textdecoder) - encodes/decodes text content
3031
* [TemplateEncoder](#templateencoder) - encodes an object hierarchy using a [template document](template-reference.md)
3132
* [BeanAdapter](#beanadapter) - map adapter for Java beans
3233
* [ResultSetAdapter and Parameters](#resultsetadapter-and-parameters) - iterable adapter for JDBC result sets/applies named parameter values to prepared statements
@@ -506,9 +507,6 @@ for (Map<String, Object> month : months) {
506507
```
507508

508509
## CSVEncoder and CSVDecoder
509-
Although `WebService` automatically serializes return values as JSON, in some cases it may be preferable to return a CSV (comma-separated value) document instead. Because field keys are specified only at the beginning of the document rather than being duplicated for every record, CSV generally requires less bandwidth than JSON. Additionally, consumers can begin processing CSV as soon as the first record arrives, rather than waiting for the entire document to download.
510-
511-
### CSVEncoder
512510
The `CSVEncoder` class can be used to serialize a sequence of map values to CSV. For example, the following code could be used to export the month/day list from the previous example as CSV. The string values passed to the constructor represent the columns in the output document and the map keys to which those columns correspond:
513511

514512
```java
@@ -529,8 +527,7 @@ This code would produce the following output:
529527

530528
String values are automatically wrapped in double-quotes and escaped. Instances of `java.util.Date` are encoded as a long value representing epoch time. All other values are encoded via `toString()`.
531529

532-
### CSVDecoder
533-
The `CSVDecoder` class deserializes a CSV document into an iterable sequence of maps. Rather than loading the entire payload into memory and returning the data as a list, `CSVDecoder` returns the data as a forward-scrolling cursor, allowing consumers to process rows as soon as they are read.
530+
`CSVDecoder` deserializes a CSV document into an iterable sequence of maps. Rather than loading the entire payload into memory and returning the data as a list, `CSVDecoder` returns the data as a forward-scrolling cursor, allowing consumers to process rows as soon as they are read.
534531

535532
For example, given the CSV above as input, the following code would produce the same results as `JSONDecoder` example:
536533

@@ -546,6 +543,24 @@ for (Map<String, String> month : months) {
546543

547544
Columns with empty headings are ignored. Empty field values are treated as null.
548545

546+
## TextEncoder and TextDecoder
547+
The `TextEncoder` and `TextDecoder` classes can be used to serialize and deserialize plain text content, respectively. For example:
548+
549+
```java
550+
TextEncoder textEncoder = new TextEncoder();
551+
552+
try (FileOutputStream outputStream = new FileOutputStream(file)) {
553+
textEncoder.write("Hello, World!", outputStream);
554+
}
555+
556+
TextDecoder textDecoder = new TextDecoder();
557+
558+
String text;
559+
try (FileInputStream inputStream = new FileInputStream(file)) {
560+
text = textDecoder.read(inputStream); // Hello, World!
561+
}
562+
```
563+
549564
## TemplateEncoder
550565
The `TemplateEncoder` class transforms an object hierarchy into an output format using a [template document](template-reference.md). Template syntax is based loosely on the [Mustache](https://mustache.github.io) format and supports most Mustache features.
551566

@@ -908,6 +923,22 @@ public static <K, V> Map.Entry<K, V> entry(K key, V value) { ... }
908923

909924
These methods are provided primarily as a convenience for applications using Java 8. Applications targeting Java 9 and higher can use the standard `List.of()` and `Map.of()` methods provided by the JDK.
910925

926+
`Collections` additionally provides the `valueAt()` method, which can be used to access nested values in an object hierarchy. For example:
927+
928+
```java
929+
Map<String, ?> map = mapOf(
930+
entry("a", mapOf(
931+
entry("b", mapOf(
932+
entry("c", listOf(
933+
1, 2, 3
934+
))
935+
))
936+
))
937+
);
938+
939+
int value = valueAt(map, "a", "b", "c", 1); // 2
940+
```
941+
911942
# Kotlin Support
912943
In addition to Java, HTTP-RPC web services can be implemented using the [Kotlin](https://kotlinlang.org) programming language. For example, the following service provides some basic information about the host system:
913944

build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414

1515
subprojects {
1616
group = 'org.httprpc'
17-
version = '8.0.1'
17+
version = '8.1'
1818

1919
apply plugin: 'java-library'
2020
apply plugin: 'maven-publish'

httprpc-client/src/main/java/org/httprpc/WebServiceProxy.java

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,10 @@
1717
import org.httprpc.beans.BeanAdapter;
1818
import org.httprpc.io.JSONDecoder;
1919
import org.httprpc.io.JSONEncoder;
20+
import org.httprpc.io.TextDecoder;
2021

2122
import java.io.IOException;
2223
import java.io.InputStream;
23-
import java.io.InputStreamReader;
2424
import java.io.OutputStream;
2525
import java.io.OutputStreamWriter;
2626
import java.io.UnsupportedEncodingException;
@@ -612,17 +612,11 @@ public void encodeRequest(OutputStream outputStream) throws IOException {
612612
} else {
613613
String message;
614614
if (contentType != null && contentType.startsWith("text/plain")) {
615-
StringBuilder messageBuilder = new StringBuilder(1024);
615+
TextDecoder textDecoder = new TextDecoder();
616616

617-
try (InputStream inputStream = connection.getErrorStream();
618-
InputStreamReader reader = new InputStreamReader(inputStream, StandardCharsets.UTF_8)) {
619-
int c;
620-
while ((c = reader.read()) != EOF) {
621-
messageBuilder.append((char)c);
622-
}
617+
try (InputStream inputStream = connection.getErrorStream()) {
618+
message = textDecoder.read(inputStream);
623619
}
624-
625-
message = messageBuilder.toString();
626620
} else {
627621
String responseMessage = connection.getResponseMessage();
628622

httprpc-client/src/main/java/org/httprpc/beans/BeanAdapter.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -451,7 +451,7 @@ private static Object adapt(Object value, Map<Class<?>, Map<String, Property>> p
451451
* <li>If the target type is {@link URL}, the value's string representation is
452452
* adapted via {@link URL#URL(String)}.</li>
453453
* <li>If the target type is {@link List} or {@link Map}, the value is wrapped
454-
* in an adapter of the same type that automatically adapts its sub-elements.</li>
454+
* in an adapter of the same type that automatically adapts its elements.</li>
455455
* </ul>
456456
*
457457
* Otherwise, the target is assumed to be a bean, and the value is assumed to

httprpc-client/src/main/java/org/httprpc/io/CSVDecoder.java

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -94,8 +94,6 @@ public Map<String, String> next() {
9494
}
9595
};
9696

97-
private static final int EOF = -1;
98-
9997
private Cursor(Reader reader, char delimiter) throws IOException {
10098
this.reader = reader;
10199
this.delimiter = delimiter;

httprpc-client/src/main/java/org/httprpc/io/Decoder.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@
2626
public abstract class Decoder {
2727
private Charset charset;
2828

29+
protected static final int EOF = -1;
30+
2931
/**
3032
* Constructs a new decoder.
3133
*

httprpc-client/src/main/java/org/httprpc/io/JSONDecoder.java

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,6 @@ public class JSONDecoder extends Decoder {
4141
private static final String FALSE_KEYWORD = "false";
4242
private static final String NULL_KEYWORD = "null";
4343

44-
private static final int EOF = -1;
45-
4644
/**
4745
* Constructs a new JSON decoder.
4846
*/
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/*
2+
* Licensed under the Apache License, Version 2.0 (the "License");
3+
* you may not use this file except in compliance with the License.
4+
* You may obtain a copy of the License at
5+
*
6+
* http://www.apache.org/licenses/LICENSE-2.0
7+
*
8+
* Unless required by applicable law or agreed to in writing, software
9+
* distributed under the License is distributed on an "AS IS" BASIS,
10+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
* See the License for the specific language governing permissions and
12+
* limitations under the License.
13+
*/
14+
15+
package org.httprpc.io;
16+
17+
import java.io.IOException;
18+
import java.io.Reader;
19+
import java.nio.charset.Charset;
20+
import java.nio.charset.StandardCharsets;
21+
22+
@SuppressWarnings("unchecked")
23+
public class TextDecoder extends Decoder {
24+
/**
25+
* Constructs a new text decoder.
26+
*/
27+
public TextDecoder() {
28+
this(StandardCharsets.UTF_8);
29+
}
30+
31+
/**
32+
* Constructs a new text decoder.
33+
*
34+
* @param charset
35+
* The character set to use when decoding the text.
36+
*/
37+
public TextDecoder(Charset charset) {
38+
super(charset);
39+
}
40+
41+
@Override
42+
public String read(Reader reader) throws IOException {
43+
reader = new BufferedReader(reader);
44+
45+
StringBuilder stringBuilder = new StringBuilder();
46+
47+
int c;
48+
while ((c = reader.read()) != EOF) {
49+
stringBuilder.append((char)c);
50+
}
51+
52+
return stringBuilder.toString();
53+
}
54+
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
/*
2+
* Licensed under the Apache License, Version 2.0 (the "License");
3+
* you may not use this file except in compliance with the License.
4+
* You may obtain a copy of the License at
5+
*
6+
* http://www.apache.org/licenses/LICENSE-2.0
7+
*
8+
* Unless required by applicable law or agreed to in writing, software
9+
* distributed under the License is distributed on an "AS IS" BASIS,
10+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
* See the License for the specific language governing permissions and
12+
* limitations under the License.
13+
*/
14+
15+
package org.httprpc.io;
16+
17+
import java.io.IOException;
18+
import java.io.Writer;
19+
import java.nio.charset.Charset;
20+
import java.nio.charset.StandardCharsets;
21+
22+
/**
23+
* Text encoder.
24+
*/
25+
public class TextEncoder extends Encoder<String> {
26+
/**
27+
* Constructs a new text encoder.
28+
*/
29+
public TextEncoder() {
30+
this(StandardCharsets.UTF_8);
31+
}
32+
33+
/**
34+
* Constructs a new text encoder.
35+
*
36+
* @param charset
37+
* The character set to use when encoding the text.
38+
*/
39+
public TextEncoder(Charset charset) {
40+
super(charset);
41+
}
42+
43+
@Override
44+
public void write(String value, Writer writer) throws IOException {
45+
writer = new BufferedWriter(writer);
46+
47+
for (int i = 0, n = value.length(); i < n; i++) {
48+
writer.write(value.charAt(i));
49+
}
50+
51+
writer.flush();
52+
}
53+
}

httprpc-client/src/main/java/org/httprpc/util/Collections.java

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import java.util.AbstractMap;
1818
import java.util.Arrays;
1919
import java.util.LinkedHashMap;
20+
import java.util.LinkedList;
2021
import java.util.List;
2122
import java.util.Map;
2223

@@ -91,4 +92,45 @@ public static <K, V> Map<K, V> mapOf(Map.Entry<K, V>... entries) {
9192
public static <K, V> Map.Entry<K, V> entry(K key, V value) {
9293
return new AbstractMap.SimpleImmutableEntry<>(key, value);
9394
}
95+
96+
/**
97+
* Returns the value at a given path.
98+
*
99+
* @param <T>
100+
* The type of the value to return.
101+
*
102+
* @param root
103+
* The root object.
104+
*
105+
* @param path
106+
* The path to the value.
107+
*
108+
* @return
109+
* The value at the given path, or <code>null</code> if the value does not exist.
110+
*/
111+
public static <T> T valueAt(Object root, Object... path) {
112+
return valueAt(root, new LinkedList<>(Arrays.asList(path)));
113+
}
114+
115+
@SuppressWarnings("unchecked")
116+
private static <T> T valueAt(Object root, List<?> path) {
117+
if (root == null) {
118+
return null;
119+
} else if (path.isEmpty()) {
120+
return (T)root;
121+
} else {
122+
Object component = path.remove(0);
123+
124+
Object value;
125+
if (root instanceof List<?> && component instanceof Number) {
126+
value = ((List<?>)root).get(((Number)component).intValue());
127+
} else if (root instanceof Map<?, ?>) {
128+
value = ((Map<?, ?>)root).get(component);
129+
} else {
130+
throw new IllegalArgumentException();
131+
}
132+
133+
return valueAt(value, path);
134+
}
135+
}
94136
}

0 commit comments

Comments
 (0)