com.google.idea.blaze.base.wizard.BlazeProjectSettingsControl.java Source code

Java tutorial

Introduction

Here is the source code for com.google.idea.blaze.base.wizard.BlazeProjectSettingsControl.java

Source

/*
 * Copyright 2016 The Bazel Authors. 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 com.google.idea.blaze.base.wizard;

import com.google.common.collect.Lists;
import com.google.idea.blaze.base.bazel.BuildSystemProvider;
import com.google.idea.blaze.base.model.primitives.TargetExpression;
import com.google.idea.blaze.base.model.primitives.WorkspacePath;
import com.google.idea.blaze.base.model.primitives.WorkspaceRoot;
import com.google.idea.blaze.base.projectview.ProjectView;
import com.google.idea.blaze.base.projectview.ProjectViewSet;
import com.google.idea.blaze.base.projectview.ProjectViewStorageManager;
import com.google.idea.blaze.base.projectview.parser.ProjectViewParser;
import com.google.idea.blaze.base.projectview.section.ListSection;
import com.google.idea.blaze.base.projectview.section.ScalarSection;
import com.google.idea.blaze.base.projectview.section.sections.DirectoryEntry;
import com.google.idea.blaze.base.projectview.section.sections.DirectorySection;
import com.google.idea.blaze.base.projectview.section.sections.ImportSection;
import com.google.idea.blaze.base.projectview.section.sections.TargetSection;
import com.google.idea.blaze.base.scope.output.IssueOutput;
import com.google.idea.blaze.base.settings.Blaze.BuildSystem;
import com.google.idea.blaze.base.settings.BlazeImportSettings;
import com.google.idea.blaze.base.settings.ui.JPanelProvidingProject;
import com.google.idea.blaze.base.settings.ui.ProjectViewUi;
import com.google.idea.blaze.base.ui.BlazeValidationError;
import com.google.idea.blaze.base.ui.BlazeValidationResult;
import com.google.idea.blaze.base.ui.UiUtil;
import com.google.idea.blaze.base.vcs.BlazeVcsHelper;
import com.intellij.ide.RecentProjectsManager;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.application.ApplicationNamesInfo;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.fileChooser.FileChooserDescriptor;
import com.intellij.openapi.ui.TextComponentAccessor;
import com.intellij.openapi.ui.TextFieldWithBrowseButton;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.ui.components.JBLabel;
import com.intellij.util.SystemProperties;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import javax.swing.*;
import java.awt.*;
import java.io.File;
import java.io.IOException;
import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors;

/**
 * The UI control to collect project settings when importing a Blaze project.
 */
public final class BlazeProjectSettingsControl {

    private static final FileChooserDescriptor STUDIO_PROJECT_FOLDER_DESCRIPTOR = new FileChooserDescriptor(false,
            true, false, false, false, false);
    private static final Logger LOG = Logger.getInstance(BlazeProjectSettingsControl.class);

    private WorkspaceRoot workspaceRoot;
    @Nullable
    private File sharedProjectViewFile;
    @Nullable
    private String vcsClientName;

    private TextFieldWithBrowseButton projectDataDirField;
    private JTextField projectNameField;
    private ProjectViewUi projectViewUi;

    public BlazeProjectSettingsControl(Disposable parentDisposable) {
        this.projectViewUi = new ProjectViewUi(parentDisposable);
    }

    public JPanel createComponent(File fileToImport) {
        JPanel component = new JPanelProvidingProject(ProjectViewUi.getProject(), new GridBagLayout());
        fillUi(component, 0);
        init(fileToImport);
        UiUtil.fillBottom(component);
        return component;
    }

