Skip to content
This repository has been archived by the owner on Oct 10, 2024. It is now read-only.

Commit

Permalink
Merge pull request #305 from tbroyer/intersectiontypes-in-ecj
Browse files Browse the repository at this point in the history
Workaround for Eclipse JDT/APT bug around intersection types
  • Loading branch information
swankjesse committed Jul 12, 2015
2 parents dfa195e + ff68284 commit b716399
Show file tree
Hide file tree
Showing 5 changed files with 401 additions and 227 deletions.
6 changes: 6 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,12 @@
<version>1.10.16</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jdt.core.compiler</groupId>
<artifactId>ecj</artifactId>
<version>4.4.2</version>
<scope>test</scope>
</dependency>
</dependencies>

<build>
Expand Down
49 changes: 8 additions & 41 deletions src/main/java/com/squareup/javapoet/TypeVariableName.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,13 @@
package com.squareup.javapoet;

import java.io.IOException;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import javax.lang.model.element.NestingKind;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeKind;
import javax.lang.model.element.TypeParameterElement;
import javax.lang.model.type.TypeMirror;

import static com.squareup.javapoet.Util.checkArgument;
Expand Down Expand Up @@ -80,8 +76,13 @@ public static TypeVariableName get(String name, Type... bounds) {

/** Returns type variable equivalent to {@code mirror}. */
public static TypeVariableName get(javax.lang.model.type.TypeVariable mirror) {
String name = mirror.asElement().getSimpleName().toString();
List<? extends TypeMirror> boundsMirrors = typeVariableBounds(mirror);
return get((TypeParameterElement) mirror.asElement());
}

/** Returns type variable equivalent to {@code element}. */
public static TypeVariableName get(TypeParameterElement element) {
String name = element.getSimpleName().toString();
List<? extends TypeMirror> boundsMirrors = element.getBounds();

List<TypeName> boundsTypeNames = new ArrayList<>();
for (TypeMirror typeMirror : boundsMirrors) {
Expand All @@ -91,40 +92,6 @@ public static TypeVariableName get(javax.lang.model.type.TypeVariable mirror) {
return new TypeVariableName(name, boundsTypeNames);
}

/**
* Returns a list of type mirrors representing the unpacked bounds of {@code typeVariable}. This
* is made gnarly by the need to unpack Java 8's new IntersectionType with reflection. We don't
* have that type in Java 7, and {@link TypeVariable}'s array of bounds is sufficient anyway.
*/
@SuppressWarnings("unchecked") // Gross things in support of Java 7 and Java 8.
private static List<? extends TypeMirror> typeVariableBounds(
javax.lang.model.type.TypeVariable typeVariable) {
TypeMirror upperBound = typeVariable.getUpperBound();

// On Java 8, unwrap an intersection type into its component bounds.
if ("INTERSECTION".equals(upperBound.getKind().name())) {
try {
Method method = upperBound.getClass().getMethod("getBounds");
return (List<? extends TypeMirror>) method.invoke(upperBound);
} catch (Exception e) {
throw new RuntimeException(e);
}
}

// On Java 7, intersection types exist but without explicit API. Use a (clumsy) heuristic.
if (upperBound.getKind() == TypeKind.DECLARED) {
TypeElement upperBoundElement = (TypeElement) ((DeclaredType) upperBound).asElement();
if (upperBoundElement.getNestingKind() == NestingKind.ANONYMOUS) {
List<TypeMirror> result = new ArrayList<>();
result.add(upperBoundElement.getSuperclass());
result.addAll(upperBoundElement.getInterfaces());
return result;
}
}

return Collections.singletonList(upperBound);
}

/** Returns type variable equivalent to {@code type}. */
public static TypeVariableName get(TypeVariable<?> type) {
return new TypeVariableName(type.getName(), TypeName.list(type.getBounds()));
Expand Down
220 changes: 220 additions & 0 deletions src/test/java/com/squareup/javapoet/AbstractTypesTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,220 @@
/*
* Copyright (C) 2014 Google, Inc.
*
* Licensed 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 com.squareup.javapoet;

import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.*;

import java.io.Serializable;
import java.nio.charset.Charset;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.lang.model.element.TypeElement;
import javax.lang.model.element.TypeParameterElement;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.type.WildcardType;
import javax.lang.model.util.Elements;
import javax.lang.model.util.Types;

import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;

import com.google.testing.compile.CompilationRule;

public abstract class AbstractTypesTest {
protected abstract Elements getElements();
protected abstract Types getTypes();

private TypeElement getElement(Class<?> clazz) {
return getElements().getTypeElement(clazz.getCanonicalName());
}

private TypeMirror getMirror(Class<?> clazz) {
return getElement(clazz).asType();
}

@Test public void getBasicTypeMirror() {
assertThat(TypeName.get(getMirror(Object.class)))
.isEqualTo(ClassName.get(Object.class));
assertThat(TypeName.get(getMirror(Charset.class)))
.isEqualTo(ClassName.get(Charset.class));
assertThat(TypeName.get(getMirror(AbstractTypesTest.class)))
.isEqualTo(ClassName.get(AbstractTypesTest.class));
}

@Test public void getParameterizedTypeMirror() {
DeclaredType setType =
getTypes().getDeclaredType(getElement(Set.class), getMirror(Object.class));
assertThat(TypeName.get(setType))
.isEqualTo(ParameterizedTypeName.get(ClassName.get(Set.class), ClassName.OBJECT));
}

static class Parameterized<
Simple,
ExtendsClass extends Number,
ExtendsInterface extends Runnable,
ExtendsTypeVariable extends Simple,
Intersection extends Number & Runnable,
IntersectionOfInterfaces extends Runnable & Serializable> {}

@Test public void getTypeVariableTypeMirror() {
List<? extends TypeParameterElement> typeVariables =
getElement(Parameterized.class).getTypeParameters();

// Members of converted types use ClassName and not Class<?>.
ClassName number = ClassName.get(Number.class);
ClassName runnable = ClassName.get(Runnable.class);
ClassName serializable = ClassName.get(Serializable.class);

assertThat(TypeName.get(typeVariables.get(0).asType()))
.isEqualTo(TypeVariableName.get("Simple"));
assertThat(TypeName.get(typeVariables.get(1).asType()))
.isEqualTo(TypeVariableName.get("ExtendsClass", number));
assertThat(TypeName.get(typeVariables.get(2).asType()))
.isEqualTo(TypeVariableName.get("ExtendsInterface", runnable));
assertThat(TypeName.get(typeVariables.get(3).asType()))
.isEqualTo(TypeVariableName.get("ExtendsTypeVariable", TypeVariableName.get("Simple")));
assertThat(TypeName.get(typeVariables.get(4).asType()))
.isEqualTo(TypeVariableName.get("Intersection", number, runnable));
assertThat(TypeName.get(typeVariables.get(5).asType()))
.isEqualTo(TypeVariableName.get("IntersectionOfInterfaces", runnable, serializable));
assertThat(((TypeVariableName) TypeName.get(typeVariables.get(4).asType())).bounds)
.containsExactly(number, runnable);
}

@Test public void getPrimitiveTypeMirror() {
assertThat(TypeName.get(getTypes().getPrimitiveType(TypeKind.BOOLEAN)))
.isEqualTo(TypeName.BOOLEAN);
assertThat(TypeName.get(getTypes().getPrimitiveType(TypeKind.BYTE)))
.isEqualTo(TypeName.BYTE);
assertThat(TypeName.get(getTypes().getPrimitiveType(TypeKind.SHORT)))
.isEqualTo(TypeName.SHORT);
assertThat(TypeName.get(getTypes().getPrimitiveType(TypeKind.INT)))
.isEqualTo(TypeName.INT);
assertThat(TypeName.get(getTypes().getPrimitiveType(TypeKind.LONG)))
.isEqualTo(TypeName.LONG);
assertThat(TypeName.get(getTypes().getPrimitiveType(TypeKind.CHAR)))
.isEqualTo(TypeName.CHAR);
assertThat(TypeName.get(getTypes().getPrimitiveType(TypeKind.FLOAT)))
.isEqualTo(TypeName.FLOAT);
assertThat(TypeName.get(getTypes().getPrimitiveType(TypeKind.DOUBLE)))
.isEqualTo(TypeName.DOUBLE);
}

@Test public void getArrayTypeMirror() {
assertThat(TypeName.get(getTypes().getArrayType(getMirror(Object.class))))
.isEqualTo(ArrayTypeName.of(ClassName.OBJECT));
}

@Test public void getVoidTypeMirror() {
assertThat(TypeName.get(getTypes().getNoType(TypeKind.VOID)))
.isEqualTo(TypeName.VOID);
}

@Test public void getNullTypeMirror() {
try {
TypeName.get(getTypes().getNullType());
fail();
} catch (IllegalArgumentException expected) {
}
}

@Test public void parameterizedType() throws Exception {
ParameterizedTypeName type = ParameterizedTypeName.get(Map.class, String.class, Long.class);
assertThat(type.toString()).isEqualTo("java.util.Map<java.lang.String, java.lang.Long>");
}

@Test public void arrayType() throws Exception {
ArrayTypeName type = ArrayTypeName.of(String.class);
assertThat(type.toString()).isEqualTo("java.lang.String[]");
}

@Test public void wildcardExtendsType() throws Exception {
WildcardTypeName type = WildcardTypeName.subtypeOf(CharSequence.class);
assertThat(type.toString()).isEqualTo("? extends java.lang.CharSequence");
}

@Test public void wildcardExtendsObject() throws Exception {
WildcardTypeName type = WildcardTypeName.subtypeOf(Object.class);
assertThat(type.toString()).isEqualTo("?");
}

@Test public void wildcardSuperType() throws Exception {
WildcardTypeName type = WildcardTypeName.supertypeOf(String.class);
assertThat(type.toString()).isEqualTo("? super java.lang.String");
}

@Test public void wildcardMirrorNoBounds() throws Exception {
WildcardType wildcard = getTypes().getWildcardType(null, null);
TypeName type = TypeName.get(wildcard);
assertThat(type.toString()).isEqualTo("?");
}

@Test public void wildcardMirrorExtendsType() throws Exception {
Types types = getTypes();
Elements elements = getElements();
TypeMirror charSequence = elements.getTypeElement(CharSequence.class.getName()).asType();
WildcardType wildcard = types.getWildcardType(charSequence, null);
TypeName type = TypeName.get(wildcard);
assertThat(type.toString()).isEqualTo("? extends java.lang.CharSequence");
}

@Test public void wildcardMirrorSuperType() throws Exception {
Types types = getTypes();
Elements elements = getElements();
TypeMirror string = elements.getTypeElement(String.class.getName()).asType();
WildcardType wildcard = types.getWildcardType(null, string);
TypeName type = TypeName.get(wildcard);
assertThat(type.toString()).isEqualTo("? super java.lang.String");
}

@Test public void typeVariable() throws Exception {
TypeVariableName type = TypeVariableName.get("T", CharSequence.class);
assertThat(type.toString()).isEqualTo("T"); // (Bounds are only emitted in declaration.)
}

@Test public void box() throws Exception {
assertThat(TypeName.INT.box()).isEqualTo(ClassName.get(Integer.class));
assertThat(TypeName.VOID.box()).isEqualTo(ClassName.get(Void.class));
assertThat(ClassName.get(Integer.class).box()).isEqualTo(ClassName.get(Integer.class));
assertThat(ClassName.get(Void.class).box()).isEqualTo(ClassName.get(Void.class));
assertThat(TypeName.OBJECT.box()).isEqualTo(TypeName.OBJECT);
assertThat(ClassName.get(String.class).box()).isEqualTo(ClassName.get(String.class));
}

@Test public void unbox() throws Exception {
assertThat(TypeName.INT).isEqualTo(TypeName.INT.unbox());
assertThat(TypeName.VOID).isEqualTo(TypeName.VOID.unbox());
assertThat(ClassName.get(Integer.class).unbox()).isEqualTo(TypeName.INT.unbox());
assertThat(ClassName.get(Void.class).unbox()).isEqualTo(TypeName.VOID.unbox());
try {
TypeName.OBJECT.unbox();
fail();
} catch (UnsupportedOperationException expected) {
}
try {
ClassName.get(String.class).unbox();
fail();
} catch (UnsupportedOperationException expected) {
}
}
}
Loading

0 comments on commit b716399

Please sign in to comment.