public static DotClassPath parse(File dotClasspath, List<Link> links) throws IOException {
    Document dotClasspathXml;
    try {
      dotClasspathXml =
          XMLUtil.parse(
              new InputSource(Utilities.toURI(dotClasspath).toString()),
              false,
              true,
              XMLUtil.defaultErrorHandler(),
              null);
    } catch (SAXException e) {
      IOException ioe =
          (IOException) new IOException(dotClasspath + ": " + e.toString()).initCause(e); // NOI18N
      throw ioe;
    }
    Element classpathEl = dotClasspathXml.getDocumentElement();
    if (!"classpath".equals(classpathEl.getLocalName())) { // NOI18N
      return empty();
    }
    List<Element> classpathEntryEls;
    try {
      classpathEntryEls = XMLUtil.findSubElements(classpathEl);
    } catch (IllegalArgumentException x) {
      throw new IOException(x);
    }
    if (classpathEntryEls == null) {
      return empty();
    }

    // accessrules are ignored as they are not supported in NB anyway, eg:
    /*
    <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER">
        <accessrules>
            <accessrule kind="accessible" pattern="com/sun/management/*"/>
            <accessrule kind="nonaccessible" pattern="com/sun/**"/>
        </accessrules>
    </classpathentry>
     */

    List<DotClassPathEntry> classpath = new ArrayList<DotClassPathEntry>();
    List<DotClassPathEntry> sources = new ArrayList<DotClassPathEntry>();
    DotClassPathEntry output = null;
    DotClassPathEntry jre = null;
    for (Element classpathEntry : classpathEntryEls) {
      Map<String, String> props = new HashMap<String, String>();
      NamedNodeMap attrs = classpathEntry.getAttributes();
      String linkName = null;
      for (int i = 0; i < attrs.getLength(); i++) {
        Node n = attrs.item(i);
        String key = n.getNodeName();
        String value = classpathEntry.getAttribute(n.getNodeName());
        if (DotClassPathEntry.ATTRIBUTE_PATH.equals(key)) {
          String resolvedLink = resolveLink(value, links);
          if (resolvedLink != null) {
            linkName = value;
            value = resolvedLink;
          }
        }
        props.put(key, value);
      }
      Element entryAttrs = XMLUtil.findElement(classpathEntry, "attributes", null); // NOI18N
      if (entryAttrs != null) {
        /*
        <classpathentry kind="lib" path="/home/dev/hibernate-annotations-3.3.1.GA/lib/hibernate-commons-annotations.jar" sourcepath="/home/dev/hibernate-annotations-3.3.1.GA/src">
            <attributes>
                <attribute name="javadoc_location" value="file:/home/dev/hibernate-annotations-3.3.1.GA/doc/api/"/>
            </attributes>
        </classpathentry>
         */
        List<Element> attrsList = XMLUtil.findSubElements(entryAttrs);
        if (attrsList != null) {
          for (Element e : attrsList) {
            props.put(e.getAttribute("name"), e.getAttribute("value")); // NOI18N
          }
        }
      }
      DotClassPathEntry entry = new DotClassPathEntry(props, linkName);
      if (entry.getKind() == DotClassPathEntry.Kind.SOURCE) {
        sources.add(entry);
      } else if (entry.getKind() == DotClassPathEntry.Kind.OUTPUT) {
        assert output == null : "there should be always just one default output"; // NOI18N
        output = entry;
      } else if (entry.getKind() == DotClassPathEntry.Kind.CONTAINER
          && entry.getRawPath().startsWith(Workspace.DEFAULT_JRE_CONTAINER)) {
        jre = entry;
      } else {
        classpath.add(entry);
      }
    }
    return new DotClassPath(classpath, sources, output, jre);
  }