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