com.facebook.buck.features.project.intellij.IjProjectWriter.java Source code

Java tutorial

Introduction

Here is the source code for com.facebook.buck.features.project.intellij.IjProjectWriter.java

Source

/*
 * 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.features.project.intellij;

import static com.facebook.buck.features.project.intellij.IjProjectPaths.getUrl;

import com.facebook.buck.core.model.BuildTarget;
import com.facebook.buck.core.model.targetgraph.TargetGraph;
import com.facebook.buck.features.project.intellij.model.ContentRoot;
import com.facebook.buck.features.project.intellij.model.IjLibrary;
import com.facebook.buck.features.project.intellij.model.IjModule;
import com.facebook.buck.features.project.intellij.model.IjProjectConfig;
import com.facebook.buck.features.project.intellij.model.ModuleIndexEntry;
import com.facebook.buck.io.file.MorePaths;
import com.facebook.buck.io.filesystem.ProjectFilesystem;
import com.facebook.buck.util.json.ObjectMappers;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.type.TypeReference;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedSet;
import com.google.common.collect.Maps;
import com.google.common.collect.Ordering;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Comparator;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.TreeMap;
import org.stringtemplate.v4.ST;

/** Writes the serialized representations of IntelliJ project components to disk. */
public class IjProjectWriter {
    static final String TARGET_INFO_MAP_FILENAME = "target-info.json";
    static final String INTELLIJ_TYPE = "intellij.type";
    static final String INTELLIJ_NAME = "intellij.name";
    static final String INTELLIJ_FILE_PATH = "intellij.file_path";
    static final String MODULE_TYPE = "module";
    static final String LIBRARY_TYPE = "library";

    private final TargetGraph targetGraph;
    private final IjProjectTemplateDataPreparer projectDataPreparer;
    private final IjProjectConfig projectConfig;
    private final ProjectFilesystem projectFilesystem;
    private final IntellijModulesListParser modulesParser;
    private final IJProjectCleaner cleaner;
    private final ProjectFilesystem outFilesystem;
    private final IjProjectPaths projectPaths;

    public IjProjectWriter(TargetGraph targetGraph, IjProjectTemplateDataPreparer projectDataPreparer,
            IjProjectConfig projectConfig, ProjectFilesystem projectFilesystem,
            IntellijModulesListParser modulesParser, IJProjectCleaner cleaner, ProjectFilesystem outFilesystem) {
        this.targetGraph = targetGraph;
        this.projectDataPreparer = projectDataPreparer;
        this.projectConfig = projectConfig;
        this.projectPaths = projectConfig.getProjectPaths();
        this.projectFilesystem = projectFilesystem;
        this.modulesParser = modulesParser;
        this.cleaner = cleaner;
        this.outFilesystem = outFilesystem;
    }

    /** Write entire project to disk */
    public void write() throws IOException {
        outFilesystem.mkdirs(getIdeaConfigDir());

        writeProjectSettings();

        projectDataPreparer.getModulesToBeWritten().parallelStream().forEach(module -> {
            try {
                writeModule(module, projectDataPreparer.getContentRoots(module));
            } catch (IOException exception) {
                throw new RuntimeException(exception);
            }
        });
        projectDataPreparer.getLibrariesToBeWritten().parallelStream().forEach(library -> {
            try {
                writeLibrary(library);
            } catch (IOException exception) {
                throw new RuntimeException(exception);
            }
        });

        writeModulesIndex(projectDataPreparer.getModuleIndexEntries());
        writeWorkspace();

        if (projectConfig.isGeneratingTargetInfoMapEnabled()) {
            writeTargetInfoMap(projectDataPreparer, false);
        }
    }

    private Map<String, Map<String, String>> readTargetInfoMap() throws IOException {
        Path targetInfoMapPath = getTargetInfoMapPath();
        return outFilesystem.exists(targetInfoMapPath)
                ? ObjectMappers.createParser(outFilesystem.newFileInputStream(targetInfoMapPath))
                        .readValueAs(new TypeReference<TreeMap<String, TreeMap<String, String>>>() {
                        })
                : Maps.newTreeMap();
    }

