/*
* Copyright 2000-2010 JetBrains s.r.o.
*
* 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 org.jetbrains.android.facet;
import com.android.SdkConstants;
import com.android.annotations.NonNull;
import com.android.builder.model.*;
import com.android.prefs.AndroidLocation;
import com.android.sdklib.AndroidVersion;
import com.android.sdklib.IAndroidTarget;
import com.android.sdklib.internal.avd.AvdInfo;
import com.android.sdklib.internal.avd.AvdManager;
import com.android.tools.idea.AndroidPsiUtils;
import com.android.tools.idea.configurations.ConfigurationManager;
import com.android.tools.idea.gradle.GradleSyncState;
import com.android.tools.idea.gradle.IdeaAndroidProject;
import com.android.tools.idea.gradle.project.GradleSyncListener;
import com.android.tools.idea.gradle.util.Projects;
import com.android.tools.idea.model.AndroidModuleInfo;
import com.android.tools.idea.rendering.*;
import com.android.tools.idea.run.LaunchCompatibility;
import com.android.tools.idea.templates.TemplateManager;
import com.android.tools.idea.sdk.DefaultSdks;
import com.android.tools.idea.startup.AndroidStudioSpecificInitializer;
import com.android.utils.ILogger;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.Lists;
import com.intellij.CommonBundle;
import com.intellij.ProjectTopics;
import com.intellij.execution.ExecutionException;
import com.intellij.execution.configurations.GeneralCommandLine;
import com.intellij.execution.configurations.ParametersList;
import com.intellij.execution.process.ProcessHandler;
import com.intellij.execution.process.ProcessOutputTypes;
import com.intellij.facet.Facet;
import com.intellij.facet.FacetManager;
import com.intellij.facet.FacetTypeId;
import com.intellij.facet.FacetTypeRegistry;
import com.intellij.openapi.actionSystem.*;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.project.IndexNotReadyException;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.projectRoots.Sdk;
import com.intellij.openapi.projectRoots.SdkAdditionalData;
import com.intellij.openapi.projectRoots.SdkModificator;
import com.intellij.openapi.roots.*;
import com.intellij.openapi.startup.StartupManager;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.util.Computable;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.vfs.JarFileSystem;
import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VfsUtilCore;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.*;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.search.ProjectScope;
import com.intellij.psi.search.searches.ClassInheritorsSearch;
import com.intellij.psi.util.CachedValue;
import com.intellij.psi.util.CachedValueProvider;
import com.intellij.psi.util.CachedValuesManager;
import com.intellij.psi.util.PsiModificationTracker;
import com.intellij.util.ArrayUtilRt;
import com.intellij.util.Processor;
import com.intellij.util.ThreeState;
import com.intellij.util.containers.HashMap;
import com.intellij.util.containers.HashSet;
import com.intellij.util.messages.MessageBusConnection;
import com.intellij.util.xml.ConvertContext;
import com.intellij.util.xml.DomElement;
import org.jetbrains.android.compiler.AndroidAutogeneratorMode;
import org.jetbrains.android.compiler.AndroidCompileUtil;
import org.jetbrains.android.dom.manifest.Manifest;
import org.jetbrains.android.resourceManagers.LocalResourceManager;
import org.jetbrains.android.resourceManagers.ResourceManager;
import org.jetbrains.android.resourceManagers.SystemResourceManager;
import org.jetbrains.android.sdk.*;
import org.jetbrains.android.util.*;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.jps.android.model.impl.JpsAndroidModuleProperties;
import java.io.File;
import java.util.*;
import static org.jetbrains.android.util.AndroidUtils.SYSTEM_RESOURCE_PACKAGE;
/**
* @author yole
*/
public final class AndroidFacet extends Facet<AndroidFacetConfiguration> {
private static final Logger LOG = Logger.getInstance("#org.jetbrains.android.facet.AndroidFacet");
public static final FacetTypeId<AndroidFacet> ID = new FacetTypeId<AndroidFacet>("android");
public static final String NAME = "Android";
private static final Object APP_RESOURCES_LOCK = new Object();
private static final Object PROJECT_RESOURCES_LOCK = new Object();
private static final Object MODULE_RESOURCES_LOCK = new Object();
private static boolean ourDynamicTemplateMenuCreated;
private AvdManager myAvdManager = null;
private AndroidSdkData mySdkData;
private SystemResourceManager myPublicSystemResourceManager;
private SystemResourceManager myFullSystemResourceManager;
private LocalResourceManager myLocalResourceManager;
private final Map<String, Map<String, SmartPsiElementPointer<PsiClass>>> myInitialClassMaps =
new HashMap<String, Map<String, SmartPsiElementPointer<PsiClass>>>();
private Map<String, CachedValue<Map<String, PsiClass>>> myClassMaps = new HashMap<String, CachedValue<Map<String, PsiClass>>>();
private final Object myClassMapLock = new Object();
private final Set<AndroidAutogeneratorMode> myDirtyModes = EnumSet.noneOf(AndroidAutogeneratorMode.class);
private final Map<AndroidAutogeneratorMode, Set<String>> myAutogeneratedFiles = new HashMap<AndroidAutogeneratorMode, Set<String>>();
private volatile boolean myAutogenerationEnabled = false;
private ConfigurationManager myConfigurationManager;
private LocalResourceRepository myModuleResources;
private AppResourceRepository myAppResources;
private ProjectResourceRepository myProjectResources;
private IdeaAndroidProject myIdeaAndroidProject;
private final ResourceFolderManager myFolderManager = new ResourceFolderManager(this);
private SourceProvider myMainSourceSet;
private IdeaSourceProvider myMainIdeaSourceSet;
private final AndroidModuleInfo myAndroidModuleInfo = AndroidModuleInfo.create(this);
public AndroidFacet(@NotNull Module module, String name, @NotNull AndroidFacetConfiguration configuration) {
//noinspection ConstantConditions
super(getFacetType(), module, name, configuration, null);
configuration.setFacet(this);
}
public boolean isAutogenerationEnabled() {
return myAutogenerationEnabled;
}
public boolean isGradleProject() {
return !getProperties().ALLOW_USER_CONFIGURATION;
}
public boolean isLibraryProject() {
return getProperties().LIBRARY_PROJECT;
}
public void setLibraryProject(boolean library) {
getProperties().LIBRARY_PROJECT = library;
}
/**
* Returns the main source provider for the project. For non-Gradle projects it returns a {@link SourceProvider} wrapper
* which provides information about the old project.
*/
@NotNull
public SourceProvider getMainSourceProvider() {
if (myIdeaAndroidProject != null) {
return myIdeaAndroidProject.getDelegate().getDefaultConfig().getSourceProvider();
} else {
if (myMainSourceSet == null) {
myMainSourceSet = new LegacySourceProvider();
}
return myMainSourceSet;
}
}
@NotNull
public IdeaSourceProvider getMainIdeaSourceProvider() {
if (!isGradleProject()) {
if (myMainIdeaSourceSet == null) {
myMainIdeaSourceSet = IdeaSourceProvider.create(this);
}
} else {
SourceProvider mainSourceSet = getMainSourceProvider();
if (myMainIdeaSourceSet == null || mainSourceSet != myMainSourceSet) {
myMainIdeaSourceSet = IdeaSourceProvider.create(mainSourceSet);
}
}
return myMainIdeaSourceSet;
}
@NotNull
public List<IdeaSourceProvider> getMainIdeaTestSourceProviders() {
if (!isGradleProject()) {
return Collections.emptyList();
}
List<IdeaSourceProvider> providers = Lists.newArrayList();
for (SourceProviderContainer container : myIdeaAndroidProject.getDelegate().getDefaultConfig().getExtraSourceProviders()) {
if (AndroidProject.ARTIFACT_ANDROID_TEST.equals(container.getArtifactName())) {
providers.add(IdeaSourceProvider.create(container.getSourceProvider()));
}
}
return providers;
}
/**
* Returns the source provider for the current build type, which will never be null for a Gradle based
* Android project, and always null for a legacy Android project
*
* @return the build type source set or null
*/
@Nullable
public SourceProvider getBuildTypeSourceProvider() {
if (myIdeaAndroidProject != null) {
Variant selectedVariant = myIdeaAndroidProject.getSelectedVariant();
BuildTypeContainer buildType = myIdeaAndroidProject.findBuildType(selectedVariant.getBuildType());
assert buildType != null;
return buildType.getSourceProvider();
} else {
return null;
}
}
/**
* Like {@link #getBuildTypeSourceProvider()} but typed for internal IntelliJ usage with
* {@link VirtualFile} instead of {@link File} references
*
* @return the build type source set or null
*/
@Nullable
public IdeaSourceProvider getIdeaBuildTypeSourceProvider() {
SourceProvider sourceProvider = getBuildTypeSourceProvider();
if (sourceProvider != null) {
return IdeaSourceProvider.create(sourceProvider);
} else {
return null;
}
}
@NotNull
public List<IdeaSourceProvider> getIdeaBuildTypeTestSourceProvider() {
if (myIdeaAndroidProject == null) {
return Collections.emptyList();
}
List<IdeaSourceProvider> providers = Lists.newArrayList();
Variant selectedVariant = myIdeaAndroidProject.getSelectedVariant();
BuildTypeContainer buildType = myIdeaAndroidProject.findBuildType(selectedVariant.getBuildType());
assert buildType != null;
for (SourceProviderContainer container : buildType.getExtraSourceProviders()) {
if (AndroidProject.ARTIFACT_ANDROID_TEST.equals(container.getArtifactName())) {
providers.add(IdeaSourceProvider.create(container.getSourceProvider()));
}
}
return providers;
}
public ResourceFolderManager getResourceFolderManager() {
return myFolderManager;
}
/**
* Returns all resource directories, in the overlay order
*
* @return a list of all resource directories
*/
@NotNull
public List<VirtualFile> getAllResourceDirectories() {
return myFolderManager.getFolders();
}
/**
* Returns the name of the build type
*/
@Nullable
public String getBuildTypeName() {
return myIdeaAndroidProject != null ? myIdeaAndroidProject.getSelectedVariant().getName() : null;
}
/**
* Returns the source providers for the available flavors, which will never be null for a Gradle based
* Android project, and always null for a legacy Android project
*
* @return the flavor source providers or null in legacy projects
*/
@Nullable
public List<SourceProvider> getFlavorSourceProviders() {
if (myIdeaAndroidProject != null) {
Variant selectedVariant = myIdeaAndroidProject.getSelectedVariant();
List<String> productFlavors = selectedVariant.getProductFlavors();
List<SourceProvider> providers = Lists.newArrayList();
for (String flavor : productFlavors) {
ProductFlavorContainer productFlavor = myIdeaAndroidProject.findProductFlavor(flavor);
assert productFlavor != null;
providers.add(productFlavor.getSourceProvider());
}
return providers;
} else {
return null;
}
}
/**
* Like {@link #getFlavorSourceProviders()} but typed for internal IntelliJ usage with
* {@link VirtualFile} instead of {@link File} references
*
* @return the flavor source providers or null in legacy projects
*/
@Nullable
public List<IdeaSourceProvider> getIdeaFlavorSourceProviders() {
List<SourceProvider> sourceProviders = getFlavorSourceProviders();
if (sourceProviders != null) {
List<IdeaSourceProvider> ideaSourceProviders = Lists.newArrayListWithExpectedSize(sourceProviders.size());
for (SourceProvider provider : sourceProviders) {
ideaSourceProviders.add(IdeaSourceProvider.create(provider));
}
return ideaSourceProviders;
} else {
return null;
}
}
@NotNull
public List<IdeaSourceProvider> getIdeaFlavorTestSourceProviders() {
if (myIdeaAndroidProject == null) {
return Collections.emptyList();
}
Variant selectedVariant = myIdeaAndroidProject.getSelectedVariant();
List<String> productFlavors = selectedVariant.getProductFlavors();
List<IdeaSourceProvider> providers = Lists.newArrayList();
for (String flavor : productFlavors) {
ProductFlavorContainer productFlavor = myIdeaAndroidProject.findProductFlavor(flavor);
assert productFlavor != null;
for (SourceProviderContainer container : productFlavor.getExtraSourceProviders()) {
if (AndroidProject.ARTIFACT_ANDROID_TEST.equals(container.getArtifactName())) {
providers.add(IdeaSourceProvider.create(container.getSourceProvider()));
}
}
}
return providers;
}
/**
* Returns the source provider specific to the flavor combination, if any.
*
* @return the source provider or null
*/
@Nullable
public SourceProvider getMultiFlavorSourceProvider() {
if (myIdeaAndroidProject != null) {
Variant selectedVariant = myIdeaAndroidProject.getSelectedVariant();
AndroidArtifact mainArtifact = selectedVariant.getMainArtifact();
SourceProvider provider = mainArtifact.getMultiFlavorSourceProvider();
if (provider != null) {
return provider;
}
}
return null;
}
/**
* Like {@link #getMultiFlavorSourceProvider()} but typed for internal IntelliJ usage with
* {@link VirtualFile} instead of {@link File} references
*
* @return the flavor source providers or null in legacy projects
*/
@Nullable
public IdeaSourceProvider getIdeaMultiFlavorSourceProvider() {
SourceProvider provider = getMultiFlavorSourceProvider();
if (provider != null) {
return IdeaSourceProvider.create(provider);
}
return null;
}
/**
* Returns the source provider specific to the variant, if any.
*
* @return the source provider or null
*/
@Nullable
public SourceProvider getVariantSourceProvider() {
if (myIdeaAndroidProject != null) {
Variant selectedVariant = myIdeaAndroidProject.getSelectedVariant();
AndroidArtifact mainArtifact = selectedVariant.getMainArtifact();
SourceProvider provider = mainArtifact.getVariantSourceProvider();
if (provider != null) {
return provider;
}
}
return null;
}
/**
* Like {@link #getVariantSourceProvider()} but typed for internal IntelliJ usage with
* {@link VirtualFile} instead of {@link File} references
*
* @return the flavor source providers or null in legacy projects
*/
@Nullable
public IdeaSourceProvider getIdeaVariantSourceProvider() {
SourceProvider provider = getVariantSourceProvider();
if (provider != null) {
return IdeaSourceProvider.create(provider);
}
return null;
}
/**
* This returns the primary resource directory; the default location to place
* newly created resources etc. This method is marked deprecated since we should
* be gradually adding in UI to allow users to choose specific resource folders
* among the available flavors (see {@link #getFlavorSourceProviders()} etc).
*
* @return the primary resource dir, if any
*/
@Deprecated
@Nullable
public VirtualFile getPrimaryResourceDir() {
List<VirtualFile> dirs = getAllResourceDirectories();
if (!dirs.isEmpty()) {
return dirs.get(0);
}
return null;
}
public boolean isGeneratedFileRemoved(@NotNull AndroidAutogeneratorMode mode) {
synchronized (myAutogeneratedFiles) {
final Set<String> filePaths = myAutogeneratedFiles.get(mode);
if (filePaths != null) {
for (String path : filePaths) {
final VirtualFile file = LocalFileSystem.getInstance().findFileByPath(path);
if (file == null) {
return true;
}
}
}
}
return false;
}
public void clearAutogeneratedFiles(@NotNull AndroidAutogeneratorMode mode) {
synchronized (myAutogeneratedFiles) {
final Set<String> set = myAutogeneratedFiles.get(mode);
if (set != null) {
set.clear();
}
}
}
public void markFileAutogenerated(@NotNull AndroidAutogeneratorMode mode, @NotNull VirtualFile file) {
synchronized (myAutogeneratedFiles) {
Set<String> set = myAutogeneratedFiles.get(mode);
if (set == null) {
set = new HashSet<String>();
myAutogeneratedFiles.put(mode, set);
}
set.add(file.getPath());
}
}
@NotNull
public Set<String> getAutogeneratedFiles(@NotNull AndroidAutogeneratorMode mode) {
synchronized (myAutogeneratedFiles) {
final Set<String> set = myAutogeneratedFiles.get(mode);
return set != null ? new HashSet<String>(set) : Collections.<String>emptySet();
}
}
private void activateSourceAutogenerating() {
myAutogenerationEnabled = true;
}
public void androidPlatformChanged() {
myAvdManager = null;
myLocalResourceManager = null;
myPublicSystemResourceManager = null;
myInitialClassMaps.clear();
}
@NotNull
public AvdInfo[] getAllAvds() {
AvdManager manager = getAvdManagerSilently();
if (manager != null) {
if (reloadAvds(manager)) {
return manager.getAllAvds();
}
}
return new AvdInfo[0];
}
private boolean reloadAvds(AvdManager manager) {
try {
MessageBuildingSdkLog log = new MessageBuildingSdkLog();
manager.reloadAvds(log);
if (!log.getErrorMessage().isEmpty()) {
Messages
.showErrorDialog(getModule().getProject(), AndroidBundle.message("cant.load.avds.error.prefix") + ' ' + log.getErrorMessage(),
CommonBundle.getErrorTitle());
}
return true;
}
catch (AndroidLocation.AndroidLocationException e) {
Messages.showErrorDialog(getModule().getProject(), AndroidBundle.message("cant.load.avds.error"), CommonBundle.getErrorTitle());
}
return false;
}
public AvdInfo[] getValidCompatibleAvds() {
AvdManager manager = getAvdManagerSilently();
List<AvdInfo> result = new ArrayList<AvdInfo>();
if (manager != null && reloadAvds(manager)) {
addCompatibleAvds(result, manager.getValidAvds());
}
return result.toArray(new AvdInfo[result.size()]);
}
private AvdInfo[] addCompatibleAvds(List<AvdInfo> to, @NotNull AvdInfo[] from) {
AndroidVersion minSdk = AndroidModuleInfo.get(this).getRuntimeMinSdkVersion();
AndroidPlatform platform = getConfiguration().getAndroidPlatform();
if (platform == null) {
LOG.error("Android Platform not set for module: " + getModule().getName());
return new AvdInfo[0];
}
for (AvdInfo avd : from) {
IAndroidTarget avdTarget = avd.getTarget();
if (avdTarget == null || LaunchCompatibility.canRunOnAvd(minSdk, platform.getTarget(), avdTarget).isCompatible() != ThreeState.NO) {
to.add(avd);
}
}
return to.toArray(new AvdInfo[to.size()]);
}
@Nullable
public AvdManager getAvdManagerSilently() {
try {
return getAvdManager(new AvdManagerLog());
}
catch (AvdsNotSupportedException ignored) {
}
catch (AndroidLocation.AndroidLocationException ignored) {
}
return null;
}
@NotNull
public AvdManager getAvdManager(ILogger log) throws AvdsNotSupportedException, AndroidLocation.AndroidLocationException {
if (myAvdManager == null) {
AndroidSdkData sdkData = getSdkData();
if (sdkData != null) {
myAvdManager = AvdManager.getInstance(sdkData.getLocalSdk(), log);
}
else {
throw new AvdsNotSupportedException();
}
}
return myAvdManager;
}
@Nullable
public AndroidSdkData getSdkData() {
if (mySdkData == null) {
AndroidPlatform platform = getConfiguration().getAndroidPlatform();
mySdkData = platform != null ? platform.getSdkData() : null;
}
return mySdkData;
}
/**
* Returns a target from a hash that was generated by {@link IAndroidTarget#hashString()}.
*
* @param hash the {@link IAndroidTarget} hash string.
* @return The matching {@link IAndroidTarget} or null.
*/
@Nullable
public IAndroidTarget getTargetFromHashString(@NotNull String hash) {
AndroidSdkData sdkData = getSdkData();
return sdkData != null ? sdkData.getLocalSdk().getTargetFromHashString(hash) : null;
}
public void launchEmulator(@Nullable final String avdName, @NotNull final String commands, @NotNull final ProcessHandler handler) {
File sdkLocation = null;
if (Projects.isGradleProject(getModule().getProject()) && AndroidStudioSpecificInitializer.isAndroidStudio()) {
sdkLocation = DefaultSdks.getDefaultAndroidHome();
}
else {
AndroidPlatform platform = getConfiguration().getAndroidPlatform();
if (platform != null) {
sdkLocation = platform.getSdkData().getLocation();
}
}
if (sdkLocation != null) {
File emulatorPath = new File(sdkLocation, AndroidCommonUtils.toolPath(SdkConstants.FN_EMULATOR));
final GeneralCommandLine commandLine = new GeneralCommandLine();
commandLine.setExePath(emulatorPath.getPath());
if (avdName != null) {
commandLine.addParameter("-avd");
commandLine.addParameter(avdName);
}
String[] params = ParametersList.parse(commands);
for (String s : params) {
if (!s.isEmpty()) {
commandLine.addParameter(s);
}
}
handler.notifyTextAvailable(commandLine.getCommandLineString() + '\n', ProcessOutputTypes.STDOUT);
ApplicationManager.getApplication().executeOnPooledThread(new Runnable() {
@Override
public void run() {
try {
AndroidUtils.executeCommand(commandLine, new OutputProcessor() {
@Override
public void onTextAvailable(@NotNull String text) {
handler.notifyTextAvailable(text, ProcessOutputTypes.STDOUT);
}
}, WaitingStrategies.WaitForTime.getInstance(5000));
}
catch (ExecutionException e) {
final String stackTrace = AndroidCommonUtils.getStackTrace(e);
handler.notifyTextAvailable(stackTrace, ProcessOutputTypes.STDERR);
}
}
});
}
}
public static void createDynamicTemplateMenu() {
if (ourDynamicTemplateMenuCreated) {
return;
}
ourDynamicTemplateMenuCreated = true;
DefaultActionGroup newGroup = (DefaultActionGroup)ActionManager.getInstance().getAction("NewGroup");
newGroup.addSeparator();
final ActionGroup menu = TemplateManager.getInstance().getTemplateCreationMenu(null);
if (menu != null) {
newGroup.add(menu, new Constraints(Anchor.AFTER, "NewFromTemplate"));
}
}
@Override
public void initFacet() {
StartupManager.getInstance(getModule().getProject()).runWhenProjectIsInitialized(new Runnable() {
@Override
public void run() {
AndroidResourceFilesListener.notifyFacetInitialized(AndroidFacet.this);
if (ApplicationManager.getApplication().isUnitTestMode()) {
return;
}
addResourceFolderToSdkRootsIfNecessary();
ApplicationManager.getApplication().executeOnPooledThread(new Runnable() {
@Override
public void run() {
Module module = getModule();
Project project = module.getProject();
if (project.isDisposed()) {
return;
}
if (true) {
AndroidCompileUtil.generate(module, AndroidAutogeneratorMode.AAPT);
}
AndroidCompileUtil.generate(module, AndroidAutogeneratorMode.AIDL);
AndroidCompileUtil.generate(module, AndroidAutogeneratorMode.RENDERSCRIPT);
AndroidCompileUtil.generate(module, AndroidAutogeneratorMode.BUILDCONFIG);
activateSourceAutogenerating();
}
});
}
});
getModule().getMessageBus().connect(this).subscribe(ProjectTopics.PROJECT_ROOTS, new ModuleRootAdapter() {
private Sdk myPrevSdk;
@Override
public void rootsChanged(final ModuleRootEvent event) {
ApplicationManager.getApplication().invokeLater(new Runnable() {
@Override
public void run() {
if (isDisposed()) {
return;
}
final ModuleRootManager rootManager = ModuleRootManager.getInstance(getModule());
final Sdk newSdk = rootManager.getSdk();
if (newSdk != null && newSdk.getSdkType() instanceof AndroidSdkType && !newSdk.equals(myPrevSdk)) {
androidPlatformChanged();
synchronized (myDirtyModes) {
myDirtyModes.addAll(Arrays.asList(AndroidAutogeneratorMode.values()));
}
}
myPrevSdk = newSdk;
}
});
}
});
createDynamicTemplateMenu();
}
private void addResourceFolderToSdkRootsIfNecessary() {
final Sdk sdk = ModuleRootManager.getInstance(getModule()).getSdk();
if (sdk == null || !(sdk.getSdkType() instanceof AndroidSdkType)) {
return;
}
final SdkAdditionalData data = sdk.getSdkAdditionalData();
if (!(data instanceof AndroidSdkAdditionalData)) {
return;
}
final AndroidPlatform platform = ((AndroidSdkAdditionalData)data).getAndroidPlatform();
if (platform == null) {
return;
}
final String resFolderPath = platform.getTarget().getPath(IAndroidTarget.RESOURCES);
if (resFolderPath == null) {
return;
}
final List<VirtualFile> filesToAdd = new ArrayList<VirtualFile>();
final VirtualFile resFolder = LocalFileSystem.getInstance().findFileByPath(FileUtil.toSystemIndependentName(resFolderPath));
if (resFolder != null) {
filesToAdd.add(resFolder);
}
if (platform.needToAddAnnotationsJarToClasspath()) {
final String sdkHomePath = FileUtil.toSystemIndependentName(platform.getSdkData().getLocation().getPath());
final VirtualFile annotationsJar = JarFileSystem.getInstance().findFileByPath(
sdkHomePath + AndroidCommonUtils.ANNOTATIONS_JAR_RELATIVE_PATH + JarFileSystem.JAR_SEPARATOR);
if (annotationsJar != null) {
filesToAdd.add(annotationsJar);
}
}
addFilesToSdkIfNecessary(sdk, filesToAdd);
}
private static void addFilesToSdkIfNecessary(@NotNull Sdk sdk, @NotNull Collection<VirtualFile> files) {
final List<VirtualFile> newFiles = new ArrayList<VirtualFile>(files);
newFiles.removeAll(Arrays.asList(sdk.getRootProvider().getFiles(OrderRootType.CLASSES)));
if (newFiles.size() > 0) {
final SdkModificator modificator = sdk.getSdkModificator();
for (VirtualFile file : newFiles) {
modificator.addRoot(file, OrderRootType.CLASSES);
}
modificator.commitChanges();
}
}
@Override
public void disposeFacet() {
if (myConfigurationManager != null) {
myConfigurationManager.dispose();
}
}
@Nullable
public static AndroidFacet getInstance(@NotNull Module module) {
return FacetManager.getInstance(module).getFacetByType(ID);
}
@Nullable
public static AndroidFacet getInstance(@NotNull ConvertContext context) {
Module module = context.getModule();
return module != null ? getInstance(module) : null;
}
@Nullable
public static AndroidFacet getInstance(@NotNull final PsiElement element) {
Module module = AndroidPsiUtils.getModuleSafely(element);
if (module == null) return null;
return getInstance(module);
}
@Nullable
public static AndroidFacet getInstance(@NotNull DomElement element) {
Module module = element.getModule();
if (module == null) return null;
return getInstance(module);
}
@Nullable
public ResourceManager getResourceManager(@Nullable String resourcePackage) {
return getResourceManager(resourcePackage, null);
}
@Nullable
public ResourceManager getResourceManager(@Nullable String resourcePackage, @Nullable PsiElement contextElement) {
if (SYSTEM_RESOURCE_PACKAGE.equals(resourcePackage)) {
return getSystemResourceManager();
}
if (contextElement != null && isInAndroidSdk(contextElement)) {
return getSystemResourceManager();
}
return getLocalResourceManager();
}
private static boolean isInAndroidSdk(@NonNull PsiElement element) {
final PsiFile file = element.getContainingFile();
if (file == null) {
return false;
}
final VirtualFile vFile = file.getVirtualFile();
if (vFile == null) {
return false;
}
final ProjectFileIndex projectFileIndex = ProjectRootManager.getInstance(element.getProject()).getFileIndex();
final List<OrderEntry> entries = projectFileIndex.getOrderEntriesForFile(vFile);
for (OrderEntry entry : entries) {
if (entry instanceof JdkOrderEntry) {
final Sdk sdk = ((JdkOrderEntry)entry).getJdk();
if (sdk != null && sdk.getSdkType() instanceof AndroidSdkType) {
return true;
}
}
}
return false;
}
@NotNull
public LocalResourceManager getLocalResourceManager() {
if (myLocalResourceManager == null) {
myLocalResourceManager = new LocalResourceManager(this);
}
return myLocalResourceManager;
}
@Nullable
public SystemResourceManager getSystemResourceManager() {
return getSystemResourceManager(true);
}
@Nullable
public SystemResourceManager getSystemResourceManager(boolean publicOnly) {
if (publicOnly) {
if (myPublicSystemResourceManager == null) {
AndroidPlatform platform = getConfiguration().getAndroidPlatform();
if (platform != null) {
myPublicSystemResourceManager = new SystemResourceManager(this.getModule().getProject(), platform, true);
}
}
return myPublicSystemResourceManager;
}
if (myFullSystemResourceManager == null) {
AndroidPlatform platform = getConfiguration().getAndroidPlatform();
if (platform != null) {
myFullSystemResourceManager = new SystemResourceManager(this.getModule().getProject(), platform, false);
}
}
return myFullSystemResourceManager;
}
@Nullable
public Manifest getManifest() {
File manifestIoFile = getMainSourceProvider().getManifestFile();
final VirtualFile manifestFile = LocalFileSystem.getInstance().findFileByIoFile(manifestIoFile);
if (manifestFile == null) return null;
return AndroidUtils.loadDomElement(getModule(), manifestFile, Manifest.class);
}
public static AndroidFacetType getFacetType() {
return (AndroidFacetType)FacetTypeRegistry.getInstance().findFacetType(ID);
}
// todo: correctly support classes from external non-platform jars
@NotNull
public Map<String, PsiClass> getClassMap(@NotNull final String className, @NotNull final ClassMapConstructor constructor) {
synchronized (myClassMapLock) {
CachedValue<Map<String, PsiClass>> value = myClassMaps.get(className);
if (value == null) {
value = CachedValuesManager.getManager(getModule().getProject()).createCachedValue(
new CachedValueProvider<Map<String, PsiClass>>() {
@Nullable
@Override
public Result<Map<String, PsiClass>> compute() {
final Map<String, PsiClass> map = computeClassMap(className, constructor);
return Result.create(map, PsiModificationTracker.JAVA_STRUCTURE_MODIFICATION_COUNT);
}
}, false);
myClassMaps.put(className, value);
}
return value.getValue();
}
}
@NotNull
private Map<String, PsiClass> computeClassMap(@NotNull String className, @NotNull ClassMapConstructor constructor) {
Map<String, SmartPsiElementPointer<PsiClass>> classMap = getInitialClassMap(className, constructor, false);
final Map<String, PsiClass> result = new HashMap<String, PsiClass>();
boolean shouldRebuildInitialMap = false;
for (final String key : classMap.keySet()) {
final SmartPsiElementPointer<PsiClass> pointer = classMap.get(key);
if (!isUpToDate(pointer, key, constructor)) {
shouldRebuildInitialMap = true;
break;
}
final PsiClass aClass = pointer.getElement();
if (aClass != null) {
result.put(key, aClass);
}
}
if (shouldRebuildInitialMap) {
result.clear();
classMap = getInitialClassMap(className, constructor, true);
for (final String key : classMap.keySet()) {
final SmartPsiElementPointer<PsiClass> pointer = classMap.get(key);
final PsiClass aClass = pointer.getElement();
if (aClass != null) {
result.put(key, aClass);
}
}
}
final Project project = getModule().getProject();
fillMap(className, constructor, ProjectScope.getProjectScope(project), result, false);
return result;
}
private static boolean isUpToDate(SmartPsiElementPointer<PsiClass> pointer, String tagName, ClassMapConstructor constructor) {
final PsiClass aClass = pointer.getElement();
if (aClass == null) {
return false;
}
final String[] tagNames = constructor.getTagNamesByClass(aClass);
return ArrayUtilRt.find(tagNames, tagName) >= 0;
}
@NotNull
private Map<String, SmartPsiElementPointer<PsiClass>> getInitialClassMap(@NotNull String className,
@NotNull ClassMapConstructor constructor,
boolean forceRebuild) {
Map<String, SmartPsiElementPointer<PsiClass>> viewClassMap = myInitialClassMaps.get(className);
if (viewClassMap != null && !forceRebuild) return viewClassMap;
final HashMap<String, PsiClass> map = new HashMap<String, PsiClass>();
if (fillMap(className, constructor, getModule().getModuleWithDependenciesAndLibrariesScope(true), map, true)) {
viewClassMap = new HashMap<String, SmartPsiElementPointer<PsiClass>>(map.size());
final SmartPointerManager manager = SmartPointerManager.getInstance(getModule().getProject());
for (Map.Entry<String, PsiClass> entry : map.entrySet()) {
viewClassMap.put(entry.getKey(), manager.createSmartPsiElementPointer(entry.getValue()));
}
myInitialClassMaps.put(className, viewClassMap);
}
return viewClassMap != null
? viewClassMap
: Collections.<String, SmartPsiElementPointer<PsiClass>>emptyMap();
}
private boolean fillMap(@NotNull final String className,
@NotNull final ClassMapConstructor constructor,
GlobalSearchScope scope,
final Map<String, PsiClass> map,
final boolean libClassesOnly) {
final JavaPsiFacade facade = JavaPsiFacade.getInstance(getModule().getProject());
final PsiClass baseClass = ApplicationManager.getApplication().runReadAction(new Computable<PsiClass>() {
@Override
@Nullable
public PsiClass compute() {
return facade.findClass(className, getModule().getModuleWithDependenciesAndLibrariesScope(true));
}
});
if (baseClass != null) {
String[] baseClassTagNames = constructor.getTagNamesByClass(baseClass);
for (String tagName : baseClassTagNames) {
map.put(tagName, baseClass);
}
try {
ClassInheritorsSearch.search(baseClass, scope, true).forEach(new Processor<PsiClass>() {
@Override
public boolean process(PsiClass c) {
if (libClassesOnly && c.getManager().isInProject(c)) {
return true;
}
String[] tagNames = constructor.getTagNamesByClass(c);
for (String tagName : tagNames) {
map.put(tagName, c);
}
return true;
}
});
}
catch (IndexNotReadyException e) {
LOG.info(e);
return false;
}
}
return map.size() > 0;
}
public void scheduleSourceRegenerating(@NotNull final AndroidAutogeneratorMode mode) {
synchronized (myDirtyModes) {
myDirtyModes.add(mode);
}
}
public boolean cleanRegeneratingState(@NotNull final AndroidAutogeneratorMode mode) {
synchronized (myDirtyModes) {
return myDirtyModes.remove(mode);
}
}
@NotNull
public ConfigurationManager getConfigurationManager() {
return getConfigurationManager(true);
}
@Contract("true -> !null")
@Nullable
public ConfigurationManager getConfigurationManager(boolean createIfNecessary) {
if (myConfigurationManager == null && createIfNecessary) {
myConfigurationManager = ConfigurationManager.create(getModule());
Disposer.register(this, myConfigurationManager);
}
return myConfigurationManager;
}
@Contract("true -> !null")
@Nullable
public AppResourceRepository getAppResources(boolean createIfNecessary) {
synchronized (APP_RESOURCES_LOCK) {
if (myAppResources == null && createIfNecessary) {
myAppResources = AppResourceRepository.create(this);
}
return myAppResources;
}
}
@Contract("true -> !null")
@Nullable
public ProjectResourceRepository getProjectResources(boolean createIfNecessary) {
synchronized (PROJECT_RESOURCES_LOCK) {
if (myProjectResources == null && createIfNecessary) {
myProjectResources = ProjectResourceRepository.create(this);
}
return myProjectResources;
}
}
@Contract("true -> !null")
@Nullable
public LocalResourceRepository getModuleResources(boolean createIfNecessary) {
synchronized (MODULE_RESOURCES_LOCK) {
if (myModuleResources == null && createIfNecessary) {
myModuleResources = ModuleResourceRepository.create(this);
}
return myModuleResources;
}
}
public void refreshResources() {
myModuleResources = null;
myProjectResources = null;
myAppResources = null;
myConfigurationManager.getResolverCache().reset();
ResourceFolderRegistry.reset();
FileResourceRepository.reset();
}
@NotNull
public JpsAndroidModuleProperties getProperties() {
JpsAndroidModuleProperties state = getConfiguration().getState();
assert state != null;
return state;
}
/**
* Associates the given Android-Gradle project to this facet.
*
* @param project the new project.
*/
public void setIdeaAndroidProject(@Nullable IdeaAndroidProject project) {
myIdeaAndroidProject = project;
}
public void addListener(@NotNull GradleSyncListener listener) {
Module module = getModule();
MessageBusConnection connection = module.getProject().getMessageBus().connect(module);
connection.subscribe(GradleSyncState.GRADLE_SYNC_TOPIC, listener);
}
/**
* @return the Android-Gradle project associated to this facet.
*/
@Nullable
public IdeaAndroidProject getIdeaAndroidProject() {
return myIdeaAndroidProject;
}
public void syncSelectedVariant() {
if (myIdeaAndroidProject != null) {
Variant variant = myIdeaAndroidProject.getSelectedVariant();
JpsAndroidModuleProperties state = getProperties();
state.SELECTED_BUILD_VARIANT = variant.getName();
AndroidArtifact mainArtifact = variant.getMainArtifact();
AndroidArtifact testArtifact = myIdeaAndroidProject.findInstrumentationTestArtifactInSelectedVariant();
updateGradleTaskNames(state, mainArtifact, testArtifact);
}
}
@VisibleForTesting
static void updateGradleTaskNames(@NotNull JpsAndroidModuleProperties state,
@NotNull AndroidArtifact mainArtifact,
@Nullable AndroidArtifact testArtifact) {
state.ASSEMBLE_TASK_NAME = mainArtifact.getAssembleTaskName();
state.COMPILE_JAVA_TASK_NAME = mainArtifact.getJavaCompileTaskName();
if (testArtifact != null) {
state.TEST_SOURCE_GEN_TASK_NAME = testArtifact.getSourceGenTaskName();
state.ASSEMBLE_TEST_TASK_NAME = testArtifact.getAssembleTaskName();
}
else {
state.TEST_SOURCE_GEN_TASK_NAME = "";
state.ASSEMBLE_TEST_TASK_NAME = "";
}
state.SOURCE_GEN_TASK_NAME = mainArtifact.getSourceGenTaskName();
}
@NotNull
public AndroidModuleInfo getAndroidModuleInfo() {
return myAndroidModuleInfo;
}
// Compatibility bridge for old (non-Gradle) projects. Also used in Gradle projects before the module has been synced.
private class LegacySourceProvider implements SourceProvider {
@NonNull
@Override
public String getName() {
return "main";
}
@NonNull
@Override
public File getManifestFile() {
Module module = getModule();
VirtualFile manifestFile = AndroidRootUtil.getFileByRelativeModulePath(module, getProperties().MANIFEST_FILE_RELATIVE_PATH, true);
if (manifestFile == null) {
VirtualFile root = !isGradleProject() ? AndroidRootUtil.getMainContentRoot(AndroidFacet.this) : null;
if (root != null) {
return new File(VfsUtilCore.virtualToIoFile(root), SdkConstants.ANDROID_MANIFEST_XML);
} else {
return new File(SdkConstants.ANDROID_MANIFEST_XML);
}
} else {
return VfsUtilCore.virtualToIoFile(manifestFile);
}
}
@NonNull
@Override
public Set<File> getJavaDirectories() {
Set<File> dirs = new HashSet<File>();
final Module module = getModule();
final VirtualFile[] contentRoots = ModuleRootManager.getInstance(module).getContentRoots();
if (contentRoots.length != 0) {
for (VirtualFile root : contentRoots) {
dirs.add(VfsUtilCore.virtualToIoFile(root));
}
}
return dirs;
}
@NonNull
@Override
public Set<File> getResourcesDirectories() {
return Collections.emptySet();
}
@NonNull
@Override
public Set<File> getAidlDirectories() {
final VirtualFile dir = AndroidRootUtil.getAidlGenDir(AndroidFacet.this);
return dir == null ? Collections.<File>emptySet() : Collections.singleton(VfsUtilCore.virtualToIoFile(dir));
}
@NonNull
@Override
public Set<File> getRenderscriptDirectories() {
final VirtualFile dir = AndroidRootUtil.getRenderscriptGenDir(AndroidFacet.this);
return dir == null ? Collections.<File>emptySet() : Collections.singleton(VfsUtilCore.virtualToIoFile(dir));
}
@NonNull
@Override
public Set<File> getJniDirectories() {
return Collections.emptySet();
}
@NonNull
@Override
public Set<File> getResDirectories() {
String resRelPath = getProperties().RES_FOLDER_RELATIVE_PATH;
final VirtualFile dir = AndroidRootUtil.getFileByRelativeModulePath(getModule(), resRelPath, true);
return dir == null ? Collections.<File>emptySet() : Collections.singleton(VfsUtilCore.virtualToIoFile(dir));
}
@NonNull
@Override
public Set<File> getAssetsDirectories() {
final VirtualFile dir = AndroidRootUtil.getAssetsDir(AndroidFacet.this);
return dir == null ? Collections.<File>emptySet() : Collections.singleton(VfsUtilCore.virtualToIoFile(dir));
}
@NonNull
@Override
public Collection<File> getJniLibsDirectories() {
return Collections.emptyList();
}
}
}