OSDN Git Service

Merge "Add support for multiple instrumentation test result listeners."
[android-x86/sdk.git] / eclipse / plugins / com.android.ide.eclipse.adt / src / com / android / ide / eclipse / adt / internal / project / ProjectState.java
1 /*
2  * Copyright (C) 2010 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.AdtPlugin;
20 import com.android.ide.eclipse.adt.internal.sdk.Sdk;
21 import com.android.sdklib.IAndroidTarget;
22 import com.android.sdklib.internal.project.ApkConfigurationHelper;
23 import com.android.sdklib.internal.project.ApkSettings;
24 import com.android.sdklib.internal.project.ProjectProperties;
25
26 import org.eclipse.core.resources.IProject;
27 import org.eclipse.core.runtime.IStatus;
28 import org.eclipse.core.runtime.Status;
29
30 import java.io.File;
31 import java.io.IOException;
32 import java.util.ArrayList;
33 import java.util.List;
34
35 /**
36  * Centralized state for Android Eclipse project.
37  * <p>This gives raw access to the properties (from <code>default.properties</code>), as well
38  * as direct access to target, apksettings and library information.
39  *
40  */
41 public final class ProjectState {
42
43     /**
44      * A class that represents a library linked to a project.
45      * <p/>It does not represent the library uniquely. Instead the {@link LibraryState} is linked
46      * to the main project which is accessible through {@link #getMainProjectState()}.
47      * <p/>If a library is used by two different projects, then there will be two different
48      * instances of {@link LibraryState} for the library.
49      *
50      * @see ProjectState#getLibrary(IProject)
51      */
52     public final class LibraryState {
53         private String mRelativePath;
54         private ProjectState mProjectState;
55         private String mPath;
56
57         private LibraryState(String relativePath) {
58             mRelativePath = relativePath;
59         }
60
61         /**
62          * Returns the {@link ProjectState} of the main project using this library.
63          */
64         public ProjectState getMainProjectState() {
65             return ProjectState.this;
66         }
67
68         /**
69          * Closes the library. This resets the IProject from this object ({@link #getProjectState()} will
70          * return <code>null</code>), and updates the main project data so that the library
71          * {@link IProject} object does not show up in the return value of
72          * {@link ProjectState#getLibraryProjects()}.
73          */
74         public void close() {
75             mProjectState = null;
76             mPath = null;
77
78             updateLibraries();
79         }
80
81         private void setRelativePath(String relativePath) {
82             mRelativePath = relativePath;
83         }
84
85         private void setProject(ProjectState project) {
86             mProjectState = project;
87             mPath = project.getProject().getLocation().toOSString();
88
89             updateLibraries();
90         }
91
92         /**
93          * Returns the relative path of the library from the main project.
94          * <p/>This is identical to the value defined in the main project's default.properties.
95          */
96         public String getRelativePath() {
97             return mRelativePath;
98         }
99
100         /**
101          * Returns the {@link ProjectState} item for the library. This can be null if the project
102          * is not actually opened in Eclipse.
103          */
104         public ProjectState getProjectState() {
105             return mProjectState;
106         }
107
108         /**
109          * Returns the OS-String location of the library project.
110          * <p/>This is based on location of the Eclipse project that matched
111          * {@link #getRelativePath()}.
112          *
113          * @return The project location, or null if the project is not opened in Eclipse.
114          */
115         public String getProjectLocation() {
116             return mPath;
117         }
118
119         @Override
120         public boolean equals(Object obj) {
121             if (obj instanceof LibraryState) {
122                 // the only thing that's always non-null is the relative path.
123                 LibraryState objState = (LibraryState)obj;
124                 return mRelativePath.equals(objState.mRelativePath) &&
125                         getMainProjectState().equals(objState.getMainProjectState());
126             } else if (obj instanceof ProjectState || obj instanceof IProject) {
127                 return mProjectState != null && mProjectState.equals(obj);
128             } else if (obj instanceof String) {
129                 return mRelativePath.equals(obj);
130             }
131
132             return false;
133         }
134
135         @Override
136         public int hashCode() {
137             return mRelativePath.hashCode();
138         }
139     }
140
141     private final IProject mProject;
142     private final ProjectProperties mProperties;
143     /**
144      * list of libraries. Access to this list must be protected by
145      * <code>synchronized(mLibraries)</code>, but it is important that such code do not call
146      * out to other classes (especially those protected by {@link Sdk#getLock()}.)
147      */
148     private final ArrayList<LibraryState> mLibraries = new ArrayList<LibraryState>();
149     private IAndroidTarget mTarget;
150     private ApkSettings mApkSettings;
151     private IProject[] mLibraryProjects;
152
153     public ProjectState(IProject project, ProjectProperties properties) {
154         mProject = project;
155         mProperties = properties;
156
157         // load the ApkSettings
158         mApkSettings = ApkConfigurationHelper.getSettings(properties);
159
160         // load the libraries
161         synchronized (mLibraries) {
162             int index = 1;
163             while (true) {
164                 String propName = ProjectProperties.PROPERTY_LIB_REF + Integer.toString(index++);
165                 String rootPath = mProperties.getProperty(propName);
166
167                 if (rootPath == null) {
168                     break;
169                 }
170
171                 mLibraries.add(new LibraryState(convertPath(rootPath)));
172             }
173         }
174     }
175
176     public IProject getProject() {
177         return mProject;
178     }
179
180     public ProjectProperties getProperties() {
181         return mProperties;
182     }
183
184     public void setTarget(IAndroidTarget target) {
185         mTarget = target;
186     }
187
188     /**
189      * Returns the project's target's hash string.
190      * <p/>If {@link #getTarget()} returns a valid object, then this returns the value of
191      * {@link IAndroidTarget#hashString()}.
192      * <p/>Otherwise this will return the value of the property
193      * {@link ProjectProperties#PROPERTY_TARGET} from {@link #getProperties()} (if valid).
194      * @return the target hash string or null if not found.
195      */
196     public String getTargetHashString() {
197         if (mTarget != null) {
198             return mTarget.hashString();
199         }
200
201         if (mProperties != null) {
202             return mProperties.getProperty(ProjectProperties.PROPERTY_TARGET);
203         }
204
205         return null;
206     }
207
208     public IAndroidTarget getTarget() {
209         return mTarget;
210     }
211
212     public static class LibraryDifference {
213         public List<LibraryState> removed = new ArrayList<LibraryState>();
214         public boolean added = false;
215
216         public boolean hasDiff() {
217             return removed.size() > 0 || added;
218         }
219     }
220
221     /**
222      * Reloads the content of the properties.
223      * <p/>This also reset the reference to the target as it may have changed.
224      * <p/>This should be followed by a call to {@link Sdk#loadTarget(ProjectState)}.
225      *
226      * @return an instance of {@link LibraryDifference} describing the change in libraries.
227      */
228     public LibraryDifference reloadProperties() {
229         mTarget = null;
230         mProperties.reload();
231
232         // compare/reload the libraries.
233
234         // if the order change it won't impact the java part, so instead try to detect removed/added
235         // libraries.
236
237         LibraryDifference diff = new LibraryDifference();
238
239         synchronized (mLibraries) {
240             List<LibraryState> oldLibraries = new ArrayList<LibraryState>(mLibraries);
241             mLibraries.clear();
242
243             // load the libraries
244             int index = 1;
245             while (true) {
246                 String propName = ProjectProperties.PROPERTY_LIB_REF + Integer.toString(index++);
247                 String rootPath = mProperties.getProperty(propName);
248
249                 if (rootPath == null) {
250                     break;
251                 }
252
253                 // search for a library with the same path (not exact same string, but going
254                 // to the same folder).
255                 String convertedPath = convertPath(rootPath);
256                 boolean found = false;
257                 for (int i = 0 ; i < oldLibraries.size(); i++) {
258                     LibraryState libState = oldLibraries.get(i);
259                     if (libState.equals(convertedPath)) {
260                         // it's a match. move it back to mLibraries and remove it from the
261                         // old library list.
262                         found = true;
263                         mLibraries.add(libState);
264                         oldLibraries.remove(i);
265                         break;
266                     }
267                 }
268
269                 if (found == false) {
270                     diff.added = true;
271                     mLibraries.add(new LibraryState(convertedPath));
272                 }
273             }
274
275             // whatever's left in oldLibraries is removed.
276             diff.removed.addAll(oldLibraries);
277
278             // update the library with what IProjet are known at the time.
279             updateLibraries();
280         }
281
282         return diff;
283     }
284
285     public void setApkSettings(ApkSettings apkSettings) {
286         mApkSettings = apkSettings;
287     }
288
289     public ApkSettings getApkSettings() {
290         return mApkSettings;
291     }
292
293     /**
294      * Convenience method returning all the IProject objects for the resolved libraries.
295      * <p/>If some dependencies are not resolved (or their projects is not opened in Eclipse),
296      * they will not show up in this list.
297      * @return the resolved projects or null if there are no project (either no resolved or no
298      * dependencies)
299      */
300     public IProject[] getLibraryProjects() {
301         return mLibraryProjects;
302     }
303
304     /**
305      * Returns whether this is a library project.
306      */
307     public boolean isLibrary() {
308         String value = mProperties.getProperty(ProjectProperties.PROPERTY_LIBRARY);
309         return value != null && Boolean.valueOf(value);
310     }
311
312     /**
313      * Returns whether the project is missing some required libraries.
314      */
315     public boolean isMissingLibraries() {
316         synchronized (mLibraries) {
317             for (LibraryState state : mLibraries) {
318                 if (state.getProjectState() == null) {
319                     return true;
320                 }
321             }
322         }
323
324         return false;
325     }
326
327     /**
328      * Returns the {@link LibraryState} object for a given {@link IProject}.
329      * </p>This can only return a non-null object if the link between the main project's
330      * {@link IProject} and the library's {@link IProject} was done.
331      *
332      * @return the matching LibraryState or <code>null</code>
333      *
334      * @see #needs(IProject)
335      */
336     public LibraryState getLibrary(IProject library) {
337         synchronized (mLibraries) {
338             for (LibraryState state : mLibraries) {
339                 if (state.getProjectState().equals(library)) {
340                     return state;
341                 }
342             }
343         }
344
345         return null;
346     }
347
348     public LibraryState getLibrary(String name) {
349         synchronized (mLibraries) {
350             for (LibraryState state : mLibraries) {
351                 if (state.getProjectState().getProject().getName().equals(name)) {
352                     return state;
353                 }
354             }
355         }
356
357         return null;
358     }
359
360
361     /**
362      * Returns whether a given library project is needed by the receiver.
363      * <p/>If the library is needed, this finds the matching {@link LibraryState}, initializes it
364      * so that it contains the library's {@link IProject} object (so that
365      * {@link LibraryState#getProjectState()} does not return null) and then returns it.
366      *
367      * @param libraryProject the library project to check.
368      * @return a non null object if the project is a library dependency,
369      * <code>null</code> otherwise.
370      *
371      * @see LibraryState#getProjectState()
372      */
373     public LibraryState needs(ProjectState libraryProject) {
374         // compute current location
375         File projectFile = new File(mProject.getLocation().toOSString());
376
377         // get the location of the library.
378         File libraryFile = new File(libraryProject.getProject().getLocation().toOSString());
379
380         // loop on all libraries and check if the path match
381         synchronized (mLibraries) {
382             for (LibraryState state : mLibraries) {
383                 if (state.getProjectState() == null) {
384                     File library = new File(projectFile, state.getRelativePath());
385                     try {
386                         File absPath = library.getCanonicalFile();
387                         if (absPath.equals(libraryFile)) {
388                             state.setProject(libraryProject);
389                             return state;
390                         }
391                     } catch (IOException e) {
392                         // ignore this library
393                     }
394                 }
395             }
396         }
397
398         return null;
399     }
400
401     /**
402      * Updates a library with a new path.
403      * <p/>This method acts both as a check and an action. If the project does not depend on the
404      * given <var>oldRelativePath</var> then no action is done and <code>null</code> is returned.
405      * <p/>If the project depends on the library, then the project is updated with the new path,
406      * and the {@link LibraryState} for the library is returned.
407      * <p/>Updating the project does two things:<ul>
408      * <li>Update LibraryState with new relative path and new {@link IProject} object.</li>
409      * <li>Update the main project's <code>default.properties</code> with the new relative path
410      * for the changed library.</li>
411      * </ul>
412      *
413      * @param oldRelativePath the old library path relative to this project
414      * @param newRelativePath the new library path relative to this project
415      * @param newLibraryState the new {@link ProjectState} object.
416      * @return a non null object if the project depends on the library.
417      *
418      * @see LibraryState#getProjectState()
419      */
420     public LibraryState updateLibrary(String oldRelativePath, String newRelativePath,
421             ProjectState newLibraryState) {
422         // compute current location
423         File projectFile = new File(mProject.getLocation().toOSString());
424
425         // loop on all libraries and check if the path matches
426         synchronized (mLibraries) {
427             for (LibraryState state : mLibraries) {
428                 if (state.getProjectState() == null) {
429                     try {
430                         // oldRelativePath may not be the same exact string as the
431                         // one in the project properties (trailing separator could be different
432                         // for instance).
433                         // Use java.io.File to deal with this and also do a platform-dependent
434                         // path comparison
435                         File library1 = new File(projectFile, oldRelativePath);
436                         File library2 = new File(projectFile, state.getRelativePath());
437                         if (library1.getCanonicalPath().equals(library2.getCanonicalPath())) {
438                             // save the exact property string to replace.
439                             String oldProperty = state.getRelativePath();
440
441                             // then update the LibraryPath.
442                             state.setRelativePath(newRelativePath);
443                             state.setProject(newLibraryState);
444
445                             // update the default.properties file
446                             IStatus status = replaceLibraryProperty(oldProperty, newRelativePath);
447                             if (status != null) {
448                                 if (status.getSeverity() != IStatus.OK) {
449                                     // log the error somehow.
450                                 }
451                             } else {
452                                 // This should not happen since the library wouldn't be here in the
453                                 // first place
454                             }
455
456                             // return the LibraryState object.
457                             return state;
458                         }
459                     } catch (IOException e) {
460                         // ignore this library
461                     }
462                 }
463             }
464         }
465
466         return null;
467     }
468
469     private IStatus replaceLibraryProperty(String oldValue, String newValue) {
470         int index = 1;
471         while (true) {
472             String propName = ProjectProperties.PROPERTY_LIB_REF + Integer.toString(index++);
473             String rootPath = mProperties.getProperty(propName);
474
475             if (rootPath == null) {
476                 break;
477             }
478
479             if (rootPath.equals(oldValue)) {
480                 mProperties.setProperty(propName, newValue);
481                 try {
482                     mProperties.save();
483                 } catch (IOException e) {
484                     return new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID,
485                             String.format("Failed to save %1$s for project %2$s",
486                                     mProperties.getType().getFilename(), mProject.getName()),
487                             e);
488                 }
489                 return Status.OK_STATUS;
490             }
491         }
492
493         return null;
494     }
495
496     private void updateLibraries() {
497         ArrayList<IProject> list = new ArrayList<IProject>();
498         synchronized (mLibraries) {
499             for (LibraryState state : mLibraries) {
500                 if (state.getProjectState() != null) {
501                     list.add(state.getProjectState().getProject());
502                 }
503             }
504         }
505
506         mLibraryProjects = list.toArray(new IProject[list.size()]);
507     }
508
509     /**
510      * Converts a path containing only / by the proper platform separator.
511      */
512     private String convertPath(String path) {
513         return path.replaceAll("/", File.separator); //$NON-NLS-1$
514     }
515
516     @Override
517     public boolean equals(Object obj) {
518         if (obj instanceof ProjectState) {
519             return mProject.equals(((ProjectState) obj).mProject);
520         } else if (obj instanceof IProject) {
521             return mProject.equals(obj);
522         }
523
524         return false;
525     }
526
527     @Override
528     public int hashCode() {
529         return mProject.hashCode();
530     }
531 }