• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
RulesEngine(IProject project)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      */
dispose()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      */
preloadAndroidView()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      */
callGetDisplayName(UiViewElementNode element)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      */
callOnSelected(IGraphics gc, NodeProxy selectedNode, String displayName, boolean isMultipleSelection)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      */
callOnChildSelected(IGraphics gc, NodeProxy parentNode, NodeProxy childNode)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      */
callOnDropEnter(NodeProxy targetNode, IDragElement[] elements)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      */
callOnDropMove(NodeProxy targetNode, IDragElement[] elements, DropFeedback feedback, Point where)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      */
callOnDropLeave(NodeProxy targetNode, IDragElement[] elements, DropFeedback feedback)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      */
callOnDropped(NodeProxy targetNode, IDragElement[] elements, DropFeedback feedback, Point where)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      */
callDropFeedbackPaint(IGraphics gc, NodeProxy targetNode, DropFeedback feedback)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 {
folderChanged(IFolder folder, int kind)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      */
clearCache()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      */
loadRule(UiViewElementNode element)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      */
loadRule(String realFqcn, String targetFqcn)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      */
initializeRule(IViewRule rule, String targetFqcn)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      */
loadStream(InputStream is, String fqcn, String codeBase)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 
logError(String format, Object...params)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 
GreGroovyClassLoader(ClassLoader cl, CompilerConfiguration cc)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
createCompilationUnit( CompilerConfiguration config, CodeSource source)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 
GreCompilationUnit( CompilerConfiguration config, CodeSource source, GroovyClassLoader loader)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