1 /* 2 * Copyright (C) 2007 The Android Open Source Project 3 * 4 * Licensed under the Eclipse Public License, Version 1.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.eclipse.org/org/documents/epl-v10.php 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.ide.eclipse.adt.internal.editors.layout; 18 19 import static com.android.SdkConstants.ANDROID_PKG_PREFIX; 20 import static com.android.SdkConstants.ATTR_CLASS; 21 import static com.android.SdkConstants.ATTR_CONTEXT; 22 import static com.android.SdkConstants.ATTR_NAME; 23 import static com.android.SdkConstants.CLASS_ACTIVITY; 24 import static com.android.SdkConstants.CLASS_FRAGMENT; 25 import static com.android.SdkConstants.CLASS_V4_FRAGMENT; 26 import static com.android.SdkConstants.CLASS_VIEW; 27 import static com.android.SdkConstants.VIEW_FRAGMENT; 28 import static com.android.SdkConstants.VIEW_TAG; 29 30 import com.android.annotations.Nullable; 31 import com.android.annotations.VisibleForTesting; 32 import com.android.ide.eclipse.adt.AdtPlugin; 33 import com.android.ide.eclipse.adt.internal.editors.AndroidContentAssist; 34 import com.android.ide.eclipse.adt.internal.editors.descriptors.ElementDescriptor; 35 import com.android.ide.eclipse.adt.internal.editors.layout.descriptors.CustomViewDescriptorService; 36 import com.android.ide.eclipse.adt.internal.editors.layout.descriptors.ViewElementDescriptor; 37 import com.android.ide.eclipse.adt.internal.editors.layout.gle2.CustomViewFinder; 38 import com.android.ide.eclipse.adt.internal.project.BaseProjectHelper; 39 import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData; 40 import com.google.common.collect.Lists; 41 import com.google.common.collect.ObjectArrays; 42 43 import org.eclipse.core.resources.IProject; 44 import org.eclipse.core.runtime.CoreException; 45 import org.eclipse.core.runtime.NullProgressMonitor; 46 import org.eclipse.jdt.core.IJavaProject; 47 import org.eclipse.jdt.core.IType; 48 import org.eclipse.jdt.core.ITypeHierarchy; 49 import org.eclipse.jface.text.contentassist.ICompletionProposal; 50 import org.w3c.dom.Node; 51 52 import java.util.ArrayList; 53 import java.util.Arrays; 54 import java.util.Collection; 55 import java.util.Collections; 56 import java.util.Comparator; 57 import java.util.HashSet; 58 import java.util.List; 59 import java.util.Set; 60 61 /** 62 * Content Assist Processor for /res/layout XML files 63 */ 64 @VisibleForTesting 65 public final class LayoutContentAssist extends AndroidContentAssist { 66 67 /** 68 * Constructor for LayoutContentAssist 69 */ 70 public LayoutContentAssist() { 71 super(AndroidTargetData.DESCRIPTOR_LAYOUT); 72 } 73 74 @Override 75 protected Object[] getChoicesForElement(String parent, Node currentNode) { 76 Object[] choices = super.getChoicesForElement(parent, currentNode); 77 if (choices == null) { 78 if (currentNode.getParentNode().getNodeType() == Node.ELEMENT_NODE) { 79 String parentName = currentNode.getParentNode().getNodeName(); 80 if (parentName.indexOf('.') != -1) { 81 // Custom view with unknown children; just use the root descriptor 82 // to get all eligible views instead 83 ElementDescriptor[] children = getRootDescriptor().getChildren(); 84 for (ElementDescriptor e : children) { 85 if (e.getXmlName().startsWith(parent)) { 86 return sort(children); 87 } 88 } 89 } 90 } 91 } 92 93 if (choices == null && parent.length() >= 1 && Character.isLowerCase(parent.charAt(0))) { 94 // Custom view prefix? 95 List<ElementDescriptor> descriptors = getCustomViews(); 96 if (descriptors != null && !descriptors.isEmpty()) { 97 List<ElementDescriptor> matches = Lists.newArrayList(); 98 for (ElementDescriptor descriptor : descriptors) { 99 if (descriptor.getXmlLocalName().startsWith(parent)) { 100 matches.add(descriptor); 101 } 102 } 103 if (!matches.isEmpty()) { 104 return matches.toArray(new ElementDescriptor[matches.size()]); 105 } 106 } 107 } 108 109 return choices; 110 } 111 112 @Override 113 protected ElementDescriptor[] getElementChoicesForTextNode(Node parentNode) { 114 ElementDescriptor[] choices = super.getElementChoicesForTextNode(parentNode); 115 116 // Add in custom views, if any 117 List<ElementDescriptor> descriptors = getCustomViews(); 118 if (descriptors != null && !descriptors.isEmpty()) { 119 ElementDescriptor[] array = descriptors.toArray( 120 new ElementDescriptor[descriptors.size()]); 121 choices = ObjectArrays.concat(choices, array, ElementDescriptor.class); 122 choices = sort(choices); 123 } 124 125 return choices; 126 } 127 128 @Nullable 129 private List<ElementDescriptor> getCustomViews() { 130 // Add in custom views, if any 131 IProject project = mEditor.getProject(); 132 CustomViewFinder finder = CustomViewFinder.get(project); 133 Collection<String> views = finder.getAllViews(); 134 if (views == null) { 135 finder.refresh(); 136 views = finder.getAllViews(); 137 } 138 if (views != null && !views.isEmpty()) { 139 List<ElementDescriptor> descriptors = Lists.newArrayListWithExpectedSize(views.size()); 140 CustomViewDescriptorService customViews = CustomViewDescriptorService.getInstance(); 141 for (String fqcn : views) { 142 ViewElementDescriptor descriptor = customViews.getDescriptor(project, fqcn); 143 if (descriptor != null) { 144 descriptors.add(descriptor); 145 } 146 } 147 148 return descriptors; 149 } 150 151 return null; 152 } 153 154 @Override 155 protected boolean computeAttributeValues(List<ICompletionProposal> proposals, int offset, 156 String parentTagName, String attributeName, Node node, String wordPrefix, 157 boolean skipEndTag, int replaceLength) { 158 super.computeAttributeValues(proposals, offset, parentTagName, attributeName, node, 159 wordPrefix, skipEndTag, replaceLength); 160 161 boolean projectOnly = false; 162 List<String> superClasses = null; 163 if (VIEW_FRAGMENT.equals(parentTagName) && (attributeName.endsWith(ATTR_NAME) 164 || attributeName.equals(ATTR_CLASS))) { 165 // Insert fragment class matches 166 superClasses = Arrays.asList(CLASS_V4_FRAGMENT, CLASS_FRAGMENT); 167 } else if (VIEW_TAG.equals(parentTagName) && attributeName.endsWith(ATTR_CLASS)) { 168 // Insert custom view matches 169 superClasses = Collections.singletonList(CLASS_VIEW); 170 projectOnly = true; 171 } else if (attributeName.endsWith(ATTR_CONTEXT)) { 172 // Insert activity matches 173 superClasses = Collections.singletonList(CLASS_ACTIVITY); 174 } 175 176 if (superClasses != null) { 177 IProject project = mEditor.getProject(); 178 if (project == null) { 179 return false; 180 } 181 try { 182 IJavaProject javaProject = BaseProjectHelper.getJavaProject(project); 183 IType type = javaProject.findType(superClasses.get(0)); 184 Set<IType> elements = new HashSet<IType>(); 185 if (type != null) { 186 ITypeHierarchy hierarchy = type.newTypeHierarchy(new NullProgressMonitor()); 187 IType[] allSubtypes = hierarchy.getAllSubtypes(type); 188 for (IType subType : allSubtypes) { 189 if (!projectOnly || subType.getResource() != null) { 190 elements.add(subType); 191 } 192 } 193 } 194 assert superClasses.size() <= 2; // If more, need to do additional work below 195 if (superClasses.size() == 2) { 196 type = javaProject.findType(superClasses.get(1)); 197 if (type != null) { 198 ITypeHierarchy hierarchy = type.newTypeHierarchy( 199 new NullProgressMonitor()); 200 IType[] allSubtypes = hierarchy.getAllSubtypes(type); 201 for (IType subType : allSubtypes) { 202 if (!projectOnly || subType.getResource() != null) { 203 elements.add(subType); 204 } 205 } 206 } 207 } 208 209 List<IType> sorted = new ArrayList<IType>(elements); 210 Collections.sort(sorted, new Comparator<IType>() { 211 @Override 212 public int compare(IType type1, IType type2) { 213 String fqcn1 = type1.getFullyQualifiedName(); 214 String fqcn2 = type2.getFullyQualifiedName(); 215 int category1 = fqcn1.startsWith(ANDROID_PKG_PREFIX) ? 1 : -1; 216 int category2 = fqcn2.startsWith(ANDROID_PKG_PREFIX) ? 1 : -1; 217 if (category1 != category2) { 218 return category1 - category2; 219 } 220 return fqcn1.compareTo(fqcn2); 221 } 222 }); 223 addMatchingProposals(proposals, sorted.toArray(), offset, node, wordPrefix, 224 (char) 0, false /* isAttribute */, false /* isNew */, 225 false /* skipEndTag */, replaceLength); 226 return true; 227 } catch (CoreException e) { 228 AdtPlugin.log(e, null); 229 } 230 } 231 232 return false; 233 } 234 } 235