Skip to content

Commit

Permalink
For Boolean, Double and Integer properties, getters should be NonNull (
Browse files Browse the repository at this point in the history
…#178)

Without this change, callers will always have to defend against null values, which is a big pain.
  • Loading branch information
RicoYao authored and rahul-malik committed Mar 13, 2019
1 parent efe3c84 commit e63f15c
Show file tree
Hide file tree
Showing 5 changed files with 91 additions and 40 deletions.
12 changes: 6 additions & 6 deletions Examples/Java/Sources/Everything.java
Original file line number Diff line number Diff line change
Expand Up @@ -288,8 +288,8 @@ public int hashCode() {
return this.arrayProp;
}

public @Nullable Boolean getBooleanProp() {
return this.booleanProp;
public @NonNull Boolean getBooleanProp() {
return this.booleanProp == null ? Boolean.FALSE : this.booleanProp;
}

public @Nullable Date getDateProp() {
Expand All @@ -300,8 +300,8 @@ public int hashCode() {
return this.intEnum;
}

public @Nullable Integer getIntProp() {
return this.intProp;
public @NonNull Integer getIntProp() {
return this.intProp == null ? 0 : this.intProp;
}

public @Nullable List<Object> getListPolymorphicValues() {
Expand Down Expand Up @@ -356,8 +356,8 @@ public int hashCode() {
return this.mapWithPrimitiveValues;
}

public @Nullable Double getNumberProp() {
return this.numberProp;
public @NonNull Double getNumberProp() {
return this.numberProp == null ? 0 : this.numberProp;
}

public @Nullable User getOtherModelProp() {
Expand Down
8 changes: 4 additions & 4 deletions Examples/Java/Sources/Image.java
Original file line number Diff line number Diff line change
Expand Up @@ -88,16 +88,16 @@ public int hashCode() {
width);
}

public @Nullable Integer getHeight() {
return this.height;
public @NonNull Integer getHeight() {
return this.height == null ? 0 : this.height;
}

public @Nullable String getUrl() {
return this.url;
}

public @Nullable Integer getWidth() {
return this.width;
public @NonNull Integer getWidth() {
return this.width == null ? 0 : this.width;
}

public boolean getHeightIsSet() {
Expand Down
16 changes: 8 additions & 8 deletions Examples/Java/Sources/VariableSubtitution.java
Original file line number Diff line number Diff line change
Expand Up @@ -94,20 +94,20 @@ public int hashCode() {
newProp);
}

public @Nullable Integer getAllocProp() {
return this.allocProp;
public @NonNull Integer getAllocProp() {
return this.allocProp == null ? 0 : this.allocProp;
}

public @Nullable Integer getCopyProp() {
return this.copyProp;
public @NonNull Integer getCopyProp() {
return this.copyProp == null ? 0 : this.copyProp;
}

public @Nullable Integer getMutableCopyProp() {
return this.mutableCopyProp;
public @NonNull Integer getMutableCopyProp() {
return this.mutableCopyProp == null ? 0 : this.mutableCopyProp;
}

public @Nullable Integer getNewProp() {
return this.newProp;
public @NonNull Integer getNewProp() {
return this.newProp == null ? 0 : this.newProp;
}

public boolean getAllocPropIsSet() {
Expand Down
43 changes: 32 additions & 11 deletions Sources/Core/JavaIR.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,24 @@ struct JavaModifier: OptionSet {
}
}

enum JavaAnnotation: String {
case override = "Override"
case nullable = "Nullable"
case nonnull = "NonNull"
enum JavaAnnotation: Hashable {
case override
case nullable
case nonnull
case serializedName(name: String)

var rendered: String {
switch self {
case .override:
return "Override"
case .nullable:
return "Nullable"
case .nonnull:
return "NonNull"
case let .serializedName(name):
return "SerializedName(\"\(name)\")"
}
}
}

public struct JavaIR {
Expand All @@ -38,16 +52,19 @@ public struct JavaIR {
let modifiers: JavaModifier
let body: [String]
let signature: String
let throwableExceptions: [String]

func render() -> [String] {
// HACK: We should actually have an enum / optionset that we can check for abstract, static, ...
let annotationLines = annotations.map { "@\($0.rawValue)" }
let annotationLines = annotations.map { "@\($0.rendered)" }

let throwsString = throwableExceptions.isEmpty ? "" : " throws " + throwableExceptions.joined(separator: ", ")

if modifiers.contains(.abstract) {
return annotationLines + ["\(modifiers.render()) \(signature);"]
return annotationLines + ["\(modifiers.render()) \(signature)\(throwsString);"]
}

var toRender = annotationLines + ["\(modifiers.render()) \(signature) {"]
var toRender = annotationLines + ["\(modifiers.render()) \(signature)\(throwsString) {"]

if !body.isEmpty {
toRender.append(-->body)
Expand All @@ -59,7 +76,7 @@ public struct JavaIR {
}

public struct Property {
let annotations: Set<String>
let annotations: Set<JavaAnnotation>
let modifiers: JavaModifier
let type: String
let name: String
Expand All @@ -68,7 +85,7 @@ public struct JavaIR {
func render() -> String {
var prop = ""
if !annotations.isEmpty {
prop.append(annotations.map { "@\($0)" }.joined(separator: " ") + " ")
prop.append(annotations.map { "@\($0.rendered)" }.joined(separator: " ") + " ")
}
prop.append("\(modifiers.render()) \(type) \(name)")
if !initialValue.isEmpty {
Expand All @@ -80,7 +97,11 @@ public struct JavaIR {
}

static func method(annotations: Set<JavaAnnotation> = [], _ modifiers: JavaModifier, _ signature: String, body: () -> [String]) -> JavaIR.Method {
return JavaIR.Method(annotations: annotations, modifiers: modifiers, body: body(), signature: signature)
return JavaIR.Method(annotations: annotations, modifiers: modifiers, body: body(), signature: signature, throwableExceptions: [])
}

static func methodThatThrows(annotations: Set<JavaAnnotation> = [], _ modifiers: JavaModifier, _ signature: String, _ throwableExceptions: [String], body: () -> [String]) -> JavaIR.Method {
return JavaIR.Method(annotations: annotations, modifiers: modifiers, body: body(), signature: signature, throwableExceptions: throwableExceptions)
}

static func ifBlock(condition: String, body: () -> [String]) -> String {
Expand Down Expand Up @@ -181,7 +202,7 @@ public struct JavaIR {

let extendsStmt = extends.map { " extends \($0) " } ?? ""

var lines = annotations.map { "@\($0.rawValue)" } + [
var lines = annotations.map { "@\($0.rendered)" } + [
"\(modifiers.render()) class \(name)\(extendsStmt)\(implementsStmt) {",
]

Expand Down
52 changes: 41 additions & 11 deletions Sources/Core/JavaModelRenderer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ public struct JavaModelRenderer: JavaFileRenderer {

func renderModelProperties(modifiers _: JavaModifier = [.private]) -> [[JavaIR.Property]] {
let props = transitiveProperties.map { param, schemaObj in
JavaIR.Property(annotations: ["SerializedName(\"\(param)\")"], modifiers: [.private], type: self.typeFromSchema(param, schemaObj), name: param.snakeCaseToPropertyName(), initialValue: "")
JavaIR.Property(annotations: [.serializedName(name: param)], modifiers: [.private], type: self.typeFromSchema(param, schemaObj), name: param.snakeCaseToPropertyName(), initialValue: "")
}

let bits = JavaIR.Property(annotations: [], modifiers: [.private], type: "int", name: "_bits", initialValue: "0")
Expand All @@ -76,11 +76,39 @@ public struct JavaModelRenderer: JavaFileRenderer {
return [props, bitmasks, [bits]]
}

func renderModelGetters(modifiers: JavaModifier = [.public]) -> [JavaIR.Method] {
func propertyGetterForParam(param: String, schemaObj: SchemaObjectProperty) -> JavaIR.Method {
let propertyName = param.snakeCaseToPropertyName()
let capitalizedPropertyName = param.snakeCaseToCapitalizedPropertyName()

// For Booleans, Integers and Doubles, make the getter method @NonNull and squash to a default value if necessary.
// This makes callers less susceptible to null pointer exceptions.
switch schemaObj.schema {
case .boolean:
return JavaIR.method([.public], "@NonNull Boolean get" + capitalizedPropertyName + "()") { [
"return this." + propertyName + " == null ? Boolean.FALSE : this." + propertyName + ";",
]
}
case .integer:
return JavaIR.method([.public], "@NonNull Integer get" + capitalizedPropertyName + "()") { [
"return this." + propertyName + " == null ? 0 : this." + propertyName + ";",
]
}
case .float:
return JavaIR.method([.public], "@NonNull Double get" + capitalizedPropertyName + "()") { [
"return this." + propertyName + " == null ? 0 : this." + propertyName + ";",
]
}
default:
return JavaIR.method([.public], typeFromSchema(param, schemaObj) + " get" + capitalizedPropertyName + "()") { [
"return this." + propertyName + ";",
]
}
}
}

func renderModelPropertyGetters() -> [JavaIR.Method] {
let getters = transitiveProperties.map { param, schemaObj in
JavaIR.method(modifiers, self.typeFromSchema(param, schemaObj) + " get" + param.snakeCaseToCapitalizedPropertyName() + "()") { [
"return this." + param.snakeCaseToPropertyName() + ";",
] }
propertyGetterForParam(param: param, schemaObj: schemaObj)
}
return getters
}
Expand Down Expand Up @@ -164,7 +192,7 @@ public struct JavaModelRenderer: JavaFileRenderer {

func renderBuilderProperties(modifiers _: JavaModifier = [.private]) -> [[JavaIR.Property]] {
let props = transitiveProperties.map { param, schemaObj in
JavaIR.Property(annotations: ["SerializedName(\"\(param)\")"], modifiers: [.private], type: self.typeFromSchema(param, schemaObj), name: param.snakeCaseToPropertyName(), initialValue: "")
JavaIR.Property(annotations: [.serializedName(name: param)], modifiers: [.private], type: self.typeFromSchema(param, schemaObj), name: param.snakeCaseToPropertyName(), initialValue: "")
}

let bits = JavaIR.Property(annotations: [], modifiers: [.private], type: "int", name: "_bits", initialValue: "0")
Expand Down Expand Up @@ -215,18 +243,20 @@ public struct JavaModelRenderer: JavaFileRenderer {
"this.elementTypeAdapter = gson.getAdapter(JsonElement.class);",
] }

let write = JavaIR.method(
let write = JavaIR.methodThatThrows(
annotations: [JavaAnnotation.override],
[.public],
"void write(JsonWriter writer, " + className + " value) throws IOException"
"void write(JsonWriter writer, " + className + " value)",
["IOException"]
) { [
"this.delegateTypeAdapter.write(writer, value);",
] }

let read = JavaIR.method(
let read = JavaIR.methodThatThrows(
annotations: [JavaAnnotation.override],
[.public],
className + " read(JsonReader reader) throws IOException"
className + " read(JsonReader reader)",
["IOException"]
) { [
"JsonElement tree = this.elementTypeAdapter.read(reader);",
className + " model = this.delegateTypeAdapter.fromJsonTree(tree);",
Expand Down Expand Up @@ -378,7 +408,7 @@ public struct JavaModelRenderer: JavaFileRenderer {
self.renderModelEquals(),
self.renderModelHashCode(),
] +
renderModelGetters() +
renderModelPropertyGetters() +
renderModelIsSetCheckers(),
enums: enumProps,
innerClasses: [
Expand Down

0 comments on commit e63f15c

Please sign in to comment.