blob: 99549ab89db9ea553bc75995f495a4a37a6a40cb [file] [log] [blame]
/*
* Copyright (C) 2007 The Android Open Source Project
*
* Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
*
* 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.ide.eclipse.adt.internal.editors.layout;
import static com.android.SdkConstants.ANDROID_PKG_PREFIX;
import static com.android.SdkConstants.ATTR_CLASS;
import static com.android.SdkConstants.ATTR_CONTEXT;
import static com.android.SdkConstants.ATTR_NAME;
import static com.android.SdkConstants.CLASS_ACTIVITY;
import static com.android.SdkConstants.CLASS_FRAGMENT;
import static com.android.SdkConstants.CLASS_V4_FRAGMENT;
import static com.android.SdkConstants.CLASS_VIEW;
import static com.android.SdkConstants.VIEW_FRAGMENT;
import static com.android.SdkConstants.VIEW_TAG;
import com.android.annotations.Nullable;
import com.android.annotations.VisibleForTesting;
import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.ide.eclipse.adt.internal.editors.AndroidContentAssist;
import com.android.ide.eclipse.adt.internal.editors.descriptors.ElementDescriptor;
import com.android.ide.eclipse.adt.internal.editors.layout.descriptors.CustomViewDescriptorService;
import com.android.ide.eclipse.adt.internal.editors.layout.descriptors.ViewElementDescriptor;
import com.android.ide.eclipse.adt.internal.editors.layout.gle2.CustomViewFinder;
import com.android.ide.eclipse.adt.internal.project.BaseProjectHelper;
import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData;
import com.google.common.collect.Lists;
import com.google.common.collect.ObjectArrays;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.ITypeHierarchy;
import org.eclipse.jface.text.contentassist.ICompletionProposal;
import org.w3c.dom.Node;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* Content Assist Processor for /res/layout XML files
*/
@VisibleForTesting
public final class LayoutContentAssist extends AndroidContentAssist {
/**
* Constructor for LayoutContentAssist
*/
public LayoutContentAssist() {
super(AndroidTargetData.DESCRIPTOR_LAYOUT);
}
@Override
protected Object[] getChoicesForElement(String parent, Node currentNode) {
Object[] choices = super.getChoicesForElement(parent, currentNode);
if (choices == null) {
if (currentNode.getParentNode().getNodeType() == Node.ELEMENT_NODE) {
String parentName = currentNode.getParentNode().getNodeName();
if (parentName.indexOf('.') != -1) {
// Custom view with unknown children; just use the root descriptor
// to get all eligible views instead
ElementDescriptor[] children = getRootDescriptor().getChildren();
for (ElementDescriptor e : children) {
if (e.getXmlName().startsWith(parent)) {
return sort(children);
}
}
}
}
}
if (choices == null && parent.length() >= 1 && Character.isLowerCase(parent.charAt(0))) {
// Custom view prefix?
List<ElementDescriptor> descriptors = getCustomViews();
if (descriptors != null && !descriptors.isEmpty()) {
List<ElementDescriptor> matches = Lists.newArrayList();
for (ElementDescriptor descriptor : descriptors) {
if (descriptor.getXmlLocalName().startsWith(parent)) {
matches.add(descriptor);
}
}
if (!matches.isEmpty()) {
return matches.toArray(new ElementDescriptor[matches.size()]);
}
}
}
return choices;
}
@Override
protected ElementDescriptor[] getElementChoicesForTextNode(Node parentNode) {
ElementDescriptor[] choices = super.getElementChoicesForTextNode(parentNode);
// Add in custom views, if any
List<ElementDescriptor> descriptors = getCustomViews();
if (descriptors != null && !descriptors.isEmpty()) {
ElementDescriptor[] array = descriptors.toArray(
new ElementDescriptor[descriptors.size()]);
choices = ObjectArrays.concat(choices, array, ElementDescriptor.class);
choices = sort(choices);
}
return choices;
}
@Nullable
private List<ElementDescriptor> getCustomViews() {
// Add in custom views, if any
IProject project = mEditor.getProject();
CustomViewFinder finder = CustomViewFinder.get(project);
Collection<String> views = finder.getAllViews();
if (views == null) {
finder.refresh();
views = finder.getAllViews();
}
if (views != null && !views.isEmpty()) {
List<ElementDescriptor> descriptors = Lists.newArrayListWithExpectedSize(views.size());
CustomViewDescriptorService customViews = CustomViewDescriptorService.getInstance();
for (String fqcn : views) {
ViewElementDescriptor descriptor = customViews.getDescriptor(project, fqcn);
if (descriptor != null) {
descriptors.add(descriptor);
}
}
return descriptors;
}
return null;
}
@Override
protected boolean computeAttributeValues(List<ICompletionProposal> proposals, int offset,
String parentTagName, String attributeName, Node node, String wordPrefix,
boolean skipEndTag, int replaceLength) {
super.computeAttributeValues(proposals, offset, parentTagName, attributeName, node,
wordPrefix, skipEndTag, replaceLength);
boolean projectOnly = false;
List<String> superClasses = null;
if (VIEW_FRAGMENT.equals(parentTagName) && (attributeName.endsWith(ATTR_NAME)
|| attributeName.equals(ATTR_CLASS))) {
// Insert fragment class matches
superClasses = Arrays.asList(CLASS_V4_FRAGMENT, CLASS_FRAGMENT);
} else if (VIEW_TAG.equals(parentTagName) && attributeName.endsWith(ATTR_CLASS)) {
// Insert custom view matches
superClasses = Collections.singletonList(CLASS_VIEW);
projectOnly = true;
} else if (attributeName.endsWith(ATTR_CONTEXT)) {
// Insert activity matches
superClasses = Collections.singletonList(CLASS_ACTIVITY);
}
if (superClasses != null) {
IProject project = mEditor.getProject();
if (project == null) {
return false;
}
try {
IJavaProject javaProject = BaseProjectHelper.getJavaProject(project);
IType type = javaProject.findType(superClasses.get(0));
Set<IType> elements = new HashSet<IType>();
if (type != null) {
ITypeHierarchy hierarchy = type.newTypeHierarchy(new NullProgressMonitor());
IType[] allSubtypes = hierarchy.getAllSubtypes(type);
for (IType subType : allSubtypes) {
if (!projectOnly || subType.getResource() != null) {
elements.add(subType);
}
}
}
assert superClasses.size() <= 2; // If more, need to do additional work below
if (superClasses.size() == 2) {
type = javaProject.findType(superClasses.get(1));
if (type != null) {
ITypeHierarchy hierarchy = type.newTypeHierarchy(
new NullProgressMonitor());
IType[] allSubtypes = hierarchy.getAllSubtypes(type);
for (IType subType : allSubtypes) {
if (!projectOnly || subType.getResource() != null) {
elements.add(subType);
}
}
}
}
List<IType> sorted = new ArrayList<IType>(elements);
Collections.sort(sorted, new Comparator<IType>() {
@Override
public int compare(IType type1, IType type2) {
String fqcn1 = type1.getFullyQualifiedName();
String fqcn2 = type2.getFullyQualifiedName();
int category1 = fqcn1.startsWith(ANDROID_PKG_PREFIX) ? 1 : -1;
int category2 = fqcn2.startsWith(ANDROID_PKG_PREFIX) ? 1 : -1;
if (category1 != category2) {
return category1 - category2;
}
return fqcn1.compareTo(fqcn2);
}
});
addMatchingProposals(proposals, sorted.toArray(), offset, node, wordPrefix,
(char) 0, false /* isAttribute */, false /* isNew */,
false /* skipEndTag */, replaceLength);
return true;
} catch (CoreException e) {
AdtPlugin.log(e, null);
}
}
return false;
}
}