com.squareup.javapoet.TypeSpec.java Source code

Java tutorial

Introduction

Here is the source code for com.squareup.javapoet.TypeSpec.java

Source

/*
 * Copyright (C) 2015 Square, 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 com.google.common.base.Ascii;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
import java.io.IOException;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
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.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import static com.google.common.collect.Iterables.getOnlyElement;

/** A generated class, interface, or enum declaration. */
public final class TypeSpec {
    public final Kind kind;
    public final String name;
    public final CodeBlock anonymousTypeArguments;
    public final CodeBlock javadoc;
    public final ImmutableList<AnnotationSpec> annotations;
    public final ImmutableSet<Modifier> modifiers;
    public final ImmutableList<TypeVariable<?>> typeVariables;
    public final Type superclass;
    public final ImmutableList<Type> superinterfaces;
    public final ImmutableMap<String, TypeSpec> enumConstants;
    public final ImmutableList<FieldSpec> fieldSpecs;
    public final ImmutableList<MethodSpec> methodSpecs;
    public final ImmutableList<TypeSpec> typeSpecs;
    public final ImmutableList<Element> originatingElements;

    private TypeSpec(Builder builder) {
        this.kind = builder.kind;
        this.name = builder.name;
        this.anonymousTypeArguments = builder.anonymousTypeArguments;
        this.javadoc = builder.javadoc.build();
        this.annotations = ImmutableList.copyOf(builder.annotations);
        this.modifiers = ImmutableSet.copyOf(builder.modifiers);
        this.typeVariables = ImmutableList.copyOf(builder.typeVariables);
        this.superclass = builder.superclass;
        this.superinterfaces = ImmutableList.copyOf(builder.superinterfaces);
        this.enumConstants = ImmutableMap.copyOf(builder.enumConstants);
        this.fieldSpecs = ImmutableList.copyOf(builder.fieldSpecs);
        this.methodSpecs = ImmutableList.copyOf(builder.methodSpecs);
        this.typeSpecs = ImmutableList.copyOf(builder.typeSpecs);

        ImmutableList.Builder<Element> originatingElementsBuilder = ImmutableList.builder();
        originatingElementsBuilder.addAll(builder.originatingElements);
        for (TypeSpec typeSpec : builder.typeSpecs) {
            originatingElementsBuilder.addAll(typeSpec.originatingElements);
        }
        this.originatingElements = originatingElementsBuilder.build();
    }

    public boolean hasModifier(Modifier modifier) {
        return modifiers.contains(modifier);
    }

    public static Builder classBuilder(String name) {
        return new Builder(Kind.CLASS, checkNotNull(name), null);
    }

    public static Builder interfaceBuilder(String name) {
        return new Builder(Kind.INTERFACE, checkNotNull(name), null);
    }

    public static Builder enumBuilder(String name) {
        return new Builder(Kind.ENUM, checkNotNull(name), null);
    }

    public static Builder anonymousClassBuilder(String typeArgumentsFormat, Object... args) {
        return new Builder(Kind.CLASS, null, CodeBlock.builder().add(typeArgumentsFormat, args).build());
    }

