Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for Java 8 modifier changes #244

Merged
merged 1 commit into from
Mar 17, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 31 additions & 8 deletions src/main/java/com/squareup/javapoet/TypeSpec.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,19 +22,23 @@
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;

import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.Modifier;

import static com.squareup.javapoet.Util.checkArgument;
import static com.squareup.javapoet.Util.checkNotNull;
import static com.squareup.javapoet.Util.checkState;
import static com.squareup.javapoet.Util.hasDefaultModifier;
import static com.squareup.javapoet.Util.requireExactlyOneOf;

/** A generated class, interface, or enum declaration. */
public final class TypeSpec {
Expand Down Expand Up @@ -429,9 +433,14 @@ public Builder addFields(Collection<FieldSpec> fieldSpecs) {
}

public Builder addField(FieldSpec fieldSpec) {
checkArgument(fieldSpec.modifiers.containsAll(kind.implicitFieldModifiers),
"%s %s.%s requires modifiers %s", kind, name, fieldSpec.name,
kind.implicitFieldModifiers);
checkState(kind != Kind.ANNOTATION, "%s %s cannot have fields", kind, name);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I love this

if (kind == Kind.INTERFACE) {
requireExactlyOneOf(EnumSet.of(Modifier.PUBLIC, Modifier.PRIVATE),
fieldSpec.modifiers);
Set<Modifier> check = EnumSet.of(Modifier.STATIC, Modifier.FINAL);
checkState(fieldSpec.modifiers.containsAll(check), "%s %s.%s requires modifiers %s",
kind, name, fieldSpec.name, check);
}
fieldSpecs.add(fieldSpec);
return this;
}
Expand All @@ -453,11 +462,25 @@ public Builder addMethods(Collection<MethodSpec> methodSpecs) {
}

public Builder addMethod(MethodSpec methodSpec) {
checkArgument(methodSpec.modifiers.containsAll(kind.implicitMethodModifiers),
"%s %s.%s requires modifiers %s", kind, name, methodSpec.name,
kind.implicitMethodModifiers);
if (kind == Kind.INTERFACE) {
Set<Modifier> mods = EnumSet.of(Modifier.ABSTRACT, Modifier.STATIC);
if (Util.DEFAULT != null) {
mods.add(Util.DEFAULT);
}
requireExactlyOneOf(mods, methodSpec.modifiers);
requireExactlyOneOf(EnumSet.of(Modifier.PUBLIC, Modifier.PRIVATE),
methodSpec.modifiers);
} else if (kind == Kind.ANNOTATION) {
checkState(methodSpec.modifiers.containsAll(kind.implicitMethodModifiers),
"%s %s.%s cannot have modifiers", kind, name, methodSpec.name);
}
if (kind != Kind.ANNOTATION) {
checkState(methodSpec.defaultValue == null, "default method != null");
checkState(methodSpec.defaultValue == null, "%s %s.%s cannot have a default value",
kind, name, methodSpec.name);
}
if (kind != Kind.INTERFACE) {
checkState(!hasDefaultModifier(methodSpec.modifiers), "%s %s.%s cannot be default",
kind, name, methodSpec.name);
}
methodSpecs.add(methodSpec);
return this;
Expand All @@ -474,7 +497,7 @@ public Builder addTypes(Collection<TypeSpec> typeSpecs) {
public Builder addType(TypeSpec typeSpec) {
checkArgument(typeSpec.modifiers.containsAll(kind.implicitTypeModifiers),
"%s %s.%s requires modifiers %s", kind, name, typeSpec.name,
kind.implicitFieldModifiers);
kind.implicitTypeModifiers);
typeSpecs.add(typeSpec);
return this;
}
Expand Down
71 changes: 71 additions & 0 deletions src/main/java/com/squareup/javapoet/Util.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@
import java.util.Map;
import java.util.Set;

import javax.lang.model.element.Modifier;

/**
* Like Guava, but worse and standalone. This makes it easier to mix JavaPoet with libraries that
* bring their own version of Guava.
Expand All @@ -32,6 +34,16 @@ final class Util {
private Util() {
}

public static final Modifier DEFAULT;
static {
Modifier def = null;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

neat

try {
def = Modifier.valueOf("DEFAULT");
} catch (IllegalArgumentException ignored) {
}
DEFAULT = def;
}

public static <K, V> Map<K, List<V>> immutableMultimap(Map<K, List<V>> multimap) {
LinkedHashMap<K, List<V>> result = new LinkedHashMap<>();
for (Map.Entry<K, List<V>> entry : multimap.entrySet()) {
Expand Down Expand Up @@ -82,4 +94,63 @@ public static <T> Set<T> union(Set<T> a, Set<T> b) {
result.addAll(b);
return result;
}

public static <T> Set<Set<T>> powerSet(Set<T> originalSet) {
Set<Set<T>> sets = new LinkedHashSet<>();
if (originalSet.isEmpty()) {
sets.add(new LinkedHashSet<T>());
return sets;
}
List<T> list = new ArrayList<T>(originalSet);
T head = list.get(0);
Set<T> rest = new LinkedHashSet<T>(list.subList(1, list.size()));
for (Set<T> set : powerSet(rest)) {
Set<T> newSet = new LinkedHashSet<T>();
newSet.add(head);
newSet.addAll(set);
sets.add(newSet);
sets.add(set);
}
return sets;
}

/**
* Finds a subset of {@code set} in {@code modifiers}. Ignores empty subsets,
* and if {@code set} is empty then {@code true} is always returned.
*
* @param modifiers
* - The input modifiers
* @param set
* - The set of modifiers to make subsets of
*/
public static void requireSubsetOf(Set<Modifier> set, Set<Modifier> modifiers) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unused.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(Though neat!)

Set<Set<Modifier>> powerSet = powerSet(set);
boolean containsSubset = set.isEmpty();
for (Set<Modifier> subset : powerSet) {
if (subset.isEmpty()) {
continue;
}
containsSubset |= modifiers.containsAll(subset);
}
checkState(containsSubset, "%s should contain a subset of %s", modifiers, set);
}

public static void requireExactlyOneOf(Set<Modifier> set, Set<Modifier> modifiers) {
boolean containsOne = false;
boolean containsMany = false;
for (Modifier check : set) {
boolean containsCheck = modifiers.contains(check);
containsMany = containsOne && containsCheck;
if (containsMany) {
break;
}
containsOne |= containsCheck;
}
checkState(containsOne, "%s must contain one of %s", modifiers, set);
checkState(!containsMany, "%s must contain only one of %s", modifiers, set);
}

public static boolean hasDefaultModifier(Collection<Modifier> modifiers) {
return DEFAULT != null && modifiers.contains(DEFAULT);
}
}
45 changes: 45 additions & 0 deletions src/test/java/com/squareup/javapoet/TypeSpecTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

