All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.android.tools.idea.wizard.ModuleListModel Maven / Gradle / Ivy

/*
 * 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 myModules;
  private Multimap myRequiredModules;
  @Nullable private VirtualFile mySelectedDirectory;
  private Map myNameOverrides = Maps.newHashMap();
  private ModuleToImport myPrimaryModule;
  private Map myExplicitSelection = Maps.newHashMap();

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

  @Nullable
  private static ModuleToImport findPrimaryModule(@Nullable VirtualFile directory, @NotNull Iterable 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 computeRequiredModules(Set modules) {
    Map 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 requiredModules = LinkedListMultimap.create();
    Queue 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 modules) {
    mySelectedDirectory = selectedDirectory;
    myPrimaryModule = findPrimaryModule(selectedDirectory, modules);
    revalidate(modules);
  }

  private void checkForDuplicateNames() {
    Collection modules = getSelectedModules();
    ImmutableMultiset names = ImmutableMultiset.copyOf(Iterables.transform(modules, new Function() {
      @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 getSelectedModules() {
    return ImmutableSet.copyOf(Iterables.filter(myModules.keySet(), new Predicate() {
      @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 validateModules(Iterable modules) {
    Map 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 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 requiredBy = Iterables.transform(myRequiredModules.get(module), new Function() {
          @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 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
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy