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