import com.google.common.collect.ImmutableMap;
import com.google.testing.compile.CompilationRule;

import java.io.IOException;
import java.io.Serializable;
import java.math.BigDecimal;
Expand All @@ -30,10 +31,12 @@
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Random;

import javax.lang.model.element.Element;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.TypeMirror;

import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
Expand All @@ -42,6 +45,7 @@

import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.fail;
import static org.junit.Assume.assumeTrue;

@RunWith(JUnit4.class)
public final class TypeSpecTest {
Expand All @@ -54,6 +58,10 @@ private TypeElement getElement(Class<?> clazz) {
return compilation.getElements().getTypeElement(clazz.getCanonicalName());
}

private boolean isJava8() {
return Util.DEFAULT != null;
}

@Test public void basic() throws Exception {
TypeSpec taco = TypeSpec.classBuilder("Taco")
.addMethod(MethodSpec.methodBuilder("toString")
Expand Down Expand Up @@ -703,6 +711,43 @@ public void classCannotHaveDefaultValueForMethod() throws Exception {
} catch (IllegalStateException expected) {}
}

@Test
public void classCannotHaveDefaultMethods() throws Exception {
assumeTrue(isJava8());
try {
TypeSpec.classBuilder("Tacos")
.addMethod(MethodSpec.methodBuilder("test")
.addModifiers(Modifier.PUBLIC, Modifier.valueOf("DEFAULT"))
.returns(int.class)
.addCode(CodeBlock.builder().addStatement("return 0").build())
.build())
.build();
fail();
} catch (IllegalStateException expected) {}
}

@Test
public void interfaceDefaultMethods() throws Exception {
assumeTrue(isJava8());
TypeSpec bar = TypeSpec.interfaceBuilder("Tacos")
.addMethod(MethodSpec.methodBuilder("test")
.addModifiers(Modifier.PUBLIC, Modifier.valueOf("DEFAULT"))
.returns(int.class)
.addCode(CodeBlock.builder().addStatement("return 0").build())
.build())
.build();

assertThat(toString(bar)).isEqualTo(""
+ "package com.squareup.tacos;\n"
+ "\n"
+ "interface Tacos {\n"
+ " default int test() {\n"
+ " return 0;\n"
+ " }\n"
+ "}\n"
);
}

@Test public void referencedAndDeclaredSimpleNamesConflict() throws Exception {
FieldSpec internalTop = FieldSpec.builder(
ClassName.get(tacosPackage, "Top"), "internalTop").build();
Expand Down