Skip to content

Commit 0599436

Browse files
authored
Merge pull request #6 from pettermahlen/mixed-generics
ensure generics use doesn't lead to warnings in generated code
2 parents dbbd9b5 + f6c6114 commit 0599436

9 files changed

Lines changed: 281 additions & 34 deletions

File tree

dataenum-processor/src/main/java/com/spotify/dataenum/processor/generator/spec/SpecTypeFactory.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ public static TypeSpec create(OutputSpec spec) throws ParserException {
5656
ValueMethods valueMethods = new ValueMethods(value);
5757
factoryMethods.add(valueMethods.createFactoryMethod(spec));
5858
isMethods.add(valueMethods.createIsMethod());
59-
asMethods.add(valueMethods.createAsMethod());
59+
asMethods.add(valueMethods.createAsMethod(spec));
6060
}
6161

6262
TypeSpec.Builder enumBuilder =

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

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import com.spotify.dataenum.processor.data.OutputValue;
2424
import com.spotify.dataenum.processor.data.Parameter;
2525
import com.squareup.javapoet.MethodSpec;
26+
import com.squareup.javapoet.MethodSpec.Builder;
2627
import com.squareup.javapoet.ParameterSpec;
2728
import java.util.ArrayList;
2829
import java.util.List;
@@ -95,12 +96,19 @@ public MethodSpec createIsMethod() {
9596
.build();
9697
}
9798

