2 * Copyright (C) 2007 The Android Open Source Project
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
8 * http://www.eclipse.org/org/documents/epl-v10.php
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.
17 package com.android.ide.eclipse.adt.internal.project;
19 import com.android.ide.eclipse.adt.AdtPlugin;
20 import com.android.ide.eclipse.adt.AndroidConstants;
21 import com.android.sdklib.SdkConstants;
22 import com.android.sdklib.xml.ManifestData;
24 import org.eclipse.core.resources.IFile;
25 import org.eclipse.core.resources.IFolder;
26 import org.eclipse.core.resources.IMarker;
27 import org.eclipse.core.resources.IProject;
28 import org.eclipse.core.resources.IProjectDescription;
29 import org.eclipse.core.resources.IResource;
30 import org.eclipse.core.resources.IWorkspace;
31 import org.eclipse.core.resources.IncrementalProjectBuilder;
32 import org.eclipse.core.resources.ResourcesPlugin;
33 import org.eclipse.core.runtime.CoreException;
34 import org.eclipse.core.runtime.IPath;
35 import org.eclipse.core.runtime.NullProgressMonitor;
36 import org.eclipse.core.runtime.Path;
37 import org.eclipse.core.runtime.QualifiedName;
38 import org.eclipse.jdt.core.IClasspathEntry;
39 import org.eclipse.jdt.core.IJavaModel;
40 import org.eclipse.jdt.core.IJavaProject;
41 import org.eclipse.jdt.core.JavaCore;
42 import org.eclipse.jdt.core.JavaModelException;
43 import org.eclipse.jdt.launching.JavaRuntime;
45 import java.util.ArrayList;
46 import java.util.List;
49 * Utility class to manipulate Project parameters/properties.
51 public final class ProjectHelper {
52 public final static int COMPILER_COMPLIANCE_OK = 0;
53 public final static int COMPILER_COMPLIANCE_LEVEL = 1;
54 public final static int COMPILER_COMPLIANCE_SOURCE = 2;
55 public final static int COMPILER_COMPLIANCE_CODEGEN_TARGET = 3;
58 * Adds the corresponding source folder to the class path entries.
60 * @param entries The class path entries to read. A copy will be returned.
61 * @param new_entry The parent source folder to remove.
62 * @return A new class path entries array.
64 public static IClasspathEntry[] addEntryToClasspath(
65 IClasspathEntry[] entries, IClasspathEntry new_entry) {
66 int n = entries.length;
67 IClasspathEntry[] newEntries = new IClasspathEntry[n + 1];
68 System.arraycopy(entries, 0, newEntries, 0, n);
69 newEntries[n] = new_entry;
74 * Adds the corresponding source folder to the project's class path entries.
76 * @param javaProject The java project of which path entries to update.
77 * @param new_entry The parent source folder to remove.
78 * @throws JavaModelException
80 public static void addEntryToClasspath(
81 IJavaProject javaProject, IClasspathEntry new_entry)
82 throws JavaModelException {
84 IClasspathEntry[] entries = javaProject.getRawClasspath();
85 entries = addEntryToClasspath(entries, new_entry);
86 javaProject.setRawClasspath(entries, new NullProgressMonitor());
90 * Remove a classpath entry from the array.
91 * @param entries The class path entries to read. A copy will be returned
92 * @param index The index to remove.
93 * @return A new class path entries array.
95 public static IClasspathEntry[] removeEntryFromClasspath(
96 IClasspathEntry[] entries, int index) {
97 int n = entries.length;
98 IClasspathEntry[] newEntries = new IClasspathEntry[n-1];
100 // copy the entries before index
101 System.arraycopy(entries, 0, newEntries, 0, index);
103 // copy the entries after index
104 System.arraycopy(entries, index + 1, newEntries, index,
105 entries.length - index - 1);
111 * Converts a OS specific path into a path valid for the java doc location
112 * attributes of a project.
113 * @param javaDocOSLocation The OS specific path.
114 * @return a valid path for the java doc location.
116 public static String getJavaDocPath(String javaDocOSLocation) {
117 // first thing we do is convert the \ into /
118 String javaDoc = javaDocOSLocation.replaceAll("\\\\", //$NON-NLS-1$
119 AndroidConstants.WS_SEP);
121 // then we add file: at the beginning for unix path, and file:/ for non
123 if (javaDoc.startsWith(AndroidConstants.WS_SEP)) {
124 return "file:" + javaDoc; //$NON-NLS-1$
127 return "file:/" + javaDoc; //$NON-NLS-1$
131 * Look for a specific classpath entry by full path and return its index.
132 * @param entries The entry array to search in.
133 * @param entryPath The OS specific path of the entry.
134 * @param entryKind The kind of the entry. Accepted values are 0
135 * (no filter), IClasspathEntry.CPE_LIBRARY, IClasspathEntry.CPE_PROJECT,
136 * IClasspathEntry.CPE_SOURCE, IClasspathEntry.CPE_VARIABLE,
137 * and IClasspathEntry.CPE_CONTAINER
138 * @return the index of the found classpath entry or -1.
140 public static int findClasspathEntryByPath(IClasspathEntry[] entries,
141 String entryPath, int entryKind) {
142 for (int i = 0 ; i < entries.length ; i++) {
143 IClasspathEntry entry = entries[i];
145 int kind = entry.getEntryKind();
147 if (kind == entryKind || entryKind == 0) {
149 IPath path = entry.getPath();
151 String osPathString = path.toOSString();
152 if (osPathString.equals(entryPath)) {
158 // not found, return bad index.
163 * Look for a specific classpath entry for file name only and return its
165 * @param entries The entry array to search in.
166 * @param entryName The filename of the entry.
167 * @param entryKind The kind of the entry. Accepted values are 0
168 * (no filter), IClasspathEntry.CPE_LIBRARY, IClasspathEntry.CPE_PROJECT,
169 * IClasspathEntry.CPE_SOURCE, IClasspathEntry.CPE_VARIABLE,
170 * and IClasspathEntry.CPE_CONTAINER
171 * @param startIndex Index where to start the search
172 * @return the index of the found classpath entry or -1.
174 public static int findClasspathEntryByName(IClasspathEntry[] entries,
175 String entryName, int entryKind, int startIndex) {
176 if (startIndex < 0) {
179 for (int i = startIndex ; i < entries.length ; i++) {
180 IClasspathEntry entry = entries[i];
182 int kind = entry.getEntryKind();
184 if (kind == entryKind || entryKind == 0) {
186 IPath path = entry.getPath();
187 String name = path.segment(path.segmentCount()-1);
189 if (name.equals(entryName)) {
195 // not found, return bad index.
200 * Fix the project. This checks the SDK location.
201 * @param project The project to fix.
202 * @throws JavaModelException
204 public static void fixProject(IProject project) throws JavaModelException {
205 if (AdtPlugin.getOsSdkFolder().length() == 0) {
206 AdtPlugin.printToConsole(project, "Unknown SDK Location, project not fixed.");
210 // get a java project
211 IJavaProject javaProject = JavaCore.create(project);
212 fixProjectClasspathEntries(javaProject);
216 * Fix the project classpath entries. The method ensures that:
218 * <li>The project does not reference any old android.zip/android.jar archive.</li>
219 * <li>The project does not use its output folder as a sourc folder.</li>
220 * <li>The project does not reference a desktop JRE</li>
221 * <li>The project references the AndroidClasspathContainer.
223 * @param javaProject The project to fix.
224 * @throws JavaModelException
226 public static void fixProjectClasspathEntries(IJavaProject javaProject)
227 throws JavaModelException {
229 // get the project classpath
230 IClasspathEntry[] entries = javaProject.getRawClasspath();
231 IClasspathEntry[] oldEntries = entries;
233 // check if the JRE is set as library
234 int jreIndex = ProjectHelper.findClasspathEntryByPath(entries, JavaRuntime.JRE_CONTAINER,
235 IClasspathEntry.CPE_CONTAINER);
236 if (jreIndex != -1) {
237 // the project has a JRE included, we remove it
238 entries = ProjectHelper.removeEntryFromClasspath(entries, jreIndex);
241 // get the output folder
242 IPath outputFolder = javaProject.getOutputLocation();
244 boolean foundContainer = false;
246 for (int i = 0 ; i < entries.length ;) {
247 // get the entry and kind
248 IClasspathEntry entry = entries[i];
249 int kind = entry.getEntryKind();
251 if (kind == IClasspathEntry.CPE_SOURCE) {
252 IPath path = entry.getPath();
254 if (path.equals(outputFolder)) {
255 entries = ProjectHelper.removeEntryFromClasspath(entries, i);
257 // continue, to skip the i++;
260 } else if (kind == IClasspathEntry.CPE_CONTAINER) {
261 if (AndroidClasspathContainerInitializer.checkPath(entry.getPath())) {
262 foundContainer = true;
269 // if the framework container is not there, we add it
270 if (foundContainer == false) {
271 // add the android container to the array
272 entries = ProjectHelper.addEntryToClasspath(entries,
273 AndroidClasspathContainerInitializer.getContainerEntry());
276 // set the new list of entries to the project
277 if (entries != oldEntries) {
278 javaProject.setRawClasspath(entries, new NullProgressMonitor());
281 // If needed, check and fix compiler compliance and source compatibility
282 ProjectHelper.checkAndFixCompilerCompliance(javaProject);
287 * Checks the project compiler compliance level is supported.
288 * @param javaProject The project to check
290 * <li><code>COMPILER_COMPLIANCE_OK</code> if the project is properly configured</li>
291 * <li><code>COMPILER_COMPLIANCE_LEVEL</code> for unsupported compiler level</li>
292 * <li><code>COMPILER_COMPLIANCE_SOURCE</code> for unsupported source compatibility</li>
293 * <li><code>COMPILER_COMPLIANCE_CODEGEN_TARGET</code> for unsupported .class format</li>
296 public static final int checkCompilerCompliance(IJavaProject javaProject) {
297 // get the project compliance level option
298 String compliance = javaProject.getOption(JavaCore.COMPILER_COMPLIANCE, true);
300 // check it against a list of valid compliance level strings.
301 if (checkCompliance(compliance) == false) {
302 // if we didn't find the proper compliance level, we return an error
303 return COMPILER_COMPLIANCE_LEVEL;
306 // otherwise we check source compatibility
307 String source = javaProject.getOption(JavaCore.COMPILER_SOURCE, true);
309 // check it against a list of valid compliance level strings.
310 if (checkCompliance(source) == false) {
311 // if we didn't find the proper compliance level, we return an error
312 return COMPILER_COMPLIANCE_SOURCE;
315 // otherwise check codegen level
316 String codeGen = javaProject.getOption(JavaCore.COMPILER_CODEGEN_TARGET_PLATFORM, true);
318 // check it against a list of valid compliance level strings.
319 if (checkCompliance(codeGen) == false) {
320 // if we didn't find the proper compliance level, we return an error
321 return COMPILER_COMPLIANCE_CODEGEN_TARGET;
324 return COMPILER_COMPLIANCE_OK;
328 * Checks the project compiler compliance level is supported.
329 * @param project The project to check
331 * <li><code>COMPILER_COMPLIANCE_OK</code> if the project is properly configured</li>
332 * <li><code>COMPILER_COMPLIANCE_LEVEL</code> for unsupported compiler level</li>
333 * <li><code>COMPILER_COMPLIANCE_SOURCE</code> for unsupported source compatibility</li>
334 * <li><code>COMPILER_COMPLIANCE_CODEGEN_TARGET</code> for unsupported .class format</li>
337 public static final int checkCompilerCompliance(IProject project) {
338 // get the java project from the IProject resource object
339 IJavaProject javaProject = JavaCore.create(project);
341 // check and return the result.
342 return checkCompilerCompliance(javaProject);
347 * Checks, and fixes if needed, the compiler compliance level, and the source compatibility
349 * @param project The project to check and fix.
351 public static final void checkAndFixCompilerCompliance(IProject project) {
352 // get the java project from the IProject resource object
353 IJavaProject javaProject = JavaCore.create(project);
355 // Now we check the compiler compliance level and make sure it is valid
356 checkAndFixCompilerCompliance(javaProject);
360 * Checks, and fixes if needed, the compiler compliance level, and the source compatibility
362 * @param javaProject The Java project to check and fix.
364 public static final void checkAndFixCompilerCompliance(IJavaProject javaProject) {
365 if (checkCompilerCompliance(javaProject) != COMPILER_COMPLIANCE_OK) {
366 // setup the preferred compiler compliance level.
367 javaProject.setOption(JavaCore.COMPILER_COMPLIANCE,
368 AndroidConstants.COMPILER_COMPLIANCE_PREFERRED);
369 javaProject.setOption(JavaCore.COMPILER_SOURCE,
370 AndroidConstants.COMPILER_COMPLIANCE_PREFERRED);
371 javaProject.setOption(JavaCore.COMPILER_CODEGEN_TARGET_PLATFORM,
372 AndroidConstants.COMPILER_COMPLIANCE_PREFERRED);
374 // clean the project to make sure we recompile
376 javaProject.getProject().build(IncrementalProjectBuilder.CLEAN_BUILD,
377 new NullProgressMonitor());
378 } catch (CoreException e) {
379 AdtPlugin.printErrorToConsole(javaProject.getProject(),
380 "Project compiler settings changed. Clean your project.");
386 * Returns a {@link IProject} by its running application name, as it returned by the AVD.
388 * <var>applicationName</var> will in most case be the package declared in the manifest, but
389 * can, in some cases, be a custom process name declared in the manifest, in the
390 * <code>application</code>, <code>activity</code>, <code>receiver</code>, or
391 * <code>service</code> nodes.
392 * @param applicationName The application name.
393 * @return a project or <code>null</code> if no matching project were found.
395 public static IProject findAndroidProjectByAppName(String applicationName) {
396 // Get the list of project for the current workspace
397 IWorkspace workspace = ResourcesPlugin.getWorkspace();
398 IProject[] projects = workspace.getRoot().getProjects();
400 // look for a project that matches the packageName of the app
401 // we're trying to debug
402 for (IProject p : projects) {
405 if (p.hasNature(AndroidConstants.NATURE_DEFAULT) == false) {
406 // ignore non android projects
409 } catch (CoreException e) {
410 // failed to get the nature? skip project.
414 // check that there is indeed a manifest file.
415 IFile manifestFile = getManifest(p);
416 if (manifestFile == null) {
417 // no file? skip this project.
421 ManifestData data = AndroidManifestHelper.parseForData(manifestFile);
423 // skip this project.
427 String manifestPackage = data.getPackage();
429 if (manifestPackage != null && manifestPackage.equals(applicationName)) {
430 // this is the project we were looking for!
433 // if the package and application name don't match,
434 // we look for other possible process names declared in the manifest.
435 String[] processes = data.getProcesses();
436 for (String process : processes) {
437 if (process.equals(applicationName)) {
449 public static void fixProjectNatureOrder(IProject project) throws CoreException {
450 IProjectDescription description = project.getDescription();
451 String[] natures = description.getNatureIds();
453 // if the android nature is not the first one, we reorder them
454 if (AndroidConstants.NATURE_DEFAULT.equals(natures[0]) == false) {
455 // look for the index
456 for (int i = 0 ; i < natures.length ; i++) {
457 if (AndroidConstants.NATURE_DEFAULT.equals(natures[i])) {
458 // if we try to just reorder the array in one pass, this doesn't do
459 // anything. I guess JDT check that we are actually adding/removing nature.
460 // So, first we'll remove the android nature, and then add it back.
462 // remove the android nature
463 removeNature(project, AndroidConstants.NATURE_DEFAULT);
465 // now add it back at the first index.
466 description = project.getDescription();
467 natures = description.getNatureIds();
469 String[] newNatures = new String[natures.length + 1];
471 // first one is android
472 newNatures[0] = AndroidConstants.NATURE_DEFAULT;
474 // next the rest that was before the android nature
475 System.arraycopy(natures, 0, newNatures, 1, natures.length);
477 // set the new natures
478 description.setNatureIds(newNatures);
479 project.setDescription(description, null);
490 * Removes a specific nature from a project.
491 * @param project The project to remove the nature from.
492 * @param nature The nature id to remove.
493 * @throws CoreException
495 public static void removeNature(IProject project, String nature) throws CoreException {
496 IProjectDescription description = project.getDescription();
497 String[] natures = description.getNatureIds();
499 // check if the project already has the android nature.
500 for (int i = 0; i < natures.length; ++i) {
501 if (nature.equals(natures[i])) {
502 String[] newNatures = new String[natures.length - 1];
504 System.arraycopy(natures, 0, newNatures, 0, i);
506 System.arraycopy(natures, i + 1, newNatures, i, natures.length - i - 1);
507 description.setNatureIds(newNatures);
508 project.setDescription(description, null);
517 * Returns if the project has error level markers.
518 * @param includeReferencedProjects flag to also test the referenced projects.
519 * @throws CoreException
521 public static boolean hasError(IProject project, boolean includeReferencedProjects)
522 throws CoreException {
523 IMarker[] markers = project.findMarkers(IMarker.PROBLEM, true, IResource.DEPTH_INFINITE);
524 if (markers != null && markers.length > 0) {
525 // the project has marker(s). even though they are "problem" we
526 // don't know their severity. so we loop on them and figure if they
527 // are warnings or errors
528 for (IMarker m : markers) {
529 int s = m.getAttribute(IMarker.SEVERITY, -1);
530 if (s == IMarker.SEVERITY_ERROR) {
536 // test the referenced projects if needed.
537 if (includeReferencedProjects) {
538 IProject[] projects = getReferencedProjects(project);
540 for (IProject p : projects) {
541 if (hasError(p, false)) {
551 * Saves a String property into the persistent storage of a resource.
552 * @param resource The resource into which the string value is saved.
553 * @param propertyName the name of the property. The id of the plug-in is added to this string.
554 * @param value the value to save
555 * @return true if the save succeeded.
557 public static boolean saveStringProperty(IResource resource, String propertyName,
559 QualifiedName qname = new QualifiedName(AdtPlugin.PLUGIN_ID, propertyName);
562 resource.setPersistentProperty(qname, value);
563 } catch (CoreException e) {
571 * Loads a String property from the persistent storage of a resource.
572 * @param resource The resource from which the string value is loaded.
573 * @param propertyName the name of the property. The id of the plug-in is added to this string.
574 * @return the property value or null if it was not found.
576 public static String loadStringProperty(IResource resource, String propertyName) {
577 QualifiedName qname = new QualifiedName(AdtPlugin.PLUGIN_ID, propertyName);
580 String value = resource.getPersistentProperty(qname);
582 } catch (CoreException e) {
588 * Saves a property into the persistent storage of a resource.
589 * @param resource The resource into which the boolean value is saved.
590 * @param propertyName the name of the property. The id of the plug-in is added to this string.
591 * @param value the value to save
592 * @return true if the save succeeded.
594 public static boolean saveBooleanProperty(IResource resource, String propertyName,
596 return saveStringProperty(resource, propertyName, Boolean.toString(value));
600 * Loads a boolean property from the persistent storage of a resource.
601 * @param resource The resource from which the boolean value is loaded.
602 * @param propertyName the name of the property. The id of the plug-in is added to this string.
603 * @param defaultValue The default value to return if the property was not found.
604 * @return the property value or the default value if the property was not found.
606 public static boolean loadBooleanProperty(IResource resource, String propertyName,
607 boolean defaultValue) {
608 String value = loadStringProperty(resource, propertyName);
610 return Boolean.parseBoolean(value);
617 * Saves the path of a resource into the persistent storage of a resource.
618 * @param resource The resource into which the resource path is saved.
619 * @param propertyName the name of the property. The id of the plug-in is added to this string.
620 * @param value The resource to save. It's its path that is actually stored. If null, an
621 * empty string is stored.
622 * @return true if the save succeeded
624 public static boolean saveResourceProperty(IResource resource, String propertyName,
627 IPath iPath = value.getFullPath();
628 return saveStringProperty(resource, propertyName, iPath.toString());
631 return saveStringProperty(resource, propertyName, ""); //$NON-NLS-1$
635 * Loads the path of a resource from the persistent storage of a resource, and returns the
636 * corresponding IResource object.
637 * @param resource The resource from which the resource path is loaded.
638 * @param propertyName the name of the property. The id of the plug-in is added to this string.
639 * @return The corresponding IResource object (or children interface) or null
641 public static IResource loadResourceProperty(IResource resource, String propertyName) {
642 String value = loadStringProperty(resource, propertyName);
644 if (value != null && value.length() > 0) {
645 return ResourcesPlugin.getWorkspace().getRoot().findMember(new Path(value));
652 * Returns the list of referenced project that are opened and Java projects.
654 * @return list of opened referenced java project.
655 * @throws CoreException
657 public static IProject[] getReferencedProjects(IProject project) throws CoreException {
658 IProject[] projects = project.getReferencedProjects();
660 ArrayList<IProject> list = new ArrayList<IProject>();
662 for (IProject p : projects) {
663 if (p.isOpen() && p.hasNature(JavaCore.NATURE_ID)) {
668 return list.toArray(new IProject[list.size()]);
673 * Checks a Java project compiler level option against a list of supported versions.
674 * @param optionValue the Compiler level option.
675 * @return true if the option value is supproted.
677 private static boolean checkCompliance(String optionValue) {
678 for (String s : AndroidConstants.COMPILER_COMPLIANCE) {
679 if (s != null && s.equals(optionValue)) {
688 * Returns the apk filename for the given project
689 * @param project The project.
690 * @param config An optional config name. Can be null.
692 public static String getApkFilename(IProject project, String config) {
693 if (config != null) {
694 return project.getName() + "-" + config + AndroidConstants.DOT_ANDROID_PACKAGE; //$NON-NLS-1$
697 return project.getName() + AndroidConstants.DOT_ANDROID_PACKAGE;
701 * Find the list of projects on which this JavaProject is dependent on at the compilation level.
703 * @param javaProject Java project that we are looking for the dependencies.
704 * @return A list of Java projects for which javaProject depend on.
705 * @throws JavaModelException
707 public static List<IJavaProject> getAndroidProjectDependencies(IJavaProject javaProject)
708 throws JavaModelException {
709 String[] requiredProjectNames = javaProject.getRequiredProjectNames();
711 // Go from java project name to JavaProject name
712 IJavaModel javaModel = javaProject.getJavaModel();
714 // loop through all dependent projects and keep only those that are Android projects
715 List<IJavaProject> projectList = new ArrayList<IJavaProject>(requiredProjectNames.length);
716 for (String javaProjectName : requiredProjectNames) {
717 IJavaProject androidJavaProject = javaModel.getJavaProject(javaProjectName);
719 //Verify that the project has also the Android Nature
721 if (!androidJavaProject.getProject().hasNature(AndroidConstants.NATURE_DEFAULT)) {
724 } catch (CoreException e) {
728 projectList.add(androidJavaProject);
735 * Returns the android package file as an IFile object for the specified
737 * @param project The project
738 * @return The android package as an IFile object or null if not found.
740 public static IFile getApplicationPackage(IProject project) {
741 // get the output folder
742 IFolder outputLocation = BaseProjectHelper.getOutputFolder(project);
744 if (outputLocation == null) {
745 AdtPlugin.printErrorToConsole(project,
746 "Failed to get the output location of the project. Check build path properties"
752 // get the package path
753 String packageName = project.getName() + AndroidConstants.DOT_ANDROID_PACKAGE;
754 IResource r = outputLocation.findMember(packageName);
756 // check the package is present
757 if (r instanceof IFile && r.exists()) {
761 String msg = String.format("Could not find %1$s!", packageName);
762 AdtPlugin.printErrorToConsole(project, msg);
768 * Returns an {@link IFile} object representing the manifest for the given project.
770 * @param project The project containing the manifest file.
771 * @return An IFile object pointing to the manifest or null if the manifest
774 public static IFile getManifest(IProject project) {
775 IResource r = project.findMember(AndroidConstants.WS_SEP
776 + SdkConstants.FN_ANDROID_MANIFEST_XML);
778 if (r == null || r.exists() == false || (r instanceof IFile) == false) {