    void emit(CodeWriter codeWriter, String enumName, Set<Modifier> implicitModifiers) throws IOException {
        if (enumName != null) {
            codeWriter.emit("$L", enumName);
            if (!anonymousTypeArguments.formatParts.isEmpty()) {
                codeWriter.emit("(");
                codeWriter.emit(anonymousTypeArguments);
                codeWriter.emit(")");
            }
            if (fieldSpecs.isEmpty() && methodSpecs.isEmpty() && typeSpecs.isEmpty()) {
                return; // Avoid unnecessary braces "{}".
            }
            codeWriter.emit(" {\n");
        } else if (anonymousTypeArguments != null) {
            codeWriter.emit("new $T(", getOnlyElement(superinterfaces, superclass));
            codeWriter.emit(anonymousTypeArguments);
            codeWriter.emit(") {\n");
        } else {
            codeWriter.emitJavadoc(javadoc);
            codeWriter.emitAnnotations(annotations, false);
            codeWriter.emitModifiers(modifiers, Sets.union(implicitModifiers, kind.asMemberModifiers));
            codeWriter.emit("$L $L", Ascii.toLowerCase(kind.name()), name);
            codeWriter.emitTypeVariables(typeVariables);

            List<Type> extendsTypes;
            List<Type> implementsTypes;
            if (kind == Kind.INTERFACE) {
                extendsTypes = superinterfaces;
                implementsTypes = ImmutableList.of();
            } else {
                extendsTypes = superclass.equals(ClassName.OBJECT) ? ImmutableList.<Type>of()
                        : ImmutableList.of(superclass);
                implementsTypes = superinterfaces;
            }

            if (!extendsTypes.isEmpty()) {
                codeWriter.emit(" extends");
                boolean firstType = true;
                for (Type type : extendsTypes) {
                    if (!firstType)
                        codeWriter.emit(",");
                    codeWriter.emit(" $T", type);
                    firstType = false;
                }
            }

            if (!implementsTypes.isEmpty()) {
                codeWriter.emit(" implements");
                boolean firstType = true;
                for (Type type : implementsTypes) {
                    if (!firstType)
                        codeWriter.emit(",");
                    codeWriter.emit(" $T", type);
                    firstType = false;
                }
            }

            codeWriter.emit(" {\n");
        }

        codeWriter.pushType(this);
        codeWriter.indent();
        boolean firstMember = true;
        for (Iterator<Map.Entry<String, TypeSpec>> i = enumConstants.entrySet().iterator(); i.hasNext();) {
            Map.Entry<String, TypeSpec> enumConstant = i.next();
            if (!firstMember)
                codeWriter.emit("\n");
            enumConstant.getValue().emit(codeWriter, enumConstant.getKey(), ImmutableSet.<Modifier>of());
            firstMember = false;
            if (i.hasNext()) {
                codeWriter.emit(",\n");
            } else if (!fieldSpecs.isEmpty() || !methodSpecs.isEmpty() || !typeSpecs.isEmpty()) {
                codeWriter.emit(";\n");
            } else {
                codeWriter.emit("\n");
            }
        }

        // Static fields.
        for (FieldSpec fieldSpec : fieldSpecs) {
            if (!fieldSpec.hasModifier(Modifier.STATIC))
                continue;
            if (!firstMember)
                codeWriter.emit("\n");
            fieldSpec.emit(codeWriter, kind.implicitFieldModifiers);
            firstMember = false;
        }

        // Non-static fields.
        for (FieldSpec fieldSpec : fieldSpecs) {
            if (fieldSpec.hasModifier(Modifier.STATIC))
                continue;
            if (!firstMember)
                codeWriter.emit("\n");
            fieldSpec.emit(codeWriter, kind.implicitFieldModifiers);
            firstMember = false;
        }

        // Constructors.
        for (MethodSpec methodSpec : methodSpecs) {
            if (!methodSpec.isConstructor())
                continue;
            if (!firstMember)
                codeWriter.emit("\n");
            methodSpec.emit(codeWriter, name, kind.implicitMethodModifiers);
            firstMember = false;
        }

        // Methods (static and non-static).
        for (MethodSpec methodSpec : methodSpecs) {
            if (methodSpec.isConstructor())
                continue;
            if (!firstMember)
                codeWriter.emit("\n");
            methodSpec.emit(codeWriter, name, kind.implicitMethodModifiers);
            firstMember = false;
        }

        // Types.
        for (TypeSpec typeSpec : typeSpecs) {
            if (!firstMember)
                codeWriter.emit("\n");
            typeSpec.emit(codeWriter, null, kind.implicitTypeModifiers);
            firstMember = false;
        }

        codeWriter.unindent();
        codeWriter.popType();

        codeWriter.emit("}");
        if (enumName == null && anonymousTypeArguments == null) {
            codeWriter.emit("\n"); // If this type isn't also a value, include a trailing newline.
        }
    }

    private enum Kind {
        CLASS(ImmutableSet.<Modifier>of(), ImmutableSet.<Modifier>of(), ImmutableSet.<Modifier>of(),
                ImmutableSet.<Modifier>of()),

        INTERFACE(ImmutableSet.of(Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL),
                ImmutableSet.of(Modifier.PUBLIC, Modifier.ABSTRACT),
                ImmutableSet.of(Modifier.PUBLIC, Modifier.STATIC), ImmutableSet.of(Modifier.STATIC)),

        ENUM(ImmutableSet.<Modifier>of(), ImmutableSet.<Modifier>of(), ImmutableSet.<Modifier>of(),
                ImmutableSet.of(Modifier.STATIC));

        private final ImmutableSet<Modifier> implicitFieldModifiers;
        private final ImmutableSet<Modifier> implicitMethodModifiers;
        private final ImmutableSet<Modifier> implicitTypeModifiers;
        private final ImmutableSet<Modifier> asMemberModifiers;

        private Kind(ImmutableSet<Modifier> implicitFieldModifiers, ImmutableSet<Modifier> implicitMethodModifiers,
                ImmutableSet<Modifier> implicitTypeModifiers, ImmutableSet<Modifier> asMemberModifiers) {
            this.implicitFieldModifiers = implicitFieldModifiers;
            this.implicitMethodModifiers = implicitMethodModifiers;
            this.implicitTypeModifiers = implicitTypeModifiers;
            this.asMemberModifiers = asMemberModifiers;
        }
    }

    public static final class Builder {
        private final Kind kind;
        private final String name;
        private final CodeBlock anonymousTypeArguments;

