com.google.idea.blaze.android.resources.actions.BlazeCreateResourceUtils.java Source code

Java tutorial

Introduction

Here is the source code for com.google.idea.blaze.android.resources.actions.BlazeCreateResourceUtils.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.android.resources.actions;

import com.android.SdkConstants;
import com.android.resources.ResourceFolderType;
import com.google.common.collect.ImmutableCollection;
import com.google.common.collect.Sets;
import com.google.idea.blaze.android.sync.model.AndroidResourceModule;
import com.google.idea.blaze.android.sync.model.BlazeAndroidSyncData;
import com.google.idea.blaze.base.ideinfo.TargetKey;
import com.google.idea.blaze.base.model.BlazeProjectData;
import com.google.idea.blaze.base.sync.data.BlazeProjectDataManager;
import com.google.idea.blaze.base.sync.workspace.ArtifactLocationDecoder;
import com.google.idea.blaze.base.targetmaps.SourceToTargetMap;
import com.intellij.ide.util.DirectoryUtil;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Computable;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.VfsUtilCore;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiDirectory;
import com.intellij.psi.PsiManager;
import com.intellij.ui.ComboboxWithBrowseButton;
import com.intellij.ui.components.JBLabel;
import java.io.File;
import java.util.Collection;
import java.util.Set;
import javax.swing.JComboBox;
import org.jetbrains.annotations.Nullable;

/** Utilities for setting up create resource actions and dialogs. */
class BlazeCreateResourceUtils {

    private static final String PLACEHOLDER_TEXT = "choose a res/ directory with dropdown or browse button";

    static void setupResDirectoryChoices(Project project, @Nullable VirtualFile contextFile, JBLabel resDirLabel,
            ComboboxWithBrowseButton resDirComboAndBrowser) {
        // Reset the item list before filling it back up.
        resDirComboAndBrowser.getComboBox().removeAllItems();
        BlazeProjectData blazeProjectData = BlazeProjectDataManager.getInstance(project).getBlazeProjectData();
        if (blazeProjectData != null) {
            BlazeAndroidSyncData syncData = blazeProjectData.syncState.get(BlazeAndroidSyncData.class);
            if (syncData != null) {
                ImmutableCollection<TargetKey> rulesRelatedToContext = null;
                File fileFromContext = null;
                if (contextFile != null) {
                    fileFromContext = VfsUtilCore.virtualToIoFile(contextFile);
                    rulesRelatedToContext = SourceToTargetMap.getInstance(project)
                            .getRulesForSourceFile(fileFromContext);
                    if (rulesRelatedToContext.isEmpty()) {
                        rulesRelatedToContext = null;
                    }
                }

                ArtifactLocationDecoder artifactLocationDecoder = blazeProjectData.artifactLocationDecoder;

                // Sort:
                // - contextFile/res if contextFile is a directory,
                //   to optimize the right click on directory case, or the "closest" string
                //   match to the contextFile from the res directories known to blaze
                // - the rest of the direct dirs, then transitive dirs of the context rules,
                //   then any known res dir in the project
                //   as a backup, in alphabetical order.
                Set<File> resourceDirs = Sets.newTreeSet();
                Set<File> transitiveDirs = Sets.newTreeSet();
                Set<File> allResDirs = Sets.newTreeSet();
                for (AndroidResourceModule androidResourceModule : syncData.importResult.androidResourceModules) {

                    Collection<File> resources = artifactLocationDecoder.decodeAll(androidResourceModule.resources);

                    Collection<File> transitiveResources = artifactLocationDecoder
                            .decodeAll(androidResourceModule.transitiveResources);

                    // labelsRelatedToContext should include deps,
                    // but as a first pass we only check the rules themselves
                    // for resources. If we come up empty, then have anyResDir as a backup.
                    allResDirs.addAll(transitiveResources);

                    if (rulesRelatedToContext != null
                            && !rulesRelatedToContext.contains(androidResourceModule.targetKey)) {
                        continue;
                    }
                    resourceDirs.addAll(resources);
                    transitiveDirs.addAll(transitiveResources);
                }
                // No need to show some directories twice.
                transitiveDirs.removeAll(resourceDirs);

                JComboBox resDirCombo = resDirComboAndBrowser.getComboBox();
                // Allow the user to browse and overwrite some of the entries,
                // in case our inference is wrong.
                resDirCombo.setEditable(true);
                // Optimize the right-click on a non-res directory (consider res directory right under that)
                // After the use confirms the choice, a directory will be created if it is missing.
                if (fileFromContext != null && fileFromContext.isDirectory()) {
                    File closestDirToContext = new File(fileFromContext.getPath(), "res");
                    resDirCombo.setSelectedItem(closestDirToContext);
                } else {
                    // If we're not completely sure, let people know there are options
                    // via the placeholder text, and put the most likely on top.
                    String placeHolder = PLACEHOLDER_TEXT;
                    resDirCombo.addItem(placeHolder);
                    resDirCombo.setSelectedItem(placeHolder);
                    if (fileFromContext != null) {
                        File closestDirToContext = findClosestDirToContext(fileFromContext.getPath(), resourceDirs);
                        closestDirToContext = closestDirToContext != null ? closestDirToContext
                                : findClosestDirToContext(fileFromContext.getPath(), transitiveDirs);
                        if (closestDirToContext != null) {
                            resDirCombo.addItem(closestDirToContext);
                            resourceDirs.remove(closestDirToContext);
                            transitiveDirs.remove(closestDirToContext);
                        }
                    }
                }
                if (!resourceDirs.isEmpty() || !transitiveDirs.isEmpty()) {
                    for (File resourceDir : resourceDirs) {
                        resDirCombo.addItem(resourceDir);
                    }
                    for (File resourceDir : transitiveDirs) {
                        resDirCombo.addItem(resourceDir);
                    }
                } else {
                    for (File resourceDir : allResDirs) {
                        resDirCombo.addItem(resourceDir);
                    }
                }
                resDirComboAndBrowser.setVisible(true);
                resDirLabel.setVisible(true);
            }
        }
    }

