FileDocCategorySizeDatePackage
CustomViewDescriptorService.javaAPI DocAndroid 1.5 API12200Wed May 06 22:41:10 BST 2009com.android.ide.eclipse.editors.layout.descriptors

CustomViewDescriptorService.java

/*
 * Copyright (C) 2008 The Android Open Source Project
 *
 * Licensed under the Eclipse Public License, Version 1.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.eclipse.org/org/documents/epl-v10.php
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.ide.eclipse.editors.layout.descriptors;

import com.android.ide.eclipse.adt.sdk.AndroidTargetData;
import com.android.ide.eclipse.adt.sdk.Sdk;
import com.android.ide.eclipse.common.resources.ViewClassInfo;
import com.android.ide.eclipse.editors.descriptors.AttributeDescriptor;
import com.android.ide.eclipse.editors.descriptors.ElementDescriptor;
import com.android.sdklib.IAndroidTarget;

import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.ITypeHierarchy;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.JavaModelException;

import java.util.HashMap;
import java.util.List;

/**
 * Service responsible for creating/managing {@link ElementDescriptor} objects for custom
 * View classes per project.
 * <p/>
 * The service provides an on-demand monitoring of custom classes to check for changes. Monitoring
 * starts once a request for an {@link ElementDescriptor} object has been done for a specific
 * class.<br>
 * The monitoring will notify a listen of any changes in the class triggering a change in its
 * associated {@link ElementDescriptor} object.
 * <p/>
 * If the custom class does not exist, no monitoring is put in place to avoid having to listen
 * to all class changes in the projects. 
 * 
 */
public final class CustomViewDescriptorService {

    private static CustomViewDescriptorService sThis = new CustomViewDescriptorService();
    
    /**
     * Map where keys are the project, and values are another map containing all the known
     * custom View class for this project. The custom View class are stored in a map
     * where the keys are the fully qualified class name, and the values are their associated
     * {@link ElementDescriptor}.
     */
    private HashMap<IProject, HashMap<String, ElementDescriptor>> mCustomDescriptorMap =
        new HashMap<IProject, HashMap<String, ElementDescriptor>>();

    /**
     * TODO will be used to update the ElementDescriptor of the custom view when it
     * is modified (either the class itself or its attributes.xml)
     */
    @SuppressWarnings("unused")
    private ICustomViewDescriptorListener mListener;
    
    /**
     * Classes which implements this interface provide a method that deal with modifications
     * in custom View class triggering a change in its associated {@link ViewClassInfo} object. 
     */
    public interface ICustomViewDescriptorListener {
        /**
         * Sent when a custom View class has changed and its {@link ElementDescriptor} was modified.
         * @param project the project containing the class.
         * @param className the fully qualified class name.
         * @param descriptor the updated ElementDescriptor.
         */
        public void updatedClassInfo(IProject project, String className, ElementDescriptor descriptor);
    }
    
    /**
     * Returns the singleton instance of {@link CustomViewDescriptorService}.
     */
    public static CustomViewDescriptorService getInstance() {
        return sThis;
    }
    
    /**
     * Sets the listener receiving custom View class modification notifications.
     * @param listener the listener to receive the notifications.
     *
     * TODO will be used to update the ElementDescriptor of the custom view when it
     * is modified (either the class itself or its attributes.xml)
     */
    public void setListener(ICustomViewDescriptorListener listener) {
        mListener = listener;
    }
    
