com.android.tools.idea.wizard.ModuleListModel.java Source code

Java tutorial

Introduction

Here is the source code for com.android.tools.idea.wizard.ModuleListModel.java

Source

/*
 * Copyright (C) 2014 The Android Open Source Project
 *
 * 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.android.tools.idea.wizard;

import com.android.annotations.VisibleForTesting;
import com.android.tools.idea.gradle.project.ModuleToImport;
import com.android.tools.idea.gradle.util.GradleUtil;
import com.google.common.base.Function;
import com.google.common.base.Functions;
import com.google.common.base.Objects;
import com.google.common.base.Predicate;
import com.google.common.collect.*;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.MessageType;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.VirtualFile;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.Collection;
import java.util.Map;
import java.util.Queue;
import java.util.Set;

/**
 * Manages list of modules.
 */
public final class ModuleListModel {
    @Nullable
    private final Project myProject;
    private Map<ModuleToImport, ModuleValidationState> myModules;
    private Multimap<ModuleToImport, ModuleToImport> myRequiredModules;
    @Nullable
    private VirtualFile mySelectedDirectory;
    private Map<ModuleToImport, String> myNameOverrides = Maps.newHashMap();
    private ModuleToImport myPrimaryModule;
    private Map<ModuleToImport, Boolean> myExplicitSelection = Maps.newHashMap();

    public ModuleListModel(@Nullable Project project) {
        myProject = project;
    }

    @Nullable
    private static ModuleToImport findPrimaryModule(@Nullable VirtualFile directory,
            @NotNull Iterable<ModuleToImport> modules) {
        if (directory == null) {
            return null;
        }
        for (ModuleToImport module : modules) {
            if (Objects.equal(module.location, directory)) {
                return module;
            }
        }
        return null;
    }

    private static boolean isValidModuleName(String moduleName) {
        if (StringUtil.isEmpty(moduleName)) {
            return false;
        }
        int previousSegmentStart = 0;
        for (int segmentSeparator = moduleName.indexOf(":",
                previousSegmentStart); segmentSeparator >= 0; segmentSeparator = moduleName.indexOf(":",
                        previousSegmentStart)) {
            if (!isValidPathSegment(moduleName, previousSegmentStart, segmentSeparator)) {
                return false;
            }
            previousSegmentStart = segmentSeparator + 1;
        }
        return isValidPathSegment(moduleName, previousSegmentStart, moduleName.length());
    }

    private static boolean isValidPathSegment(String string, int segmentStart, int segmentEnd) {
        if (segmentEnd == segmentStart) {
            return segmentStart == 0; // Only allowed at string start to allow for absolute paths
        }
        String segment = string.substring(segmentStart, segmentEnd);
        return !StringUtil.isEmpty(segment) && GradleUtil.isValidGradlePath(segment) < 0;
    }

    private static String getNameErrorMessage(String moduleName) {
        if (StringUtil.isEmptyOrSpaces(moduleName)) {
            return "Module name is empty";
        } else {
            return "Module name is not valid";
        }
    }

    private Multimap<ModuleToImport, ModuleToImport> computeRequiredModules(Set<ModuleToImport> modules) {
        Map<String, ModuleToImport> namesToModules = Maps.newHashMapWithExpectedSize(modules.size());
        // We only care about modules we are actually going to import.
        for (ModuleToImport module : modules) {
            namesToModules.put(module.name, module);
        }
        Multimap<ModuleToImport, ModuleToImport> requiredModules = LinkedListMultimap.create();
        Queue<ModuleToImport> queue = Lists.newLinkedList();

        for (ModuleToImport module : modules) {
            if (Objects.equal(module, myPrimaryModule) || !isUnselected(module, false)) {
                queue.add(module);
            }
        }
        while (!queue.isEmpty()) {
            ModuleToImport moduleToImport = queue.remove();
            for (ModuleToImport dep : Iterables.transform(moduleToImport.getDependencies(),
                    Functions.forMap(namesToModules, null))) {
                if (dep != null) {
                    if (!requiredModules.containsKey(dep)) {
                        queue.add(dep);
                    }
                    requiredModules.put(dep, moduleToImport);
                }
            }
        }
        return requiredModules;
    }

    private boolean isUnselected(ModuleToImport module, boolean isSelected) {
        if (module.location == null) {
            return true;
        } else if (Objects.equal(myPrimaryModule, module)) {
            return false;
        } else if (myModules.get(module) == ModuleValidationState.ALREADY_EXISTS) {
            return !Objects.equal(true, myExplicitSelection.get(module));
        } else {
            return !isSelected && isExplicitlyUnselected(module);
        }
    }

    private ModuleValidationState validateModule(ModuleToImport module) {
        VirtualFile location = module.location;
        if (location == null || !location.exists()) {
            return ModuleValidationState.NOT_FOUND;
        }
        String moduleName = getModuleName(module);
        if (!isValidModuleName(moduleName)) {
            return ModuleValidationState.INVALID_NAME;
        } else if (GradleUtil.hasModule(myProject, moduleName, true)) {
            return ModuleValidationState.ALREADY_EXISTS;
        } else {
            return ModuleValidationState.OK;
        }
    }

    public void setContents(@Nullable VirtualFile selectedDirectory, @NotNull Iterable<ModuleToImport> modules) {
        mySelectedDirectory = selectedDirectory;
        myPrimaryModule = findPrimaryModule(selectedDirectory, modules);
        revalidate(modules);
    }

