• 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.builders;
18 
19 import com.android.ide.eclipse.adt.AdtConstants;
20 import com.android.ide.eclipse.adt.AdtPlugin;
21 import com.android.ide.eclipse.adt.internal.build.Messages;
22 import com.android.ide.eclipse.adt.internal.build.SourceChangeHandler;
23 import com.android.ide.eclipse.adt.internal.build.SourceProcessor;
24 import com.android.ide.eclipse.adt.internal.build.builders.BaseBuilder.BaseDeltaVisitor;
25 import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs.BuildVerbosity;
26 import com.android.ide.eclipse.adt.internal.project.AndroidManifestHelper;
27 import com.android.ide.eclipse.adt.io.IFileWrapper;
28 import com.android.sdklib.SdkConstants;
29 import com.android.sdklib.xml.ManifestData;
30 
31 import org.eclipse.core.resources.IContainer;
32 import org.eclipse.core.resources.IFile;
33 import org.eclipse.core.resources.IFolder;
34 import org.eclipse.core.resources.IResource;
35 import org.eclipse.core.resources.IResourceDelta;
36 import org.eclipse.core.resources.IResourceDeltaVisitor;
37 import org.eclipse.core.resources.IWorkspaceRoot;
38 import org.eclipse.core.resources.ResourcesPlugin;
39 import org.eclipse.core.runtime.CoreException;
40 import org.eclipse.core.runtime.IPath;
41 
42 import java.util.ArrayList;
43 import java.util.List;
44 
45 /**
46  * Resource Delta visitor for the pre-compiler.
47  * <p/>This delta visitor only cares about files that are the source or the result of actions of the
48  * {@link PreCompilerBuilder}:
49  * <ul><li>R.java/Manifest.java generated by compiling the resources</li>
50  * <li>Any Java files generated by <code>aidl</code></li></ul>.
51  *
52  * Therefore it looks for the following:
53  * <ul><li>Any modification in the resource folder</li>
54  * <li>Removed files from the source folder receiving generated Java files</li>
55  * <li>Any modification to aidl files.</li>
56  *
57  */
58 class PreCompilerDeltaVisitor extends BaseDeltaVisitor implements IResourceDeltaVisitor {
59 
60     // Result fields.
61     /**
62      * Compile flag. This is set to true if one of the changed/added/removed
63      * files is Manifest.xml, Manifest.java, or R.java. All other file changes
64      * will be taken care of by ResourceManager.
65      */
66     private boolean mCompileResources = false;
67 
68     /** Manifest check/parsing flag. */
69     private boolean mCheckedManifestXml = false;
70 
71     /** Application Package, gathered from the parsing of the manifest */
72     private String mJavaPackage = null;
73     /** minSDKVersion attribute value, gathered from the parsing of the manifest */
74     private String mMinSdkVersion = null;
75 
76     // Internal usage fields.
77     /**
78      * In Resource folder flag. This allows us to know if we're in the
79      * resource folder.
80      */
81     private boolean mInRes = false;
82 
83     /**
84      * Current Source folder. This allows us to know if we're in a source
85      * folder, and which folder.
86      */
87     private IFolder mSourceFolder = null;
88 
89     /** List of source folders. */
90     private final List<IPath> mSourceFolders;
91     private boolean mIsGenSourceFolder = false;
92 
93     private final List<SourceChangeHandler> mSourceChangeHandlers =
94         new ArrayList<SourceChangeHandler>();
95     private final IWorkspaceRoot mRoot;
96 
97 
98 
PreCompilerDeltaVisitor(BaseBuilder builder, List<IPath> sourceFolders, List<SourceProcessor> processors)99     public PreCompilerDeltaVisitor(BaseBuilder builder, List<IPath> sourceFolders,
100             List<SourceProcessor> processors) {
101         super(builder);
102         mSourceFolders = sourceFolders;
103         mRoot = ResourcesPlugin.getWorkspace().getRoot();
104 
105         for (SourceProcessor processor : processors) {
106             SourceChangeHandler handler = processor.getChangeHandler();
107             mSourceChangeHandlers.add(handler);
108         }
109     }
110 
111     /**
112      * Get whether Manifest.java, Manifest.xml, or R.java have changed
113      * @return true if any of Manifest.xml, Manifest.java, or R.java have been modified
114      */
getCompileResources()115     public boolean getCompileResources() {
116         return mCompileResources;
117     }
118 
119     /**
120      * Returns whether the manifest file was parsed/checked for error during the resource delta
121      * visiting.
122      */
getCheckedManifestXml()123     public boolean getCheckedManifestXml() {
124         return mCheckedManifestXml;
125     }
126 
127     /**
128      * Returns the manifest package if the manifest was checked/parsed.
129      * <p/>
130      * This can return null in two cases:
131      * <ul>
132      * <li>The manifest was not part of the resource change delta, and the manifest was
133      * not checked/parsed ({@link #getCheckedManifestXml()} returns <code>false</code>)</li>
134      * <li>The manifest was parsed ({@link #getCheckedManifestXml()} returns <code>true</code>),
135      * but the package declaration is missing</li>
136      * </ul>
137      * @return the manifest package or null.
138      */
getManifestPackage()139     public String getManifestPackage() {
140         return mJavaPackage;
141     }
142 
143     /**
144      * Returns the minSDkVersion attribute from the manifest if it was checked/parsed.
145      * <p/>
146      * This can return null in two cases:
147      * <ul>
148      * <li>The manifest was not part of the resource change delta, and the manifest was
149      * not checked/parsed ({@link #getCheckedManifestXml()} returns <code>false</code>)</li>
150      * <li>The manifest was parsed ({@link #getCheckedManifestXml()} returns <code>true</code>),
151      * but the package declaration is missing</li>
152      * </ul>
153      * @return the minSdkVersion or null.
154      */
getMinSdkVersion()155     public String getMinSdkVersion() {
156         return mMinSdkVersion;
157     }
158 
159     /*
160      * (non-Javadoc)
161      *
162      * @see org.eclipse.core.resources.IResourceDeltaVisitor
163      *      #visit(org.eclipse.core.resources.IResourceDelta)
164      */
165     @Override
visit(IResourceDelta delta)166     public boolean visit(IResourceDelta delta) throws CoreException {
167         // we are only going to look for changes in res/, source folders and in
168         // AndroidManifest.xml since the delta visitor goes through the main
169         // folder before its children we can check when the path segment
170         // count is 2 (format will be /$Project/folder) and make sure we are
171         // processing res/, source folders or AndroidManifest.xml
172 
173         IResource resource = delta.getResource();
174         IPath path = resource.getFullPath();
175         String[] segments = path.segments();
176 
177         // since the delta visitor also visits the root we return true if
178         // segments.length = 1
179         if (segments.length == 1) {
180             // this is always the Android project since we call
181             // Builder#getDelta(IProject) on the project itself.
182             return true;
183         } else if (segments.length == 2) {
184             // if we are at an item directly under the root directory,
185             // then we are not yet in a source or resource folder
186             mInRes = false;
187             mSourceFolder = null;
188 
189             if (SdkConstants.FD_RESOURCES.equalsIgnoreCase(segments[1])) {
190                 // this is the resource folder that was modified. we want to
191                 // see its content.
192 
193                 // since we're going to visit its children next, we set the
194                 // flag
195                 mInRes = true;
196                 mSourceFolder = null;
197                 return true;
198             } else if (SdkConstants.FN_ANDROID_MANIFEST_XML.equalsIgnoreCase(segments[1])) {
199                 // any change in the manifest could trigger a new R.java
200                 // class, so we don't need to check the delta kind
201                 if (delta.getKind() != IResourceDelta.REMOVED) {
202                     // clean the error markers on the file.
203                     IFile manifestFile = (IFile)resource;
204 
205                     if (manifestFile.exists()) {
206                         manifestFile.deleteMarkers(AdtConstants.MARKER_XML, true,
207                                 IResource.DEPTH_ZERO);
208                         manifestFile.deleteMarkers(AdtConstants.MARKER_ANDROID, true,
209                                 IResource.DEPTH_ZERO);
210                     }
211 
212                     // parse the manifest for data and error
213                     ManifestData manifestData = AndroidManifestHelper.parse(
214                             new IFileWrapper(manifestFile), true /*gatherData*/, this);
215 
216                     if (manifestData != null) {
217                         mJavaPackage = manifestData.getPackage();
218                         mMinSdkVersion = manifestData.getMinSdkVersionString();
219                     }
220 
221                     mCheckedManifestXml = true;
222                 }
223                 mCompileResources = true;
224 
225                 // we don't want to go to the children, not like they are
226                 // any for this resource anyway.
227                 return false;
228             }
229         }
230 
231         // at this point we can either be in the source folder or in the
232         // resource folder or in a different folder that contains a source
233         // folder.
234         // This is due to not all source folder being src/. Some could be
235         // something/somethingelse/src/
236 
237         // so first we test if we already know we are in a source or
238         // resource folder.
239 
240         if (mSourceFolder != null) {
241             // if we are in the res folder, we are looking for the following changes:
242             // - added/removed/modified aidl files.
243             // - missing R.java file
244 
245             // if the resource is a folder, we just go straight to the children
246             if (resource.getType() == IResource.FOLDER) {
247                 return true;
248             }
249 
250             if (resource.getType() != IResource.FILE) {
251                 return false;
252             }
253             IFile file = (IFile)resource;
254 
255             // get the modification kind
256             int kind = delta.getKind();
257 
258             // we process normal source folder and the 'gen' source folder differently.
259             if (mIsGenSourceFolder) {
260                 // this is the generated java file source folder.
261                 // - if R.java/Manifest.java are removed/modified, we recompile the resources
262                 // - if aidl files are removed/modified, we recompile them.
263 
264                 boolean outputWarning = false;
265 
266                 String fileName = resource.getName();
267 
268                 // Special case of R.java/Manifest.java.
269                 if (AdtConstants.FN_RESOURCE_CLASS.equals(fileName) ||
270                         AdtConstants.FN_MANIFEST_CLASS.equals(fileName)) {
271                     // if it was removed, there's a possibility that it was removed due to a
272                     // package change, or an aidl that was removed, but the only thing
273                     // that will happen is that we'll have an extra build. Not much of a problem.
274                     mCompileResources = true;
275 
276                     // we want a warning
277                     outputWarning = true;
278                 } else {
279                     // look to see if this file was generated by a processor.
280                     for (SourceChangeHandler handler : mSourceChangeHandlers) {
281                         if (handler.handleGeneratedFile(file, kind)) {
282                             outputWarning = true;
283                             break; // there shouldn't be 2 processors that handle the same file.
284                         }
285                     }
286                 }
287 
288                 if (outputWarning) {
289                     if (kind == IResourceDelta.REMOVED) {
290                         // We pring an error just so that it's red, but it's just a warning really.
291                         String msg = String.format(Messages.s_Removed_Recreating_s, fileName);
292                         AdtPlugin.printErrorToConsole(mBuilder.getProject(), msg);
293                     } else if (kind == IResourceDelta.CHANGED) {
294                         // the file was modified manually! we can't allow it.
295                         String msg = String.format(Messages.s_Modified_Manually_Recreating_s,
296                                 fileName);
297                         AdtPlugin.printErrorToConsole(mBuilder.getProject(), msg);
298                     }
299                 }
300             } else {
301                 // this is another source folder.
302                 for (SourceChangeHandler handler : mSourceChangeHandlers) {
303                     handler.handleSourceFile(file, kind);
304                 }
305             }
306 
307             // no children.
308             return false;
309         } else if (mInRes) {
310             // if we are in the res folder, we are looking for the following
311             // changes:
312             // - added/removed/modified xml files.
313             // - added/removed files of any other type
314 
315             // if the resource is a folder, we just go straight to the
316             // children
317             if (resource.getType() == IResource.FOLDER) {
318                 return true;
319             }
320 
321             // get the extension of the resource
322             String ext = resource.getFileExtension();
323             int kind = delta.getKind();
324 
325             String p = resource.getProjectRelativePath().toString();
326             String message = null;
327             switch (kind) {
328                 case IResourceDelta.CHANGED:
329                     // display verbose message
330                     message = String.format(Messages.s_Modified_Recreating_s, p);
331                     break;
332                 case IResourceDelta.ADDED:
333                     // display verbose message
334                     message = String.format(Messages.Added_s_s_Needs_Updating, p,
335                             AdtConstants.FN_RESOURCE_CLASS);
336                     break;
337                 case IResourceDelta.REMOVED:
338                     // display verbose message
339                     message = String.format(Messages.s_Removed_s_Needs_Updating, p,
340                             AdtConstants.FN_RESOURCE_CLASS);
341                     break;
342             }
343             if (message != null) {
344                 AdtPlugin.printBuildToConsole(BuildVerbosity.VERBOSE,
345                         mBuilder.getProject(), message);
346             }
347 
348             for (SourceChangeHandler handler : mSourceChangeHandlers) {
349                 handler.handleResourceFile((IFile)resource, kind);
350             }
351             // If it's an XML resource, check the syntax
352             if (AdtConstants.EXT_XML.equalsIgnoreCase(ext) && kind != IResourceDelta.REMOVED) {
353                 // check xml Validity
354                 mBuilder.checkXML(resource, this);
355             }
356             // Whether or not to generate R.java for a changed resource is taken care of by the
357             // Resource Manager.
358         } else if (resource instanceof IFolder) {
359             // in this case we may be inside a folder that contains a source
360             // folder, go through the list of known source folders
361 
362             for (IPath sourceFolderPath : mSourceFolders) {
363                 // first check if they match exactly.
364                 if (sourceFolderPath.equals(path)) {
365                     // this is a source folder!
366                     mInRes = false;
367                     mSourceFolder = getFolder(sourceFolderPath); // all non null due to test above
368                     mIsGenSourceFolder = path.segmentCount() == 2 &&
369                             path.segment(1).equals(SdkConstants.FD_GEN_SOURCES);
370                     return true;
371                 }
372 
373                 // check if we are on the way to a source folder.
374                 int count = sourceFolderPath.matchingFirstSegments(path);
375                 if (count == path.segmentCount()) {
376                     mInRes = false;
377                     return true;
378                 }
379             }
380 
381             // if we're here, we are visiting another folder
382             // like /$Project/bin/ for instance (we get notified for changes
383             // in .class!)
384             // This could also be another source folder and we have found
385             // R.java in a previous source folder
386             // We don't want to visit its children
387             return false;
388         }
389 
390         return false;
391     }
392 
393     /**
394      * Returns a handle to the folder identified by the given path in this container.
395      * <p/>The different with {@link IContainer#getFolder(IPath)} is that this returns a non
396      * null object only if the resource actually exists and is a folder (and not a file)
397      * @param path the path of the folder to return.
398      * @return a handle to the folder if it exists, or null otherwise.
399      */
getFolder(IPath path)400     private IFolder getFolder(IPath path) {
401         IResource resource = mRoot.findMember(path);
402         if (resource != null && resource.exists() && resource.getType() == IResource.FOLDER) {
403             return (IFolder)resource;
404         }
405 
406         return null;
407     }
408 
409 }
410