/*******************************************************************************
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* Licensed under the Apache License, Version 2.0 (the "License"). You may not use
* this file except in compliance with the License. A copy of the License is located at
*
* http://aws.amazon.com/apache2.0
*
* or in the "license" file accompanying this file.
* This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
* *****************************************************************************
* __ _ _ ___
* ( )( \/\/ )/ __)
* /__\ \ / \__ \
* (_)(_) \/\/ (___/
*
* AWS SDK for .NET
*/
using System;
using System.Collections.Generic;
using System.Globalization;
namespace Amazon.Util
{
///
/// Object to track circular references in nested types.
/// At each level of nesting, make a call to Track to retrieve Tracker,
/// a tracking object implementing the IDisposable interface.
/// Dispose of this tracker when leaving the context of the tracked object.
///
public class CircularReferenceTracking
{
private object referenceTrackersLock = new object();
private Stack referenceTrackers = new Stack();
///
/// Tracker. Must be disposed.
///
private class Tracker : IDisposable
{
public object Target { get; private set; }
private CircularReferenceTracking State { get; set; }
private bool disposed;
public Tracker(CircularReferenceTracking state, object target)
{
State = state;
Target = target;
}
public override string ToString()
{
return string.Format(CultureInfo.InvariantCulture,
"Tracking {0}", Target);
}
#region Dispose Pattern Implementation
///
/// Implements the Dispose pattern
///
/// Whether this object is being disposed via a call to Dispose
/// or garbage collected.
protected virtual void Dispose(bool disposing)
{
if (!this.disposed)
{
if (disposing)
{
State.PopTracker(this);
}
this.disposed = true;
}
}
///
/// Disposes of all managed and unmanaged resources.
///
public void Dispose()
{
this.Dispose(true);
GC.SuppressFinalize(this);
}
~Tracker()
{
this.Dispose(false);
}
#endregion
}
///
/// Adds the current target to a reference list and returns a tracker.
/// The tracker removes the target from the reference list when the
/// tracker is disposed.
///
///
///
public IDisposable Track(object target)
{
if (target == null) throw new ArgumentNullException("target");
lock (referenceTrackersLock)
{
if (TrackerExists(target))
throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture,
"Circular reference detected with object [{0}] of type {1}", target, target.GetType().FullName));
var tracker = new Tracker(this, target);
referenceTrackers.Push(tracker);
return tracker;
}
}
private void PopTracker(Tracker tracker)
{
lock (referenceTrackersLock)
{
if (referenceTrackers.Peek() != tracker)
throw new InvalidOperationException("Tracker being released is not the latest one. Make sure to release child trackers before releasing parent.");
referenceTrackers.Pop();
}
}
private bool TrackerExists(object target)
{
foreach (var tracker in referenceTrackers)
if (object.ReferenceEquals(tracker.Target, target))
return true;
return false;
}
}
}