98-
public MethodSpec createAsMethod() {
99-
return MethodSpec.methodBuilder("as" + value.name())
100-
.addModifiers(Modifier.PUBLIC, Modifier.FINAL)
101-
.returns(value.parameterizedOutputClass())
102-
.addStatement("return ($T) this", value.parameterizedOutputClass())
103-
.build();
99+
public MethodSpec createAsMethod(OutputSpec spec) {
100+
Builder builder =
101+
MethodSpec.methodBuilder("as" + value.name())
102+
.addModifiers(Modifier.PUBLIC, Modifier.FINAL)
103+
.returns(value.parameterizedOutputClass())
104+
.addStatement("return ($T) this", value.parameterizedOutputClass());
105+
106+
if (!ValueTypeFactory.extractMissingTypeVariablesForValue(value, spec).isEmpty()
107+
&& value.hasTypeVariables()) {
108+
builder.addAnnotation(ValueTypeFactory.SUPPRESS_UNCHECKED_WARNINGS);
109+
}
110+
111+
return builder.build();
104112
}
105113

106114
private static String asCamelCase(String text) {

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

Lines changed: 44 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -26,20 +26,27 @@
2626
import com.spotify.dataenum.processor.generator.match.MatchMethods;
2727
import com.spotify.dataenum.processor.parser.ParserException;
2828
import com.spotify.dataenum.processor.util.Iterables;
29+
import com.squareup.javapoet.AnnotationSpec;
2930
import com.squareup.javapoet.FieldSpec;
3031
import com.squareup.javapoet.MethodSpec;
32+
import com.squareup.javapoet.MethodSpec.Builder;
3133
import com.squareup.javapoet.ParameterizedTypeName;
3234
import com.squareup.javapoet.TypeName;
3335
import com.squareup.javapoet.TypeSpec;
3436
import com.squareup.javapoet.TypeVariableName;
37+
import com.squareup.javapoet.WildcardTypeName;
3538
import java.util.ArrayList;
39+
import java.util.Arrays;
3640
import java.util.List;
3741
import javax.annotation.Nonnull;
3842
import javax.annotation.Nullable;
3943
import javax.lang.model.element.Modifier;
4044

4145
public class ValueTypeFactory {
4246

47+
static final AnnotationSpec SUPPRESS_UNCHECKED_WARNINGS =
48+
AnnotationSpec.builder(SuppressWarnings.class).addMember("value", "$S", "unchecked").build();
49+
4350
private ValueTypeFactory() {}
4451

4552
public static TypeSpec create(
@@ -163,7 +170,8 @@ private static MethodSpec createEquals(OutputValue value) throws ParserException
163170
result.addStatement("if (other == this) return true");
164171
result.addStatement("if (!(other instanceof $T)) return false", value.outputClass());
165172

166-
result.addStatement("$1T o = ($1T) other", value.outputClass());
173+
TypeName wildCardTypeName = withWildCardTypeParameters(value);
174+
result.addStatement("$1T o = ($1T) other", wildCardTypeName);
167175
result.addCode("$[return ");
168176
boolean first = true;
169177
for (Parameter parameter : value.parameters()) {
@@ -189,6 +197,18 @@ private static MethodSpec createEquals(OutputValue value) throws ParserException
189197
return result.build();
190198
}
191199

200+
private static TypeName withWildCardTypeParameters(OutputValue value) {
201+
if (!value.hasTypeVariables()) {
202+
return value.outputClass();
203+
}
204+
205+
TypeName[] wildCards = new TypeName[Iterables.sizeOf(value.typeVariables())];
206+
207+
Arrays.fill(wildCards, WildcardTypeName.subtypeOf(TypeName.OBJECT));
208+
209+
return ParameterizedTypeName.get(value.outputClass(), wildCards);
210+
}
211+
192212
private static MethodSpec createHashCode(OutputValue value) {
193213
MethodSpec.Builder result =
194214
MethodSpec.methodBuilder("hashCode")
@@ -252,18 +272,34 @@ private static MethodSpec createToString(OutputValue value) {
252272
}
253273

254274
private static MethodSpec createAsSpecMethod(OutputValue value, OutputSpec spec) {
275+
List<TypeVariableName> missingTypeVariables = extractMissingTypeVariablesForValue(value, spec);
276+
277+
Builder builder =
278+
MethodSpec.methodBuilder("as" + spec.outputClass().simpleName())
279+
.addModifiers(Modifier.PUBLIC, Modifier.FINAL)
280+
.returns(spec.parameterizedOutputClass())
281+
.addTypeVariables(missingTypeVariables)
282+
.addStatement("return ($T) this", spec.parameterizedOutputClass());
283+
284+
// if there are type variables that this sub-type doesn't use, they will lead to 'unchecked
285+
// cast'
286+
// warnings when compiling the generated code. These warnings are safe to suppress, since this
287+
// sub type will never use those type variables.
288+
if (!missingTypeVariables.isEmpty()) {
289+
builder.addAnnotation(SUPPRESS_UNCHECKED_WARNINGS);
290+
}
291+
292+
return builder.build();
293+
}
294+
295+
static List<TypeVariableName> extractMissingTypeVariablesForValue(
296+
OutputValue value, OutputSpec spec) {
255297
List<TypeVariableName> missingTypeVariables = new ArrayList<>();
256298
for (TypeVariableName typeVariableName : spec.typeVariables()) {
257299
if (!Iterables.contains(value.typeVariables(), typeVariableName)) {
258300
missingTypeVariables.add(typeVariableName);
259301
}
260302
}
261-
262-
return MethodSpec.methodBuilder("as" + spec.outputClass().simpleName())
263-
.addModifiers(Modifier.PUBLIC, Modifier.FINAL)
264-
.returns(spec.parameterizedOutputClass())
265-
.addTypeVariables(missingTypeVariables)
266-
.addStatement("return ($T) this", spec.parameterizedOutputClass())
267-
.build();
303+
return missingTypeVariables;
268304
}
269305
}

dataenum-processor/src/main/java/com/spotify/dataenum/processor/util/Iterables.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,4 +43,14 @@ public static <T> T[] toArray(Iterable<T> iterable, Class<T> tClass) {
4343
}
4444
return out.toArray((T[]) Array.newInstance(tClass, out.size()));
4545
}
46+
47+
public static <T> int sizeOf(Iterable<T> iterable) {
48+
int size = 0;
49+
50+
for (T entry : iterable) {
51+
size++;
52+
}
53+
54+
return size;
55+
}
4656
}

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,16 @@ public void genericValuesEnum() throws Exception {
7676
assertThatEnumGeneratedMatchingFile("GenericValues");
7777
}
7878

79+
@Test
80+
public void genericValuesGeneratedCodeCompiles() throws Exception {
81+
Compilation compilation =
82+
javac()
83+
.withOptions("-Xlint:all")
84+
.compile(JavaFileObjects.forResource("GenericValues.java"));
85+
86+
assertThat(compilation).succeededWithoutWarnings();
87+
}
88+
7989
@Test
8090
public void recursiveGenericValueEnum() throws Exception {
8191
assertThatEnumGeneratedMatchingFile("RecursiveGenericValue");

0 commit comments

Comments
 (0)