blob: 07419e334cc8d6e120fb6ad33f2307a94d2bc4be [file] [log] [blame]
/*
* Copyright (C) 2013 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.annotations.VisibleForTesting;
import com.android.builder.model.SourceProvider;
import com.android.sdklib.AndroidVersion;
import com.android.tools.idea.gradle.IdeaAndroidProject;
import com.android.tools.idea.model.AndroidModuleInfo;
import com.android.tools.idea.model.ManifestInfo;
import com.android.tools.idea.templates.KeystoreUtils;
import com.android.tools.idea.templates.TemplateManager;
import com.android.tools.idea.templates.TemplateMetadata;
import com.android.tools.idea.templates.TemplateUtils;
import com.android.tools.idea.wizard.template.TemplateWizard;
import com.android.tools.idea.wizard.template.TemplateWizardState;
import com.android.tools.idea.wizard.template.TemplateWizardStep;
import com.android.tools.idea.wizard.dynamic.DynamicWizard;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.roots.ModuleRootManager;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.vfs.VfsUtilCore;
import com.intellij.openapi.vfs.VirtualFile;
import org.jetbrains.android.facet.AndroidFacet;
import org.jetbrains.android.facet.AndroidRootUtil;
import org.jetbrains.android.facet.IdeaSourceProvider;
import org.jetbrains.android.sdk.AndroidPlatform;
import org.jetbrains.android.util.AndroidUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.awt.*;
import java.io.File;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import static com.android.tools.idea.templates.KeystoreUtils.getDebugKeystore;
import static com.android.tools.idea.templates.TemplateMetadata.*;
/**
* NewTemplateObjectWizard is a base class for templates that instantiate new Android objects based on templates. These aren't for
* complex objects like projects or modules that get customized wizards, but objects simple enough that we can show a generic template
* parameter page and run the template against the source tree.
*
* Deprecated. Extend from {@link DynamicWizard} instead.
*/
@Deprecated
public class NewTemplateObjectWizard extends TemplateWizard implements TemplateWizardStep.UpdateListener,
ChooseTemplateStep.TemplateChangeListener,
ChooseSourceSetStep.SourceProviderSelectedListener {
private static final Logger LOG = Logger.getInstance("#" + NewTemplateObjectWizard.class.getName());
protected TemplateWizardState myWizardState;
protected Project myProject;
protected Module myModule;
private String myTemplateCategory;
private String myTemplateName;
private VirtualFile myTargetFolder;
private Set<String> myExcluded;
@VisibleForTesting RasterAssetSetStep myAssetSetStep;
@VisibleForTesting ChooseTemplateStep myChooseTemplateStep;
private List<SourceProvider> mySourceProviders;
private IdeaAndroidProject myAndroidModel;
protected TemplateParameterStep myTemplateParameterStep;
protected ChooseSourceSetStep myChooseSourceSetStep;
private File myTemplateFile;
public NewTemplateObjectWizard(@Nullable Project project,
@Nullable Module module,
@Nullable VirtualFile invocationTarget,
@Nullable String templateCategory) {
this(project, module, invocationTarget, templateCategory, null, null);
init();
}
public NewTemplateObjectWizard(@Nullable Project project,
@Nullable Module module,
@Nullable VirtualFile invocationTarget,
@Nullable String title,
@Nullable File templateFile) {
this(project, module, invocationTarget, null, title, null);
if (templateFile != null) {
myWizardState.setTemplateLocation(templateFile);
myTemplateFile = templateFile;
}
init();
}
public NewTemplateObjectWizard(@Nullable Project project,
@Nullable Module module,
@Nullable VirtualFile invocationTarget,
@Nullable String title,
@NotNull List<File> templateFiles) {
this(project, module, invocationTarget, null, title, null);
init();
myChooseTemplateStep.setListData(ChooseTemplateStep.getTemplateList(myWizardState, templateFiles, null));
}
private NewTemplateObjectWizard(@Nullable Project project,
@Nullable Module module,
@Nullable VirtualFile invocationTarget,
@Nullable String templateCategory,
@Nullable String templateName,
@Nullable Set<String> excluded) {
super("New " + (templateName != null ? templateName : templateCategory), project);
myProject = project;
myModule = module;
myTemplateCategory = templateCategory;
myTemplateName = templateName;
if (invocationTarget == null) {
myTargetFolder = null;
}
else if (invocationTarget.isDirectory()) {
myTargetFolder = invocationTarget;
}
else {
myTargetFolder = invocationTarget.getParent();
}
myExcluded = excluded;
myWizardState = new TemplateWizardState();
}
@Override
protected void init() {
AndroidFacet facet = AndroidFacet.getInstance(myModule);
assert facet != null;
AndroidPlatform platform = AndroidPlatform.getInstance(myModule);
assert platform != null;
AndroidVersion version = platform.getTarget().getVersion();
myWizardState.put(ATTR_BUILD_API, version.getFeatureLevel());
myWizardState.put(ATTR_BUILD_API_STRING, TemplateMetadata.getBuildApiString(version));
// Read minSdkVersion and package from manifest and/or build.gradle files
int minSdkVersion;
String minSdkName;
AndroidModuleInfo moduleInfo = AndroidModuleInfo.get(facet);
IdeaAndroidProject androidModel = facet.getAndroidModel();
if (androidModel != null) {
myAndroidModel = androidModel;
// Select the source set that we're targeting
mySourceProviders = IdeaSourceProvider.getSourceProvidersForFile(facet, myTargetFolder, facet.getMainSourceProvider());
SourceProvider sourceProvider = mySourceProviders.get(0);
selectSourceProvider(sourceProvider, androidModel);
}
minSdkVersion = moduleInfo.getMinSdkVersion().getFeatureLevel();
minSdkName = moduleInfo.getMinSdkVersion().getApiString();
myWizardState.put(ATTR_MIN_API, minSdkName);
myWizardState.put(ATTR_MIN_API_LEVEL, minSdkVersion);
myWizardState.put(ATTR_TARGET_API, moduleInfo.getTargetSdkVersion().getFeatureLevel());
myWizardState.put(ATTR_TARGET_API_STRING, moduleInfo.getTargetSdkVersion().getApiString());
myWizardState.put(ATTR_IS_LIBRARY_MODULE, facet.isLibraryProject());
try {
myWizardState.put(ATTR_DEBUG_KEYSTORE_SHA1, KeystoreUtils.sha1(getDebugKeystore(facet)));
}
catch (Exception e) {
LOG.info("Could not compute SHA1 hash of debug keystore.", e);
myWizardState.put(ATTR_DEBUG_KEYSTORE_SHA1, "");
}
File templateFile = myTemplateFile != null ? myTemplateFile : TemplateManager.getInstance().getTemplateFile(myTemplateCategory, myTemplateName);
if (myTemplateName == null || templateFile == null) {
myChooseTemplateStep = new ChooseTemplateStep(myWizardState, myTemplateCategory, myProject, myModule, null, this, this, myExcluded);
mySteps.add(myChooseTemplateStep);
} else {
myWizardState.setTemplateLocation(templateFile);
}
if (mySourceProviders != null && mySourceProviders.size() != 1) {
myChooseSourceSetStep = new ChooseSourceSetStep(myWizardState, myProject, myModule, null, this, this, mySourceProviders);
mySteps.add(myChooseSourceSetStep);
}
myTemplateParameterStep = new TemplateParameterStep(myWizardState, myProject, myModule, null, this);
mySteps.add(myTemplateParameterStep);
myAssetSetStep = new RasterAssetSetStep(myWizardState, myProject, myModule, null, this, myTargetFolder);
Disposer.register(getDisposable(), myAssetSetStep);
mySteps.add(myAssetSetStep);
myAssetSetStep.setVisible(false);
myWizardState.put(NewModuleWizardState.ATTR_PROJECT_LOCATION, myProject.getBasePath());
// We're really interested in the directory name on disk, not the module name. These will be different if you give a module the same
// name as its containing project.
String moduleName = new File(myModule.getModuleFilePath()).getParentFile().getName();
myWizardState.put(FormFactorUtils.ATTR_MODULE_NAME, moduleName);
if (!ApplicationManager.getApplication().isUnitTestMode()) {
super.init();
// Ensure that the window is large enough to accommodate the contents without clipping the validation error label
Dimension preferredSize = getContentPanel().getPreferredSize();
getContentPanel().setPreferredSize(new Dimension(Math.max(800, preferredSize.width), Math.max(640, preferredSize.height)));
}
}
private void selectSourceProvider(@NotNull SourceProvider sourceProvider, @NotNull IdeaAndroidProject androidModel) {
// Look up the resource directories inside this source set
File moduleDirPath = androidModel.getRootDirPath();
File javaDir = findSrcDirectory(sourceProvider);
if (javaDir != null) {
String javaPath = FileUtil.getRelativePath(moduleDirPath, javaDir);
if (javaPath != null) {
javaPath = FileUtil.toSystemIndependentName(javaPath);
}
myWizardState.put(ATTR_SRC_DIR, javaPath);
}
File resDir = findResDirectory(sourceProvider);
if (resDir != null) {
String resPath = FileUtil.getRelativePath(moduleDirPath, resDir);
if (resPath != null) {
resPath = FileUtil.toSystemIndependentName(resPath);
}
myWizardState.put(ATTR_RES_DIR, resPath);
myWizardState.put(ATTR_RES_OUT, FileUtil.toSystemIndependentName(resDir.getPath()));
}
File manifestDir = findManifestDirectory(sourceProvider);
if (manifestDir != null) {
String manifestPath = FileUtil.getRelativePath(moduleDirPath, manifestDir);
myWizardState.put(ATTR_MANIFEST_DIR, manifestPath);
myWizardState.put(ATTR_MANIFEST_OUT, FileUtil.toSystemIndependentName(manifestDir.getPath()));
}
File aidlDir = findAidlDir(sourceProvider);
if (aidlDir != null) {
String aidlPath = FileUtil.getRelativePath(moduleDirPath, aidlDir);
myWizardState.put(ATTR_AIDL_DIR, aidlPath);
myWizardState.put(ATTR_AIDL_OUT, FileUtil.toSystemIndependentName(aidlDir.getPath()));
}
// Calculate package name
String applicationPackageName = ManifestInfo.get(myModule, false).getPackage();
String packageName = null;
if (myTargetFolder != null && IdeaSourceProvider.containsFile(sourceProvider, VfsUtilCore.virtualToIoFile(myTargetFolder))) {
packageName = getPackageFromDirectory(VfsUtilCore.virtualToIoFile(myTargetFolder), sourceProvider, myModule, myWizardState);
if (packageName != null && !packageName.equals(applicationPackageName)) {
// If we have selected a folder, make sure we pass along the application package
// so that we can do proper imports
myWizardState.put(ATTR_APPLICATION_PACKAGE, applicationPackageName);
myWizardState.myFinal.add(ATTR_APPLICATION_PACKAGE);
}
}
if (packageName == null) {
// Fall back to the application package but allow the user to edit
packageName = applicationPackageName;
} else {
myWizardState.myHidden.add(TemplateMetadata.ATTR_PACKAGE_NAME);
myWizardState.myFinal.add(TemplateMetadata.ATTR_PACKAGE_NAME);
if (javaDir != null) {
String packagePath = FileUtil.toSystemDependentName(packageName.replace('.', '/'));
File packageRoot = new File(javaDir, packagePath);
myWizardState.put(TemplateMetadata.ATTR_PACKAGE_ROOT, FileUtil.toSystemIndependentName(packageRoot.getPath()));
myWizardState.myFinal.add(TemplateMetadata.ATTR_PACKAGE_ROOT);
}
}
if (javaDir != null) {
File srcOut = new File(javaDir, packageName.replace('.', File.separatorChar));
myWizardState.put(ATTR_SRC_OUT, FileUtil.toSystemIndependentName(srcOut.getPath()));
}
myWizardState.put(ATTR_PACKAGE_NAME, packageName);
myWizardState.put(ATTR_SOURCE_PROVIDER_NAME, sourceProvider.getName());
myWizardState.setSourceProvider(sourceProvider);
}
/**
* Finds and returns the main src directory for the given project or null if one cannot be found.
*/
@Nullable
public static File findSrcDirectory(@NotNull SourceProvider sourceProvider) {
Collection<File> javaDirectories = sourceProvider.getJavaDirectories();
return javaDirectories.isEmpty() ? null : javaDirectories.iterator().next();
}
/**
* Finds and returns the main res directory for the given project or null if one cannot be found.
*/
@Nullable
public static File findResDirectory(@NotNull SourceProvider sourceProvider) {
Collection<File> resDirectories = sourceProvider.getResDirectories();
File resDir = null;
if (!resDirectories.isEmpty()) {
resDir = resDirectories.iterator().next();
}
return resDir;
}
/**
* Finds and returns the main res directory for the given project or null if one cannot be found.
*/
@Nullable
public static File findAidlDir(@NotNull SourceProvider sourceProvider) {
Collection<File> aidlDirectories = sourceProvider.getAidlDirectories();
File resDir = null;
if (!aidlDirectories.isEmpty()) {
resDir = aidlDirectories.iterator().next();
}
return resDir;
}
/**
* Finds and returns the main manifest directory for the given project or null if one cannot be found.
*/
@Nullable
public static File findManifestDirectory(@NotNull SourceProvider sourceProvider) {
File manifestFile = sourceProvider.getManifestFile();
File manifestDir = manifestFile.getParentFile();
if (manifestDir != null) {
return manifestDir;
}
return null;
}
/**
* Calculate the package name from the given target directory. Returns the package name or null if no package name could
* be calculated.
*/
@Nullable
public static String getPackageFromDirectory(@NotNull File directory, @NotNull SourceProvider sourceProvider,
@NotNull Module module, @NotNull TemplateWizardState wizardState) {
File javaSourceRoot;
File javaDir = findSrcDirectory(sourceProvider);
if (javaDir == null) {
javaSourceRoot = new File(AndroidRootUtil.getModuleDirPath(module),
FileUtil.toSystemDependentName(wizardState.getString(ATTR_SRC_DIR)));
}
else {
javaSourceRoot = new File(javaDir.getPath());
}
File javaSourcePackageRoot = new File(directory.getPath());
if (!FileUtil.isAncestor(javaSourceRoot, javaSourcePackageRoot, true)) {
return null;
}
String relativePath = FileUtil.getRelativePath(javaSourceRoot, javaSourcePackageRoot);
String packageName = relativePath != null ? FileUtil.toSystemIndependentName(relativePath).replace('/', '.') : null;
if (packageName == null || !AndroidUtils.isValidJavaPackageName(packageName)) {
return null;
}
return packageName;
}
/**
* Exclude the given template name from the selection presented to the user
*/
public void exclude(String templateName) {
myExcluded.add(templateName);
}
public void createTemplateObject(final boolean openEditors) {
ApplicationManager.getApplication().runWriteAction(new Runnable() {
@Override
public void run() {
try {
myWizardState.populateDirectoryParameters();
myWizardState.populateRelativePackage(myModule);
File projectRoot = new File(myProject.getBasePath());
ModuleRootManager moduleRootManager = ModuleRootManager.getInstance(myModule);
VirtualFile[] contentRoots = moduleRootManager.getContentRoots();
if (contentRoots.length > 0) {
VirtualFile rootDir = contentRoots[0];
File moduleRoot = VfsUtilCore.virtualToIoFile(rootDir);
myWizardState.myTemplate.render(projectRoot, moduleRoot, myWizardState.myParameters, myProject);
// Render the assets if necessary
if (myAssetSetStep.isStepVisible()) {
myAssetSetStep.createAssets(myModule);
}
// Open any new files specified by the template
if (openEditors) {
TemplateUtils.openEditors(myProject, myWizardState.myTemplate.getFilesToOpen(), true);
}
}
}
catch (Exception e) {
LOG.error(e);
}
}
});
}
public void createTemplateObject() {
createTemplateObject(true);
}
@Override
public void templateChanged(String templateName) {
if (myChooseTemplateStep != null) {
TemplateMetadata chosenTemplateMetadata = myChooseTemplateStep.getSelectedTemplateMetadata();
updateAssetSetStep(chosenTemplateMetadata);
}
}
protected void updateAssetSetStep(@Nullable TemplateMetadata chosenTemplateMetadata) {
if (chosenTemplateMetadata != null && chosenTemplateMetadata.getIconType() != null) {
myAssetSetStep.finalizeAssetType(chosenTemplateMetadata.getIconType());
myWizardState.put(ATTR_ICON_NAME, chosenTemplateMetadata.getIconName());
myAssetSetStep.setVisible(true);
}
else {
myAssetSetStep.setVisible(false);
}
}
@Override
public void sourceProviderSelected(@NotNull SourceProvider sourceProvider) {
if (myAndroidModel != null) {
selectSourceProvider(sourceProvider, myAndroidModel);
}
}
@Override
protected boolean canFinish() {
if (!super.canFinish()) {
return false;
}
TemplateMetadata metadata = myWizardState.getTemplateMetadata();
int minApi = myWizardState.getInt(ATTR_MIN_API_LEVEL);
if (metadata != null && metadata.getMinSdk() > minApi) {
((TemplateWizardStep)getCurrentStepObject()).setErrorHtml("This template requires a minimum API level of " + metadata.getMinSdk());
return false;
}
return true;
}
}