        private final CodeBlock.Builder javadoc = CodeBlock.builder();
        private final List<AnnotationSpec> annotations = new ArrayList<>();
        private final List<Modifier> modifiers = new ArrayList<>();
        private final List<TypeVariable<?>> typeVariables = new ArrayList<>();
        private Type superclass = ClassName.OBJECT;
        private final List<Type> superinterfaces = new ArrayList<>();
        private final Map<String, TypeSpec> enumConstants = new LinkedHashMap<>();
        private final List<FieldSpec> fieldSpecs = new ArrayList<>();
        private final List<MethodSpec> methodSpecs = new ArrayList<>();
        private final List<TypeSpec> typeSpecs = new ArrayList<>();
        private final List<Element> originatingElements = new ArrayList<>();

        private Builder(Kind kind, String name, CodeBlock anonymousTypeArguments) {
            checkArgument(name == null || SourceVersion.isName(name), "not a valid name: %s", name);
            this.kind = kind;
            this.name = name;
            this.anonymousTypeArguments = anonymousTypeArguments;
        }

        public Builder addJavadoc(String format, Object... args) {
            checkState(anonymousTypeArguments == null);
            javadoc.add(format, args);
            return this;
        }

        public Builder addAnnotation(AnnotationSpec annotationSpec) {
            checkState(anonymousTypeArguments == null);
            this.annotations.add(annotationSpec);
            return this;
        }

        public Builder addAnnotation(Type annotation) {
            return addAnnotation(AnnotationSpec.builder(annotation).build());
        }

        public Builder addModifiers(Modifier... modifiers) {
            checkState(anonymousTypeArguments == null);
            Collections.addAll(this.modifiers, modifiers);
            return this;
        }

        public Builder addTypeVariable(TypeVariable<?> typeVariable) {
            checkState(anonymousTypeArguments == null);
            typeVariables.add(typeVariable);
            return this;
        }

        public Builder superclass(Type superclass) {
            this.superclass = superclass;
            return this;
        }

        public Builder addSuperinterface(Type superinterface) {
            this.superinterfaces.add(superinterface);
            return this;
        }

        public Builder addEnumConstant(String name) {
            return addEnumConstant(name, anonymousClassBuilder("").build());
        }

        public Builder addEnumConstant(String name, TypeSpec typeSpec) {
            checkState(kind == Kind.ENUM, "%s is not enum", this.name);
            checkArgument(typeSpec.anonymousTypeArguments != null,
                    "enum constants must have anonymous type arguments");
            checkArgument(SourceVersion.isName(name), "not a valid enum constant: %s", name);
            enumConstants.put(name, typeSpec);
            return this;
        }

        public Builder addField(FieldSpec fieldSpec) {
            checkArgument(fieldSpec.modifiers.containsAll(kind.implicitFieldModifiers),
                    "%s %s.%s requires modifiers %s", kind, name, fieldSpec.name, kind.implicitFieldModifiers);
            fieldSpecs.add(fieldSpec);
            return this;
        }

        public Builder addField(Type type, String name, Modifier... modifiers) {
            return addField(FieldSpec.builder(type, name, modifiers).build());
        }

        public Builder addMethod(MethodSpec methodSpec) {
            checkArgument(methodSpec.modifiers.containsAll(kind.implicitMethodModifiers),
                    "%s %s.%s requires modifiers %s", kind, name, methodSpec.name, kind.implicitMethodModifiers);
            methodSpecs.add(methodSpec);
            return this;
        }

        public Builder addType(TypeSpec typeSpec) {
            checkArgument(typeSpec.modifiers.containsAll(kind.implicitTypeModifiers),
                    "%s %s.%s requires modifiers %s", kind, name, typeSpec.name, kind.implicitFieldModifiers);
            typeSpecs.add(typeSpec);
            return this;
        }

        public Builder addOriginatingElement(Element originatingElement) {
            originatingElements.add(originatingElement);
            return this;
        }

        public TypeSpec build() {
            boolean isInterface = kind == Kind.INTERFACE;
            boolean isAbstract = modifiers.contains(Modifier.ABSTRACT) || isInterface;
            checkArgument(kind != Kind.ENUM || !enumConstants.isEmpty(),
                    "at least one enum constant is required for %s", name);

            for (MethodSpec methodSpec : methodSpecs) {
                checkArgument(isAbstract || !methodSpec.hasModifier(Modifier.ABSTRACT),
                        "non-abstract type %s cannot declare abstract method %s", name, methodSpec.name);
            }

            boolean superclassIsObject = superclass.equals(ClassName.OBJECT);
            int interestingSupertypeCount = (superclassIsObject ? 0 : 1) + superinterfaces.size();
            checkArgument(anonymousTypeArguments == null || interestingSupertypeCount <= 1,
                    "anonymous type has too many supertypes");

            return new TypeSpec(this);
        }
    }
}