diff options
author | Tor Norbye <tnorbye@google.com> | 2012-04-25 17:18:48 -0700 |
---|---|---|
committer | Tor Norbye <tnorbye@google.com> | 2012-04-27 16:02:27 -0700 |
commit | 57f1244facb6c3de1fe8011ab66cdc3dd9ce0b61 (patch) | |
tree | a964edbacd5e2403c1ec1b6a5ee7034d1a4b3f8a /eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/descriptors/CustomViewDescriptorService.java | |
parent | 1d853f24d12b3a777879610781d53236e8c33c36 (diff) | |
download | sdk-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.java | 212 |
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. * |