    private static File findClosestDirToContext(String contextPath, Set<File> resourceDirs) {
        File closestDirToContext = null;
        int curStringDistance = Integer.MAX_VALUE;
        for (File resDir : resourceDirs) {
            int distance = StringUtil.difference(contextPath, resDir.getPath());
            if (distance < curStringDistance) {
                curStringDistance = distance;
                closestDirToContext = resDir;
            }
        }
        return closestDirToContext;
    }

    static PsiDirectory getResDirFromUI(Project project, ComboboxWithBrowseButton directoryCombo) {
        PsiManager psiManager = PsiManager.getInstance(project);
        Object selectedItem = directoryCombo.getComboBox().getEditor().getItem();
        File selectedFile = null;
        if (selectedItem instanceof File) {
            selectedFile = (File) selectedItem;
        } else if (selectedItem instanceof String) {
            String selectedDir = (String) selectedItem;
            if (!selectedDir.equals(PLACEHOLDER_TEXT)) {
                selectedFile = new File(selectedDir);
            }
        }
        if (selectedFile == null) {
            return null;
        }
        final File finalSelectedFile = selectedFile;
        return ApplicationManager.getApplication().runWriteAction(new Computable<PsiDirectory>() {
            @Override
            public PsiDirectory compute() {
                return DirectoryUtil.mkdirs(psiManager, finalSelectedFile.getPath());
            }
        });
    }

    static VirtualFile getResDirFromDataContext(VirtualFile contextFile) {
        // Check if the contextFile is somewhere in
        // the <path>/res/resType/foo.xml hierarchy and return <path>/res/.
        if (contextFile.isDirectory()) {
            if (contextFile.getName().equalsIgnoreCase(SdkConstants.FD_RES)) {
                return contextFile;
            }
            if (ResourceFolderType.getFolderType(contextFile.getName()) != null) {
                VirtualFile parent = contextFile.getParent();
                if (parent != null && parent.getName().equalsIgnoreCase(SdkConstants.FD_RES)) {
                    return parent;
                }
            }
        } else {
            VirtualFile parent = contextFile.getParent();
            if (parent != null && ResourceFolderType.getFolderType(parent.getName()) != null) {
                // Otherwise, the contextFile is a file w/ a parent that is plausible.
                // Recurse one level, on the parent.
                return getResDirFromDataContext(parent);
            }
        }
        // Otherwise, it may be too ambiguous to figure out (e.g., we're in a .java file).
        return null;
    }
}