/**
   * Returns whether a given library project is needed by the receiver.
   *
   * <p>If the library is needed, this finds the matching {@link LibraryState}, initializes it so
   * that it contains the library's {@link IProject} object (so that {@link
   * LibraryState#getProjectState()} does not return null) and then returns it.
   *
   * @param libraryProject the library project to check.
   * @return a non null object if the project is a library dependency, <code>null</code> otherwise.
   * @see LibraryState#getProjectState()
   */
  public LibraryState needs(ProjectState libraryProject) {
    // compute current location
    File projectFile = mProject.getLocation().toFile();

    // get the location of the library.
    File libraryFile = libraryProject.getProject().getLocation().toFile();

    // loop on all libraries and check if the path match
    synchronized (mLibraries) {
      for (LibraryState state : mLibraries) {
        if (state.getProjectState() == null) {
          File library = new File(projectFile, state.getRelativePath());
          try {
            File absPath = library.getCanonicalFile();
            if (absPath.equals(libraryFile)) {
              state.setProject(libraryProject);
              return state;
            }
          } catch (IOException e) {
            // ignore this library
          }
        }
      }
    }

    return null;
  }
  /**
   * Reloads the content of the properties.
   *
   * <p>This also reset the reference to the target as it may have changed, therefore this should be
   * followed by a call to {@link Sdk#loadTarget(ProjectState)}.
   *
   * <p>If the project libraries changes, they are updated to a certain extent.<br>
   * Removed libraries are removed from the state list, and added to the {@link LibraryDifference}
   * object that is returned so that they can be processed.<br>
   * Added libraries are added to the state (as new {@link LibraryState} objects), but their
   * IProject is not resolved. {@link ProjectState#needs(ProjectState)} should be called afterwards
   * to properly initialize the libraries.
   *
   * @return an instance of {@link LibraryDifference} describing the change in libraries.
   */
  public LibraryDifference reloadProperties() {
    mTarget = null;
    mProperties.reload();

    // compare/reload the libraries.

    // if the order change it won't impact the java part, so instead try to detect removed/added
    // libraries.

    LibraryDifference diff = new LibraryDifference();

    synchronized (mLibraries) {
      List<LibraryState> oldLibraries = new ArrayList<LibraryState>(mLibraries);
      mLibraries.clear();

      // load the libraries
      int index = 1;
      while (true) {
        String propName = ProjectProperties.PROPERTY_LIB_REF + Integer.toString(index++);
        String rootPath = mProperties.getProperty(propName);

        if (rootPath == null) {
          break;
        }

        // search for a library with the same path (not exact same string, but going
        // to the same folder).
        String convertedPath = convertPath(rootPath);
        boolean found = false;
        for (int i = 0; i < oldLibraries.size(); i++) {
          LibraryState libState = oldLibraries.get(i);
          if (libState.equals(convertedPath)) {
            // it's a match. move it back to mLibraries and remove it from the
            // old library list.
            found = true;
            mLibraries.add(libState);
            oldLibraries.remove(i);
            break;
          }
        }

        if (found == false) {
          diff.added = true;
          mLibraries.add(new LibraryState(convertedPath));
        }
      }

      // whatever's left in oldLibraries is removed.
      diff.removed = oldLibraries.size() > 0;

      // update the library with what IProjet are known at the time.
      updateFullLibraryList();
    }

    return diff;
  }
  /** Returns whether the project is missing some required libraries. */
  public boolean isMissingLibraries() {
    synchronized (mLibraries) {
      for (LibraryState state : mLibraries) {
        if (state.getProjectState() == null) {
          return true;
        }
      }
    }

    return false;
  }
  /**
   * Returns the {@link LibraryState} object for a given <var>name</var>. This can only return a
   * non-null object if the link between the main project's {@link IProject} and the library's
   * {@link IProject} was done.
   *
   * @return the matching LibraryState or <code>null</code>
   * @see #needs(IProject)
   */
  public LibraryState getLibrary(String name) {
    synchronized (mLibraries) {
      for (LibraryState state : mLibraries) {
        ProjectState ps = state.getProjectState();
        if (ps != null && ps.getProject().getName().equals(name)) {
          return state;
        }
      }
    }

    return null;
  }
  /**
   * Returns the {@link LibraryState} object for a given {@link IProject}. This can only return a
   * non-null object if the link between the main project's {@link IProject} and the library's
   * {@link IProject} was done.
   *
   * @return the matching LibraryState or <code>null</code>
   * @see #needs(ProjectState)
   */
  public LibraryState getLibrary(IProject library) {
    synchronized (mLibraries) {
      for (LibraryState state : mLibraries) {
        ProjectState ps = state.getProjectState();
        if (ps != null && ps.getProject().equals(library)) {
          return state;
        }
      }
    }

    return null;
  }
  /**
   * Returns whether the project depends on a given <var>library</var>
   *
   * @param library the library to check.
   * @return true if the project depends on the library. This is not affected by whether the link
   *     was done through {@link #needs(ProjectState)}.
   */
  public boolean dependsOn(ProjectState library) {
    synchronized (mLibraries) {
      for (LibraryState state : mLibraries) {
        if (state != null
            && state.getProjectState() != null
            && library.getProject().equals(state.getProjectState().getProject())) {
          return true;
        }
      }
    }

    return false;
  }
    @Override
    public boolean equals(Object obj) {
      if (obj instanceof LibraryState) {
        // the only thing that's always non-null is the relative path.
        LibraryState objState = (LibraryState) obj;
        return mRelativePath.equals(objState.mRelativePath)
            && getMainProjectState().equals(objState.getMainProjectState());
      } else if (obj instanceof ProjectState || obj instanceof IProject) {
        return mProjectState != null && mProjectState.equals(obj);
      } else if (obj instanceof String) {
        return normalizePath(mRelativePath).equals(normalizePath((String) obj));
      }

      return false;
    }
  /**
   * Resolves a given list of libraries, finds out if they depend on other libraries, and returns a
   * full list of all the direct and indirect dependencies in the proper order (first is higher
   * priority when calling aapt).
   *
   * @param inLibraries the libraries to resolve
   * @param outLibraries where to store all the libraries.
   */
  private void buildFullLibraryDependencies(
      List<LibraryState> inLibraries, ArrayList<IProject> outLibraries) {
    // loop in the inverse order to resolve dependencies on the libraries, so that if a library
    // is required by two higher level libraries it can be inserted in the correct place
    for (int i = inLibraries.size() - 1; i >= 0; i--) {
      LibraryState library = inLibraries.get(i);

      // get its libraries if possible
      ProjectState libProjectState = library.getProjectState();
      if (libProjectState != null) {
        List<LibraryState> dependencies = libProjectState.getLibraries();

        // build the dependencies for those libraries
        buildFullLibraryDependencies(dependencies, outLibraries);

        // and add the current library (if needed) in front (higher priority)
        if (outLibraries.contains(libProjectState.getProject()) == false) {
          outLibraries.add(0, libProjectState.getProject());
        }
      }
    }
  }
  /**
   * Updates a library with a new path.
   *
   * <p>This method acts both as a check and an action. If the project does not depend on the given
   * <var>oldRelativePath</var> then no action is done and <code>null</code> is returned.
   *
   * <p>If the project depends on the library, then the project is updated with the new path, and
   * the {@link LibraryState} for the library is returned.
   *
   * <p>Updating the project does two things:
   *
   * <ul>
   *   <li>Update LibraryState with new relative path and new {@link IProject} object.
   *   <li>Update the main project's <code>project.properties</code> with the new relative path for
   *       the changed library.
   * </ul>
   *
   * @param oldRelativePath the old library path relative to this project
   * @param newRelativePath the new library path relative to this project
   * @param newLibraryState the new {@link ProjectState} object.
   * @return a non null object if the project depends on the library.
   * @see LibraryState#getProjectState()
   */
  public LibraryState updateLibrary(
      String oldRelativePath, String newRelativePath, ProjectState newLibraryState) {
    // compute current location
    File projectFile = mProject.getLocation().toFile();

    // loop on all libraries and check if the path matches
    synchronized (mLibraries) {
      for (LibraryState state : mLibraries) {
        if (state.getProjectState() == null) {
          try {
            // oldRelativePath may not be the same exact string as the
            // one in the project properties (trailing separator could be different
            // for instance).
            // Use java.io.File to deal with this and also do a platform-dependent
            // path comparison
            File library1 = new File(projectFile, oldRelativePath);
            File library2 = new File(projectFile, state.getRelativePath());
            if (library1.getCanonicalPath().equals(library2.getCanonicalPath())) {
              // save the exact property string to replace.
              String oldProperty = state.getRelativePath();

              // then update the LibraryPath.
              state.setRelativePath(newRelativePath);
              state.setProject(newLibraryState);

              // update the project.properties file
              IStatus status = replaceLibraryProperty(oldProperty, newRelativePath);
              if (status != null) {
                if (status.getSeverity() != IStatus.OK) {
                  // log the error somehow.
                }
              } else {
                // This should not happen since the library wouldn't be here in the
                // first place
              }

              // return the LibraryState object.
              return state;
            }
          } catch (IOException e) {
            // ignore this library
          }
        }
      }
    }

    return null;
  }