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