aboutsummaryrefslogtreecommitdiff
path: root/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/descriptors/CustomViewDescriptorService.java
diff options
context:
space:
mode:
authorTor Norbye <tnorbye@google.com>2012-04-25 17:18:48 -0700
committerTor Norbye <tnorbye@google.com>2012-04-27 16:02:27 -0700
commit57f1244facb6c3de1fe8011ab66cdc3dd9ce0b61 (patch)
treea964edbacd5e2403c1ec1b6a5ee7034d1a4b3f8a /eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/descriptors/CustomViewDescriptorService.java
parent1d853f24d12b3a777879610781d53236e8c33c36 (diff)
downloadsdk-57f1244facb6c3de1fe8011ab66cdc3dd9ce0b61.tar.gz
Make XML code completion work for custom views & attributes
This changeset improves the custom view handling such that XML code completion offers any custom attributes (along with documentation tooltips and type information). This is done by finding any declare-styleable attributes defined in the project defining the custom view. In particular, this will also work for the GridLayout library project shipped as part of the android.support package. The fix is not tied to completion; it's improving the metadata descriptors computed for custom views, so this fix for example also makes these custom attributes show up in the property sheet in the layout editor. Finally, this changeset fixes a couple of bugs in this area: - One initialization path was not looking up custom views for unknown descriptors, this might be the fix for http://code.google.com/p/android/issues/detail?id=23020 - There was a bug in the code which looks up the namespace prefix to use for a given namespace URI: it would return the default Android prefix for some non-Android URIs. - Small performance tweak to avoid regexp construction in a loop where it's not needed Change-Id: I55dfcea6e6ea9d7c38e18a47b757678176facbd2
Diffstat (limited to 'eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/descriptors/CustomViewDescriptorService.java')
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/descriptors/CustomViewDescriptorService.java212
1 files changed, 208 insertions, 4 deletions
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/descriptors/CustomViewDescriptorService.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/descriptors/CustomViewDescriptorService.java
index 5abbb0c97..23aa2a0b0 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/descriptors/CustomViewDescriptorService.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/descriptors/CustomViewDescriptorService.java
@@ -16,19 +16,43 @@
package com.android.ide.eclipse.adt.internal.editors.layout.descriptors;
+import static com.android.ide.common.layout.LayoutConstants.ANDROID_NS_NAME_PREFIX;
+import static com.android.ide.common.layout.LayoutConstants.ANDROID_URI;
import static com.android.sdklib.SdkConstants.CLASS_VIEWGROUP;
-
+import static com.android.tools.lint.detector.api.LintConstants.AUTO_URI;
+import static com.android.tools.lint.detector.api.LintConstants.URI_PREFIX;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.ide.common.resources.ResourceFile;
+import com.android.ide.common.resources.ResourceItem;
+import com.android.ide.common.resources.platform.AttributeInfo;
+import com.android.ide.common.resources.platform.AttrsXmlParser;
import com.android.ide.common.resources.platform.ViewClassInfo;
+import com.android.ide.common.resources.platform.ViewClassInfo.LayoutParamsInfo;
+import com.android.ide.eclipse.adt.AdtPlugin;
+import com.android.ide.eclipse.adt.AdtUtils;
import com.android.ide.eclipse.adt.internal.editors.IconFactory;
import com.android.ide.eclipse.adt.internal.editors.descriptors.AttributeDescriptor;
import com.android.ide.eclipse.adt.internal.editors.descriptors.DescriptorsUtils;
import com.android.ide.eclipse.adt.internal.editors.descriptors.ElementDescriptor;
+import com.android.ide.eclipse.adt.internal.editors.manifest.ManifestInfo;
+import com.android.ide.eclipse.adt.internal.resources.manager.ProjectResources;
+import com.android.ide.eclipse.adt.internal.resources.manager.ResourceManager;
import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData;
+import com.android.ide.eclipse.adt.internal.sdk.ProjectState;
import com.android.ide.eclipse.adt.internal.sdk.Sdk;
+import com.android.resources.ResourceType;
import com.android.sdklib.IAndroidTarget;
+import com.google.common.collect.ObjectArrays;
import org.eclipse.core.resources.IProject;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.resources.IWorkspaceRoot;
+import org.eclipse.core.resources.ResourcesPlugin;
+import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.NullProgressMonitor;
+import org.eclipse.jdt.core.IClassFile;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.ITypeHierarchy;
@@ -36,8 +60,13 @@ import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.swt.graphics.Image;
+import java.util.ArrayList;
+import java.util.Collection;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.List;
+import java.util.Map;
+import java.util.Set;
/**
* Service responsible for creating/managing {@link ViewElementDescriptor} objects for custom
@@ -162,11 +191,24 @@ public final class CustomViewDescriptorService {
if (parentDescriptor != null) {
// we have a valid parent, lets create a new ViewElementDescriptor.
-
+ List<AttributeDescriptor> attrList = new ArrayList<AttributeDescriptor>();
+ List<AttributeDescriptor> paramList = new ArrayList<AttributeDescriptor>();
+ findCustomDescriptors(project, type, attrList, paramList);
+
+ AttributeDescriptor[] attributes =
+ getAttributeDescriptor(type, parentDescriptor);
+ if (!attrList.isEmpty()) {
+ attributes = join(attrList, attributes);
+ }
+ AttributeDescriptor[] layoutAttributes =
+ getLayoutAttributeDescriptors(type, parentDescriptor);
+ if (!paramList.isEmpty()) {
+ layoutAttributes = join(paramList, layoutAttributes);
+ }
String name = DescriptorsUtils.getBasename(fqcn);
ViewElementDescriptor descriptor = new CustomViewDescriptor(name, fqcn,
- getAttributeDescriptor(type, parentDescriptor),
- getLayoutAttributeDescriptors(type, parentDescriptor),
+ attributes,
+ layoutAttributes,
parentDescriptor.getChildren());
descriptor.setSuperClass(parentDescriptor);
@@ -193,6 +235,168 @@ public final class CustomViewDescriptorService {
return null;
}
+ private static AttributeDescriptor[] join(
+ @NonNull List<AttributeDescriptor> attributeList,
+ @NonNull AttributeDescriptor[] attributes) {
+ if (!attributeList.isEmpty()) {
+ return ObjectArrays.concat(
+ attributeList.toArray(new AttributeDescriptor[attributeList.size()]),
+ attributes,
+ AttributeDescriptor.class);
+ } else {
+ return attributes;
+ }
+
+ }
+
+ /** Cache used by {@link #getParser(ResourceFile)} */
+ private Map<ResourceFile, AttrsXmlParser> mParserCache;
+
+ private AttrsXmlParser getParser(ResourceFile file) {
+ if (mParserCache == null) {
+ mParserCache = new HashMap<ResourceFile, AttrsXmlParser>();
+ }
+
+ AttrsXmlParser parser = mParserCache.get(file);
+ if (parser == null) {
+ parser = new AttrsXmlParser(
+ file.getFile().getOsLocation(),
+ AdtPlugin.getDefault());
+ parser.preload();
+ mParserCache.put(file, parser);
+ }
+
+ return parser;
+ }
+
+ /** Compute/find the styleable resources for the given type, if possible */
+ private void findCustomDescriptors(
+ IProject project,
+ IType type,
+ List<AttributeDescriptor> customAttributes,
+ List<AttributeDescriptor> customLayoutAttributes) {
+ // Look up the project where the type is declared (could be a library project;
+ // we cannot use type.getJavaProject().getProject())
+ IProject library = getProjectDeclaringType(type);
+ if (library == null) {
+ library = project;
+ }
+
+ String className = type.getElementName();
+ Set<ResourceFile> resourceFiles = findAttrsFiles(library, className);
+ if (resourceFiles != null && resourceFiles.size() > 0) {
+ String appUri = getAppResUri(project);
+ for (ResourceFile file : resourceFiles) {
+ AttrsXmlParser attrsXmlParser = getParser(file);
+ String fqcn = type.getFullyQualifiedName();
+
+ // Attributes
+ ViewClassInfo classInfo = new ViewClassInfo(true, fqcn, className);
+ attrsXmlParser.loadViewAttributes(classInfo);
+ appendAttributes(customAttributes, classInfo.getAttributes(), appUri);
+
+ // Layout params
+ LayoutParamsInfo layoutInfo = new ViewClassInfo.LayoutParamsInfo(
+ classInfo, "Layout", null /*superClassInfo*/); //$NON-NLS-1$
+ attrsXmlParser.loadLayoutParamsAttributes(layoutInfo);
+ appendAttributes(customLayoutAttributes, layoutInfo.getAttributes(), appUri);
+ }
+ }
+ }
+
+ /**
+ * Finds the set of XML files (if any) in the given library declaring
+ * attributes for the given class name
+ */
+ @Nullable
+ private Set<ResourceFile> findAttrsFiles(IProject library, String className) {
+ Set<ResourceFile> resourceFiles = null;
+ ResourceManager manager = ResourceManager.getInstance();
+ ProjectResources resources = manager.getProjectResources(library);
+ if (resources != null) {
+ Collection<ResourceItem> items =
+ resources.getResourceItemsOfType(ResourceType.DECLARE_STYLEABLE);
+ for (ResourceItem item : items) {
+ String viewName = item.getName();
+ if (viewName.equals(className)
+ || (viewName.startsWith(className)
+ && viewName.equals(className + "_Layout"))) { //$NON-NLS-1$
+ if (resourceFiles == null) {
+ resourceFiles = new HashSet<ResourceFile>();
+ }
+ resourceFiles.addAll(item.getSourceFileList());
+ }
+ }
+ }
+ return resourceFiles;
+ }
+
+ /**
+ * Find the project containing this type declaration. We cannot use
+ * {@link IType#getJavaProject()} since that will return the including
+ * project and we're after the library project such that we can find the
+ * attrs.xml file in the same project.
+ */
+ @Nullable
+ private IProject getProjectDeclaringType(IType type) {
+ IClassFile classFile = type.getClassFile();
+ if (classFile != null) {
+ IPath path = classFile.getPath();
+ IWorkspaceRoot workspace = ResourcesPlugin.getWorkspace().getRoot();
+ IResource resource;
+ if (path.isAbsolute()) {
+ resource = AdtUtils.fileToResource(path.toFile());
+ } else {
+ resource = workspace.findMember(path);
+ }
+ if (resource != null && resource.getProject() != null) {
+ return resource.getProject();
+ }
+ }
+
+ return null;
+ }
+
+ /** Returns the name space to use for application attributes */
+ private String getAppResUri(IProject project) {
+ String appResource;
+ ProjectState projectState = Sdk.getProjectState(project);
+ if (projectState != null && projectState.isLibrary()) {
+ appResource = AUTO_URI;
+ } else {
+ ManifestInfo manifestInfo = ManifestInfo.get(project);
+ appResource = URI_PREFIX + manifestInfo.getPackage();
+ }
+ return appResource;
+ }
+
+
+ /** Append the {@link AttributeInfo} objects converted {@link AttributeDescriptor}
+ * objects into the given attribute list.
+ * <p>
+ * This is nearly identical to
+ * {@link DescriptorsUtils#appendAttribute(List, String, String, AttributeInfo, boolean, Map)}
+ * but it handles namespace declarations in the attrs.xml file where the android:
+ * namespace is included in the names.
+ */
+ private static void appendAttributes(List<AttributeDescriptor> attributes,
+ AttributeInfo[] attributeInfos, String appResource) {
+ // Custom attributes
+ for (AttributeInfo info : attributeInfos) {
+ String nsUri;
+ if (info.getName().startsWith(ANDROID_NS_NAME_PREFIX)) {
+ info.setName(info.getName().substring(ANDROID_NS_NAME_PREFIX.length()));
+ nsUri = ANDROID_URI;
+ } else {
+ nsUri = appResource;
+ }
+
+ DescriptorsUtils.appendAttribute(attributes,
+ null /*elementXmlName*/, nsUri, info, false /*required*/,
+ null /*overrides*/);
+ }
+ }
+
/**
* Computes (if needed) and returns the {@link ViewElementDescriptor} for the specified type.
*