Home | History | Annotate | Download | only in layout
      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