com.github.imasahiro.stringformatter.processor.FormatterMethod.java Source code

Java tutorial

Introduction

Here is the source code for com.github.imasahiro.stringformatter.processor.FormatterMethod.java

Source

/*
 * Copyright (C) 2016 Masahiro Ide
 *
 * 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.github.imasahiro.stringformatter.processor;

import java.util.List;
import java.util.Set;

import javax.annotation.processing.ProcessingEnvironment;
import javax.lang.model.element.Element;
import javax.lang.model.element.Modifier;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.Types;

import com.github.imasahiro.stringformatter.processor.util.ErrorReporter;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Sets;
import com.squareup.javapoet.CodeBlock;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.ParameterSpec;
import com.squareup.javapoet.TypeName;

class FormatterMethod {
    private final String name;
    private final String format;
    private final int bufferCapacity;
    private final List<TypeMirror> argumentTypes;
    private final Element element;
    private final ErrorReporter errorReporter;

    FormatterMethod(String name, String format, int bufferCapacity, List<TypeMirror> argumentTypes, Element element,
            ErrorReporter errorReporter) {
        this.name = name;
        this.format = format;
        this.bufferCapacity = bufferCapacity;
        this.argumentTypes = argumentTypes;
        this.element = element;
        this.errorReporter = errorReporter;
    }

    private static List<ParameterSpec> buildParamTypes(List<TypeMirror> argumentTypes) {
        ImmutableList.Builder<ParameterSpec> builder = ImmutableList.builder();
        for (int i = 0; i < argumentTypes.size(); i++) {
            builder.add(
                    ParameterSpec.builder(TypeName.get(argumentTypes.get(i)), "arg" + i, Modifier.FINAL).build());
        }
        return builder.build();
    }

    private CodeBlock buildBody(List<FormatString> formatStringList, List<TypeMirror> argumentTypes) {
        CodeBlock.Builder builder = CodeBlock.builder()
                .add("final StringBuilder sb = new StringBuilder(" + bufferCapacity + ");\n");
        int idx = 0;
        for (int i = 0; i < formatStringList.size(); i++) {
            FormatString formatString = formatStringList.get(i);
            if (formatString instanceof FormatSpecifier) {
                formatStringList.get(i).emit(builder, argumentTypes.get(idx++));
            } else {
                formatStringList.get(i).emit(builder, null);
            }
        }
        return builder.add("return sb.toString();\n").build();
    }

    private void checkArgumentTypes(ProcessingEnvironment processingEnv, List<FormatString> formatStringList,
            List<TypeMirror> expectedTypeList) {
        List<FormatSpecifier> formatSpecifiers = FluentIterable.from(formatStringList).filter(FormatSpecifier.class)
                .toList();

        if (formatSpecifiers.size() != expectedTypeList.size()) {
            throw new RuntimeException(name + " cannot not acceptable to " + expectedTypeList);
        }

        Set<List<TypeMirror>> candidateArgumentTypes = Sets
                .cartesianProduct(
                        FluentIterable
                                .from(formatSpecifiers).transform(e -> e.getConversionType()
                                        .getType(processingEnv.getTypeUtils(), processingEnv.getElementUtils()))
                                .toList());

        if (!candidateArgumentTypes.stream()
                .anyMatch(candidate -> isAssignableArguments(processingEnv, candidate, expectedTypeList))) {
            errorReporter.fatal(name + " cannot not apply to " + expectedTypeList, element);
        }
    }

    private boolean isAssignableArguments(ProcessingEnvironment processingEnv, List<TypeMirror> candidate,
            List<TypeMirror> expectedTypeList) {
        Types typeUtils = processingEnv.getTypeUtils();
        for (int i = 0; i < candidate.size(); i++) {
            if (!typeUtils.isAssignable(expectedTypeList.get(i), candidate.get(i))) {
                return false;
            }
        }
        return true;
    }

    public MethodSpec getMethod(ProcessingEnvironment processingEnv) {
        List<FormatString> formatStringList = FormatParser.parse(format, element, errorReporter);
        checkArgumentTypes(processingEnv, formatStringList, argumentTypes);
        return MethodSpec.methodBuilder(name).addModifiers(Modifier.PUBLIC, Modifier.FINAL)
                .addParameters(buildParamTypes(argumentTypes)).addCode(buildBody(formatStringList, argumentTypes))
                .returns(TypeName.get(String.class)).build();
    }

    @Override
    public String toString() {
        return "FormatterMethod(name:" + name + ", format:" + format + ", bufferCapacity:" + bufferCapacity + ")";
    }

    public static Builder builder() {
        return new Builder();
    }

    static class Builder {
        private String name;
        private int bufferCapacity;
        private String format;
        private ImmutableList<TypeMirror> argumentTypes;
        private Element element;
        private ErrorReporter errorReporter;

        public Builder name(String name) {
            this.name = name;
            return this;
        }

        public Builder formatter(String format) {
            this.format = format;
            return this;
        }

        public Builder bufferCapacity(int bufferCapacity) {
            this.bufferCapacity = bufferCapacity;
            return this;
        }

        public Builder argumentTypeNames(ImmutableList<TypeMirror> argumentTypeNames) {
            this.argumentTypes = argumentTypeNames;
            return this;
        }

        public Builder element(Element element) {
            this.element = element;
            return this;
        }

        public Builder errorReporter(ErrorReporter errorReporter) {
            this.errorReporter = errorReporter;
            return this;
        }

        public FormatterMethod build() {
            return new FormatterMethod(name, format, bufferCapacity, argumentTypes, element, errorReporter);
        }

    }
}