| /* |
| * Copyright (C) 2015 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.databinding; |
| |
| |
| import com.android.SdkConstants; |
| import com.android.ide.common.res2.DataBindingResourceType; |
| import com.android.ide.common.resources.ResourceUrl; |
| import com.android.resources.ResourceType; |
| import com.android.tools.idea.gradle.util.GradleUtil; |
| import com.android.tools.idea.model.ManifestInfo; |
| import com.android.tools.idea.rendering.DataBindingInfo; |
| import com.android.tools.idea.rendering.LocalResourceRepository; |
| import com.android.tools.idea.rendering.PsiDataBindingResourceItem; |
| import com.google.common.collect.Lists; |
| import com.google.common.collect.Maps; |
| import com.intellij.lang.Language; |
| import com.intellij.lang.java.JavaLanguage; |
| import com.intellij.lang.java.JavaParserDefinition; |
| import com.intellij.lexer.Lexer; |
| import com.intellij.openapi.module.Module; |
| import com.intellij.openapi.project.Project; |
| import com.intellij.openapi.util.ModificationTracker; |
| import com.intellij.openapi.util.text.StringUtil; |
| import com.intellij.pom.java.LanguageLevel; |
| import com.intellij.psi.*; |
| import com.intellij.psi.impl.light.*; |
| import com.intellij.psi.scope.ElementClassHint; |
| import com.intellij.psi.scope.NameHint; |
| import com.intellij.psi.scope.PsiScopeProcessor; |
| import com.intellij.psi.search.searches.AnnotatedElementsSearch; |
| import com.intellij.psi.tree.IElementType; |
| import com.intellij.psi.util.CachedValue; |
| import com.intellij.psi.util.CachedValuesManager; |
| import com.intellij.psi.util.PsiUtil; |
| import com.intellij.psi.xml.XmlTag; |
| import com.intellij.util.ArrayUtil; |
| import com.intellij.util.containers.HashSet; |
| import org.jetbrains.android.augment.AndroidLightClassBase; |
| import org.jetbrains.android.facet.AndroidFacet; |
| import org.jetbrains.annotations.NonNls; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| |
| import java.util.*; |
| import java.util.concurrent.atomic.AtomicLong; |
| |
| /** |
| * Utility class that handles the interaction between Data Binding and the IDE. |
| * <p/> |
| * This class handles adding class finders and short names caches for DataBinding related code |
| * completion etc. |
| */ |
| public class DataBindingUtil { |
| public static final String BR = "BR"; |
| |
| private static List<String> VIEW_PACKAGE_ELEMENTS = Arrays.asList(SdkConstants.VIEW, SdkConstants.VIEW_GROUP, SdkConstants.VIEW_STUB, |
| SdkConstants.TEXTURE_VIEW, SdkConstants.SURFACE_VIEW); |
| |
| private static AtomicLong ourDataBindingEnabledModificationCount = new AtomicLong(0); |
| |
| /** |
| * Package private class used by BR class finder and BR short names cache to create a BR file on demand. |
| * |
| * @param facet The facet for which the BR file is necessary. |
| * @return The LightBRClass that belongs to the given AndroidFacet |
| */ |
| static LightBrClass getOrCreateBrClassFor(AndroidFacet facet) { |
| LightBrClass existing = facet.getLightBrClass(); |
| if (existing == null) { |
| //noinspection SynchronizationOnLocalVariableOrMethodParameter |
| synchronized (facet) { |
| existing = facet.getLightBrClass(); |
| if (existing == null) { |
| existing = new LightBrClass(PsiManager.getInstance(facet.getModule().getProject()), facet); |
| facet.setLightBrClass(existing); |
| } |
| } |
| } |
| return existing; |
| } |
| |
| private static PsiType parsePsiType(String text, AndroidFacet facet, PsiElement context) { |
| return PsiElementFactory.SERVICE.getInstance(facet.getModule().getProject()).createTypeFromText(text, context); |
| } |
| |
| private static void handleGradleSyncResult(Project project, AndroidFacet facet) { |
| boolean wasEnabled = facet.isDataBindingEnabled(); |
| boolean enabled = project != null && resolveHasDataBinding(facet); |
| if (enabled != wasEnabled) { |
| facet.setDataBindingEnabled(enabled); |
| ourDataBindingEnabledModificationCount.incrementAndGet(); |
| } |
| } |
| |
| public static PsiType resolveViewPsiType(DataBindingInfo.ViewWithId viewWithId, AndroidFacet facet) { |
| String viewClassName = getViewClassName(viewWithId.tag, facet); |
| if (StringUtil.isNotEmpty(viewClassName)) { |
| return parsePsiType(viewClassName, facet, null); |
| } |
| return null; |
| } |
| /** |
| * Receives an {@linkplain XmlTag} and returns the View class that is represented by the tag. |
| * May return null if it cannot find anything reasonable (e.g. it is a merge but does not have data binding) |
| * |
| * @param tag The {@linkplain XmlTag} that represents the View |
| */ |
| @Nullable |
| private static String getViewClassName(XmlTag tag, AndroidFacet facet) { |
| final String elementName = getViewName(tag); |
| if (elementName.indexOf('.') == -1) { |
| if (VIEW_PACKAGE_ELEMENTS.contains(elementName)) { |
| return SdkConstants.VIEW_PKG_PREFIX + elementName; |
| } else if (SdkConstants.WEB_VIEW.equals(elementName)) { |
| return SdkConstants.ANDROID_WEBKIT_PKG + elementName; |
| } else if (SdkConstants.VIEW_MERGE.equals(elementName)) { |
| return getViewClassNameFromMerge(tag, facet); |
| } else if (SdkConstants.VIEW_INCLUDE.equals(elementName)) { |
| return getViewClassNameFromInclude(tag, facet); |
| } |
| return SdkConstants.WIDGET_PKG_PREFIX + elementName; |
| } else { |
| return elementName; |
| } |
| } |
| |
| private static String getViewClassNameFromInclude(XmlTag tag, AndroidFacet facet) { |
| String reference = getViewClassNameFromLayoutReferenceTag(tag, facet); |
| return reference == null ? SdkConstants.CLASS_VIEW : reference; |
| } |
| |
| private static String getViewClassNameFromMerge(XmlTag tag, AndroidFacet facet) { |
| return getViewClassNameFromLayoutReferenceTag(tag, facet); |
| } |
| |
| private static String getViewClassNameFromLayoutReferenceTag(XmlTag tag, AndroidFacet facet) { |
| String layout = tag.getAttributeValue(SdkConstants.ATTR_LAYOUT); |
| if (layout == null) { |
| return null; |
| } |
| LocalResourceRepository moduleResources = facet.getModuleResources(false); |
| if (moduleResources == null) { |
| return null; |
| } |
| ResourceUrl resourceUrl = ResourceUrl.parse(layout); |
| if (resourceUrl == null || resourceUrl.type != ResourceType.LAYOUT) { |
| return null; |
| } |
| DataBindingInfo info = moduleResources.getDataBindingInfoForLayout(resourceUrl.name); |
| if (info == null) { |
| return null; |
| } |
| return info.getQualifiedName(); |
| } |
| |
| private static String getViewName(XmlTag tag) { |
| String viewName = tag.getName(); |
| if (SdkConstants.VIEW_TAG.equals(viewName)) { |
| viewName = tag.getAttributeValue(SdkConstants.ATTR_CLASS, SdkConstants.ANDROID_URI); |
| } |
| return viewName; |
| } |
| |
| private static boolean resolveHasDataBinding(AndroidFacet facet) { |
| if (!facet.requiresAndroidModel()) { |
| return false; |
| } |
| if (facet.getAndroidModel() == null) { |
| return false; |
| } |
| // TODO Instead of checking library dependency, we should be checking whether data binding plugin is |
| // applied to this facet or not. Having library dependency does not guarantee data binding |
| // unless the plugin is applied as well. |
| return GradleUtil.dependsOn(facet.getAndroidModel(), SdkConstants.DATA_BINDING_LIB_ARTIFACT); |
| } |
| |
| static PsiClass getOrCreatePsiClass(DataBindingInfo info) { |
| PsiClass psiClass = info.getPsiClass(); |
| if (psiClass == null) { |
| //noinspection SynchronizationOnLocalVariableOrMethodParameter |
| synchronized (info) { |
| psiClass = info.getPsiClass(); |
| if (psiClass == null) { |
| psiClass = new LightBindingClass(info.getFacet(), PsiManager.getInstance(info.getProject()), info); |
| info.setPsiClass(psiClass); |
| } |
| } |
| } |
| return psiClass; |
| } |
| |
| /** |
| * Utility method that implements Data Binding's logic to convert a file name to a Java Class name |
| * |
| * @param name The name of the file |
| * @return The class name that will represent the given file |
| */ |
| public static String convertToJavaClassName(String name) { |
| int dotIndex = name.indexOf('.'); |
| if (dotIndex >= 0) { |
| name = name.substring(0, dotIndex); |
| } |
| |
| String[] split = name.split("[_-]"); |
| StringBuilder out = new StringBuilder(); |
| for (String section : split) { |
| out.append(StringUtil.capitalize(section)); |
| } |
| return out.toString(); |
| } |
| |
| /** |
| * Utility method to convert a variable name into java field name. |
| * |
| * @param name The variable name. |
| * @return The java field name for the given variable name. |
| */ |
| public static String convertToJavaFieldName(String name) { |
| int dotIndex = name.indexOf('.'); |
| if (dotIndex >= 0) { |
| name = name.substring(0, dotIndex); |
| } |
| |
| String[] split = name.split("[_-]"); |
| StringBuilder out = new StringBuilder(); |
| boolean first = true; |
| for (String section : split) { |
| if (first) { |
| first = false; |
| out.append(section); |
| } |
| else { |
| out.append(StringUtil.capitalize(section)); |
| } |
| } |
| return out.toString(); |
| } |
| |
| /** |
| * Returns the qualified name for the BR file for the given Facet. |
| * |
| * @param facet The {@linkplain AndroidFacet} to check. |
| * @return The qualified name for the BR class of the given Android Facet. |
| */ |
| public static String getBrQualifiedName(AndroidFacet facet) { |
| return getGeneratedPackageName(facet) + "." + BR; |
| } |
| |
| /** |
| * Returns the package name that will be use to generate R file or BR file. |
| * |
| * @param facet The {@linkplain AndroidFacet} to check. |
| * @return The package name that can be used to generate R and BR classes. |
| */ |
| public static String getGeneratedPackageName(AndroidFacet facet) { |
| return ManifestInfo.get(facet.getModule(), false).getPackage(); |
| } |
| |
| /** |
| * Called by the {@linkplain AndroidFacet} to refresh its data binding status. |
| * |
| * @param facet the {@linkplain AndroidFacet} whose IdeaProject is just set. |
| */ |
| public static void onIdeaProjectSet(AndroidFacet facet) { |
| handleGradleSyncResult(facet.getModule().getProject(), facet); |
| } |
| |
| /** |
| * The light class that represents the generated data binding code for a layout file. |
| */ |
| static class LightBindingClass extends AndroidLightClassBase { |
| private static final String BASE_CLASS = "android.databinding.ViewDataBinding"; |
| static final int STATIC_METHOD_COUNT = 3; |
| private DataBindingInfo myInfo; |
| private CachedValue<PsiMethod[]> myPsiMethodsCache; |
| private CachedValue<PsiField[]> myPsiFieldsCache; |
| private CachedValue<Map<String, String>> myAliasCache; |
| |
| private PsiReferenceList myExtendsList; |
| private PsiClassType[] myExtendsListTypes; |
| private final AndroidFacet myFacet; |
| private static Lexer ourJavaLexer; |
| |
| protected LightBindingClass(final AndroidFacet facet, @NotNull PsiManager psiManager, DataBindingInfo info) { |
| super(psiManager); |
| myInfo = info; |
| myFacet = facet; |
| CachedValuesManager cachedValuesManager = CachedValuesManager.getManager(info.getProject()); |
| myAliasCache = |
| cachedValuesManager.createCachedValue(new ResourceCacheValueProvider<Map<String, String>>(facet) { |
| @Override |
| Map<String, String> doCompute() { |
| List<PsiDataBindingResourceItem> imports = myInfo.getItems(DataBindingResourceType.IMPORT); |
| Map<String, String> result = new HashMap<String, String>(); |
| if (imports != null) { |
| for (PsiDataBindingResourceItem imp : imports) { |
| String alias = imp.getExtra(SdkConstants.ATTR_ALIAS); |
| if (alias != null) { |
| result.put(alias, imp.getExtra(SdkConstants.ATTR_TYPE)); |
| } |
| } |
| } |
| return result; |
| } |
| |
| @Override |
| Map<String, String> defaultValue() { |
| return Maps.newHashMap(); |
| } |
| }, false); |
| |
| myPsiMethodsCache = |
| cachedValuesManager.createCachedValue(new ResourceCacheValueProvider<PsiMethod[]>(facet) { |
| @Override |
| PsiMethod[] doCompute() { |
| List<PsiDataBindingResourceItem> variables = myInfo.getItems(DataBindingResourceType.VARIABLE); |
| if (variables == null) { |
| return PsiMethod.EMPTY_ARRAY; |
| } |
| PsiMethod[] methods = new PsiMethod[variables.size() * 2 + STATIC_METHOD_COUNT]; |
| PsiElementFactory factory = PsiElementFactory.SERVICE.getInstance(myInfo.getProject()); |
| for (int i = 0; i < variables.size(); i++) { |
| createVariableMethods(factory, variables.get(i), methods, i * 2); |
| } |
| createStaticMethods(factory, methods, variables.size() * 2); |
| return methods; |
| } |
| |
| |
| @Override |
| PsiMethod[] defaultValue() { |
| return PsiMethod.EMPTY_ARRAY; |
| } |
| }); |
| |
| myPsiFieldsCache = |
| cachedValuesManager.createCachedValue(new ResourceCacheValueProvider<PsiField[]>(facet) { |
| @Override |
| PsiField[] doCompute() { |
| List<DataBindingInfo.ViewWithId> viewsWithIds = myInfo.getViewsWithIds(); |
| PsiElementFactory factory = PsiElementFactory.SERVICE.getInstance(myInfo.getProject()); |
| PsiField[] result = new PsiField[viewsWithIds.size()]; |
| int i = 0; |
| int unresolved = 0; |
| for (DataBindingInfo.ViewWithId viewWithId : viewsWithIds) { |
| PsiField psiField = createPsiField(factory, viewWithId); |
| if (psiField == null) { |
| unresolved++; |
| } else { |
| result[i++] = psiField; |
| } |
| } |
| if (unresolved > 0) { |
| PsiField[] validResult = new PsiField[i]; |
| System.arraycopy(result, 0, validResult, 0, i); |
| return validResult; |
| } |
| return result; |
| } |
| |
| @Override |
| PsiField[] defaultValue() { |
| return PsiField.EMPTY_ARRAY; |
| } |
| }); |
| } |
| |
| @Override |
| public String toString() { |
| return myInfo.getClassName(); |
| } |
| |
| @Nullable |
| @Override |
| public String getQualifiedName() { |
| return myInfo.getQualifiedName(); |
| } |
| |
| @Nullable |
| @Override |
| public PsiClass getContainingClass() { |
| return null; |
| } |
| |
| @NotNull |
| @Override |
| public PsiField[] getFields() { |
| return myPsiFieldsCache.getValue(); |
| } |
| |
| @NotNull |
| @Override |
| public PsiField[] getAllFields() { |
| return getFields(); |
| } |
| |
| @NotNull |
| @Override |
| public PsiMethod[] getMethods() { |
| return myPsiMethodsCache.getValue(); |
| } |
| |
| @Override |
| public PsiClass getSuperClass() { |
| return JavaPsiFacade.getInstance(myInfo.getProject()) |
| .findClass(BASE_CLASS, myFacet.getModule().getModuleWithDependenciesAndLibrariesScope(false)); |
| } |
| |
| @Override |
| public PsiReferenceList getExtendsList() { |
| if (myExtendsList == null) { |
| PsiElementFactory factory = PsiElementFactory.SERVICE.getInstance(myInfo.getProject()); |
| PsiJavaCodeReferenceElement referenceElementByType = factory.createReferenceElementByType(getExtendsListTypes()[0]); |
| myExtendsList = factory.createReferenceList(new PsiJavaCodeReferenceElement[]{referenceElementByType}); |
| } |
| return myExtendsList; |
| } |
| |
| @NotNull |
| @Override |
| public PsiClassType[] getSuperTypes() { |
| return getExtendsListTypes(); |
| } |
| |
| @NotNull |
| @Override |
| public PsiClassType[] getExtendsListTypes() { |
| if (myExtendsListTypes == null) { |
| myExtendsListTypes = new PsiClassType[]{ |
| PsiType.getTypeByName(BASE_CLASS, myInfo.getProject(), |
| myFacet.getModule().getModuleWithDependenciesAndLibrariesScope(false))}; |
| } |
| return myExtendsListTypes; |
| } |
| |
| |
| @NotNull |
| @Override |
| public PsiMethod[] getAllMethods() { |
| return getMethods(); |
| } |
| |
| @NotNull |
| @Override |
| public PsiMethod[] findMethodsByName(@NonNls String name, boolean checkBases) { |
| List<PsiMethod> matched = null; |
| for (PsiMethod method : getMethods()) { |
| if (name.equals(method.getName())) { |
| if (matched == null) { |
| matched = Lists.newArrayList(); |
| } |
| matched.add(method); |
| } |
| } |
| return matched == null ? PsiMethod.EMPTY_ARRAY : matched.toArray(new PsiMethod[matched.size()]); |
| } |
| |
| @Override |
| public boolean processDeclarations(@NotNull PsiScopeProcessor processor, |
| @NotNull ResolveState state, |
| PsiElement lastParent, |
| @NotNull PsiElement place) { |
| boolean continueProcessing = super.processDeclarations(processor, state, lastParent, place); |
| if (!continueProcessing) { |
| return false; |
| } |
| List<PsiDataBindingResourceItem> imports = myInfo.getItems(DataBindingResourceType.IMPORT); |
| if (imports == null || imports.isEmpty()) { |
| return true; |
| } |
| final ElementClassHint classHint = processor.getHint(ElementClassHint.KEY); |
| if (classHint != null && classHint.shouldProcess(ElementClassHint.DeclarationKind.CLASS)) { |
| final NameHint nameHint = processor.getHint(NameHint.KEY); |
| final String name = nameHint != null ? nameHint.getName(state) : null; |
| for (PsiDataBindingResourceItem imp : imports) { |
| String alias = imp.getExtra(SdkConstants.ATTR_ALIAS); |
| if (alias != null) { |
| continue; // aliases are pre-resolved in {@linkplain #replaceImportAliases} |
| } |
| String qName = imp.getExtra(SdkConstants.ATTR_TYPE); |
| if (qName == null) { |
| continue; |
| } |
| |
| if (name != null && !qName.endsWith("." + name)) { |
| continue; |
| } |
| |
| Module module = myInfo.getModule(); |
| if (module == null) { |
| return true; // this should not really happen but just to be safe |
| } |
| PsiClass aClass = JavaPsiFacade.getInstance(myManager.getProject()).findClass(qName, module |
| .getModuleWithDependenciesAndLibrariesScope(true)); |
| if (aClass != null) { |
| if (!processor.execute(aClass, state)) { |
| // found it! |
| return false; |
| } |
| } |
| } |
| } |
| return true; |
| } |
| |
| private static Lexer getJavaLexer() { |
| if (ourJavaLexer == null) { |
| ourJavaLexer = JavaParserDefinition.createLexer(LanguageLevel.JDK_1_6); |
| } |
| return ourJavaLexer; |
| } |
| |
| private String replaceImportAliases(String type) { |
| Map<String, String> lookup = myAliasCache.getValue(); |
| if (lookup == null || lookup.isEmpty()) { |
| return type; |
| } |
| Lexer lexer = getJavaLexer(); |
| lexer.start(type); |
| boolean checkNext = true; |
| StringBuilder out = new StringBuilder(); |
| IElementType tokenType = lexer.getTokenType(); |
| while (tokenType != null) { |
| if (checkNext && tokenType == JavaTokenType.IDENTIFIER) { |
| // this might be something we want to replace |
| String tokenText = lexer.getTokenText(); |
| String replacement = lookup.get(tokenText); |
| if (replacement != null) { |
| out.append(replacement); |
| } else { |
| out.append(tokenText); |
| } |
| } else { |
| out.append(lexer.getTokenText()); |
| } |
| if (tokenType != TokenType.WHITE_SPACE) { // ignore spaces |
| if (tokenType == JavaTokenType.LT || tokenType == JavaTokenType.COMMA) { |
| checkNext = true; |
| } else { |
| checkNext = false; |
| } |
| } |
| lexer.advance(); |
| tokenType = lexer.getTokenType(); |
| } |
| return out.toString(); |
| } |
| |
| private void createVariableMethods(PsiElementFactory factory, PsiDataBindingResourceItem item, PsiMethod[] outPsiMethods, int index) { |
| PsiManager psiManager = PsiManager.getInstance(myInfo.getProject()); |
| PsiMethod setter = factory.createMethod("set" + StringUtil.capitalize(item.getName()), PsiType.VOID); |
| |
| String variableType = replaceImportAliases(item.getExtra(SdkConstants.ATTR_TYPE)); |
| |
| PsiType type = parsePsiType(variableType, myFacet, this); |
| PsiParameter param = factory.createParameter(item.getName(), type); |
| setter.getParameterList().add(param); |
| PsiUtil.setModifierProperty(setter, PsiModifier.PUBLIC, true); |
| |
| outPsiMethods[index] = new LightDataBindingMethod(item.getXmlTag(), psiManager, setter, this, JavaLanguage.INSTANCE); |
| |
| PsiMethod getter = factory.createMethod("get" + StringUtil.capitalize(item.getName()), type); |
| PsiUtil.setModifierProperty(getter, PsiModifier.PUBLIC, true); |
| outPsiMethods[index + 1] = new LightDataBindingMethod(item.getXmlTag(), psiManager, getter, this, JavaLanguage.INSTANCE); |
| } |
| |
| private void createStaticMethods(PsiElementFactory factory, PsiMethod[] outPsiMethods, int index) { |
| PsiClassType myType = factory.createType(this); |
| PsiClassType viewGroupType = PsiType |
| .getTypeByName(SdkConstants.CLASS_VIEWGROUP, myInfo.getProject(), |
| myFacet.getModule().getModuleWithDependenciesAndLibrariesScope(true)); |
| PsiClassType layoutInflaterType = PsiType.getTypeByName(SdkConstants.CLASS_LAYOUT_INFLATER, myInfo.getProject(), |
| myFacet.getModule().getModuleWithDependenciesAndLibrariesScope(true)); |
| PsiClassType viewType = PsiType |
| .getTypeByName(SdkConstants.CLASS_VIEW, myInfo.getProject(), myFacet.getModule().getModuleWithDependenciesAndLibrariesScope(true)); |
| PsiParameter layoutInflaterParam = factory.createParameter("inflater", layoutInflaterType); |
| PsiParameter rootParam = factory.createParameter("root", viewGroupType); |
| PsiParameter attachToRootParam = factory.createParameter("attachToRoot", PsiType.BOOLEAN); |
| PsiParameter viewParam = factory.createParameter("view", viewType); |
| |
| PsiMethod inflate3Arg = factory.createMethod("inflate", myType); |
| inflate3Arg.getParameterList().add(layoutInflaterParam); |
| inflate3Arg.getParameterList().add(rootParam); |
| inflate3Arg.getParameterList().add(attachToRootParam); |
| |
| PsiMethod inflate1Arg = factory.createMethod("inflate", myType); |
| inflate1Arg.getParameterList().add(layoutInflaterParam); |
| |
| PsiMethod bind = factory.createMethod("bind", myType); |
| bind.getParameterList().add(viewParam); |
| |
| PsiMethod[] methods = new PsiMethod[]{inflate1Arg, inflate3Arg, bind}; |
| PsiManager psiManager = PsiManager.getInstance(myInfo.getProject()); |
| for (PsiMethod method : methods) { |
| PsiUtil.setModifierProperty(method, PsiModifier.PUBLIC, true); |
| PsiUtil.setModifierProperty(method, PsiModifier.STATIC, true); |
| //noinspection ConstantConditions |
| outPsiMethods[index++] = |
| new LightDataBindingMethod(myInfo.getPsiFile(), psiManager, method, this, JavaLanguage.INSTANCE); |
| } |
| } |
| |
| @Nullable |
| private PsiField createPsiField(PsiElementFactory factory, DataBindingInfo.ViewWithId viewWithId) { |
| PsiType type = resolveViewPsiType(viewWithId, myFacet); |
| assert type != null; |
| PsiField field = factory.createField(viewWithId.name, type); |
| PsiUtil.setModifierProperty(field, PsiModifier.PUBLIC, true); |
| PsiUtil.setModifierProperty(field, PsiModifier.FINAL, true); |
| return new LightDataBindingField(viewWithId, PsiManager.getInstance(myInfo.getProject()), field, this); |
| } |
| |
| @Override |
| public boolean isInterface() { |
| return false; |
| } |
| |
| @NotNull |
| @Override |
| public PsiElement getNavigationElement() { |
| return myInfo.getNavigationElement(); |
| } |
| |
| @Override |
| public String getName() { |
| return myInfo.getClassName(); |
| } |
| |
| @Nullable |
| @Override |
| public PsiFile getContainingFile() { |
| return myInfo.getPsiFile(); |
| } |
| } |
| |
| /** |
| * The light method class that represents the generated data binding methods for a layout file. |
| */ |
| static class LightDataBindingMethod extends LightMethod { |
| private PsiElement myNavigationElement; |
| |
| public LightDataBindingMethod(@NotNull PsiElement navigationElement, |
| @NotNull PsiManager manager, |
| @NotNull PsiMethod method, |
| @NotNull PsiClass containingClass, |
| @NotNull Language language) { |
| super(manager, method, containingClass, language); |
| myNavigationElement = navigationElement; |
| } |
| |
| @NotNull |
| @Override |
| public PsiElement getNavigationElement() { |
| return myNavigationElement; |
| } |
| |
| @Override |
| public PsiIdentifier getNameIdentifier() { |
| return new LightIdentifier(getManager(), getName()); |
| } |
| } |
| |
| /** |
| * The light field class that represents the generated view fields for a layout file. |
| */ |
| static class LightDataBindingField extends LightField { |
| private final DataBindingInfo.ViewWithId myViewWithId; |
| |
| public LightDataBindingField(DataBindingInfo.ViewWithId viewWithId, |
| @NotNull PsiManager manager, |
| @NotNull PsiField field, |
| @NotNull PsiClass containingClass) { |
| super(manager, field, containingClass); |
| myViewWithId = viewWithId; |
| } |
| |
| @NotNull |
| @Override |
| public PsiElement getNavigationElement() { |
| return myViewWithId.tag; |
| } |
| } |
| |
| /** |
| * The light class that represents a data binding BR file |
| */ |
| public static class LightBrClass extends AndroidLightClassBase { |
| private static final String BINDABLE_QUALIFIED_NAME = "android.databinding.Bindable"; |
| private final AndroidFacet myFacet; |
| private CachedValue<PsiField[]> myFieldCache; |
| @NotNull |
| private String[] myCachedFieldNames = new String[]{"_all"}; |
| private final String myQualifiedName; |
| private PsiFile myContainingFile; |
| |
| public LightBrClass(@NotNull PsiManager psiManager, final AndroidFacet facet) { |
| super(psiManager); |
| myQualifiedName = getBrQualifiedName(facet); |
| myFacet = facet; |
| myFieldCache = |
| CachedValuesManager.getManager(facet.getModule().getProject()).createCachedValue( |
| new ResourceCacheValueProvider<PsiField[]>(facet, psiManager.getModificationTracker().getJavaStructureModificationTracker()) { |
| @Override |
| PsiField[] doCompute() { |
| Project project = facet.getModule().getProject(); |
| PsiElementFactory elementFactory = PsiElementFactory.SERVICE.getInstance(project); |
| LocalResourceRepository moduleResources = facet.getModuleResources(false); |
| if (moduleResources == null) { |
| return defaultValue(); |
| } |
| Map<String, DataBindingInfo> dataBindingResourceFiles = moduleResources.getDataBindingResourceFiles(); |
| if (dataBindingResourceFiles == null) { |
| return defaultValue(); |
| } |
| Set<String> variableNames = new HashSet<String>(); |
| for (DataBindingInfo info : dataBindingResourceFiles.values()) { |
| List<PsiDataBindingResourceItem> variables = info.getItems(DataBindingResourceType.VARIABLE); |
| if (variables != null) { |
| for (PsiDataBindingResourceItem item : variables) { |
| variableNames.add(item.getName()); |
| } |
| } |
| } |
| Set<String> bindables = collectVariableNamesFromBindables(); |
| if (bindables != null) { |
| variableNames.addAll(bindables); |
| } |
| PsiField[] result = new PsiField[variableNames.size() + 1]; |
| result[0] = createPsiField(project, elementFactory, "_all"); |
| int i = 1; |
| for (String variable : variableNames) { |
| result[i++] = createPsiField(project, elementFactory, variable); |
| } |
| myCachedFieldNames = ArrayUtil.toStringArray(variableNames); |
| return result; |
| } |
| |
| @Override |
| PsiField[] defaultValue() { |
| Project project = facet.getModule().getProject(); |
| return new PsiField[]{createPsiField(project, PsiElementFactory.SERVICE.getInstance(project), "_all")}; |
| } |
| }); |
| } |
| |
| private Set<String> collectVariableNamesFromBindables() { |
| JavaPsiFacade facade = JavaPsiFacade.getInstance(myFacet.getModule().getProject()); |
| PsiClass aClass = facade.findClass(BINDABLE_QUALIFIED_NAME, myFacet.getModule().getModuleWithDependenciesAndLibrariesScope(false)); |
| if (aClass == null) { |
| return null; |
| } |
| //noinspection unchecked |
| final Collection<? extends PsiModifierListOwner> psiElements = |
| AnnotatedElementsSearch.searchElements(aClass, myFacet.getModule().getModuleScope(), PsiMethod.class, PsiField.class).findAll(); |
| return BrUtil.collectIds(psiElements); |
| } |
| |
| private PsiField createPsiField(Project project, PsiElementFactory factory, String id) { |
| PsiField field = factory.createField(id, PsiType.INT); |
| PsiUtil.setModifierProperty(field, PsiModifier.PUBLIC, true); |
| PsiUtil.setModifierProperty(field, PsiModifier.STATIC, true); |
| PsiUtil.setModifierProperty(field, PsiModifier.FINAL, true); |
| return new LightBRField(PsiManager.getInstance(project), field, this); |
| } |
| |
| @Override |
| public String toString() { |
| return "BR class for " + myFacet; |
| } |
| |
| @Nullable |
| @Override |
| public String getQualifiedName() { |
| return myQualifiedName; |
| } |
| |
| @Override |
| public String getName() { |
| return BR; |
| } |
| |
| @NotNull |
| public String[] getAllFieldNames() { |
| return myCachedFieldNames; |
| } |
| |
| @Nullable |
| @Override |
| public PsiClass getContainingClass() { |
| return null; |
| } |
| |
| @NotNull |
| @Override |
| public PsiField[] getFields() { |
| return myFieldCache.getValue(); |
| } |
| |
| @NotNull |
| @Override |
| public PsiField[] getAllFields() { |
| return getFields(); |
| } |
| |
| |
| @Nullable |
| @Override |
| public PsiFile getContainingFile() { |
| if (myContainingFile == null) { |
| // TODO: using R file for now. Would be better if we create a real VirtualFile for this. |
| PsiClass aClass = JavaPsiFacade.getInstance(myFacet.getModule().getProject()) |
| .findClass(getGeneratedPackageName(myFacet) + ".R", myFacet.getModule().getModuleScope()); |
| if (aClass != null) { |
| myContainingFile = aClass.getContainingFile(); |
| } |
| } |
| return myContainingFile; |
| } |
| |
| @Override |
| public PsiIdentifier getNameIdentifier() { |
| return new LightIdentifier(getManager(), getName()); |
| } |
| |
| @NotNull |
| @Override |
| public PsiElement getNavigationElement() { |
| PsiFile containingFile = getContainingFile(); |
| return containingFile == null ? super.getNavigationElement() : containingFile; |
| } |
| } |
| |
| /** |
| * The light field representing elements of BR class |
| */ |
| static class LightBRField extends LightField { |
| |
| public LightBRField(@NotNull PsiManager manager, @NotNull PsiField field, @NotNull PsiClass containingClass) { |
| super(manager, field, containingClass); |
| } |
| } |
| |
| /** |
| * Tracker that changes when a facet's data binding enabled value changes |
| */ |
| public static ModificationTracker DATA_BINDING_ENABLED_TRACKER = new ModificationTracker() { |
| @Override |
| public long getModificationCount() { |
| return ourDataBindingEnabledModificationCount.longValue(); |
| } |
| }; |
| |
| } |