    private void checkForDuplicateNames() {
        Collection<ModuleToImport> modules = getSelectedModules();
        ImmutableMultiset<String> names = ImmutableMultiset
                .copyOf(Iterables.transform(modules, new Function<ModuleToImport, String>() {
                    @Override
                    public String apply(@Nullable ModuleToImport input) {
                        return input == null ? null : getModuleName(input);
                    }
                }));
        for (ModuleToImport module : modules) {
            ModuleValidationState state = myModules.get(module);
            if (state == ModuleValidationState.OK) {
                if (names.count(getModuleName(module)) > 1) {
                    myModules.put(module, ModuleValidationState.DUPLICATE_MODULE_NAME);
                }
            }
        }
    }

    public Set<ModuleToImport> getSelectedModules() {
        return ImmutableSet.copyOf(Iterables.filter(myModules.keySet(), new Predicate<ModuleToImport>() {
            @Override
            public boolean apply(@Nullable ModuleToImport input) {
                assert input != null;
                return isSelected(input);
            }
        }));
    }

    public boolean hasPrimary() {
        return myPrimaryModule != null;
    }

    public String getModuleName(ModuleToImport module) {
        if (myNameOverrides.containsKey(module) && !isRequiredModule(module)) {
            return myNameOverrides.get(module);
        }
        return module.name;
    }

    @VisibleForTesting
    public ModuleValidationState getModuleState(ModuleToImport module) {
        if (module == null) {
            return ModuleValidationState.NULL;
        }
        ModuleValidationState state = myModules.get(module);
        if (state == ModuleValidationState.OK && isRequiredModule(module)) {
            return ModuleValidationState.REQUIRED;
        } else {
            return state;
        }
    }

    private boolean isRequiredModule(ModuleToImport module) {
        return myRequiredModules.containsKey(module);
    }

    private Map<ModuleToImport, ModuleValidationState> validateModules(Iterable<ModuleToImport> modules) {
        Map<ModuleToImport, ModuleValidationState> result = Maps.newHashMap();
        for (ModuleToImport module : modules) {
            result.put(module, validateModule(module));
        }
        return result;
    }

    public void setSelected(ModuleToImport module, boolean isSelected) {
        myExplicitSelection.put(module, isSelected);
        revalidate(myModules.keySet());
    }

    private void revalidate(Iterable<ModuleToImport> modules) {
        myModules = validateModules(modules);
        myRequiredModules = computeRequiredModules(myModules.keySet());
        for (ModuleToImport module : myRequiredModules.keySet()) {
            myNameOverrides.remove(module);
        }
        checkForDuplicateNames();
    }

    public void setModuleName(ModuleToImport module, @Nullable String newName) {
        if (!isExplicitlyUnselected(module)) {
            if (newName == null) {
                myNameOverrides.remove(module);
            } else {
                myNameOverrides.put(module, newName);
            }
            revalidate(myModules.keySet());
        }
    }

    private boolean isExplicitlyUnselected(ModuleToImport module) {
        return Objects.equal(false, myExplicitSelection.get(module));
    }

    @Nullable
    public MessageType getStatusSeverity(ModuleToImport module) {
        ModuleValidationState state = getModuleState(module);
        switch (state) {
        case OK:
        case NULL:
            return null;
        case NOT_FOUND:
        case DUPLICATE_MODULE_NAME:
        case INVALID_NAME:
            return MessageType.ERROR;
        case ALREADY_EXISTS:
            return getSelectedModules().contains(module) ? MessageType.ERROR : MessageType.WARNING;
        case REQUIRED:
            return MessageType.INFO;
        }
        throw new IllegalArgumentException(state.name());
    }

    @Nullable
    public String getStatusDescription(@NotNull ModuleToImport module) {
        ModuleValidationState state = getModuleState(module);
        switch (state) {
        case OK:
        case NULL:
            return null;
        case NOT_FOUND:
            return "Module sources not found";
        case ALREADY_EXISTS:
            if (isSelected(module) && isRequiredModule(module)) {
                return "Cannot rename module required by another";
            }
            return "Project already contains module with this name";
        case DUPLICATE_MODULE_NAME:
            return "More then one module with this name is selected";
        case REQUIRED:
            Iterable<String> requiredBy = Iterables.transform(myRequiredModules.get(module),
                    new Function<ModuleToImport, String>() {
                        @Override
                        public String apply(ModuleToImport input) {
                            return "'" + getModuleName(input) + "'";
                        }
                    });
            return ImportUIUtil.formatElementListString(requiredBy, "Required by module %s",
                    "Required by modules %s and %s", "Required by modules %s and %d more");
        case INVALID_NAME:
            return getNameErrorMessage(getModuleName(module));
        }
        throw new IllegalStateException(state.name());
    }

    @Nullable
    public ModuleToImport getPrimary() {
        return myPrimaryModule;
    }

    @Nullable
    public VirtualFile getCurrentPath() {
        return mySelectedDirectory;
    }

    public Collection<ModuleToImport> getAllModules() {
        return ImmutableList.copyOf(myModules.keySet());
    }

    public boolean isSelected(@NotNull ModuleToImport module) {
        return !isUnselected(module, isRequiredModule(module));
    }

    public boolean canToggleModuleSelection(ModuleToImport module) {
        ModuleValidationState state = getModuleState(module);
        return state != ModuleValidationState.NOT_FOUND && !isRequiredModule(module);
    }

    public boolean canRename(ModuleToImport module) {
        return !isRequiredModule(module) && isSelected(module);
    }

    @VisibleForTesting
    public enum ModuleValidationState {
        OK, NULL, NOT_FOUND, ALREADY_EXISTS, REQUIRED, DUPLICATE_MODULE_NAME, INVALID_NAME
    }
}