/* * 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.tools.idea.gradle.IdeaGradleProject; import com.android.tools.idea.gradle.facet.AndroidGradleFacet; import com.android.tools.idea.gradle.util.GradleUtil; 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.Disposable; 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; } } } }