blob: a4d1b68b0b02b9a69dab09d9c135b78e4d546921 [file] [log] [blame]
/*
* 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.dom;
import com.android.SdkConstants;
import com.intellij.codeInsight.completion.CompletionUtil;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.util.Computable;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.JavaPsiFacade;
import com.intellij.psi.PsiClass;
import com.intellij.psi.xml.XmlAttribute;
import com.intellij.psi.xml.XmlElement;
import com.intellij.psi.xml.XmlTag;
import com.intellij.util.ArrayUtil;
import com.intellij.util.Processor;
import com.intellij.util.containers.HashMap;
import com.intellij.util.xml.*;
import com.intellij.util.xml.reflect.DomExtender;
import com.intellij.util.xml.reflect.DomExtension;
import com.intellij.util.xml.reflect.DomExtensionsRegistrar;
import org.jetbrains.android.dom.animation.AndroidAnimationUtils;
import org.jetbrains.android.dom.animation.AnimationElement;
import org.jetbrains.android.dom.animator.AndroidAnimatorUtil;
import org.jetbrains.android.dom.animator.AnimatorElement;
import org.jetbrains.android.dom.attrs.AttributeDefinition;
import org.jetbrains.android.dom.attrs.AttributeDefinitions;
import org.jetbrains.android.dom.attrs.AttributeFormat;
import org.jetbrains.android.dom.attrs.StyleableDefinition;
import org.jetbrains.android.dom.color.ColorDomElement;
import org.jetbrains.android.dom.color.ColorStateListItem;
import org.jetbrains.android.dom.converters.CompositeConverter;
import org.jetbrains.android.dom.converters.ResourceReferenceConverter;
import org.jetbrains.android.dom.drawable.*;
import org.jetbrains.android.dom.layout.*;
import org.jetbrains.android.dom.manifest.AndroidManifestUtils;
import org.jetbrains.android.dom.manifest.Manifest;
import org.jetbrains.android.dom.manifest.ManifestElement;
import org.jetbrains.android.dom.menu.MenuElement;
import org.jetbrains.android.dom.resources.ResourceValue;
import org.jetbrains.android.dom.transition.*;
import org.jetbrains.android.dom.xml.AndroidXmlResourcesUtil;
import org.jetbrains.android.dom.xml.Intent;
import org.jetbrains.android.dom.xml.PreferenceElement;
import org.jetbrains.android.dom.xml.XmlResourceElement;
import org.jetbrains.android.facet.AndroidFacet;
import org.jetbrains.android.facet.SimpleClassMapConstructor;
import org.jetbrains.android.resourceManagers.ResourceManager;
import org.jetbrains.android.resourceManagers.SystemResourceManager;
import org.jetbrains.android.util.AndroidUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import java.util.*;
import static com.android.SdkConstants.*;
import static org.jetbrains.android.dom.transition.TransitionDomUtil.*;
import static org.jetbrains.android.util.AndroidUtils.SYSTEM_RESOURCE_PACKAGE;
/**
* @author Eugene.Kudelevsky
*/
public class AndroidDomExtender extends DomExtender<AndroidDomElement> {
private static final String[] LAYOUT_ATTRIBUTES_SUFS = new String[]{"_Layout", "_MarginLayout", "_Cell"};
private static final MyAttributeProcessor ourLayoutAttrsProcessor = new MyAttributeProcessor() {
@Override
public void process(@NotNull XmlName attrName, @NotNull DomExtension extension, @NotNull DomElement element) {
// Mark layout_width and layout_height required - if the context calls for it
String localName = attrName.getLocalName();
if (!(ATTR_LAYOUT_WIDTH.equals(localName) || ATTR_LAYOUT_HEIGHT.equals(localName))) {
return;
}
if ((element instanceof LayoutViewElement || element instanceof Fragment) &&
SdkConstants.NS_RESOURCES.equals(attrName.getNamespaceKey())) {
XmlElement xmlElement = element.getXmlElement();
XmlTag tag = xmlElement instanceof XmlTag ? (XmlTag)xmlElement : null;
String tagName = tag != null ? tag.getName() : null;
if (!VIEW_MERGE.equals(tagName) &&
!TABLE_ROW.equals(tagName) &&
!VIEW_INCLUDE.equals(tagName) &&
!REQUEST_FOCUS.equals(tagName) &&
!TAG_LAYOUT.equals(tagName) &&
!TAG_DATA.equals(tagName) &&
!TAG_VARIABLE.equals(tagName) &&
!TAG_IMPORT.equals(tagName) &&
!TAG.equals(tagName) &&
(tag == null || tag.getAttribute(ATTR_STYLE) == null)) {
XmlTag parentTag = tag != null ? tag.getParentTag() : null;
String parentTagName = parentTag != null ? parentTag.getName() : null;
if (!TABLE_ROW.equals(parentTagName) &&
!TABLE_LAYOUT.equals(parentTagName) &&
!VIEW_MERGE.equals(parentTagName) &&
!GRID_LAYOUT.equals(parentTagName) &&
!FQCN_GRID_LAYOUT_V7.equals(parentTagName)) {
extension.addCustomAnnotation(new MyRequired());
}
}
}
}
};
@Nullable
public static String getNamespaceKeyByResourcePackage(@NotNull AndroidFacet facet, @Nullable String resPackage) {
if (resPackage == null) {
if (facet.getProperties().LIBRARY_PROJECT || facet.requiresAndroidModel()) {
return AUTO_URI;
}
Manifest manifest = facet.getManifest();
if (manifest != null) {
String aPackage = manifest.getPackage().getValue();
if (aPackage != null && aPackage.length() > 0) {
return URI_PREFIX + aPackage;
}
}
}
else if (resPackage.equals(SYSTEM_RESOURCE_PACKAGE)) {
return ANDROID_URI;
}
return null;
}
protected static void registerStyleableAttributes(DomElement element,
@NotNull StyleableDefinition[] styleables,
@Nullable String namespace,
MyCallback callback,
MyAttributeProcessor processor,
Set<XmlName> skippedAttrSet) {
for (StyleableDefinition styleable : styleables) {
for (AttributeDefinition attrDef : styleable.getAttributes()) {
String attrName = attrDef.getName();
if (!skippedAttrSet.contains(new XmlName(attrName, namespace))) {
skippedAttrSet.add(new XmlName(attrName, namespace));
registerAttribute(attrDef, styleable.getName(), namespace, callback, processor, element);
}
}
}
}
private static boolean mustBeSoft(@NotNull Converter converter, Collection<AttributeFormat> formats) {
if (converter instanceof CompositeConverter || converter instanceof ResourceReferenceConverter) {
return false;
}
return formats.size() > 1;
}
private static void registerAttribute(@NotNull AttributeDefinition attrDef,
@Nullable String parentStyleableName,
String namespaceKey,
MyCallback callback,
@Nullable MyAttributeProcessor processor,
@NotNull DomElement element) {
String name = attrDef.getName();
if (!SdkConstants.NS_RESOURCES.equals(namespaceKey) && name.startsWith(SdkConstants.PREFIX_ANDROID)) {
// A styleable-definition in the app namespace (user specified or from a library) can include
// a reference to a platform attribute. In such a case, register it under the android namespace
// as opposed to the app namespace. See https://code.google.com/p/android/issues/detail?id=171162
name = name.substring(SdkConstants.PREFIX_ANDROID.length());
namespaceKey = SdkConstants.NS_RESOURCES;
}
XmlName xmlName = new XmlName(name, namespaceKey);
final DomExtension extension = callback.processAttribute(xmlName, attrDef, parentStyleableName);
if (extension == null) {
return;
}
Converter converter = AndroidDomUtil.getSpecificConverter(xmlName, element);
if (converter == null) {
converter = AndroidDomUtil.getConverter(attrDef);
}
if (converter != null) {
extension.setConverter(converter, mustBeSoft(converter, attrDef.getFormats()));
}
if (processor != null) {
processor.process(xmlName, extension, element);
}
}
protected static void registerAttributes(AndroidFacet facet,
DomElement element,
@NotNull String[] styleableNames,
MyCallback callback,
MyAttributeProcessor processor,
Set<XmlName> skipNames) {
registerAttributes(facet, element, styleableNames, null, callback, processor, skipNames);
registerAttributes(facet, element, styleableNames, SYSTEM_RESOURCE_PACKAGE, callback, processor, skipNames);
}
private static StyleableDefinition[] getStyleables(@NotNull AttributeDefinitions definitions, @NotNull String[] names) {
List<StyleableDefinition> styleables = new ArrayList<StyleableDefinition>();
for (String name : names) {
StyleableDefinition styleable = definitions.getStyleableByName(name);
if (styleable != null) {
styleables.add(styleable);
}
}
return styleables.toArray(new StyleableDefinition[styleables.size()]);
}
protected static void registerAttributes(AndroidFacet facet,
DomElement element,
@NotNull String styleableName,
@Nullable String resPackage,
MyCallback callback,
Set<XmlName> skipNames) {
registerAttributes(facet, element, new String[]{styleableName}, resPackage, callback, null, skipNames);
}
protected static void registerAttributes(AndroidFacet facet,
DomElement element,
@NotNull String[] styleableNames,
@Nullable String resPackage,
MyCallback callback,
MyAttributeProcessor processor,
Set<XmlName> skipNames) {
ResourceManager manager = facet.getResourceManager(resPackage);
if (manager == null) return;
AttributeDefinitions attrDefs = manager.getAttributeDefinitions();
if (attrDefs == null) return;
StyleableDefinition[] styleables = getStyleables(attrDefs, styleableNames);
registerAttributes(facet, element, styleables, resPackage, callback, processor, skipNames);
}
private static void registerAttributes(AndroidFacet facet,
DomElement element,
StyleableDefinition[] styleables, String resPackage,
MyCallback callback,
MyAttributeProcessor processor,
Set<XmlName> skipNames) {
String namespace = getNamespaceKeyByResourcePackage(facet, resPackage);
registerStyleableAttributes(element, styleables, namespace, callback, processor, skipNames);
}
@NotNull
private static Class getValueClass(@Nullable AttributeFormat format) {
if (format == null) return String.class;
switch (format) {
case Boolean:
return boolean.class;
case Reference:
case Dimension:
case Color:
return ResourceValue.class;
default:
return String.class;
}
}
protected static void registerAttributesForClassAndSuperclasses(AndroidFacet facet,
DomElement element,
PsiClass c,
MyCallback callback,
MyAttributeProcessor processor,
Set<XmlName> skipNames) {
while (c != null) {
String styleableName = c.getName();
if (styleableName != null) {
registerAttributes(facet, element, new String[]{styleableName}, callback, processor, skipNames);
}
c = getSuperclass(c);
}
}
@Nullable
protected static PsiClass getSuperclass(@NotNull final PsiClass c) {
return ApplicationManager.getApplication().runReadAction(new Computable<PsiClass>() {
@Override
@Nullable
public PsiClass compute() {
return c.isValid() ? c.getSuperClass() : null;
}
});
}
private static boolean isPreference(@NotNull Map<String, PsiClass> preferenceClassMap, @NotNull PsiClass c) {
PsiClass preferenceClass = preferenceClassMap.get("Preference");
return preferenceClass != null && (preferenceClass == c || c.isInheritor(preferenceClass, true));
}
public static void registerExtensionsForXmlResources(AndroidFacet facet,
XmlTag tag,
XmlResourceElement element,
MyCallback callback,
Set<String> registeredSubtags,
Set<XmlName> skipAttrNames) {
final String tagName = tag.getName();
String styleableName = AndroidXmlResourcesUtil.SPECIAL_STYLEABLE_NAMES.get(tagName);
if (styleableName != null) {
String[] attrsToSkip = element instanceof Intent ? new String[]{"action"} : ArrayUtil.EMPTY_STRING_ARRAY;
final Set<XmlName> newSkipAttrNames = new HashSet<XmlName>();
for (String attrName : attrsToSkip) {
newSkipAttrNames.add(new XmlName(attrName, SdkConstants.NS_RESOURCES));
}
registerAttributes(facet, element, styleableName, SYSTEM_RESOURCE_PACKAGE, callback, newSkipAttrNames);
}
if (tagName.equals("searchable")) {
registerSubtags("actionkey", XmlResourceElement.class, callback, registeredSubtags);
}
// for keyboard api
if (tagName.equals("Keyboard")) {
registerSubtags("Row", XmlResourceElement.class, callback, registeredSubtags);
}
else if (tagName.equals("Row")) {
registerSubtags("Key", XmlResourceElement.class, callback, registeredSubtags);
}
// for device-admin api
if (tagName.equals("device-admin")) {
registerSubtags("uses-policies", XmlResourceElement.class, callback, registeredSubtags);
}
else if (tagName.equals("uses-policies")) {
registerSubtags("limit-password", XmlResourceElement.class, callback, registeredSubtags);
registerSubtags("watch-login", XmlResourceElement.class, callback, registeredSubtags);
registerSubtags("reset-password", XmlResourceElement.class, callback, registeredSubtags);
registerSubtags("force-lock", XmlResourceElement.class, callback, registeredSubtags);
registerSubtags("wipe-data", XmlResourceElement.class, callback, registeredSubtags);
registerSubtags("expire-password", XmlResourceElement.class, callback, registeredSubtags);
registerSubtags("encrypted-storage", XmlResourceElement.class, callback, registeredSubtags);
registerSubtags("disable-camera", XmlResourceElement.class, callback, registeredSubtags);
}
// DevicePolicyManager API
if (tagName.equals("preference-headers")) {
registerSubtags("header", PreferenceElement.class, callback, registeredSubtags);
}
// for preferences
Map<String, PsiClass> prefClassMap = getPreferencesClassMap(facet);
String prefClassName = element.getXmlTag().getName();
PsiClass c = prefClassMap.get(prefClassName);
// register attributes by preference class
registerAttributesForClassAndSuperclasses(facet, element, c, callback, null, skipAttrNames);
//register attributes by widget
String suffix = "Preference";
if (prefClassName.endsWith(suffix)) {
String widgetClassName = prefClassName.substring(0, prefClassName.length() - suffix.length());
PsiClass widgetClass = SimpleClassMapConstructor.findClassByTagName(facet, widgetClassName, AndroidUtils.VIEW_CLASS_NAME);
registerAttributesForClassAndSuperclasses(facet, element, widgetClass, callback, null, skipAttrNames);
}
if (c != null && isPreference(prefClassMap, c)) {
registerClassNameSubtags(tag, prefClassMap, PreferenceElement.class, registeredSubtags, callback);
}
}
@NotNull
public static Map<String, PsiClass> getPreferencesClassMap(@NotNull AndroidFacet facet) {
return facet.getClassMap(AndroidXmlResourcesUtil.PREFERENCE_CLASS_NAME, SimpleClassMapConstructor.getInstance());
}
public static void registerExtensionsForAnimation(final AndroidFacet facet,
String tagName,
AnimationElement element,
MyCallback callback,
Set<String> registeredSubtags,
Set<XmlName> skipAttrNames) {
if (tagName.equals("set")) {
for (String subtagName : AndroidAnimationUtils.getPossibleChildren(facet)) {
registerSubtags(subtagName, AnimationElement.class, callback, registeredSubtags);
}
}
final String styleableName = AndroidAnimationUtils.getStyleableNameByTagName(tagName);
final JavaPsiFacade facade = JavaPsiFacade.getInstance(facet.getModule().getProject());
final PsiClass c = ApplicationManager.getApplication().runReadAction(new Computable<PsiClass>() {
@Override
@Nullable
public PsiClass compute() {
return facade.findClass(AndroidAnimationUtils.ANIMATION_PACKAGE + '.' + styleableName,
facet.getModule().getModuleWithDependenciesAndLibrariesScope(true));
}
});
if (c != null) {
registerAttributesForClassAndSuperclasses(facet, element, c, callback, null, skipAttrNames);
}
else {
registerAttributes(facet, element, styleableName, SYSTEM_RESOURCE_PACKAGE, callback, skipAttrNames);
String layoutAnim = "LayoutAnimation";
if (styleableName.endsWith(layoutAnim) && !styleableName.equals(layoutAnim)) {
registerAttributes(facet, element, layoutAnim, SYSTEM_RESOURCE_PACKAGE, callback, skipAttrNames);
}
if (styleableName.endsWith("Animation")) {
registerAttributes(facet, element, "Animation", SYSTEM_RESOURCE_PACKAGE, callback, skipAttrNames);
}
}
}
public static void registerExtensionsForAnimator(final AndroidFacet facet,
String tagName,
AnimatorElement element,
MyCallback callback,
Set<String> registeredSubtags,
Set<XmlName> skipAttrNames) {
if (tagName.equals("set")) {
for (String subtagName : AndroidAnimatorUtil.getPossibleChildren()) {
registerSubtags(subtagName, AnimatorElement.class, callback, registeredSubtags);
}
}
registerAttributes(facet, element, "Animator", SYSTEM_RESOURCE_PACKAGE, callback, skipAttrNames);
final String styleableName = AndroidAnimatorUtil.getStyleableNameByTagName(tagName);
if (styleableName != null) {
registerAttributes(facet, element, styleableName, SYSTEM_RESOURCE_PACKAGE, callback, skipAttrNames);
}
}
public static Map<String, PsiClass> getViewClassMap(@NotNull AndroidFacet facet) {
return facet.getClassMap(AndroidUtils.VIEW_CLASS_NAME, SimpleClassMapConstructor.getInstance());
}
private static String[] getClassNames(@NotNull Collection<PsiClass> classes) {
List<String> names = new ArrayList<String>();
for (PsiClass aClass : classes) {
names.add(aClass.getName());
}
return ArrayUtil.toStringArray(names);
}
private static void registerLayoutAttributes(AndroidFacet facet,
DomElement element,
XmlTag tag,
MyCallback callback,
MyAttributeProcessor processor,
Set<XmlName> skipAttrNames) {
XmlTag parentTag = tag.getParentTag();
Map<String, PsiClass> map = getViewClassMap(facet);
if (parentTag != null) {
final String parentTagName = parentTag.getName();
if (!VIEW_MERGE.equals(parentTagName)) {
PsiClass c = map.get(parentTagName);
while (c != null) {
registerLayoutAttributes(facet, element, c, callback, processor, skipAttrNames);
c = getSuperclass(c);
}
return;
}
}
for (String className : map.keySet()) {
PsiClass c = map.get(className);
registerLayoutAttributes(facet, element, c, callback, processor, skipAttrNames);
}
}
private static void registerLayoutAttributes(AndroidFacet facet,
DomElement element,
PsiClass c,
MyCallback callback,
MyAttributeProcessor processor,
Set<XmlName> skipAttrNames) {
String styleableName = c.getName();
if (styleableName != null) {
for (String suf : LAYOUT_ATTRIBUTES_SUFS) {
registerAttributes(facet, element, new String[]{styleableName + suf}, callback, processor, skipAttrNames);
}
}
}
public static void registerExtensionsForLayout(AndroidFacet facet,
XmlTag tag,
LayoutElement element,
MyCallback callback,
Set<String> registeredSubtags,
Set<XmlName> skipAttrNames) {
Map<String, PsiClass> map = getViewClassMap(facet);
if (element instanceof Include) {
for (String className : map.keySet()) {
PsiClass c = map.get(className);
registerLayoutAttributes(facet, element, c, callback, ourLayoutAttrsProcessor, skipAttrNames);
}
return;
}
else if (element instanceof Fragment) {
registerAttributes(facet, element, new String[]{"Fragment"}, callback, ourLayoutAttrsProcessor, skipAttrNames);
} else if (element instanceof Tag) {
registerAttributes(facet, element, "ViewTag", SYSTEM_RESOURCE_PACKAGE, callback, skipAttrNames);
return;
}
else {
String tagName = tag.getName();
if (!tagName.equals("view")) {
PsiClass c = map.get(tagName);
registerAttributesForClassAndSuperclasses(facet, element, c, callback, ourLayoutAttrsProcessor, skipAttrNames);
}
else {
String[] styleableNames = getClassNames(map.values());
registerAttributes(facet, element, styleableNames, callback, ourLayoutAttrsProcessor, skipAttrNames);
}
}
registerLayoutAttributes(facet, element, tag, callback, ourLayoutAttrsProcessor, skipAttrNames);
registerClassNameSubtags(tag, map, LayoutViewElement.class, registeredSubtags, callback);
}
private static void registerClassNameSubtags(XmlTag tag,
Map<String, PsiClass> classMap,
Type type,
Set<String> registeredSubtags,
MyCallback callback) {
final Set<String> allAllowedTags = new HashSet<String>();
final Map<String, String> class2Name = new HashMap<String, String>();
for (String tagName : classMap.keySet()) {
final PsiClass aClass = classMap.get(tagName);
if (!AndroidUtils.isAbstract(aClass)) {
allAllowedTags.add(tagName);
final String qName = aClass.getQualifiedName();
final String prevTagName = class2Name.get(qName);
if (prevTagName == null || tagName.indexOf('.') == -1) {
class2Name.put(qName, tagName);
}
}
}
registerSubtags(callback, tag, allAllowedTags, class2Name.values(), registeredSubtags, type);
}
private static void registerSubtags(MyCallback callback,
XmlTag tag,
final Set<String> allowedTags,
Collection<String> tagsToComplete,
Set<String> registeredSubtags,
Type type) {
for (String tagName : tagsToComplete) {
registerSubtags(tagName, type, callback, registeredSubtags);
}
registerExistingSubtags(tag, callback, new Processor<String>() {
@Override
public boolean process(String s) {
return allowedTags.contains(s);
}
}, type);
}
public static void registerExtensionsForManifest(AndroidFacet facet,
String tagName,
ManifestElement element,
MyCallback callback,
Set<String> registeredSubtags,
Set<XmlName> skippedNames,
boolean processStaticallyDefinedElements) {
String styleableName = AndroidManifestUtils.getStyleableNameForElement(element);
if (styleableName == null) {
styleableName = AndroidManifestUtils.getStyleableNameByTagName(tagName);
}
final Set<XmlName> newSkippedNames = new HashSet<XmlName>(skippedNames);
if (!processStaticallyDefinedElements) {
for (String attrName : AndroidManifestUtils.getStaticallyDefinedAttrs(element)) {
newSkippedNames.add(new XmlName(attrName, SdkConstants.NS_RESOURCES));
}
}
SystemResourceManager manager = facet.getSystemResourceManager();
if (manager == null) return;
AttributeDefinitions attrDefs = manager.getAttributeDefinitions();
if (attrDefs == null) return;
StyleableDefinition styleable = attrDefs.getStyleableByName(styleableName);
if (styleable == null) return;
registerStyleableAttributes(element, new StyleableDefinition[]{styleable}, SdkConstants.NS_RESOURCES, callback,
new MyAttributeProcessor() {
@Override
public void process(@NotNull XmlName attrName,
@NotNull DomExtension extension,
@NotNull DomElement element) {
if (AndroidManifestUtils.isRequiredAttribute(attrName, element)) {
extension.addCustomAnnotation(new MyRequired());
}
}
}, newSkippedNames);
Set<String> subtagSet;
if (!processStaticallyDefinedElements) {
subtagSet = new HashSet<String>();
Collections.addAll(subtagSet, AndroidManifestUtils.getStaticallyDefinedSubtags(element));
}
else {
subtagSet = Collections.emptySet();
}
for (StyleableDefinition child : styleable.getChildren()) {
String childTagName = AndroidManifestUtils.getTagNameByStyleableName(child.getName());
if (childTagName != null && !subtagSet.contains(childTagName)) {
registerSubtags(childTagName, ManifestElement.class, callback, registeredSubtags);
}
}
}
public static void processAttrsAndSubtags(@NotNull AndroidDomElement element,
@NotNull MyCallback callback,
@NotNull AndroidFacet facet,
boolean processAllExistingAttrsFirst,
boolean processStaticallyDefinedElements) {
try {
XmlTag tag = element.getXmlTag();
final Set<XmlName> skippedAttributes = processAllExistingAttrsFirst
? registerExistingAttributes(facet, tag, callback, element)
: new HashSet<XmlName>();
String tagName = tag.getName();
Set<String> registeredSubtags = new HashSet<String>();
if (element instanceof ManifestElement) {
registerExtensionsForManifest(facet, tagName, (ManifestElement)element, callback, registeredSubtags,
skippedAttributes, processStaticallyDefinedElements);
}
else if (element instanceof LayoutElement) {
registerExtensionsForLayout(facet, tag, (LayoutElement)element, callback, registeredSubtags, skippedAttributes);
}
else if (element instanceof AnimationElement) {
registerExtensionsForAnimation(facet, tagName, (AnimationElement)element, callback, registeredSubtags, skippedAttributes);
}
else if (element instanceof AnimatorElement) {
registerExtensionsForAnimator(facet, tagName, (AnimatorElement)element, callback, registeredSubtags, skippedAttributes);
}
else if (element instanceof MenuElement) {
String styleableName = StringUtil.capitalize(tagName);
if (!styleableName.equals("Menu")) {
styleableName = "Menu" + styleableName;
}
registerAttributes(facet, element, styleableName, SYSTEM_RESOURCE_PACKAGE, callback, skippedAttributes);
}
else if (element instanceof XmlResourceElement) {
registerExtensionsForXmlResources(facet, tag, (XmlResourceElement)element, callback, registeredSubtags, skippedAttributes);
}
else if (element instanceof DrawableDomElement || element instanceof ColorDomElement) {
registerExtensionsForDrawable(facet, tagName, element, callback, skippedAttributes);
}
else if (element instanceof TransitionDomElement) {
registerExtensionsForTransition(facet, tagName, (TransitionDomElement)element, callback, registeredSubtags, skippedAttributes);
}
if (!processStaticallyDefinedElements) {
Collections.addAll(registeredSubtags, AndroidDomUtil.getStaticallyDefinedSubtags(element));
}
}
catch (MyStopException ignored) {
}
}
private static void registerExtensionsForDrawable(AndroidFacet facet,
String tagName,
AndroidDomElement element,
MyCallback callback,
Set<XmlName> skipAttrNames) {
final String specialStyleableName = AndroidDrawableDomUtil.SPECIAL_STYLEABLE_NAMES.get(tagName);
if (specialStyleableName != null) {
registerAttributes(facet, element, specialStyleableName, SYSTEM_RESOURCE_PACKAGE, callback, skipAttrNames);
}
if (element instanceof DrawableStateListItem || element instanceof ColorStateListItem) {
registerAttributes(facet, element, "DrawableStates", SYSTEM_RESOURCE_PACKAGE, callback, skipAttrNames);
final AttributeDefinitions attrDefs = getAttrDefs(facet);
if (attrDefs != null) {
registerAttributes(facet, element, attrDefs.getStateStyleables(), SYSTEM_RESOURCE_PACKAGE, callback, null, skipAttrNames);
}
}
else if (element instanceof LayerListItem) {
registerAttributes(facet, element, "LayerDrawableItem", SYSTEM_RESOURCE_PACKAGE, callback, skipAttrNames);
}
else if (element instanceof LevelListItem) {
registerAttributes(facet, element, "LevelListDrawableItem", SYSTEM_RESOURCE_PACKAGE, callback, skipAttrNames);
}
else if (element instanceof AnimationListItem) {
registerAttributes(facet, element, "AnimationDrawableItem", SYSTEM_RESOURCE_PACKAGE, callback, skipAttrNames);
}
else if (element instanceof AnimatedStateListTransition) {
registerAttributes(facet, element, "AnimatedStateListDrawableTransition", SYSTEM_RESOURCE_PACKAGE, callback, skipAttrNames);
}
else if (element instanceof VectorPath) {
registerAttributes(facet, element, "VectorDrawablePath", SYSTEM_RESOURCE_PACKAGE, callback, skipAttrNames);
}
else if (element instanceof VectorGroup) {
registerAttributes(facet, element, "VectorDrawableGroup", SYSTEM_RESOURCE_PACKAGE, callback, skipAttrNames);
}
else if (element instanceof VectorClipPath) {
registerAttributes(facet, element, "VectorDrawableClipPath", SYSTEM_RESOURCE_PACKAGE, callback, skipAttrNames);
}
else if (element instanceof AnimatedVectorTarget) {
registerAttributes(facet, element, "AnimatedVectorDrawableTarget", SYSTEM_RESOURCE_PACKAGE, callback, skipAttrNames);
}
}
public static void registerExtensionsForTransition(final AndroidFacet facet,
String tagName,
TransitionDomElement element,
MyCallback callback,
Set<String> registeredSubTags,
Set<XmlName> skipAttrNames) {
if (tagName.equals(FADE_TAG)) {
registerTransition(facet, element, callback, registeredSubTags, skipAttrNames, "Fade");
} else if (tagName.equals(TRANSITION_SET_TAG)) {
registerTransition(facet, element, callback, registeredSubTags, skipAttrNames, "TransitionSet");
registerSubtags(TRANSITION_SET_TAG, TransitionSet.class, callback, registeredSubTags);
// See TransitionInflater#createTransitionFromXml:
registerSubtags(FADE_TAG, Fade.class, callback, registeredSubTags);
registerSubtags(CHANGE_BOUNDS_TAG, ChangeBounds.class, callback, registeredSubTags);
registerSubtags(SLIDE_TAG, Slide.class, callback, registeredSubTags);
registerSubtags(EXPLODE_TAG, Explode.class, callback, registeredSubTags);
registerSubtags(CHANGE_IMAGE_TRANSFORM_TAG, ChangeImageTransform.class, callback, registeredSubTags);
registerSubtags(CHANGE_TRANSFORM_TAG, ChangeTransform.class, callback, registeredSubTags);
registerSubtags(CHANGE_CLIP_BOUNDS_TAG, ChangeClipBounds.class, callback, registeredSubTags);
registerSubtags(AUTO_TRANSITION_TAG, AutoTransition.class, callback, registeredSubTags);
registerSubtags(RECOLOR_TAG, Recolor.class, callback, registeredSubTags);
registerSubtags(CHANGE_SCROLL_TAG, ChangeScroll.class, callback, registeredSubTags);
registerSubtags(ARC_MOTION_TAG, ArcMotion.class, callback, registeredSubTags);
registerSubtags(PATH_MOTION_TAG, PathMotion.class, callback, registeredSubTags);
registerSubtags(PATTERN_PATH_MOTION_TAG, PatternPathMotion.class, callback, registeredSubTags);
// Check whether I also have for "transition":Transition
} else if (tagName.equals(TRANSITION_MANAGER_TAG)) {
registerSubtags(TRANSITION_TAG, TransitionTag.class, callback, registeredSubTags);
} else if (tagName.equals(TRANSITION_TAG)) {
registerAttributes(facet, element, "TransitionManager", SYSTEM_RESOURCE_PACKAGE, callback, skipAttrNames);
} else if (tagName.equals(TARGETS_TAG)) {
registerSubtags(TARGET_TAG, Target.class, callback, registeredSubTags);
} else if (tagName.equals(TARGET_TAG)) {
registerAttributes(facet, element, "TransitionTarget", SYSTEM_RESOURCE_PACKAGE, callback, skipAttrNames);
} else if (tagName.equals(SLIDE_TAG)) {
registerTransition(facet, element, callback, registeredSubTags, skipAttrNames, "Slide");
} else if (tagName.equals(CHANGE_TRANSFORM_TAG)) {
registerTransition(facet, element, callback, registeredSubTags, skipAttrNames, "ChangeTransform");
} else if (tagName.equals(ARC_MOTION_TAG)) {
registerTransition(facet, element, callback, registeredSubTags, skipAttrNames, "ArcMotion");
} else if (tagName.equals(PATTERN_PATH_MOTION_TAG)) {
registerTransition(facet, element, callback, registeredSubTags, skipAttrNames, "PatternPathMotion");
} else if (tagName.equals(AUTO_TRANSITION_TAG)
|| tagName.equals(CHANGE_BOUNDS_TAG)
|| tagName.equals(EXPLODE_TAG)
|| tagName.equals(CHANGE_IMAGE_TRANSFORM_TAG)
|| tagName.equals(CHANGE_CLIP_BOUNDS_TAG)
|| tagName.equals(RECOLOR_TAG)
|| tagName.equals(CHANGE_SCROLL_TAG)
|| tagName.equals(PATH_MOTION_TAG)) {
registerTransition(facet, element, callback, registeredSubTags, skipAttrNames, null);
}
}
public static void registerTransition(AndroidFacet facet,
TransitionDomElement element,
MyCallback callback,
Set<String> registeredSubTags,
Set<XmlName> skipAttrNames,
@Nullable String specific) {
registerAttributes(facet, element, "Transition", SYSTEM_RESOURCE_PACKAGE, callback, skipAttrNames);
if (specific != null) {
registerAttributes(facet, element, specific, SYSTEM_RESOURCE_PACKAGE, callback, skipAttrNames);
}
registerSubtags(TARGETS_TAG, Targets.class, callback, registeredSubTags);
}
@Nullable
private static AttributeDefinitions getAttrDefs(AndroidFacet facet) {
final SystemResourceManager manager = facet.getSystemResourceManager();
return manager != null ? manager.getAttributeDefinitions() : null;
}
private static void registerSubtags(@NotNull String name, Type type, MyCallback callback, Set<String> registeredTags) {
callback.processSubtag(new XmlName(name), type);
registeredTags.add(name);
}
private static void registerExistingSubtags(@NotNull XmlTag tag,
@NotNull MyCallback callback,
@Nullable Processor<String> filter,
@NotNull Type type) {
XmlTag[] subtags = tag.getSubTags();
for (XmlTag subtag : subtags) {
String localName = subtag.getLocalName();
if (filter == null || filter.process(localName)) {
if (!localName.endsWith(CompletionUtil.DUMMY_IDENTIFIER_TRIMMED)) {
callback.processSubtag(new XmlName(localName), type);
}
}
}
}
@NotNull
private static Set<XmlName> registerExistingAttributes(AndroidFacet facet,
XmlTag tag,
MyCallback callback,
AndroidDomElement element) {
final Set<XmlName> result = new HashSet<XmlName>();
XmlAttribute[] attrs = tag.getAttributes();
for (XmlAttribute attr : attrs) {
String localName = attr.getLocalName();
if (!localName.endsWith(CompletionUtil.DUMMY_IDENTIFIER_TRIMMED)) {
if (!"xmlns".equals(attr.getNamespacePrefix())) {
AttributeDefinition attrDef = AndroidDomUtil.getAttributeDefinition(facet, attr);
if (attrDef != null) {
String namespace = attr.getNamespace();
result.add(new XmlName(attr.getLocalName(), attr.getNamespace()));
registerAttribute(attrDef, null, namespace.length() > 0 ? namespace : null, callback, null, element);
}
}
}
}
return result;
}
@Override
public void registerExtensions(@NotNull AndroidDomElement element, @NotNull final DomExtensionsRegistrar registrar) {
final AndroidFacet facet = AndroidFacet.getInstance(element);
if (facet == null) {
return;
}
processAttrsAndSubtags(element, new MyCallback() {
@Nullable
@Override
public DomExtension processAttribute(@NotNull XmlName xmlName,
@NotNull AttributeDefinition attrDef,
@Nullable String parentStyleableName) {
Set<AttributeFormat> formats = attrDef.getFormats();
Class valueClass = formats.size() == 1 ? getValueClass(formats.iterator().next()) : String.class;
registrar.registerAttributeChildExtension(xmlName, GenericAttributeValue.class);
return registrar.registerGenericAttributeValueChildExtension(xmlName, valueClass);
}
@Override
public void processSubtag(@NotNull XmlName xmlName, @NotNull Type type) {
registrar.registerCollectionChildrenExtension(xmlName, type);
}
}, facet, true, false);
}
private interface MyAttributeProcessor {
void process(@NotNull XmlName attrName, @NotNull DomExtension extension, @NotNull DomElement element);
}
private static class MyRequired implements Required {
@Override
public boolean value() {
return true;
}
@Override
public boolean nonEmpty() {
return true;
}
@Override
public boolean identifier() {
return false;
}
@Override
public Class<? extends Annotation> annotationType() {
return Required.class;
}
}
private static class MyStopException extends RuntimeException {
}
public static class MyCallback {
protected void stop() {
throw new MyStopException();
}
@Nullable
DomExtension processAttribute(@NotNull XmlName xmlName, @NotNull AttributeDefinition attrDef, @Nullable String parentStyleableName) {
return null;
}
void processSubtag(@NotNull XmlName xmlName, @NotNull Type type) {
}
}
}