Version 0.5.0.0 .

svn merge -r 21520:21754 https://dart.googlecode.com/svn/branches/bleeding_edge trunk

git-svn-id: http://dart.googlecode.com/svn/trunk@21768 260f80e4-7a28-3924-810f-c04153c831b5
diff --git a/lib/compiler/implementation/lib/web.dart b/lib/compiler/implementation/lib/web.dart
new file mode 100644
index 0000000..a75db7b
--- /dev/null
+++ b/lib/compiler/implementation/lib/web.dart
@@ -0,0 +1,9 @@
+// Copyright (c) 2012, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+#library("web");
+
+String htmlEscape(String text) {
+  throw "Unimplemented: web::htmlEscape(String).";
+}
diff --git a/lib/compiler/implementation/lib/web.dartp b/lib/compiler/implementation/lib/web.dartp
new file mode 100644
index 0000000..c3ba2ad
--- /dev/null
+++ b/lib/compiler/implementation/lib/web.dartp
@@ -0,0 +1,13 @@
+// Copyright (c) 2012, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+// Patch file for dart:web
+
+/*patch*/ String htmlEscape(String text) {
+  return text.replaceAll("&", "&")
+             .replaceAll("<", "&lt;")
+             .replaceAll(">", "&gt;")
+             .replaceAll('"', "&quot;")
+             .replaceAll("'", "&apos;");  // Different from original.
+}
diff --git a/lib/dom/templates/html/dartium/factoryprovider__Elements.darttemplate b/lib/dom/templates/html/dartium/factoryprovider__Elements.darttemplate
new file mode 100644
index 0000000..8fe27e5
--- /dev/null
+++ b/lib/dom/templates/html/dartium/factoryprovider__Elements.darttemplate
@@ -0,0 +1,7 @@
+// Copyright (c) 2012, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+class _Elements {
+
+$!FACTORY_METHODS}
diff --git a/lib/dom/templates/html/dartium/impl_EventTarget.darttemplate b/lib/dom/templates/html/dartium/impl_EventTarget.darttemplate
new file mode 100644
index 0000000..1b4a00d
--- /dev/null
+++ b/lib/dom/templates/html/dartium/impl_EventTarget.darttemplate
@@ -0,0 +1,106 @@
+// Copyright (c) 2012, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+class _EventsImpl implements Events {
+  // TODO(podivilov): add type.
+  final _ptr;
+
+  final Map<String, EventListenerList> _listenerMap;
+
+  _EventsImpl(this._ptr) : _listenerMap = <EventListenerList>{};
+
+  EventListenerList operator [](String type) {
+    return _listenerMap.putIfAbsent(type,
+      () => new _EventListenerListImpl(_ptr, type));
+  }
+}
+
+class _EventListenerWrapper {
+  final EventListener raw;
+  final Function wrapped;
+  final bool useCapture;
+  _EventListenerWrapper(this.raw, this.wrapped, this.useCapture);
+}
+
+class _EventListenerListImpl implements EventListenerList {
+  // TODO(podivilov): add type.
+  final _ptr;
+  final String _type;
+  List<_EventListenerWrapper> _wrappers;
+
+  _EventListenerListImpl(this._ptr, this._type) :
+    // TODO(jacobr): switch to <_EventListenerWrapper>[] when the VM allow it.
+    _wrappers = new List<_EventListenerWrapper>();
+
+  EventListenerList add(EventListener listener, [bool useCapture = false]) {
+    _add(listener, useCapture);
+    return this;
+  }
+
+  EventListenerList remove(EventListener listener, [bool useCapture = false]) {
+    _remove(listener, useCapture);
+    return this;
+  }
+
+  bool dispatch(Event evt) {
+    // TODO(jacobr): what is the correct behavior here. We could alternately
+    // force the event to have the expected type.
+    assert(evt.type == _type);
+    return _ptr.$dom_dispatchEvent(evt);
+  }
+
+  void _add(EventListener listener, bool useCapture) {
+    _ptr.$dom_addEventListener(_type,
+                          _findOrAddWrapper(listener, useCapture),
+                          useCapture);
+  }
+
+  void _remove(EventListener listener, bool useCapture) {
+    Function wrapper = _removeWrapper(listener, useCapture);
+    if (wrapper !== null) {
+      _ptr.$dom_removeEventListener(_type, wrapper, useCapture);
+    }
+  }
+
+  Function _removeWrapper(EventListener listener, bool useCapture) {
+    if (_wrappers === null) {
+      return null;
+    }
+    for (int i = 0; i < _wrappers.length; i++) {
+      _EventListenerWrapper wrapper = _wrappers[i];
+      if (wrapper.raw === listener && wrapper.useCapture == useCapture) {
+        // Order doesn't matter so we swap with the last element instead of
+        // performing a more expensive remove from the middle of the list.
+        if (i + 1 != _wrappers.length) {
+          _wrappers[i] = _wrappers.removeLast();
+        } else {
+          _wrappers.removeLast();
+        }
+        return wrapper.wrapped;
+      }
+    }
+    return null;
+  }
+
+  Function _findOrAddWrapper(EventListener listener, bool useCapture) {
+    if (_wrappers === null) {
+      _wrappers = <_EventListenerWrapper>[];
+    } else {
+      for (_EventListenerWrapper wrapper in _wrappers) {
+        if (wrapper.raw === listener && wrapper.useCapture == useCapture) {
+          return wrapper.wrapped;
+        }
+      }
+    }
+    final wrapped = (e) { listener(e); };
+    _wrappers.add(new _EventListenerWrapper(listener, wrapped, useCapture));
+    return wrapped;
+  }
+}
+
+class $CLASSNAME$EXTENDS$IMPLEMENTS$NATIVESPEC {
+/*
+$!MEMBERS
+*/
+}
diff --git a/lib/dom/templates/html/frog/factoryprovider__Elements.darttemplate b/lib/dom/templates/html/frog/factoryprovider__Elements.darttemplate
new file mode 100644
index 0000000..8fe27e5
--- /dev/null
+++ b/lib/dom/templates/html/frog/factoryprovider__Elements.darttemplate
@@ -0,0 +1,7 @@
+// Copyright (c) 2012, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+class _Elements {
+
+$!FACTORY_METHODS}
diff --git a/lib/dom/templates/html/frog/impl_EventTarget.darttemplate b/lib/dom/templates/html/frog/impl_EventTarget.darttemplate
new file mode 100644
index 0000000..893f3d2
--- /dev/null
+++ b/lib/dom/templates/html/frog/impl_EventTarget.darttemplate
@@ -0,0 +1,61 @@
+// Copyright (c) 2012, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+class _EventsImpl implements Events {
+  /* Raw event target. */
+  // TODO(jacobr): it would be nice if we could specify this as
+  // _EventTargetImpl or EventTarget
+  final Dynamic _ptr;
+
+  _EventsImpl(this._ptr);
+
+  _EventListenerListImpl operator [](String type) {
+    return new _EventListenerListImpl(_ptr, type);
+  }
+}
+
+class _EventListenerListImpl implements EventListenerList {
+  
+  // TODO(jacobr): make this _EventTargetImpl
+  final Dynamic _ptr;
+  final String _type;
+
+  _EventListenerListImpl(this._ptr, this._type);
+
+  // TODO(jacobr): implement equals.
+
+  _EventListenerListImpl add(EventListener listener,
+      [bool useCapture = false]) {
+    _add(listener, useCapture);
+    return this;
+  }
+
+  _EventListenerListImpl remove(EventListener listener,
+      [bool useCapture = false]) {
+    _remove(listener, useCapture);
+    return this;
+  }
+
+  bool dispatch(Event evt) {
+    // TODO(jacobr): what is the correct behavior here. We could alternately
+    // force the event to have the expected type.
+    assert(evt.type == _type);
+    return _ptr.$dom_dispatchEvent(evt);
+  }
+
+  void _add(EventListener listener, bool useCapture) {
+    _ptr.$dom_addEventListener(_type, listener, useCapture);
+  }
+
+  void _remove(EventListener listener, bool useCapture) {
+    _ptr.$dom_removeEventListener(_type, listener, useCapture);
+  }
+}
+
+
+class $CLASSNAME$EXTENDS$IMPLEMENTS$NATIVESPEC {
+
+  Events get on() => new _EventsImpl(this);
+$!MEMBERS
+}
diff --git a/tools/VERSION b/tools/VERSION
index 453bc99..e1d42d1 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -1,4 +1,4 @@
 MAJOR 0
