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.project; 18 19 import com.android.ide.eclipse.adt.AdtConstants; 20 import com.android.ide.eclipse.adt.internal.build.builders.PostCompilerBuilder; 21 import com.android.ide.eclipse.adt.internal.build.builders.PreCompilerBuilder; 22 import com.android.ide.eclipse.adt.internal.build.builders.ResourceManagerBuilder; 23 24 import org.eclipse.core.resources.ICommand; 25 import org.eclipse.core.resources.IProject; 26 import org.eclipse.core.resources.IProjectDescription; 27 import org.eclipse.core.resources.IProjectNature; 28 import org.eclipse.core.runtime.CoreException; 29 import org.eclipse.core.runtime.IProgressMonitor; 30 import org.eclipse.core.runtime.NullProgressMonitor; 31 import org.eclipse.core.runtime.SubProgressMonitor; 32 import org.eclipse.jdt.core.JavaCore; 33 34 /** 35 * Project nature for the Android Projects. 36 */ 37 public class AndroidNature implements IProjectNature { 38 39 /** the project this nature object is associated with */ 40 private IProject mProject; 41 42 /** 43 * Configures this nature for its project. This is called by the workspace 44 * when natures are added to the project using 45 * <code>IProject.setDescription</code> and should not be called directly 46 * by clients. The nature extension id is added to the list of natures 47 * before this method is called, and need not be added here. 48 * 49 * Exceptions thrown by this method will be propagated back to the caller of 50 * <code>IProject.setDescription</code>, but the nature will remain in 51 * the project description. 52 * 53 * The Android nature adds the pre-builder and the APK builder if necessary. 54 * 55 * @see org.eclipse.core.resources.IProjectNature#configure() 56 * @throws CoreException if configuration fails. 57 */ configure()58 public void configure() throws CoreException { 59 configureResourceManagerBuilder(mProject); 60 configurePreBuilder(mProject); 61 configureApkBuilder(mProject); 62 } 63 64 /** 65 * De-configures this nature for its project. This is called by the 66 * workspace when natures are removed from the project using 67 * <code>IProject.setDescription</code> and should not be called directly 68 * by clients. The nature extension id is removed from the list of natures 69 * before this method is called, and need not be removed here. 70 * 71 * Exceptions thrown by this method will be propagated back to the caller of 72 * <code>IProject.setDescription</code>, but the nature will still be 73 * removed from the project description. 74 * 75 * The Android nature removes the custom pre builder and APK builder. 76 * 77 * @see org.eclipse.core.resources.IProjectNature#deconfigure() 78 * @throws CoreException if configuration fails. 79 */ deconfigure()80 public void deconfigure() throws CoreException { 81 // remove the android builders 82 removeBuilder(mProject, ResourceManagerBuilder.ID); 83 removeBuilder(mProject, PreCompilerBuilder.ID); 84 removeBuilder(mProject, PostCompilerBuilder.ID); 85 } 86 87 /** 88 * Returns the project to which this project nature applies. 89 * 90 * @return the project handle 91 * @see org.eclipse.core.resources.IProjectNature#getProject() 92 */ getProject()93 public IProject getProject() { 94 return mProject; 95 } 96 97 /** 98 * Sets the project to which this nature applies. Used when instantiating 99 * this project nature runtime. This is called by 100 * <code>IProject.create()</code> or 101 * <code>IProject.setDescription()</code> and should not be called 102 * directly by clients. 103 * 104 * @param project the project to which this nature applies 105 * @see org.eclipse.core.resources.IProjectNature#setProject(org.eclipse.core.resources.IProject) 106 */ setProject(IProject project)107 public void setProject(IProject project) { 108 mProject = project; 109 } 110 111 /** 112 * Adds the Android Nature and the Java Nature to the project if it doesn't 113 * already have them. 114 * 115 * @param project An existing or new project to update 116 * @param monitor An optional progress monitor. Can be null. 117 * @throws CoreException if fails to change the nature. 118 */ setupProjectNatures(IProject project, IProgressMonitor monitor)119 public static synchronized void setupProjectNatures(IProject project, 120 IProgressMonitor monitor) throws CoreException { 121 if (project == null || !project.isOpen()) return; 122 if (monitor == null) monitor = new NullProgressMonitor(); 123 124 // Add the natures. We need to add the Java nature first, so it adds its builder to the 125 // project first. This way, when the android nature is added, we can control where to put 126 // the android builders in relation to the java builder. 127 // Adding the java nature after the android one, would place the java builder before the 128 // android builders. 129 addNatureToProjectDescription(project, JavaCore.NATURE_ID, monitor); 130 addNatureToProjectDescription(project, AdtConstants.NATURE_DEFAULT, monitor); 131 } 132 133 /** 134 * Add the specified nature to the specified project. The nature is only 135 * added if not already present. 136 * <p/> 137 * Android Natures are always inserted at the beginning of the list of natures in order to 138 * have the jdt views/dialogs display the proper icon. 139 * 140 * @param project The project to modify. 141 * @param natureId The Id of the nature to add. 142 * @param monitor An existing progress monitor. 143 * @throws CoreException if fails to change the nature. 144 */ addNatureToProjectDescription(IProject project, String natureId, IProgressMonitor monitor)145 private static void addNatureToProjectDescription(IProject project, 146 String natureId, IProgressMonitor monitor) throws CoreException { 147 if (!project.hasNature(natureId)) { 148 149 IProjectDescription description = project.getDescription(); 150 String[] natures = description.getNatureIds(); 151 String[] newNatures = new String[natures.length + 1]; 152 153 // Android natures always come first. 154 if (natureId.equals(AdtConstants.NATURE_DEFAULT)) { 155 System.arraycopy(natures, 0, newNatures, 1, natures.length); 156 newNatures[0] = natureId; 157 } else { 158 System.arraycopy(natures, 0, newNatures, 0, natures.length); 159 newNatures[natures.length] = natureId; 160 } 161 162 description.setNatureIds(newNatures); 163 project.setDescription(description, new SubProgressMonitor(monitor, 10)); 164 } 165 } 166 167 /** 168 * Adds the ResourceManagerBuilder, if its not already there. It'll insert 169 * itself as the first builder. 170 * @throws CoreException 171 * 172 */ configureResourceManagerBuilder(IProject project)173 public static void configureResourceManagerBuilder(IProject project) 174 throws CoreException { 175 // get the builder list 176 IProjectDescription desc = project.getDescription(); 177 ICommand[] commands = desc.getBuildSpec(); 178 179 // look for the builder in case it's already there. 180 for (int i = 0; i < commands.length; ++i) { 181 if (ResourceManagerBuilder.ID.equals(commands[i].getBuilderName())) { 182 return; 183 } 184 } 185 186 // it's not there, lets add it at the beginning of the builders 187 ICommand[] newCommands = new ICommand[commands.length + 1]; 188 System.arraycopy(commands, 0, newCommands, 1, commands.length); 189 ICommand command = desc.newCommand(); 190 command.setBuilderName(ResourceManagerBuilder.ID); 191 newCommands[0] = command; 192 desc.setBuildSpec(newCommands); 193 project.setDescription(desc, null); 194 } 195 196 /** 197 * Adds the PreCompilerBuilder if its not already there. It'll check for 198 * presence of the ResourceManager and insert itself right after. 199 * @param project 200 * @throws CoreException 201 */ configurePreBuilder(IProject project)202 public static void configurePreBuilder(IProject project) 203 throws CoreException { 204 // get the builder list 205 IProjectDescription desc = project.getDescription(); 206 ICommand[] commands = desc.getBuildSpec(); 207 208 // look for the builder in case it's already there. 209 for (int i = 0; i < commands.length; ++i) { 210 if (PreCompilerBuilder.ID.equals(commands[i].getBuilderName())) { 211 return; 212 } 213 } 214 215 // we need to add it after the resource manager builder. 216 // Let's look for it 217 int index = -1; 218 for (int i = 0; i < commands.length; ++i) { 219 if (ResourceManagerBuilder.ID.equals(commands[i].getBuilderName())) { 220 index = i; 221 break; 222 } 223 } 224 225 // we're inserting after 226 index++; 227 228 // do the insertion 229 230 // copy the builders before. 231 ICommand[] newCommands = new ICommand[commands.length + 1]; 232 System.arraycopy(commands, 0, newCommands, 0, index); 233 234 // insert the new builder 235 ICommand command = desc.newCommand(); 236 command.setBuilderName(PreCompilerBuilder.ID); 237 newCommands[index] = command; 238 239 // copy the builder after 240 System.arraycopy(commands, index, newCommands, index + 1, commands.length-index); 241 242 // set the new builders in the project 243 desc.setBuildSpec(newCommands); 244 project.setDescription(desc, null); 245 } 246 configureApkBuilder(IProject project)247 public static void configureApkBuilder(IProject project) 248 throws CoreException { 249 // Add the .apk builder at the end if it's not already there 250 IProjectDescription desc = project.getDescription(); 251 ICommand[] commands = desc.getBuildSpec(); 252 253 for (int i = 0; i < commands.length; ++i) { 254 if (PostCompilerBuilder.ID.equals(commands[i].getBuilderName())) { 255 return; 256 } 257 } 258 259 ICommand[] newCommands = new ICommand[commands.length + 1]; 260 System.arraycopy(commands, 0, newCommands, 0, commands.length); 261 ICommand command = desc.newCommand(); 262 command.setBuilderName(PostCompilerBuilder.ID); 263 newCommands[commands.length] = command; 264 desc.setBuildSpec(newCommands); 265 project.setDescription(desc, null); 266 } 267 268 /** 269 * Removes a builder from the project. 270 * @param project The project to remove the builder from. 271 * @param id The String ID of the builder to remove. 272 * @return true if the builder was found and removed. 273 * @throws CoreException 274 */ removeBuilder(IProject project, String id)275 public static boolean removeBuilder(IProject project, String id) throws CoreException { 276 IProjectDescription description = project.getDescription(); 277 ICommand[] commands = description.getBuildSpec(); 278 for (int i = 0; i < commands.length; ++i) { 279 if (id.equals(commands[i].getBuilderName())) { 280 ICommand[] newCommands = new ICommand[commands.length - 1]; 281 System.arraycopy(commands, 0, newCommands, 0, i); 282 System.arraycopy(commands, i + 1, newCommands, i, commands.length - i - 1); 283 description.setBuildSpec(newCommands); 284 project.setDescription(description, null); 285 return true; 286 } 287 } 288 289 return false; 290 } 291 } 292