• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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          */
updatedClassInfo(IProject project, String className, ViewElementDescriptor descriptor)120         public void updatedClassInfo(IProject project,
121                                      String className,
122                                      ViewElementDescriptor descriptor);
123     }
124 
125     /**
126      * Returns the singleton instance of {@link CustomViewDescriptorService}.
127      */
getInstance()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      */
setListener(ICustomViewDescriptorListener listener)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      */
getDescriptor(IProject project, String fqcn)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 
join( @onNull List<AttributeDescriptor> attributeList, @NonNull AttributeDescriptor[] attributes)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 
getParser(ResourceFile file)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 */
findCustomDescriptors( IProject project, IType type, List<AttributeDescriptor> customAttributes, List<AttributeDescriptor> customLayoutAttributes)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
findAttrsFiles(IProject library, String className)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
getProjectDeclaringType(IType type)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 */
getAppResUri(IProject project)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      */
appendAttributes(List<AttributeDescriptor> attributes, AttributeInfo[] attributeInfos, String appResource)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      */
createViewDescriptor(IType type, IProject project, ITypeHierarchy typeHierarchy)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      */
getAttributeDescriptor(IType type, ViewElementDescriptor parentDescriptor)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 
getLayoutAttributeDescriptors(IType type, ViewElementDescriptor parentDescriptor)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 
CustomViewDescriptor(String name, String fqcn, AttributeDescriptor[] attributes, AttributeDescriptor[] layoutAttributes, ElementDescriptor[] children, IProject project, Map<ResourceFile, Long> timestamps)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
getGenericIcon()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
syncAttributes()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