blob: 68d74e0bbc7a88771a219363a8f64f2bcb8ec7a2 [file] [log] [blame]
/*
* Copyright (C) 2019 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 org.jetbrains.android.dom;
import com.google.common.collect.Multimap;
import com.intellij.codeInsight.completion.CompletionUtil;
import com.intellij.psi.xml.XmlTag;
import com.intellij.util.xml.XmlName;
import java.lang.reflect.Type;
import java.util.Collection;
import java.util.function.Predicate;
import org.jetbrains.android.dom.navigation.NavElement;
import org.jetbrains.android.dom.navigation.NavigationSchema;
import org.jetbrains.android.dom.resources.PublicResource;
import org.jetbrains.android.dom.resources.Resources;
import org.jetbrains.android.dom.xml.PreferenceElementBase;
import org.jetbrains.android.dom.xml.XmlResourceElement;
import org.jetbrains.android.facet.AndroidFacet;
import org.jetbrains.annotations.NotNull;
/**
* Utility functions for enumerating available children tag types in the context of a given XML tag.
*
* Entry point is {@link #processSubTags(AndroidFacet, AndroidDomElement, boolean, SubtagProcessor)}, look for a
* Javadoc there.
*/
public class SubtagsProcessingUtil {
private SubtagsProcessingUtil() {
}
/**
* Provides information about available tags for resources in xml/ folder
*/
public static void registerXmlResourcesSubtags(@NotNull AndroidFacet facet,
@NotNull XmlTag tag,
@NotNull SubtagProcessor subtagProcessor,
boolean processExistingSubTags) {
final String tagName = tag.getName();
switch (tagName) {
case "searchable":
subtagProcessor.processSubtag("actionkey", XmlResourceElement.class);
break;
// for keyboard api
case "Keyboard":
subtagProcessor.processSubtag("Row", XmlResourceElement.class);
break;
case "Row":
subtagProcessor.processSubtag("Key", XmlResourceElement.class);
break;
// for device-admin api
case "device-admin":
subtagProcessor.processSubtag("uses-policies", XmlResourceElement.class);
break;
case "uses-policies":
subtagProcessor.processSubtag("limit-password", XmlResourceElement.class);
subtagProcessor.processSubtag("watch-login", XmlResourceElement.class);
subtagProcessor.processSubtag("reset-password", XmlResourceElement.class);
subtagProcessor.processSubtag("force-lock", XmlResourceElement.class);
subtagProcessor.processSubtag("wipe-data", XmlResourceElement.class);
subtagProcessor.processSubtag("set-global-proxy", XmlResourceElement.class);
subtagProcessor.processSubtag("expire-password", XmlResourceElement.class);
subtagProcessor.processSubtag("encrypted-storage", XmlResourceElement.class);
subtagProcessor.processSubtag("disable-camera", XmlResourceElement.class);
subtagProcessor.processSubtag("disable-keyguard-features", XmlResourceElement.class);
break;
// DevicePolicyManager API
case "preference-headers":
subtagProcessor.processSubtag("header", PreferenceElementBase.class);
break;
}
}
private static void registerSubtags(@NotNull XmlTag tag,
@NotNull Collection<String> allowedTags,
@NotNull Collection<String> tagsToComplete,
@NotNull Type type,
@NotNull SubtagProcessor subtagProcessor,
boolean processExistingSubTags) {
for (String tagName : tagsToComplete) {
subtagProcessor.processSubtag(tagName, type);
}
if (processExistingSubTags) {
processExistingSubTags(tag, allowedTags::contains, type, subtagProcessor);
}
}
/**
* Enumerate children types that are valid inside a given XML tag, represented by {@link AndroidDomElement}.
* Proceeds by dispatching on element type by instanceof checks, "returns" information about available tags
* via {@code subtagCallback}.
*/
public static void processSubTags(@NotNull AndroidFacet facet,
@NotNull AndroidDomElement element,
boolean processExistingSubTags,
@NotNull SubtagProcessor subtagProcessor) {
if (element instanceof XmlResourceElement) {
XmlTag tag = element.getXmlTag();
if (tag != null) {
registerXmlResourcesSubtags(facet, tag, subtagProcessor, true);
}
}
else if (element instanceof NavElement) {
try {
NavigationSchema.createIfNecessary(facet.getModule());
}
catch (ClassNotFoundException e) {
// We must not have added the nav library dependency yet, but encountered a nav file. Ignore for now.
return;
}
NavigationSchema schema = NavigationSchema.get(facet.getModule());
Multimap<Class<? extends AndroidDomElement>, String> subtags = schema.getDestinationSubtags(element.getXmlTag().getName());
for (Class<? extends AndroidDomElement> c : subtags.keys()) {
registerSubtags(element.getXmlTag(), subtags.get(c), subtags.get(c), c, subtagProcessor, processExistingSubTags);
}
}
else if (element instanceof Resources && facet.getConfiguration().isLibraryProject()) {
subtagProcessor.processSubtag("public", PublicResource.class);
}
}
/**
* Enumerate types of XML tags that are already are children of a tag, via {@code subtagProcessor}
*/
private static void processExistingSubTags(@NotNull XmlTag tag,
@NotNull Predicate<String> filter,
@NotNull Type type,
@NotNull SubtagProcessor subtagProcessor) {
XmlTag[] subtags = tag.getSubTags();
for (XmlTag subtag : subtags) {
String localName = subtag.getLocalName();
if (filter.test(localName)) {
// Skip child tag for which code completion is invoked at the moment
if (!localName.endsWith(CompletionUtil.DUMMY_IDENTIFIER_TRIMMED)) {
subtagProcessor.processSubtag(new XmlName(localName), type);
}
}
}
}
public interface SubtagProcessor {
void processSubtag(@NotNull XmlName xmlName, @NotNull Type type);
default void processSubtag(@NotNull String xmlName, @NotNull Type type) {
processSubtag(new XmlName(xmlName), type);
}
}
}