blob: ea3058ec8728ce91a225d9de79b6fb4b2301eb06 [file] [log] [blame]
/*
* Copyright (C) 2016 The Dagger Authors.
*
* 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 dagger.internal.codegen;
import static com.squareup.javapoet.MethodSpec.methodBuilder;
import static com.squareup.javapoet.TypeSpec.anonymousClassBuilder;
import static dagger.internal.codegen.TypeNames.providerOf;
import static dagger.internal.codegen.TypeNames.rawTypeName;
import static java.util.stream.StreamSupport.stream;
import static javax.lang.model.element.Modifier.PUBLIC;
import com.google.auto.common.MoreElements;
import com.google.auto.common.MoreTypes;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.CodeBlock;
import com.squareup.javapoet.CodeBlock.Builder;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.TypeName;
import java.util.stream.Collector;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.DeclaredType;
final class CodeBlocks {
/**
* A {@link Collector} implementation that joins {@link CodeBlock} instances together into one
* separated by {@code delimiter}. For example, joining {@code String s}, {@code Object o} and
* {@code int i} using {@code ", "} would produce {@code String s, Object o, int i}.
*/
static Collector<CodeBlock, ?, CodeBlock> joiningCodeBlocks(String delimiter) {
return Collector.of(
() -> new CodeBlockJoiner(delimiter, CodeBlock.builder()),
CodeBlockJoiner::add,
CodeBlockJoiner::merge,
CodeBlockJoiner::join);
}
/**
* Joins {@link CodeBlock} instances in a manner suitable for use as method parameters (or
* arguments). This is equivalent to {@code joiningCodeBlocks(", ")}.
*/
static Collector<CodeBlock, ?, CodeBlock> toParametersCodeBlock() {
return joiningCodeBlocks(", ");
}
/**
* Joins {@link TypeName} instances into a {@link CodeBlock} that is a comma-separated list for
* use as type parameters or javadoc method arguments.
*/
static Collector<TypeName, ?, CodeBlock> toTypeNamesCodeBlock() {
return Collector.of(
() -> new CodeBlockJoiner(", ", CodeBlock.builder()),
CodeBlockJoiner::addTypeName,
CodeBlockJoiner::merge,
CodeBlockJoiner::join);
}
/**
* Concatenates {@link CodeBlock} instances separated by newlines for readability. This is
* equivalent to {@code joiningCodeBlocks("\n")}.
*/
static Collector<CodeBlock, ?, CodeBlock> toConcatenatedCodeBlock() {
return joiningCodeBlocks("\n");
}
/** Returns a comma-separated version of {@code codeBlocks} as one unified {@link CodeBlock}. */
static CodeBlock makeParametersCodeBlock(Iterable<CodeBlock> codeBlocks) {
return stream(codeBlocks.spliterator(), false).collect(toParametersCodeBlock());
}
/** Adds an annotation to a method. */
static void addAnnotation(MethodSpec.Builder method, DeclaredType nullableType) {
method.addAnnotation(ClassName.get(MoreTypes.asTypeElement(nullableType)));
}
/**
* Returns an anonymous {@link javax.inject.Provider} class with the single {@link
* javax.inject.Provider#get()} method implemented by {@code body}.
*/
static CodeBlock anonymousProvider(TypeName providedType, CodeBlock body) {
return CodeBlock.of("$L",
anonymousClassBuilder("")
.superclass(providerOf(providedType))
.addMethod(
methodBuilder("get")
.addAnnotation(Override.class)
.addModifiers(PUBLIC)
.returns(providedType)
.addCode(body)
.build())
.build());
}
private static final class CodeBlockJoiner {
private final String delimiter;
private final CodeBlock.Builder builder;
private boolean first = true;
CodeBlockJoiner(String delimiter, Builder builder) {
this.delimiter = delimiter;
this.builder = builder;
}
@CanIgnoreReturnValue
CodeBlockJoiner add(CodeBlock codeBlock) {
maybeAddDelimiter();
builder.add(codeBlock);
return this;
}
@CanIgnoreReturnValue
CodeBlockJoiner addTypeName(TypeName typeName) {
maybeAddDelimiter();
builder.add("$T", typeName);
return this;
}
private void maybeAddDelimiter() {
if (!first) {
builder.add(delimiter);
}
first = false;
}
@CanIgnoreReturnValue
CodeBlockJoiner merge(CodeBlockJoiner other) {
CodeBlock otherBlock = other.builder.build();
if (!otherBlock.isEmpty()) {
add(otherBlock);
}
return this;
}
CodeBlock join() {
return builder.build();
}
}
/**
* Returns one unified {@link CodeBlock} which joins each item in {@code codeBlocks} with a
* newline.
*/
static CodeBlock concat(Iterable<CodeBlock> codeBlocks) {
return stream(codeBlocks.spliterator(), false).collect(toConcatenatedCodeBlock());
}
static CodeBlock stringLiteral(String toWrap) {
return CodeBlock.of("$S", toWrap);
}
/** Returns a javadoc {@literal @link} tag that poins to the given {@link ExecutableElement}. */
static CodeBlock javadocLinkTo(ExecutableElement executableElement) {
CodeBlock.Builder builder =
CodeBlock.builder()
.add(
"{@link $T#",
rawTypeName(
ClassName.get(MoreElements.asType(executableElement.getEnclosingElement()))));
switch (executableElement.getKind()) {
case METHOD:
builder.add("$L", executableElement.getSimpleName());
break;
case CONSTRUCTOR:
builder.add("$L", executableElement.getEnclosingElement().getSimpleName());
break;
case STATIC_INIT:
case INSTANCE_INIT:
throw new IllegalArgumentException(
"cannot create a javadoc link to an initializer: " + executableElement);
default:
throw new AssertionError(executableElement.toString());
}
builder.add("(");
builder.add(
executableElement
.getParameters()
.stream()
.map(VariableElement::asType)
.map(TypeName::get)
.map(TypeNames::rawTypeName)
.collect(toTypeNamesCodeBlock()));
return builder.add(")}").build();
}
private CodeBlocks() {}
}