    private void fillUi(@NotNull JPanel canvas, int indentLevel) {
        JLabel projectDataDirLabel = new JBLabel("Project data directory:");

        Dimension minSize = ProjectViewUi.getMinimumSize();
        // Add 120 pixels so we have room for our extra fields
        minSize.setSize(minSize.width, minSize.height + 120);
        canvas.setMinimumSize(minSize);
        canvas.setPreferredSize(minSize);

        projectDataDirField = new TextFieldWithBrowseButton();
        projectDataDirField.addBrowseFolderListener("", "Blaze Android Studio project data directory", null,
                STUDIO_PROJECT_FOLDER_DESCRIPTOR, TextComponentAccessor.TEXT_FIELD_WHOLE_TEXT, false);
        final String dataDirToolTipText = "Directory in which to store the project's metadata. Choose a directory outside of"
                + " the Piper/CitC client directories or Git5 directory.";
        projectDataDirField.setToolTipText(dataDirToolTipText);
        projectDataDirLabel.setToolTipText(dataDirToolTipText);

        canvas.add(projectDataDirLabel, UiUtil.getLabelConstraints(indentLevel));
        canvas.add(projectDataDirField, UiUtil.getFillLineConstraints(0));

        JLabel projectNameLabel = new JLabel("Project name:");
        projectNameField = new JTextField();
        final String projectNameToolTipText = "Project display name.";
        projectNameField.setToolTipText(projectNameToolTipText);
        projectNameLabel.setToolTipText(projectNameToolTipText);
        canvas.add(projectNameLabel, UiUtil.getLabelConstraints(indentLevel));
        canvas.add(projectNameField, UiUtil.getFillLineConstraints(0));

        projectViewUi.fillUi(canvas, indentLevel);
    }

    private void init(File fileToImport) {
        workspaceRoot = BuildSystemProvider.getWorkspaceRootProvider(BuildSystem.Blaze)
                .findWorkspaceRoot(fileToImport);
        if (workspaceRoot == null) {
            throw new IllegalArgumentException("Invalid workspace root: " + fileToImport);
        }
        vcsClientName = BlazeVcsHelper.getClientName(workspaceRoot);

        File importDirectory = null;
        if (ProjectViewStorageManager.isProjectViewFile(fileToImport.getPath())) {
            importDirectory = fileToImport.getParentFile();
            sharedProjectViewFile = new File(fileToImport.getPath());
        } else if (ImportSource.isBuildFile(fileToImport)) {
            importDirectory = fileToImport.getParentFile();
            for (String extension : ProjectViewStorageManager.VALID_EXTENSIONS) {
                File defaultProjectViewFile = new File(fileToImport.getParentFile(), "." + extension);
                if (defaultProjectViewFile.exists()) {
                    sharedProjectViewFile = defaultProjectViewFile;
                    break;
                }
            }
        }

        String defaultProjectName = importDirectory != null ? importDirectory.getName()
                : workspaceRoot.directory().getParentFile().getName();
        projectNameField.setText(defaultProjectName);

        String defaultDataDir = getDefaultProjectDataDirectory(defaultProjectName, vcsClientName);
        projectDataDirField.setText(defaultDataDir);

        String projectViewText = "";
        if (sharedProjectViewFile != null) {
            try {
                projectViewText = ProjectViewStorageManager.getInstance().loadProjectView(sharedProjectViewFile);
                if (projectViewText == null) {
                    LOG.error("Could not load project view: " + sharedProjectViewFile);
                    projectViewText = "";
                }
            } catch (IOException e) {
                LOG.error(e);
            }
        }
        if (projectViewText.isEmpty() && importDirectory != null) {
            projectViewText = guessProjectViewFromLocation(workspaceRoot, importDirectory);
        }

        projectViewUi.init(workspaceRoot, projectViewText, sharedProjectViewFile != null ? projectViewText : null,
                sharedProjectViewFile, sharedProjectViewFile != null,
                false /* allowEditShared - not allowed during import */
        );
    }

    @NotNull
    private static String getDefaultProjectDataDirectory(@NotNull String projectName,
            @Nullable String vcsClientName) {
        File defaultDataDirectory = new File(getDefaultProjectsDirectory());
        if (vcsClientName != null) {
            // Ensure that each client gets its own data directory.
            projectName = vcsClientName + "-" + projectName;
        }
        File desiredLocation = new File(defaultDataDirectory, projectName);
        return newUniquePath(desiredLocation);
    }

