blob: cdea89475ff48164721d735a101414a8e95260d5 [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.sdklib.AndroidVersion;
import com.android.sdklib.IAndroidTarget;
import com.android.sdklib.SdkVersionInfo;
import com.android.sdklib.repository.FullRevision;
import com.android.sdklib.repository.descriptors.IPkgDesc;
import com.android.sdklib.repository.descriptors.PkgType;
import com.android.sdklib.repository.local.LocalPkgInfo;
import com.android.tools.idea.templates.Parameter;
import com.android.tools.idea.templates.TemplateMetadata;
import com.android.tools.idea.templates.TemplateUtils;
import com.android.tools.idea.ui.ComboBoxItemWithApiTag;
import com.android.tools.idea.wizard.template.TemplateWizardState;
import com.android.tools.idea.wizard.template.TemplateWizardStep;
import com.google.common.base.CharMatcher;
import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableSet;
import com.intellij.ide.util.PropertiesComponent;
import com.intellij.ide.util.projectWizard.ModuleBuilder;
import com.intellij.ide.util.projectWizard.ProjectBuilder;
import com.intellij.ide.util.projectWizard.WizardContext;
import com.intellij.openapi.fileChooser.FileChooserFactory;
import com.intellij.openapi.fileChooser.FileSaverDescriptor;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.module.ModuleManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.projectRoots.JavaSdk;
import com.intellij.openapi.projectRoots.JavaSdkVersion;
import com.intellij.openapi.projectRoots.ProjectJdkTable;
import com.intellij.openapi.projectRoots.Sdk;
import com.intellij.openapi.ui.TextFieldWithBrowseButton;
import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.vfs.VirtualFileWrapper;
import com.intellij.pom.java.LanguageLevel;
import org.jetbrains.android.sdk.AndroidPlatform;
import org.jetbrains.android.sdk.AndroidSdkData;
import org.jetbrains.android.sdk.AndroidSdkType;
import org.jetbrains.android.sdk.AndroidSdkUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import java.util.concurrent.Callable;
import static com.android.tools.idea.npw.NewModuleWizardState.APP_NAME;
import static com.android.tools.idea.npw.NewProjectWizardState.*;
import static com.android.tools.idea.templates.TemplateMetadata.*;
/**
* ConfigureAndroidModuleStep is the first page in the New Project wizard that sets project/module name, location, and other project-global
* parameters.
*
* Deprecated. Use {@link ConfigureAndroidModuleStepDynamic} instead.
*/
@Deprecated
public class ConfigureAndroidModuleStep extends TemplateWizardStep {
private static final String SAMPLE_PACKAGE_PREFIX = "com.example.";
private static final String INVALID_FILENAME_CHARS = "[/\\\\?%*:|\"<>]";
private static final CharMatcher ILLEGAL_CHARACTER_MATCHER = CharMatcher.anyOf(INVALID_FILENAME_CHARS);
@VisibleForTesting
static final Set<String> INVALID_MSFT_FILENAMES = ImmutableSet
.of("con", "prn", "aux", "clock$", "nul", "com1", "com2", "com3", "com4", "com5", "com6", "com7", "com8", "com9", "lpt1", "lpt2",
"lpt3", "lpt4", "lpt5", "lpt6", "lpt7", "lpt8", "lpt9", "$mft", "$mftmirr", "$logfile", "$volume", "$attrdef", "$bitmap", "$boot",
"$badclus", "$secure", "$upcase", "$extend", "$quota", "$objid", "$reparse");
private TextFieldWithBrowseButton myProjectLocation;
private JTextField myAppName;
private JTextField myPackageName;
@VisibleForTesting JComboBox myMinSdk;
@VisibleForTesting JComboBox myTargetSdk;
@VisibleForTesting JComboBox myCompileWith;
private JComboBox myTheme;
@VisibleForTesting JCheckBox myCreateCustomLauncherIconCheckBox;
private JCheckBox myCreateActivityCheckBox;
private JCheckBox myLibraryCheckBox;
private JCheckBox myFragmentCheckBox;
private JCheckBox myActionBarCheckBox;
private JPanel myPanel;
private JTextField myModuleName;
private JLabel myDescription;
private JLabel myError;
private JLabel myProjectLocationLabel;
private JLabel myModuleNameLabel;
private JCheckBox myGridLayoutCheckBox;
private JCheckBox myNavigationDrawerCheckBox;
private JComboBox mySourceCombo;
private JLabel mySourceVersionLabel;
private JLabel myAppNameLabel;
boolean myInitializedPackageNameText = false;
private boolean myInitialized = false;
private boolean myInitializing = false;
private String myPackagePrefix = SAMPLE_PACKAGE_PREFIX;
@Nullable private WizardContext myWizardContext;
public ConfigureAndroidModuleStep(TemplateWizardState state, @Nullable Project project, @Nullable Icon sidePanelIcon,
UpdateListener updateListener) {
super(state, project, null, sidePanelIcon, updateListener);
}
private void initialize() {
IAndroidTarget[] targets = getCompilationTargets();
if (AndroidSdkUtils.isAndroidSdkAvailable()) {
String[] knownVersions = TemplateUtils.getKnownVersions();
for (int i = 0; i < knownVersions.length; i++) {
AndroidTargetComboBoxItem targetInfo = new AndroidTargetComboBoxItem(knownVersions[i], i + 1);
myMinSdk.addItem(targetInfo);
myTargetSdk.addItem(targetInfo);
}
myTemplateState.put(ATTR_TARGET_API, SdkVersionInfo.HIGHEST_KNOWN_API);
myTemplateState.put(ATTR_TARGET_API_STRING, Integer.toString(SdkVersionInfo.HIGHEST_KNOWN_API));
myTemplateState.myModified.add(ATTR_TARGET_API);
myTemplateState.myModified.add(ATTR_TARGET_API_STRING);
}
AndroidVersion highest = null;
for (IAndroidTarget target : targets) {
if (highest == null || target.getVersion().getApiLevel() >= highest.getApiLevel()) {
highest = target.getVersion();
}
AndroidTargetComboBoxItem targetInfo = new AndroidTargetComboBoxItem(target);
myCompileWith.addItem(targetInfo);
if (target.getVersion().isPreview()) {
myMinSdk.addItem(targetInfo);
myTargetSdk.addItem(targetInfo);
}
}
if (highest != null) {
myTemplateState.put(ATTR_BUILD_API, getItemId(highest));
myTemplateState.put(ATTR_BUILD_API_STRING, getBuildApiString(highest));
myTemplateState.myModified.add(ATTR_TARGET_API);
myTemplateState.myModified.add(ATTR_TARGET_API_STRING);
myTemplateState.put(ATTR_TARGET_API, getItemId(highest));
myTemplateState.put(ATTR_TARGET_API_STRING, getBuildApiString(highest));
myTemplateState.myModified.add(ATTR_BUILD_API);
myTemplateState.myModified.add(ATTR_BUILD_API_STRING);
}
// If using KitKat platform tools, we can support language level
if (isJdk7Supported(getSdkData())) {
// We only support a few levels at this point, not for example 1.3 or 1.8
mySourceCombo.addItem(new SourceLevelComboBoxItem(LanguageLevel.JDK_1_5));
mySourceCombo.addItem(new SourceLevelComboBoxItem(LanguageLevel.JDK_1_6));
mySourceCombo.addItem(new SourceLevelComboBoxItem(LanguageLevel.JDK_1_7));
if (!myTemplateState.hasAttr(ATTR_JAVA_VERSION)) {
LanguageLevel defaultLevel = LanguageLevel.JDK_1_6;
myTemplateState.put(ATTR_JAVA_VERSION, languageLevelToString( defaultLevel));
}
} else {
hide(mySourceVersionLabel, mySourceCombo);
}
if (!myTemplateState.hasAttr(ATTR_PROJECT_LOCATION) && myWizardContext != null && myProject == null) {
myTemplateState.put(ATTR_PROJECT_LOCATION, myWizardContext.getProjectFileDirectory());
}
// Find a unique project location
if (myWizardContext == null) {
computeUniqueProjectLocation();
}
if (!myTemplateState.hasAttr(ATTR_APP_TITLE) && myProject != null) {
myTemplateState.put(ATTR_APP_TITLE, myProject.getName());
}
preselectTargetAndBuildApi();
registerUiElements();
myProjectLocation.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
FileSaverDescriptor fileSaverDescriptor = new FileSaverDescriptor("Project location", "Please choose a location for your project");
File currentPath = new File(myProjectLocation.getText());
File parentPath = currentPath.getParentFile();
if (parentPath == null) {
parentPath = new File("/");
}
VirtualFile parent = LocalFileSystem.getInstance().findFileByIoFile(parentPath);
String filename = currentPath.getName();
VirtualFileWrapper fileWrapper =
FileChooserFactory.getInstance().createSaveFileDialog(fileSaverDescriptor, (Project)null).save(parent, filename);
if (fileWrapper != null) {
myProjectLocation.setText(fileWrapper.getFile().getAbsolutePath());
}
}
});
myProjectLocation.getTextField().addFocusListener(this);
myProjectLocation.getTextField().getDocument().addDocumentListener(this);
if (myTemplateState.myHidden.contains(ATTR_PROJECT_LOCATION)) {
myProjectLocation.setVisible(false);
myProjectLocationLabel.setVisible(false);
}
if (myTemplateState.myHidden.contains(ATTR_IS_LIBRARY_MODULE)) {
myLibraryCheckBox.setVisible(false);
}
if (myTemplateState.myHidden.contains(FormFactorUtils.ATTR_MODULE_NAME)) {
myModuleName.setVisible(false);
myModuleNameLabel.setVisible(false);
}
if (myTemplateState.myHidden.contains(ATTR_APP_TITLE)) {
myAppNameLabel.setVisible(false);
myAppName.setVisible(false);
}
}
private void preselectTargetAndBuildApi() {
// In IntelliJ user chooses SDK entry, which also contains api level, in the first step,
// so we preselect "compile with" and "target api" options.
// This code shouldn't be executed in Android Studio: myWizardContext should be null.
if (myWizardContext != null) {
final ProjectBuilder builder = myWizardContext.getProjectBuilder();
if (builder instanceof ModuleBuilder) {
Sdk sdk = ((ModuleBuilder)builder).getModuleJdk();
if (sdk == null) {
sdk = myWizardContext.getProjectJdk();
}
if (sdk != null && sdk.getSdkType() instanceof AndroidSdkType) {
final AndroidPlatform platform = AndroidPlatform.getInstance(sdk);
if (platform != null) {
AndroidVersion version = platform.getTarget().getVersion();
final int apiLevel = version.getFeatureLevel();
myTemplateState.put(ATTR_TARGET_API, apiLevel);
myTemplateState.put(ATTR_TARGET_API_STRING, version.getApiString());
myTemplateState.put(ATTR_BUILD_API, apiLevel);
myTemplateState.put(ATTR_BUILD_API_STRING, TemplateMetadata.getBuildApiString(version));
myCompileWith.setEnabled(false);
}
}
}
}
}
private void registerUiElements() {
TemplateMetadata metadata = myTemplateState.getTemplateMetadata();
if (metadata != null) {
Parameter param = metadata.getParameter(ATTR_BASE_THEME);
if (param != null) {
populateComboBox(myTheme, param);
register(ATTR_BASE_THEME, myTheme);
}
}
register(FormFactorUtils.ATTR_MODULE_NAME, myModuleName);
register(ATTR_PROJECT_LOCATION, myProjectLocation);
register(ATTR_APP_TITLE, myAppName);
register(ATTR_PACKAGE_NAME, myPackageName);
register(ATTR_MIN_API_LEVEL, myMinSdk);
register(ATTR_TARGET_API, myTargetSdk);
register(ATTR_BUILD_API, myCompileWith);
register(ATTR_CREATE_ACTIVITY, myCreateActivityCheckBox);
register(ATTR_CREATE_ICONS, myCreateCustomLauncherIconCheckBox);
register(ATTR_IS_LIBRARY_MODULE, myLibraryCheckBox);
register(ATTR_FRAGMENTS_EXTRA, myFragmentCheckBox);
register(ATTR_NAVIGATION_DRAWER_EXTRA, myNavigationDrawerCheckBox);
register(ATTR_ACTION_BAR_EXTRA, myActionBarCheckBox);
register(ATTR_GRID_LAYOUT_EXTRA, myGridLayoutCheckBox);
if (mySourceCombo.isVisible()) {
register(ATTR_JAVA_VERSION, mySourceCombo);
}
}
@Override
public void refreshUiFromParameters() {
if (!myVisible) {
return;
}
// It's easier to just re-register the UI elements instead of trying to set their values manually. Not all of the elements have
// parameters in the template, and the super refreshUiFromParameters won't touch those elements.
registerUiElements();
super.refreshUiFromParameters();
}
@Override
@NotNull
public JComponent getComponent() {
return myPanel;
}
@Override
public void updateStep() {
if (!myInitializing && AndroidSdkUtils.isAndroidSdkAvailable()) {
myInitializing = true;
initialize();
myInitialized = true;
}
myCreateCustomLauncherIconCheckBox.setVisible(!myTemplateState.myHidden.contains(ATTR_CREATE_ICONS));
}
@Override
public String getHelpId() {
return "Android-Gradle_Page";
}
@Override
public JComponent getPreferredFocusedComponent() {
return myAppName;
}
@NotNull
@VisibleForTesting
IAndroidTarget[] getCompilationTargets() {
AndroidSdkData sdkData = getSdkData();
if (sdkData == null) {
return new IAndroidTarget[0];
}
return getCompilationTargets(sdkData);
}
@NotNull
public static IAndroidTarget[] getCompilationTargets(@NotNull AndroidSdkData sdkData) {
IAndroidTarget[] targets = sdkData.getTargets();
List<IAndroidTarget> list = new ArrayList<IAndroidTarget>();
for (IAndroidTarget target : targets) {
if (!target.isPlatform() && target.getAdditionalLibraries().isEmpty()) {
continue;
}
list.add(target);
}
return list.toArray(new IAndroidTarget[list.size()]);
}
@Override
@Nullable
public String getHelpText(@NotNull String param) {
if (param.equals(FormFactorUtils.ATTR_MODULE_NAME)) {
return "This module name is used only by the IDE. It can typically be the same as the application name.";
} else if (param.equals(ATTR_APP_TITLE)) {
return "The application name is shown in the Play store, as well as in the Manage Applications list in Settings.";
} else if (param.equals(ATTR_PACKAGE_NAME)) {
return "The package name must be a unique identifier for your application.\n It is typically not shown to users, " +
"but it <b>must</b> stay the same for the lifetime of your application; it is how multiple versions of the same application " +
"are considered the \"same app\".\nThis is typically the reverse domain name of your organization plus one or more " +
"application identifiers, and it must be a valid Java package name.";
} else if (param.equals(ATTR_MIN_API)) {
return "Choose the lowest version of Android that your application will support. Lower API levels target more devices, " +
"but means fewer features are available. By targeting API 8 and later, you reach approximately 95% of the market.";
} else if (param.equals(ATTR_TARGET_API)) {
return "Choose the highest API level that the application is known to work with. This attribute informs the system that you have " +
"tested against the target version and the system should not enable any compatibility behaviors to maintain your app's " +
"forward-compatibility with the target version. The application is still able to run on older versions (down to " +
"minSdkVersion). Your application may look dated if you are not targeting the current version.";
} else if (param.equals(ATTR_BUILD_API)) {
return "Choose a target API to compile your code against, from your installed SDKs. This is typically the most recent version, " +
"or the first version that supports all the APIs you want to directly access without reflection.";
} else if (param.equals(ATTR_BASE_THEME)) {
return "Choose the base theme to use for the application";
} else if (param.equals(ATTR_FRAGMENTS_EXTRA)) {
return "Select this box if you plan to use Fragments and will need the Support Library.";
} else if (param.equals(ATTR_ACTION_BAR_EXTRA)) {
return "Select this box if you plan to use the Action Bar and will need the AppCompat Library.";
} else if (param.equals(ATTR_GRID_LAYOUT_EXTRA)) {
return "Select this box if you plan to use the new GridLayout and will need the GridLayout Support Library.";
} else if (param.equals(ATTR_NAVIGATION_DRAWER_EXTRA)) {
return "Select this box if you plan to use the Navigation Drawer and will need the Support Library.";
} else {
return null;
}
}
@Override
public void onStepLeaving() {
((NewModuleWizardState)myTemplateState).updateParameters();
}
@Override
@NotNull
protected JLabel getDescription() {
return myDescription;
}
@Override
@NotNull
protected JLabel getError() {
return myError;
}
@Override
public void updateParams() {
if (!myInitialized) {
return;
}
super.updateParams();
AndroidTargetComboBoxItem item = (AndroidTargetComboBoxItem)myMinSdk.getSelectedItem();
if (item != null) {
myTemplateState.put(ATTR_MIN_API_LEVEL, item.apiLevel);
if (item.target != null) {
myTemplateState.put(ATTR_MIN_API, item.target.getVersion().getApiString());
} else {
myTemplateState.put(ATTR_MIN_API, Integer.toString(item.apiLevel));
}
}
item = (AndroidTargetComboBoxItem)myTargetSdk.getSelectedItem();
if (item != null) {
myTemplateState.put(ATTR_TARGET_API, item.apiLevel);
if (item.target != null) {
myTemplateState.put(ATTR_TARGET_API_STRING, item.target.getVersion().getApiString());
} else {
myTemplateState.put(ATTR_TARGET_API_STRING, Integer.toString(item.apiLevel));
}
}
item = (AndroidTargetComboBoxItem)myCompileWith.getSelectedItem();
if (item != null) {
myTemplateState.put(ATTR_BUILD_API, item.apiLevel);
if (item.target != null) {
myTemplateState.put(ATTR_BUILD_API_STRING, item.target.getVersion().getApiString());
} else {
myTemplateState.put(ATTR_BUILD_API_STRING, Integer.toString(item.apiLevel));
}
}
if (myTemplateState.myModified.contains(ATTR_PACKAGE_NAME) &&
!computePackagePrefix(myTemplateState.getString(ATTR_PACKAGE_NAME)).equals(myPackagePrefix)) {
// The package prefix has been changed by the user. Save it for next time
myPackagePrefix = computePackagePrefix(myTemplateState.getString(ATTR_PACKAGE_NAME));
PropertiesComponent.getInstance().setValue(LAST_USED_CLASS_PREFIX_KEY, myPackagePrefix);
}
}
@Override
protected void deriveValues() {
updateDerivedValue(FormFactorUtils.ATTR_MODULE_NAME, myModuleName, new Callable<String>() {
@Override
public String call() {
return computeModuleName();
}
});
updateDerivedValue(ATTR_PACKAGE_NAME, myPackageName, new Callable<String>() {
@Override
public String call() {
return computePackageName();
}
});
if (!myTemplateState.myHidden.contains(ATTR_PROJECT_LOCATION)) {
updateDerivedValue(ATTR_PROJECT_LOCATION, myProjectLocation.getTextField(), new Callable<String>() {
@Override
public String call() {
return computeProjectLocation();
}
});
}
if (!myInitializedPackageNameText) {
myInitializedPackageNameText = true;
String maybePackagePrefix = PropertiesComponent.getInstance().getValue(LAST_USED_CLASS_PREFIX_KEY);
if (maybePackagePrefix != null && !maybePackagePrefix.isEmpty()) {
myPackagePrefix = maybePackagePrefix;
}
if ((myTemplateState.getString(ATTR_PACKAGE_NAME)).startsWith(SAMPLE_PACKAGE_PREFIX)) {
int length = SAMPLE_PACKAGE_PREFIX.length();
if (SAMPLE_PACKAGE_PREFIX.endsWith("")) {
length--;
}
myPackageName.select(0, length);
}
}
}
@Override
public boolean validate() {
((NewModuleWizardState)myTemplateState).updateParameters();
if (!super.validate()) {
return false;
}
setErrorHtml("");
if (!myTemplateState.myHidden.contains(ATTR_APP_TITLE)) {
String applicationName = myTemplateState.getString(ATTR_APP_TITLE);
if (applicationName.isEmpty()) {
setErrorHtml("Enter an application name (shown in launcher)");
return false;
}
if (Character.isLowerCase(applicationName.charAt(0))) {
setErrorHtml("The application name for most apps begins with an uppercase letter");
}
}
if (!myTemplateState.hasAttr(ATTR_PACKAGE_NAME)) {
myTemplateState.put(ATTR_PACKAGE_NAME, SAMPLE_PACKAGE_PREFIX + '.' + myTemplateState.getString(FormFactorUtils.ATTR_MODULE_NAME));
}
String packageName = myTemplateState.getString(ATTR_PACKAGE_NAME);
if (packageName.startsWith(SAMPLE_PACKAGE_PREFIX)) {
setErrorHtml(String.format("The prefix '%1$s' is meant as a placeholder and should " +
"not be used", SAMPLE_PACKAGE_PREFIX));
}
String moduleName = myTemplateState.getString(FormFactorUtils.ATTR_MODULE_NAME);
if (moduleName.isEmpty()) {
setErrorHtml("Please specify a module name.");
return false;
} else if (!isValidModuleName(moduleName)) {
setErrorHtml("Invalid module name.");
return false;
}
Integer minSdk = (Integer)myTemplateState.get(ATTR_MIN_API_LEVEL);
if (minSdk == null) {
setErrorHtml("Select a minimum SDK version");
return false;
}
if (myTemplateState.get(ATTR_BUILD_API) == null) {
setErrorHtml("Select a compile target");
return false;
}
if (myTemplateState.get(ATTR_TARGET_API) == null) {
setErrorHtml("Select a target SDK");
return false;
}
// TODO: Properly handle preview versions
int minLevel = myTemplateState.getInt(ATTR_MIN_API_LEVEL);
int buildLevel = myTemplateState.getInt(ATTR_BUILD_API);
int targetLevel = myTemplateState.getInt(ATTR_TARGET_API);
if (targetLevel < minLevel) {
setErrorHtml("The target SDK version should be at least as high as the minimum SDK version");
return false;
}
if (buildLevel < minLevel) {
setErrorHtml("The build target version should be at least as high as the minimum SDK version");
return false;
}
if (myTemplateState.hasAttr(ATTR_JAVA_VERSION)) {
String sourceVersion = myTemplateState.getString(ATTR_JAVA_VERSION);
if ("1.7".equals(sourceVersion)) {
if (buildLevel < 19) {
setErrorHtml("Using Java language level 7 requires compiling with API 19: Android 4.4 (KitKat)");
return false;
}
if (minLevel < 19) {
setErrorHtml("Note: With minSdkVersion less than 19, you cannot use try-with-resources, but other Java 7 language " +
"features are fine");
}
}
}
toggleVisibleOnApi(myFragmentCheckBox, 10, minLevel);
toggleVisibleOnApi(myNavigationDrawerCheckBox, 10, minLevel);
toggleVisibleOnApi(myActionBarCheckBox, 10, minLevel);
toggleVisibleOnApi(myGridLayoutCheckBox, 13, minLevel);
if (!myTemplateState.myHidden.contains(ATTR_PROJECT_LOCATION)) {
String projectLocation = myTemplateState.getString(ATTR_PROJECT_LOCATION);
if (projectLocation.isEmpty()) {
setErrorHtml("Please specify a project location");
return false;
}
// Check the individual components for not allowed characters.
File testFile = new File(projectLocation);
if ((File.separatorChar == '/' && projectLocation.contains("\\")) ||
(File.separatorChar == '\\' && projectLocation.contains("/"))) {
setErrorHtml("Your project location contains incorrect slashes ('\\' vs '/')");
return false;
}
while (testFile != null) {
String filename = testFile.getName();
if (ILLEGAL_CHARACTER_MATCHER.matchesAnyOf(filename)) {
char illegalChar = filename.charAt(ILLEGAL_CHARACTER_MATCHER.indexIn(filename));
setErrorHtml(String.format("Illegal character in project location path: '%c' in filename: %s", illegalChar, filename));
return false;
}
if (CharMatcher.WHITESPACE.matchesAnyOf(filename)) {
setErrorHtml("Your project location contains whitespace. This can cause problems on some platforms and is not recommended.");
}
if (!CharMatcher.ASCII.matchesAllOf(filename)) {
setErrorHtml("Your project location contains non-ASCII characters. This can cause problems on Windows. Proceed with caution.");
}
// Check that we can write to that location: make sure we can write into the first extant directory in the path.
if (!testFile.exists() && testFile.getParentFile() != null && testFile.getParentFile().exists()) {
if (!testFile.getParentFile().canWrite()) {
setErrorHtml(String.format("The path '%s' is not writeable. Please choose a new location.", testFile.getParentFile().getPath()));
return false;
}
}
testFile = testFile.getParentFile();
}
File file = new File(projectLocation);
if (file.isFile()) {
setErrorHtml("There must not already be a file at the project location");
return false;
} else if (file.isDirectory() && WizardUtils.listFiles(file).length > 0) {
setErrorHtml("A non-empty directory already exists at the specified project location. Existing files may be overwritten. Proceed with caution.");
}
if (file.getParent() == null) {
setErrorHtml("The project location can not be at the filesystem root");
return false;
}
if (file.getParentFile().exists() && !file.getParentFile().isDirectory()) {
setErrorHtml("The project location's parent directory must be a directory, not a plain file");
return false;
}
} else if (!isUniqueModuleName(moduleName)) {
// In this state, we've got a pre-existing project. Let's make sure we're not trying to overwrite an existing module
setErrorHtml(String.format(Locale.getDefault(), "Module %1$s already exists", moduleName));
return false;
}
return true;
}
/**
* Shows or hides a checkbox based on a given API level and the max API level for which it should be shown
* @param component The component to hide
* @param maxApiLevel the maximum API level for which the given component should be visible
* @param apiLevel the selected API level
*/
private static void toggleVisibleOnApi(JCheckBox component, int maxApiLevel, int apiLevel) {
component.setVisible(apiLevel <= maxApiLevel);
if (!component.isVisible()) {
component.setSelected(false);
}
}
@VisibleForTesting static String nameToPackage(String moduleName) {
moduleName = moduleName.replace('-', '_');
moduleName = moduleName.replaceAll("[^a-zA-Z0-9_]", "");
moduleName = moduleName.toLowerCase();
return moduleName;
}
@NotNull
@VisibleForTesting
String computePackageName() {
String moduleName = myTemplateState.getString(FormFactorUtils.ATTR_MODULE_NAME);
String projectName = myTemplateState.getString(ATTR_APP_TITLE);
projectName = nameToPackage(projectName);
moduleName = nameToPackage(moduleName);
if (!myPackagePrefix.endsWith("")) {
myPackagePrefix += '.';
}
if (moduleName.isEmpty() && projectName.isEmpty()) {
return "";
} else if (moduleName.isEmpty()) {
return myPackagePrefix + projectName;
} else if (projectName.isEmpty()) {
return myPackagePrefix + moduleName;
} else {
return myPackagePrefix + projectName + '.' + moduleName;
}
}
@NotNull
@VisibleForTesting
String computePackagePrefix(String packageName) {
String moduleName = myTemplateState.getString(FormFactorUtils.ATTR_MODULE_NAME);
String projectName = myTemplateState.getString(ATTR_APP_TITLE);
if (!projectName.isEmpty()) {
projectName = nameToPackage(projectName);
int nameIndex = packageName.lastIndexOf(projectName);
if (nameIndex != -1) {
return packageName.substring(0, nameIndex);
}
}
if (!moduleName.isEmpty()) {
moduleName = nameToPackage(moduleName);
int nameIndex = packageName.lastIndexOf(moduleName);
if (nameIndex != -1) {
return packageName.substring(0, nameIndex);
} else {
return packageName;
}
} else {
return packageName;
}
}
@NotNull
@VisibleForTesting
String computeModuleName() {
String name = myTemplateState.getBoolean(ATTR_IS_LIBRARY_MODULE) ? LIB_NAME : APP_NAME;
if (!isUniqueModuleName(name)) {
int i = 2;
while (!isUniqueModuleName(name + Integer.toString(i))) {
i++;
}
name += Integer.toString(i);
}
return name;
}
@VisibleForTesting
static boolean isValidModuleName(@NotNull String moduleName) {
if (!moduleName.replaceAll(INVALID_FILENAME_CHARS, "").equals(moduleName)) {
return false;
}
for (String s : Splitter.on('.').split(moduleName)) {
if (INVALID_MSFT_FILENAMES.contains(s.toLowerCase())) {
return false;
}
}
return true;
}
private boolean isUniqueModuleName(@NotNull String moduleName) {
if (myProject == null) {
return true;
}
// Check our modules
ModuleManager moduleManager = ModuleManager.getInstance(myProject);
for (Module m : moduleManager.getModules()) {
if (m.getName().equalsIgnoreCase(moduleName)) {
return false;
}
}
return true;
}
@NotNull
private String computeProjectLocation() {
String name = myTemplateState.getString(ATTR_APP_TITLE);
name = name.replaceAll("[^a-zA-Z0-9_\\-.]", "");
return new File(NewProjectWizardState.getProjectFileDirectory(), name)
.getAbsolutePath();
}
public void setWizardContext(@Nullable WizardContext wizardContext) {
myWizardContext = wizardContext;
}
public static boolean isJdk7Supported(@Nullable AndroidSdkData sdkData) {
if (sdkData != null) {
LocalPkgInfo info = sdkData.getLocalSdk().getPkgInfo(PkgType.PKG_PLATFORM_TOOLS);
IPkgDesc d = info == null ? null : info.getDesc();
if (d != null && d.hasFullRevision()) {
FullRevision fullRevision = d.getFullRevision();
assert fullRevision != null;
if (fullRevision.getMajor() >= 19) {
JavaSdk jdk = JavaSdk.getInstance();
Sdk sdk = ProjectJdkTable.getInstance().findMostRecentSdkOfType(jdk);
if (sdk != null) {
JavaSdkVersion version = jdk.getVersion(sdk);
if (version != null && version.isAtLeast(JavaSdkVersion.JDK_1_7)) {
return true;
}
}
}
}
}
return false;
}
@Nullable
private AndroidSdkData getSdkData() {
AndroidSdkData sdkData = null;
if (myWizardContext != null) {
final Sdk projectJdk = myWizardContext.getProjectJdk();
if (projectJdk != null) {
sdkData = AndroidSdkData.getSdkData(projectJdk);
}
}
if (sdkData == null) {
sdkData = AndroidSdkUtils.tryToChooseAndroidSdk();
}
return sdkData;
}
public static String languageLevelToString(LanguageLevel level) { // Performs the reverse of LanguageLevel.parse()
switch (level) {
case JDK_1_5: return "1.5";
case JDK_1_6: return "1.6";
case JDK_1_7: return "1.7";
default: return level.name().substring(4).replace('_','.'); // JDK_1_2 => 1.2
}
}
/**
* Find a unique filename to avoid conflicts.
*/
@VisibleForTesting
void computeUniqueProjectLocation() {
String projectLocation = myTemplateState.hasAttr(ATTR_PROJECT_LOCATION) ? myTemplateState.getString(ATTR_PROJECT_LOCATION)
: computeProjectLocation();
if (!projectLocation.isEmpty() && (myProject == null || !myProject.isInitialized())) {
File file = new File(projectLocation);
if (file.exists()) {
String appName = myTemplateState.getString(ATTR_APP_TITLE);
int i = 2;
while (file.exists()) {
myTemplateState.put(ATTR_APP_TITLE, String.format(Locale.getDefault(), "%s %d", appName, i));
file = new File(computeProjectLocation());
i++;
}
deriveValues();
}
}
}
private static Object getItemId(@NotNull AndroidVersion version) {
if (version.isPreview()) {
String codename = version.getCodename();
assert codename != null; // because isPreview()
return codename;
} else {
return version.getFeatureLevel();
}
}
public static class SourceLevelComboBoxItem extends ComboBoxItemWithApiTag {
public final LanguageLevel level;
public SourceLevelComboBoxItem(@NotNull LanguageLevel level) {
super(languageLevelToString(level), level.getPresentableText(), 1, 1);
this.level = level;
}
@Override
public String toString() {
return level.getPresentableText();
}
}
public static class AndroidTargetComboBoxItem extends ComboBoxItemWithApiTag {
public int apiLevel = -1;
public IAndroidTarget target = null;
public AndroidTargetComboBoxItem(@NotNull String label, int apiLevel) {
super(apiLevel, label, 1, 1);
this.apiLevel = apiLevel;
}
public AndroidTargetComboBoxItem(@NotNull IAndroidTarget target) {
super(getId(target), AndroidSdkUtils.getTargetLabel(target), 1, 1);
this.target = target;
apiLevel = target.getVersion().getFeatureLevel();
}
@Override
public String toString() {
return label;
}
@NotNull
private static Object getId(@NotNull IAndroidTarget target) {
final AndroidVersion version = target.getVersion();
return getItemId(version);
}
}
}