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