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.sdklib.SdkConstants.CLASS_VIEWGROUP;
     20 
     21 import com.android.ide.common.resources.platform.ViewClassInfo;
     22 import com.android.ide.eclipse.adt.internal.editors.descriptors.AttributeDescriptor;
     23 import com.android.ide.eclipse.adt.internal.editors.descriptors.DescriptorsUtils;
     24 import com.android.ide.eclipse.adt.internal.editors.descriptors.ElementDescriptor;
     25 import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData;
     26 import com.android.ide.eclipse.adt.internal.sdk.Sdk;
     27 import com.android.sdklib.IAndroidTarget;
     28 
     29 import org.eclipse.core.resources.IProject;
     30 import org.eclipse.core.runtime.NullProgressMonitor;
     31 import org.eclipse.jdt.core.IJavaProject;
     32 import org.eclipse.jdt.core.IType;
     33 import org.eclipse.jdt.core.ITypeHierarchy;
     34 import org.eclipse.jdt.core.JavaCore;
     35 import org.eclipse.jdt.core.JavaModelException;
     36 import org.eclipse.jdt.ui.ISharedImages;
     37 import org.eclipse.jdt.ui.JavaUI;
     38 import org.eclipse.jface.resource.ImageDescriptor;
     39 import org.eclipse.swt.graphics.Image;
     40 
     41 import java.util.HashMap;
     42 import java.util.List;
     43 
     44 /**
     45  * Service responsible for creating/managing {@link ViewElementDescriptor} objects for custom
     46  * View classes per project.
     47  * <p/>
     48  * The service provides an on-demand monitoring of custom classes to check for changes. Monitoring
     49  * starts once a request for an {@link ViewElementDescriptor} object has been done for a specific
     50  * class.
     51  * <p/>
     52  * The monitoring will notify a listener of any changes in the class triggering a change in its
     53  * associated {@link ViewElementDescriptor} object.
     54  * <p/>
     55  * If the custom class does not exist, no monitoring is put in place to avoid having to listen
     56  * to all class changes in the projects.
     57  */
     58 public final class CustomViewDescriptorService {
     59 
     60     private static CustomViewDescriptorService sThis = new CustomViewDescriptorService();
     61 
     62     /**
     63      * Map where keys are the project, and values are another map containing all the known
     64      * custom View class for this project. The custom View class are stored in a map
     65      * where the keys are the fully qualified class name, and the values are their associated
     66      * {@link ViewElementDescriptor}.
     67      */
     68     private HashMap<IProject, HashMap<String, ViewElementDescriptor>> mCustomDescriptorMap =
     69         new HashMap<IProject, HashMap<String, ViewElementDescriptor>>();
     70 
     71     /**
     72      * TODO will be used to update the ViewElementDescriptor of the custom view when it
     73      * is modified (either the class itself or its attributes.xml)
     74      */
     75     @SuppressWarnings("unused")
     76     private ICustomViewDescriptorListener mListener;
     77 
     78     /**
     79      * Classes which implements this interface provide a method that deal with modifications
     80      * in custom View class triggering a change in its associated {@link ViewClassInfo} object.
     81      */
     82     public interface ICustomViewDescriptorListener {
     83         /**
     84          * Sent when a custom View class has changed and
     85          * its {@link ViewElementDescriptor} was modified.
     86          *
     87          * @param project the project containing the class.
     88          * @param className the fully qualified class name.
     89          * @param descriptor the updated ElementDescriptor.
     90          */
     91         public void updatedClassInfo(IProject project,
     92                                      String className,
     93                                      ViewElementDescriptor descriptor);
     94     }
     95 
     96     /**
     97      * Returns the singleton instance of {@link CustomViewDescriptorService}.
     98      */
     99     public static CustomViewDescriptorService getInstance() {
    100         return sThis;
    101     }
    102 
    103     /**
    104      * Sets the listener receiving custom View class modification notifications.
    105      * @param listener the listener to receive the notifications.
    106      *
    107      * TODO will be used to update the ViewElementDescriptor of the custom view when it
    108      * is modified (either the class itself or its attributes.xml)
    109      */
    110     public void setListener(ICustomViewDescriptorListener listener) {
    111         mListener = listener;
    112     }
    113 
    114     /**
    115      * Returns the {@link ViewElementDescriptor} for a particular project/class when the
    116      * fully qualified class name actually matches a class from the given project.
    117      * <p/>
    118      * Custom descriptors are created as needed.
    119      * <p/>
    120      * If it is the first time the {@link ViewElementDescriptor} is requested, the method
    121      * will check that the specified class is in fact a custom View class. Once this is
    122      * established, a monitoring for that particular class is initiated. Any change will
    123      * trigger a notification to the {@link ICustomViewDescriptorListener}.
    124      *
    125      * @param project the project containing the class.
    126      * @param fqcn the fully qualified name of the class.
    127      * @return a {@link ViewElementDescriptor} or <code>null</code> if the class was not
    128      *         a custom View class.
    129      */
    130     public ViewElementDescriptor getDescriptor(IProject project, String fqcn) {
    131         // look in the map first
    132         synchronized (mCustomDescriptorMap) {
    133             HashMap<String, ViewElementDescriptor> map = mCustomDescriptorMap.get(project);
    134 
    135             if (map != null) {
    136                 ViewElementDescriptor descriptor = map.get(fqcn);
    137                 if (descriptor != null) {
    138                     return descriptor;
    139                 }
    140             }
    141 
    142             // if we step here, it looks like we haven't created it yet.
    143             // First lets check this is in fact a valid type in the project
    144 
    145             try {
    146                 // We expect the project to be both opened and of java type (since it's an android
    147                 // project), so we can create a IJavaProject object from our IProject.
    148                 IJavaProject javaProject = JavaCore.create(project);
    149 
    150                 // replace $ by . in the class name
    151                 String javaClassName = fqcn.replaceAll("\\$", "\\."); //$NON-NLS-1$ //$NON-NLS-2$
    152 
    153                 // look for the IType object for this class
    154                 IType type = javaProject.findType(javaClassName);
    155                 if (type != null && type.exists()) {
    156                     // the type exists. Let's get the parent class and its ViewClassInfo.
    157 
    158                     // get the type hierarchy
    159                     ITypeHierarchy hierarchy = type.newSupertypeHierarchy(
    160                             new NullProgressMonitor());
    161 
    162                     ViewElementDescriptor parentDescriptor = createViewDescriptor(
    163                             hierarchy.getSuperclass(type), project, hierarchy);
    164 
    165                     if (parentDescriptor != null) {
    166                         // we have a valid parent, lets create a new ViewElementDescriptor.
    167 
    168                         String name = DescriptorsUtils.getBasename(fqcn);
    169                         ViewElementDescriptor descriptor = new CustomViewDescriptor(name, fqcn,
    170                                 getAttributeDescriptor(type, parentDescriptor),
    171                                 getLayoutAttributeDescriptors(type, parentDescriptor),
    172                                 parentDescriptor.getChildren());
    173                         descriptor.setSuperClass(parentDescriptor);
    174 
    175                         synchronized (mCustomDescriptorMap) {
    176                             map = mCustomDescriptorMap.get(project);
    177                             if (map == null) {
    178                                 map = new HashMap<String, ViewElementDescriptor>();
    179                                 mCustomDescriptorMap.put(project, map);
    180                             }
    181 
    182                             map.put(fqcn, descriptor);
    183                         }
    184 
    185                         //TODO setup listener on this resource change.
    186 
    187                         return descriptor;
    188                     }
    189                 }
    190             } catch (JavaModelException e) {
    191                 // there was an error accessing any of the IType, we'll just return null;
    192             }
    193         }
    194 
    195         return null;
    196     }
    197 
    198     /**
    199      * Computes (if needed) and returns the {@link ViewElementDescriptor} for the specified type.
    200      *
    201      * @return A {@link ViewElementDescriptor} or null if type or typeHierarchy is null.
    202      */
    203     private ViewElementDescriptor createViewDescriptor(IType type, IProject project,
    204             ITypeHierarchy typeHierarchy) {
    205         // check if the type is a built-in View class.
    206         List<ViewElementDescriptor> builtInList = null;
    207 
    208         // give up if there's no type
    209         if (type == null) {
    210             return null;
    211         }
    212 
    213         String fqcn = type.getFullyQualifiedName();
    214 
    215         Sdk currentSdk = Sdk.getCurrent();
    216         if (currentSdk != null) {
    217             IAndroidTarget target = currentSdk.getTarget(project);
    218             if (target != null) {
    219                 AndroidTargetData data = currentSdk.getTargetData(target);
    220                 if (data != null) {
    221                     LayoutDescriptors descriptors = data.getLayoutDescriptors();
    222                     ViewElementDescriptor d = descriptors.findDescriptorByClass(fqcn);
    223                     if (d != null) {
    224                         return d;
    225                     }
    226                     builtInList = descriptors.getViewDescriptors();
    227                 }
    228             }
    229         }
    230 
    231         // it's not a built-in class? Lets look if the superclass is built-in
    232         // give up if there's no type
    233         if (typeHierarchy == null) {
    234             return null;
    235         }
    236 
    237         IType parentType = typeHierarchy.getSuperclass(type);
    238         if (parentType != null) {
    239             ViewElementDescriptor parentDescriptor = createViewDescriptor(parentType, project,
    240                     typeHierarchy);
    241 
    242             if (parentDescriptor != null) {
    243                 // parent class is a valid View class with a descriptor, so we create one
    244                 // for this class.
    245                 String name = DescriptorsUtils.getBasename(fqcn);
    246                 // A custom view accepts children if its parent descriptor also does.
    247                 // The only exception to this is ViewGroup, which accepts children even though
    248                 // its parent does not.
    249                 boolean isViewGroup = fqcn.equals(CLASS_VIEWGROUP);
    250                 boolean hasChildren = isViewGroup || parentDescriptor.hasChildren();
    251                 ViewElementDescriptor[] children = null;
    252                 if (hasChildren && builtInList != null) {
    253                     // We can't figure out what the allowable children are by just
    254                     // looking at the class, so assume any View is valid
    255                     children = builtInList.toArray(new ViewElementDescriptor[builtInList.size()]);
    256                 }
    257                 ViewElementDescriptor descriptor = new CustomViewDescriptor(name, fqcn,
    258                         getAttributeDescriptor(type, parentDescriptor),
    259                         getLayoutAttributeDescriptors(type, parentDescriptor),
    260                         children);
    261                 descriptor.setSuperClass(parentDescriptor);
    262 
    263                 // add it to the map
    264                 synchronized (mCustomDescriptorMap) {
    265                     HashMap<String, ViewElementDescriptor> map = mCustomDescriptorMap.get(project);
    266 
    267                     if (map == null) {
    268                         map = new HashMap<String, ViewElementDescriptor>();
    269                         mCustomDescriptorMap.put(project, map);
    270                     }
    271 
    272                     map.put(fqcn, descriptor);
    273 
    274                 }
    275 
    276                 //TODO setup listener on this resource change.
    277 
    278                 return descriptor;
    279             }
    280         }
    281 
    282         // class is neither a built-in view class, nor extend one. return null.
    283         return null;
    284     }
    285 
    286     /**
    287      * Returns the array of {@link AttributeDescriptor} for the specified {@link IType}.
    288      * <p/>
    289      * The array should contain the descriptor for this type and all its supertypes.
    290      *
    291      * @param type the type for which the {@link AttributeDescriptor} are returned.
    292      * @param parentDescriptor the {@link ViewElementDescriptor} of the direct superclass.
    293      */
    294     private static AttributeDescriptor[] getAttributeDescriptor(IType type,
    295             ViewElementDescriptor parentDescriptor) {
    296         // TODO add the class attribute descriptors to the parent descriptors.
    297         return parentDescriptor.getAttributes();
    298     }
    299 
    300     private static AttributeDescriptor[] getLayoutAttributeDescriptors(IType type,
    301             ViewElementDescriptor parentDescriptor) {
    302         return parentDescriptor.getLayoutAttributes();
    303     }
    304 
    305     private static class CustomViewDescriptor extends ViewElementDescriptor {
    306         public CustomViewDescriptor(String name, String fqcn, AttributeDescriptor[] attributes,
    307                 AttributeDescriptor[] layoutAttributes,
    308                 ElementDescriptor[] children) {
    309             super(
    310                     fqcn, // xml name
    311                     name, // ui name
    312                     fqcn, // full class name
    313                     fqcn, // tooltip
    314                     null, // sdk_url
    315                     attributes,
    316                     layoutAttributes,
    317                     children,
    318                     false // mandatory
    319             );
    320         }
    321 
    322         @Override
    323         public Image getGenericIcon() {
    324             // Java source file icon. We could use the Java class icon here
    325             // (IMG_OBJS_CLASS), but it does not work well on anything but
    326             // white backgrounds
    327             ISharedImages sharedImages = JavaUI.getSharedImages();
    328             String key = ISharedImages.IMG_OBJS_CUNIT;
    329             ImageDescriptor descriptor = sharedImages.getImageDescriptor(key);
    330             return descriptor.createImage();
    331         }
    332     }
    333 }
    334