• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2007 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.build;
18 
19 import com.android.ide.eclipse.adt.AdtPlugin;
20 import com.android.ide.eclipse.adt.AndroidConstants;
21 import com.android.ide.eclipse.adt.internal.build.BaseBuilder.BaseDeltaVisitor;
22 import com.android.ide.eclipse.adt.internal.build.PreCompilerBuilder.AidlData;
23 import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs.BuildVerbosity;
24 import com.android.ide.eclipse.adt.internal.project.AndroidManifestHelper;
25 import com.android.ide.eclipse.adt.io.IFileWrapper;
26 import com.android.sdklib.SdkConstants;
27 import com.android.sdklib.xml.ManifestData;
28 
29 import org.eclipse.core.resources.IContainer;
30 import org.eclipse.core.resources.IFile;
31 import org.eclipse.core.resources.IFolder;
32 import org.eclipse.core.resources.IResource;
33 import org.eclipse.core.resources.IResourceDelta;
34 import org.eclipse.core.resources.IResourceDeltaVisitor;
35 import org.eclipse.core.resources.IWorkspaceRoot;
36 import org.eclipse.core.resources.ResourcesPlugin;
37 import org.eclipse.core.runtime.CoreException;
38 import org.eclipse.core.runtime.IPath;
39 
40 import java.util.ArrayList;
41 
42 /**
43  * Resource Delta visitor for the pre-compiler.
44  * <p/>This delta visitor only cares about files that are the source or the result of actions of the
45  * {@link PreCompilerBuilder}:
46  * <ul><li>R.java/Manifest.java generated by compiling the resources</li>
47  * <li>Any Java files generated by <code>aidl</code></li></ul>.
48  *
49  * Therefore it looks for the following:
50  * <ul><li>Any modification in the resource folder</li>
51  * <li>Removed files from the source folder receiving generated Java files</li>
52  * <li>Any modification to aidl files.</li>
53  *
54  */
55 class PreCompilerDeltaVisitor extends BaseDeltaVisitor implements
56         IResourceDeltaVisitor {
57 
58     private enum AidlType {
59         UNKNOWN, INTERFACE, PARCELABLE;
60     }
61 
62     // See comment in #getAidlType()
63 //    private final static Pattern sParcelablePattern = Pattern.compile(
64 //            "^\\s*parcelable\\s+([a-zA-Z_][a-zA-Z0-9_]*)\\s*;\\s*$");
65 //
66 //    private final static Pattern sInterfacePattern = Pattern.compile(
67 //            "^\\s*interface\\s+([a-zA-Z_][a-zA-Z0-9_]*)\\s*(?:\\{.*)?$");
68 
69     // Result fields.
70     /**
71      * Compile flag. This is set to true if one of the changed/added/removed
72      * file is a resource file. Upon visiting all the delta resources, if
73      * this flag is true, then we know we'll have to compile the resources
74      * into R.java
75      */
76     private boolean mCompileResources = false;
77 
78     /**
79      * Aidl force recompilation flag. If true, we'll attempt to recompile all aidl files.
80      */
81     private boolean mForceAidlCompile = false;
82 
83     /** List of .aidl files found that are modified or new. */
84     private final ArrayList<AidlData> mAidlToCompile = new ArrayList<AidlData>();
85 
86     /** List of .aidl files that have been removed. */
87     private final ArrayList<AidlData> mAidlToRemove = new ArrayList<AidlData>();
88 
89     /** Manifest check/parsing flag. */
90     private boolean mCheckedManifestXml = false;
91 
92     /** Application Package, gathered from the parsing of the manifest */
93     private String mJavaPackage = null;
94     /** minSDKVersion attribute value, gathered from the parsing of the manifest */
95     private String mMinSdkVersion = null;
96 
97     // Internal usage fields.
98     /**
99      * In Resource folder flag. This allows us to know if we're in the
100      * resource folder.
101      */
102     private boolean mInRes = false;
103 
104     /**
105      * Current Source folder. This allows us to know if we're in a source
106      * folder, and which folder.
107      */
108     private IFolder mSourceFolder = null;
109 
110     /** List of source folders. */
111     private ArrayList<IPath> mSourceFolders;
112     private boolean mIsGenSourceFolder = false;
113 
114     private IWorkspaceRoot mRoot;
115 
116 
PreCompilerDeltaVisitor(BaseBuilder builder, ArrayList<IPath> sourceFolders)117     public PreCompilerDeltaVisitor(BaseBuilder builder, ArrayList<IPath> sourceFolders) {
118         super(builder);
119         mSourceFolders = sourceFolders;
120         mRoot = ResourcesPlugin.getWorkspace().getRoot();
121     }
122 
getCompileResources()123     public boolean getCompileResources() {
124         return mCompileResources;
125     }
126 
getForceAidlCompile()127     public boolean getForceAidlCompile() {
128         return mForceAidlCompile;
129     }
130 
getAidlToCompile()131     public ArrayList<AidlData> getAidlToCompile() {
132         return mAidlToCompile;
133     }
134 
getAidlToRemove()135     public ArrayList<AidlData> getAidlToRemove() {
136         return mAidlToRemove;
137     }
138 
139     /**
140      * Returns whether the manifest file was parsed/checked for error during the resource delta
141      * visiting.
142      */
getCheckedManifestXml()143     public boolean getCheckedManifestXml() {
144         return mCheckedManifestXml;
145     }
146 
147     /**
148      * Returns the manifest package if the manifest was checked/parsed.
149      * <p/>
150      * This can return null in two cases:
151      * <ul>
152      * <li>The manifest was not part of the resource change delta, and the manifest was
153      * not checked/parsed ({@link #getCheckedManifestXml()} returns <code>false</code>)</li>
154      * <li>The manifest was parsed ({@link #getCheckedManifestXml()} returns <code>true</code>),
155      * but the package declaration is missing</li>
156      * </ul>
157      * @return the manifest package or null.
158      */
getManifestPackage()159     public String getManifestPackage() {
160         return mJavaPackage;
161     }
162 
163     /**
164      * Returns the minSDkVersion attribute from the manifest if it was checked/parsed.
165      * <p/>
166      * This can return null in two cases:
167      * <ul>
168      * <li>The manifest was not part of the resource change delta, and the manifest was
169      * not checked/parsed ({@link #getCheckedManifestXml()} returns <code>false</code>)</li>
170      * <li>The manifest was parsed ({@link #getCheckedManifestXml()} returns <code>true</code>),
171      * but the package declaration is missing</li>
172      * </ul>
173      * @return the minSdkVersion or null.
174      */
getMinSdkVersion()175     public String getMinSdkVersion() {
176         return mMinSdkVersion;
177     }
178 
179     /*
180      * (non-Javadoc)
181      *
182      * @see org.eclipse.core.resources.IResourceDeltaVisitor
183      *      #visit(org.eclipse.core.resources.IResourceDelta)
184      */
visit(IResourceDelta delta)185     public boolean visit(IResourceDelta delta) throws CoreException {
186         // we are only going to look for changes in res/, source folders and in
187         // AndroidManifest.xml since the delta visitor goes through the main
188         // folder before its children we can check when the path segment
189         // count is 2 (format will be /$Project/folder) and make sure we are
190         // processing res/, source folders or AndroidManifest.xml
191 
192         IResource resource = delta.getResource();
193         IPath path = resource.getFullPath();
194         String[] segments = path.segments();
195 
196         // since the delta visitor also visits the root we return true if
197         // segments.length = 1
198         if (segments.length == 1) {
199             // this is always the Android project since we call
200             // Builder#getDelta(IProject) on the project itself.
201             return true;
202         } else if (segments.length == 2) {
203             // if we are at an item directly under the root directory,
204             // then we are not yet in a source or resource folder
205             mInRes = false;
206             mSourceFolder = null;
207 
208             if (SdkConstants.FD_RESOURCES.equalsIgnoreCase(segments[1])) {
209                 // this is the resource folder that was modified. we want to
210                 // see its content.
211 
212                 // since we're going to visit its children next, we set the
213                 // flag
214                 mInRes = true;
215                 mSourceFolder = null;
216                 return true;
217             } else if (SdkConstants.FN_ANDROID_MANIFEST_XML.equalsIgnoreCase(segments[1])) {
218                 // any change in the manifest could trigger a new R.java
219                 // class, so we don't need to check the delta kind
220                 if (delta.getKind() != IResourceDelta.REMOVED) {
221                     // clean the error markers on the file.
222                     IFile manifestFile = (IFile)resource;
223 
224                     if (manifestFile.exists()) {
225                         manifestFile.deleteMarkers(AndroidConstants.MARKER_XML, true,
226                                 IResource.DEPTH_ZERO);
227                         manifestFile.deleteMarkers(AndroidConstants.MARKER_ANDROID, true,
228                                 IResource.DEPTH_ZERO);
229                     }
230 
231                     // parse the manifest for data and error
232                     ManifestData manifestData = AndroidManifestHelper.parse(
233                             new IFileWrapper(manifestFile), true /*gatherData*/, this);
234 
235                     if (manifestData != null) {
236                         mJavaPackage = manifestData.getPackage();
237                         mMinSdkVersion = manifestData.getMinSdkVersionString();
238                     }
239 
240                     mCheckedManifestXml = true;
241                 }
242                 mCompileResources = true;
243 
244                 // we don't want to go to the children, not like they are
245                 // any for this resource anyway.
246                 return false;
247             }
248         }
249 
250         // at this point we can either be in the source folder or in the
251         // resource folder or in a different folder that contains a source
252         // folder.
253         // This is due to not all source folder being src/. Some could be
254         // something/somethingelse/src/
255 
256         // so first we test if we already know we are in a source or
257         // resource folder.
258 
259         if (mSourceFolder != null) {
260             // if we are in the res folder, we are looking for the following changes:
261             // - added/removed/modified aidl files.
262             // - missing R.java file
263 
264             // if the resource is a folder, we just go straight to the children
265             if (resource.getType() == IResource.FOLDER) {
266                 return true;
267             }
268 
269             if (resource.getType() != IResource.FILE) {
270                 return false;
271             }
272             IFile file = (IFile)resource;
273 
274             // get the modification kind
275             int kind = delta.getKind();
276 
277             // we process normal source folder and the 'gen' source folder differently.
278             if (mIsGenSourceFolder) {
279                 // this is the generated java file source folder.
280                 // - if R.java/Manifest.java are removed/modified, we recompile the resources
281                 // - if aidl files are removed/modified, we recompile them.
282 
283                 boolean outputWarning = false;
284 
285                 String fileName = resource.getName();
286 
287                 // Special case of R.java/Manifest.java.
288                 if (AndroidConstants.FN_RESOURCE_CLASS.equals(fileName) ||
289                         AndroidConstants.FN_MANIFEST_CLASS.equals(fileName)) {
290                     // if it was removed, there's a possibility that it was removed due to a
291                     // package change, or an aidl that was removed, but the only thing
292                     // that will happen is that we'll have an extra build. Not much of a problem.
293                     mCompileResources = true;
294 
295                     // we want a warning
296                     outputWarning = true;
297                 } else {
298                     // this has to be a Java file created from an aidl file.
299                     // Look for the source aidl file in all the source folders.
300                     String aidlFileName = fileName.replaceAll(AndroidConstants.RE_JAVA_EXT,
301                             AndroidConstants.DOT_AIDL);
302 
303                     for (IPath sourceFolderPath : mSourceFolders) {
304                         // do not search in the current source folder as it is the 'gen' folder.
305                         if (sourceFolderPath.equals(mSourceFolder.getFullPath())) {
306                             continue;
307                         }
308 
309                         IFolder sourceFolder = getFolder(sourceFolderPath);
310                         if (sourceFolder != null) {
311                             // go recursively, segment by segment.
312                             // index starts at 2 (0 is project, 1 is 'gen'
313                             IFile sourceFile = findFile(sourceFolder, segments, 2, aidlFileName);
314 
315                             if (sourceFile != null) {
316                                 // found the source. add it to the list of files to compile
317                                 mAidlToCompile.add(new AidlData(sourceFolder, sourceFile));
318                                 outputWarning = true;
319                                 break;
320                             }
321                         }
322                     }
323                 }
324 
325                 if (outputWarning) {
326                     if (kind == IResourceDelta.REMOVED) {
327                         // We pring an error just so that it's red, but it's just a warning really.
328                         String msg = String.format(Messages.s_Removed_Recreating_s, fileName);
329                         AdtPlugin.printErrorToConsole(mBuilder.getProject(), msg);
330                     } else if (kind == IResourceDelta.CHANGED) {
331                         // the file was modified manually! we can't allow it.
332                         String msg = String.format(Messages.s_Modified_Manually_Recreating_s,
333                                 fileName);
334                         AdtPlugin.printErrorToConsole(mBuilder.getProject(), msg);
335                     }
336                 }
337             } else {
338                 // this is another source folder.
339                 // We only care about aidl files being added/modified/removed.
340 
341                 // get the extension of the resource
342                 String ext = resource.getFileExtension();
343                 if (AndroidConstants.EXT_AIDL.equalsIgnoreCase(ext)) {
344                     // first check whether it's a regular file or a parcelable.
345                     AidlType type = getAidlType(file);
346 
347                     if (type == AidlType.INTERFACE) {
348                         if (kind == IResourceDelta.REMOVED) {
349                             // we'll have to remove the generated file.
350                             mAidlToRemove.add(new AidlData(mSourceFolder, file));
351                         } else if (mForceAidlCompile == false) {
352                             // add the aidl file to the list of file to (re)compile
353                             mAidlToCompile.add(new AidlData(mSourceFolder, file));
354                         }
355                     } else {
356                         // force recompilations of all Aidl Files.
357                         mForceAidlCompile = true;
358                         mAidlToCompile.clear();
359                     }
360                 }
361             }
362 
363             // no children.
364             return false;
365         } else if (mInRes) {
366             // if we are in the res folder, we are looking for the following
367             // changes:
368             // - added/removed/modified xml files.
369             // - added/removed files of any other type
370 
371             // if the resource is a folder, we just go straight to the
372             // children
373             if (resource.getType() == IResource.FOLDER) {
374                 return true;
375             }
376 
377             // get the extension of the resource
378             String ext = resource.getFileExtension();
379             int kind = delta.getKind();
380 
381             String p = resource.getProjectRelativePath().toString();
382             String message = null;
383             switch (kind) {
384                 case IResourceDelta.CHANGED:
385                     // display verbose message
386                     message = String.format(Messages.s_Modified_Recreating_s, p,
387                             AndroidConstants.FN_RESOURCE_CLASS);
388                     break;
389                 case IResourceDelta.ADDED:
390                     // display verbose message
391                     message = String.format(Messages.Added_s_s_Needs_Updating, p,
392                             AndroidConstants.FN_RESOURCE_CLASS);
393                     break;
394                 case IResourceDelta.REMOVED:
395                     // display verbose message
396                     message = String.format(Messages.s_Removed_s_Needs_Updating, p,
397                             AndroidConstants.FN_RESOURCE_CLASS);
398                     break;
399             }
400             if (message != null) {
401                 AdtPlugin.printBuildToConsole(BuildVerbosity.VERBOSE,
402                         mBuilder.getProject(), message);
403             }
404 
405             if (AndroidConstants.EXT_XML.equalsIgnoreCase(ext)) {
406                 if (kind != IResourceDelta.REMOVED) {
407                     // check xml Validity
408                     mBuilder.checkXML(resource, this);
409                 }
410 
411                 // if we are going through this resource, it was modified
412                 // somehow.
413                 // we don't care if it was an added/removed/changed event
414                 mCompileResources = true;
415                 return false;
416             } else {
417                 // this is a non xml resource.
418                 if (kind == IResourceDelta.ADDED
419                         || kind == IResourceDelta.REMOVED) {
420                     mCompileResources = true;
421                     return false;
422                 }
423             }
424         } else if (resource instanceof IFolder) {
425             // in this case we may be inside a folder that contains a source
426             // folder, go through the list of known source folders
427 
428             for (IPath sourceFolderPath : mSourceFolders) {
429                 // first check if they match exactly.
430                 if (sourceFolderPath.equals(path)) {
431                     // this is a source folder!
432                     mInRes = false;
433                     mSourceFolder = getFolder(sourceFolderPath); // all non null due to test above
434                     mIsGenSourceFolder = path.segmentCount() == 2 &&
435                             path.segment(1).equals(SdkConstants.FD_GEN_SOURCES);
436                     return true;
437                 }
438 
439                 // check if we are on the way to a source folder.
440                 int count = sourceFolderPath.matchingFirstSegments(path);
441                 if (count == path.segmentCount()) {
442                     mInRes = false;
443                     return true;
444                 }
445             }
446 
447             // if we're here, we are visiting another folder
448             // like /$Project/bin/ for instance (we get notified for changes
449             // in .class!)
450             // This could also be another source folder and we have found
451             // R.java in a previous source folder
452             // We don't want to visit its children
453             return false;
454         }
455 
456         return false;
457     }
458 
459     /**
460      * Searches for and return a file in a folder. The file is defined by its segments, and a new
461      * name (replacing the last segment).
462      * @param folder the folder we are searching
463      * @param segments the segments of the file to search.
464      * @param index the index of the current segment we are looking for
465      * @param filename the new name to replace the last segment.
466      * @return the {@link IFile} representing the searched file, or null if not found
467      */
findFile(IFolder folder, String[] segments, int index, String filename)468     private IFile findFile(IFolder folder, String[] segments, int index, String filename) {
469         boolean lastSegment = index == segments.length - 1;
470         IResource resource = folder.findMember(lastSegment ? filename : segments[index]);
471         if (resource != null && resource.exists()) {
472             if (lastSegment) {
473                 if (resource.getType() == IResource.FILE) {
474                     return (IFile)resource;
475                 }
476             } else {
477                 if (resource.getType() == IResource.FOLDER) {
478                     return findFile((IFolder)resource, segments, index+1, filename);
479                 }
480             }
481         }
482         return null;
483     }
484 
485     /**
486      * Returns a handle to the folder identified by the given path in this container.
487      * <p/>The different with {@link IContainer#getFolder(IPath)} is that this returns a non
488      * null object only if the resource actually exists and is a folder (and not a file)
489      * @param path the path of the folder to return.
490      * @return a handle to the folder if it exists, or null otherwise.
491      */
getFolder(IPath path)492     private IFolder getFolder(IPath path) {
493         IResource resource = mRoot.findMember(path);
494         if (resource != null && resource.exists() && resource.getType() == IResource.FOLDER) {
495             return (IFolder)resource;
496         }
497 
498         return null;
499     }
500 
501     /**
502      * Returns the type of the aidl file. Aidl files can either declare interfaces, or declare
503      * parcelables. This method will attempt to parse the file and return the type. If the type
504      * cannot be determined, then it will return {@link AidlType#UNKNOWN}.
505      * @param file The aidl file
506      * @return the type of the aidl.
507      * @throws CoreException
508      */
getAidlType(IFile file)509     private AidlType getAidlType(IFile file) throws CoreException {
510         // At this time, parsing isn't available, so we return UNKNOWN. This will force
511         // a recompilation of all aidl file as soon as one is changed.
512         return AidlType.UNKNOWN;
513 
514         // TODO: properly parse aidl file to determine type and generate dependency graphs.
515 //
516 //        String className = file.getName().substring(0,
517 //                file.getName().length() - AndroidConstants.DOT_AIDL.length());
518 //
519 //        InputStream input = file.getContents(true /* force*/);
520 //        try {
521 //            BufferedReader reader = new BufferedReader(new InputStreamReader(input));
522 //            String line;
523 //            while ((line = reader.readLine()) != null) {
524 //                if (line.length() == 0) {
525 //                    continue;
526 //                }
527 //
528 //                Matcher m = sParcelablePattern.matcher(line);
529 //                if (m.matches() && m.group(1).equals(className)) {
530 //                    return AidlType.PARCELABLE;
531 //                }
532 //
533 //                m = sInterfacePattern.matcher(line);
534 //                if (m.matches() && m.group(1).equals(className)) {
535 //                    return AidlType.INTERFACE;
536 //                }
537 //            }
538 //        } catch (IOException e) {
539 //            throw new CoreException(new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID,
540 //                    "Error parsing aidl file", e));
541 //        } finally {
542 //            try {
543 //                input.close();
544 //            } catch (IOException e) {
545 //                throw new CoreException(new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID,
546 //                        "Error parsing aidl file", e));
547 //            }
548 //        }
549 //
550 //        return AidlType.UNKNOWN;
551     }
552 }
553