    @NotNull
    private static String getDefaultProjectsDirectory() {
        final String lastProjectLocation = RecentProjectsManager.getInstance().getLastProjectCreationLocation();
        if (lastProjectLocation != null) {
            return lastProjectLocation.replace('/', File.separatorChar);
        }
        final String userHome = SystemProperties.getUserHome();
        String productName = ApplicationNamesInfo.getInstance().getLowercaseProductName();
        return userHome.replace('/', File.separatorChar) + File.separator + productName.replace(" ", "")
                + "Projects";
    }

    @NotNull
    private static String guessProjectViewFromLocation(@NotNull WorkspaceRoot workspaceRoot,
            @NotNull File importDirectory) {

        WorkspacePath mainModuleGoogle3RelativePath = workspaceRoot.workspacePathFor(importDirectory);
        WorkspacePath testModuleGoogle3RelativePath = guessTestRelativePath(workspaceRoot,
                mainModuleGoogle3RelativePath);

        ListSection.Builder<DirectoryEntry> directorySectionBuilder = ListSection.builder(DirectorySection.KEY);
        directorySectionBuilder.add(DirectoryEntry.include(mainModuleGoogle3RelativePath));
        if (testModuleGoogle3RelativePath != null) {
            directorySectionBuilder.add(DirectoryEntry.include(testModuleGoogle3RelativePath));
        }

        ListSection.Builder<TargetExpression> targetSectionBuilder = ListSection.builder(TargetSection.KEY);
        targetSectionBuilder.add(TargetExpression.fromString("//" + mainModuleGoogle3RelativePath + "/...:all"));
        if (testModuleGoogle3RelativePath != null) {
            targetSectionBuilder
                    .add(TargetExpression.fromString("//" + testModuleGoogle3RelativePath + "/...:all"));
        }

        return ProjectViewParser.projectViewToString(
                ProjectView.builder().put(directorySectionBuilder).put(targetSectionBuilder).build());
    }

    @Nullable
    private static WorkspacePath guessTestRelativePath(@NotNull WorkspaceRoot workspaceRoot,
            @NotNull WorkspacePath projectWorkspacePath) {
        String projectRelativePath = projectWorkspacePath.relativePath();
        String testBuildFileRelativePath = null;
        if (projectRelativePath.startsWith("java/")) {
            testBuildFileRelativePath = projectRelativePath.replaceFirst("java/", "javatests/");
        } else if (projectRelativePath.contains("/java/")) {
            testBuildFileRelativePath = projectRelativePath.replaceFirst("/java/", "/javatests/");
        }
        if (testBuildFileRelativePath != null) {
            File testBuildFile = workspaceRoot.fileForPath(new WorkspacePath(testBuildFileRelativePath));
            if (testBuildFile.exists()) {
                return new WorkspacePath(testBuildFileRelativePath);
            }
        }
        return null;
    }

    /**
     * Returns a unique file path by appending numbers until a non-collision is found.
     */
    private static String newUniquePath(File location) {
        if (!location.exists()) {
            return location.getAbsolutePath();
        }

        String name = location.getName();
        File directory = location.getParentFile();
        int tries = 0;
        while (true) {
            String candidateName = String.format("%s-%02d", name, tries);
            File candidateFile = new File(directory, candidateName);
            if (!candidateFile.exists()) {
                return candidateFile.getAbsolutePath();
            }
            tries++;
        }
    }

    @Nullable
    private BlazeValidationError validateProjectDataDirectory(@NotNull File projectDataDirPath) {
        if (workspaceRoot.isInWorkspace(projectDataDirPath)) {
            return new BlazeValidationError(
                    "Project data directory should be placed outside of the client directory");
        }
        return null;
    }