    /**
     * Returns the {@link ElementDescriptor} for a particular project/class.
     * <p/>
     * If it is the first time the <code>ElementDescriptor</code> is requested, the method
     * will check that the specified class is in fact a custom View class. Once this is
     * established, a monitoring for that particular class is initiated. Any change will
     * trigger a notification to the {@link ICustomViewDescriptorListener}.
     * @param project the project containing the class.
     * @param fqClassName the fully qualified name of the class.
     * @return a <code>ElementDescriptor</code> or <code>null</code> if the class was not
     * a custom View class.
     */
    public ElementDescriptor getDescriptor(IProject project, String fqClassName) {
        // look in the map first
        synchronized (mCustomDescriptorMap) {
            HashMap<String, ElementDescriptor> map = mCustomDescriptorMap.get(project);
            
            if (map != null) {
                ElementDescriptor descriptor = map.get(fqClassName);
                if (descriptor != null) {
                    return descriptor;
                }
            }
        
            // if we step here, it looks like we haven't created it yet.
            // First lets check this is in fact a valid type in the project
            
            try {
                // We expect the project to be both opened and of java type (since it's an android
                // project), so we can create a IJavaProject object from our IProject.
                IJavaProject javaProject = JavaCore.create(project);
                
                // replace $ by . in the class name
                String javaClassName = fqClassName.replaceAll("\\$", "\\."); //$NON-NLS-1$ //$NON-NLS-2$
        
                // look for the IType object for this class
                IType type = javaProject.findType(javaClassName);
                if (type != null && type.exists()) {
                    // the type exists. Let's get the parent class and its ViewClassInfo.
                    
                    // get the type hierarchy
                    ITypeHierarchy hierarchy = type.newSupertypeHierarchy(
                            new NullProgressMonitor());
                    
                    ElementDescriptor parentDescriptor = getDescriptor(
                            hierarchy.getSuperclass(type), project, hierarchy);
                    
                    if (parentDescriptor != null) {
                        // we have a valid parent, lets create a new ElementDescriptor.

                        ViewElementDescriptor descriptor = new ViewElementDescriptor(fqClassName,
                                fqClassName, // ui_name
                                fqClassName, // canonical class name
                                null, // tooltip
                                null, // sdk_url
                                getAttributeDescriptor(type, parentDescriptor),
                                null, // layout attributes
                                null, // children
                                false /* mandatory */);

                        synchronized (mCustomDescriptorMap) {
                            map = mCustomDescriptorMap.get(project);
                            if (map == null) {
                                map = new HashMap<String, ElementDescriptor>();
                                mCustomDescriptorMap.put(project, map);
                            }
                        
                            map.put(fqClassName, descriptor);
                        }
                        
                        //TODO setup listener on this resource change.
                        
                        return descriptor;
                    }
                }
            } catch (JavaModelException e) {
                // there was an error accessing any of the IType, we'll just return null;
            }
        }


        return null;
    }
    
    /**
     * Computes (if needed) and returns the {@link ElementDescriptor} for the specified type.
     * 
     * @param type 
     * @param project 
     * @param typeHierarchy
     * @return A ViewElementDescriptor or null if type or typeHierarchy is null.
     */
    private ViewElementDescriptor getDescriptor(IType type, IProject project,
            ITypeHierarchy typeHierarchy) {
        // check if the type is a built-in View class.
        List<ElementDescriptor> builtInList = null;

        Sdk currentSdk = Sdk.getCurrent();
        IAndroidTarget target = currentSdk == null ? null : currentSdk.getTarget(project);
        if (target != null) {
            AndroidTargetData data = currentSdk.getTargetData(target);
            builtInList = data.getLayoutDescriptors().getViewDescriptors();
        }

        // give up if there's no type
        if (type == null) {
            return null;
        }

        String canonicalName = type.getFullyQualifiedName();
        
        if (builtInList != null) {
            for (ElementDescriptor desc : builtInList) {
                if (desc instanceof ViewElementDescriptor) {
                    ViewElementDescriptor viewDescriptor = (ViewElementDescriptor)desc;
                    if (canonicalName.equals(viewDescriptor.getCanonicalClassName())) {
                        return viewDescriptor;
                    }
                }
            }
        }
        
        // it's not a built-in class? Lets look if the superclass is built-in
        // give up if there's no type
        if (typeHierarchy == null) {
            return null;
        }

        IType parentType = typeHierarchy.getSuperclass(type);
        if (parentType != null) {
            ViewElementDescriptor parentDescriptor = getDescriptor(parentType, project,
                    typeHierarchy);
            
            if (parentDescriptor != null) {
                // parent class is a valid View class with a descriptor, so we create one
                // for this class.
                ViewElementDescriptor descriptor = new ViewElementDescriptor(canonicalName,
                        canonicalName, // ui_name
                        canonicalName, // canonical name
                        null, // tooltip
                        null, // sdk_url
                        getAttributeDescriptor(type, parentDescriptor),
                        null, // layout attributes
                        null, // children
                        false /* mandatory */);
                
                // add it to the map
                synchronized (mCustomDescriptorMap) {
                    HashMap<String, ElementDescriptor> map = mCustomDescriptorMap.get(project);
                    
                    if (map == null) {
                        map = new HashMap<String, ElementDescriptor>();
                        mCustomDescriptorMap.put(project, map);
                    }
                    
                    map.put(canonicalName, descriptor);
                    
                }

                //TODO setup listener on this resource change.
                
                return descriptor;
            }
        }
        
        // class is neither a built-in view class, nor extend one. return null.
        return null;
    }
    
    /**
     * Returns the array of {@link AttributeDescriptor} for the specified {@link IType}.
     * <p/>
     * The array should contain the descriptor for this type and all its supertypes.
     * @param type the type for which the {@link AttributeDescriptor} are returned.
     * @param parentDescriptor the {@link ElementDescriptor} of the direct superclass.
     */
    private AttributeDescriptor[] getAttributeDescriptor(IType type,
            ElementDescriptor parentDescriptor) {
        // TODO add the class attribute descriptors to the parent descriptors.
        return parentDescriptor.getAttributes();
    }
}