ADT GRE (Groovy Rules Engine), part 1.

This CL adds the new "Groovy Rules Engine" (GRE) to the GLE2.

The rules engine can load groovy files located in the ADT
namespace or the project associated with the current GLE2.
Each groovy file defines a class with callbacks invoked
by the LayoutCanvas.
Project rules are reloaded when they change.

Change-Id: I168234da739b2120374d3eb4552169f7dd36439d
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtPlugin.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtPlugin.java
index ddedd93..f34f72b 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtPlugin.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtPlugin.java
@@ -115,6 +115,7 @@
 /**
  * The activator class controls the plug-in life cycle
  */
+@SuppressWarnings("deprecation")
 public class AdtPlugin extends AbstractUIPlugin {
     /** The plug-in ID */
     public static final String PLUGIN_ID = "com.android.ide.eclipse.adt"; //$NON-NLS-1$
@@ -222,7 +223,6 @@
      * (non-Javadoc)
      * @see org.eclipse.ui.plugin.AbstractUIPlugin#start(org.osgi.framework.BundleContext)
      */
-    @SuppressWarnings("deprecation")
     @Override
     public void start(BundleContext context) throws Exception {
         super.start(context);
@@ -1244,7 +1244,8 @@
 
                                 IEditorPart oldEditor = page == null ? null :
                                                         page.findEditor(new FileEditorInput(file));
-                                if (oldEditor != null &&
+                                if (page != null &&
+                                        oldEditor != null &&
                                         AdtPlugin.displayPrompt("Android XML Editor",
                                             String.format("The file you just saved as been recognized as a file that could be better handled using the Android XML Editor. Do you want to edit '%1$s' using the Android XML editor instead?",
                                                     file.getFullPath()))) {
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/ApkBuilder.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/ApkBuilder.java
index a7c0e62..287ea41 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/ApkBuilder.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/ApkBuilder.java
@@ -232,7 +232,7 @@
             mNativeLibs.clear();
             mNativeLibInteference = false;
         }
-    };
+    }
 
     private final JavaAndNativeResourceFilter mResourceFilter = new JavaAndNativeResourceFilter();
 
@@ -253,7 +253,7 @@
     }
 
     // build() returns a list of project from which this project depends for future compilation.
-    @SuppressWarnings("unchecked")
+    @SuppressWarnings({"unchecked", "unused"})
     @Override
     protected IProject[] build(int kind, Map args, IProgressMonitor monitor)
             throws CoreException {
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/descriptors/DescriptorsUtils.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/descriptors/DescriptorsUtils.java
index f7c71b7..246113c 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/descriptors/DescriptorsUtils.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/descriptors/DescriptorsUtils.java
@@ -226,8 +226,8 @@
                         continue;
                     }
 