    @NotNull
    public BlazeValidationResult validate() {
        // Validate project settings fields
        String projectName = projectNameField.getText().trim();
        if (StringUtil.isEmpty(projectName)) {
            return BlazeValidationResult.failure(new BlazeValidationError("Project name is not specified"));
        }
        String projectDataDirPath = projectDataDirField.getText().trim();
        if (StringUtil.isEmpty(projectDataDirPath)) {
            return BlazeValidationResult
                    .failure(new BlazeValidationError("Project data directory is not specified"));
        }
        File projectDataDir = new File(projectDataDirPath);
        if (!projectDataDir.isAbsolute()) {
            return BlazeValidationResult.failure(new BlazeValidationError("Project data directory is not valid"));
        }
        BlazeValidationError projectDataDirectoryValidation = validateProjectDataDirectory(projectDataDir);
        if (projectDataDirectoryValidation != null) {
            return BlazeValidationResult.failure(projectDataDirectoryValidation);
        }

        List<IssueOutput> issues = Lists.newArrayList();

        ProjectViewSet projectViewSet = projectViewUi.parseProjectView(issues);
        BlazeValidationError projectViewParseError = validationErrorFromIssueList(issues);
        if (projectViewParseError != null) {
            return BlazeValidationResult.failure(projectViewParseError);
        }

        return BlazeValidationResult.success();
    }

    public ImportResults getResults() {
        String projectName = projectNameField.getText().trim();
        String projectDataDirectory = projectDataDirField.getText().trim();

        // Create unique location hash
        final String locationHash = createLocationHash(projectName);

        // Only support blaze in the old import wizard. TODO: remove this wizard prior to public bazel release.
        BuildSystem fixedBuildSystem = BuildSystem.Blaze;

        File sharedProjectViewFile = this.sharedProjectViewFile;
        File localProjectViewFile = ProjectViewStorageManager.getLocalProjectViewFileName(fixedBuildSystem,
                new File(projectDataDirectory));

        BlazeImportSettings importSettings = new BlazeImportSettings(workspaceRoot.directory().getPath(),
                projectName, projectDataDirectory, locationHash, localProjectViewFile.getPath(), fixedBuildSystem);

        boolean useSharedProjectView = projectViewUi.getUseSharedProjectView();

        // If we're using a shared project view, synthesize a local one that imports the shared one
        final ProjectView projectView;
        if (useSharedProjectView && sharedProjectViewFile != null) {
            WorkspaceRoot workspaceRoot = WorkspaceRoot.fromImportSettings(importSettings);
            projectView = ProjectView.builder().put(ScalarSection.builder(ImportSection.KEY)
                    .set(workspaceRoot.workspacePathFor(sharedProjectViewFile))).build();
        } else {
            ProjectViewSet parseResult = projectViewUi.parseProjectView(Lists.<IssueOutput>newArrayList());
            ProjectViewSet.ProjectViewFile projectViewFile = parseResult.getTopLevelProjectViewFile();
            assert projectViewFile != null;
            projectView = projectViewFile.projectView;
        }

        return new ImportResults(importSettings, projectView, calculateProjectName(projectName, vcsClientName),
                projectDataDirectory);
    }

    @NotNull
    private static String createLocationHash(@NotNull String projectName) {
        String uuid = UUID.randomUUID().toString();
        uuid = uuid.substring(0, Math.min(uuid.length(), 8));
        return projectName.replaceAll("[^a-zA-Z0-9]", "") + "-" + uuid;
    }

    private static String calculateProjectName(@NotNull String projectName, @Nullable String vcsClientName) {
        if (vcsClientName != null) {
            projectName = String.format("%s (%s)", projectName, vcsClientName);
        }
        return projectName;
    }

    @Nullable
    private static BlazeValidationError validationErrorFromIssueList(List<IssueOutput> issues) {
        List<IssueOutput> errors = issues.stream()
                .filter(issue -> issue.getCategory() == IssueOutput.Category.ERROR).collect(Collectors.toList());

        if (!errors.isEmpty()) {
            StringBuilder errorMessage = new StringBuilder();
            errorMessage.append("The following issues were found:\n\n");
            for (IssueOutput issue : errors) {
                errorMessage.append(issue.getMessage());
                errorMessage.append('\n');
            }
            return new BlazeValidationError(errorMessage.toString());
        }
        return null;
    }
}