dagger2.internal.codegen.writer.JavaWriter.java Source code

Java tutorial

Introduction

Here is the source code for dagger2.internal.codegen.writer.JavaWriter.java

Source

/*
 * 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 dagger2.internal.codegen.writer;

import com.google.common.base.Function;
import com.google.common.base.Optional;
import com.google.common.collect.BiMap;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.HashBiMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Ordering;
import com.google.common.collect.Queues;
import com.google.common.collect.Sets;
import com.google.common.io.Closer;
import dagger2.internal.codegen.writer.Writable.Context;
import java.io.IOException;
import java.util.Deque;
import java.util.List;
import java.util.Set;
import javax.annotation.processing.Filer;
import javax.lang.model.element.Element;
import javax.lang.model.element.PackageElement;
import javax.tools.JavaFileObject;

import static com.google.common.base.Preconditions.checkNotNull;
import static java.util.Collections.unmodifiableList;

/**
 * Writes a single compilation unit.
 */
public final class JavaWriter {
    public static JavaWriter inPackage(String packageName) {
        return new JavaWriter(packageName);
    }

    public static JavaWriter inPackage(Package enclosingPackage) {
        return new JavaWriter(enclosingPackage.getName());
    }

    public static JavaWriter inPackage(PackageElement packageElement) {
        return new JavaWriter(packageElement.getQualifiedName().toString());
    }

    private final String packageName;
    // TODO(gak): disallow multiple types in a file?
    private final List<TypeWriter> typeWriters;
    private final List<ClassName> explicitImports;

    private JavaWriter(String packageName) {
        this.packageName = packageName;
        this.typeWriters = Lists.newArrayList();
        this.explicitImports = Lists.newArrayList();
    }

    public List<TypeWriter> getTypeWriters() {
        return unmodifiableList(typeWriters);
    }

    public JavaWriter addImport(Class<?> importedClass) {
        explicitImports.add(ClassName.fromClass(importedClass));
        return this;
    }

    public ClassWriter addClass(String simpleName) {
        checkNotNull(simpleName);
        ClassWriter classWriter = new ClassWriter(ClassName.create(packageName, simpleName));
        typeWriters.add(classWriter);
        return classWriter;
    }

    public EnumWriter addEnum(String simpleName) {
        checkNotNull(simpleName);
        EnumWriter writer = new EnumWriter(ClassName.create(packageName, simpleName));
        typeWriters.add(writer);
        return writer;
    }

    public InterfaceWriter addInterface(String simpleName) {
        InterfaceWriter writer = new InterfaceWriter(ClassName.create(packageName, simpleName));
        typeWriters.add(writer);
        return writer;
    }

    public Appendable write(Appendable appendable) throws IOException {
        if (!packageName.isEmpty()) {
            appendable.append("package ").append(packageName).append(";\n\n");
        }

        // write imports
        ImmutableSet<ClassName> classNames = FluentIterable.from(typeWriters)
                .transformAndConcat(new Function<HasClassReferences, Set<ClassName>>() {
                    @Override
                    public Set<ClassName> apply(HasClassReferences input) {
                        return input.referencedClasses();
                    }
                }).toSet();

        ImmutableSortedSet<ClassName> importCandidates = ImmutableSortedSet.<ClassName>naturalOrder()
                .addAll(explicitImports).addAll(classNames).build();
        ImmutableSet<ClassName> typeNames = FluentIterable.from(typeWriters)
                .transform(new Function<TypeWriter, ClassName>() {
                    @Override
                    public ClassName apply(TypeWriter input) {
                        return input.name;
                    }
                }).toSet();

        ImmutableSet.Builder<String> declaredSimpleNamesBuilder = ImmutableSet.builder();
        Deque<TypeWriter> declaredTypes = Queues.newArrayDeque(typeWriters);
        while (!declaredTypes.isEmpty()) {
            TypeWriter currentType = declaredTypes.pop();
            declaredSimpleNamesBuilder.add(currentType.name().simpleName());
            declaredTypes.addAll(currentType.nestedTypeWriters);
        }

        ImmutableSet<String> declaredSimpleNames = declaredSimpleNamesBuilder.build();

        BiMap<String, ClassName> importedClassIndex = HashBiMap.create();
        for (ClassName className : importCandidates) {
            if (!(className.packageName().equals(packageName) && !className.enclosingClassName().isPresent())
                    && !(className.packageName().equals("java.lang") && className.enclosingSimpleNames().isEmpty())
                    && !typeNames.contains(className.topLevelClassName())) {
                Optional<ClassName> importCandidate = Optional.of(className);
                while (importCandidate.isPresent()
                        && (importedClassIndex.containsKey(importCandidate.get().simpleName())
                                || declaredSimpleNames.contains(importCandidate.get().simpleName()))) {
                    importCandidate = importCandidate.get().enclosingClassName();
                }
                if (importCandidate.isPresent()) {
                    appendable.append("import ").append(importCandidate.get().canonicalName()).append(";\n");
                    importedClassIndex.put(importCandidate.get().simpleName(), importCandidate.get());
                }
            }
        }

        appendable.append('\n');

        CompilationUnitContext context = new CompilationUnitContext(packageName,
                ImmutableSet.copyOf(importedClassIndex.values()));

        // write types
        for (TypeWriter typeWriter : typeWriters) {
            typeWriter.write(appendable, context.createSubcontext(typeNames)).append('\n');
        }
        return appendable;
    }

