blob: a060633fdcddac66e352700587f484a4c7671d6b [file] [log] [blame]
/*
* 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.npw;
import com.android.tools.idea.gradle.IdeaGradleProject;
import com.android.tools.idea.gradle.facet.AndroidGradleFacet;
import com.android.tools.idea.gradle.util.GradleUtil;
import com.android.tools.idea.wizard.dynamic.AndroidStudioWizardStep;
import com.android.tools.idea.wizard.template.TemplateWizardState;
import com.google.common.base.Splitter;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.io.Files;
import com.intellij.ide.util.projectWizard.ModuleWizardStep;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.fileChooser.FileChooserDescriptor;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.module.ModuleManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.MessageType;
import com.intellij.openapi.ui.TextFieldWithBrowseButton;
import com.intellij.openapi.vfs.VfsUtil;
import com.intellij.openapi.vfs.VfsUtilCore;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.ui.DocumentAdapter;
import com.intellij.ui.components.JBLabel;
import com.intellij.uiDesigner.core.GridLayoutManager;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import javax.swing.event.DocumentEvent;
import java.awt.*;
import java.io.File;
import java.util.Set;
/**
* Step for selecting archive to import and specifying Gradle subproject name.
*/
public final class WrapArchiveOptionsStep extends ModuleWizardStep implements AndroidStudioWizardStep {
private static final Set<String> SUPPORTED_EXTENSIONS = ImmutableSet.of("jar", "aar");
@Nullable private final Project myProject;
private final TemplateWizardState myWizardState;
private AsyncValidator<ValidationStatus> myValidator;
private JPanel myPanel;
private JTextField myGradlePath;
private TextFieldWithBrowseButton myArchivePath;
private JBLabel myValidationStatus;
private JCheckBox myRemoveOriginalFileCheckBox;
private JCheckBox myReplaceFileDependencyWithCheckBox;
private ValidationStatus myResult = ValidationStatus.EMPTY_PATH;
private boolean myIsFirstUIUpdate = true;
private Icon mySidePanelIcon;
public WrapArchiveOptionsStep(@Nullable Project project,
@NotNull TemplateWizardState state,
@Nullable Icon sidePanelIcon) {
mySidePanelIcon = sidePanelIcon;
centerControls();
myProject = project;
myWizardState = state;
FileChooserDescriptor descriptor = new FileChooserDescriptor(true, false, true, true, false, false) {
@Override
public boolean isFileSelectable(VirtualFile file) {
String extension = file.getExtension();
return extension != null && SUPPORTED_EXTENSIONS.contains(extension.toLowerCase());
}
};
myArchivePath.addBrowseFolderListener("Select Package", "Select jar or aar package to import as a new module", project, descriptor);
myArchivePath.getTextField().getDocument().addDocumentListener(new DocumentAdapter() {
@Override
protected void textChanged(DocumentEvent e) {
pathUpdated(myArchivePath.getText());
updateDataModel();
}
});
myGradlePath.getDocument().addDocumentListener(new DocumentAdapter() {
@Override
protected void textChanged(DocumentEvent e) {
updateDataModel();
}
});
myValidationStatus.setText("");
myValidationStatus.setIcon(null);
myValidator = new AsyncValidator<ValidationStatus>(ApplicationManager.getApplication()) {
@Override
protected void showValidationResult(ValidationStatus result) {
updateStep(result);
}
@NotNull
@Override
protected ValidationStatus validate() {
return validatePath();
}
};
updateDataModel();
}
@Override
public Icon getIcon() {
return mySidePanelIcon;
}
/**
* This page is too empty, adding more whitespace to the top makes it look less desolate.
*/
private void centerControls() {
GridLayoutManager layout = (GridLayoutManager)myPanel.getLayout();
int height = (int)Math.floor(myGradlePath.getPreferredSize().getHeight() * 3);
layout.setMargin(new Insets(height, 16, 0, 0));
}
private void updateStep(@NotNull ValidationStatus result) {
myResult = result;
String message = "";
Icon icon = null;
if (!myIsFirstUIUpdate && (result.getMessageType() != MessageType.INFO)) {
message = getValidationMessage(result);
icon = result.getMessageType().getDefaultIcon();
}
myIsFirstUIUpdate = false;
myValidationStatus.setText(message);
myValidationStatus.setIcon(icon);
myRemoveOriginalFileCheckBox.setVisible(result == ValidationStatus.IN_MODULE);
myReplaceFileDependencyWithCheckBox.setVisible(result == ValidationStatus.IN_MODULE);
fireStateChanged();
}
private String getValidationMessage(ValidationStatus result) {
String gradlePath = myGradlePath.getText().trim();
switch (result) {
case MODULE_EXISTS:
return String.format("Project already contains subproject with name %1$s", gradlePath);
case OK:
case IN_MODULE:
return "";
case EMPTY_PATH:
return "Archive file path is required";
case EMPTY_GRADLE_PATH:
return "Subproject name is required";
case DOES_NOT_EXIST:
return "File does not exist";
case INVALID_MODULE_PATH:
return String.format("Character %1$c is not allowed in a Gradle path",
gradlePath.charAt(GradleUtil.isValidGradlePath(gradlePath)));
default:
throw new IllegalStateException(result.name());
}
}
@NotNull
private ValidationStatus validatePath() {
String archive = myArchivePath.getText().trim();
if (Strings.isNullOrEmpty(archive)) {
return ValidationStatus.EMPTY_PATH;
}
File archiveFile = new File(archive);
if (!archiveFile.isFile()) {
return ValidationStatus.DOES_NOT_EXIST;
}
String gradlePath = myGradlePath.getText().trim();
if (Strings.isNullOrEmpty(gradlePath)) {
return ValidationStatus.EMPTY_GRADLE_PATH;
}
if (GradleUtil.isValidGradlePath(gradlePath) >= 0) {
return ValidationStatus.INVALID_MODULE_PATH;
}
if (GradleUtil.hasModule(myProject, gradlePath, true)) {
return ValidationStatus.MODULE_EXISTS;
}
VirtualFile vfile = VfsUtil.findFileByIoFile(archiveFile, false);
if (vfile != null && getContainingModule(vfile) != null) {
return ValidationStatus.IN_MODULE;
}
else {
return ValidationStatus.OK;
}
}
private void pathUpdated(@NotNull String newPath) {
myGradlePath.setText(Files.getNameWithoutExtension(newPath));
}
@Override
public boolean validate() {
return !MessageType.ERROR.equals(myResult.getMessageType());
}
@Override
public JComponent getComponent() {
return myPanel;
}
@Override
public JComponent getPreferredFocusedComponent() {
return myArchivePath.getTextField();
}
@Override
public void updateDataModel() {
myValidator.invalidate();
}
@Override
public void updateStep() {
String filePath = myArchivePath.getText().trim();
String gradlePath = myGradlePath.getText().trim();
myWizardState.put(WrapArchiveWizardPath.KEY_ARCHIVE, filePath);
myWizardState.put(WrapArchiveWizardPath.KEY_GRADLE_PATH, gradlePath);
boolean move = false;
Module[] modules = new Module[0];
if (myResult == ValidationStatus.IN_MODULE) {
VirtualFile file = VfsUtil.findFileByIoFile(new File(myArchivePath.getText()), false);
if (file != null) {
move = myRemoveOriginalFileCheckBox.isSelected();
modules = new Module[]{getContainingModule(file)};
}
}
myWizardState.put(WrapArchiveWizardPath.KEY_MOVE_ARCHIVE, move);
myWizardState.put(WrapArchiveWizardPath.KEY_MODULES_FOR_DEPENDENCY_UPDATE, modules);
}
@Nullable
private Module getContainingModule(@NotNull VirtualFile file) {
Module bestMatch = null;
if (myProject != null) {
int bestMatchValue = Integer.MAX_VALUE;
for (Module module : ModuleManager.getInstance(myProject).getModules()) {
AndroidGradleFacet facet = AndroidGradleFacet.getInstance(module);
if (facet != null) {
IdeaGradleProject gradleProject = facet.getGradleProject();
assert gradleProject != null;
VirtualFile buildFile = gradleProject.getBuildFile();
if (buildFile != null) {
VirtualFile root = buildFile.getParent();
if (VfsUtilCore.isAncestor(root, file, true)) {
String relativePath = VfsUtilCore.getRelativePath(file, root, '/');
if (relativePath != null) {
int value = Iterables.size(Splitter.on('/').split(relativePath));
if (value < bestMatchValue) {
bestMatch = module;
}
}
}
}
}
}
}
return bestMatch;
}
@Override
public boolean isValid() {
return validate();
}
private enum ValidationStatus {
OK, EMPTY_PATH, DOES_NOT_EXIST, INVALID_MODULE_PATH, MODULE_EXISTS, EMPTY_GRADLE_PATH, IN_MODULE;
public MessageType getMessageType() {
switch (this) {
case OK:
case IN_MODULE:
return MessageType.INFO;
default:
return MessageType.ERROR;
}
}
}
}