    private void writeTargetInfoMap(IjProjectTemplateDataPreparer projectDataPreparer, boolean update)
            throws IOException {
        Map<String, Map<String, String>> targetInfoMap = update ? readTargetInfoMap() : Maps.newTreeMap();
        projectDataPreparer.getModulesToBeWritten().forEach(module -> {
            module.getTargets().forEach(target -> {
                Map<String, String> targetInfo = Maps.newTreeMap();
                targetInfo.put(INTELLIJ_TYPE, MODULE_TYPE);
                targetInfo.put(INTELLIJ_NAME, module.getName());
                targetInfo.put(INTELLIJ_FILE_PATH, projectPaths.getModuleImlFilePath(module).toString());
                targetInfo.put("buck.type", getRuleNameForBuildTarget(target));
                targetInfoMap.put(target.getFullyQualifiedName(), targetInfo);
            });
        });
        projectDataPreparer.getLibrariesToBeWritten().forEach(library -> {
            library.getTargets().forEach(target -> {
                Map<String, String> targetInfo = Maps.newTreeMap();
                targetInfo.put(INTELLIJ_TYPE, LIBRARY_TYPE);
                targetInfo.put(INTELLIJ_NAME, library.getName());
                targetInfo.put(INTELLIJ_FILE_PATH, projectPaths.getLibraryXmlFilePath(library).toString());
                targetInfo.put("buck.type", getRuleNameForBuildTarget(target));
                targetInfoMap.put(target.getFullyQualifiedName(), targetInfo);
            });
        });
        Path targetInfoMapPath = getTargetInfoMapPath();
        try (JsonGenerator generator = ObjectMappers
                .createGenerator(outFilesystem.newFileOutputStream(targetInfoMapPath)).useDefaultPrettyPrinter()) {
            generator.writeObject(targetInfoMap);
        }
    }

    private Path getTargetInfoMapPath() {
        return getIdeaConfigDir().resolve(TARGET_INFO_MAP_FILENAME);
    }

    private String getRuleNameForBuildTarget(BuildTarget buildTarget) {
        return targetGraph.get(buildTarget).getRuleType().getName();
    }

    private boolean writeModule(IjModule module, ImmutableList<ContentRoot> contentRoots) throws IOException {

        ST moduleContents = StringTemplateFile.MODULE_TEMPLATE.getST();

        moduleContents.add("contentRoots", contentRoots);
        moduleContents.add("dependencies", projectDataPreparer.getDependencies(module));
        moduleContents.add("generatedSourceFolders", projectDataPreparer.getGeneratedSourceFolders(module));
        moduleContents.add("androidFacet", projectDataPreparer.getAndroidProperties(module));
        moduleContents.add("sdk", module.getModuleType().getSdkName(projectConfig).orElse(null));
        moduleContents.add("sdkType", module.getModuleType().getSdkType(projectConfig));
        moduleContents.add("languageLevel",
                JavaLanguageLevelHelper.convertLanguageLevelToIjFormat(module.getLanguageLevel().orElse(null)));
        moduleContents.add("moduleType", module.getModuleType().getImlModuleType());
        moduleContents.add("metaInfDirectory", module.getMetaInfDirectory()
                .map((dir) -> getUrl(projectPaths.getModuleQualifiedPath(dir, module))).orElse(null));

        return writeTemplate(moduleContents, projectPaths.getModuleImlFilePath(module));
    }

    private void writeProjectSettings() throws IOException {

        Optional<String> sdkName = projectConfig.getProjectJdkName();
        Optional<String> sdkType = projectConfig.getProjectJdkType();

        if (!sdkName.isPresent() || !sdkType.isPresent()) {
            return;
        }

        ST contents = StringTemplateFile.MISC_TEMPLATE.getST();

        String languageLevelInIjFormat = getLanguageLevelFromConfig();

        contents.add("languageLevel", languageLevelInIjFormat);
        contents.add("jdk15", getJdk15FromLanguageLevel(languageLevelInIjFormat));
        contents.add("jdkName", sdkName.get());
        contents.add("jdkType", sdkType.get());
        contents.add("outputUrl", projectConfig.getOutputUrl().orElse(null));

        writeTemplate(contents, getIdeaConfigDir().resolve("misc.xml"));
    }

    private String getLanguageLevelFromConfig() {
        Optional<String> languageLevelFromConfig = projectConfig.getProjectLanguageLevel();
        if (languageLevelFromConfig.isPresent()) {
            return languageLevelFromConfig.get();
        } else {
            String languageLevel = projectConfig.getJavaBuckConfig().getDefaultJavacOptions().getSourceLevel();
            return JavaLanguageLevelHelper.convertLanguageLevelToIjFormat(languageLevel);
        }
    }

    private static boolean getJdk15FromLanguageLevel(String languageLevel) {
        boolean jdkUnder15 = "JDK_1_3".equals(languageLevel) || "JDK_1_4".equals(languageLevel);
        return !jdkUnder15;
    }