    public void file(Filer filer, Iterable<? extends Element> originatingElements) throws IOException {
        file(filer, Iterables.getOnlyElement(typeWriters).name.canonicalName(), originatingElements);
    }

    public void file(Filer filer, CharSequence name, Iterable<? extends Element> originatingElements)
            throws IOException {
        JavaFileObject sourceFile = filer.createSourceFile(name,
                Iterables.toArray(originatingElements, Element.class));
        Closer closer = Closer.create();
        try {
            write(closer.register(sourceFile.openWriter()));
        } catch (Exception e) {
            try {
                sourceFile.delete();
            } catch (Exception e2) {
                // couldn't delete the file
            }
            throw closer.rethrow(e);
        } finally {
            closer.close();
        }
    }

    @Override
    public String toString() {
        try {
            return write(new StringBuilder()).toString();
        } catch (IOException e) {
            throw new AssertionError();
        }
    }

    static final class CompilationUnitContext implements Context {
        private final String packageName;
        private final ImmutableSortedSet<ClassName> visibleClasses;

        CompilationUnitContext(String packageName, Set<ClassName> visibleClasses) {
            this.packageName = packageName;
            this.visibleClasses = ImmutableSortedSet.copyOf(Ordering.natural().reverse(), visibleClasses);
        }

        @Override
        public Context createSubcontext(Set<ClassName> newTypes) {
            return new CompilationUnitContext(packageName, Sets.union(visibleClasses, newTypes));
        }

        @Override
        public String sourceReferenceForClassName(ClassName className) {
            if (isImported(className)) {
                return className.simpleName();
            }
            Optional<ClassName> enclosingClassName = className.enclosingClassName();
            while (enclosingClassName.isPresent()) {
                if (isImported(enclosingClassName.get())) {
                    return enclosingClassName.get().simpleName() + className.canonicalName()
                            .substring(enclosingClassName.get().canonicalName().length());
                }
                enclosingClassName = enclosingClassName.get().enclosingClassName();
            }
            return className.canonicalName();
        }

        private boolean collidesWithVisibleClass(ClassName className) {
            return collidesWithVisibleClass(className.simpleName());
        }

        private boolean collidesWithVisibleClass(String simpleName) {
            return FluentIterable.from(visibleClasses).transform(new Function<ClassName, String>() {
                @Override
                public String apply(ClassName input) {
                    return input.simpleName();
                }
            }).contains(simpleName);
        }

        private boolean isImported(ClassName className) {
            return (packageName.equals(className.packageName()) && !className.enclosingClassName().isPresent()
                    && !collidesWithVisibleClass(className)) // need to account for scope & hiding
                    || visibleClasses.contains(className)
                    || (className.packageName().equals("java.lang") && className.enclosingSimpleNames().isEmpty());
        }
    }
}