blob: 7388778af82bbc1db522d1a6884c3cb87cd7af5e [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;
import com.android.resources.ResourceFolderType;
import com.android.resources.ResourceType;
import com.android.tools.idea.model.ManifestInfo;
import com.android.tools.idea.rendering.ResourceHelper;
import com.intellij.openapi.application.Application;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.module.ModuleUtilCore;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Computable;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.*;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.xml.XmlAttribute;
import com.intellij.psi.xml.XmlFile;
import com.intellij.psi.xml.XmlTag;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import static com.android.SdkConstants.*;
public class AndroidPsiUtils {
/**
* Looks up the {@link PsiFile} for a given {@link VirtualFile} in a given {@link Project}, in
* a safe way (meaning it will acquire a read lock first, and will check that the file is valid
*
* @param project the project
* @param file the file
* @return the corresponding {@link PsiFile}, or null if not found or valid
*/
@Nullable
public static PsiFile getPsiFileSafely(@NotNull final Project project, @NotNull final VirtualFile file) {
return ApplicationManager.getApplication().runReadAction(new Computable<PsiFile>() {
@Nullable
@Override
public PsiFile compute() {
return file.isValid() ? PsiManager.getInstance(project).findFile(file) : null;
}
});
}
/**
* Looks up the {@link Module} for a given {@link PsiElement}, in a safe way (meaning it will
* acquire a read lock first.
*
* @param element the element
* @return the module containing the element, or null if not found
*/
@Nullable
public static Module getModuleSafely(@NotNull final PsiElement element) {
return ApplicationManager.getApplication().runReadAction(new Computable<Module>() {
@Nullable
@Override
public Module compute() {
return ModuleUtilCore.findModuleForPsiElement(element);
}
});
}
/**
* Looks up the {@link Module} containing a given {@link VirtualFile} in a given {@link Project}, in
* a safe way (meaning it will acquire a read lock first
*
* @param project the project
* @param file the file
* @return the corresponding {@link Module}, or null if not found
*/
@Nullable
public static Module getModuleSafely(@NotNull final Project project, @NotNull final VirtualFile file) {
return ApplicationManager.getApplication().runReadAction(new Computable<Module>() {
@Nullable
@Override
public Module compute() {
PsiFile psiFile = PsiManager.getInstance(project).findFile(file);
return psiFile == null ? null : ModuleUtilCore.findModuleForPsiElement(psiFile);
}
});
}
/**
* Returns the root tag for the given {@link XmlFile}, if any, acquiring the read
* lock to do so if necessary
*
* @param file the file to look up the root tag for
* @return the corresponding root tag, if any
*/
@Nullable
public static XmlTag getRootTagSafely(@NotNull final XmlFile file) {
if (ApplicationManager.getApplication().isReadAccessAllowed()) {
return file.getRootTag();
}
return ApplicationManager.getApplication().runReadAction(new Computable<XmlTag>() {
@Nullable
@Override
public XmlTag compute() {
return file.getRootTag();
}
});
}
/**
* Get the value of an attribute in the {@link com.intellij.psi.xml.XmlFile} safely (meaning it will acquire the read lock first).
*/
@Nullable
public static String getRootTagAttributeSafely(@NotNull final XmlFile file,
@NotNull final String attribute,
@Nullable final String namespace) {
Application application = ApplicationManager.getApplication();
if (!application.isReadAccessAllowed()) {
return application.runReadAction(new Computable<String>() {
@Nullable
@Override
public String compute() {
return getRootTagAttributeSafely(file, attribute, namespace);
}
});
} else {
XmlTag tag = file.getRootTag();
if (tag != null) {
XmlAttribute attr = namespace != null ? tag.getAttribute(attribute, namespace) : tag.getAttribute(attribute);
if (attr != null) {
return attr.getValue();
}
}
return null;
}
}
/**
* Returns the {@link PsiDirectory} for the given {@link VirtualFile}, with a read lock.
*
* @param dir the file to look up the PSI directory for
* @return the corresponding PSI directory, if any
*/
@Nullable
public static PsiDirectory getPsiDirectorySafely(@NotNull final Project project, @NotNull final VirtualFile dir) {
return ApplicationManager.getApplication().runReadAction(new Computable<PsiDirectory>() {
@Nullable
@Override
public PsiDirectory compute() {
return PsiManager.getInstance(project).findDirectory(dir);
}
});
}
/**
* Returns the root tag for the given {@link PsiFile}, if any, acquiring the read
* lock to do so if necessary
*
* @param file the file to look up the root tag for
* @return the corresponding root tag, if any
*/
@Nullable
public static String getRootTagName(@NotNull PsiFile file) {
if (ResourceHelper.getFolderType(file) == ResourceFolderType.XML) {
if (file instanceof XmlFile) {
XmlTag rootTag = getRootTagSafely(((XmlFile)file));
return rootTag == null ? null : rootTag.getName();
}
}
return null;
}
/** Type of resource reference: R.type.name or android.R.type.name or neither */
public enum ResourceReferenceType { NONE, APP, FRAMEWORK }
/**
* Returns true if the given PsiElement is a reference to an Android Resource.
* The element can either be an identifier such as y in R.x.y, or the expression R.x.y itself.
*/
public static boolean isResourceReference(@NotNull PsiElement element) {
return getResourceReferenceType(element) != ResourceReferenceType.NONE;
}
/**
* Returns the type of resource reference for the given PSiElement; for R fields and android.R
* fields it will return {@link ResourceReferenceType#APP} and {@link ResourceReferenceType#FRAMEWORK}
* respectively, and otherwise it returns {@link ResourceReferenceType#NONE}.
* <p>
* The element can either be an identifier such as y in R.x.y, or the expression R.x.y itself.
*/
@NotNull
public static ResourceReferenceType getResourceReferenceType(@NotNull PsiElement element) {
if (element instanceof PsiReferenceExpression) {
return getResourceReferenceType((PsiReferenceExpression)element);
}
if (element instanceof PsiIdentifier && element.getParent() instanceof PsiReferenceExpression) {
return getResourceReferenceType((PsiReferenceExpression)element.getParent());
}
return ResourceReferenceType.NONE;
}
/**
* Returns the resource name; e.g. for "R.string.foo" it returns "foo".
* NOTE: This method should only be called for elements <b>known</b> to be
* resource references!
* */
@NotNull
public static String getResourceName(@NotNull PsiElement element) {
assert isResourceReference(element);
if (element instanceof PsiReferenceExpression) {
PsiReferenceExpression refExp = (PsiReferenceExpression)element;
String name = refExp.getReferenceName();
if (name != null) {
return name;
}
}
return element.getText();
}
@NotNull
public static ResourceReferenceType getResourceReferenceType(PsiReferenceExpression element) {
PsiExpression exp = element.getQualifierExpression();
if (!(exp instanceof PsiReferenceExpression)) {
return ResourceReferenceType.NONE;
}
exp = ((PsiReferenceExpression)exp).getQualifierExpression();
if (!(exp instanceof PsiReferenceExpression)) {
return ResourceReferenceType.NONE;
}
PsiReferenceExpression ref = (PsiReferenceExpression)exp;
if (R_CLASS.equals(ref.getReferenceName())) {
PsiExpression qualifierExpression = ref.getQualifierExpression();
if (qualifierExpression instanceof PsiReferenceExpression &&
ANDROID_PKG.equals(((PsiReferenceExpression)qualifierExpression).getReferenceName())) {
return ResourceReferenceType.FRAMEWORK;
} else {
// TODO: Check resolved type to make sure it's not a class with android.R imported?
// Not super important since we actively discourage importing android.R directly
// via lint checks and the AndroidImportFilter preventing it from happening automatically
return ResourceReferenceType.APP;
}
}
return ResourceReferenceType.NONE;
}
/** Returns the Android {@link ResourceType} given a PSI reference to an Android resource. */
@Nullable
public static ResourceType getResourceType(PsiElement resourceRefElement) {
if (!isResourceReference(resourceRefElement)) {
return null;
}
PsiReferenceExpression exp = resourceRefElement instanceof PsiReferenceExpression ?
(PsiReferenceExpression)resourceRefElement :
(PsiReferenceExpression)resourceRefElement.getParent();
PsiExpression qualifierExpression = exp.getQualifierExpression();
if (qualifierExpression == null) {
return null;
}
return ResourceType.getEnum(qualifierExpression.getLastChild().getText());
}
/**
* Looks up the declared associated context/activity for the given XML file and
* returns the resolved fully qualified name if found
*
* @param module module containing the XML file
* @param xmlFile the XML file
* @return the associated fully qualified name, or null
*/
@Nullable
public static String getDeclaredContextFqcn(@NotNull Module module, @NotNull XmlFile xmlFile) {
String context = getRootTagAttributeSafely(xmlFile, ATTR_CONTEXT, TOOLS_URI);
if (context != null && !context.isEmpty()) {
boolean startsWithDot = context.charAt(0) == '.';
if (startsWithDot || context.indexOf('.') == -1) {
// Prepend application package
String pkg = ManifestInfo.get(module, false).getPackage();
return startsWithDot ? pkg + context : pkg + '.' + context;
}
}
return null;
}
/**
* Looks up the declared associated context/activity for the given XML file and
* returns the associated class, if found
*
* @param module module containing the XML file
* @param xmlFile the XML file
* @return the associated class, or null
*/
@Nullable
public static PsiClass getContextClass(@NotNull Module module, @NotNull XmlFile xmlFile) {
String fqn = getDeclaredContextFqcn(module, xmlFile);
if (fqn != null) {
Project project = module.getProject();
return JavaPsiFacade.getInstance(project).findClass(fqn, GlobalSearchScope.allScope(project));
}
return null;
}
/**
* Returns the {@link com.intellij.psi.PsiClass#getQualifiedName()} and acquires a read lock
* if necessary
*
* @param psiClass the class to look up the qualified name for
* @return the qualified name, or null
*/
@Nullable
public static String getQualifiedNameSafely(@NotNull final PsiClass psiClass) {
if (ApplicationManager.getApplication().isReadAccessAllowed()) {
return psiClass.getQualifiedName();
} else {
return ApplicationManager.getApplication().runReadAction(new Computable<String>() {
@Nullable
@Override
public String compute() {
return psiClass.getQualifiedName();
}
});
}
}
/**
* Returns the value of the given tag's attribute and acquires a read lock if necessary
*
* @param tag the tag to look up the attribute for
* @return the attribute value, or null
*/
@Nullable
public static String getAttributeSafely(@NotNull final XmlTag tag, @Nullable final String namespace, @NotNull final String name) {
if (ApplicationManager.getApplication().isReadAccessAllowed()) {
return tag.getAttributeValue(name, namespace);
} else {
return ApplicationManager.getApplication().runReadAction(new Computable<String>() {
@Nullable
@Override
public String compute() {
return tag.getAttributeValue(name, namespace);
}
});
}
}
}