Java tutorial
/* * Copyright 2015-present Facebook, 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.facebook.buck.java.intellij; import com.facebook.buck.io.ProjectFilesystem; import com.facebook.buck.java.JavaPackageFinder; import com.facebook.buck.rules.TargetNode; import com.facebook.buck.util.immutables.BuckStyleImmutable; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Function; import com.google.common.base.Optional; import com.google.common.base.Predicate; import com.google.common.collect.FluentIterable; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSortedSet; import com.google.common.collect.Ordering; import org.immutables.value.Value; import java.io.IOException; import java.nio.file.FileVisitResult; import java.nio.file.FileVisitor; import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.attribute.BasicFileAttributes; import java.util.HashSet; import java.util.Map; import java.util.Set; import javax.annotation.Nullable; /** * Does the converting of abstract data structures to a format immediately consumable by the * StringTemplate-based templates employed by {@link IjProjectWriter}. This is a separate class * mainly for testing convenience. */ @VisibleForTesting public class IjProjectTemplateDataPreparer { private JavaPackageFinder javaPackageFinder; private IjModuleGraph moduleGraph; private ProjectFilesystem projectFilesystem; private ImmutableSet<Path> referencedFolderPaths; private ImmutableSet<Path> filesystemTraversalBoundaryPaths; private ImmutableSet<IjModule> modulesToBeWritten; private ImmutableSet<IjLibrary> librariesToBeWritten; public IjProjectTemplateDataPreparer(JavaPackageFinder javaPackageFinder, IjModuleGraph moduleGraph, ProjectFilesystem projectFilesystem) { this.javaPackageFinder = javaPackageFinder; this.moduleGraph = moduleGraph; this.projectFilesystem = projectFilesystem; this.modulesToBeWritten = createModulesToBeWritten(moduleGraph); this.librariesToBeWritten = FluentIterable.from(moduleGraph.getNodes()).filter(IjLibrary.class).toSet(); this.filesystemTraversalBoundaryPaths = createFilesystemTraversalBoundaryPathSet(modulesToBeWritten); this.referencedFolderPaths = createReferencedFolderPathsSet(modulesToBeWritten); } private static void addPathAndParents(Set<Path> pathSet, Path path) { do { pathSet.add(path); path = path.getParent(); } while (path != null && !pathSet.contains(path)); } public static ImmutableSet<Path> createReferencedFolderPathsSet(ImmutableSet<IjModule> modules) { Set<Path> pathSet = new HashSet<>(); for (IjModule module : modules) { addPathAndParents(pathSet, module.getModuleBasePath()); for (IjFolder folder : module.getFolders()) { addPathAndParents(pathSet, folder.getPath()); } } return ImmutableSet.copyOf(pathSet); } public static ImmutableSet<Path> createFilesystemTraversalBoundaryPathSet(ImmutableSet<IjModule> modules) { return FluentIterable.from(modules).transform(IjModule.TO_MODULE_BASE_PATH) .append(IjProjectWriter.IDEA_CONFIG_DIR_PREFIX).toSet(); } public static ImmutableSet<Path> createPackageLookupPathSet(IjModuleGraph moduleGraph) { ImmutableSet.Builder<Path> builder = ImmutableSet.builder(); for (IjModule module : moduleGraph.getModuleNodes()) { for (IjFolder folder : module.getFolders()) { if (!folder.getWantsPackagePrefix()) { continue; } Optional<Path> firstJavaFile = FluentIterable.from(folder.getInputs()) .filter(new Predicate<Path>() { @Override public boolean apply(Path input) { return input.getFileName().toString().endsWith(".java"); } }).first(); if (firstJavaFile.isPresent()) { builder.add(firstJavaFile.get()); } } } return builder.build(); } private static ImmutableSet<IjModule> createModulesToBeWritten(IjModuleGraph graph) { Path rootModuleBasePath = Paths.get(""); boolean hasRootModule = FluentIterable.from(graph.getModuleNodes()).transform(IjModule.TO_MODULE_BASE_PATH) .contains(rootModuleBasePath); ImmutableSet<IjModule> supplementalModules = ImmutableSet.of(); if (!hasRootModule) { supplementalModules = ImmutableSet.of(IjModule.builder().setModuleBasePath(rootModuleBasePath) .setTargets(ImmutableSet.<TargetNode<?>>of()).build()); } return FluentIterable.from(graph.getModuleNodes()).append(supplementalModules).toSet(); } /** * @param path path to folder. * @param moduleLocationBasePath path to the location of the .iml file. * @return a path, relative to the module .iml file location describing a folder * in IntelliJ format. */ private static String toModuleDirRelativeString(Path path, Path moduleLocationBasePath) { String moduleRelativePath = moduleLocationBasePath.relativize(path).toString(); if (moduleRelativePath.isEmpty()) { return "file://$MODULE_DIR$"; } else { return "file://$MODULE_DIR$/" + moduleRelativePath; } } private static String toProjectDirRelativeString(Path projectRelativePath) { String path = projectRelativePath.toString(); if (path.isEmpty()) { return "file://$PROJECT_DIR$"; } else { return "file://$PROJECT_DIR$/" + path; } } public static Path getModuleOutputFilePath(String name) { return IjProjectWriter.MODULES_PREFIX.resolve(name + ".iml"); } @Value.Immutable @BuckStyleImmutable public abstract static class AbstractIjSourceFolder implements Comparable<IjSourceFolder> { public abstract String getType(); public abstract String getUrl(); public abstract boolean getIsTestSource(); @Nullable public abstract String getPackagePrefix(); @Override public int compareTo(IjSourceFolder o) { return getUrl().compareTo(o.getUrl()); } } @Value.Immutable @BuckStyleImmutable public abstract static class AbstractContentRoot implements Comparable<ContentRoot> { public abstract String getUrl(); public abstract ImmutableSortedSet<IjSourceFolder> getFolders(); @Override public int compareTo(ContentRoot o) { return getUrl().compareTo(o.getUrl()); } } public ImmutableSet<IjModule> getModulesToBeWritten() { return modulesToBeWritten; } public ImmutableSet<IjLibrary> getLibrariesToBeWritten() { return librariesToBeWritten; } private IjSourceFolder createSourceFolder(IjFolder folder, Path moduleLocationBasePath) { String packagePrefix = null; if (folder.getWantsPackagePrefix()) { Path fileToLookupPackageIn = FluentIterable.from(folder.getInputs()).first() .or(folder.getPath().resolve("notfound")); packagePrefix = javaPackageFinder.findJavaPackage(fileToLookupPackageIn); } return IjSourceFolder.builder().setType(folder.getType().getIjName()) .setUrl(toModuleDirRelativeString(folder.getPath(), moduleLocationBasePath)) .setIsTestSource(folder.isTest()).setPackagePrefix(packagePrefix).build(); } private ContentRoot createContentRoot(Path contentRootPath, ImmutableSet<IjFolder> folders, final Path moduleLocationBasePath) { String url = toModuleDirRelativeString(contentRootPath, moduleLocationBasePath); ImmutableSortedSet<IjSourceFolder> sourceFolders = FluentIterable.from(folders) .transform(new Function<IjFolder, IjSourceFolder>() { @Override public IjSourceFolder apply(IjFolder input) { return createSourceFolder(input, moduleLocationBasePath); } }).toSortedSet(Ordering.natural()); return ContentRoot.builder().setUrl(url).setFolders(sourceFolders).build(); } public ImmutableSet<IjFolder> createExcludes(IjModule module) throws IOException { final ImmutableSet.Builder<IjFolder> excludesBuilder = ImmutableSet.builder(); final Path moduleBasePath = module.getModuleBasePath(); projectFilesystem.walkRelativeFileTree(moduleBasePath, new FileVisitor<Path>() { @Override public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException { // This is another module that's nested in this one. The entire subtree will be handled // When we create excludes for that module. if (filesystemTraversalBoundaryPaths.contains(dir) && !moduleBasePath.equals(dir)) { return FileVisitResult.SKIP_SUBTREE; } if (!referencedFolderPaths.contains(dir)) { excludesBuilder .add(IjFolder.builder().setPath(dir).setType(AbstractIjFolder.Type.EXCLUDE_FOLDER) .setWantsPackagePrefix(false).setInputs(ImmutableSortedSet.<Path>of()).build()); return FileVisitResult.SKIP_SUBTREE; } return FileVisitResult.CONTINUE; } @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { return FileVisitResult.CONTINUE; } @Override public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException { return FileVisitResult.CONTINUE; } @Override public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException { return FileVisitResult.CONTINUE; } }); return excludesBuilder.build(); } public ContentRoot getContentRoot(IjModule module) throws IOException { Path moduleBasePath = module.getModuleBasePath(); Path moduleLocation = getModuleOutputFilePath(module.getName()); final Path moduleLocationBasePath = (moduleLocation.getParent() == null) ? Paths.get("") : moduleLocation.getParent(); ImmutableSet<IjFolder> sourcesAndExcludes = FluentIterable.from(module.getFolders()) .append(createExcludes(module)).toSet(); return createContentRoot(moduleBasePath, sourcesAndExcludes, moduleLocationBasePath); } public ImmutableSet<DependencyEntry> getDependencies(IjModule module) { ImmutableMap<IjProjectElement, IjModuleGraph.DependencyType> deps = moduleGraph.getDepsFor(module); IjDependencyListBuilder dependencyListBuilder = new IjDependencyListBuilder(); for (Map.Entry<IjProjectElement, IjModuleGraph.DependencyType> entry : deps.entrySet()) { IjProjectElement element = entry.getKey(); IjModuleGraph.DependencyType dependencyType = entry.getValue(); element.addAsDependency(dependencyType, dependencyListBuilder); } return dependencyListBuilder.build(); } @Value.Immutable @BuckStyleImmutable abstract static class AbstractModuleIndexEntry implements Comparable<ModuleIndexEntry> { public abstract String getFileUrl(); public abstract Path getFilePath(); @Nullable public abstract String getGroup(); @Override public int compareTo(ModuleIndexEntry o) { return getFilePath().compareTo(o.getFilePath()); } } public ImmutableSortedSet<ModuleIndexEntry> getModuleIndexEntries() { return FluentIterable.from(modulesToBeWritten).filter(IjModule.class) .transform(new Function<IjModule, ModuleIndexEntry>() { @Override public ModuleIndexEntry apply(IjModule module) { Path moduleOutputFilePath = getModuleOutputFilePath(module.getName()); String fileUrl = toProjectDirRelativeString(moduleOutputFilePath); // The root project module cannot belong to any group. String group = (module.getModuleBasePath().toString().isEmpty()) ? null : "modules"; return ModuleIndexEntry.builder().setFileUrl(fileUrl).setFilePath(moduleOutputFilePath) .setGroup(group).build(); } }).toSortedSet(Ordering.natural()); } }