-MINOR 1
-BUILD 2
+MINOR 5
+BUILD 0
 PATCH 0
diff --git a/utils/pub/solver/greedy_solver.dart b/utils/pub/solver/greedy_solver.dart
new file mode 100644
index 0000000..e664ea2
--- /dev/null
+++ b/utils/pub/solver/greedy_solver.dart
@@ -0,0 +1,556 @@
+// Copyright (c) 2012, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+/// Attempts to resolve a set of version constraints for a package dependency
+/// graph and select an appropriate set of best specific versions for all
+/// dependent packages. It works iteratively and tries to reach a stable
+/// solution where the constraints of all dependencies are met. If it fails to
+/// reach a solution after a certain number of iterations, it assumes the
+/// dependency graph is unstable and reports and error.
+///
+/// There are two fundamental operations in the process of iterating over the
+/// graph:
+///
+/// 1.  Changing the selected concrete version of some package. (This includes
+///     adding and removing a package too, which is considering changing the
+///     version to or from "none".) In other words, a node has changed.
+/// 2.  Changing the version constraint that one package places on another. In
+///     other words, and edge has changed.
+///
+/// Both of these events have a corresponding (potentional) async operation and
+/// roughly cycle back and forth between each other. When we change the version
+/// of package changes, we asynchronously load the pubspec for the new version.
+/// When that's done, we compare the dependencies of the new version versus the
+/// old one. For everything that differs, we change those constraints between
+/// this package and that dependency.
+///
+/// When a constraint on a package changes, we re-calculate the overall
+/// constraint on that package. I.e. with a shared dependency, we intersect all
+/// of the constraints that its depending packages place on it. If that overall
+/// constraint changes (say from "<3.0.0" to "<2.5.0"), then the currently
+/// picked version for that package may fall outside of the new constraint. If
+/// that happens, we find the new best version that meets the updated constraint
+/// and then the change the package to use that version. That cycles back up to
+/// the beginning again.
+library version_solver1;
+
+import 'dart:async';
+import 'dart:collection' show Queue;
+import 'dart:math' as math;
+
+import '../lock_file.dart';
+import '../log.dart' as log;
+import '../package.dart';
+import '../source.dart';
+import '../source_registry.dart';
+import '../version.dart';
+import 'version_solver.dart';
+
+class GreedyVersionSolver extends VersionSolver {
+  final _packages = <String, DependencyNode>{};
+  final _work = new Queue<WorkItem>();
+  int _numIterations = 0;
+
+  GreedyVersionSolver(SourceRegistry sources, Package root, LockFile lockFile,
+                      List<String> useLatest)
+      : super(sources, root, lockFile, useLatest);
+
+  /// The non-backtracking solver always only tries one solution.
+  int get attemptedSolutions => 1;
+
+  void forceLatestVersion(String package) {
+    // TODO(nweiz): How do we want to detect and handle unknown dependencies
+    // here?
+    getDependency(package).useLatestVersion = true;
+  }
+
+  Future<List<PackageId>> runSolver() {
+    // Kick off the work by adding the root package at its concrete version to
+    // the dependency graph.
+    enqueue(new AddConstraint('(entrypoint)', new PackageRef.root(root)));
+
+    Future processNextWorkItem(_) {
+      while (true) {
+        // Stop if we are done.
+        if (_work.isEmpty) return new Future.value(buildResults());
+
+        // If we appear to be stuck in a loop, then we probably have an unstable
+        // graph, bail. We guess this based on a rough heuristic that it should
+        // only take a certain number of steps to solve a graph with a given
+        // number of connections.
+        // TODO(rnystrom): These numbers here are magic and arbitrary. Tune
+        // when we have a better picture of real-world package topologies.
+        _numIterations++;
+        if (_numIterations > math.max(50, _packages.length * 5)) {
+          throw new CouldNotSolveException();
+        }
+
+        // Run the first work item.
+        var future = _work.removeFirst().process(this);
+
+        // If we have an async operation to perform, chain the loop to resume
+        // when it's done. Otherwise, just loop synchronously.
+        if (future != null) {
+          return future.then(processNextWorkItem);
+        }
+      }
+    }
+
+    return processNextWorkItem(null);
+  }
+
+  void enqueue(WorkItem work) {
+    _work.add(work);
+  }
+
+  DependencyNode getDependency(String package) {
+    // There can be unused dependencies in the graph, so just create an empty
+    // one if needed.
+    _packages.putIfAbsent(package, () => new DependencyNode(package));
+    return _packages[package];
+  }
+
+  /// Sets the best selected version of [package] to [version].
+  void setVersion(String package, Version version) {
+    _packages[package].version = version;
+  }
+
+  /// Returns the most recent version of [dependency] that satisfies all of its
+  /// version constraints.
+  Future<Version> getBestVersion(DependencyNode dependency) {
+    return cache.getVersions(dependency.name,
+        dependency.source, dependency.description).then((versions) {
+      var best = null;
+      for (var ref in versions) {
+        if (dependency.useLatestVersion ||
+            dependency.constraint.allows(ref.version)) {
+          if (best == null || ref.version > best) best = ref.version;
+        }
+      }
+
+      // TODO(rnystrom): Better exception.
+      if (best == null) {
+        if (tryUnlockDepender(dependency)) return null;
+        throw new NoVersionException(dependency.name, dependency.constraint,
+            dependency.toList());
+      } else if (!dependency.constraint.allows(best)) {
+        if (tryUnlockDepender(dependency)) return null;
+        throw new CouldNotUpdateException(
+            dependency.name, dependency.constraint, best);
+      }
+
+      return best;
+    });
+  }
+
+  /// Looks for a package that depends (transitively) on [dependency] and has
+  /// its version locked in the lockfile. If one is found, enqueues an
+  /// [UnlockPackage] work item for it and returns true. Otherwise, returns
+  /// false.
+  ///
+  /// This does a breadth-first search; immediate dependers will be unlocked
+  /// first, followed by transitive dependers.
+  bool tryUnlockDepender(DependencyNode dependency, [Set<String> seen]) {
+    if (seen == null) seen = new Set();
+    // Avoid an infinite loop if there are circular dependencies.
+    if (seen.contains(dependency.name)) return false;
+    seen.add(dependency.name);
+
+    for (var dependerName in dependency.dependers) {
+      var depender = getDependency(dependerName);
+      var locked = lockFile.packages[dependerName];
+      if (locked != null && depender.version == locked.version &&
+          depender.source.name == locked.source.name) {
+        enqueue(new UnlockPackage(depender));
+        return true;
+      }
+    }
+
+    return dependency.dependers.map(getDependency).any((subdependency) =>
+        tryUnlockDepender(subdependency, seen));
+  }
+
+  List<PackageId> buildResults() {
+    return _packages.values
+        .where((dep) => dep.isDependedOn)
+        .map(_dependencyToPackageId)
+        .toList();
+  }
+
+  PackageId _dependencyToPackageId(DependencyNode dep) {
+    var description = dep.description;
+
+    // If the lockfile contains a fully-resolved description for the package,
+    // use that. This allows e.g. Git to ensure that the same commit is used.
+    var lockedPackage = lockFile.packages[dep.name];
+    if (lockedPackage != null && lockedPackage.version == dep.version &&
+        lockedPackage.source.name == dep.source.name &&
+        dep.source.descriptionsEqual(
+            description, lockedPackage.description)) {
+      description = lockedPackage.description;
+    }
+
+    return new PackageId(dep.name, dep.source, dep.version, description);
+  }
+}
+
+/// The constraint solver works by iteratively processing a queue of work items.
+/// Each item is a single atomic change to the dependency graph. Handling them
+/// in a queue lets us handle asynchrony (resolving versions requires
+/// information from servers) as well as avoid deeply nested recursion.
+abstract class WorkItem {
+  /// Processes this work item. Returns a future that completes when the work is
+  /// done. If `null` is returned, that means the work has completed
+  /// synchronously and the next item can be started immediately.
+  Future process(GreedyVersionSolver solver);
+}
+
+/// The best selected version for a package has changed to [version]. If the
+/// previous version of the package is `null`, that means the package is being
+/// added to the graph. If [version] is `null`, it is being removed.
+class ChangeVersion implements WorkItem {
+  /// The name of the package whose version is being changed.
+  final String package;
+
+  /// The source of the package whose version is changing.
+  final Source source;
+
+  /// The description identifying the package whose version is changing.
+  final description;
+
+  /// The new selected version.
+  final Version version;
+
+  ChangeVersion(this.package, this.source, this.description, this.version);
+
+  Future process(GreedyVersionSolver solver) {
+    log.fine("Changing $package to version $version.");
+
+    var dependency = solver.getDependency(package);
+    var oldVersion = dependency.version;
+    solver.setVersion(package, version);
+
+    // The dependencies between the old and new version may be different. Walk
+    // them both and update any constraints that differ between the two.
+    return Future.wait([
+        getDependencyRefs(solver, oldVersion),
+        getDependencyRefs(solver, version)]).then((list) {
+      var oldDependencyRefs = list[0];
+      var newDependencyRefs = list[1];
+
+      for (var oldRef in oldDependencyRefs.values) {
+        if (newDependencyRefs.containsKey(oldRef.name)) {
+          // The dependency is in both versions of this package, but its
+          // constraint may have changed.
+          var newRef = newDependencyRefs.remove(oldRef.name);
+          solver.enqueue(new AddConstraint(package, newRef));
+        } else {
+          // The dependency is not in the new version of the package, so just
+          // remove its constraint.
+          solver.enqueue(new RemoveConstraint(package, oldRef.name));
+        }
+      }
+
+      // Everything that's left is a depdendency that's only in the new
+      // version of the package.
+      for (var newRef in newDependencyRefs.values) {
+        solver.enqueue(new AddConstraint(package, newRef));
+      }
+    });
+  }
+
+  /// Get the dependencies at [version] of the package being changed.
+  Future<Map<String, PackageRef>> getDependencyRefs(VersionSolver solver,
+      Version version) {
+    // If there is no version, it means no package, so no dependencies.
+    if (version == null) {
+      return new Future<Map<String, PackageRef>>.value(<String, PackageRef>{});
+    }
+
+    var id = new PackageId(package, source, version, description);
+    return solver.cache.getPubspec(id).then((pubspec) {
+      var dependencies = <String, PackageRef>{};
+      for (var dependency in pubspec.dependencies) {
+        dependencies[dependency.name] = dependency;
+      }
+
+      // Include dev dependencies only from the root package.
+      if (id.isRoot) {
+        for (var dependency in pubspec.devDependencies) {
+          dependencies[dependency.name] = dependency;
+        }
+      }
+
+      return dependencies;
+    });
+  }
+}
+
+/// A constraint that a depending package places on a dependent package has
+/// changed.
+///
+/// This is an abstract class that contains logic for updating the dependency
+/// graph once a dependency has changed. Changing the dependency is the
+/// responsibility of subclasses.
+abstract class ChangeConstraint implements WorkItem {
+  Future process(GreedyVersionSolver solver);
+
+  void undo(GreedyVersionSolver solver);
+
+  Future _processChange(GreedyVersionSolver solver,
+                        DependencyNode oldDependency,
+                        DependencyNode newDependency) {
+    var name = newDependency.name;
+    var source = oldDependency.source != null ?
+      oldDependency.source : newDependency.source;
+    var description = oldDependency.description != null ?
+      oldDependency.description : newDependency.description;
+    var oldConstraint = oldDependency.constraint;
+    var newConstraint = newDependency.constraint;
+
+    // If the package is over-constrained, i.e. the packages depending have
+    // disjoint constraints, then try unlocking a depender that's locked by the
+    // lockfile. If there are no remaining locked dependencies, throw an error.
+    if (newConstraint != null && newConstraint.isEmpty) {
+      if (solver.tryUnlockDepender(newDependency)) {
+        undo(solver);
+        return null;
+      }
+
+      throw new DisjointConstraintException(name, newDependency.toList());
+    }
+
+    // If this constraint change didn't cause the overall constraint on the
+    // package to change, then we don't need to do any further work.
+    if (oldConstraint == newConstraint) return null;
+
+    // If the dependency has been cut free from the graph, just remove it.
+    if (!newDependency.isDependedOn) {
+      solver.enqueue(new ChangeVersion(name, source, description, null));
+      return null;
+    }
+
+    // If the dependency is on the root package, then we don't need to do
+    // anything since it's already at the best version.
+    if (name == solver.root.name) {
+      solver.enqueue(new ChangeVersion(
+          name, source, description, solver.root.version));
+      return null;
+    }
+
+    // If the dependency is on a package in the lockfile, use the lockfile's
+    // version for that package if it's valid given the other constraints.
+    var lockedPackage = solver.lockFile.packages[name];
+    if (lockedPackage != null && newDependency.source == lockedPackage.source) {
+      var lockedVersion = lockedPackage.version;
+      if (newConstraint.allows(lockedVersion)) {
+        solver.enqueue(
+            new ChangeVersion(name, source, description, lockedVersion));
+        return null;
+      }
+    }
+
+    // The constraint has changed, so see what the best version of the package
+    // that meets the new constraint is.
+    return solver.getBestVersion(newDependency).then((best) {
+      if (best == null) {
+        undo(solver);
+      } else if (newDependency.version != best) {
+        solver.enqueue(new ChangeVersion(name, source, description, best));
+      }
+    });
+  }
+}
+
+/// The constraint given by [ref] is being placed by [depender].
+class AddConstraint extends ChangeConstraint {
+  /// The package that has the dependency.
+  final String depender;
+
+  /// The package being depended on and the constraints being placed on it. The
+  /// source, version, and description in this ref are all considered
+  /// constraints on the dependent package.
+  final PackageRef ref;
+
+  AddConstraint(this.depender, this.ref);
+
+  Future process(GreedyVersionSolver solver) {
+    log.fine("Adding $depender's constraint $ref.");
+
+    var dependency = solver.getDependency(ref.name);
+    var oldDependency = dependency.clone();
+    dependency.placeConstraint(depender, ref);
+    return _processChange(solver, oldDependency, dependency);
+  }
+
+  void undo(GreedyVersionSolver solver) {
+    solver.getDependency(ref.name).removeConstraint(depender);
+  }
+}
+
+/// [depender] is no longer placing a constraint on [dependent].
+class RemoveConstraint extends ChangeConstraint {
+  /// The package that was placing a constraint on [dependent].
+  String depender;
+
+  /// The package that was being depended on.
+  String dependent;
+
+  /// The constraint that was removed.
+  PackageRef _removed;
+
+  RemoveConstraint(this.depender, this.dependent);
+
+  Future process(GreedyVersionSolver solver) {
+    log.fine("Removing $depender's constraint ($_removed) on $dependent.");
+
+    var dependency = solver.getDependency(dependent);
+    var oldDependency = dependency.clone();
+    _removed = dependency.removeConstraint(depender);
+    return _processChange(solver, oldDependency, dependency);
+  }
+
+  void undo(GreedyVersionSolver solver) {
+    solver.getDependency(dependent).placeConstraint(depender, _removed);
+  }
+}
+
+/// [package]'s version is no longer constrained by the lockfile.
+class UnlockPackage implements WorkItem {
+  /// The package being unlocked.
+  DependencyNode package;
+
+  UnlockPackage(this.package);
+
+  Future process(GreedyVersionSolver solver) {
+    log.fine("Unlocking ${package.name}.");
+
+    solver.lockFile.packages.remove(package.name);
+    return solver.getBestVersion(package).then((best) {
+      if (best == null) return null;
+      solver.enqueue(new ChangeVersion(
+          package.name, package.source, package.description, best));
+    });
+  }
+}
+
+/// Describes one [Package] in the [DependencyGraph] and keeps track of which
+/// packages depend on it and what constraints they place on it.
+class DependencyNode {
+  /// The name of the this dependency's package.
+  final String name;
+
+  /// The [PackageRefs] that represent constraints that depending packages have
+  /// placed on this one.
+  final Map<String, PackageRef> _refs;
+
+  /// The currently-selected best version for this dependency.
+  Version version;
+
+  /// Whether this dependency should always select the latest version.
+  bool useLatestVersion = false;
+
+  /// Gets whether or not any other packages are currently depending on this
+  /// one. If `false`, then it means this package is not part of the dependency
+  /// graph and should be omitted.
+  bool get isDependedOn => !_refs.isEmpty;
+
+  /// The names of all the packages that depend on this dependency.
+  Iterable<String> get dependers => _refs.keys;
+
+  /// Gets the overall constraint that all packages are placing on this one.
+  /// If no packages have a constraint on this one (which can happen when this
+  /// package is in the process of being added to the graph), returns `null`.
+  VersionConstraint get constraint {
+    if (_refs.isEmpty) return null;
+    return new VersionConstraint.intersection(
+        _refs.values.map((ref) => ref.constraint));
+  }
+
+  /// The source of this dependency's package.
+  Source get source {
+     var canonical = _canonicalRef();
+     if (canonical == null) return null;
+     return canonical.source;
+  }
+
+  /// The description of this dependency's package.
+  get description {
+     var canonical = _canonicalRef();
+     if (canonical == null) return null;
+     return canonical.description;
+  }
+
+  /// Return the PackageRef that has the canonical source and description for
+  /// this package. If any dependency is on the root package, that will be used;
+  /// otherwise, it will be the source and description that all dependencies
+  /// agree upon.
+  PackageRef _canonicalRef() {
+    if (_refs.isEmpty) return null;
+    var refs = _refs.values;
+    for (var ref in refs) {
+      if (ref.isRoot) return ref;
+    }
+    return refs.first;
+  }
+
+  DependencyNode(this.name)
+      : _refs = <String, PackageRef>{};
+
+  DependencyNode._clone(DependencyNode other)
+      : name = other.name,
+        version = other.version,
+        _refs = new Map<String, PackageRef>.from(other._refs);
+
+  /// Creates a copy of this dependency.
+  DependencyNode clone() => new DependencyNode._clone(this);
+
+  /// Places [ref] as a constraint from [package] onto this.
+  void placeConstraint(String package, PackageRef ref) {
+    var requiredDepender = _requiredDepender();
+    if (requiredDepender != null) {
+      var required = _refs[requiredDepender];
+      if (required.source.name != ref.source.name) {
+        throw new SourceMismatchException(name, [
+            new Dependency(requiredDepender, required),
+            new Dependency(package, ref)]);
+      } else if (!required.descriptionEquals(ref)) {
+        throw new DescriptionMismatchException(name, [
+            new Dependency(requiredDepender, required),
+            new Dependency(package, ref)]);
+      }
+    }
+
+    _refs[package] = ref;
+  }
+
+  /// Returns the name of a package whose constraint source and description
+  /// all other constraints must match. Returns null if there are no
+  /// requirements on new constraints.
+  String _requiredDepender() {
+    if (_refs.isEmpty) return null;
+
+    var dependers = _refs.keys.toList();
+    if (dependers.length == 1) {
+      var depender = dependers[0];
+      if (_refs[depender].isRoot) return null;
+      return depender;
+    }
+
+    return dependers[1];
+  }
+
+  /// Removes the constraint from [package] onto this.
+  PackageRef removeConstraint(String package) => _refs.remove(package);
+
+  /// Converts this to a list of [Dependency] objects like the error types
+  /// expect.
+  List<Dependency> toList() {
+    var result = <Dependency>[];
+    _refs.forEach((name, ref) {
+      result.add(new Dependency(name, ref));
+    });
+    return result;
+  }
+}