/* * Copyright 2014 Google Inc. All rights reserved. * * 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 org.inferred.freebuilder.processor.util; import static com.google.common.collect.Iterables.addAll; import static com.google.common.collect.Iterables.getOnlyElement; import static org.inferred.freebuilder.processor.util.Shading.unshadedName; import com.google.common.base.Function; import com.google.common.base.Joiner; import com.google.common.collect.ImmutableSet; import com.google.common.collect.LinkedHashMultimap; import com.google.common.collect.Lists; import com.google.common.collect.Multimaps; import com.google.common.collect.SetMultimap; import java.util.Collections; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; import java.util.TreeSet; import javax.lang.model.element.Element; import javax.lang.model.element.ElementKind; import javax.lang.model.element.Name; import javax.lang.model.element.PackageElement; import javax.lang.model.element.TypeElement; import javax.lang.model.type.DeclaredType; import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; import javax.lang.model.util.SimpleTypeVisitor6; /** * Manages the imports for a source file, and produces short type references by adding extra * imports when possible. * * <p>To ensure we never import common names like 'Builder', nested classes are never directly * imported. This is necessarily less readable when types are used as namespaces, e.g. in proto2. */ class ImportManager extends SimpleTypeVisitor6<String, Void> implements Function<TypeMirror, String>, TypeShortener { private static final String JAVA_LANG_PACKAGE = "java.lang"; private static final String PACKAGE_PREFIX = "package "; /** * Builder of {@link ImportManager} instances. */ public static class Builder { /** * Simple names of implicitly imported types, mapped to qualified name if that type is safe to * use, null otherwise. */ private final SetMultimap<String, QualifiedName> implicitImports = LinkedHashMultimap.create(); /** * Adds a type which is implicitly imported into the current compilation unit. */ public Builder addImplicitImport(QualifiedName type) { implicitImports.put(type.getSimpleName(), type); return this; } public ImportManager build() { Set<String> nonConflictingImports = new LinkedHashSet<String>(); for (Set<QualifiedName> importGroup : Multimaps.asMap(implicitImports).values()) { if (importGroup.size() == 1) { QualifiedName implicitImport = getOnlyElement(importGroup); if (implicitImport.isTopLevel()) { nonConflictingImports.add(implicitImport.toString()); } } } return new ImportManager(implicitImports.keySet(), nonConflictingImports); } } private final Set<String> visibleSimpleNames = new HashSet<String>(); private final ImmutableSet<String> implicitImports; private final Set<String> explicitImports = new TreeSet<String>(); private ImportManager(Iterable<String> visibleSimpleNames, Iterable<String> implicitImports) { addAll(this.visibleSimpleNames, visibleSimpleNames); this.implicitImports = ImmutableSet.copyOf(implicitImports); } public Set<String> getClassImports() { return Collections.unmodifiableSet(explicitImports); } @Override public String shorten(TypeMirror mirror) { return mirror.accept(this, null); } @Override public String shorten(QualifiedName type) { String prefix = getPrefixForTopLevelClass(type.getPackage(), type.getSimpleNames().get(0)); return prefix + Joiner.on('.').join(type.getSimpleNames()); } @Override public String apply(TypeMirror mirror) { return mirror.accept(this, null); } @Override public String visitDeclared(DeclaredType mirror, Void p) { Name name = mirror.asElement().getSimpleName(); final String prefix; Element enclosingElement = mirror.asElement().getEnclosingElement(); if (mirror.getEnclosingType().getKind() != TypeKind.NONE) { prefix = visit(mirror.getEnclosingType()) + "."; } else if (enclosingElement.getKind() == ElementKind.PACKAGE) { PackageElement pkg = (PackageElement) enclosingElement; prefix = getPrefixForTopLevelClass(pkg.getQualifiedName().toString(), name); } else if (enclosingElement.getKind().isClass() || enclosingElement.getKind().isInterface()) { prefix = shorten(QualifiedName.of((TypeElement) enclosingElement)) + "."; } else { prefix = enclosingElement.toString() + "."; } final String suffix; if (!mirror.getTypeArguments().isEmpty()) { List<String> shortTypeArguments = Lists.transform(mirror.getTypeArguments(), this); suffix = "<" + Joiner.on(", ").join(shortTypeArguments) + ">"; } else { suffix = ""; } return prefix + name + suffix; } private String getPrefixForTopLevelClass(String pkg, CharSequence name) { if (pkg.startsWith(PACKAGE_PREFIX)) { pkg = pkg.substring(PACKAGE_PREFIX.length()); } pkg = unshadedName(pkg); String qualifiedName = pkg + "." + name; if (implicitImports.contains(qualifiedName) || explicitImports.contains(qualifiedName)) { return ""; } else if (visibleSimpleNames.contains(name.toString())) { return pkg + "."; } else if (pkg.equals(JAVA_LANG_PACKAGE)) { return ""; } else { visibleSimpleNames.add(name.toString()); explicitImports.add(qualifiedName); return ""; } } @Override protected String defaultAction(TypeMirror mirror, Void p) { return mirror.toString(); } }