THRIFT-1909 Java: Add compiler flag to use the "option pattern" for optional fields
Patch: Eirik Sletteberg & rebase by Wouter Lammers
diff --git a/compiler/cpp/src/generate/t_java_generator.cc b/compiler/cpp/src/generate/t_java_generator.cc
index df3d1cf..e8c70ad 100644
--- a/compiler/cpp/src/generate/t_java_generator.cc
+++ b/compiler/cpp/src/generate/t_java_generator.cc
@@ -85,6 +85,9 @@
iter = parsed_options.find("reuse-objects");
reuse_objects_ = (iter != parsed_options.end());
+ iter = parsed_options.find("option_type");
+ use_option_type_ = (iter != parsed_options.end());
+
out_dir_base_ = (bean_style_ ? "gen-javabean" : "gen-java");
}
@@ -336,6 +339,7 @@
bool java5_;
bool sorted_containers_;
bool reuse_objects_;
+ bool use_option_type_;
};
/**
@@ -385,17 +389,25 @@
string t_java_generator::java_type_imports() {
string hash_builder;
string tree_set_and_map;
+ string option;
if (sorted_containers_) {
tree_set_and_map = string() + "import java.util.TreeSet;\n" + "import java.util.TreeMap;\n";
}
+ if (use_option_type_) {
+ option = string() +
+ "import org.apache.thrift.Option;\n";
+ }
+
return string() + hash_builder + "import org.apache.thrift.scheme.IScheme;\n"
+ "import org.apache.thrift.scheme.SchemeFactory;\n"
+ "import org.apache.thrift.scheme.StandardScheme;\n\n"
+ "import org.apache.thrift.scheme.TupleScheme;\n"
+ "import org.apache.thrift.protocol.TTupleProtocol;\n"
+ "import org.apache.thrift.protocol.TProtocolException;\n"
- + "import org.apache.thrift.EncodingUtils;\n" + "import org.apache.thrift.TException;\n"
+ + "import org.apache.thrift.EncodingUtils;\n"
+ + option
+ + "import org.apache.thrift.TException;\n"
+ "import org.apache.thrift.async.AsyncMethodCallback;\n"
+ "import org.apache.thrift.server.AbstractNonblockingServer.*;\n"
+ "import java.util.List;\n" + "import java.util.ArrayList;\n" + "import java.util.Map;\n"
@@ -2017,16 +2029,7 @@
string cap_name) {
indent(out) << "case " << constant_name(field_name) << ":" << endl;
indent_up();
-
- if (type->is_base_type() && !type->is_string()) {
- t_base_type* base_type = (t_base_type*)type;
-
- indent(out) << "return " << type_name(type, true, false) << ".valueOf("
- << (base_type->is_bool() ? "is" : "get") << cap_name << "());" << endl << endl;
- } else {
- indent(out) << "return get" << cap_name << "();" << endl << endl;
- }
-
+ indent(out) << "return " << (type->is_bool() ? "is" : "get") << cap_name << "();" << endl << endl;
indent_down();
}
@@ -2130,17 +2133,36 @@
t_type* type = get_true_type(field->get_type());
std::string field_name = field->get_name();
std::string cap_name = get_cap_name(field_name);
+ bool optional = use_option_type_ && field->get_req() == t_field::e_req::T_OPTIONAL;
if (type->is_container()) {
// Method to return the size of the collection
- indent(out) << "public int get" << cap_name;
- out << get_cap_name("size() {") << endl;
+ if (optional) {
+ indent(out) << "public Option<Integer> get" << cap_name;
+ out << get_cap_name("size() {") << endl;
- indent_up();
- indent(out) << "return (this." << field_name << " == null) ? 0 : "
- << "this." << field_name << ".size();" << endl;
- indent_down();
- indent(out) << "}" << endl << endl;
+ indent_up();
+ indent(out) << "if (this." << field_name << " == null) {" << endl;
+ indent_up();
+ indent(out) << "return Option.none();" << endl;
+ indent_down();
+ indent(out) << "} else {" << endl;
+ indent_up();
+ indent(out) << "return Option.some(this." << field_name << ".size());" << endl;
+ indent_down();
+ indent(out) << "}" << endl;
+ indent_down();
+ indent(out) << "}" << endl << endl;
+ } else {
+ indent(out) << "public int get" << cap_name;
+ out << get_cap_name("size() {") << endl;
+
+ indent_up();
+ indent(out) << "return (this." << field_name << " == null) ? 0 : " <<
+ "this." << field_name << ".size();" << endl;
+ indent_down();
+ indent(out) << "}" << endl << endl;
+ }
}
if (type->is_set() || type->is_list()) {
@@ -2152,15 +2174,34 @@
}
// Iterator getter for sets and lists
- indent(out) << "public java.util.Iterator<" << type_name(element_type, true, false) << "> get"
- << cap_name;
- out << get_cap_name("iterator() {") << endl;
+ if (optional) {
+ indent(out) << "public Option<java.util.Iterator<" <<
+ type_name(element_type, true, false) << ">> get" << cap_name;
+ out << get_cap_name("iterator() {") << endl;
- indent_up();
- indent(out) << "return (this." << field_name << " == null) ? null : "
- << "this." << field_name << ".iterator();" << endl;
- indent_down();
- indent(out) << "}" << endl << endl;
+ indent_up();
+ indent(out) << "if (this." << field_name << " == null) {" << endl;
+ indent_up();
+ indent(out) << "return Option.none();" << endl;
+ indent_down();
+ indent(out) << "} else {" << endl;
+ indent_up();
+ indent(out) << "return Option.some(this." << field_name << ".iterator());" << endl;
+ indent_down();
+ indent(out) << "}" << endl;
+ indent_down();
+ indent(out) << "}" << endl << endl;
+ } else {
+ indent(out) << "public java.util.Iterator<" <<
+ type_name(element_type, true, false) << "> get" << cap_name;
+ out << get_cap_name("iterator() {") << endl;
+
+ indent_up();
+ indent(out) << "return (this." << field_name << " == null) ? null : " <<
+ "this." << field_name << ".iterator();" << endl;
+ indent_down();
+ indent(out) << "}" << endl << endl;
+ }
// Add to set or list, create if the set/list is null
indent(out);
@@ -2215,17 +2256,42 @@
<< endl;
indent(out) << "}" << endl << endl;
} else {
- indent(out) << "public " << type_name(type);
- if (type->is_base_type() && ((t_base_type*)type)->get_base() == t_base_type::TYPE_BOOL) {
- out << " is";
+ if (optional) {
+ indent(out) << "public Option<" << type_name(type, true) << ">";
+ if (type->is_base_type() &&
+ ((t_base_type*)type)->get_base() == t_base_type::TYPE_BOOL) {
+ out << " is";
+ } else {
+ out << " get";
+ }
+ out << cap_name << "() {" << endl;
+ indent_up();
+
+ indent(out) << "if (this.isSet" << cap_name << "()) {" << endl;
+ indent_up();
+ indent(out) << "return Option.some(this." << field_name << ");" << endl;
+ indent_down();
+ indent(out) << "} else {" << endl;
+ indent_up();
+ indent(out) << "return Option.none();" << endl;
+ indent_down();
+ indent(out) << "}" << endl;
+ indent_down();
+ indent(out) << "}" << endl << endl;
} else {
- out << " get";
+ indent(out) << "public " << type_name(type);
+ if (type->is_base_type() &&
+ ((t_base_type*)type)->get_base() == t_base_type::TYPE_BOOL) {
+ out << " is";
+ } else {
+ out << " get";
+ }
+ out << cap_name << "() {" << endl;
+ indent_up();
+ indent(out) << "return this." << field_name << ";" << endl;
+ indent_down();
+ indent(out) << "}" << endl << endl;
}
- out << cap_name << "() {" << endl;
- indent_up();
- indent(out) << "return this." << field_name << ";" << endl;
- indent_down();
- indent(out) << "}" << endl << endl;
}
// Simple setter
@@ -5029,6 +5095,7 @@
" android: Generated structures are Parcelable.\n"
" android_legacy: Do not use java.io.IOException(throwable) (available for Android 2.3 and "
"above).\n"
+ " option_type: Wrap optional fields in an Option type.\n"
" java5: Generate Java 1.5 compliant code (includes android_legacy flag).\n"
" reuse-objects: Data objects will not be allocated, but existing instances will be used "
"(read and write).\n"
diff --git a/lib/java/src/org/apache/thrift/Option.java b/lib/java/src/org/apache/thrift/Option.java
new file mode 100644
index 0000000..db25ec5
--- /dev/null
+++ b/lib/java/src/org/apache/thrift/Option.java
@@ -0,0 +1,121 @@
+/*
+ * 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.thrift;
+
+/**
+ * Implementation of the Option type pattern
+ */
+public abstract class Option<T> {
+
+ /**
+ * Whether the Option is defined or not
+ * @return
+ * true if the Option is defined (of type Some)
+ * false if the Option is not defined (of type None)
+ */
+ public abstract boolean isDefined();
+
+ /**
+ * Get the value of the Option (if it is defined)
+ * @return the value
+ * @throws IllegalStateException if called on a None
+ */
+ public abstract T get();
+
+ /**
+ * Get the contained value (if defined) or else return a default value
+ * @param other what to return if the value is not defined (a None)
+ * @return either the value, or other if the value is not defined
+ */
+ public T or(T other) {
+ if (isDefined()) {
+ return get();
+ } else {
+ return other;
+ }
+ }
+ /**
+ * The None type, representing an absent value (instead of "null")
+ */
+ public static class None<T> extends Option<T> {
+ public boolean isDefined() {
+ return false;
+ }
+
+ public T get() {
+ throw new IllegalStateException("Cannot call get() on None");
+ }
+
+ public String toString() {
+ return "None";
+ }
+ }
+
+ /**
+ * The Some type, representing an existence of some value
+ * @param <T> The type of value
+ */
+ public static class Some<T> extends Option<T> {
+ private final T value;
+ public Some(T value) {
+ this.value = value;
+ }
+
+ public boolean isDefined() {
+ return true;
+ }
+
+ public T get() {
+ return value;
+ }
+
+ public String toString() {
+ return "Some("+value.toString()+")";
+ }
+ }
+
+ /**
+ * Wraps value in an Option type, depending on whether or not value is null
+ * @param value
+ * @param <T> type of value
+ * @return Some(value) if value is not null, None if value is null
+ */
+ public static <T> Option<T> fromNullable(T value) {
+ if (value != null) {
+ return new Some<T>(value);
+ } else {
+ return new None<T>();
+ }
+ }
+
+ /**
+ * Wrap value in a Some type (NB! value must not be null!)
+ * @param value
+ * @param <T> type of value
+ * @return a new Some(value)
+ */
+ public static <T> Some<T> some(T value) {
+ return new Some<T>(value);
+ }
+
+ public static <T> None<T> none() {
+ return new None<T>();
+ }
+}
\ No newline at end of file
diff --git a/lib/java/test/org/apache/thrift/TestOptionType.java b/lib/java/test/org/apache/thrift/TestOptionType.java
new file mode 100644
index 0000000..f70285f
--- /dev/null
+++ b/lib/java/test/org/apache/thrift/TestOptionType.java
@@ -0,0 +1,66 @@
+/*
+ * 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.thrift;
+
+import junit.framework.TestCase;
+import org.apache.thrift.Option;
+
+// Tests and documents behavior for the "Option<T>" type
+public class TestOptionType extends TestCase {
+ public void testSome() throws Exception {
+ String name = "Chuck Norris";
+ Option<String> option = Option.fromNullable(name);
+
+ assertTrue(option instanceof Option.Some);
+ assertTrue(option.isDefined());
+ assertEquals("Some(Chuck Norris)", option.toString());
+ assertEquals(option.or("default value"), "Chuck Norris");
+ assertEquals(option.get(),"Chuck Norris");
+ }
+
+ public void testNone() throws Exception {
+ String name = null;
+ Option<String> option = Option.fromNullable(name);
+
+ assertTrue(option instanceof Option.None);
+ assertFalse(option.isDefined());
+ assertEquals("None", option.toString());
+ assertEquals(option.or("default value"), "default value");
+ // Expect exception
+ try {
+ Object value = option.get();
+ fail("Expected IllegalStateException, got no exception");
+ } catch (IllegalStateException ex) {
+
+ } catch(Exception ex) {
+ fail("Expected IllegalStateException, got some other exception: "+ex.toString());
+ }
+ }
+
+ public void testMakeSome() throws Exception {
+ Option<String> some = Option.some("wee");
+ assertTrue(some instanceof Option.Some);
+ }
+
+ public void testMakeNone() throws Exception {
+ Option<Integer> none = Option.none();
+ assertTrue(none instanceof Option.None);
+ }
+}