• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.sdk;
18 
19 import com.android.annotations.NonNull;
20 import com.android.annotations.Nullable;
21 import com.android.ide.eclipse.adt.AdtPlugin;
22 import com.android.sdklib.BuildToolInfo;
23 import com.android.sdklib.IAndroidTarget;
24 import com.android.sdklib.internal.project.ProjectProperties;
25 import com.android.sdklib.internal.project.ProjectPropertiesWorkingCopy;
26 
27 import org.eclipse.core.resources.IProject;
28 import org.eclipse.core.runtime.IStatus;
29 import org.eclipse.core.runtime.Status;
30 
31 import java.io.File;
32 import java.io.IOException;
33 import java.util.ArrayList;
34 import java.util.Collection;
35 import java.util.Collections;
36 import java.util.HashSet;
37 import java.util.List;
38 import java.util.Set;
39 import java.util.regex.Matcher;
40 
41 /**
42  * Centralized state for Android Eclipse project.
43  * <p>This gives raw access to the properties (from <code>project.properties</code>), as well
44  * as direct access to target and library information.
45  *
46  * This also gives access to library information.
47  *
48  * {@link #isLibrary()} indicates if the project is a library.
49  * {@link #hasLibraries()} and {@link #getLibraries()} give access to the libraries through
50  * instances of {@link LibraryState}. A {@link LibraryState} instance is a link between a main
51  * project and its library. Theses instances are owned by the {@link ProjectState}.
52  *
53  * {@link #isMissingLibraries()} will indicate if the project has libraries that are not resolved.
54  * Unresolved libraries are libraries that do not have any matching opened Eclipse project.
55  * When there are missing libraries, the {@link LibraryState} instance for them will return null
56  * for {@link LibraryState#getProjectState()}.
57  *
58  */
59 public final class ProjectState {
60 
61     /**
62      * A class that represents a library linked to a project.
63      * <p/>It does not represent the library uniquely. Instead the {@link LibraryState} is linked
64      * to the main project which is accessible through {@link #getMainProjectState()}.
65      * <p/>If a library is used by two different projects, then there will be two different
66      * instances of {@link LibraryState} for the library.
67      *
68      * @see ProjectState#getLibrary(IProject)
69      */
70     public final class LibraryState {
71         private String mRelativePath;
72         private ProjectState mProjectState;
73         private String mPath;
74 
LibraryState(String relativePath)75         private LibraryState(String relativePath) {
76             mRelativePath = relativePath;
77         }
78 
79         /**
80          * Returns the {@link ProjectState} of the main project using this library.
81          */
getMainProjectState()82         public ProjectState getMainProjectState() {
83             return ProjectState.this;
84         }
85 
86         /**
87          * Closes the library. This resets the IProject from this object ({@link #getProjectState()} will
88          * return <code>null</code>), and updates the main project data so that the library
89          * {@link IProject} object does not show up in the return value of
90          * {@link ProjectState#getFullLibraryProjects()}.
91          */
close()92         public void close() {
93             mProjectState.removeParentProject(getMainProjectState());
94             mProjectState = null;
95             mPath = null;
96 
97             getMainProjectState().updateFullLibraryList();
98         }
99 
setRelativePath(String relativePath)100         private void setRelativePath(String relativePath) {
101             mRelativePath = relativePath;
102         }
103 
setProject(ProjectState project)104         private void setProject(ProjectState project) {
105             mProjectState = project;
106             mPath = project.getProject().getLocation().toOSString();
107             mProjectState.addParentProject(getMainProjectState());
108 
109             getMainProjectState().updateFullLibraryList();
110         }
111 
112         /**
113          * Returns the relative path of the library from the main project.
114          * <p/>This is identical to the value defined in the main project's project.properties.
115          */
getRelativePath()116         public String getRelativePath() {
117             return mRelativePath;
118         }
119 
120         /**
121          * Returns the {@link ProjectState} item for the library. This can be null if the project
122          * is not actually opened in Eclipse.
123          */
getProjectState()124         public ProjectState getProjectState() {
125             return mProjectState;
126         }
127 
128         /**
129          * Returns the OS-String location of the library project.
130          * <p/>This is based on location of the Eclipse project that matched
131          * {@link #getRelativePath()}.
132          *
133          * @return The project location, or null if the project is not opened in Eclipse.
134          */
getProjectLocation()135         public String getProjectLocation() {
136             return mPath;
137         }
138 
139         @Override
equals(Object obj)140         public boolean equals(Object obj) {
141             if (obj instanceof LibraryState) {
142                 // the only thing that's always non-null is the relative path.
143                 LibraryState objState = (LibraryState)obj;
144                 return mRelativePath.equals(objState.mRelativePath) &&
145                         getMainProjectState().equals(objState.getMainProjectState());
146             } else if (obj instanceof ProjectState || obj instanceof IProject) {
147                 return mProjectState != null && mProjectState.equals(obj);
148             } else if (obj instanceof String) {
149                 return normalizePath(mRelativePath).equals(normalizePath((String) obj));
150             }
151 
152             return false;
153         }
154 
155         @Override
hashCode()156         public int hashCode() {
157             return normalizePath(mRelativePath).hashCode();
158         }
159     }
160 
161     private final IProject mProject;
162     private final ProjectProperties mProperties;
163     private IAndroidTarget mTarget;
164     private BuildToolInfo mBuildToolInfo;
165 
166     /**
167      * list of libraries. Access to this list must be protected by
168      * <code>synchronized(mLibraries)</code>, but it is important that such code do not call
169      * out to other classes (especially those protected by {@link Sdk#getLock()}.)
170      */
171     private final ArrayList<LibraryState> mLibraries = new ArrayList<LibraryState>();
172     /** Cached list of all IProject instances representing the resolved libraries, including
173      * indirect dependencies. This must never be null. */
174     private List<IProject> mLibraryProjects = Collections.emptyList();
175     /**
176      * List of parent projects. When this instance is a library ({@link #isLibrary()} returns
177      * <code>true</code>) then this is filled with projects that depends on this project.
178      */
179     private final ArrayList<ProjectState> mParentProjects = new ArrayList<ProjectState>();
180 
ProjectState(IProject project, ProjectProperties properties)181     ProjectState(IProject project, ProjectProperties properties) {
182         if (project == null || properties == null) {
183             throw new NullPointerException();
184         }
185 
186         mProject = project;
187         mProperties = properties;
188 
189         // load the libraries
190         synchronized (mLibraries) {
191             int index = 1;
192             while (true) {
193                 String propName = ProjectProperties.PROPERTY_LIB_REF + Integer.toString(index++);
194                 String rootPath = mProperties.getProperty(propName);
195 
196                 if (rootPath == null) {
197                     break;
198                 }
199 
200                 mLibraries.add(new LibraryState(convertPath(rootPath)));
201             }
202         }
203     }
204 
getProject()205     public IProject getProject() {
206         return mProject;
207     }
208 
getProperties()209     public ProjectProperties getProperties() {
210         return mProperties;
211     }
212 
getProperty(@onNull String name)213     public @Nullable String getProperty(@NonNull String name) {
214         if (mProperties != null) {
215             return mProperties.getProperty(name);
216         }
217 
218         return null;
219     }
220 
setTarget(IAndroidTarget target)221     public void setTarget(IAndroidTarget target) {
222         mTarget = target;
223     }
224 
225     /**
226      * Returns the project's target's hash string.
227      * <p/>If {@link #getTarget()} returns a valid object, then this returns the value of
228      * {@link IAndroidTarget#hashString()}.
229      * <p/>Otherwise this will return the value of the property
230      * {@link ProjectProperties#PROPERTY_TARGET} from {@link #getProperties()} (if valid).
231      * @return the target hash string or null if not found.
232      */
getTargetHashString()233     public String getTargetHashString() {
234         if (mTarget != null) {
235             return mTarget.hashString();
236         }
237 
238         return mProperties.getProperty(ProjectProperties.PROPERTY_TARGET);
239     }
240 
getTarget()241     public IAndroidTarget getTarget() {
242         return mTarget;
243     }
244 
setBuildToolInfo(BuildToolInfo buildToolInfo)245     public void setBuildToolInfo(BuildToolInfo buildToolInfo) {
246         mBuildToolInfo = buildToolInfo;
247     }
248 
getBuildToolInfo()249     public BuildToolInfo getBuildToolInfo() {
250         return mBuildToolInfo;
251     }
252 
253     /**
254      * Returns the build tools version from the project's properties.
255      * @return the value or null
256      */
257     @Nullable
getBuildToolInfoVersion()258     public String getBuildToolInfoVersion() {
259         return mProperties.getProperty(ProjectProperties.PROPERTY_BUILD_TOOLS);
260     }
261 
getRenderScriptSupportMode()262     public boolean getRenderScriptSupportMode() {
263         String supportModeValue = mProperties.getProperty(ProjectProperties.PROPERTY_RS_SUPPORT);
264         if (supportModeValue != null) {
265             return Boolean.parseBoolean(supportModeValue);
266         }
267 
268         return false;
269     }
270 
271     public static class LibraryDifference {
272         public boolean removed = false;
273         public boolean added = false;
274 
hasDiff()275         public boolean hasDiff() {
276             return removed || added;
277         }
278     }
279 
280     /**
281      * Reloads the content of the properties.
282      * <p/>This also reset the reference to the target as it may have changed, therefore this
283      * should be followed by a call to {@link Sdk#loadTarget(ProjectState)}.
284      *
285      * <p/>If the project libraries changes, they are updated to a certain extent.<br>
286      * Removed libraries are removed from the state list, and added to the {@link LibraryDifference}
287      * object that is returned so that they can be processed.<br>
288      * Added libraries are added to the state (as new {@link LibraryState} objects), but their
289      * IProject is not resolved. {@link ProjectState#needs(ProjectState)} should be called
290      * afterwards to properly initialize the libraries.
291      *
292      * @return an instance of {@link LibraryDifference} describing the change in libraries.
293      */
reloadProperties()294     public LibraryDifference reloadProperties() {
295         mTarget = null;
296         mProperties.reload();
297 
298         // compare/reload the libraries.
299 
300         // if the order change it won't impact the java part, so instead try to detect removed/added
301         // libraries.
302 
303         LibraryDifference diff = new LibraryDifference();
304 
305         synchronized (mLibraries) {
306             List<LibraryState> oldLibraries = new ArrayList<LibraryState>(mLibraries);
307             mLibraries.clear();
308 
309             // load the libraries
310             int index = 1;
311             while (true) {
312                 String propName = ProjectProperties.PROPERTY_LIB_REF + Integer.toString(index++);
313                 String rootPath = mProperties.getProperty(propName);
314 
315                 if (rootPath == null) {
316                     break;
317                 }
318 
319                 // search for a library with the same path (not exact same string, but going
320                 // to the same folder).
321                 String convertedPath = convertPath(rootPath);
322                 boolean found = false;
323                 for (int i = 0 ; i < oldLibraries.size(); i++) {
324                     LibraryState libState = oldLibraries.get(i);
325                     if (libState.equals(convertedPath)) {
326                         // it's a match. move it back to mLibraries and remove it from the
327                         // old library list.
328                         found = true;
329                         mLibraries.add(libState);
330                         oldLibraries.remove(i);
331                         break;
332                     }
333                 }
334 
335                 if (found == false) {
336                     diff.added = true;
337                     mLibraries.add(new LibraryState(convertedPath));
338                 }
339             }
340 
341             // whatever's left in oldLibraries is removed.
342             diff.removed = oldLibraries.size() > 0;
343 
344             // update the library with what IProjet are known at the time.
345             updateFullLibraryList();
346         }
347 
348         return diff;
349     }
350 
351     /**
352      * Returns the list of {@link LibraryState}.
353      */
getLibraries()354     public List<LibraryState> getLibraries() {
355         synchronized (mLibraries) {
356             return Collections.unmodifiableList(mLibraries);
357         }
358     }
359 
360     /**
361      * Returns all the <strong>resolved</strong> library projects, including indirect dependencies.
362      * The list is ordered to match the library priority order for resource processing with
363      * <code>aapt</code>.
364      * <p/>If some dependencies are not resolved (or their projects is not opened in Eclipse),
365      * they will not show up in this list.
366      * @return the resolved projects as an unmodifiable list. May be an empty.
367      */
getFullLibraryProjects()368     public List<IProject> getFullLibraryProjects() {
369         return mLibraryProjects;
370     }
371 
372     /**
373      * Returns whether this is a library project.
374      */
isLibrary()375     public boolean isLibrary() {
376         String value = mProperties.getProperty(ProjectProperties.PROPERTY_LIBRARY);
377         return value != null && Boolean.valueOf(value);
378     }
379 
380     /**
381      * Returns whether the project depends on one or more libraries.
382      */
hasLibraries()383     public boolean hasLibraries() {
384         synchronized (mLibraries) {
385             return mLibraries.size() > 0;
386         }
387     }
388 
389     /**
390      * Returns whether the project is missing some required libraries.
391      */
isMissingLibraries()392     public boolean isMissingLibraries() {
393         synchronized (mLibraries) {
394             for (LibraryState state : mLibraries) {
395                 if (state.getProjectState() == null) {
396                     return true;
397                 }
398             }
399         }
400 
401         return false;
402     }
403 
404     /**
405      * Returns the {@link LibraryState} object for a given {@link IProject}.
406      * </p>This can only return a non-null object if the link between the main project's
407      * {@link IProject} and the library's {@link IProject} was done.
408      *
409      * @return the matching LibraryState or <code>null</code>
410      *
411      * @see #needs(ProjectState)
412      */
getLibrary(IProject library)413     public LibraryState getLibrary(IProject library) {
414         synchronized (mLibraries) {
415             for (LibraryState state : mLibraries) {
416                 ProjectState ps = state.getProjectState();
417                 if (ps != null && ps.getProject().equals(library)) {
418                     return state;
419                 }
420             }
421         }
422 
423         return null;
424     }
425 
426     /**
427      * Returns the {@link LibraryState} object for a given <var>name</var>.
428      * </p>This can only return a non-null object if the link between the main project's
429      * {@link IProject} and the library's {@link IProject} was done.
430      *
431      * @return the matching LibraryState or <code>null</code>
432      *
433      * @see #needs(IProject)
434      */
getLibrary(String name)435     public LibraryState getLibrary(String name) {
436         synchronized (mLibraries) {
437             for (LibraryState state : mLibraries) {
438                 ProjectState ps = state.getProjectState();
439                 if (ps != null && ps.getProject().getName().equals(name)) {
440                     return state;
441                 }
442             }
443         }
444 
445         return null;
446     }
447 
448 
449     /**
450      * Returns whether a given library project is needed by the receiver.
451      * <p/>If the library is needed, this finds the matching {@link LibraryState}, initializes it
452      * so that it contains the library's {@link IProject} object (so that
453      * {@link LibraryState#getProjectState()} does not return null) and then returns it.
454      *
455      * @param libraryProject the library project to check.
456      * @return a non null object if the project is a library dependency,
457      * <code>null</code> otherwise.
458      *
459      * @see LibraryState#getProjectState()
460      */
needs(ProjectState libraryProject)461     public LibraryState needs(ProjectState libraryProject) {
462         // compute current location
463         File projectFile = mProject.getLocation().toFile();
464 
465         // get the location of the library.
466         File libraryFile = libraryProject.getProject().getLocation().toFile();
467 
468         // loop on all libraries and check if the path match
469         synchronized (mLibraries) {
470             for (LibraryState state : mLibraries) {
471                 if (state.getProjectState() == null) {
472                     File library = new File(projectFile, state.getRelativePath());
473                     try {
474                         File absPath = library.getCanonicalFile();
475                         if (absPath.equals(libraryFile)) {
476                             state.setProject(libraryProject);
477                             return state;
478                         }
479                     } catch (IOException e) {
480                         // ignore this library
481                     }
482                 }
483             }
484         }
485 
486         return null;
487     }
488 
489     /**
490      * Returns whether the project depends on a given <var>library</var>
491      * @param library the library to check.
492      * @return true if the project depends on the library. This is not affected by whether the link
493      * was done through {@link #needs(ProjectState)}.
494      */
dependsOn(ProjectState library)495     public boolean dependsOn(ProjectState library) {
496         synchronized (mLibraries) {
497             for (LibraryState state : mLibraries) {
498                 if (state != null && state.getProjectState() != null &&
499                         library.getProject().equals(state.getProjectState().getProject())) {
500                     return true;
501                 }
502             }
503         }
504 
505         return false;
506     }
507 
508 
509     /**
510      * Updates a library with a new path.
511      * <p/>This method acts both as a check and an action. If the project does not depend on the
512      * given <var>oldRelativePath</var> then no action is done and <code>null</code> is returned.
513      * <p/>If the project depends on the library, then the project is updated with the new path,
514      * and the {@link LibraryState} for the library is returned.
515      * <p/>Updating the project does two things:<ul>
516      * <li>Update LibraryState with new relative path and new {@link IProject} object.</li>
517      * <li>Update the main project's <code>project.properties</code> with the new relative path
518      * for the changed library.</li>
519      * </ul>
520      *
521      * @param oldRelativePath the old library path relative to this project
522      * @param newRelativePath the new library path relative to this project
523      * @param newLibraryState the new {@link ProjectState} object.
524      * @return a non null object if the project depends on the library.
525      *
526      * @see LibraryState#getProjectState()
527      */
updateLibrary(String oldRelativePath, String newRelativePath, ProjectState newLibraryState)528     public LibraryState updateLibrary(String oldRelativePath, String newRelativePath,
529             ProjectState newLibraryState) {
530         // compute current location
531         File projectFile = mProject.getLocation().toFile();
532 
533         // loop on all libraries and check if the path matches
534         synchronized (mLibraries) {
535             for (LibraryState state : mLibraries) {
536                 if (state.getProjectState() == null) {
537                     try {
538                         // oldRelativePath may not be the same exact string as the
539                         // one in the project properties (trailing separator could be different
540                         // for instance).
541                         // Use java.io.File to deal with this and also do a platform-dependent
542                         // path comparison
543                         File library1 = new File(projectFile, oldRelativePath);
544                         File library2 = new File(projectFile, state.getRelativePath());
545                         if (library1.getCanonicalPath().equals(library2.getCanonicalPath())) {
546                             // save the exact property string to replace.
547                             String oldProperty = state.getRelativePath();
548 
549                             // then update the LibraryPath.
550                             state.setRelativePath(newRelativePath);
551                             state.setProject(newLibraryState);
552 
553                             // update the project.properties file
554                             IStatus status = replaceLibraryProperty(oldProperty, newRelativePath);
555                             if (status != null) {
556                                 if (status.getSeverity() != IStatus.OK) {
557                                     // log the error somehow.
558                                 }
559                             } else {
560                                 // This should not happen since the library wouldn't be here in the
561                                 // first place
562                             }
563 
564                             // return the LibraryState object.
565                             return state;
566                         }
567                     } catch (IOException e) {
568                         // ignore this library
569                     }
570                 }
571             }
572         }
573 
574         return null;
575     }
576 
577 
addParentProject(ProjectState parentState)578     private void addParentProject(ProjectState parentState) {
579         mParentProjects.add(parentState);
580     }
581 
removeParentProject(ProjectState parentState)582     private void removeParentProject(ProjectState parentState) {
583         mParentProjects.remove(parentState);
584     }
585 
getParentProjects()586     public List<ProjectState> getParentProjects() {
587         return Collections.unmodifiableList(mParentProjects);
588     }
589 
590     /**
591      * Computes the transitive closure of projects referencing this project as a
592      * library project
593      *
594      * @return a collection (in any order) of project states for projects that
595      *         directly or indirectly include this project state's project as a
596      *         library project
597      */
getFullParentProjects()598     public Collection<ProjectState> getFullParentProjects() {
599         Set<ProjectState> result = new HashSet<ProjectState>();
600         addParentProjects(result, this);
601         return result;
602     }
603 
604     /** Adds all parent projects of the given project, transitively, into the given parent set */
addParentProjects(Set<ProjectState> parents, ProjectState state)605     private static void addParentProjects(Set<ProjectState> parents, ProjectState state) {
606         for (ProjectState s : state.mParentProjects) {
607             if (!parents.contains(s)) {
608                 parents.add(s);
609                 addParentProjects(parents, s);
610             }
611         }
612     }
613 
614     /**
615      * Update the value of a library dependency.
616      * <p/>This loops on all current dependency looking for the value to replace and then replaces
617      * it.
618      * <p/>This both updates the in-memory {@link #mProperties} values and on-disk
619      * project.properties file.
620      * @param oldValue the old value to replace
621      * @param newValue the new value to set.
622      * @return the status of the replacement. If null, no replacement was done (value not found).
623      */
replaceLibraryProperty(String oldValue, String newValue)624     private IStatus replaceLibraryProperty(String oldValue, String newValue) {
625         int index = 1;
626         while (true) {
627             String propName = ProjectProperties.PROPERTY_LIB_REF + Integer.toString(index++);
628             String rootPath = mProperties.getProperty(propName);
629 
630             if (rootPath == null) {
631                 break;
632             }
633 
634             if (rootPath.equals(oldValue)) {
635                 // need to update the properties. Get a working copy to change it and save it on
636                 // disk since ProjectProperties is read-only.
637                 ProjectPropertiesWorkingCopy workingCopy = mProperties.makeWorkingCopy();
638                 workingCopy.setProperty(propName, newValue);
639                 try {
640                     workingCopy.save();
641 
642                     // reload the properties with the new values from the disk.
643                     mProperties.reload();
644                 } catch (Exception e) {
645                     return new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID, String.format(
646                             "Failed to save %1$s for project %2$s",
647                                     mProperties.getType() .getFilename(), mProject.getName()),
648                             e);
649 
650                 }
651                 return Status.OK_STATUS;
652             }
653         }
654 
655         return null;
656     }
657 
658     /**
659      * Update the full library list, including indirect dependencies. The result is returned by
660      * {@link #getFullLibraryProjects()}.
661      */
updateFullLibraryList()662     void updateFullLibraryList() {
663         ArrayList<IProject> list = new ArrayList<IProject>();
664         synchronized (mLibraries) {
665             buildFullLibraryDependencies(mLibraries, list);
666         }
667 
668         mLibraryProjects = Collections.unmodifiableList(list);
669     }
670 
671     /**
672      * Resolves a given list of libraries, finds out if they depend on other libraries, and
673      * returns a full list of all the direct and indirect dependencies in the proper order (first
674      * is higher priority when calling aapt).
675      * @param inLibraries the libraries to resolve
676      * @param outLibraries where to store all the libraries.
677      */
buildFullLibraryDependencies(List<LibraryState> inLibraries, ArrayList<IProject> outLibraries)678     private void buildFullLibraryDependencies(List<LibraryState> inLibraries,
679             ArrayList<IProject> outLibraries) {
680         // loop in the inverse order to resolve dependencies on the libraries, so that if a library
681         // is required by two higher level libraries it can be inserted in the correct place
682         for (int i = inLibraries.size() - 1  ; i >= 0 ; i--) {
683             LibraryState library = inLibraries.get(i);
684 
685             // get its libraries if possible
686             ProjectState libProjectState = library.getProjectState();
687             if (libProjectState != null) {
688                 List<LibraryState> dependencies = libProjectState.getLibraries();
689 
690                 // build the dependencies for those libraries
691                 buildFullLibraryDependencies(dependencies, outLibraries);
692 
693                 // and add the current library (if needed) in front (higher priority)
694                 if (outLibraries.contains(libProjectState.getProject()) == false) {
695                     outLibraries.add(0, libProjectState.getProject());
696                 }
697             }
698         }
699     }
700 
701 
702     /**
703      * Converts a path containing only / by the proper platform separator.
704      */
convertPath(String path)705     private String convertPath(String path) {
706         return path.replaceAll("/", Matcher.quoteReplacement(File.separator)); //$NON-NLS-1$
707     }
708 
709     /**
710      * Normalizes a relative path.
711      */
normalizePath(String path)712     private String normalizePath(String path) {
713         path = convertPath(path);
714         if (path.endsWith("/")) { //$NON-NLS-1$
715             path = path.substring(0, path.length() - 1);
716         }
717         return path;
718     }
719 
720     @Override
equals(Object obj)721     public boolean equals(Object obj) {
722         if (obj instanceof ProjectState) {
723             return mProject.equals(((ProjectState) obj).mProject);
724         } else if (obj instanceof IProject) {
725             return mProject.equals(obj);
726         }
727 
728         return false;
729     }
730 
731     @Override
hashCode()732     public int hashCode() {
733         return mProject.hashCode();
734     }
735 
736     @Override
toString()737     public String toString() {
738         return mProject.getName();
739     }
740 }
741