    private void writeLibrary(IjLibrary library) throws IOException {
        ST contents = StringTemplateFile.LIBRARY_TEMPLATE.getST();
        contents.add("name", library.getName());
        contents.add("binaryJars", library.getBinaryJars().stream().map(projectPaths::getProjectRelativePath)
                .collect(ImmutableSortedSet.toImmutableSortedSet(Ordering.natural())));
        contents.add("classPaths", library.getClassPaths().stream().map(projectPaths::getProjectRelativePath)
                .collect(ImmutableSortedSet.toImmutableSortedSet(Ordering.natural())));
        contents.add("sourceJars", library.getSourceJars().stream().map(projectPaths::getProjectRelativePath)
                .collect(ImmutableSortedSet.toImmutableSortedSet(Ordering.natural())));
        contents.add("javadocUrls", library.getJavadocUrls());
        // TODO(mkosiba): support res and assets for aar.

        Path path = projectPaths.getLibraryXmlFilePath(library);
        writeTemplate(contents, path);
    }

    // Write modules.xml
    private void writeModulesIndex(ImmutableSortedSet<ModuleIndexEntry> moduleEntries) throws IOException {
        ST moduleIndexContents = StringTemplateFile.MODULE_INDEX_TEMPLATE.getST();
        moduleIndexContents.add("modules", moduleEntries);

        writeTemplate(moduleIndexContents, getIdeaConfigDir().resolve("modules.xml"));
    }

    private Path getIdeaConfigDir() {
        return projectPaths.getIdeaConfigDir();
    }

    /**
     * Writes template to output project filesystem
     *
     * @param path Relative path from project root
     */
    private boolean writeTemplate(ST contents, Path path) throws IOException {
        boolean didUpdate = StringTemplateFile.writeToFile(outFilesystem, contents, path, getIdeaConfigDir());
        cleaner.doNotDelete(path);
        return didUpdate;
    }

    private void writeWorkspace() throws IOException {
        WorkspaceUpdater workspaceUpdater = new WorkspaceUpdater(outFilesystem, getIdeaConfigDir());
        workspaceUpdater.updateOrCreateWorkspace();
        cleaner.doNotDelete(workspaceUpdater.getWorkspacePath());
    }

    /**
     * Update project files and modules index
     *
     * @throws IOException if a file cannot be written
     */
    public void update() throws IOException {
        outFilesystem.mkdirs(getIdeaConfigDir());
        for (IjModule module : projectDataPreparer.getModulesToBeWritten()) {
            ImmutableList<ContentRoot> contentRoots = projectDataPreparer.getContentRoots(module);
            writeModule(module, contentRoots);
        }
        for (IjLibrary library : projectDataPreparer.getLibrariesToBeWritten()) {
            writeLibrary(library);
        }
        updateModulesIndex(projectDataPreparer.getModulesToBeWritten());

        if (projectConfig.isGeneratingTargetInfoMapEnabled()) {
            writeTargetInfoMap(projectDataPreparer, true);
        }
    }

    /** Update the modules.xml file with any new modules from the given set */
    private void updateModulesIndex(ImmutableSet<IjModule> modulesEdited) throws IOException {
        final Set<ModuleIndexEntry> existingModules = modulesParser
                .getAllModules(projectFilesystem.newFileInputStream(getIdeaConfigDir().resolve("modules.xml")));
        final Set<Path> existingModuleFilepaths = existingModules.stream().map(ModuleIndexEntry::getFilePath)
                .map(MorePaths::pathWithUnixSeparators).map(Paths::get).collect(ImmutableSet.toImmutableSet());
        ImmutableSet<Path> remainingModuleFilepaths = modulesEdited.stream().map(projectPaths::getModuleImlFilePath)
                .map(MorePaths::pathWithUnixSeparators).map(Paths::get)
                .filter(modulePath -> !existingModuleFilepaths.contains(modulePath))
                .collect(ImmutableSet.toImmutableSet());

        // Merge the existing and new modules into a single sorted set
        ImmutableSortedSet.Builder<ModuleIndexEntry> finalModulesBuilder = ImmutableSortedSet
                .orderedBy(Comparator.<ModuleIndexEntry>naturalOrder());
        // Add the existing definitions
        finalModulesBuilder.addAll(existingModules);
        // Add any new module definitions that we haven't seen yet
        remainingModuleFilepaths.forEach(modulePath -> finalModulesBuilder
                .add(ModuleIndexEntry.builder().setFilePath(projectPaths.getProjectRelativePath(modulePath))
                        .setFileUrl(getUrl(projectPaths.getProjectQualifiedPath(modulePath)))
                        .setGroup(projectConfig.getModuleGroupName()).build()));

        // Write out the merged set to disk
        writeModulesIndex(finalModulesBuilder.build());
    }
}