--- /dev/null
+/*
+ * 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.adt.internal.editors.layout.descriptors;
+
+import com.android.ide.common.resources.platform.ViewClassInfo;
+import com.android.ide.eclipse.adt.internal.editors.descriptors.AttributeDescriptor;
+import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData;
+import com.android.ide.eclipse.adt.internal.sdk.Sdk;
+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 ViewElementDescriptor} 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 ViewElementDescriptor} object has been done for a specific
+ * class.
+ * <p/>
+ * The monitoring will notify a listener of any changes in the class triggering a change in its
+ * associated {@link ViewElementDescriptor} 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 ViewElementDescriptor}.
+ */
+ private HashMap<IProject, HashMap<String, ViewElementDescriptor>> mCustomDescriptorMap =
+ new HashMap<IProject, HashMap<String, ViewElementDescriptor>>();
+
+ /**
+ * TODO will be used to update the ViewElementDescriptor 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 ViewElementDescriptor} 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,
+ ViewElementDescriptor 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 ViewElementDescriptor 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 ViewElementDescriptor} for a particular project/class when the
+ * fully qualified class name actually matches a class from the given project.
+ * <p/>
+ * Custom descriptors are created as needed.
+ * <p/>
+ * If it is the first time the {@link ViewElementDescriptor} 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 fqcn the fully qualified name of the class.
+ * @return a {@link ViewElementDescriptor} or <code>null</code> if the class was not
+ * a custom View class.
+ */
+ public ViewElementDescriptor getDescriptor(IProject project, String fqcn) {
+ // look in the map first
+ synchronized (mCustomDescriptorMap) {
+ HashMap<String, ViewElementDescriptor> map = mCustomDescriptorMap.get(project);
+
+ if (map != null) {
+ ViewElementDescriptor descriptor = map.get(fqcn);
+ 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 = fqcn.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());
+
+ ViewElementDescriptor parentDescriptor = createViewDescriptor(
+ hierarchy.getSuperclass(type), project, hierarchy);
+
+ if (parentDescriptor != null) {
+ // we have a valid parent, lets create a new ViewElementDescriptor.
+
+ ViewElementDescriptor descriptor = new ViewElementDescriptor(fqcn,
+ fqcn, // ui_name
+ fqcn, // canonical class name
+ null, // tooltip
+ null, // sdk_url
+ getAttributeDescriptor(type, parentDescriptor),
+ null, // layout attributes
+ null, // children
+ false /* mandatory */);
+
+ descriptor.setSuperClass(parentDescriptor);
+
+ synchronized (mCustomDescriptorMap) {
+ map = mCustomDescriptorMap.get(project);
+ if (map == null) {
+ map = new HashMap<String, ViewElementDescriptor>();
+ mCustomDescriptorMap.put(project, map);
+ }
+
+ map.put(fqcn, 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 ViewElementDescriptor} for the specified type.
+ *
+ * @return A {@link ViewElementDescriptor} or null if type or typeHierarchy is null.
+ */
+ private ViewElementDescriptor createViewDescriptor(IType type, IProject project,
+ ITypeHierarchy typeHierarchy) {
+ // check if the type is a built-in View class.
+ List<ViewElementDescriptor> builtInList = null;
+
+ Sdk currentSdk = Sdk.getCurrent();
+ if (currentSdk != null) {
+ IAndroidTarget target = currentSdk.getTarget(project);
+ if (target != null) {
+ AndroidTargetData data = currentSdk.getTargetData(target);
+ if (data != null) {
+ builtInList = data.getLayoutDescriptors().getViewDescriptors();
+ }
+ }
+ }
+
+ // give up if there's no type
+ if (type == null) {
+ return null;
+ }
+
+ String fqcn = type.getFullyQualifiedName();
+
+ if (builtInList != null) {
+ for (ViewElementDescriptor viewDescriptor : builtInList) {
+ if (fqcn.equals(viewDescriptor.getFullClassName())) {
+ 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 = createViewDescriptor(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(fqcn,
+ fqcn, // ui_name
+ fqcn, // canonical name
+ null, // tooltip
+ null, // sdk_url
+ getAttributeDescriptor(type, parentDescriptor),
+ null, // layout attributes
+ null, // children
+ false /* mandatory */);
+
+ descriptor.setSuperClass(parentDescriptor);
+
+ // add it to the map
+ synchronized (mCustomDescriptorMap) {
+ HashMap<String, ViewElementDescriptor> map = mCustomDescriptorMap.get(project);
+
+ if (map == null) {
+ map = new HashMap<String, ViewElementDescriptor>();
+ mCustomDescriptorMap.put(project, map);
+ }
+
+ map.put(fqcn, 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 ViewElementDescriptor} of the direct superclass.
+ */
+ private AttributeDescriptor[] getAttributeDescriptor(IType type,
+ ViewElementDescriptor parentDescriptor) {
+ // TODO add the class attribute descriptors to the parent descriptors.
+ return parentDescriptor.getAttributes();
+ }
+}