Skip to content

Commit ab188c0

Browse files
authored
Merge pull request #18 from pettermahlen/generated-code-fb
Ensure generated code doesn't cause FindBugs/SpotBugs violations
2 parents 70c1a4d + abdf7a7 commit ab188c0

21 files changed

Lines changed: 295 additions & 72 deletions

File tree

dataenum-integration-test/pom.xml

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
<?xml version="1.0" encoding="UTF-8"?>
2-
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
2+
<project xmlns="http://maven.apache.org/POM/4.0.0"
3+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
35
<parent>
46
<artifactId>dataenum-parent</artifactId>
57
<groupId>com.spotify.dataenum</groupId>
@@ -45,6 +47,25 @@
4547
<groupId>com.github.siom79.japicmp</groupId>
4648
<artifactId>japicmp-maven-plugin</artifactId>
4749
</plugin>
50+
<plugin>
51+
<groupId>com.github.spotbugs</groupId>
52+
<artifactId>spotbugs-maven-plugin</artifactId>
53+
<executions>
54+
<execution>
55+
<phase>verify</phase>
56+
<goals><goal>check</goal></goals>
57+
</execution>
58+
</executions>
59+
<configuration>
60+
<plugins>
61+
<plugin>
62+
<groupId>com.mebigfatguy.fb-contrib</groupId>
63+
<artifactId>fb-contrib</artifactId>
64+
<version>7.4.0</version>
65+
</plugin>
66+
</plugins>
67+
</configuration>
68+
</plugin>
4869
</plugins>
4970
</build>
5071

