Home | History | Annotate | Download | only in gre
      1 /*
      2  * Copyright (C) 2009 The Android Open Source Project
      3  *
      4  * Licensed under the Eclipse Public License, Version 1.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.eclipse.org/org/documents/epl-v10.php
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.ide.eclipse.adt.internal.editors.layout.gre;
     18 
     19 import com.android.ide.eclipse.adt.AdtPlugin;
     20 import com.android.ide.eclipse.adt.AndroidConstants;
     21 import com.android.ide.eclipse.adt.editors.layout.gscripts.DropFeedback;
     22 import com.android.ide.eclipse.adt.editors.layout.gscripts.IDragElement;
     23 import com.android.ide.eclipse.adt.editors.layout.gscripts.IGraphics;
     24 import com.android.ide.eclipse.adt.editors.layout.gscripts.INode;
     25 import com.android.ide.eclipse.adt.editors.layout.gscripts.IViewRule;
     26 import com.android.ide.eclipse.adt.editors.layout.gscripts.Point;
     27 import com.android.ide.eclipse.adt.internal.editors.descriptors.ElementDescriptor;
     28 import com.android.ide.eclipse.adt.internal.editors.layout.descriptors.ViewElementDescriptor;
     29 import com.android.ide.eclipse.adt.internal.editors.layout.uimodel.UiViewElementNode;
     30 import com.android.ide.eclipse.adt.internal.resources.manager.GlobalProjectMonitor;
     31 import com.android.ide.eclipse.adt.internal.resources.manager.GlobalProjectMonitor.IFolderListener;
     32 import com.android.sdklib.SdkConstants;
     33 
     34 import org.codehaus.groovy.control.CompilationFailedException;
     35 import org.codehaus.groovy.control.CompilationUnit;
     36 import org.codehaus.groovy.control.CompilerConfiguration;
     37 import org.codehaus.groovy.control.Phases;
     38 import org.codehaus.groovy.control.SourceUnit;
     39 import org.eclipse.core.resources.IFile;
     40 import org.eclipse.core.resources.IFolder;
     41 import org.eclipse.core.resources.IProject;
     42 import org.eclipse.core.resources.IResource;
     43 import org.eclipse.core.resources.IResourceDelta;
     44 
     45 import groovy.lang.GroovyClassLoader;
     46 import groovy.lang.GroovyCodeSource;
     47 import groovy.lang.GroovyResourceLoader;
     48 
     49 import java.io.InputStream;
     50 import java.io.InputStreamReader;
     51 import java.net.MalformedURLException;
     52 import java.net.URI;
     53 import java.net.URL;
     54 import java.nio.charset.Charset;
     55 import java.security.CodeSource;
     56 import java.util.HashMap;
     57 import java.util.HashSet;
     58 import java.util.Map;
     59 
     60 /* TODO:
     61  * - create a logger object and pass it around.
     62  *
     63  */
     64 
     65 /**
     66  * The rule engine manages the groovy rules files and interacts with them.
     67  * There's one {@link RulesEngine} instance per layout editor.
     68  * Each instance has 2 sets of scripts: the static ADT rules (shared across all instances)
     69  * and the project specific rules (local to the current instance / layout editor).
     70  */
     71 public class RulesEngine {
     72 
     73     /**
     74      * The project folder where the scripts are located.
     75      * This is for both our unique ADT project folder and the user projects folders.
     76      */
     77     private static final String FD_GSCRIPTS = "gscripts";                       //$NON-NLS-1$
     78     /**
     79      * The extension we expect for the groovy scripts.
     80      */
     81     private static final String SCRIPT_EXT = ".groovy";                         //$NON-NLS-1$
     82     /**
     83      * The package we expect for our groovy scripts.
     84      * User scripts do not need to use the same (and in fact should probably not.)
     85      */
     86     private static final String SCRIPT_PACKAGE = "com.android.adt.gscripts";    //$NON-NLS-1$
     87 
     88     private final GroovyClassLoader mClassLoader;
     89     private final IProject mProject;
     90     private final Map<Object, IViewRule> mRulesCache = new HashMap<Object, IViewRule>();
     91     private ProjectFolderListener mProjectFolderListener;
     92 
     93 
     94     public RulesEngine(IProject project) {
     95         mProject = project;
     96         ClassLoader cl = getClass().getClassLoader();
     97 
     98         // Note: we could use the CompilerConfiguration to add an output log collector
     99         CompilerConfiguration cc = new CompilerConfiguration();
    100         cc.setDefaultScriptExtension(SCRIPT_EXT);
    101 
    102         mClassLoader = new GreGroovyClassLoader(cl, cc);
    103 
    104         // Add the project's gscript folder to the classpath, if it exists.
    105         IResource f = project.findMember(FD_GSCRIPTS);
    106         if ((f instanceof IFolder) && f.exists()) {
    107             URI uri = ((IFolder) f).getLocationURI();
    108             try {
    109                 URL url = uri.toURL();
    110                 mClassLoader.addURL(url);
    111             } catch (MalformedURLException e) {
    112                 // ignore; it's not a valid URL, we obviously won't use it
    113                 // in the class path.
    114             }
    115         }
    116 
    117         mProjectFolderListener = new ProjectFolderListener();
    118         GlobalProjectMonitor.getMonitor().addFolderListener(
    119                 mProjectFolderListener,
    120                 IResourceDelta.ADDED | IResourceDelta.REMOVED | IResourceDelta.CHANGED);
    121     }
    122 
    123     /**
    124      * Called by the owner of the {@link RulesEngine} when it is going to be disposed.
    125      * This frees some resources, such as the project's folder monitor.
    126      */
    127     public void dispose() {
    128         if (mProjectFolderListener != null) {
    129             GlobalProjectMonitor.getMonitor().removeFolderListener(mProjectFolderListener);
    130             mProjectFolderListener = null;
    131         }
    132         clearCache();
    133     }
    134 
    135     /**
    136      * Eventually all rules are going to try to load the base android.view.View rule.
    137      * Clients can request to preload it to make the first call faster.
    138      */
    139     public void preloadAndroidView() {
    140         loadRule(SdkConstants.CLASS_VIEW, SdkConstants.CLASS_VIEW);
    141     }
    142 
    143     /**
    144      * Invokes {@link IViewRule#getDisplayName()} on the rule matching the specified element.
    145      *
    146      * @param element The view element to target. Can be null.
    147      * @return Null if the rule failed, there's no rule or the rule does not want to override
    148      *   the display name. Otherwise, a string as returned by the groovy script.
    149      */
    150     public String callGetDisplayName(UiViewElementNode element) {
    151         // try to find a rule for this element's FQCN
    152         IViewRule rule = loadRule(element);
    153 
    154         if (rule != null) {
    155             try {
    156                 return rule.getDisplayName();
    157 
    158             } catch (Exception e) {
    159                 logError("%s.getDisplayName() failed: %s",
    160                         rule.getClass().getSimpleName(),
    161                         e.toString());
    162             }
    163         }
    164 
    165         return null;
    166     }
    167 
    168     /**
    169      * Invokes {@link IViewRule#onSelected(IGraphics, INode, String, boolean)}
    170      * on the rule matching the specified element.
    171      *
    172      * @param gc An {@link IGraphics} instance, to perform drawing operations.
    173      * @param selectedNode The node selected. Never null.
    174      * @param displayName The name to display, as returned by {@link IViewRule#getDisplayName()}.
    175      * @param isMultipleSelection A boolean set to true if more than one element is selected.
    176      */
    177     public void callOnSelected(IGraphics gc, NodeProxy selectedNode,
    178             String displayName, boolean isMultipleSelection) {
    179         // try to find a rule for this element's FQCN
    180         IViewRule rule = loadRule(selectedNode.getNode());
    181 
    182         if (rule != null) {
    183             try {
    184                 rule.onSelected(gc, selectedNode, displayName, isMultipleSelection);
    185 
    186             } catch (Exception e) {
    187                 logError("%s.onSelected() failed: %s",
    188                         rule.getClass().getSimpleName(),
    189                         e.toString());
    190             }
    191         }
    192     }
    193 
    194     /**
    195      * Invokes {@link IViewRule#onChildSelected(IGraphics, INode, INode)}
    196      * on the rule matching the specified element.
    197      *
    198      * @param gc An {@link IGraphics} instance, to perform drawing operations.
    199      * @param parentNode The parent of the node selected. Never null.
    200      * @param childNode The child node that was selected. Never null.
    201      */
    202     public void callOnChildSelected(IGraphics gc, NodeProxy parentNode, NodeProxy childNode) {
    203         // try to find a rule for this element's FQCN
    204         IViewRule rule = loadRule(parentNode.getNode());
    205 
    206         if (rule != null) {
    207             try {
    208                 rule.onChildSelected(gc, parentNode, childNode);
    209 
    210             } catch (Exception e) {
    211                 logError("%s.onChildSelected() failed: %s",
    212                         rule.getClass().getSimpleName(),
    213                         e.toString());
    214             }
    215         }
    216     }
    217 
    218 
    219     /**
    220      * Called when the d'n'd starts dragging over the target node.
    221      * If interested, returns a DropFeedback passed to onDrop/Move/Leave/Paint.
    222      * If not interested in drop, return false.
    223      * Followed by a paint.
    224      */
    225     public DropFeedback callOnDropEnter(NodeProxy targetNode,
    226             IDragElement[] elements) {
    227         // try to find a rule for this element's FQCN
    228         IViewRule rule = loadRule(targetNode.getNode());
    229 
    230         if (rule != null) {
    231             try {
    232                 return rule.onDropEnter(targetNode, elements);
    233 
    234             } catch (Exception e) {
    235                 logError("%s.onDropEnter() failed: %s",
    236                         rule.getClass().getSimpleName(),
    237                         e.toString());
    238             }
    239         }
    240 
    241         return null;
    242     }
    243 
    244     /**
    245      * Called after onDropEnter.
    246      * Returns a DropFeedback passed to onDrop/Move/Leave/Paint (typically same
    247      * as input one).
    248      */
    249     public DropFeedback callOnDropMove(NodeProxy targetNode,
    250             IDragElement[] elements,
    251             DropFeedback feedback,
    252             Point where) {
    253         // try to find a rule for this element's FQCN
    254         IViewRule rule = loadRule(targetNode.getNode());
    255 
    256         if (rule != null) {
    257             try {
    258                 return rule.onDropMove(targetNode, elements, feedback, where);
    259 
    260             } catch (Exception e) {
    261                 logError("%s.onDropMove() failed: %s",
    262                         rule.getClass().getSimpleName(),
    263                         e.toString());
    264             }
    265         }
    266 
    267         return null;
    268     }
    269 
    270     /**
    271      * Called when drop leaves the target without actually dropping
    272      */
    273     public void callOnDropLeave(NodeProxy targetNode,
    274             IDragElement[] elements,
    275             DropFeedback feedback) {
    276         // try to find a rule for this element's FQCN
    277         IViewRule rule = loadRule(targetNode.getNode());
    278 
    279         if (rule != null) {
    280             try {
    281                 rule.onDropLeave(targetNode, elements, feedback);
    282 
    283             } catch (Exception e) {
    284                 logError("%s.onDropLeave() failed: %s",
    285                         rule.getClass().getSimpleName(),
    286                         e.toString());
    287             }
    288         }
    289     }
    290 
    291     /**
    292      * Called when drop is released over the target to perform the actual drop.
    293      */
    294     public void callOnDropped(NodeProxy targetNode,
    295             IDragElement[] elements,
    296             DropFeedback feedback,
    297             Point where) {
    298         // try to find a rule for this element's FQCN
    299         IViewRule rule = loadRule(targetNode.getNode());
    300 
    301         if (rule != null) {
    302             try {
    303                 rule.onDropped(targetNode, elements, feedback, where);
    304 
    305             } catch (Exception e) {
    306                 logError("%s.onDropped() failed: %s",
    307                         rule.getClass().getSimpleName(),
    308                         e.toString());
    309             }
    310         }
    311     }
    312 
    313     /**
    314      * Called when a paint has been requested via DropFeedback.
    315      * @param targetNode
    316      */
    317     public void callDropFeedbackPaint(IGraphics gc,
    318             NodeProxy targetNode,
    319             DropFeedback feedback) {
    320         if (gc != null && feedback != null && feedback.paintClosure != null) {
    321             try {
    322                 feedback.paintClosure.call(new Object[] { gc, targetNode, feedback });
    323             } catch (Exception e) {
    324                 logError("DropFeedback.paintClosure failed: %s",
    325                         e.toString());
    326             }
    327         }
    328     }
    329 
    330     // ---- private ---
    331 
    332     private class ProjectFolderListener implements IFolderListener {
    333         public void folderChanged(IFolder folder, int kind) {
    334             if (folder.getProject() == mProject &&
    335                     FD_GSCRIPTS.equals(folder.getName())) {
    336                 // Clear our whole rules cache, to not have to deal with dependencies.
    337                 clearCache();
    338             }
    339         }
    340     }
    341 
    342     /**
    343      * Clear the Rules cache. Calls onDispose() on each rule.
    344      */
    345     private void clearCache() {
    346         // The cache can contain multiple times the same rule instance for different
    347         // keys (e.g. the UiViewElementNode key vs. the FQCN string key.) So transfer
    348         // all values to a unique set.
    349         HashSet<IViewRule> rules = new HashSet<IViewRule>(mRulesCache.values());
    350 
    351         mRulesCache.clear();
    352 
    353         for (IViewRule rule : rules) {
    354             if (rule != null) {
    355                 try {
    356                     rule.onDispose();
    357                 } catch (Exception e) {
    358                     logError("%s.onDispose() failed: %s",
    359                             rule.getClass().getSimpleName(),
    360                             e.toString());
    361                 }
    362             }
    363         }
    364     }
    365 
    366     /**
    367      * Load a rule using its descriptor. This will try to first load the rule using its
    368      * actual FQCN and if that fails will find the first parent that works in the view
    369      * hierarchy.
    370      */
    371     private IViewRule loadRule(UiViewElementNode element) {
    372         if (element == null) {
    373             return null;
    374         } else {
    375             // sanity check. this can't fail.
    376             ElementDescriptor d = element.getDescriptor();
    377             if (d == null || !(d instanceof ViewElementDescriptor)) {
    378                 return null;
    379             }
    380         }
    381 
    382         String targetFqcn = null;
    383         ViewElementDescriptor targetDesc = (ViewElementDescriptor) element.getDescriptor();
    384 
    385         // Return the rule if we find it in the cache, even if it was stored as null
    386         // (which means we didn't find it earlier, so don't look for it again)
    387         IViewRule rule = mRulesCache.get(targetDesc);
    388         if (rule != null || mRulesCache.containsKey(targetDesc)) {
    389             return rule;
    390         }
    391 
    392         // Get the descriptor and loop through the super class hierarchy
    393         for (ViewElementDescriptor desc = targetDesc;
    394                 desc != null;
    395                 desc = desc.getSuperClassDesc()) {
    396 
    397             // Get the FQCN of this View
    398             String fqcn = desc.getFullClassName();
    399             if (fqcn == null) {
    400                 return null;
    401             }
    402 
    403             // The first time we keep the FQCN around as it's the target class we were
    404             // initially trying to load. After, as we move through the hierarchy, the
    405             // target FQCN remains constant.
    406             if (targetFqcn == null) {
    407                 targetFqcn = fqcn;
    408             }
    409 
    410             // Try to find a rule matching the "real" FQCN. If we find it, we're done.
    411             // If not, the for loop will move to the parent descriptor.
    412             rule = loadRule(fqcn, targetFqcn);
    413             if (rule != null) {
    414                 // We found one.
    415                 // As a side effect, loadRule() also cached the rule using the target FQCN.
    416                 return rule;
    417             }
    418         }
    419 
    420         // Memorize in the cache that we couldn't find a rule for this descriptor
    421         mRulesCache.put(targetDesc, null);
    422         return null;
    423     }
    424 
    425     /**
    426      * Try to load a rule given a specific FQCN. This looks for an exact match in either
    427      * the ADT scripts or the project scripts and does not look at parent hierarchy.
    428      * <p/>
    429      * Once a rule is found (or not), it is stored in a cache using its target FQCN
    430      * so we don't try to reload it.
    431      * <p/>
    432      * The real FQCN is the actual groovy filename we're loading, e.g. "android.view.View.groovy"
    433      * where target FQCN is the class we were initially looking for, which might be the same as
    434      * the real FQCN or might be a derived class, e.g. "android.widget.TextView".
    435      *
    436      * @param realFqcn The FQCN of the groovy rule actually being loaded.
    437      * @param targetFqcn The FQCN of the class actually processed, which might be different from
    438      *          the FQCN of the rule being loaded.
    439      */
    440     private IViewRule loadRule(String realFqcn, String targetFqcn) {
    441         if (realFqcn == null || targetFqcn == null) {
    442             return null;
    443         }
    444 
    445         // Return the rule if we find it in the cache, even if it was stored as null
    446         // (which means we didn't find it earlier, so don't look for it again)
    447         IViewRule rule = mRulesCache.get(realFqcn);
    448         if (rule != null || mRulesCache.containsKey(realFqcn)) {
    449             return rule;
    450         }
    451 
    452         // Look for the file in ADT first.
    453         // That means a project can't redefine any of the rules we define.
    454         String filename = realFqcn + SCRIPT_EXT;
    455 
    456         try {
    457             InputStream is = AdtPlugin.readEmbeddedFileAsStream(
    458                     FD_GSCRIPTS + AndroidConstants.WS_SEP + filename);
    459             rule = loadStream(is, realFqcn, "ADT");     //$NON-NLS-1$
    460             if (rule != null) {
    461                 return initializeRule(rule, targetFqcn);
    462             }
    463         } catch (Exception e) {
    464             logError("load rule error (%s): %s", filename, e.toString());
    465         }
    466 
    467 
    468         // Then look for the file in the project
    469         IResource r = mProject.findMember(FD_GSCRIPTS);
    470         if (r != null && r.getType() == IResource.FOLDER) {
    471             r = ((IFolder) r).findMember(filename);
    472             if (r != null && r.getType() == IResource.FILE) {
    473                 try {
    474                     InputStream is = ((IFile) r).getContents();
    475                     rule = loadStream(is, realFqcn, mProject.getName());
    476                     if (rule != null) {
    477                         return initializeRule(rule, targetFqcn);
    478                     }
    479                 } catch (Exception e) {
    480                     logError("load rule error (%s): %s", filename, e.getMessage());
    481                 }
    482             }
    483         }
    484 
    485         // Memorize in the cache that we couldn't find a rule for this real FQCN
    486         mRulesCache.put(realFqcn, null);
    487         return null;
    488     }
    489 
    490     /**
    491      * Initialize a rule we just loaded. The rule has a chance to examine the target FQCN
    492      * and bail out.
    493      * <p/>
    494      * Contract: the rule is not in the {@link #mRulesCache} yet and this method will
    495      * cache it using the target FQCN if the rule is accepted.
    496      * <p/>
    497      * The real FQCN is the actual groovy filename we're loading, e.g. "android.view.View.groovy"
    498      * where target FQCN is the class we were initially looking for, which might be the same as
    499      * the real FQCN or might be a derived class, e.g. "android.widget.TextView".
    500      *
    501      * @param rule A rule freshly loaded.
    502      * @param targetFqcn The FQCN of the class actually processed, which might be different from
    503      *          the FQCN of the rule being loaded.
    504      * @return The rule if accepted, or null if the rule can't handle that FQCN.
    505      */
    506     private IViewRule initializeRule(IViewRule rule, String targetFqcn) {
    507 
    508         try {
    509             if (rule.onInitialize(targetFqcn)) {
    510                 // Add it to the cache and return it
    511                 mRulesCache.put(targetFqcn, rule);
    512                 return rule;
    513             } else {
    514                 rule.onDispose();
    515             }
    516         } catch (Exception e) {
    517             logError("%s.onInit() failed: %s",
    518                     rule.getClass().getSimpleName(),
    519                     e.toString());
    520         }
    521 
    522         return null;
    523     }
    524 
    525     /**
    526      * Actually load a groovy script and instantiate an {@link IViewRule} from it.
    527      * On error, outputs (hopefully meaningful) groovy error messages.
    528      *
    529      * @param is The input stream for the groovy script. Can be null.
    530      * @param fqcn The class name, for display purposes only.
    531      * @param codeBase A string eventually passed to {@link CodeSource} to define some kind
    532      *                 of security permission. Quite irrelevant in our case since it all
    533      *                 comes from an input stream. However this method uses it to print
    534      *                 the origin of the source in the exception errors.
    535      * @return A new {@link IViewRule} or null if loading failed for any reason.
    536      */
    537     private IViewRule loadStream(InputStream is, String fqcn, String codeBase) {
    538         try {
    539             if (is == null) {
    540                 // We handle this case for convenience. It typically means that the
    541                 // input stream couldn't be opened because the file was not found.
    542                 // Since we expect this to be a common case, we don't log it as an error.
    543                 return null;
    544             }
    545 
    546             // We don't really now the character encoding, we're going to assume UTF-8.
    547             InputStreamReader reader = new InputStreamReader(is, Charset.forName("UTF-8"));
    548             GroovyCodeSource source = new GroovyCodeSource(reader, fqcn, codeBase);
    549 
    550             // Create a groovy class from it. Can fail to compile.
    551             Class<?> c = mClassLoader.parseClass(source);
    552 
    553             // Get an instance. This might throw ClassCastException.
    554             return (IViewRule) c.newInstance();
    555 
    556         } catch (CompilationFailedException e) {
    557             logError("Compilation error in %1$s:%2$s.groovy: %3$s", codeBase, fqcn, e.toString());
    558         } catch (ClassCastException e) {
    559             logError("Script %1$s:%2$s.groovy does not implement IViewRule", codeBase, fqcn);
    560         } catch (Exception e) {
    561             logError("Failed to use %1$s:%2$s.groovy: %3$s", codeBase, fqcn, e.toString());
    562         }
    563 
    564         return null;
    565     }
    566 
    567     private void logError(String format, Object...params) {
    568         String s = String.format(format, params);
    569         AdtPlugin.printErrorToConsole(mProject, s);
    570     }
    571 
    572     // -----
    573 
    574     /**
    575      * A custom {@link GroovyClassLoader} that lets us override the {@link CompilationUnit}
    576      * and the {@link GroovyResourceLoader}.
    577      */
    578     private static class GreGroovyClassLoader extends GroovyClassLoader {
    579 
    580         public GreGroovyClassLoader(ClassLoader cl, CompilerConfiguration cc) {
    581             super(cl, cc);
    582 
    583             // Override the resource loader: when a class is not found, we try to find a class
    584             // defined in our internal ADT groovy script, assuming it has our special package.
    585             // Note that these classes do not have to implement IViewRule. That means we can
    586             // create utility classes in groovy used by the other groovy rules.
    587             final GroovyResourceLoader resLoader = getResourceLoader();
    588             setResourceLoader(new GroovyResourceLoader() {
    589                 public URL loadGroovySource(String filename) throws MalformedURLException {
    590                     URL url = resLoader.loadGroovySource(filename);
    591                     if (url == null) {
    592                         // We only try to load classes in our own groovy script package
    593                         String p = SCRIPT_PACKAGE + ".";      //$NON-NLS-1$
    594 
    595                         if (filename.startsWith(p)) {
    596                             filename = filename.substring(p.length());
    597 
    598                             // This will return null if the file doesn't exists.
    599                             // The groovy resolver will actually load and verify the class
    600                             // implemented matches the one it was expecting in the first place,
    601                             // so we don't have anything to do here besides returning the URL to
    602                             // the source file.
    603                             url = AdtPlugin.getEmbeddedFileUrl(
    604                                     AndroidConstants.WS_SEP +
    605                                     FD_GSCRIPTS +
    606                                     AndroidConstants.WS_SEP +
    607                                     filename +
    608                                     SCRIPT_EXT);
    609                         }
    610                     }
    611                     return url;
    612                 }
    613             });
    614         }
    615 
    616         @Override
    617         protected CompilationUnit createCompilationUnit(
    618                 CompilerConfiguration config,
    619                 CodeSource source) {
    620             return new GreCompilationUnit(config, source, this);
    621         }
    622     }
    623 
    624     /**
    625      * A custom {@link CompilationUnit} that lets us add default import for our base classes
    626      * using the base package of {@link IViewRule} (e.g. "import com.android...gscripts.*")
    627      */
    628     private static class GreCompilationUnit extends CompilationUnit {
    629 
    630         public GreCompilationUnit(
    631                 CompilerConfiguration config,
    632                 CodeSource source,
    633                 GroovyClassLoader loader) {
    634             super(config, source, loader);
    635 
    636             SourceUnitOperation op = new SourceUnitOperation() {
    637                 @Override
    638                 public void call(SourceUnit source) throws CompilationFailedException {
    639                     // add the equivalent of "import com.android...gscripts.*" to the source.
    640                     String p = IViewRule.class.getPackage().getName();
    641                     source.getAST().addStarImport(p + ".");  //$NON-NLS-1$
    642                 }
    643             };
    644 
    645             addPhaseOperation(op, Phases.CONVERSION);
    646         }
    647     }
    648 
    649 }
    650