-                    boolean ok_element = elements.length < 1;
-                    if (!ok_element) {
+                    boolean ok_element = elements != null && elements.length < 1;
+                    if (!ok_element && elements != null) {
                         for (String element : elements) {
                             if (element.equals("*")              //$NON-NLS-1$
                                     || element.equals(elementXmlName)) {
@@ -242,7 +242,7 @@
                     }
 
                     Object override = entry.getValue();
-                    if (override instanceof Class) {
+                    if (override instanceof Class<?>) {
                         try {
                             // The override is instance of the class to create, which must
                             // have a constructor compatible with TextAttributeDescriptor.
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/descriptors/ElementDescriptor.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/descriptors/ElementDescriptor.java
index 9135241..650a8ac 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/descriptors/ElementDescriptor.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/descriptors/ElementDescriptor.java
@@ -30,10 +30,10 @@
 
 /**
  * {@link ElementDescriptor} describes the properties expected for a given XML element node.
- * 
+ *
  * {@link ElementDescriptor} have an XML name, UI name, a tooltip, an SDK url,
  * an attributes list and a children list.
- * 
+ *
  * An UI node can be "mandatory", meaning the UI node is never deleted and it may lack
  * an actual XML node attached. A non-mandatory UI node MUST have an XML node attached
  * and it will cease to exist when the XML node ceases to exist.
@@ -57,7 +57,7 @@
     /**
      * Constructs a new {@link ElementDescriptor} based on its XML name, UI name,
      * tooltip, SDK url, attributes list, children list and mandatory.
-     * 
+     *
      * @param xml_name The XML element node name. Case sensitive.
      * @param ui_name The XML element name for the user interface, typically capitalized.
      * @param tooltip An optional tooltip. Can be null or empty.
@@ -86,7 +86,7 @@
      * Constructs a new {@link ElementDescriptor} based on its XML name and children list.
      * The UI name is build by capitalizing the XML name.
      * The UI nodes will be non-mandatory.
-     * 
+     *
      * @param xml_name The XML element node name. Case sensitive.
      * @param children The list of allowed children. Can be null or empty.
      * @param mandatory Whether this node must always exist (even for empty models). A mandatory
@@ -102,7 +102,7 @@
      * Constructs a new {@link ElementDescriptor} based on its XML name and children list.
      * The UI name is build by capitalizing the XML name.
      * The UI nodes will be non-mandatory.
-     * 
+     *
      * @param xml_name The XML element node name. Case sensitive.
      * @param children The list of allowed children. Can be null or empty.
      */
@@ -114,7 +114,7 @@
      * Constructs a new {@link ElementDescriptor} based on its XML name.
      * The UI name is build by capitalizing the XML name.
      * The UI nodes will be non-mandatory.
-     * 
+     *
      * @param xml_name The XML element node name. Case sensitive.
      */
     public ElementDescriptor(String xml_name) {
@@ -125,12 +125,12 @@
     public boolean isMandatory() {
         return mMandatory;
     }
-    
+
     /**
      * Returns the XML element node local name (case sensitive)
      */
     public final String getXmlLocalName() {
-        int pos = mXmlName.indexOf(':'); 
+        int pos = mXmlName.indexOf(':');
         if (pos != -1) {
             return mXmlName.substring(pos+1);
         }
@@ -141,7 +141,7 @@
     public String getXmlName() {
         return mXmlName;
     }
-    
+
     /**
      * Returns the namespace of the attribute.
      */
@@ -150,7 +150,7 @@
         if (mXmlName.startsWith("android:")) { //$NON-NLs-1$
             return SdkConstants.NS_RESOURCES;
         }
-        
+
         return ""; //$NON-NLs-1$
     }
 
@@ -160,13 +160,13 @@
         return mUiName;
     }
 
-    /** 
+    /**
      * Returns an optional icon for the element.
      * <p/>
      * By default this tries to return an icon based on the XML name of the element.
      * If this fails, it tries to return the default Android logo as defined in the
      * plugin. If all fails, it returns null.
-     * 
+     *
      * @return An icon for this element or null.
      */
     public Image getIcon() {
@@ -177,13 +177,13 @@
         return icon != null ? icon : AdtPlugin.getAndroidLogo();
     }
 
-    /** 
+    /**
      * Returns an optional ImageDescriptor for the element.
      * <p/>
      * By default this tries to return an image based on the XML name of the element.
      * If this fails, it tries to return the default Android logo as defined in the
      * plugin. If all fails, it returns null.
-     * 
+     *
      * @return An ImageDescriptor for this element or null.
      */
     public ImageDescriptor getImageDescriptor() {
@@ -198,7 +198,7 @@
     public AttributeDescriptor[] getAttributes() {
         return mAttributes;
     }
-    
+
     /* Sets the list of allowed attributes. */
     public void setAttributes(AttributeDescriptor[] attributes) {
         mAttributes = attributes;
@@ -222,7 +222,8 @@
         mChildren = newChildren;
     }
 
-    /** Sets the list of allowed children.
+    /**
+     * Sets the list of allowed children.
      * <p/>
      * This is just a convenience method that converts a Collection into an array and
      * calls {@link #setChildren(ElementDescriptor[])}.
@@ -254,7 +255,7 @@
     public void setTooltip(String tooltip) {
         mTooltip = tooltip;
     }
-    
+
     /** Sets the optional SDK URL. Can be null or empty. */
     public void setSdkUrl(String sdkUrl) {
         mSdkUrl = sdkUrl;
@@ -266,12 +267,12 @@
     public UiElementNode createUiNode() {
         return new UiElementNode(this);
     }
-    
+
     /**
-     * Returns the first children of this descriptor that describes the given XML element name. 
+     * Returns the first children of this descriptor that describes the given XML element name.
      * <p/>
      * In recursive mode, searches the direct children first before descending in the hierarchy.
-     * 
+     *
      * @return The ElementDescriptor matching the requested XML node element name or null.
      */
     public ElementDescriptor findChildrenDescriptor(String element_name, boolean recursive) {
@@ -316,7 +317,7 @@
     /**
      * Utility helper than pretty-formats an XML Name for the UI.
      * This is used by the simplified constructor that takes only an XML element name.
-     * 
+     *
      * @param xml_name The XML name to convert.
      * @return The XML name with dashes replaced by spaces and capitalized.
      */
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/UiElementPullParser.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/UiElementPullParser.java
index b075c17..3a05e8a 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/UiElementPullParser.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/UiElementPullParser.java
@@ -25,6 +25,7 @@
 import com.android.ide.eclipse.adt.internal.sdk.Sdk;
 import com.android.layoutlib.api.IXmlPullParser;
 import com.android.layoutlib.api.IDensityBasedResourceValue.Density;
+import com.android.layoutlib.api.ILayoutResult.ILayoutViewInfo;
 import com.android.sdklib.IAndroidTarget;
 import com.android.sdklib.SdkConstants;
 
@@ -42,8 +43,10 @@
 
 /**
  * {@link IXmlPullParser} implementation on top of {@link UiElementNode}.
- * <p/>It's designed to work on layout files, and will most likely not work on other resource
- * files.
+ * <p/>
+ * It's designed to work on layout files, and will most likely not work on other resource files.
+ * <p/>
+ * This pull parser generates {@link ILayoutViewInfo}s which key is a {@link UiElementNode}.
  */
 public final class UiElementPullParser extends BasePullParser {
     private final static String ATTR_PADDING = "padding"; //$NON-NLS-1$
@@ -139,8 +142,14 @@
 
     /**
      * {@inheritDoc}
-     *
-     * This implementation returns the underlying DOM node.
+     * <p/>
+     * This implementation returns the underlying DOM node of type {@link UiElementNode}.
+     * Note that the link between the GLE and the parsing code depends on this being the actual
+     * type returned, so you can't just randomly change it here.
+     * <p/>
+     * Currently used by:
+     * - private method GraphicalLayoutEditor#updateNodeWithBounds(ILayoutViewInfo).
+     * - private constructor of LayoutCanvas.ViewInfo.
      */
     public Object getViewKey() {
         return getCurrentNode();
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ConfigManagerDialog.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ConfigManagerDialog.java
index 61d870b..8b585dd 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ConfigManagerDialog.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ConfigManagerDialog.java
@@ -459,6 +459,7 @@
      * Returns a {@link DeviceSelection} object representing the selected path in the
      * {@link TreeViewer}
      */
+    @SuppressWarnings("unchecked")
     private DeviceSelection getSelection() {
         // get the selection paths
         TreeSelection selection = (TreeSelection)mTreeViewer.getSelection();
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/descriptors/CustomViewDescriptorService.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/descriptors/CustomViewDescriptorService.java
index be844cc..72fee74 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/descriptors/CustomViewDescriptorService.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/descriptors/CustomViewDescriptorService.java
@@ -273,6 +273,7 @@
      * 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.
      */
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/descriptors/LayoutDescriptors.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/descriptors/LayoutDescriptors.java
index 89f0a3b..19d93f8 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/descriptors/LayoutDescriptors.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/descriptors/LayoutDescriptors.java
@@ -31,7 +31,9 @@
 
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map.Entry;
 
 
 /**
@@ -54,25 +56,25 @@
 
     /** The list of all known View (not ViewLayout) descriptors. */
     private ArrayList<ElementDescriptor> mViewDescriptors = new ArrayList<ElementDescriptor>();
-    
+
     /** Read-Only list of View Descriptors. */
     private List<ElementDescriptor> mROViewDescriptors;
-    
+
     /** @return the document descriptor. Contains all layouts and views linked together. */
     public DocumentDescriptor getDescriptor() {
         return mRootDescriptor;
     }
-    
+
     /** @return The read-only list of all known ViewLayout descriptors. */
     public List<ElementDescriptor> getLayoutDescriptors() {
         return mROLayoutDescriptors;
     }
-    
+
     /** @return The read-only list of all known View (not ViewLayout) descriptors. */
     public List<ElementDescriptor> getViewDescriptors() {
         return mROViewDescriptors;
     }
-    
+
     public ElementDescriptor[] getRootElementDescriptors() {
         return mRootDescriptor.getChildren();
     }
@@ -82,18 +84,24 @@
      * <p/>
      * It first computes the new children of the descriptor and then update them
      * all at once.
-     * <p/> 
+     * <p/>
      *  TODO: differentiate groups from views in the tree UI? => rely on icons
-     * <p/> 
-     * 
+     * <p/>
+     *
      * @param views The list of views in the framework.
      * @param layouts The list of layouts in the framework.
      */
     public synchronized void updateDescriptors(ViewClassInfo[] views, ViewClassInfo[] layouts) {
+
+        // This map links every ViewClassInfo to the ElementDescriptor we created.
+        // It is filled by convertView() and used later to fix the super-class hierarchy.
+        HashMap<ViewClassInfo, ViewElementDescriptor> infoDescMap =
+            new HashMap<ViewClassInfo, ViewElementDescriptor>();
+
         ArrayList<ElementDescriptor> newViews = new ArrayList<ElementDescriptor>();
         if (views != null) {
             for (ViewClassInfo info : views) {
-                ElementDescriptor desc = convertView(info);
+                ElementDescriptor desc = convertView(info, infoDescMap);
                 newViews.add(desc);
             }
         }
@@ -105,7 +113,7 @@
         ArrayList<ElementDescriptor> newLayouts = new ArrayList<ElementDescriptor>();
         if (layouts != null) {
             for (ViewClassInfo info : layouts) {
-                ElementDescriptor desc = convertView(info);
+                ElementDescriptor desc = convertView(info, infoDescMap);
                 newLayouts.add(desc);
             }
         }
@@ -119,6 +127,8 @@
             layoutDesc.setChildren(newDescriptors);
         }
 
+        fixSuperClasses(infoDescMap);
+
         // The <merge> tag can only be a root tag, so it is added at the end.
         // It gets everything else as children but it is not made a child itself.
         ElementDescriptor mergeTag = createMerge(newLayouts);
@@ -129,20 +139,26 @@
         mViewDescriptors = newViews;
         mLayoutDescriptors  = newLayouts;
         mRootDescriptor.setChildren(newDescriptors);
-        
+
         mROLayoutDescriptors = Collections.unmodifiableList(mLayoutDescriptors);
         mROViewDescriptors = Collections.unmodifiableList(mViewDescriptors);
     }
 
     /**
      * Creates an element descriptor from a given {@link ViewClassInfo}.
+     *
+     * @param info The {@link ViewClassInfo} to convert into a new {@link ViewElementDescriptor}.
+     * @param infoDescMap This map links every ViewClassInfo to the ElementDescriptor it created.
+     *                    It is filled by here and used later to fix the super-class hierarchy.
      */
-    private ElementDescriptor convertView(ViewClassInfo info) {
+    private ElementDescriptor convertView(
+            ViewClassInfo info,
+            HashMap<ViewClassInfo, ViewElementDescriptor> infoDescMap) {
         String xml_name = info.getShortClassName();
         String tooltip = info.getJavaDoc();
-        
+
         ArrayList<AttributeDescriptor> attributes = new ArrayList<AttributeDescriptor>();
-        
+
         // All views and groups have an implicit "style" attribute which is a reference.
         AttributeInfo styleInfo = new DeclareStyleableInfo.AttributeInfo(
                 "style",    //$NON-NLS-1$ xmlLocalName
@@ -156,7 +172,7 @@
                 styleInfo,
                 false,      //required
                 null);      // overrides
-        
+
         // Process all View attributes
         DescriptorsUtils.appendAttributes(attributes,
                 null, // elementName
@@ -164,14 +180,14 @@
                 info.getAttributes(),
                 null, // requiredAttributes
                 null /* overrides */);
-        
+
         for (ViewClassInfo link = info.getSuperClass();
                 link != null;
                 link = link.getSuperClass()) {
             AttributeInfo[] attrList = link.getAttributes();
             if (attrList.length > 0) {
                 attributes.add(new SeparatorAttributeDescriptor(
-                        String.format("Attributes from %1$s", link.getShortClassName()))); 
+                        String.format("Attributes from %1$s", link.getShortClassName())));
                 DescriptorsUtils.appendAttributes(attributes,
                         null, // elementName
                         SdkConstants.NS_RESOURCES,
@@ -215,7 +231,7 @@
             }
         }
 
-        return new ViewElementDescriptor(xml_name,
+        ViewElementDescriptor desc = new ViewElementDescriptor(xml_name,
                 xml_name, // ui_name
                 info.getFullClassName(),
                 tooltip,
@@ -224,11 +240,13 @@
                 layoutAttributes.toArray(new AttributeDescriptor[layoutAttributes.size()]),
                 null, // children
                 false /* mandatory */);
+        infoDescMap.put(info, desc);
+        return desc;
     }
 
     /**
      * Creates a new <include> descriptor and adds it to the list of view descriptors.
-     * 
+     *
      * @param knownViews A list of view descriptors being populated. Also used to find the
      *   View descriptor and extract its layout attributes.
      */
@@ -237,7 +255,7 @@
 
         // Create the include custom attributes
         ArrayList<AttributeDescriptor> attributes = new ArrayList<AttributeDescriptor>();
-        
+
         // Note that the "layout" attribute does NOT have the Android namespace
         DescriptorsUtils.appendAttribute(attributes,
                 null, //elementXmlName
@@ -273,7 +291,7 @@
                 viewLayoutAttribs,  // layout attributes
                 null, // children
                 false /* mandatory */);
-        
+
         knownViews.add(desc);
     }
 
@@ -318,7 +336,37 @@
                 }
             }
         }
-        
+
         return null;
     }
+
+    /**
+     * Set the super-class of each {@link ViewElementDescriptor} by using the super-class
+     * information available in the {@link ViewClassInfo}.
+     */
+    private void fixSuperClasses(HashMap<ViewClassInfo, ViewElementDescriptor> infoDescMap) {
+
+        for (Entry<ViewClassInfo, ViewElementDescriptor> entry : infoDescMap.entrySet()) {
+            ViewClassInfo info = entry.getKey();
+            ViewElementDescriptor desc = entry.getValue();
+
+            ViewClassInfo sup = info.getSuperClass();
+            if (sup != null) {
+                ViewElementDescriptor supDesc = infoDescMap.get(sup);
+                while (supDesc == null && sup != null) {
+                    // We don't have a descriptor for the super-class. That means the class is
+                    // probably abstract, so we just need to walk up the super-class chain till
+                    // we find one we have. All views derive from android.view.View so we should
+                    // surely find that eventually.
+                    sup = sup.getSuperClass();
+                    if (sup != null) {
+                        supDesc = infoDescMap.get(sup);
+                    }
+                }
+                if (supDesc != null) {
+                    desc.setSuperClass(supDesc);
+                }
+            }
+        }
+    }
 }
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/descriptors/ViewElementDescriptor.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/descriptors/ViewElementDescriptor.java
index bd3332e..b4f4516 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/descriptors/ViewElementDescriptor.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/descriptors/ViewElementDescriptor.java
@@ -24,6 +24,22 @@
 /**
  * {@link ViewElementDescriptor} describes the properties expected for a given XML element node
  * representing a class in an XML Layout file.
+ * <p/>
+ * These descriptors describe Android views XML elements.
+ * <p/>
+ * The base class {@link ElementDescriptor} has a notion of "children", that is an XML element
+ * can produce another set of XML elements. Because of the flat nature of Android's layout
+ * XML files all possible views are children of the document and of themselves (that is any
+ * view group can contain any other view). This is an implied contract of this class that is
+ * enforces at construction by {@link LayoutDescriptors}. Note that by construction any code
+ * that deals with the children hierarchy must also deal with potential infinite loops since views
+ * <em>will</em> reference themselves (e.g. a ViewGroup can contain a ViewGroup).
+ * <p/>
+ * Since Views are also Java classes, they derive from each other. Here this is represented
+ * as the "super class", which denotes the fact that a given View java class derives from
+ * another class. These properties are also set at construction by {@link LayoutDescriptors}.
+ * The super class hierarchy is very different from the descriptor's children hierarchy: the
+ * format represents Java inheritance, the former represents an XML nesting capability.
  *
  * @see ElementDescriptor
  */
@@ -35,6 +51,8 @@
     /** The list of layout attributes. Can be empty but not null. */
     private AttributeDescriptor[] mLayoutAttributes;
 
+    /** The super-class descriptor. Can be null. */
+    private ViewElementDescriptor mSuperClassDesc;
 
     /**
      * Constructs a new {@link ViewElementDescriptor} based on its XML name, UI name,
@@ -79,6 +97,8 @@
      *  UI node is never deleted and it may lack an actual XML node attached. A non-mandatory
      *  UI node MUST have an XML node attached and it will cease to exist when the XML node
      *  ceases to exist.
+     *
+     *  @deprecated Never used. We should clean it up someday.
      */
     public ViewElementDescriptor(String xml_name, String fullClassName,
             ElementDescriptor[] children,
@@ -96,6 +116,8 @@
      * @param fullClassName The fully qualified class name the {@link ViewElementDescriptor} is
      * representing.
      * @param children The list of allowed children. Can be null or empty.
+     *
+     *  @deprecated Never used. We should clean it up someday.
      */
     public ViewElementDescriptor(String xml_name, String fullClassName,
             ElementDescriptor[] children) {
@@ -112,6 +134,8 @@
      * @param xml_name The XML element node name. Case sensitive.
      * @param fullClassName The fully qualified class name the {@link ViewElementDescriptor} is
      * representing.
+     *
+     *  @deprecated Never used. We should clean it up someday.
      */
     public ViewElementDescriptor(String xml_name, String fullClassName) {
         super(xml_name);
@@ -132,10 +156,26 @@
     }
 
     /**
-     * @return A new {@link UiViewElementNode} linked to this descriptor.
+     * Returns a new {@link UiViewElementNode} linked to this descriptor.
      */
     @Override
     public UiElementNode createUiNode() {
         return new UiViewElementNode(this);
     }
+
+    /**
+     * Returns the {@link ViewElementDescriptor} of the super-class of this View descriptor
+     * that matches the java View hierarchy. Can be null.
+     */
+    public ViewElementDescriptor getSuperClassDesc() {
+        return mSuperClassDesc;
+    }
+
+    /**
+     * Sets the {@link ViewElementDescriptor} of the super-class of this View descriptor
+     * that matches the java View hierarchy. Can be null.
+     */
+    public void setSuperClass(ViewElementDescriptor superClassDesc) {
+        mSuperClassDesc = superClassDesc;
+    }
 }
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ElementDescTransfer.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ElementDescTransfer.java
new file mode 100755
index 0000000..ec2e0e0
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ElementDescTransfer.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2009 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.gle2;
+
+import com.android.ide.eclipse.adt.internal.editors.descriptors.ElementDescriptor;
+
+import org.eclipse.swt.dnd.ByteArrayTransfer;
+import org.eclipse.swt.dnd.Transfer;
+import org.eclipse.swt.dnd.TransferData;
+
+import java.io.UnsupportedEncodingException;
+
+/**
+ * A d'n'd {@link Transfer} class that can transfer {@link ElementDescriptor}s.
+ * <p/>
+ * The implementation is based on the {@link ByteArrayTransfer} and what we transfer
+ * is actually only the inner XML name of the element, which is unique enough.
+ * <p/>
+ * Drag source provides an {@link ElementDescriptor} object.
+ * Drog receivers get back a {@link String} object representing the
+ * {@link ElementDescriptor#getXmlName()}.
+ * <p/>
+ * Drop receivers can find the corresponding element by using
+ * {@link ElementDescriptor#findChildrenDescriptor(String, boolean)} with the
+ * XML name returned by this transfer operation and their root descriptor.
+ * <p/>
+ * Drop receivers must deal with the fact that this XML name may not exist in their
+ * own {@link ElementDescriptor} hierarchy -- e.g. if the drag came from a different
+ * GLE based on a different SDK platform or using custom widgets. In this case they
+ * must refuse the drop.
+ */
+public class ElementDescTransfer extends ByteArrayTransfer {
+
+    // Reference: http://www.eclipse.org/articles/Article-SWT-DND/DND-in-SWT.html
+
+
+    private static final String TYPE_NAME = "android.ADT.element.desc.transfer.1";
+    private static final int TYPE_ID = registerType(TYPE_NAME);
+    private static ElementDescTransfer sInstance = new ElementDescTransfer();
+
+    private ElementDescTransfer() {
+        // pass
+    }
+
+    public static ElementDescTransfer getInstance() {
+        return sInstance;
+    }
+
+    @Override
+    protected int[] getTypeIds() {
+        return new int[] { TYPE_ID };
+    }
+
+    @Override
+    protected String[] getTypeNames() {
+        return new String[] { TYPE_NAME };
+    }
+
+    @Override
+    protected void javaToNative(Object object, TransferData transferData) {
+        if (object == null || !(object instanceof ElementDescriptor[])) {
+            return;
+        }
+
+        if (isSupportedType(transferData)) {
+            StringBuilder sb = new StringBuilder();
+            boolean needSeparator = false;
+            for (ElementDescriptor desc : (ElementDescriptor[]) object) {
+                if (needSeparator) {
+                    sb.append(';');
+                }
+                sb.append(desc.getXmlName());
+                needSeparator = true;
+            }
+            try {
+                byte[] buf = sb.toString().getBytes("UTF-8");  //$NON-NLS-1$
+                super.javaToNative(buf, transferData);
+            } catch (UnsupportedEncodingException e) {
+                // unlikely; ignore
+            }
+        }
+    }
+
+    @Override
+    protected Object nativeToJava(TransferData transferData) {
+        if (isSupportedType(transferData)) {
+            byte[] buf = (byte[]) super.nativeToJava(transferData);
+            if (buf != null && buf.length > 0) {
+                try {
+                    String s = new String(buf, "UTF-8"); //$NON-NLS-1$
+                    String[] names = s.split(";");  //$NON-NLS-1$
+                    return names;
+                } catch (UnsupportedEncodingException e) {
+                    // unlikely to happen, but still possible
+                }
+            }
+        }
+
+        return null;
+    }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GraphicalEditorPart.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GraphicalEditorPart.java
index a8c6386..31ad66c 100755
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GraphicalEditorPart.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GraphicalEditorPart.java
@@ -28,6 +28,7 @@
 import com.android.ide.eclipse.adt.internal.editors.layout.configuration.LayoutCreatorDialog;

 import com.android.ide.eclipse.adt.internal.editors.layout.configuration.ConfigurationComposite.CustomToggle;

 import com.android.ide.eclipse.adt.internal.editors.layout.configuration.ConfigurationComposite.IConfigListener;

+import com.android.ide.eclipse.adt.internal.editors.layout.gre.RulesEngine;

 import com.android.ide.eclipse.adt.internal.editors.layout.parts.ElementCreateCommand;

 import com.android.ide.eclipse.adt.internal.editors.uimodel.UiDocumentNode;

 import com.android.ide.eclipse.adt.internal.editors.uimodel.UiElementNode;

@@ -110,7 +111,7 @@
     /** Reference to the layout editor */

     private final LayoutEditor mLayoutEditor;

 

-    /** reference to the file being edited. */

+    /** reference to the file being edited. Can also be used to access the {@link IProject}. */

     private IFile mEditedFile;

 

     /** The current clipboard. Must be disposed later. */

@@ -126,9 +127,12 @@
     /** The palette displayed on the left of the sash. */

     private PaletteComposite mPalette;

 

-    /** The layout canvas displayed o the right of the sash. */

+    /** The layout canvas displayed to the right of the sash. */

     private LayoutCanvas mLayoutCanvas;

 

+    /** The Groovy Rules Engine associated with this editor. It is project-specific. */

+    private RulesEngine mRulesEngine;

+

     private StyledText mErrorLabel;

 

     /** The {@link FolderConfiguration} being edited. */

@@ -178,6 +182,10 @@
             mReloadListener = new ReloadListener();

             LayoutReloadMonitor.getMonitor().addListener(mEditedFile.getProject(), mReloadListener);

         }

+

+        if (mRulesEngine == null) {

+            mRulesEngine = new RulesEngine(mEditedFile.getProject());

+        }

     }

 

     /**

@@ -256,6 +264,8 @@
         mSashError.setLayoutData(new GridData(GridData.FILL_BOTH));

 

         mLayoutCanvas = new LayoutCanvas(mSashError, SWT.NONE);

+        mLayoutCanvas.setRulesEngine(mRulesEngine);

+

         mErrorLabel = new StyledText(mSashError, SWT.READ_ONLY);

         mErrorLabel.setEditable(false);

         mErrorLabel.setBackground(d.getSystemColor(SWT.COLOR_INFO_BACKGROUND));

diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutCanvas.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutCanvas.java
index 1f7658f..d4d029f 100755
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutCanvas.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutCanvas.java
@@ -16,11 +16,19 @@
 

 package com.android.ide.eclipse.adt.internal.editors.layout.gle2;

 

+import com.android.ide.eclipse.adt.AdtPlugin;

+import com.android.ide.eclipse.adt.internal.editors.layout.gre.RulesEngine;

+import com.android.ide.eclipse.adt.internal.editors.layout.uimodel.UiViewElementNode;

 import com.android.layoutlib.api.ILayoutResult;

 import com.android.layoutlib.api.ILayoutResult.ILayoutViewInfo;

 

 import org.eclipse.swt.SWT;

 import org.eclipse.swt.dnd.Clipboard;

+import org.eclipse.swt.dnd.DND;

+import org.eclipse.swt.dnd.DropTarget;

+import org.eclipse.swt.dnd.DropTargetEvent;

+import org.eclipse.swt.dnd.DropTargetListener;

+import org.eclipse.swt.dnd.Transfer;

 import org.eclipse.swt.events.MouseEvent;

 import org.eclipse.swt.events.MouseListener;

 import org.eclipse.swt.events.MouseMoveListener;

@@ -68,11 +76,15 @@
      * width and height pseudo widgets.

      */

     private static final int IMAGE_MARGIN = 5;

+

     /**

      * Minimal size of the selection, in case an empty view or layout is selected.

      */

     private static final int SELECTION_MIN_SIZE = 6;

 

+    /** The Groovy Rules Engine, associated with the current project. */

+    private RulesEngine mRulesEngine;

+

     /*

      * The last valid ILayoutResult passed to {@link #setResult(ILayoutResult)}.

      * This can be null.

@@ -135,6 +147,9 @@
     /** When true, always display the outline of all views. */

     private boolean mShowOutline;

 

+    /** Drop target associated with this composite. */

+    private DropTarget mDropTarget;

+

 

     public LayoutCanvas(Composite parent, int style) {

         super(parent, style | SWT.DOUBLE_BUFFERED);

@@ -170,11 +185,34 @@
                 onDoubleClick(e);

             }

         });

+

+        mDropTarget = new DropTarget(this, DND.DROP_COPY | DND.DROP_MOVE);

+        mDropTarget.setTransfer(new Transfer[] { ElementDescTransfer.getInstance() });

+        mDropTarget.addDropListener(new CanvasDropListener());

     }

 

     @Override

     public void dispose() {

         super.dispose();

+

+        if (mHoverFgColor != null) {

+            mHoverFgColor.dispose();

+            mHoverFgColor = null;

+        }

+

+        if (mDropTarget != null) {

+            mDropTarget.dispose();

+            mDropTarget = null;

+        }

+

+        if (mRulesEngine != null) {

+            mRulesEngine.dispose();

+            mRulesEngine = null;

+        }

+    }

+

+    public void setRulesEngine(RulesEngine rulesEngine) {

+        mRulesEngine = rulesEngine;

     }

 

     /**

@@ -188,7 +226,6 @@
      * @param result The new rendering result, either valid or not.

      */

     public void setResult(ILayoutResult result) {

-

         // disable any hover

         mHoverRect = null;

 

@@ -205,7 +242,7 @@
                 Selection s = it.next();

 

                 // Check the if the selected object still exists

-                Object key = s.getViewInfo().getKey();

+                Object key = s.getViewInfo().getUiViewKey();

                 ViewInfo vi = findViewInfoKey(key, mLastValidViewInfoRoot);

 

                 // Remove the previous selection -- if the selected object still exists

@@ -539,7 +576,7 @@
      * Returns null if not found.

      */

     private ViewInfo findViewInfoKey(Object viewKey, ViewInfo viewInfo) {

-        if (viewInfo.getKey() == viewKey) {

+        if (viewInfo.getUiViewKey() == viewKey) {

             return viewInfo;

         }

 

@@ -642,7 +679,7 @@
         private final Rectangle mAbsRect;

         private final Rectangle mSelectionRect;

         private final String mName;

-        private final Object mKey;

+        private final UiViewElementNode mUiViewKey;

         private final ViewInfo mParent;

         private final ArrayList<ViewInfo> mChildren = new ArrayList<ViewInfo>();

 

@@ -658,9 +695,16 @@
 

         private ViewInfo(ILayoutViewInfo viewInfo, ViewInfo parent, int parentX, int parentY) {

             mParent = parent;

-            mKey  = viewInfo.getViewKey();

             mName = viewInfo.getName();

 

+            // The ILayoutViewInfo#getViewKey() method returns a key which depends on the

+            // IXmlPullParser used to parse the layout files. In this case, the parser is

+            // guaranteed to be an UiElementPullParser, which creates keys that are of type

+            // UiViewElementNode.

+            // We'll simply crash if the type is not right, as this is not supposed to happen

+            // and nothing could work if there's a type mismatch.

+            mUiViewKey  = (UiViewElementNode) viewInfo.getViewKey();

+

             int x = viewInfo.getLeft();

             int y = viewInfo.getTop();

             int w = viewInfo.getRight() - x;

@@ -717,10 +761,11 @@
 

         /**

          * Returns the view key. Could be null, although unlikely.

+         * @return An {@link UiViewElementNode} that uniquely identifies the object in the XML model.

          * @see ILayoutViewInfo#getViewKey()

          */

-        public Object getKey() {

-            return mKey;

+        public UiViewElementNode getUiViewKey() {

+            return mUiViewKey;

         }

 

         /**

@@ -756,7 +801,7 @@
     /**

      * Represents one selection.

      */

-    private static class Selection {

+    private class Selection {

         /** Current selected view info. Cannot be null. */

         private final ViewInfo mViewInfo;

 

@@ -783,30 +828,44 @@
                 mRect = new Rectangle(r.x + IMAGE_MARGIN, r.y + IMAGE_MARGIN, r.width, r.height);

             }

 

-            String name = viewInfo == null ? null : viewInfo.getName();

-            if (name != null) {

+            mName = getViewShortName(viewInfo);

+        }

+

+        private String getViewShortName(ViewInfo viewInfo) {

+            if (viewInfo == null) {

+                return null;

+            }

+

+            String fqcn = viewInfo.getName();

+            if (fqcn == null) {

+                return null;

+            }

+

+            String name = mRulesEngine.getDisplayName(viewInfo.getUiViewKey());

+

+            if (name == null) {

                 // The name is typically a fully-qualified class name. Let's make it a tad shorter.

 

-                if (name.startsWith("android.")) {                                      // $NON-NLS-1$

+                if (fqcn.startsWith("android.")) {                                      // $NON-NLS-1$

                     // For android classes, convert android.foo.Name to android...Name

-                    int first = name.indexOf('.');

-                    int last = name.lastIndexOf('.');

+                    int first = fqcn.indexOf('.');

+                    int last = fqcn.lastIndexOf('.');

                     if (last > first) {

-                        name = name.substring(0, first) + ".." + name.substring(last);   // $NON-NLS-1$

+                        name = fqcn.substring(0, first) + ".." + fqcn.substring(last);   // $NON-NLS-1$

                     }

                 } else {

                     // For custom non-android classes, it's best to keep the 2 first segments of

                     // the namespace, e.g. we want to get something like com.example...MyClass

-                    int first = name.indexOf('.');

-                    first = name.indexOf('.', first + 1);

-                    int last = name.lastIndexOf('.');

+                    int first = fqcn.indexOf('.');

+                    first = fqcn.indexOf('.', first + 1);

+                    int last = fqcn.lastIndexOf('.');

                     if (last > first) {

-                        name = name.substring(0, first) + ".." + name.substring(last);   // $NON-NLS-1$

+                        name = fqcn.substring(0, first) + ".." + fqcn.substring(last);   // $NON-NLS-1$

                     }

                 }

             }

 

-            mName = name;

+            return name;

         }

 

         /**

@@ -887,4 +946,101 @@
         }

     }

 

+    // --- drop support ----

+

+    private class CanvasDropListener implements DropTargetListener {

+

+        private ViewInfo mCurrentView;

+

+        /*

+         * The cursor has entered the drop target boundaries.

+         * {@inheritDoc}

+         */

+        public void dragEnter(DropTargetEvent event) {

+            AdtPlugin.printErrorToConsole("DEBUG", "drag enter", event);

+            updateDropInfo(event);

+        }

+

+        /*

+         * The cursor has left the drop target boundaries.

+         * {@inheritDoc}

+         */

+        public void dragLeave(DropTargetEvent event) {

+            AdtPlugin.printErrorToConsole("DEBUG", "drag leave");

+            clearDropInfo();

+        }

+

+        /*

+         * The operation being performed has changed (e.g. modifier key).

+         * {@inheritDoc}

+         */

+        public void dragOperationChanged(DropTargetEvent event) {

+            // TODO Auto-generated method stub

+            AdtPlugin.printErrorToConsole("DEBUG", "drag changed");

+

+        }

+

+        /*

+         * The cursor is moving over the drop target.

+         * {@inheritDoc}

+         */

+        public void dragOver(DropTargetEvent event) {

+            // TODO Auto-generated method stub

+            AdtPlugin.printErrorToConsole("DEBUG", "drag over", event);

+            updateDropInfo(event);

+        }

+

+        /*

+         * The drop is about to be performed.

+         * The drop target is given a last chance to change the nature of the drop

+         * {@inheritDoc}

+         */

+        public void dropAccept(DropTargetEvent event) {

+            // TODO Auto-generated method stub

+            AdtPlugin.printErrorToConsole("DEBUG", "drop accept");

+

+        }

+

+        /*

+         * The data is being dropped.

+         * {@inheritDoc}

+         */

+        public void drop(DropTargetEvent event) {

+            // TODO Auto-generated method stub

+            AdtPlugin.printErrorToConsole("DEBUG", "drop");

+

+        }

+

+        private void updateDropInfo(DropTargetEvent event) {

+            if (!mIsResultValid) {

+                // We don't allow drop on an invalid layout, even if we have some obsolete

+                // layout info for it.

+                clearDropInfo();

+                return;

+            }

+

+            int x = event.x - IMAGE_MARGIN;

+            int y = event.y - IMAGE_MARGIN;

+            ViewInfo vi = findViewInfoAt(x, y, mLastValidViewInfoRoot);

+

+            if (vi != mCurrentView) {

+                // We switched to a new view.

+                mCurrentView = vi;

+

+                // Query

+

+                redraw();

+            }

+        }

+

+        private void clearDropInfo() {

+            if (mCurrentView != null) {

+                mCurrentView = null;

+                // TODO

+                redraw();

+            }

+        }

+

+

+    }

 }

diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/PaletteComposite.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/PaletteComposite.java
index 1dd7298..e780595 100755
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/PaletteComposite.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/PaletteComposite.java
@@ -21,13 +21,11 @@
 

 import org.eclipse.swt.SWT;

 import org.eclipse.swt.custom.CLabel;

-import org.eclipse.swt.dnd.ByteArrayTransfer;

 import org.eclipse.swt.dnd.DND;

 import org.eclipse.swt.dnd.DragSource;

 import org.eclipse.swt.dnd.DragSourceEvent;

 import org.eclipse.swt.dnd.DragSourceListener;

 import org.eclipse.swt.dnd.Transfer;

-import org.eclipse.swt.dnd.TransferData;

 import org.eclipse.swt.events.MouseEvent;

 import org.eclipse.swt.events.MouseListener;

 import org.eclipse.swt.events.MouseTrackListener;

@@ -40,7 +38,6 @@
 import org.eclipse.swt.widgets.Listener;

 import org.eclipse.swt.widgets.ScrollBar;

 

-import java.io.UnsupportedEncodingException;

 import java.util.ArrayList;

 import java.util.List;

 

@@ -104,8 +101,8 @@
         setGridLayout(mRoot, 0);

 

         if (targetData != null) {

-            addGroup(mRoot, "Layouts", targetData.getLayoutDescriptors().getLayoutDescriptors());

             addGroup(mRoot, "Views", targetData.getLayoutDescriptors().getViewDescriptors());

+            addGroup(mRoot, "Layouts", targetData.getLayoutDescriptors().getLayoutDescriptors());

         }

 

         layout(true);

@@ -358,94 +355,4 @@
         }

     }

 

-    // TODO move out of this scope once we need it on the other side.

-    /**

-     * A d'n'd {@link Transfer} class that can transfer {@link ElementDescriptor}s.

-     * <p/>

-     * The implementation is based on the {@link ByteArrayTransfer} and what we transfer

-     * is actually only the inner XML name of the element, which is unique enough.

-     * <p/>

-     * Drag source provides an {@link ElementDescriptor} object.

-     * Drog receivers get back a {@link String} object representing the

-     * {@link ElementDescriptor#getXmlName()}.

-     * <p/>

-     * Drop receivers can find the corresponding element by using

-     * {@link ElementDescriptor#findChildrenDescriptor(String, boolean)} with the

-     * XML name returned by this transfer operation and their root descriptor.

-     * <p/>

-     * Drop receivers must deal with the fact that this XML name may not exist in their

-     * own {@link ElementDescriptor} hierarchy -- e.g. if the drag came from a different

-     * GLE based on a different SDK platform or using custom widgets. In this case they

-     * must refuse the drop.

-     */

-    public static class ElementDescTransfer extends ByteArrayTransfer {

-

-        // Reference: http://www.eclipse.org/articles/Article-SWT-DND/DND-in-SWT.html

-

-

-        private static final String TYPE_NAME = "android.ADT.element.desc.transfer.1";

-        private static final int TYPE_ID = registerType(TYPE_NAME);

-        private static ElementDescTransfer sInstance = new ElementDescTransfer();

-

-        private ElementDescTransfer() {

-            // pass

-        }

-

-        public static ElementDescTransfer getInstance() {

-            return sInstance;

-        }

-

-        @Override

-        protected int[] getTypeIds() {

-            return new int[] { TYPE_ID };

-        }

-

-        @Override

-        protected String[] getTypeNames() {

-            return new String[] { TYPE_NAME };

-        }

-

-        @Override

-        protected void javaToNative(Object object, TransferData transferData) {

-            if (object == null || !(object instanceof ElementDescriptor[])) {

-                return;

-            }

-

-            if (isSupportedType(transferData)) {

-                StringBuilder sb = new StringBuilder();

-                boolean needSeparator = false;

-                for (ElementDescriptor desc : (ElementDescriptor[]) object) {

-                    if (needSeparator) {

-                        sb.append(';');

-                    }

-                    sb.append(desc.getXmlName());

-                    needSeparator = true;

-                }

-                try {

-                    byte[] buf = sb.toString().getBytes("UTF-8");  //$NON-NLS-1$

-                    super.javaToNative(buf, transferData);

-                } catch (UnsupportedEncodingException e) {

-                    // unlikely; ignore

-                }

-            }

-        }

-

-        @Override

-        protected Object nativeToJava(TransferData transferData) {

-            if (isSupportedType(transferData)) {

-                byte[] buf = (byte[]) super.nativeToJava(transferData);

-                if (buf != null && buf.length > 0) {

-                    try {

-                        String s = new String(buf, "UTF-8"); //$NON-NLS-1$

-                        String[] names = s.split(";");  //$NON-NLS-1$

-                        return names;

-                    } catch (UnsupportedEncodingException e) {

-                        // unlikely to happen, but still possible

-                    }

-                }

-            }

-

-            return null;

-        }

-    }

 }

diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gre/IViewRule.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gre/IViewRule.java
new file mode 100755
index 0000000..2232733
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gre/IViewRule.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2009 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.gre;
+
+import com.android.ide.eclipse.adt.internal.editors.descriptors.ElementDescriptor;
+
+import java.util.ArrayList;
+
+
+/**
+ * An {@link IViewRule} describes the GLE rules that apply to a given Layout or View object
+ * in the Graphical Layout Editor (GLE).
+ * <p/>
+ * Such a rule is implemented using a Groovy script located in the
+ * com.android.ide.eclipse.adt.internal.editors.layout.gre package or in a
+ * projects' /gscript folder for custom views.
+ * <p/>
+ * The Groovy script must be named using the fully qualified class name of the View or Layout,
+ * e.g. "android.widget.LinearLayout.groovy". If the rule engine can't find a groovy script
+ * for a given element, it will use the closest matching parent (e.g. View instead of ViewGroup).
+ * <p/>
+ * Rules instances are stateless. They are created once per View class to handle and are shared
+ * across platforms or editor instances. As such, rules methods should never cache editor-specific
+ * arguments that they might receive.
+ */
+public interface IViewRule {
+
+    public class Rect {
+        public int x, y, w, h;
+    }
+
+    /**
+     * This method is called by the rule engine when the script is first loaded.
+     * It gives the rule a chance to initialize itself.
+     *
+     * @param fqcn The fully qualified class name of the Layout or View that will be managed by
+     *   this rule. This can be cached as it will never change for the lifetime of this rule
+     *   instance. This may or may not match the script's filename as it may be the fqcn of a
+     *   class derived from the one this rule can handle.
+     * @return True if this rule can handle the given FQCN. False if the rule can't handle the
+     *   given FQCN, in which case the rule engine will find another rule matching a parent clas.
+     */
+    boolean onInitialize(String fqcn);
+
+    /**
+     * This method is called by the rules engine just before the script is unloaded.
+     */
+    void onDispose();
+
+    /**
+     * Returns the class name to display when an element is selected in the GLE.
+     * <p/>
+     * If null is returned, the GLE will automatically shorten the class name using its
+     * own heuristic, which is to keep the first 2 package components and the class name.
+     * The class name is the <code>fqcn</code> argument that was given
+     * to {@link #onInitialize(String)}.
+     *
+     * @return Null for the default behavior or a shortened string.
+     */
+    String getDisplayName();
+
+
+
+    // ==== Drag'n'drop support ====
+
+    public class DropZone {
+        /** The rectangle (in absolute coordinates) of the drop zone. */
+        public final Rect bounds = new Rect();
+        /** An opaque object that the script can use for its own purpose, e.g. some pre-computed
+         * data or a closure. */
+        public Object data;
+    }
+
+    /**
+     * Called when a drop operation occurs to add a new element, typically dragged from
+     * the view/layout palette. The purpose of the drop operation is to create a new element.
+     * <p/>
+     * Drop targets that can't accept child views should always return null.
+     * <p/>
+     * The method should return a list of drop zones, customized to the actual bounds
+     * of the target. The drop zones will be visually shown to the user. Once the user drops in
+     * one of the zone the {@link #afterDrop(ElementDescriptor, NodeProxy, DropZone)} method
+     * will be called.
+     *
+     * @param source The {@link ElementDescriptor} of the drag source.
+     * @param targetNode The XML view that is currently the target of the drop.
+     * @return Null if the rule rejects the drop, or a list of usage drop zones.
+     */
+    ArrayList<DropZone> beforeDrop(ElementDescriptor source, NodeProxy targetNode);
+
+    /**
+     * Called after the user selects to drop the given source into one of the drop zones.
+     * This method should use the methods from the {@link NodeProxy} to actually create the
+     * new XML matching the source descriptor.
+     *
+     * @param source The {@link ElementDescriptor} of the drag source.
+     * @param targetNode The XML view that is currently the target of the drop.
+     * @param selectedZone One of the drop zones returned by
+     * {@link #beforeDrop(ElementDescriptor, NodeProxy)}
+     */
+    void afterDrop(ElementDescriptor source, NodeProxy targetNode, DropZone selectedZone);
+
+
+    ArrayList<DropZone> beforeMove(NodeProxy sourceNode, NodeProxy targetNode, boolean copy);
+    void afterMove(NodeProxy sourceNode, NodeProxy targetNode, boolean copy, DropZone selectedZone);
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gre/NodeProxy.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gre/NodeProxy.java
new file mode 100755
index 0000000..864d0ec
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gre/NodeProxy.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2009 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.gre;
+
+import com.android.ide.eclipse.adt.internal.editors.layout.gre.IViewRule.Rect;
+import com.android.ide.eclipse.adt.internal.editors.uimodel.UiElementNode;
+
+/**
+ *
+ */
+public class NodeProxy {
+
+    private final UiElementNode mNode;
+    private final Rect mBounds;
+
+    public NodeProxy(UiElementNode node, IViewRule.Rect bounds) {
+        mNode = node;
+        mBounds = bounds;
+    }
+
+    public Rect getBounds() {
+        return mBounds;
+    }
+
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gre/RulesEngine.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gre/RulesEngine.java
new file mode 100755
index 0000000..d890653
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gre/RulesEngine.java
@@ -0,0 +1,329 @@
+/*
+ * Copyright (C) 2009 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.gre;
+
+import com.android.ide.eclipse.adt.AdtPlugin;
+import com.android.ide.eclipse.adt.internal.editors.descriptors.ElementDescriptor;
+import com.android.ide.eclipse.adt.internal.editors.layout.descriptors.ViewElementDescriptor;
+import com.android.ide.eclipse.adt.internal.editors.layout.uimodel.UiViewElementNode;
+import com.android.ide.eclipse.adt.internal.resources.manager.ResourceMonitor;
+import com.android.ide.eclipse.adt.internal.resources.manager.ResourceMonitor.IFolderListener;
+
+import org.codehaus.groovy.control.CompilationFailedException;
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.IFolder;
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.resources.IResourceDelta;
+
+import groovy.lang.GroovyClassLoader;
+
+import java.io.InputStream;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+
+/**
+ * The rule engine manages the groovy rules files and interacts with them.
+ * There's one {@link RulesEngine} instance per layout editor.
+ * Each instance has 2 sets of scripts: the static ADT rules (shared across all instances)
+ * and the project specific rules (local to the current instance / layout editor).
+ */
+public class RulesEngine {
+
+    private static final String PROJECT_SCRIPT_DIR = "gscripts";
+    private static final String SCRIPT_EXT = ".groovy";  //$NON-NLS-1$
+
+    private final GroovyClassLoader mClassLoader;
+    private final IProject mProject;
+    private final Map<Object, IViewRule> mRulesCache = new HashMap<Object, IViewRule>();
+    private ProjectFolderListener mProjectFolderListener;
+
+    public RulesEngine(IProject project) {
+        mProject = project;
+        ClassLoader cl = getClass().getClassLoader();
+        mClassLoader = new GroovyClassLoader(cl);
+
+        mProjectFolderListener = new ProjectFolderListener();
+        ResourceMonitor.getMonitor().addFolderListener(
+                mProjectFolderListener,
+                IResourceDelta.ADDED | IResourceDelta.REMOVED | IResourceDelta.CHANGED);
+    }
+
+    /**
+     * Called by the owner of the {@link RulesEngine} when it is going to be disposed.
+     * This frees some resources, such as the project's folder monitor.
+     */
+    public void dispose() {
+        if (mProjectFolderListener != null) {
+            ResourceMonitor.getMonitor().removeFolderListener(mProjectFolderListener);
+            mProjectFolderListener = null;
+        }
+        clearCache();
+    }
+
+    public String getDisplayName(UiViewElementNode element) {
+        // try to find a rule for this element's FQCN
+        IViewRule rule = loadRule(element);
+
+        if (rule != null) {
+            try {
+                return rule.getDisplayName();
+
+            } catch (Exception e) {
+                logError("%s.getDisplayName() failed: %s",
+                        rule.getClass().getSimpleName(),
+                        e.toString());
+            }
+        }
+
+        return null;
+    }
+
+
+    // ---- private ---
+
+    private class ProjectFolderListener implements IFolderListener {
+        public void folderChanged(IFolder folder, int kind) {
+            if (folder.getProject() == mProject &&
+                    PROJECT_SCRIPT_DIR.equals(folder.getName())) {
+                // Clear our whole rules cache, to not have to deal with dependencies.
+                clearCache();
+            }
+        }
+    }
+
+    /**
+     * Clear the Rules cache. Calls onDispose() on each rule.
+     */
+    private void clearCache() {
+        // The cache can contain multiple times the same rule instance for different
+        // keys (e.g. the UiViewElementNode key vs. the FQCN string key.) So transfer
+        // all values to a unique set.
+        HashSet<IViewRule> rules = new HashSet<IViewRule>(mRulesCache.values());
+
+        mRulesCache.clear();
+
+        for (IViewRule rule : rules) {
+            if (rule != null) {
+                try {
+                    rule.onDispose();
+                } catch (Exception e) {
+                    logError("%s.onDispose() failed: %s",
+                            rule.getClass().getSimpleName(),
+                            e.toString());
+                }
+            }
+        }
+    }
+
+    /**
+     * Load a rule using its descriptor. This will try to first load the rule using its
+     * actual FQCN and if that fails will find the first parent that works in the view
+     * hierarchy.
+     */
+    private IViewRule loadRule(UiViewElementNode element) {
+        if (element == null) {
+            return null;
+        } else {
+            // sanity check. this can't fail.
+            ElementDescriptor d = element.getDescriptor();
+            if (d == null || !(d instanceof ViewElementDescriptor)) {
+                return null;
+            }
+        }
+
+        String targetFqcn = null;
+        ViewElementDescriptor targetDesc = (ViewElementDescriptor) element.getDescriptor();
+
+        // Return the rule if we find it in the cache, even if it was stored as null
+        // (which means we didn't find it earlier, so don't look for it again)
+        IViewRule rule = mRulesCache.get(targetDesc);
+        if (rule != null || mRulesCache.containsKey(targetDesc)) {
+            return rule;
+        }
+
+        // Get the descriptor and loop through the super class hierarchy
+        for (ViewElementDescriptor desc = targetDesc;
+                desc != null;
+                desc = desc.getSuperClassDesc()) {
+
+            // Get the FQCN of this View
+            String fqcn = desc.getFullClassName();
+            if (fqcn == null) {
+                return null;
+            }
+
+            // The first time we keep the FQCN around as it's the target class we were
+            // initially trying to load. After, as we move through the hierarchy, the
+            // target FQCN remains constant.
+            if (targetFqcn == null) {
+                targetFqcn = fqcn;
+            }
+
+            // Try to find a rule matching the "real" FQCN. If we find it, we're done.
+            // If not, the for loop will move to the parent descriptor.
+            rule = loadRule(fqcn, targetFqcn);
+            if (rule != null) {
+                // We found one.
+                // As a side effect, loadRule() also cached the rule using the target FQCN.
+                return rule;
+            }
+        }
+
+        // Memorize in the cache that we couldn't find a rule for this descriptor
+        mRulesCache.put(targetDesc, null);
+        return null;
+    }
+
+    /**
+     * Try to load a rule given a specific FQCN. This looks for an exact match in either
+     * the ADT scripts or the project scripts and does not look at parent hierarchy.
+     * <p/>
+     * Once a rule is found (or not), it is stored in a cache using its target FQCN
+     * so we don't try to reload it.
+     * <p/>
+     * The real FQCN is the actual groovy filename we're loading, e.g. "android.view.View.groovy"
+     * where target FQCN is the class we were initially looking for, which might be the same as
+     * the real FQCN or might be a derived class, e.g. "android.widget.TextView".
+     *
+     * @param realFqcn The FQCN of the groovy rule actually being loaded.
+     * @param targetFqcn The FQCN of the class actually processed, which might be different from
+     *          the FQCN of the rule being loaded.
+     */
+    private IViewRule loadRule(String realFqcn, String targetFqcn) {
+        if (realFqcn == null || targetFqcn == null) {
+            return null;
+        }
+
+        // Return the rule if we find it in the cache, even if it was stored as null
+        // (which means we didn't find it earlier, so don't look for it again)
+        IViewRule rule = mRulesCache.get(realFqcn);
+        if (rule != null || mRulesCache.containsKey(realFqcn)) {
+            return rule;
+        }
+
+        // Look for the file in ADT first.
+        // That means a project can't redefine any of the rules we define.
+        String filename = realFqcn + SCRIPT_EXT;
+
+        try {
+            InputStream is = getClass().getResourceAsStream(filename);
+            rule = loadStream(is, realFqcn);
+            if (rule != null) {
+                return initializeRule(rule, targetFqcn);
+            }
+        } catch (Exception e) {
+            logError("load rule error (%s): %s", filename, e.getMessage());
+        }
+
+        // Then look for the file in the project
+        IResource r = mProject.findMember(PROJECT_SCRIPT_DIR);
+        if (r != null && r.getType() == IResource.FOLDER) {
+            r = ((IFolder) r).findMember(filename);
+            if (r != null && r.getType() == IResource.FILE) {
+                try {
+                    InputStream is = ((IFile) r).getContents();
+                    rule = loadStream(is, realFqcn);
+                    if (rule != null) {
+                        return initializeRule(rule, targetFqcn);
+                    }
+                } catch (Exception e) {
+                    logError("load rule error (%s): %s", filename, e.getMessage());
+                }
+            }
+        }
+
+        // Memorize in the cache that we couldn't find a rule for this real FQCN
+        mRulesCache.put(realFqcn, null);
+        return null;
+    }
+
+    /**
+     * Initialize a rule we just loaded. The rule has a chance to examine the target FQCN
+     * and bail out.
+     * <p/>
+     * Contract: the rule is not in the {@link #mRulesCache} yet and this method will
+     * cache it using the target FQCN if the rule is accepted.
+     * <p/>
+     * The real FQCN is the actual groovy filename we're loading, e.g. "android.view.View.groovy"
+     * where target FQCN is the class we were initially looking for, which might be the same as
+     * the real FQCN or might be a derived class, e.g. "android.widget.TextView".
+     *
+     * @param rule A rule freshly loaded.
+     * @param targetFqcn The FQCN of the class actually processed, which might be different from
+     *          the FQCN of the rule being loaded.
+     * @return The rule if accepted, or null if the rule can't handle that FQCN.
+     */
+    private IViewRule initializeRule(IViewRule rule, String targetFqcn) {
+
+        try {
+            if (rule.onInitialize(targetFqcn)) {
+                // Add it to the cache and return it
+                mRulesCache.put(targetFqcn, rule);
+                return rule;
+            } else {
+                rule.onDispose();
+            }
+        } catch (Exception e) {
+            logError("%s.onInit() failed: %s",
+                    rule.getClass().getSimpleName(),
+                    e.toString());
+        }
+
+        return null;
+    }
+
+    /**
+     * Actually load a groovy script and instantiate an {@link IViewRule} from it.
+     * On error, outputs (hopefully meaningful) groovy error messages.
+     *
+     * @param is The input stream for the groovy script. Can be null.
+     * @param fqcn The class name, for display purposes only.
+     * @return A new {@link IViewRule} or null if loading failed for any reason.
+     */
+    private IViewRule loadStream(InputStream is, String fqcn) {
+        try {
+            if (is == null) {
+                // We handle this case for convenience. It typically means that the
+                // input stream couldn't be opened because the file was not found.
+                // Since we expect this to be a common case, we don't log it as an error.
+                return null;
+            }
+
+            // Create a groovy class from it. Can fail to compile.
+            Class<?> c = mClassLoader.parseClass(is, fqcn);
+
+            // Get an instance. This might throw ClassCastException.
+            return (IViewRule) c.newInstance();
+
+        } catch (CompilationFailedException e) {
+            logError("Compilation error in %s.groovy: %s", fqcn, e.toString());
+        } catch (ClassCastException e) {
+            logError("Script %s.groovy does not implement IViewRule", fqcn);
+        } catch (Exception e) {
+            logError("Failed to use %s.groovy: %s", fqcn, e.getMessage());
+        }
+
+        return null;
+    }
+
+    private void logError(String format, Object...params) {
+        String s = String.format(format, params);
+        AdtPlugin.printErrorToConsole(mProject, s);
+    }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gre/android.view.View.groovy b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gre/android.view.View.groovy
new file mode 100755
index 0000000..8fec822
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gre/android.view.View.groovy
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2009 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.gre;
+
+import com.android.ide.eclipse.adt.internal.editors.layout.gre.IViewRule;
+import com.android.ide.eclipse.adt.internal.editors.layout.gre.IViewRule.Rect;
+import com.android.ide.eclipse.adt.internal.editors.layout.gre.IViewRule.DropZone;
+import com.android.ide.eclipse.adt.internal.editors.descriptors.ElementDescriptor;
+
+import java.util.ArrayList;
+
+
+/**
+ * An {@link IViewRule} for android.view.View and all its derived classes.
+ * This is the "root" rule, that is used whenever there is not more specific rule to apply.
+ */
+public class AndroidViewViewRule implements IViewRule {
+
+    private String mFqcn;
+
+    /**
+     * This method is called by the rule engine when the script is first loaded.
+     * It gives the rule a chance to initialize itself.
+     *
+     * @param fqcn The fully qualified class name of the Layout or View that will be managed by
+     *   this rule. This can be cached as it will never change for the lifetime of this rule
+     *   instance. This may or may not match the script's filename as it may be the fqcn of a
+     *   class derived from the one this rule can handle.
+     * @return True if this rule can handle the given FQCN. False if the rule can't handle the
+     *   given FQCN, in which case the rule engine will find another rule matching a parent clas.
+     */
+    public boolean onInitialize(String fqcn) {
+        // This rule can handle anything.
+        mFqcn = fqcn
+        return true;
+    }
+
+    /**
+     * This method is called by the rules engine just before the script is unloaded.
+     */
+    public void onDispose() {
+    }
+
+    /**
+     * Returns the class name to display when an element is selected in the GLE.
+     * <p/>
+     * If null is returned, the GLE will automatically shorten the class name using its
+     * own heuristic, which is to keep the first 2 package components and the class name.
+     * The class name is the <code>fqcn</code> argument that was given
+     * to {@link #onInitialize(String)}.
+     *
+     * @return Null for the default behavior or a shortened string.
+     */
+    public String getDisplayName() {
+        // Use the default behavior.
+        //return null;
+        // DEBUG:
+        def f = mFqcn.split("\\.");
+        return "View:" + f[f.length-1];
+    }
+
+
+
+    // ==== Drag'n'drop support ====
+
+    /**
+     * Called when a drop operation occurs to add a new element, typically dragged from
+     * the view/layout palette. The purpose of the drop operation is to create a new element.
+     * <p/>
+     * Drop targets that can't accept child views should always return null.
+     * <p/>
+     * The method should return a list of drop zones, customized to the actual bounds
+     * of the target. The drop zones will be visually shown to the user. Once the user drops in
+     * one of the zones the {@link #afterDrop(ElementDescriptor, NodeProxy, DropZone)} method
+     * will be called.
+     *
+     * @param source The {@link ElementDescriptor} of the drag source.
+     * @param targetNode The XML view that is currently the target of the drop.
+     * @return Null if the rule rejects the drop, or a list of usage drop zones.
+     */
+    public ArrayList<DropZone> beforeDrop(ElementDescriptor source, NodeProxy targetNode) {
+        // By default views do not accept child views.
+        return null;
+    }
+
+    /**
+     * Called after the user selects to drop the given source into one of the drop zones.
+     * This method should use the methods from the {@link NodeProxy} to actually create the
+     * new XML matching the source descriptor.
+     *
+     * @param source The {@link ElementDescriptor} of the drag source.
+     * @param targetNode The XML view that is currently the target of the drop.
+     * @param selectedZone One of the drop zones returned by
+     * {@link #beforeDrop(ElementDescriptor, NodeProxy)}
+     */
+    public void afterDrop(ElementDescriptor source, NodeProxy targetNode, DropZone selectedZone) {
+        // skip, we're not doing drag'n'drop here
+    }
+
+
+    public ArrayList<DropZone> beforeMove(NodeProxy sourceNode, NodeProxy targetNode, boolean copy) {
+        // later
+    }
+    public void afterMove(NodeProxy sourceNode, NodeProxy targetNode, boolean copy, DropZone selectedZone) {
+        // later
+    }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gre/android.widget.LinearLayout.groovy b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gre/android.widget.LinearLayout.groovy
new file mode 100755
index 0000000..1b06fb6
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gre/android.widget.LinearLayout.groovy
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2009 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.gre;
+
+import com.android.ide.eclipse.adt.internal.editors.layout.gre.IViewRule;
+import com.android.ide.eclipse.adt.internal.editors.layout.gre.IViewRule.Rect;
+import com.android.ide.eclipse.adt.internal.editors.layout.gre.IViewRule.DropZone;
+import com.android.ide.eclipse.adt.internal.editors.descriptors.ElementDescriptor;
+
+import java.util.ArrayList;
+
+/**
+ * An {@link IViewRule} for android.widget.LinearLayout and all its derived classes.
+ */
+public class AndroidWidgetLinearLayourRule implements IViewRule {
+
+    /**
+     * This method is called by the rule engine when the script is first loaded.
+     * It gives the rule a chance to initialize itself.
+     *
+     * @param fqcn The fully qualified class name of the Layout or View that will be managed by
+     *   this rule. This can be cached as it will never change for the lifetime of this rule
+     *   instance. This may or may not match the script's filename as it may be the fqcn of a
+     *   class derived from the one this rule can handle.
+     * @return True if this rule can handle the given FQCN. False if the rule can't handle the
+     *   given FQCN, in which case the rule engine will find another rule matching a parent clas.
+     */
+    public boolean onInitialize(String fqcn) {
+        // We can handle any class derived from LinearLayout
+        return true;
+    }
+
+    /**
+     * This method is called by the rules engine just before the script is unloaded.
+     */
+    public void onDispose() {
+    }
+
+    /**
+     * Returns the class name to display when an element is selected in the GLE.
+     * <p/>
+     * If null is returned, the GLE will automatically shorten the class name using its
+     * own heuristic, which is to keep the first 2 package components and the class name.
+     * The class name is the <code>fqcn</code> argument that was given
+     * to {@link #onInitialize(String)}.
+     *
+     * @return Null for the default behavior or a shortened string.
+     */
+    public String getDisplayName() {
+        // Use the default behavior.
+        return null;
+    }
+
+
+
+    // ==== Drag'n'drop support ====
+
+    /**
+     * Called when a drop operation occurs to add a new element, typically dragged from
+     * the view/layout palette. The purpose of the drop operation is to create a new element.
+     * <p/>
+     * Drop targets that can't accept child views should always return null.
+     * <p/>
+     * The method should return a list of drop zones, customized to the actual bounds
+     * of the target. The drop zones will be visually shown to the user. Once the user drops in
+     * one of the zones the {@link #afterDrop(ElementDescriptor, NodeProxy, DropZone)} method
+     * will be called.
+     *
+     * @param source The {@link ElementDescriptor} of the drag source.
+     * @param targetNode The XML view that is currently the target of the drop.
+     * @return Null if the rule rejects the drop, or a list of usage drop zones.
+     */
+    public ArrayList<DropZone> beforeDrop(ElementDescriptor source, NodeProxy targetNode) {
+        // By default we accept one drop zone, which is the whole target.
+        DropZone d = new DropZone();
+        d.bounds = targetNode.getBounds();
+        d.data = null;
+        return [ d ];
+    }
+
+    /**
+     * Called after the user selects to drop the given source into one of the drop zones.
+     * This method should use the methods from the {@link NodeProxy} to actually create the
+     * new XML matching the source descriptor.
+     *
+     * @param source The {@link ElementDescriptor} of the drag source.
+     * @param targetNode The XML view that is currently the target of the drop.
+     * @param selectedZone One of the drop zones returned by
+     * {@link #beforeDrop(ElementDescriptor, NodeProxy)}
+     */
+    public void afterDrop(ElementDescriptor source, NodeProxy targetNode, DropZone selectedZone) {
+        // skip, we're not doing drag'n'drop here
+        // TODO
+    }
+
+
+    public ArrayList<DropZone> beforeMove(NodeProxy sourceNode, NodeProxy targetNode, boolean copy) {
+        // later
+    }
+    public void afterMove(NodeProxy sourceNode, NodeProxy targetNode, boolean copy, DropZone selectedZone) {
+        // later
+    }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/descriptors/AndroidManifestDescriptors.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/descriptors/AndroidManifestDescriptors.java
index 2667871..93a0f9b 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/descriptors/AndroidManifestDescriptors.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/descriptors/AndroidManifestDescriptors.java
@@ -55,7 +55,7 @@
     private static final String ANDROID_MANIFEST_STYLEABLE = "AndroidManifest"; //$NON-NLS-1$
 
     // Public attributes names, attributes descriptors and elements descriptors
-    
+
     public static final String ANDROID_LABEL_ATTR = "label";    //$NON-NLS-1$
     public static final String ANDROID_NAME_ATTR  = "name";     //$NON-NLS-1$
     public static final String PACKAGE_ATTR       = "package";  //$NON-NLS-1$
@@ -81,7 +81,7 @@
 
     /** Private package attribute for the manifest element. Needs to be handled manually. */
     private final TextAttributeDescriptor PACKAGE_ATTR_DESC;
-    
+
     public AndroidManifestDescriptors() {
         APPLICATION_ELEMENT = createElement("application", null, true); //$NON-NLS-1$ + no child & mandatory
         INTRUMENTATION_ELEMENT = createElement("instrumentation"); //$NON-NLS-1$
@@ -113,43 +113,43 @@
                 null /* nsUri */,
                 "This attribute gives a unique name for the package, using a Java-style naming convention to avoid name collisions.\nFor example, applications published by Google could have names of the form com.google.app.appname");
     }
-    
+
     public ElementDescriptor[] getRootElementDescriptors() {
         return new ElementDescriptor[] { MANIFEST_ELEMENT };
     }
-    
+
     public ElementDescriptor getDescriptor() {
         return getManifestElement();
     }
-    
+
     public ElementDescriptor getApplicationElement() {
         return APPLICATION_ELEMENT;
     }
-    
+
     public ElementDescriptor getManifestElement() {
         return MANIFEST_ELEMENT;
     }
-    
+
     public ElementDescriptor getUsesSdkElement() {
         return USES_SDK_ELEMENT;
     }
-    
+
     public ElementDescriptor getInstrumentationElement() {
         return INTRUMENTATION_ELEMENT;
     }
-    
+
     public ElementDescriptor getPermissionElement() {
         return PERMISSION_ELEMENT;
     }
-    
+
     public ElementDescriptor getUsesPermissionElement() {
         return USES_PERMISSION_ELEMENT;
     }
-    
+
     public ElementDescriptor getPermissionGroupElement() {
         return PERMISSION_GROUP_ELEMENT;
     }
-    
+
     public ElementDescriptor getPermissionTreeElement() {
         return PERMISSION_TREE_ELEMENT;
     }
@@ -159,7 +159,7 @@
      * <p/>
      * It first computes the new children of the descriptor and then updates them
      * all at once.
-     * 
+     *
      * @param manifestMap The map style => attributes from the attrs_manifest.xml file
      */
     public synchronized void updateDescriptors(
@@ -167,18 +167,18 @@
 
         XmlnsAttributeDescriptor xmlns = new XmlnsAttributeDescriptor(
                 "android", //$NON-NLS-1$
-                SdkConstants.NS_RESOURCES); 
+                SdkConstants.NS_RESOURCES);
 
         // -- setup the required attributes overrides --
-        
+
         Set<String> required = new HashSet<String>();
         required.add("provider/authorities");  //$NON-NLS-1$
-        
+
         // -- setup the various attribute format overrides --
-        
+
         // The key for each override is "element1,element2,.../attr-xml-local-name" or
         // "*/attr-xml-local-name" to match the attribute in any element.
-        
+
         Map<String, Object> overrides = new HashMap<String, Object>();
 
         overrides.put("*/icon", new DescriptorsUtils.ITextAttributeCreator() { //$NON-NLS-1$
@@ -190,11 +190,11 @@
                         tooltip);
             }
         });
-        
+
         overrides.put("*/theme",         ThemeAttributeDescriptor.class);   //$NON-NLS-1$
         overrides.put("*/permission",    ListAttributeDescriptor.class);    //$NON-NLS-1$
         overrides.put("*/targetPackage", ManifestPkgAttrDescriptor.class);  //$NON-NLS-1$
-        
+
         overrides.put("uses-library/name", ListAttributeDescriptor.class);       //$NON-NLS-1$
 
         overrides.put("action,category,uses-permission/" + ANDROID_NAME_ATTR,    //$NON-NLS-1$
@@ -210,7 +210,7 @@
         // -- list element nodes already created --
         // These elements are referenced by already opened editors, so we want to update them
         // but not re-create them when reloading an SDK on the fly.
-        
+
         HashMap<String, ElementDescriptor> elementDescs =
             new HashMap<String, ElementDescriptor>();
         elementDescs.put(MANIFEST_ELEMENT.getXmlLocalName(),         MANIFEST_ELEMENT);
@@ -231,10 +231,10 @@
                 MANIFEST_ELEMENT,
                 "AndroidManifest"); //$NON-NLS-1$
         insertAttribute(MANIFEST_ELEMENT, PACKAGE_ATTR_DESC);
-        
+
         sanityCheck(manifestMap, MANIFEST_ELEMENT);
     }
-    
+
     /**
      * Sets up an attribute override for ANDROID_NAME_ATTR using a ClassAttributeDescriptor
      * with the specified class name.
@@ -301,7 +301,7 @@
             boolean mandatory) {
         // Creates an element with no attribute overrides.
         String styleName = guessStyleName(xmlName);
-        String sdkUrl = DescriptorsUtils.MANIFEST_SDK_URL + styleName; 
+        String sdkUrl = DescriptorsUtils.MANIFEST_SDK_URL + styleName;
         String uiName = getUiName(xmlName);
 
         ElementDescriptor element = new ManifestElementDescriptor(xmlName, uiName, null, sdkUrl,
@@ -332,7 +332,7 @@
                 return;
             }
         }
-        
+
         AttributeDescriptor[] newArray = new AttributeDescriptor[attributes.length + 1];
         newArray[0] = newAttr;
         System.arraycopy(attributes, 0, newArray, 1, attributes.length);
@@ -345,7 +345,7 @@
      * This first creates all the attributes for the given ElementDescriptor.
      * It then finds all children of the descriptor, inflate them recursively and set them
      * as child to this ElementDescriptor.
-     * 
+     *
      * @param styleMap The input styleable map for manifest elements & attributes.
      * @param overrides A list of attribute overrides (to customize the type of the attribute
      *          descriptors).
@@ -356,7 +356,7 @@
      *          break these references).
      * @param elemDesc The current {@link ElementDescriptor} to inflate.
      * @param styleName The name of the {@link ElementDescriptor} to inflate. Its XML local name
-     *          will be guessed automatically from the style name. 
+     *          will be guessed automatically from the style name.
      */
     private void inflateElement(
             Map<String, DeclareStyleableInfo> styleMap,
@@ -367,7 +367,7 @@
             String styleName) {
         assert elemDesc != null;
         assert styleName != null;
-        
+
         // define attributes
         DeclareStyleableInfo style = styleMap != null ? styleMap.get(styleName) : null;
         if (style != null) {
@@ -381,7 +381,7 @@
             elemDesc.setTooltip(style.getJavaDoc());
             elemDesc.setAttributes(attrDescs.toArray(new AttributeDescriptor[attrDescs.size()]));
         }
-        
+
         // find all elements that have this one as parent
         ArrayList<ElementDescriptor> children = new ArrayList<ElementDescriptor>();
         for (Entry<String, DeclareStyleableInfo> entry : styleMap.entrySet()) {
@@ -399,7 +399,7 @@
             if (isParent) {
                 String childStyleName = entry.getKey();
                 String childXmlName = guessXmlName(childStyleName);
-                
+
                 // create or re-use element
                 ElementDescriptor child = existingElementDescs.get(childXmlName);
                 if (child == null) {
@@ -407,7 +407,7 @@
                     existingElementDescs.put(childXmlName, child);
                 }
                 children.add(child);
-                
+
                 inflateElement(styleMap,
                         overrides,
                         requiredAttributes,
@@ -439,15 +439,15 @@
                 sb.append(c);
             }
         }
-        
+
         return sb.toString();
     }
 
     /**
      * Guesses the style name for a given XML element name.
-     * <p/> 
+     * <p/>
      * The rules are:
-     * - capitalize the first letter: 
+     * - capitalize the first letter:
      * - if there's a dash, skip it and capitalize the next one
      * - prefix AndroidManifest
      * The exception is "manifest" which just becomes AndroidManifest.
@@ -474,7 +474,7 @@
                 }
             }
         }
-        
+
         sb.insert(0, ANDROID_MANIFEST_STYLEABLE);
         return sb.toString();
     }
@@ -495,7 +495,7 @@
                 stylesDeclared.add(styleName);
             }
         }
-        
+
         for (Iterator<String> it = elementsDeclared.iterator(); it.hasNext();) {
             String xmlName = it.next();
             String styleName = guessStyleName(xmlName);
@@ -509,12 +509,12 @@
             sb.append("Warning, ADT/SDK Mismatch! The following elements are declared by the SDK but unknown to ADT: ");
             for (String name : stylesDeclared) {
                 name = guessXmlName(name);
-                
+
                 if (name != stylesDeclared.last()) {
                     sb.append(", ");    //$NON-NLS-1$
                 }
             }
-            
+
             AdtPlugin.log(IStatus.WARNING, "%s", sb.toString());
             AdtPlugin.printToConsole((String)null, sb);
             sb.setLength(0);
@@ -537,8 +537,8 @@
     /**
      * Performs an approximate translation of the style name into a potential
      * xml name. This is more or less the reverse from guessStyleName().
-     * 
-     * @return The XML local name for a given style name. 
+     *
+     * @return The XML local name for a given style name.
      */
     private String guessXmlName(String name) {
         StringBuilder sb = new StringBuilder();
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/uimodel/UiElementNode.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/uimodel/UiElementNode.java
index b31917e..48c7940 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/uimodel/UiElementNode.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/uimodel/UiElementNode.java
@@ -98,7 +98,7 @@
     private ArrayList<UiElementNode> mUiChildren;
     /** The list of <em>all</em> UI attributes, as declared in the {@link ElementDescriptor}.
      *  The list is always defined and never null. Unlike the UiElementNode children list, this
-     *  is always defined, even for attributes that do not exist in the XML model -- that's because
+     *  is always defined, even for attributes that do not exist in the XML model - that's because
      *  "missing" attributes in the XML model simply mean a default value is used. Also note that
      *  the underlying collection is a map, so order is not respected. To get the desired attribute
      *  order, iterate through the {@link ElementDescriptor}'s attribute list. */
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/ResourceMonitor.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/ResourceMonitor.java
index 06b23d5..5f33168 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/ResourceMonitor.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/ResourceMonitor.java
@@ -97,7 +97,7 @@
          */
         public void folderChanged(IFolder folder, int kind);
     }
-    
+
     /**
      * Interface for a listener to be notified when resource change event starts and ends.
      */
@@ -105,13 +105,13 @@
         public void resourceChangeEventStart();
         public void resourceChangeEventEnd();
     }
-    
+
     /**
      * Base listener bundle to associate a listener to an event mask.
      */
     private static class ListenerBundle {
         /** Mask value to accept all events */
-        public final static int MASK_NONE = -1; 
+        public final static int MASK_NONE = -1;
 
         /**
          * Event mask. Values accepted are IResourceDelta.###
@@ -123,7 +123,7 @@
          * */
         int kindMask;
     }
-    
+
     /**
      * Listener bundle for file event.
      */
@@ -132,7 +132,7 @@
         /** The file listener */
         IFileListener listener;
     }
-    
+
     /**
      * Listener bundle for folder event.
      */
@@ -140,7 +140,7 @@
         /** The file listener */
         IFolderListener listener;
     }
-    
+
     private final ArrayList<FileListenerBundle> mFileListeners =
         new ArrayList<FileListenerBundle>();
 
@@ -148,10 +148,10 @@
         new ArrayList<FolderListenerBundle>();
 
     private final ArrayList<IProjectListener> mProjectListeners = new ArrayList<IProjectListener>();
-    
+
     private final ArrayList<IResourceEventListener> mEventListeners =
         new ArrayList<IResourceEventListener>();
-    
+
     private IWorkspace mWorkspace;
 
     /**
@@ -188,7 +188,7 @@
                 if (flags == IResourceDelta.OPEN) {
                     // the project is opening or closing.
                     IProject project = (IProject)r;
-                    
+
                     if (project.isOpen()) {
                         // notify the listeners.
                         for (IProjectListener pl : mProjectListeners) {
@@ -206,12 +206,12 @@
             return true;
         }
     }
-    
+
     public static ResourceMonitor getMonitor() {
         return sThis;
     }
 
-    
+
     /**
      * Starts the resource monitoring.
      * @param ws The current workspace.
@@ -233,7 +233,7 @@
     public static void stopMonitoring(IWorkspace ws) {
         if (sThis != null) {
             ws.removeResourceChangeListener(sThis);
-            
+
             sThis.mFileListeners.clear();
             sThis.mProjectListeners.clear();
         }
@@ -243,16 +243,17 @@
      * Adds a file listener.
      * @param listener The listener to receive the events.
      * @param kindMask The event mask to filter out specific events.
-     * {@link ListenerBundle#MASK_NONE} will forward all events. 
+     * {@link ListenerBundle#MASK_NONE} will forward all events.
+     * See {@link ListenerBundle#kindMask} for more values.
      */
     public synchronized void addFileListener(IFileListener listener, int kindMask) {
         FileListenerBundle bundle = new FileListenerBundle();
         bundle.listener = listener;
         bundle.kindMask = kindMask;
-        
+
         mFileListeners.add(bundle);
     }
-    
+
     /**
      * Removes an existing file listener.
      * @param listener the listener to remove.
@@ -271,13 +272,14 @@
      * Adds a folder listener.
      * @param listener The listener to receive the events.
      * @param kindMask The event mask to filter out specific events.
-     * {@link ListenerBundle#MASK_NONE} will forward all events. 
+     * {@link ListenerBundle#MASK_NONE} will forward all events.
+     * See {@link ListenerBundle#kindMask} for more values.
      */
     public synchronized void addFolderListener(IFolderListener listener, int kindMask) {
         FolderListenerBundle bundle = new FolderListenerBundle();
         bundle.listener = listener;
         bundle.kindMask = kindMask;
-        
+
         mFolderListeners.add(bundle);
     }
 
@@ -301,7 +303,7 @@
      */
     public synchronized void addProjectListener(IProjectListener listener) {
         mProjectListeners.add(listener);
-        
+
         // we need to look at the opened projects and give them to the listener.
 
         // get the list of opened android projects.
@@ -313,7 +315,7 @@
             listener.projectOpenedWithWorkspace(androidProject.getProject());
         }
     }
-    
+
     /**
      * Removes an existing project listener.
      * @param listener the listener to remove.
@@ -321,7 +323,7 @@
     public synchronized void removeProjectListener(IProjectListener listener) {
         mProjectListeners.remove(listener);
     }
-    
+
     /**
      * Adds a resource event listener.
      * @param listener The listener to receive the events.
@@ -346,7 +348,7 @@
         for (IResourceEventListener listener : mEventListeners) {
             listener.resourceChangeEventStart();
         }
-        
+
         if (event.getType() == IResourceChangeEvent.PRE_DELETE) {
             // a project is being deleted. Lets get the project object and remove
             // its compiled resource list.
@@ -360,7 +362,7 @@
         } else {
             // this a regular resource change. We get the delta and go through it with a visitor.
             IResourceDelta delta = event.getDelta();
-            
+
             DeltaVisitor visitor = new DeltaVisitor();
             try {
                 delta.accept(visitor);
@@ -373,5 +375,5 @@
             listener.resourceChangeEventEnd();
         }
     }
-    
+
 }
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/AndroidJarLoader.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/AndroidJarLoader.java
index 477d14a..3bb125a 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/AndroidJarLoader.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/AndroidJarLoader.java
@@ -47,7 +47,7 @@
             mClass = clazz;
         }
 
-        public String getCanonicalName() {
+        public String getFullClassName() {
             return mClass.getCanonicalName();
         }
 
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/AndroidTargetParser.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/AndroidTargetParser.java
index a499137..848636c 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/AndroidTargetParser.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/AndroidTargetParser.java
@@ -47,6 +47,7 @@
 import java.lang.reflect.Constructor;
 import java.lang.reflect.Field;
 import java.lang.reflect.Modifier;
+import java.net.URI;
 import java.net.URL;
 import java.net.URLClassLoader;
 import java.util.ArrayList;
@@ -177,13 +178,14 @@
                 return Status.CANCEL_STATUS;
             }
 
-            ViewClassInfo[] layoutViewsInfo = mainList.toArray(new ViewClassInfo[mainList.size()]);
+            ViewClassInfo[] layoutViewsInfo = mainList.toArray(
+                    new ViewClassInfo[mainList.size()]);
             ViewClassInfo[] layoutGroupsInfo = groupList.toArray(
                     new ViewClassInfo[groupList.size()]);
-
-            // collect the preferences classes.
             mainList.clear();
             groupList.clear();
+
+            // collect the preferences classes.
             collectPreferenceClasses(classLoader, attrsXmlParser, mainList, groupList,
                     progress.newChild(1));
 
@@ -491,7 +493,8 @@
      */
     private void collectLayoutClasses(AndroidJarLoader classLoader,
             AttrsXmlParser attrsXmlParser,
-            Collection<ViewClassInfo> mainList, Collection<ViewClassInfo> groupList,
+            Collection<ViewClassInfo> mainList,
+            Collection<ViewClassInfo> groupList,
             IProgressMonitor monitor) {
         LayoutParamsParser ldp = null;
         try {
@@ -656,7 +659,8 @@
             if (f.isFile() == false) {
                 AdtPlugin.log(IStatus.ERROR, "layoutlib.jar is missing!"); //$NON-NLS-1$
             } else {
-                URL url = f.toURL();
+                URI uri = f.toURI();
+                URL url = uri.toURL();
 
                 // create a class loader. Because this jar reference interfaces
                 // that are in the editors plugin, it's important to provide
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/IAndroidClassLoader.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/IAndroidClassLoader.java
index c1cd3a3..e5dbb65 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/IAndroidClassLoader.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/IAndroidClassLoader.java
@@ -33,7 +33,7 @@
      */
     public interface IClassDescriptor {
 
-        String getCanonicalName();
+        String getFullClassName();
 
         IClassDescriptor getSuperclass();
 
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/LayoutParamsParser.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/LayoutParamsParser.java
index 5d26acb..d095e36 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/LayoutParamsParser.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/LayoutParamsParser.java
@@ -48,10 +48,10 @@
  * <li>Resource ID from <code>android.R</code></li>
  * <li>The list of permissions values from <code>android.Manifest$permission</code></li>
  * <li></li>
- * </ul> 
+ * </ul>
  */
 public class LayoutParamsParser {
-    
+
     /**
      * Class extending {@link ViewClassInfo} by adding the notion of instantiability.
      * {@link LayoutParamsParser#getViews()} and {@link LayoutParamsParser#getGroups()} should
@@ -66,33 +66,33 @@
             super(isLayout, canonicalClassName, shortClassName);
             mIsInstantiable = instantiable;
         }
-        
+
         boolean isInstantiable() {
             return mIsInstantiable;
         }
     }
-    
+
     /* Note: protected members/methods are overridden in unit tests */
-    
+
     /** Reference to android.view.View */
     protected IClassDescriptor mTopViewClass;
     /** Reference to android.view.ViewGroup */
     protected IClassDescriptor mTopGroupClass;
     /** Reference to android.view.ViewGroup$LayoutParams */
     protected IClassDescriptor mTopLayoutParamsClass;
-    
+
     /** Input list of all classes deriving from android.view.View */
     protected ArrayList<IClassDescriptor> mViewList;
     /** Input list of all classes deriving from android.view.ViewGroup */
     protected ArrayList<IClassDescriptor> mGroupList;
-    
+
     /** Output map of FQCN => info on View classes */
     protected TreeMap<String, ExtViewClassInfo> mViewMap;
     /** Output map of FQCN => info on ViewGroup classes */
     protected TreeMap<String, ExtViewClassInfo> mGroupMap;
     /** Output map of FQCN => info on LayoutParams classes */
     protected HashMap<String, LayoutParamsInfo> mLayoutParamsMap;
-    
+
     /** The attrs.xml parser */
     protected AttrsXmlParser mAttrsXmlParser;
 
@@ -109,7 +109,7 @@
         mClassLoader = classLoader;
         mAttrsXmlParser = attrsXmlParser;
     }
-    
+
     /** Returns the map of FQCN => info on View classes */
     public List<ViewClassInfo> getViews() {
         return getInstantiables(mViewMap);
@@ -119,7 +119,7 @@
     public List<ViewClassInfo> getGroups() {
         return getInstantiables(mGroupMap);
     }
-    
+
     /**
      * TODO: doc here.
      * <p/>
@@ -142,7 +142,7 @@
                 AndroidConstants.CLASS_PREFERENCEGROUP,
                 null /* paramsClassName */ );
     }
-    
+
     private void parseClasses(IProgressMonitor monitor,
             String rootClassName,
             String groupClassName,
@@ -172,17 +172,24 @@
             if (mTopLayoutParamsClass != null) {
                 mLayoutParamsMap = new HashMap<String, LayoutParamsInfo>();
             }
-            
+
             // Add top classes to the maps since by design they are not listed in classes deriving
             // from themselves.
-            addGroup(mTopGroupClass);
-            addView(mTopViewClass);
+            if (mTopGroupClass != null) {
+                addGroup(mTopGroupClass);
+            }
+            if (mTopViewClass != null) {
+                addView(mTopViewClass);
+            }
 
             // ViewGroup derives from View
-            mGroupMap.get(groupClassName).setSuperClass(mViewMap.get(rootClassName));
+            ExtViewClassInfo vg = mGroupMap.get(groupClassName);
+            if (vg != null) {
+                vg.setSuperClass(mViewMap.get(rootClassName));
+            }
 
             progress.setWorkRemaining(mGroupList.size() + mViewList.size());
-            
+
             for (IClassDescriptor groupChild : mGroupList) {
                 addGroup(groupChild);
                 progress.worked(1);
@@ -211,7 +218,7 @@
      * It calls itself recursively to handle super classes which are also Views.
      */
     private ExtViewClassInfo addView(IClassDescriptor viewClass) {
-        String fqcn = viewClass.getCanonicalName();
+        String fqcn = viewClass.getFullClassName();
         if (mViewMap.containsKey(fqcn)) {
             return mViewMap.get(fqcn);
         } else if (mGroupMap.containsKey(fqcn)) {
@@ -225,7 +232,7 @@
         // All view classes derive from mTopViewClass by design.
         // Do not lookup the super class for mTopViewClass itself.
         if (viewClass.equals(mTopViewClass) == false) {
-            IClassDescriptor superClass = viewClass.getSuperclass(); 
+            IClassDescriptor superClass = viewClass.getSuperclass();
             ExtViewClassInfo superClassInfo = addView(superClass);
             info.setSuperClass(superClassInfo);
         }
@@ -239,7 +246,7 @@
      * It calls itself recursively to handle super classes which are also ViewGroups.
      */
     private ExtViewClassInfo addGroup(IClassDescriptor groupClass) {
-        String fqcn = groupClass.getCanonicalName();
+        String fqcn = groupClass.getFullClassName();
         if (mGroupMap.containsKey(fqcn)) {
             return mGroupMap.get(fqcn);
         }
@@ -252,14 +259,14 @@
         // android.view.View (i.e. mTopViewClass here). So the only group that can have View as
         // its super class is the ViewGroup base class and we don't try to resolve it since groups
         // are loaded before views.
-        IClassDescriptor superClass = groupClass.getSuperclass(); 
-        
+        IClassDescriptor superClass = groupClass.getSuperclass();
+
         // Assertion: at this point, we should have
         //   superClass != mTopViewClass || fqcn.equals(AndroidConstants.CLASS_VIEWGROUP);
 
         if (superClass != null && superClass.equals(mTopViewClass) == false) {
             ExtViewClassInfo superClassInfo = addGroup(superClass);
-            
+
             // Assertion: we should have superClassInfo != null && superClassInfo != info;
             if (superClassInfo != null && superClassInfo != info) {
                 info.setSuperClass(superClassInfo);
@@ -272,10 +279,10 @@
         }
         return info;
     }
-    
+
     /**
      * Parses a ViewGroup class and returns an info object on its inner LayoutParams.
-     * 
+     *
      * @return The {@link LayoutParamsInfo} for the ViewGroup class or null.
      */
     private LayoutParamsInfo addLayoutParams(IClassDescriptor groupClass) {
@@ -298,7 +305,7 @@
         if (layoutParamsClass != null) {
             return getLayoutParamsInfo(layoutParamsClass);
         }
-        
+
         return null;
     }
 
@@ -307,20 +314,20 @@
      * It calls itself recursively to handle the super class of the LayoutParams.
      */
     private LayoutParamsInfo getLayoutParamsInfo(IClassDescriptor layoutParamsClass) {
-        String fqcn = layoutParamsClass.getCanonicalName();
+        String fqcn = layoutParamsClass.getFullClassName();
         LayoutParamsInfo layoutParamsInfo = mLayoutParamsMap.get(fqcn);
 
         if (layoutParamsInfo != null) {
             return layoutParamsInfo;
         }
-        
-        // Find the link on the LayoutParams super class 
+
+        // Find the link on the LayoutParams super class
         LayoutParamsInfo superClassInfo = null;
         if (layoutParamsClass.equals(mTopLayoutParamsClass) == false) {
-            IClassDescriptor superClass = layoutParamsClass.getSuperclass(); 
+            IClassDescriptor superClass = layoutParamsClass.getSuperclass();
             superClassInfo = getLayoutParamsInfo(superClass);
         }
-        
+
         // Find the link on the enclosing ViewGroup
         ExtViewClassInfo enclosingGroupInfo = addGroup(layoutParamsClass.getEnclosingClass());
 
@@ -338,7 +345,7 @@
      * and if found returns its class definition.
      * <p/>
      * This uses the actual defined inner classes and does not look at inherited classes.
-     *  
+     *
      * @param groupClass The ViewGroup derived class
      * @return The Class of the inner LayoutParams or null if none is declared.
      */
@@ -351,7 +358,7 @@
         }
         return null;
     }
-    
+
     /**
      * Computes and return a list of ViewClassInfo from a map by filtering out the class that
      * cannot be instantiated.
@@ -359,13 +366,13 @@
     private List<ViewClassInfo> getInstantiables(SortedMap<String, ExtViewClassInfo> map) {
         Collection<ExtViewClassInfo> values = map.values();
         ArrayList<ViewClassInfo> list = new ArrayList<ViewClassInfo>();
-        
+
         for (ExtViewClassInfo info : values) {
             if (info.isInstantiable()) {
                 list.add(info);
             }
         }
-        
+
         return list;
     }
 }
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/WidgetClassLoader.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/WidgetClassLoader.java
index 321c236..b1f5b60 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/WidgetClassLoader.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/WidgetClassLoader.java
@@ -48,7 +48,7 @@
      */
     private final static class ClassDescriptor implements IClassDescriptor {
         
-        private String mName;
+        private String mFqcn;
         private String mSimpleName;
         private ClassDescriptor mSuperClass;
         private ClassDescriptor mEnclosingClass;
@@ -57,12 +57,12 @@
         private boolean mIsInstantiable = false;
 
         ClassDescriptor(String fqcn) {
-            mName = fqcn;
+            mFqcn = fqcn;
             mSimpleName = getSimpleName(fqcn);
         }
 
-        public String getCanonicalName() {
-            return mName;
+        public String getFullClassName() {
+            return mFqcn;
         }
 
         public String getSimpleName() {
@@ -90,7 +90,7 @@
             
             // finally change the name of declared class to make sure it uses the
             // convention: package.enclosing$declared instead of package.enclosing.declared
-            mName = enclosingClass.mName + "$" + mName.substring(enclosingClass.mName.length() + 1);
+            mFqcn = enclosingClass.mFqcn + "$" + mFqcn.substring(enclosingClass.mFqcn.length() + 1);
         }
 
         public IClassDescriptor getSuperclass() {
@@ -104,14 +104,14 @@
         @Override
         public boolean equals(Object clazz) {
             if (clazz instanceof ClassDescriptor) {
-                return mName.equals(((ClassDescriptor)clazz).mName);
+                return mFqcn.equals(((ClassDescriptor)clazz).mFqcn);
             }
             return super.equals(clazz);
         }
         
         @Override
         public int hashCode() {
-            return mName.hashCode();
+            return mFqcn.hashCode();
         }
         
         public boolean isInstantiable() {
@@ -264,7 +264,7 @@
         Collection<ClassDescriptor> params = mLayoutParamsMap.values();
 
         for (ClassDescriptor param : params) {
-            String fqcn = param.getCanonicalName();
+            String fqcn = param.getFullClassName();
             
             // get the enclosed name.
             String enclosed = getEnclosedName(fqcn);
@@ -278,7 +278,7 @@
                 
                 // remove the class from the map, and put it back with the fixed name
                 mMap.remove(fqcn);
-                mMap.put(param.getCanonicalName(), param);
+                mMap.put(param.getFullClassName(), param);
             }
         }
     }
diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/sdk/AndroidJarLoaderTest.java b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/sdk/AndroidJarLoaderTest.java
index f9178f1..5f7de42 100644
--- a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/sdk/AndroidJarLoaderTest.java
+++ b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/sdk/AndroidJarLoaderTest.java
@@ -123,7 +123,7 @@
         // Class1 and Class1$InnerStaticClass1 derive from Object and are thus ignored.
         // Class1$InnerClass2 should never be seen either.
         assertEquals("jar.example.Class2",  //$NON-NLS-1$
-                found.get("jar.example.Class1").get(0).getCanonicalName());  //$NON-NLS-1$
+                found.get("jar.example.Class1").get(0).getFullClassName());  //$NON-NLS-1$
         assertEquals(1, found.get("jar.example.Class1").size());      //$NON-NLS-1$
         assertEquals(0, found.get("jar.example.Class2").size());      //$NON-NLS-1$
     }