diff options
Diffstat (limited to 'eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/ProjectHelper.java')
-rw-r--r-- | eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/ProjectHelper.java | 1153 |
1 files changed, 1153 insertions, 0 deletions
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/ProjectHelper.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/ProjectHelper.java new file mode 100644 index 000000000..a32b4ca8b --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/ProjectHelper.java @@ -0,0 +1,1153 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Eclipse Public License, Version 1.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.eclipse.org/org/documents/epl-v10.php + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.ide.eclipse.adt.internal.project; + +import static com.android.ide.eclipse.adt.AdtConstants.COMPILER_COMPLIANCE_PREFERRED; + +import com.android.SdkConstants; +import com.android.annotations.NonNull; +import com.android.ide.common.xml.ManifestData; +import com.android.ide.eclipse.adt.AdtConstants; +import com.android.ide.eclipse.adt.AdtPlugin; +import com.android.ide.eclipse.adt.internal.build.builders.PostCompilerBuilder; +import com.android.ide.eclipse.adt.internal.build.builders.PreCompilerBuilder; +import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs; +import com.android.ide.eclipse.adt.internal.sdk.ProjectState; +import com.android.ide.eclipse.adt.internal.sdk.Sdk; +import com.android.sdklib.BuildToolInfo; +import com.android.sdklib.IAndroidTarget; +import com.android.utils.Pair; + +import org.eclipse.core.resources.ICommand; +import org.eclipse.core.resources.IFile; +import org.eclipse.core.resources.IFolder; +import org.eclipse.core.resources.IMarker; +import org.eclipse.core.resources.IProject; +import org.eclipse.core.resources.IProjectDescription; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.resources.IWorkspace; +import org.eclipse.core.resources.IncrementalProjectBuilder; +import org.eclipse.core.resources.ResourcesPlugin; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IPath; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.NullProgressMonitor; +import org.eclipse.core.runtime.Path; +import org.eclipse.core.runtime.QualifiedName; +import org.eclipse.jdt.core.IClasspathEntry; +import org.eclipse.jdt.core.IJavaModel; +import org.eclipse.jdt.core.IJavaProject; +import org.eclipse.jdt.core.JavaCore; +import org.eclipse.jdt.core.JavaModelException; +import org.eclipse.jdt.internal.corext.util.JavaModelUtil; +import org.eclipse.jdt.launching.IVMInstall; +import org.eclipse.jdt.launching.IVMInstall2; +import org.eclipse.jdt.launching.IVMInstallType; +import org.eclipse.jdt.launching.JavaRuntime; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; + +/** + * Utility class to manipulate Project parameters/properties. + */ +public final class ProjectHelper { + public final static int COMPILER_COMPLIANCE_OK = 0; + public final static int COMPILER_COMPLIANCE_LEVEL = 1; + public final static int COMPILER_COMPLIANCE_SOURCE = 2; + public final static int COMPILER_COMPLIANCE_CODEGEN_TARGET = 3; + + /** + * Adds the given ClasspathEntry object to the class path entries. + * This method does not check whether the entry is already defined in the project. + * + * @param entries The class path entries to read. A copy will be returned. + * @param newEntry The new class path entry to add. + * @return A new class path entries array. + */ + public static IClasspathEntry[] addEntryToClasspath( + IClasspathEntry[] entries, IClasspathEntry newEntry) { + int n = entries.length; + IClasspathEntry[] newEntries = new IClasspathEntry[n + 1]; + System.arraycopy(entries, 0, newEntries, 0, n); + newEntries[n] = newEntry; + return newEntries; + } + + /** + * Replaces the given ClasspathEntry in the classpath entries. + * + * If the classpath does not yet exists (Check is based on entry path), then it is added. + * + * @param entries The class path entries to read. The same array (replace) or a copy (add) + * will be returned. + * @param newEntry The new class path entry to add. + * @return The same array (replace) or a copy (add) will be returned. + * + * @see IClasspathEntry#getPath() + */ + public static IClasspathEntry[] replaceEntryInClasspath( + IClasspathEntry[] entries, IClasspathEntry newEntry) { + + IPath path = newEntry.getPath(); + for (int i = 0, count = entries.length; i < count ; i++) { + if (path.equals(entries[i].getPath())) { + entries[i] = newEntry; + return entries; + } + } + + return addEntryToClasspath(entries, newEntry); + } + + /** + * Adds the corresponding source folder to the project's class path entries. + * This method does not check whether the entry is already defined in the project. + * + * @param javaProject The java project of which path entries to update. + * @param newEntry The new class path entry to add. + * @throws JavaModelException + */ + public static void addEntryToClasspath(IJavaProject javaProject, IClasspathEntry newEntry) + throws JavaModelException { + + IClasspathEntry[] entries = javaProject.getRawClasspath(); + entries = addEntryToClasspath(entries, newEntry); + javaProject.setRawClasspath(entries, new NullProgressMonitor()); + } + + /** + * Checks whether the given class path entry is already defined in the project. + * + * @param javaProject The java project of which path entries to check. + * @param newEntry The parent source folder to remove. + * @return True if the class path entry is already defined. + * @throws JavaModelException + */ + public static boolean isEntryInClasspath(IJavaProject javaProject, IClasspathEntry newEntry) + throws JavaModelException { + + IClasspathEntry[] entries = javaProject.getRawClasspath(); + for (IClasspathEntry entry : entries) { + if (entry.equals(newEntry)) { + return true; + } + } + return false; + } + + /** + * Remove a classpath entry from the array. + * @param entries The class path entries to read. A copy will be returned + * @param index The index to remove. + * @return A new class path entries array. + */ + public static IClasspathEntry[] removeEntryFromClasspath( + IClasspathEntry[] entries, int index) { + int n = entries.length; + IClasspathEntry[] newEntries = new IClasspathEntry[n-1]; + + // copy the entries before index + System.arraycopy(entries, 0, newEntries, 0, index); + + // copy the entries after index + System.arraycopy(entries, index + 1, newEntries, index, + entries.length - index - 1); + + return newEntries; + } + + /** + * Converts a OS specific path into a path valid for the java doc location + * attributes of a project. + * @param javaDocOSLocation The OS specific path. + * @return a valid path for the java doc location. + */ + public static String getJavaDocPath(String javaDocOSLocation) { + // first thing we do is convert the \ into / + String javaDoc = javaDocOSLocation.replaceAll("\\\\", //$NON-NLS-1$ + AdtConstants.WS_SEP); + + // then we add file: at the beginning for unix path, and file:/ for non + // unix path + if (javaDoc.startsWith(AdtConstants.WS_SEP)) { + return "file:" + javaDoc; //$NON-NLS-1$ + } + + return "file:/" + javaDoc; //$NON-NLS-1$ + } + + /** + * Look for a specific classpath entry by full path and return its index. + * @param entries The entry array to search in. + * @param entryPath The OS specific path of the entry. + * @param entryKind The kind of the entry. Accepted values are 0 + * (no filter), IClasspathEntry.CPE_LIBRARY, IClasspathEntry.CPE_PROJECT, + * IClasspathEntry.CPE_SOURCE, IClasspathEntry.CPE_VARIABLE, + * and IClasspathEntry.CPE_CONTAINER + * @return the index of the found classpath entry or -1. + */ + public static int findClasspathEntryByPath(IClasspathEntry[] entries, + String entryPath, int entryKind) { + for (int i = 0 ; i < entries.length ; i++) { + IClasspathEntry entry = entries[i]; + + int kind = entry.getEntryKind(); + + if (kind == entryKind || entryKind == 0) { + // get the path + IPath path = entry.getPath(); + + String osPathString = path.toOSString(); + if (osPathString.equals(entryPath)) { + return i; + } + } + } + + // not found, return bad index. + return -1; + } + + /** + * Look for a specific classpath entry for file name only and return its + * index. + * @param entries The entry array to search in. + * @param entryName The filename of the entry. + * @param entryKind The kind of the entry. Accepted values are 0 + * (no filter), IClasspathEntry.CPE_LIBRARY, IClasspathEntry.CPE_PROJECT, + * IClasspathEntry.CPE_SOURCE, IClasspathEntry.CPE_VARIABLE, + * and IClasspathEntry.CPE_CONTAINER + * @param startIndex Index where to start the search + * @return the index of the found classpath entry or -1. + */ + public static int findClasspathEntryByName(IClasspathEntry[] entries, + String entryName, int entryKind, int startIndex) { + if (startIndex < 0) { + startIndex = 0; + } + for (int i = startIndex ; i < entries.length ; i++) { + IClasspathEntry entry = entries[i]; + + int kind = entry.getEntryKind(); + + if (kind == entryKind || entryKind == 0) { + // get the path + IPath path = entry.getPath(); + String name = path.segment(path.segmentCount()-1); + + if (name.equals(entryName)) { + return i; + } + } + } + + // not found, return bad index. + return -1; + } + + public static boolean updateProject(IJavaProject project) { + return updateProjects(new IJavaProject[] { project}); + } + + /** + * Update the android-specific projects's classpath containers. + * @param projects the projects to update + * @return + */ + public static boolean updateProjects(IJavaProject[] projects) { + boolean r = AndroidClasspathContainerInitializer.updateProjects(projects); + if (r) { + return LibraryClasspathContainerInitializer.updateProjects(projects); + } + return false; + } + + /** + * Fix the project. This checks the SDK location. + * @param project The project to fix. + * @throws JavaModelException + */ + public static void fixProject(IProject project) throws JavaModelException { + if (AdtPlugin.getOsSdkFolder().length() == 0) { + AdtPlugin.printToConsole(project, "Unknown SDK Location, project not fixed."); + return; + } + + // get a java project + IJavaProject javaProject = JavaCore.create(project); + fixProjectClasspathEntries(javaProject); + } + + /** + * Fix the project classpath entries. The method ensures that: + * <ul> + * <li>The project does not reference any old android.zip/android.jar archive.</li> + * <li>The project does not use its output folder as a sourc folder.</li> + * <li>The project does not reference a desktop JRE</li> + * <li>The project references the AndroidClasspathContainer. + * </ul> + * @param javaProject The project to fix. + * @throws JavaModelException + */ + public static void fixProjectClasspathEntries(IJavaProject javaProject) + throws JavaModelException { + + // get the project classpath + IClasspathEntry[] entries = javaProject.getRawClasspath(); + IClasspathEntry[] oldEntries = entries; + boolean forceRewriteOfCPE = false; + + // check if the JRE is set as library + int jreIndex = ProjectHelper.findClasspathEntryByPath(entries, JavaRuntime.JRE_CONTAINER, + IClasspathEntry.CPE_CONTAINER); + if (jreIndex != -1) { + // the project has a JRE included, we remove it + entries = ProjectHelper.removeEntryFromClasspath(entries, jreIndex); + } + + // get the output folder + IPath outputFolder = javaProject.getOutputLocation(); + + boolean foundFrameworkContainer = false; + IClasspathEntry foundLibrariesContainer = null; + IClasspathEntry foundDependenciesContainer = null; + + for (int i = 0 ; i < entries.length ;) { + // get the entry and kind + IClasspathEntry entry = entries[i]; + int kind = entry.getEntryKind(); + + if (kind == IClasspathEntry.CPE_SOURCE) { + IPath path = entry.getPath(); + + if (path.equals(outputFolder)) { + entries = ProjectHelper.removeEntryFromClasspath(entries, i); + + // continue, to skip the i++; + continue; + } + } else if (kind == IClasspathEntry.CPE_CONTAINER) { + String path = entry.getPath().toString(); + if (AdtConstants.CONTAINER_FRAMEWORK.equals(path)) { + foundFrameworkContainer = true; + } else if (AdtConstants.CONTAINER_PRIVATE_LIBRARIES.equals(path)) { + foundLibrariesContainer = entry; + } else if (AdtConstants.CONTAINER_DEPENDENCIES.equals(path)) { + foundDependenciesContainer = entry; + } + } + + i++; + } + + // look to see if we have the m2eclipse nature + boolean m2eNature = false; + try { + m2eNature = javaProject.getProject().hasNature("org.eclipse.m2e.core.maven2Nature"); + } catch (CoreException e) { + AdtPlugin.log(e, "Failed to query project %s for m2e nature", + javaProject.getProject().getName()); + } + + + // if the framework container is not there, we add it + if (!foundFrameworkContainer) { + // add the android container to the array + entries = ProjectHelper.addEntryToClasspath(entries, + JavaCore.newContainerEntry(new Path(AdtConstants.CONTAINER_FRAMEWORK))); + } + + // same thing for the library container + if (foundLibrariesContainer == null) { + // add the exported libraries android container to the array + entries = ProjectHelper.addEntryToClasspath(entries, + JavaCore.newContainerEntry( + new Path(AdtConstants.CONTAINER_PRIVATE_LIBRARIES), true)); + } else if (!m2eNature && !foundLibrariesContainer.isExported()) { + // the container is present but it's not exported and since there's no m2e nature + // we do want it to be exported. + // keep all the other parameters the same. + entries = ProjectHelper.replaceEntryInClasspath(entries, + JavaCore.newContainerEntry( + new Path(AdtConstants.CONTAINER_PRIVATE_LIBRARIES), + foundLibrariesContainer.getAccessRules(), + foundLibrariesContainer.getExtraAttributes(), + true)); + forceRewriteOfCPE = true; + } + + // same thing for the dependencies container + if (foundDependenciesContainer == null) { + // add the android dependencies container to the array + entries = ProjectHelper.addEntryToClasspath(entries, + JavaCore.newContainerEntry( + new Path(AdtConstants.CONTAINER_DEPENDENCIES), true)); + } else if (!m2eNature && !foundDependenciesContainer.isExported()) { + // the container is present but it's not exported and since there's no m2e nature + // we do want it to be exported. + // keep all the other parameters the same. + entries = ProjectHelper.replaceEntryInClasspath(entries, + JavaCore.newContainerEntry( + new Path(AdtConstants.CONTAINER_DEPENDENCIES), + foundDependenciesContainer.getAccessRules(), + foundDependenciesContainer.getExtraAttributes(), + true)); + forceRewriteOfCPE = true; + } + + // set the new list of entries to the project + if (entries != oldEntries || forceRewriteOfCPE) { + javaProject.setRawClasspath(entries, new NullProgressMonitor()); + } + + // If needed, check and fix compiler compliance and source compatibility + ProjectHelper.checkAndFixCompilerCompliance(javaProject); + } + + + /** + * Checks the project compiler compliance level is supported. + * @param javaProject The project to check + * @return A pair with the first integer being an error code, and the second value + * being the invalid value found or null. The error code can be: <ul> + * <li><code>COMPILER_COMPLIANCE_OK</code> if the project is properly configured</li> + * <li><code>COMPILER_COMPLIANCE_LEVEL</code> for unsupported compiler level</li> + * <li><code>COMPILER_COMPLIANCE_SOURCE</code> for unsupported source compatibility</li> + * <li><code>COMPILER_COMPLIANCE_CODEGEN_TARGET</code> for unsupported .class format</li> + * </ul> + */ + public static final Pair<Integer, String> checkCompilerCompliance(IJavaProject javaProject) { + // get the project compliance level option + String compliance = javaProject.getOption(JavaCore.COMPILER_COMPLIANCE, true); + + // check it against a list of valid compliance level strings. + if (!checkCompliance(javaProject, compliance)) { + // if we didn't find the proper compliance level, we return an error + return Pair.of(COMPILER_COMPLIANCE_LEVEL, compliance); + } + + // otherwise we check source compatibility + String source = javaProject.getOption(JavaCore.COMPILER_SOURCE, true); + + // check it against a list of valid compliance level strings. + if (!checkCompliance(javaProject, source)) { + // if we didn't find the proper compliance level, we return an error + return Pair.of(COMPILER_COMPLIANCE_SOURCE, source); + } + + // otherwise check codegen level + String codeGen = javaProject.getOption(JavaCore.COMPILER_CODEGEN_TARGET_PLATFORM, true); + + // check it against a list of valid compliance level strings. + if (!checkCompliance(javaProject, codeGen)) { + // if we didn't find the proper compliance level, we return an error + return Pair.of(COMPILER_COMPLIANCE_CODEGEN_TARGET, codeGen); + } + + return Pair.of(COMPILER_COMPLIANCE_OK, null); + } + + /** + * Checks the project compiler compliance level is supported. + * @param project The project to check + * @return A pair with the first integer being an error code, and the second value + * being the invalid value found or null. The error code can be: <ul> + * <li><code>COMPILER_COMPLIANCE_OK</code> if the project is properly configured</li> + * <li><code>COMPILER_COMPLIANCE_LEVEL</code> for unsupported compiler level</li> + * <li><code>COMPILER_COMPLIANCE_SOURCE</code> for unsupported source compatibility</li> + * <li><code>COMPILER_COMPLIANCE_CODEGEN_TARGET</code> for unsupported .class format</li> + * </ul> + */ + public static final Pair<Integer, String> checkCompilerCompliance(IProject project) { + // get the java project from the IProject resource object + IJavaProject javaProject = JavaCore.create(project); + + // check and return the result. + return checkCompilerCompliance(javaProject); + } + + + /** + * Checks, and fixes if needed, the compiler compliance level, and the source compatibility + * level + * @param project The project to check and fix. + */ + public static final void checkAndFixCompilerCompliance(IProject project) { + // FIXME This method is never used. Shall we just removed it? + // {@link #checkAndFixCompilerCompliance(IJavaProject)} is used instead. + + // get the java project from the IProject resource object + IJavaProject javaProject = JavaCore.create(project); + + // Now we check the compiler compliance level and make sure it is valid + checkAndFixCompilerCompliance(javaProject); + } + + /** + * Checks, and fixes if needed, the compiler compliance level, and the source compatibility + * level + * @param javaProject The Java project to check and fix. + */ + public static final void checkAndFixCompilerCompliance(IJavaProject javaProject) { + Pair<Integer, String> result = checkCompilerCompliance(javaProject); + if (result.getFirst().intValue() != COMPILER_COMPLIANCE_OK) { + // setup the preferred compiler compliance level. + javaProject.setOption(JavaCore.COMPILER_COMPLIANCE, + AdtConstants.COMPILER_COMPLIANCE_PREFERRED); + javaProject.setOption(JavaCore.COMPILER_SOURCE, + AdtConstants.COMPILER_COMPLIANCE_PREFERRED); + javaProject.setOption(JavaCore.COMPILER_CODEGEN_TARGET_PLATFORM, + AdtConstants.COMPILER_COMPLIANCE_PREFERRED); + + // clean the project to make sure we recompile + try { + javaProject.getProject().build(IncrementalProjectBuilder.CLEAN_BUILD, + new NullProgressMonitor()); + } catch (CoreException e) { + AdtPlugin.printErrorToConsole(javaProject.getProject(), + "Project compiler settings changed. Clean your project."); + } + } + } + + /** + * Makes the given project use JDK 6 (or more specifically, + * {@link AdtConstants#COMPILER_COMPLIANCE_PREFERRED} as the compilation + * target, regardless of what the default IDE JDK level is, provided a JRE + * of the given level is installed. + * + * @param javaProject the Java project + * @throws CoreException if the IDE throws an exception setting the compiler + * level + */ + @SuppressWarnings("restriction") // JDT API for setting compliance options + public static void enforcePreferredCompilerCompliance(@NonNull IJavaProject javaProject) + throws CoreException { + String compliance = javaProject.getOption(JavaCore.COMPILER_COMPLIANCE, true); + if (compliance == null || + JavaModelUtil.isVersionLessThan(compliance, COMPILER_COMPLIANCE_PREFERRED)) { + IVMInstallType[] types = JavaRuntime.getVMInstallTypes(); + for (int i = 0; i < types.length; i++) { + IVMInstallType type = types[i]; + IVMInstall[] installs = type.getVMInstalls(); + for (int j = 0; j < installs.length; j++) { + IVMInstall install = installs[j]; + if (install instanceof IVMInstall2) { + IVMInstall2 install2 = (IVMInstall2) install; + // Java version can be 1.6.0, and preferred is 1.6 + if (install2.getJavaVersion().startsWith(COMPILER_COMPLIANCE_PREFERRED)) { + Map<String, String> options = javaProject.getOptions(false); + JavaCore.setComplianceOptions(COMPILER_COMPLIANCE_PREFERRED, options); + JavaModelUtil.setDefaultClassfileOptions(options, + COMPILER_COMPLIANCE_PREFERRED); + javaProject.setOptions(options); + return; + } + } + } + } + } + } + + /** + * Returns a {@link IProject} by its running application name, as it returned by the AVD. + * <p/> + * <var>applicationName</var> will in most case be the package declared in the manifest, but + * can, in some cases, be a custom process name declared in the manifest, in the + * <code>application</code>, <code>activity</code>, <code>receiver</code>, or + * <code>service</code> nodes. + * @param applicationName The application name. + * @return a project or <code>null</code> if no matching project were found. + */ + public static IProject findAndroidProjectByAppName(String applicationName) { + // Get the list of project for the current workspace + IWorkspace workspace = ResourcesPlugin.getWorkspace(); + IProject[] projects = workspace.getRoot().getProjects(); + + // look for a project that matches the packageName of the app + // we're trying to debug + for (IProject p : projects) { + if (p.isOpen()) { + try { + if (p.hasNature(AdtConstants.NATURE_DEFAULT) == false) { + // ignore non android projects + continue; + } + } catch (CoreException e) { + // failed to get the nature? skip project. + continue; + } + + // check that there is indeed a manifest file. + IFile manifestFile = getManifest(p); + if (manifestFile == null) { + // no file? skip this project. + continue; + } + + ManifestData data = AndroidManifestHelper.parseForData(manifestFile); + if (data == null) { + // skip this project. + continue; + } + + String manifestPackage = data.getPackage(); + + if (manifestPackage != null && manifestPackage.equals(applicationName)) { + // this is the project we were looking for! + return p; + } else { + // if the package and application name don't match, + // we look for other possible process names declared in the manifest. + String[] processes = data.getProcesses(); + for (String process : processes) { + if (process.equals(applicationName)) { + return p; + } + } + } + } + } + + return null; + + } + + public static void fixProjectNatureOrder(IProject project) throws CoreException { + IProjectDescription description = project.getDescription(); + String[] natures = description.getNatureIds(); + + // if the android nature is not the first one, we reorder them + if (AdtConstants.NATURE_DEFAULT.equals(natures[0]) == false) { + // look for the index + for (int i = 0 ; i < natures.length ; i++) { + if (AdtConstants.NATURE_DEFAULT.equals(natures[i])) { + // if we try to just reorder the array in one pass, this doesn't do + // anything. I guess JDT check that we are actually adding/removing nature. + // So, first we'll remove the android nature, and then add it back. + + // remove the android nature + removeNature(project, AdtConstants.NATURE_DEFAULT); + + // now add it back at the first index. + description = project.getDescription(); + natures = description.getNatureIds(); + + String[] newNatures = new String[natures.length + 1]; + + // first one is android + newNatures[0] = AdtConstants.NATURE_DEFAULT; + + // next the rest that was before the android nature + System.arraycopy(natures, 0, newNatures, 1, natures.length); + + // set the new natures + description.setNatureIds(newNatures); + project.setDescription(description, null); + + // and stop + break; + } + } + } + } + + + /** + * Removes a specific nature from a project. + * @param project The project to remove the nature from. + * @param nature The nature id to remove. + * @throws CoreException + */ + public static void removeNature(IProject project, String nature) throws CoreException { + IProjectDescription description = project.getDescription(); + String[] natures = description.getNatureIds(); + + // check if the project already has the android nature. + for (int i = 0; i < natures.length; ++i) { + if (nature.equals(natures[i])) { + String[] newNatures = new String[natures.length - 1]; + if (i > 0) { + System.arraycopy(natures, 0, newNatures, 0, i); + } + System.arraycopy(natures, i + 1, newNatures, i, natures.length - i - 1); + description.setNatureIds(newNatures); + project.setDescription(description, null); + + return; + } + } + + } + + /** + * Returns if the project has error level markers. + * @param includeReferencedProjects flag to also test the referenced projects. + * @throws CoreException + */ + public static boolean hasError(IProject project, boolean includeReferencedProjects) + throws CoreException { + IMarker[] markers = project.findMarkers(IMarker.PROBLEM, true, IResource.DEPTH_INFINITE); + if (markers != null && markers.length > 0) { + // the project has marker(s). even though they are "problem" we + // don't know their severity. so we loop on them and figure if they + // are warnings or errors + for (IMarker m : markers) { + int s = m.getAttribute(IMarker.SEVERITY, -1); + if (s == IMarker.SEVERITY_ERROR) { + return true; + } + } + } + + // test the referenced projects if needed. + if (includeReferencedProjects) { + List<IProject> projects = getReferencedProjects(project); + + for (IProject p : projects) { + if (hasError(p, false)) { + return true; + } + } + } + + return false; + } + + /** + * Saves a String property into the persistent storage of a resource. + * @param resource The resource into which the string value is saved. + * @param propertyName the name of the property. The id of the plug-in is added to this string. + * @param value the value to save + * @return true if the save succeeded. + */ + public static boolean saveStringProperty(IResource resource, String propertyName, + String value) { + QualifiedName qname = new QualifiedName(AdtPlugin.PLUGIN_ID, propertyName); + + try { + resource.setPersistentProperty(qname, value); + } catch (CoreException e) { + return false; + } + + return true; + } + + /** + * Loads a String property from the persistent storage of a resource. + * @param resource The resource from which the string value is loaded. + * @param propertyName the name of the property. The id of the plug-in is added to this string. + * @return the property value or null if it was not found. + */ + public static String loadStringProperty(IResource resource, String propertyName) { + QualifiedName qname = new QualifiedName(AdtPlugin.PLUGIN_ID, propertyName); + + try { + String value = resource.getPersistentProperty(qname); + return value; + } catch (CoreException e) { + return null; + } + } + + /** + * Saves a property into the persistent storage of a resource. + * @param resource The resource into which the boolean value is saved. + * @param propertyName the name of the property. The id of the plug-in is added to this string. + * @param value the value to save + * @return true if the save succeeded. + */ + public static boolean saveBooleanProperty(IResource resource, String propertyName, + boolean value) { + return saveStringProperty(resource, propertyName, Boolean.toString(value)); + } + + /** + * Loads a boolean property from the persistent storage of a resource. + * @param resource The resource from which the boolean value is loaded. + * @param propertyName the name of the property. The id of the plug-in is added to this string. + * @param defaultValue The default value to return if the property was not found. + * @return the property value or the default value if the property was not found. + */ + public static boolean loadBooleanProperty(IResource resource, String propertyName, + boolean defaultValue) { + String value = loadStringProperty(resource, propertyName); + if (value != null) { + return Boolean.parseBoolean(value); + } + + return defaultValue; + } + + public static Boolean loadBooleanProperty(IResource resource, String propertyName) { + String value = loadStringProperty(resource, propertyName); + if (value != null) { + return Boolean.valueOf(value); + } + + return null; + } + + /** + * Saves the path of a resource into the persistent storage of a resource. + * @param resource The resource into which the resource path is saved. + * @param propertyName the name of the property. The id of the plug-in is added to this string. + * @param value The resource to save. It's its path that is actually stored. If null, an + * empty string is stored. + * @return true if the save succeeded + */ + public static boolean saveResourceProperty(IResource resource, String propertyName, + IResource value) { + if (value != null) { + IPath iPath = value.getFullPath(); + return saveStringProperty(resource, propertyName, iPath.toString()); + } + + return saveStringProperty(resource, propertyName, ""); //$NON-NLS-1$ + } + + /** + * Loads the path of a resource from the persistent storage of a resource, and returns the + * corresponding IResource object. + * @param resource The resource from which the resource path is loaded. + * @param propertyName the name of the property. The id of the plug-in is added to this string. + * @return The corresponding IResource object (or children interface) or null + */ + public static IResource loadResourceProperty(IResource resource, String propertyName) { + String value = loadStringProperty(resource, propertyName); + + if (value != null && value.length() > 0) { + return ResourcesPlugin.getWorkspace().getRoot().findMember(new Path(value)); + } + + return null; + } + + /** + * Returns the list of referenced project that are opened and Java projects. + * @param project + * @return a new list object containing the opened referenced java project. + * @throws CoreException + */ + public static List<IProject> getReferencedProjects(IProject project) throws CoreException { + IProject[] projects = project.getReferencedProjects(); + + ArrayList<IProject> list = new ArrayList<IProject>(); + + for (IProject p : projects) { + if (p.isOpen() && p.hasNature(JavaCore.NATURE_ID)) { + list.add(p); + } + } + + return list; + } + + + /** + * Checks a Java project compiler level option against a list of supported versions. + * @param optionValue the Compiler level option. + * @return true if the option value is supported. + */ + private static boolean checkCompliance(@NonNull IJavaProject project, String optionValue) { + for (String s : AdtConstants.COMPILER_COMPLIANCE) { + if (s != null && s.equals(optionValue)) { + return true; + } + } + + if (JavaCore.VERSION_1_7.equals(optionValue)) { + // Requires API 19 and buildTools 19 + Sdk currentSdk = Sdk.getCurrent(); + if (currentSdk != null) { + IProject p = project.getProject(); + IAndroidTarget target = currentSdk.getTarget(p); + if (target == null || target.getVersion().getApiLevel() < 19) { + return false; + } + + ProjectState projectState = Sdk.getProjectState(p); + if (projectState != null) { + BuildToolInfo buildToolInfo = projectState.getBuildToolInfo(); + if (buildToolInfo == null) { + buildToolInfo = currentSdk.getLatestBuildTool(); + } + if (buildToolInfo == null || buildToolInfo.getRevision().getMajor() < 19) { + return false; + } + } + + return true; + } + } + + return false; + } + + /** + * Returns the apk filename for the given project + * @param project The project. + * @param config An optional config name. Can be null. + */ + public static String getApkFilename(IProject project, String config) { + if (config != null) { + return project.getName() + "-" + config + SdkConstants.DOT_ANDROID_PACKAGE; //$NON-NLS-1$ + } + + return project.getName() + SdkConstants.DOT_ANDROID_PACKAGE; + } + + /** + * Find the list of projects on which this JavaProject is dependent on at the compilation level. + * + * @param javaProject Java project that we are looking for the dependencies. + * @return A list of Java projects for which javaProject depend on. + * @throws JavaModelException + */ + public static List<IJavaProject> getAndroidProjectDependencies(IJavaProject javaProject) + throws JavaModelException { + String[] requiredProjectNames = javaProject.getRequiredProjectNames(); + + // Go from java project name to JavaProject name + IJavaModel javaModel = javaProject.getJavaModel(); + + // loop through all dependent projects and keep only those that are Android projects + List<IJavaProject> projectList = new ArrayList<IJavaProject>(requiredProjectNames.length); + for (String javaProjectName : requiredProjectNames) { + IJavaProject androidJavaProject = javaModel.getJavaProject(javaProjectName); + + //Verify that the project has also the Android Nature + try { + if (!androidJavaProject.getProject().hasNature(AdtConstants.NATURE_DEFAULT)) { + continue; + } + } catch (CoreException e) { + continue; + } + + projectList.add(androidJavaProject); + } + + return projectList; + } + + /** + * Returns the android package file as an IFile object for the specified + * project. + * @param project The project + * @return The android package as an IFile object or null if not found. + */ + public static IFile getApplicationPackage(IProject project) { + // get the output folder + IFolder outputLocation = BaseProjectHelper.getAndroidOutputFolder(project); + + if (outputLocation == null) { + AdtPlugin.printErrorToConsole(project, + "Failed to get the output location of the project. Check build path properties" + ); + return null; + } + + + // get the package path + String packageName = project.getName() + SdkConstants.DOT_ANDROID_PACKAGE; + IResource r = outputLocation.findMember(packageName); + + // check the package is present + if (r instanceof IFile && r.exists()) { + return (IFile)r; + } + + String msg = String.format("Could not find %1$s!", packageName); + AdtPlugin.printErrorToConsole(project, msg); + + return null; + } + + /** + * Returns an {@link IFile} object representing the manifest for the given project. + * + * @param project The project containing the manifest file. + * @return An IFile object pointing to the manifest or null if the manifest + * is missing. + */ + public static IFile getManifest(IProject project) { + IResource r = project.findMember(AdtConstants.WS_SEP + + SdkConstants.FN_ANDROID_MANIFEST_XML); + + if (r == null || r.exists() == false || (r instanceof IFile) == false) { + return null; + } + return (IFile) r; + } + + /** + * Does a full release build of the application, including the libraries. Do not build the + * package. + * + * @param project The project to be built. + * @param monitor A eclipse runtime progress monitor to be updated by the builders. + * @throws CoreException + */ + @SuppressWarnings("unchecked") + public static void compileInReleaseMode(IProject project, IProgressMonitor monitor) + throws CoreException { + compileInReleaseMode(project, true /*includeDependencies*/, monitor); + } + + /** + * Does a full release build of the application, including the libraries. Do not build the + * package. + * + * @param project The project to be built. + * @param monitor A eclipse runtime progress monitor to be updated by the builders. + * @throws CoreException + */ + @SuppressWarnings("unchecked") + private static void compileInReleaseMode(IProject project, boolean includeDependencies, + IProgressMonitor monitor) + throws CoreException { + + if (includeDependencies) { + ProjectState projectState = Sdk.getProjectState(project); + + // this gives us all the library projects, direct and indirect dependencies, + // so no need to run this method recursively. + List<IProject> libraries = projectState.getFullLibraryProjects(); + + // build dependencies in reverse order to prevent libraries being rebuilt + // due to refresh of other libraries (they would be compiled in the wrong mode). + for (int i = libraries.size() - 1 ; i >= 0 ; i--) { + IProject lib = libraries.get(i); + compileInReleaseMode(lib, false /*includeDependencies*/, monitor); + + // force refresh of the dependency. + lib.refreshLocal(IResource.DEPTH_INFINITE, monitor); + } + } + + // do a full build on all the builders to guarantee that the builders are called. + // (Eclipse does an optimization where builders are not called if there aren't any + // deltas). + + ICommand[] commands = project.getDescription().getBuildSpec(); + for (ICommand command : commands) { + String name = command.getBuilderName(); + if (PreCompilerBuilder.ID.equals(name)) { + Map newArgs = new HashMap(); + newArgs.put(PreCompilerBuilder.RELEASE_REQUESTED, ""); + if (command.getArguments() != null) { + newArgs.putAll(command.getArguments()); + } + + project.build(IncrementalProjectBuilder.FULL_BUILD, + PreCompilerBuilder.ID, newArgs, monitor); + } else if (PostCompilerBuilder.ID.equals(name)) { + if (includeDependencies == false) { + // this is a library, we need to build it! + project.build(IncrementalProjectBuilder.FULL_BUILD, name, + command.getArguments(), monitor); + } + } else { + + project.build(IncrementalProjectBuilder.FULL_BUILD, name, + command.getArguments(), monitor); + } + } + } + + /** + * Force building the project and all its dependencies. + * + * @param project the project to build + * @param kind the build kind + * @param monitor + * @throws CoreException + */ + public static void buildWithDeps(IProject project, int kind, IProgressMonitor monitor) + throws CoreException { + // Get list of projects that we depend on + ProjectState projectState = Sdk.getProjectState(project); + + // this gives us all the library projects, direct and indirect dependencies, + // so no need to run this method recursively. + List<IProject> libraries = projectState.getFullLibraryProjects(); + + // build dependencies in reverse order to prevent libraries being rebuilt + // due to refresh of other libraries (they would be compiled in the wrong mode). + for (int i = libraries.size() - 1 ; i >= 0 ; i--) { + IProject lib = libraries.get(i); + lib.build(kind, monitor); + lib.refreshLocal(IResource.DEPTH_INFINITE, monitor); + } + + project.build(kind, monitor); + } + + + /** + * Build project incrementally, including making the final packaging even if it is disabled + * by default. + * + * @param project The project to be built. + * @param monitor A eclipse runtime progress monitor to be updated by the builders. + * @throws CoreException + */ + public static void doFullIncrementalDebugBuild(IProject project, IProgressMonitor monitor) + throws CoreException { + // Get list of projects that we depend on + List<IJavaProject> androidProjectList = new ArrayList<IJavaProject>(); + try { + androidProjectList = getAndroidProjectDependencies( + BaseProjectHelper.getJavaProject(project)); + } catch (JavaModelException e) { + AdtPlugin.printErrorToConsole(project, e); + } + // Recursively build dependencies + for (IJavaProject dependency : androidProjectList) { + doFullIncrementalDebugBuild(dependency.getProject(), monitor); + } + + // Do an incremental build to pick up all the deltas + project.build(IncrementalProjectBuilder.INCREMENTAL_BUILD, monitor); + + // If the preferences indicate not to use post compiler optimization + // then the incremental build will have done everything necessary, otherwise, + // we have to run the final builder manually (if requested). + if (AdtPrefs.getPrefs().getBuildSkipPostCompileOnFileSave()) { + // Create the map to pass to the PostC builder + Map<String, String> args = new TreeMap<String, String>(); + args.put(PostCompilerBuilder.POST_C_REQUESTED, ""); //$NON-NLS-1$ + + // call the post compiler manually, forcing FULL_BUILD otherwise Eclipse won't + // call the builder since the delta is empty. + project.build(IncrementalProjectBuilder.FULL_BUILD, + PostCompilerBuilder.ID, args, monitor); + } + + // because the post compiler builder does a delayed refresh due to + // library not picking the refresh up if it's done during the build, + // we want to force a refresh here as this call is generally asking for + // a build to use the apk right after the call. + project.refreshLocal(IResource.DEPTH_INFINITE, monitor); + } +} |