dataenum-processor/src/main/java/com/spotify/dataenum/processor/data/Parameter.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,14 @@ public class Parameter {
2626
private final TypeName type;
2727
private final boolean nullable;
2828
private final boolean redacted;
29+
private final boolean isEnum;
2930

30-
public Parameter(String name, TypeName type, boolean nullable, boolean redacted) {
31+
public Parameter(String name, TypeName type, boolean nullable, boolean redacted, boolean isEnum) {
3132
this.name = name;
3233
this.type = type;
3334
this.nullable = nullable;
3435
this.redacted = redacted;
36+
this.isEnum = isEnum;
3537
}
3638

3739
public String name() {
@@ -49,4 +51,8 @@ public boolean canBeNull() {
4951
public boolean redacted() {
5052
return redacted;
5153
}
54+
55+
public boolean isEnum() {
56+
return isEnum;
57+
}
5258
}

dataenum-processor/src/main/java/com/spotify/dataenum/processor/generator/data/OutputSpecFactory.java

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@
2525
import com.spotify.dataenum.processor.data.Value;
2626
import com.spotify.dataenum.processor.parser.ParserException;
2727
import com.squareup.javapoet.ClassName;
28-
import com.squareup.javapoet.TypeName;
2928
import java.util.ArrayList;
3029
import java.util.List;
3130

@@ -48,8 +47,7 @@ public static OutputSpec create(Spec spec) throws ParserException {
4847
return new OutputSpec(spec, outputClass, values);
4948
}
5049

51-
public static ClassName toOutputClass(TypeName dataEnumType) throws ParserException {
52-
ClassName dataEnumClass = (ClassName) dataEnumType;
50+
static ClassName toOutputClass(ClassName dataEnumClass) throws ParserException {
5351

5452
String packageName = dataEnumClass.packageName();
5553
String name = dataEnumClass.simpleName();

dataenum-processor/src/main/java/com/spotify/dataenum/processor/generator/data/OutputValueFactory.java

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,10 +51,14 @@ static OutputValue create(Value value, ClassName specOutputClass, Spec spec)
5151

5252
if (isDataEnumParameter(rawParamType)) {
5353
TypeName paramOutputType =
54-
withParametersFromOther(toOutputClass(rawParamType), parameter.type());
54+
withParametersFromOther(toOutputClass((ClassName) rawParamType), parameter.type());
5555
parameters.add(
5656
new Parameter(
57-
parameter.name(), paramOutputType, parameter.canBeNull(), parameter.redacted()));
57+
parameter.name(),
58+
paramOutputType,
59+
parameter.canBeNull(),
60+
parameter.redacted(),
61+
false));
5862
} else {
5963
parameters.add(parameter);
6064
}
@@ -64,7 +68,8 @@ static OutputValue create(Value value, ClassName specOutputClass, Spec spec)
6468
}
6569

6670
private static boolean isDataEnumParameter(TypeName rawParamType) {
67-
return rawParamType.toString().endsWith(OutputSpecFactory.SUFFIX);
71+
return rawParamType.toString().endsWith(OutputSpecFactory.SUFFIX)
72+
&& rawParamType instanceof ClassName;
6873
}
6974

7075
private static Iterable<TypeVariableName> getTypeVariables(

dataenum-processor/src/main/java/com/spotify/dataenum/processor/generator/value/ValueTypeFactory.java

Lines changed: 43 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -182,30 +182,54 @@ private static MethodSpec createEquals(OutputValue value) throws ParserException
182182
TypeName wildCardTypeName = withWildCardTypeParameters(value);
183183
result.addStatement("$1T o = ($1T) other", wildCardTypeName);
184184
result.addCode("$[return ");
185-
boolean first = true;
185+
186+
List<Parameter> usingReferenceEquality = new ArrayList<>();
187+
List<Parameter> usingEquals = new ArrayList<>();
188+
186189
for (Parameter parameter : value.parameters()) {
190+
if (useReferenceEquality(parameter)) {
191+
usingReferenceEquality.add(parameter);
192+
} else {
193+
usingEquals.add(parameter);
194+
}
195+
}
196+
197+
boolean first = true;
198+
for (Parameter parameter : usingReferenceEquality) {
187199
if (first) {
188200
first = false;
189201
} else {
190202
result.addCode("\n&& ");
191203
}
192204

193205
String fieldName = parameter.name();
194-
if (parameter.type().isPrimitive()) {
195-
result.addCode("o.$1L == $1L", fieldName);
206+
result.addCode("o.$1L == $1L", fieldName);
207+
}
208+
209+
for (Parameter parameter : usingEquals) {
210+
if (first) {
211+
first = false;
196212
} else {
197-
if (parameter.canBeNull()) {
198-
result.addCode("equal(o.$1L, this.$1L)", fieldName);
199-
} else {
200-
result.addCode("o.$1L.equals(this.$1L)", fieldName);
201-
}
213+
result.addCode("\n&& ");
214+
}
215+
216+
String fieldName = parameter.name();
217+
if (parameter.canBeNull()) {
218+
result.addCode("equal(o.$1L, this.$1L)", fieldName);
219+
} else {
220+
result.addCode("o.$1L.equals(this.$1L)", fieldName);
202221
}
203222
}
223+
204224
result.addCode(";\n$]");
205225

206226
return result.build();
207227
}
208228

229+
private static boolean useReferenceEquality(Parameter parameter) {
230+
return parameter.isEnum() || parameter.type().isPrimitive();
231+
}
232+
209233
private static TypeName withWildCardTypeParameters(OutputValue value) {
210234
if (!value.hasTypeVariables()) {
211235
return value.outputClass();
@@ -231,9 +255,19 @@ private static MethodSpec createHashCode(OutputValue value) {
231255
}
232256

233257
result.addStatement("int result = 0");
258+
259+
int parameterCount = Iterables.sizeOf(value.parameters());
260+
int parameterIndex = 0;
261+
234262
for (Parameter parameter : value.parameters()) {
235263
String fieldName = parameter.name();
236-
result.addCode("result = result * 31 + ");
264+
parameterIndex++;
265+
266+
if (parameterIndex == parameterCount) {
267+
result.addCode("return result * 31 + ");
268+
} else {
269+
result.addCode("result = result * 31 + ");
270+
}
237271
if (parameter.type().isPrimitive()) {
238272
TypeName boxedType = parameter.type().box();
239273
result.addStatement("$T.valueOf($L).hashCode()", boxedType, fieldName);
@@ -245,7 +279,6 @@ private static MethodSpec createHashCode(OutputValue value) {
245279
}
246280
}
247281
}
248-
result.addStatement("return result");
249282
return result.build();
250283
}
251284

dataenum-processor/src/main/java/com/spotify/dataenum/processor/parser/ValueParser.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,8 +92,12 @@ static Value parse(Element element, ProcessingEnvironment processingEnv) {
9292
boolean nullable = isAnnotationPresent(parameterElement, ValueParser::isNullableAnnotation);
9393
boolean redacted =
9494
isAnnotationPresent(parameterElement, ValueParser.isRedactedAnnotation(processingEnv));
95+
Element parameterTypeElement =
96+
processingEnv.getTypeUtils().asElement(parameterElement.asType());
97+
boolean isEnum =
98+
parameterTypeElement != null && parameterTypeElement.getKind() == ElementKind.ENUM;
9599

96-
parameters.add(new Parameter(parameterName, parameterType, nullable, redacted));
100+
parameters.add(new Parameter(parameterName, parameterType, nullable, redacted, isEnum));
97101
}
98102

99103
String valueSimpleName = methodElement.getSimpleName().toString();

dataenum-processor/src/test/java/com/spotify/dataenum/processor/IntegrationTest.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,11 @@ public void privateConstructors() throws Exception {
9292
assertThatEnumGeneratedMatchingFile("hide/ctors/PrivateConstructors", "hide/package-info.java");
9393
}
9494

95+
@Test
96+
public void efficientEquals() throws Exception {
97+
assertThatEnumGeneratedMatchingFile("EfficientEquals", "MyEnum.java");
98+
}
99+
95100
@Test
96101
public void genericValuesGeneratedCodeCompiles() throws Exception {
97102
Compilation compilation =

dataenum-processor/src/test/java/com/spotify/dataenum/processor/generator/data/OutputSpecFactoryTest.java

Lines changed: 1 addition & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,6 @@
2525

2626
import com.spotify.dataenum.processor.parser.ParserException;
2727
import com.squareup.javapoet.ClassName;
28-
import com.squareup.javapoet.TypeName;
29-
import org.assertj.core.api.ThrowableAssert.ThrowingCallable;
3028
import org.junit.Test;
3129

3230
public class OutputSpecFactoryTest {
@@ -40,26 +38,8 @@ public void shouldRemoveDataEnumInterfaceSuffix() throws Exception {
4038

4139
@Test
4240
public void shouldThrowForNonDataenumClassName() throws Exception {
43-
assertThatThrownBy(
44-
new ThrowingCallable() {
45-
@Override
46-
public void call() throws Throwable {
47-
OutputSpecFactory.toOutputClass(ClassName.get("com.spotify", "My"));
48-
}
49-
})
41+
assertThatThrownBy(() -> OutputSpecFactory.toOutputClass(ClassName.get("com.spotify", "My")))
5042
.isInstanceOf(ParserException.class)
5143
.hasMessageContaining("Bad name");
5244
}
53-
54-
@Test
55-
public void shouldThrowForNonClassName() throws Exception {
56-
assertThatThrownBy(
57-
new ThrowingCallable() {
58-
@Override
59-
public void call() throws Throwable {
60-
OutputSpecFactory.toOutputClass(TypeName.BOOLEAN);
61-
}
62-
})
63-
.isInstanceOf(ClassCastException.class);
64-
}
6545
}
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
/*
2+
* -\-\-
3+
* DataEnum
4+
* --
5+
* Copyright (c) 2017 Spotify AB
6+
* --
7+
* Licensed under the Apache License, Version 2.0 (the "License");
8+
* you may not use this file except in compliance with the License.
9+
* You may obtain a copy of the License at
10+
*
11+
* http://www.apache.org/licenses/LICENSE-2.0
12+
*
13+
* Unless required by applicable law or agreed to in writing, software
14+
* distributed under the License is distributed on an "AS IS" BASIS,
15+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+
* See the License for the specific language governing permissions and
17+
* limitations under the License.
18+
* -/-/-
19+
*/
20+
21+
import static com.spotify.dataenum.DataenumUtils.checkNotNull;
22+
23+
import com.spotify.dataenum.function.Consumer;
24+
import com.spotify.dataenum.function.Function;
25+
import java.lang.Double;
26+
import java.lang.Integer;
27+
import java.lang.Object;
28+
import java.lang.Override;
29+
import java.lang.String;
30+
import java.lang.StringBuilder;
31+
import javax.annotation.Generated;
32+
import javax.annotation.Nonnull;
33+
34+
@Generated("com.spotify.dataenum.processor.DataEnumProcessor")
35+
public abstract class EfficientEquals {
36+
EfficientEquals() {
37+
}
38+
39+
public static EfficientEquals value(int param1, @Nonnull String param2, @Nonnull MyEnum param3, double param4) {
40+
return new Value(param1, param2, param3, param4);
41+
}
42+
43+
public final boolean isValue() {
44+
return (this instanceof Value);
45+
}
46+
47+
public final Value asValue() {
48+
return (Value) this;
49+
}
50+
51+
public abstract void match(@Nonnull Consumer<Value> value);
52+
53+
public abstract <R_> R_ map(@Nonnull Function<Value, R_> value);
54+
55+
public static final class Value extends EfficientEquals {
56+
private final int param1;
57+
58+
private final String param2;
59+
60+
private final MyEnum param3;
61+
62+
private final double param4;
63+
64+
Value(int param1, String param2, MyEnum param3, double param4) {
65+
this.param1 = param1;
66+
this.param2 = checkNotNull(param2);
67+
this.param3 = checkNotNull(param3);
68+
this.param4 = param4;
69+
}
70+
71+
public final int param1() {
72+
return param1;
73+
}
74+
75+
@Nonnull
76+
public final String param2() {
77+
return param2;
78+
}
79+
80+
@Nonnull
81+
public final MyEnum param3() {
82+
return param3;
83+
}
84+
85+
public final double param4() {
86+
return param4;
87+
}
88+
89+
@Override
90+
public boolean equals(Object other) {
91+
if (other == this) return true;
92+
if (!(other instanceof Value)) return false;
93+
Value o = (Value) other;
94+
return o.param1 == param1
95+
&& o.param3 == param3
96+
&& o.param4 == param4
97+
&& o.param2.equals(this.param2);
98+
}
99+
100+
@Override
101+
public int hashCode() {
102+
int result = 0;
103+
result = result * 31 + Integer.valueOf(param1).hashCode();
104+
result = result * 31 + param2.hashCode();
105+
result = result * 31 + param3.hashCode();
106+
return result * 31 + Double.valueOf(param4).hashCode();
107+
}
108+
109+
@Override
110+
public String toString() {
111+
StringBuilder builder = new StringBuilder();
112+
builder.append("Value{param1=").append(param1);
113+
builder.append(", param2=").append(param2);
114+
builder.append(", param3=").append(param3);
115+
builder.append(", param4=").append(param4);
116+
return builder.append('}').toString();
117+
}
118+
119+
@Override
120+
public final void match(@Nonnull Consumer<Value> value) {
121+
value.accept(this);
122+
}
123+
124+
@Override
125+
public final <R_> R_ map(@Nonnull Function<Value, R_> value) {
126+
return value.apply(this);
127+
}
128+
}
129+
}

0 commit comments

Comments
 (0)