Home | History | Annotate | Download | only in descriptors
      1 /*
      2  * Copyright (C) 2008 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.descriptors;
     18 
     19 import static com.android.SdkConstants.ANDROID_NS_NAME_PREFIX;
     20 import static com.android.SdkConstants.ANDROID_URI;
     21 import static com.android.SdkConstants.AUTO_URI;
     22 import static com.android.SdkConstants.CLASS_VIEWGROUP;
     23 import static com.android.SdkConstants.URI_PREFIX;
     24 
     25 import com.android.annotations.NonNull;
     26 import com.android.annotations.Nullable;
     27 import com.android.ide.common.resources.ResourceFile;
     28 import com.android.ide.common.resources.ResourceItem;
     29 import com.android.ide.common.resources.platform.AttributeInfo;
     30 import com.android.ide.common.resources.platform.AttrsXmlParser;
     31 import com.android.ide.common.resources.platform.ViewClassInfo;
     32 import com.android.ide.common.resources.platform.ViewClassInfo.LayoutParamsInfo;
     33 import com.android.ide.eclipse.adt.AdtPlugin;
     34 import com.android.ide.eclipse.adt.AdtUtils;
     35 import com.android.ide.eclipse.adt.internal.editors.IconFactory;
     36 import com.android.ide.eclipse.adt.internal.editors.descriptors.AttributeDescriptor;
     37 import com.android.ide.eclipse.adt.internal.editors.descriptors.DescriptorsUtils;
     38 import com.android.ide.eclipse.adt.internal.editors.descriptors.ElementDescriptor;
     39 import com.android.ide.eclipse.adt.internal.editors.manifest.ManifestInfo;
     40 import com.android.ide.eclipse.adt.internal.resources.manager.ProjectResources;
     41 import com.android.ide.eclipse.adt.internal.resources.manager.ResourceManager;
     42 import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData;
     43 import com.android.ide.eclipse.adt.internal.sdk.ProjectState;
     44 import com.android.ide.eclipse.adt.internal.sdk.Sdk;
     45 import com.android.resources.ResourceType;
     46 import com.android.sdklib.IAndroidTarget;
     47 import com.google.common.collect.Maps;
     48 import com.google.common.collect.ObjectArrays;
     49 
     50 import org.eclipse.core.resources.IProject;
     51 import org.eclipse.core.resources.IResource;
     52 import org.eclipse.core.resources.IWorkspaceRoot;
     53 import org.eclipse.core.resources.ResourcesPlugin;
     54 import org.eclipse.core.runtime.CoreException;
     55 import org.eclipse.core.runtime.IPath;
     56 import org.eclipse.core.runtime.NullProgressMonitor;
     57 import org.eclipse.jdt.core.IClassFile;
     58 import org.eclipse.jdt.core.IJavaProject;
     59 import org.eclipse.jdt.core.IType;
     60 import org.eclipse.jdt.core.ITypeHierarchy;
     61 import org.eclipse.jdt.core.JavaCore;
     62 import org.eclipse.jdt.core.JavaModelException;
     63 import org.eclipse.swt.graphics.Image;
     64 
     65 import java.util.ArrayList;
     66 import java.util.Collection;
     67 import java.util.HashMap;
     68 import java.util.HashSet;
     69 import java.util.List;
     70 import java.util.Map;
     71 import java.util.Set;
     72 
     73 /**
     74  * Service responsible for creating/managing {@link ViewElementDescriptor} objects for custom
     75  * View classes per project.
     76  * <p/>
     77  * The service provides an on-demand monitoring of custom classes to check for changes. Monitoring
     78  * starts once a request for an {@link ViewElementDescriptor} object has been done for a specific
     79  * class.
     80  * <p/>
     81  * The monitoring will notify a listener of any changes in the class triggering a change in its
     82  * associated {@link ViewElementDescriptor} object.
     83  * <p/>
     84  * If the custom class does not exist, no monitoring is put in place to avoid having to listen
     85  * to all class changes in the projects.
     86  */
     87 public final class CustomViewDescriptorService {
     88 
     89     private static CustomViewDescriptorService sThis = new CustomViewDescriptorService();
     90 
     91     /**
     92      * Map where keys are the project, and values are another map containing all the known
     93      * custom View class for this project. The custom View class are stored in a map
     94      * where the keys are the fully qualified class name, and the values are their associated
     95      * {@link ViewElementDescriptor}.
     96      */
     97     private HashMap<IProject, HashMap<String, ViewElementDescriptor>> mCustomDescriptorMap =
     98         new HashMap<IProject, HashMap<String, ViewElementDescriptor>>();
     99 
    100     /**
    101      * TODO will be used to update the ViewElementDescriptor of the custom view when it
    102      * is modified (either the class itself or its attributes.xml)
    103      */
    104     @SuppressWarnings("unused")
    105     private ICustomViewDescriptorListener mListener;
    106 
    107     /**
    108      * Classes which implements this interface provide a method that deal with modifications
    109      * in custom View class triggering a change in its associated {@link ViewClassInfo} object.
    110      */
    111     public interface ICustomViewDescriptorListener {
    112         /**
    113          * Sent when a custom View class has changed and
    114          * its {@link ViewElementDescriptor} was modified.
    115          *
    116          * @param project the project containing the class.
    117          * @param className the fully qualified class name.
    118          * @param descriptor the updated ElementDescriptor.
    119          */
    120         public void updatedClassInfo(IProject project,
    121                                      String className,
    122                                      ViewElementDescriptor descriptor);
    123     }
    124 
    125     /**
    126      * Returns the singleton instance of {@link CustomViewDescriptorService}.
    127      */
    128     public static CustomViewDescriptorService getInstance() {
    129         return sThis;
    130     }
    131 
    132     /**
    133      * Sets the listener receiving custom View class modification notifications.
    134      * @param listener the listener to receive the notifications.
    135      *
    136      * TODO will be used to update the ViewElementDescriptor of the custom view when it
    137      * is modified (either the class itself or its attributes.xml)
    138      */
    139     public void setListener(ICustomViewDescriptorListener listener) {
    140         mListener = listener;
    141     }
    142 
    143     /**
    144      * Returns the {@link ViewElementDescriptor} for a particular project/class when the
    145      * fully qualified class name actually matches a class from the given project.
    146      * <p/>
    147      * Custom descriptors are created as needed.
    148      * <p/>
    149      * If it is the first time the {@link ViewElementDescriptor} is requested, the method
    150      * will check that the specified class is in fact a custom View class. Once this is
    151      * established, a monitoring for that particular class is initiated. Any change will
    152      * trigger a notification to the {@link ICustomViewDescriptorListener}.
    153      *
    154      * @param project the project containing the class.
    155      * @param fqcn the fully qualified name of the class.
    156      * @return a {@link ViewElementDescriptor} or <code>null</code> if the class was not
    157      *         a custom View class.
    158      */
    159     public ViewElementDescriptor getDescriptor(IProject project, String fqcn) {
    160         // look in the map first
    161         synchronized (mCustomDescriptorMap) {
    162             HashMap<String, ViewElementDescriptor> map = mCustomDescriptorMap.get(project);
    163 
    164             if (map != null) {
    165                 ViewElementDescriptor descriptor = map.get(fqcn);
    166                 if (descriptor != null) {
    167                     return descriptor;
    168                 }
    169             }
    170 
    171             // if we step here, it looks like we haven't created it yet.
    172             // First lets check this is in fact a valid type in the project
    173 
    174             try {
    175                 // We expect the project to be both opened and of java type (since it's an android
    176                 // project), so we can create a IJavaProject object from our IProject.
    177                 IJavaProject javaProject = JavaCore.create(project);
    178 
    179                 // replace $ by . in the class name
    180                 String javaClassName = fqcn.replaceAll("\\$", "\\."); //$NON-NLS-1$ //$NON-NLS-2$
    181 
    182                 // look for the IType object for this class
    183                 IType type = javaProject.findType(javaClassName);
    184                 if (type != null && type.exists()) {
    185                     // the type exists. Let's get the parent class and its ViewClassInfo.
    186 
    187                     // get the type hierarchy
    188                     ITypeHierarchy hierarchy = type.newSupertypeHierarchy(
    189                             new NullProgressMonitor());
    190 
    191                     ViewElementDescriptor parentDescriptor = createViewDescriptor(
    192                             hierarchy.getSuperclass(type), project, hierarchy);
    193 
    194                     if (parentDescriptor != null) {
    195                         // we have a valid parent, lets create a new ViewElementDescriptor.
    196                         List<AttributeDescriptor> attrList = new ArrayList<AttributeDescriptor>();
    197                         List<AttributeDescriptor> paramList = new ArrayList<AttributeDescriptor>();
    198                         Map<ResourceFile, Long> files = findCustomDescriptors(project, type,
    199                                 attrList, paramList);
    200 
    201                         AttributeDescriptor[] attributes =
    202                                 getAttributeDescriptor(type, parentDescriptor);
    203                         if (!attrList.isEmpty()) {
    204                             attributes = join(attrList, attributes);
    205                         }
    206                         AttributeDescriptor[] layoutAttributes =
    207                                 getLayoutAttributeDescriptors(type, parentDescriptor);
    208                         if (!paramList.isEmpty()) {
    209                             layoutAttributes = join(paramList, layoutAttributes);
    210                         }
    211                         String name = DescriptorsUtils.getBasename(fqcn);
    212                         ViewElementDescriptor descriptor = new CustomViewDescriptor(name, fqcn,
    213                                 attributes,
    214                                 layoutAttributes,
    215                                 parentDescriptor.getChildren(),
    216                                 project, files);
    217                         descriptor.setSuperClass(parentDescriptor);
    218 
    219                         synchronized (mCustomDescriptorMap) {
    220                             map = mCustomDescriptorMap.get(project);
    221                             if (map == null) {
    222                                 map = new HashMap<String, ViewElementDescriptor>();
    223                                 mCustomDescriptorMap.put(project, map);
    224                             }
    225 
    226                             map.put(fqcn, descriptor);
    227                         }
    228 
    229                         //TODO setup listener on this resource change.
    230 
    231                         return descriptor;
    232                     }
    233                 }
    234             } catch (JavaModelException e) {
    235                 // there was an error accessing any of the IType, we'll just return null;
    236             }
    237         }
    238 
    239         return null;
    240     }
    241 
    242     private static AttributeDescriptor[] join(
    243             @NonNull List<AttributeDescriptor> attributeList,
    244             @NonNull AttributeDescriptor[] attributes) {
    245         if (!attributeList.isEmpty()) {
    246             return ObjectArrays.concat(
    247                     attributeList.toArray(new AttributeDescriptor[attributeList.size()]),
    248                     attributes,
    249                     AttributeDescriptor.class);
    250         } else {
    251             return attributes;
    252         }
    253 
    254     }
    255 
    256     /** Cache used by {@link #getParser(ResourceFile)} */
    257     private Map<ResourceFile, AttrsXmlParser> mParserCache;
    258 
    259     private AttrsXmlParser getParser(ResourceFile file) {
    260         if (mParserCache == null) {
    261             mParserCache = new HashMap<ResourceFile, AttrsXmlParser>();
    262         }
    263 
    264         AttrsXmlParser parser = mParserCache.get(file);
    265         if (parser == null) {
    266             parser = new AttrsXmlParser(
    267                     file.getFile().getOsLocation(),
    268                     AdtPlugin.getDefault(), 20);
    269             parser.preload();
    270             mParserCache.put(file, parser);
    271         }
    272 
    273         return parser;
    274     }
    275 
    276     /** Compute/find the styleable resources for the given type, if possible */
    277     private Map<ResourceFile, Long> findCustomDescriptors(
    278             IProject project,
    279             IType type,
    280             List<AttributeDescriptor> customAttributes,
    281             List<AttributeDescriptor> customLayoutAttributes) {
    282         // Look up the project where the type is declared (could be a library project;
    283         // we cannot use type.getJavaProject().getProject())
    284         IProject library = getProjectDeclaringType(type);
    285         if (library == null) {
    286             library = project;
    287         }
    288 
    289         String className = type.getElementName();
    290         Set<ResourceFile> resourceFiles = findAttrsFiles(library, className);
    291         if (resourceFiles != null && resourceFiles.size() > 0) {
    292             String appUri = getAppResUri(project);
    293             Map<ResourceFile, Long> timestamps =
    294                     Maps.newHashMapWithExpectedSize(resourceFiles.size());
    295             for (ResourceFile file : resourceFiles) {
    296                 AttrsXmlParser attrsXmlParser = getParser(file);
    297                 String fqcn = type.getFullyQualifiedName();
    298 
    299                 // Attributes
    300                 ViewClassInfo classInfo = new ViewClassInfo(true, fqcn, className);
    301                 attrsXmlParser.loadViewAttributes(classInfo);
    302                 appendAttributes(customAttributes, classInfo.getAttributes(), appUri);
    303 
    304                 // Layout params
    305                 LayoutParamsInfo layoutInfo = new ViewClassInfo.LayoutParamsInfo(
    306                         classInfo, "Layout", null /*superClassInfo*/); //$NON-NLS-1$
    307                 attrsXmlParser.loadLayoutParamsAttributes(layoutInfo);
    308                 appendAttributes(customLayoutAttributes, layoutInfo.getAttributes(), appUri);
    309 
    310                 timestamps.put(file, file.getFile().getModificationStamp());
    311             }
    312 
    313             return timestamps;
    314         }
    315 
    316         return null;
    317     }
    318 
    319     /**
    320      * Finds the set of XML files (if any) in the given library declaring
    321      * attributes for the given class name
    322      */
    323     @Nullable
    324     private static Set<ResourceFile> findAttrsFiles(IProject library, String className) {
    325         Set<ResourceFile> resourceFiles = null;
    326         ResourceManager manager = ResourceManager.getInstance();
    327         ProjectResources resources = manager.getProjectResources(library);
    328         if (resources != null) {
    329             Collection<ResourceItem> items =
    330                 resources.getResourceItemsOfType(ResourceType.DECLARE_STYLEABLE);
    331             for (ResourceItem item : items) {
    332                 String viewName = item.getName();
    333                 if (viewName.equals(className)
    334                         || (viewName.startsWith(className)
    335                             && viewName.equals(className + "_Layout"))) { //$NON-NLS-1$
    336                     if (resourceFiles == null) {
    337                         resourceFiles = new HashSet<ResourceFile>();
    338                     }
    339                     resourceFiles.addAll(item.getSourceFileList());
    340                 }
    341             }
    342         }
    343         return resourceFiles;
    344     }
    345 
    346     /**
    347      * Find the project containing this type declaration. We cannot use
    348      * {@link IType#getJavaProject()} since that will return the including
    349      * project and we're after the library project such that we can find the
    350      * attrs.xml file in the same project.
    351      */
    352     @Nullable
    353     private static IProject getProjectDeclaringType(IType type) {
    354         IClassFile classFile = type.getClassFile();
    355         if (classFile != null) {
    356             IPath path = classFile.getPath();
    357             IWorkspaceRoot workspace = ResourcesPlugin.getWorkspace().getRoot();
    358             IResource resource;
    359             if (path.isAbsolute()) {
    360                 resource = AdtUtils.fileToResource(path.toFile());
    361             } else {
    362                 resource = workspace.findMember(path);
    363             }
    364             if (resource != null && resource.getProject() != null) {
    365                 return resource.getProject();
    366             }
    367         }
    368 
    369         return null;
    370     }
    371 
    372     /** Returns the name space to use for application attributes */
    373     private static String getAppResUri(IProject project) {
    374         String appResource;
    375         ProjectState projectState = Sdk.getProjectState(project);
    376         if (projectState != null && projectState.isLibrary()) {
    377             appResource = AUTO_URI;
    378         } else {
    379             ManifestInfo manifestInfo = ManifestInfo.get(project);
    380             appResource = URI_PREFIX + manifestInfo.getPackage();
    381         }
    382         return appResource;
    383     }
    384 
    385 
    386     /** Append the {@link AttributeInfo} objects converted {@link AttributeDescriptor}
    387      * objects into the given attribute list.
    388      * <p>
    389      * This is nearly identical to
    390      *  {@link DescriptorsUtils#appendAttribute(List, String, String, AttributeInfo, boolean, Map)}
    391      * but it handles namespace declarations in the attrs.xml file where the android:
    392      * namespace is included in the names.
    393      */
    394     private static void appendAttributes(List<AttributeDescriptor> attributes,
    395             AttributeInfo[] attributeInfos, String appResource) {
    396         // Custom attributes
    397         for (AttributeInfo info : attributeInfos) {
    398             String nsUri;
    399             if (info.getName().startsWith(ANDROID_NS_NAME_PREFIX)) {
    400                 info.setName(info.getName().substring(ANDROID_NS_NAME_PREFIX.length()));
    401                 nsUri = ANDROID_URI;
    402             } else {
    403                 nsUri = appResource;
    404             }
    405 
    406             DescriptorsUtils.appendAttribute(attributes,
    407                     null /*elementXmlName*/, nsUri, info, false /*required*/,
    408                     null /*overrides*/);
    409         }
    410     }
    411 
    412     /**
    413      * Computes (if needed) and returns the {@link ViewElementDescriptor} for the specified type.
    414      *
    415      * @return A {@link ViewElementDescriptor} or null if type or typeHierarchy is null.
    416      */
    417     private ViewElementDescriptor createViewDescriptor(IType type, IProject project,
    418             ITypeHierarchy typeHierarchy) {
    419         // check if the type is a built-in View class.
    420         List<ViewElementDescriptor> builtInList = null;
    421 
    422         // give up if there's no type
    423         if (type == null) {
    424             return null;
    425         }
    426 
    427         String fqcn = type.getFullyQualifiedName();
    428 
    429         Sdk currentSdk = Sdk.getCurrent();
    430         if (currentSdk != null) {
    431             IAndroidTarget target = currentSdk.getTarget(project);
    432             if (target != null) {
    433                 AndroidTargetData data = currentSdk.getTargetData(target);
    434                 if (data != null) {
    435                     LayoutDescriptors descriptors = data.getLayoutDescriptors();
    436                     ViewElementDescriptor d = descriptors.findDescriptorByClass(fqcn);
    437                     if (d != null) {
    438                         return d;
    439                     }
    440                     builtInList = descriptors.getViewDescriptors();
    441                 }
    442             }
    443         }
    444 
    445         // it's not a built-in class? Lets look if the superclass is built-in
    446         // give up if there's no type
    447         if (typeHierarchy == null) {
    448             return null;
    449         }
    450 
    451         IType parentType = typeHierarchy.getSuperclass(type);
    452         if (parentType != null) {
    453             ViewElementDescriptor parentDescriptor = createViewDescriptor(parentType, project,
    454                     typeHierarchy);
    455 
    456             if (parentDescriptor != null) {
    457                 // parent class is a valid View class with a descriptor, so we create one
    458                 // for this class.
    459                 String name = DescriptorsUtils.getBasename(fqcn);
    460                 // A custom view accepts children if its parent descriptor also does.
    461                 // The only exception to this is ViewGroup, which accepts children even though
    462                 // its parent does not.
    463                 boolean isViewGroup = fqcn.equals(CLASS_VIEWGROUP);
    464                 boolean hasChildren = isViewGroup || parentDescriptor.hasChildren();
    465                 ViewElementDescriptor[] children = null;
    466                 if (hasChildren && builtInList != null) {
    467                     // We can't figure out what the allowable children are by just
    468                     // looking at the class, so assume any View is valid
    469                     children = builtInList.toArray(new ViewElementDescriptor[builtInList.size()]);
    470                 }
    471                 ViewElementDescriptor descriptor = new CustomViewDescriptor(name, fqcn,
    472                         getAttributeDescriptor(type, parentDescriptor),
    473                         getLayoutAttributeDescriptors(type, parentDescriptor),
    474                         children, project, null);
    475                 descriptor.setSuperClass(parentDescriptor);
    476 
    477                 // add it to the map
    478                 synchronized (mCustomDescriptorMap) {
    479                     HashMap<String, ViewElementDescriptor> map = mCustomDescriptorMap.get(project);
    480 
    481                     if (map == null) {
    482                         map = new HashMap<String, ViewElementDescriptor>();
    483                         mCustomDescriptorMap.put(project, map);
    484                     }
    485 
    486                     map.put(fqcn, descriptor);
    487 
    488                 }
    489 
    490                 //TODO setup listener on this resource change.
    491 
    492                 return descriptor;
    493             }
    494         }
    495 
    496         // class is neither a built-in view class, nor extend one. return null.
    497         return null;
    498     }
    499 
    500     /**
    501      * Returns the array of {@link AttributeDescriptor} for the specified {@link IType}.
    502      * <p/>
    503      * The array should contain the descriptor for this type and all its supertypes.
    504      *
    505      * @param type the type for which the {@link AttributeDescriptor} are returned.
    506      * @param parentDescriptor the {@link ViewElementDescriptor} of the direct superclass.
    507      */
    508     private static AttributeDescriptor[] getAttributeDescriptor(IType type,
    509             ViewElementDescriptor parentDescriptor) {
    510         // TODO add the class attribute descriptors to the parent descriptors.
    511         return parentDescriptor.getAttributes();
    512     }
    513 
    514     private static AttributeDescriptor[] getLayoutAttributeDescriptors(IType type,
    515             ViewElementDescriptor parentDescriptor) {
    516         return parentDescriptor.getLayoutAttributes();
    517     }
    518 
    519     private class CustomViewDescriptor extends ViewElementDescriptor {
    520         private Map<ResourceFile, Long> mTimeStamps;
    521         private IProject mProject;
    522 
    523         public CustomViewDescriptor(String name, String fqcn, AttributeDescriptor[] attributes,
    524                 AttributeDescriptor[] layoutAttributes,
    525                 ElementDescriptor[] children, IProject project,
    526                 Map<ResourceFile, Long> timestamps) {
    527             super(
    528                     fqcn, // xml name
    529                     name, // ui name
    530                     fqcn, // full class name
    531                     fqcn, // tooltip
    532                     null, // sdk_url
    533                     attributes,
    534                     layoutAttributes,
    535                     children,
    536                     false // mandatory
    537             );
    538             mTimeStamps = timestamps;
    539             mProject = project;
    540         }
    541 
    542         @Override
    543         public Image getGenericIcon() {
    544             IconFactory iconFactory = IconFactory.getInstance();
    545 
    546             int index = mXmlName.lastIndexOf('.');
    547             if (index != -1) {
    548                 return iconFactory.getIcon(mXmlName.substring(index + 1),
    549                         "customView"); //$NON-NLS-1$
    550             }
    551 
    552             return iconFactory.getIcon("customView"); //$NON-NLS-1$
    553         }
    554 
    555         @Override
    556         public boolean syncAttributes() {
    557             // Check if any of the descriptors
    558             if (mTimeStamps != null) {
    559                 // Prevent checking actual file timestamps too frequently on rapid burst calls
    560                 long now = System.currentTimeMillis();
    561                 if (now - sLastCheck < 1000) {
    562                     return true;
    563                 }
    564                 sLastCheck = now;
    565 
    566                 // Check whether the resource files (typically just one) which defined
    567                 // custom attributes for this custom view have changed, and if so,
    568                 // refresh the attribute descriptors.
    569                 // This doesn't work the cases where you add descriptors for a custom
    570                 // view after using it, or add attributes in a separate file, but those
    571                 // scenarios aren't quite as common (and would require a bit more expensive
    572                 // analysis.)
    573                 for (Map.Entry<ResourceFile, Long> entry : mTimeStamps.entrySet()) {
    574                     ResourceFile file = entry.getKey();
    575                     Long timestamp = entry.getValue();
    576                     boolean recompute = false;
    577                     if (file.getFile().getModificationStamp() > timestamp.longValue()) {
    578                         // One or more attributes changed: recompute
    579                         recompute = true;
    580                         mParserCache.remove(file);
    581                     }
    582 
    583                     if (recompute) {
    584                         IJavaProject javaProject = JavaCore.create(mProject);
    585                         String fqcn = getFullClassName();
    586                         IType type = null;
    587                         try {
    588                             type = javaProject.findType(fqcn);
    589                         } catch (CoreException e) {
    590                             AdtPlugin.log(e, null);
    591                         }
    592                         if (type == null || !type.exists()) {
    593                             return true;
    594                         }
    595 
    596                         List<AttributeDescriptor> attrList = new ArrayList<AttributeDescriptor>();
    597                         List<AttributeDescriptor> paramList = new ArrayList<AttributeDescriptor>();
    598 
    599                         mTimeStamps = findCustomDescriptors(mProject, type, attrList, paramList);
    600 
    601                         ViewElementDescriptor parentDescriptor = getSuperClassDesc();
    602                         AttributeDescriptor[] attributes =
    603                                 getAttributeDescriptor(type, parentDescriptor);
    604                         if (!attrList.isEmpty()) {
    605                             attributes = join(attrList, attributes);
    606                         }
    607                         attributes = attrList.toArray(new AttributeDescriptor[attrList.size()]);
    608                         setAttributes(attributes);
    609 
    610                         return false;
    611                     }
    612                 }
    613             }
    614 
    615             return true;
    616         }
    617     }
    618 
    619     /** Timestamp of the most recent {@link CustomViewDescriptor#syncAttributes} check */
    620     private static long sLastCheck;
    621 }
    622