/// <summary> /// Resolve the path into zero or more concrete object references. /// Never returns null. The returned list is sorted by ordinal. /// </summary> public List <Reference> Resolve(Reference context, Resolver resolver) { try { resolver.Trace("Path={0} context={1} line {2}:{3}", this, context, _lineNumber, _linePosition); if (this == Empty) { return(new List <Reference>()); } // Start at the path's origin object. If the origin is the context // but the context is null or empty, then set the origin to the root. Origin origin = _origin; if (origin == Origin.Context && (context == Reference.Null || context == Reference.Empty)) { origin = Origin.Root; } Reference start = null; switch (origin) { case Origin.Context: start = context; break; case Origin.Root: start = Reference.Create(ContentSourceType.None, resolver.DocumentId, true); break; } // Follow the first step from the start reference. Each step will // discover a set of zero or more resolved references, and will // then follow its own next step starting at each of those resolved // references. This will build up a set of resolved references, each // of which is a result of following the full path from the single // start position. But if we have no steps at all then resolve to // the start object. List <Reference> resolved = null; if (_firstStep != null) { resolved = _firstStep.Follow(start, resolver); } else { resolved = new List <Reference>(); resolved.Add(start); } resolver.Trace("Resolved {0} objects:", resolved.Count); foreach (Reference reference in resolved) { resolver.Trace("{0}", reference); } return(resolved); } catch (PathException ex) { ex.Path = this.ToString(); ex.Context = context.ToString(); throw; //TODO: Since framework 4.5 (I think) this naked throw statement //loses the exception's call stack and shows the exception //originating here. There are messy ways to preserve it, but //can we find a clean way? 4.5 introduces ExceptionDispatchInfo //to handle this kind of thing (although it's really designed //for async continuations) but it has some side effects. First, //the compiler won't recognise ExceptionDispatchInfo.Throw as //being a throw (because it's not) and so may insist on having //a return value in the method - no big deal. But what makes this //unusable for us is that ExceptionDispatchInfo can't be marshaled //across app domain boundaries, and we may want to use app domains //to support multiple versions of assemblies for backwards //compatibility. } }
/// <summary> /// Find the concrete objects, based on the target template object, with this /// step's relationship to the start object. Throws ArgumentException if the /// start reference is not resolved, or if the step's target is already resolved. /// The returned list is sorted by ordinal. /// </summary> /// <exception cref="ArgumentException"></exception> internal List <Reference> Follow(Reference start, Resolver resolver) { if (!start.IsResolved) { throw new ArgumentException($"Start object {start} is not resolved."); } if (_reference.IsResolved) { throw new ArgumentException($"Target reference {_reference} is already resolved."); } List <Reference> found = new List <Reference>(); // If the start reference is an instance of the path reference then // return it directly and don't look any further up/down if (resolver.IsInstanceOf(start, _reference)) { found.Add(start); return(found); } switch (_navigation.Direction) { case Direction.Up: Reference ancestor = FollowUp(start, resolver); if (ancestor != null) { found.Add(ancestor); } break; case Direction.Down: List <Reference> descendants = FollowDown(start, resolver); foreach (Reference descendant in descendants) { if (descendant != null) { found.Add(descendant); } } break; } // If we're the last step in the path then our result set is the // full result set for this fork of the path. if (_next == null) { return(found); } // If we're not the last step then use each of our resolved references // as the starting point for a fork at the next step. List <Reference> result = new List <Reference>(); foreach (Reference reference in found) { List <Reference> forks = _next.Follow(reference, resolver); result.AddRange(forks); } return(result); }