diff --git a/ICSharpCode.NRefactory/CSharp/Ast/AstNode.cs b/ICSharpCode.NRefactory/CSharp/Ast/AstNode.cs
index 7c5d62087a..53f745929e 100644
--- a/ICSharpCode.NRefactory/CSharp/Ast/AstNode.cs
+++ b/ICSharpCode.NRefactory/CSharp/Ast/AstNode.cs
@@ -28,6 +28,7 @@ using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
+using System.Threading;
namespace ICSharpCode.NRefactory.CSharp
{
@@ -322,6 +323,10 @@ namespace ICSharpCode.NRefactory.CSharp
}
}
+ ///
+ /// Clones the whole subtree starting at this AST node.
+ ///
+ /// Annotations are copied over to the new nodes; and any annotations implementating ICloneable will be cloned.
public AstNode Clone()
{
AstNode copy = (AstNode)MemberwiseClone();
@@ -338,9 +343,187 @@ namespace ICSharpCode.NRefactory.CSharp
copy.AddChildUnsafe(cur.Clone(), cur.role);
}
+ // Finally, clone the annotation, if necessary
+ ICloneable annotations = copy.annotations as ICloneable; // read from copy (for thread-safety)
+ if (annotations != null)
+ copy.annotations = annotations.Clone();
+
return copy;
}
+ #region Annotation support
+ // Annotations: points either null (no annotations), to the single annotation,
+ // or to an AnnotationList.
+ // Once it is pointed at an AnnotationList, it will never change (this allows thread-safety support by locking the list)
+ object annotations;
+
+ sealed class AnnotationList : List, ICloneable
+ {
+ // There are two uses for this custom list type:
+ // 1) it's private, and thus (unlike List) cannot be confused with real annotations
+ // 2) It allows us to simplify the cloning logic by making the list behave the same as a clonable annotation.
+ public AnnotationList(int initialCapacity) : base(initialCapacity)
+ {
+ }
+
+ public object Clone()
+ {
+ AnnotationList copy = new AnnotationList(this.Count);
+ for (int i = 0; i < this.Count; i++) {
+ object obj = this[i];
+ ICloneable c = obj as ICloneable;
+ copy.Add(c != null ? c.Clone() : obj);
+ }
+ return copy;
+ }
+ }
+
+ public void AddAnnotation(object annotation)
+ {
+ if (annotation == null)
+ throw new ArgumentNullException("annotation");
+ retry: // Retry until successful
+ object oldAnnotation = Interlocked.CompareExchange(ref this.annotations, annotation, null);
+ if (oldAnnotation == null) {
+ return; // we successfully added a single annotation
+ }
+ AnnotationList list = oldAnnotation as AnnotationList;
+ if (list == null) {
+ // we need to transform the old annotation into a list
+ list = new AnnotationList(4);
+ list.Add(oldAnnotation);
+ list.Add(annotation);
+ if (Interlocked.CompareExchange(ref this.annotations, list, oldAnnotation) != oldAnnotation) {
+ // the transformation failed (some other thread wrote to this.annotations first)
+ goto retry;
+ }
+ } else {
+ // once there's a list, use simple locking
+ lock (list) {
+ list.Add(annotation);
+ }
+ }
+ }
+
+ public void RemoveAnnotations() where T : class
+ {
+ retry: // Retry until successful
+ object oldAnnotations = this.annotations;
+ AnnotationList list = oldAnnotations as AnnotationList;
+ if (list != null) {
+ lock (list)
+ list.RemoveAll(obj => obj is T);
+ } else if (oldAnnotations is T) {
+ if (Interlocked.CompareExchange(ref this.annotations, null, oldAnnotations) != oldAnnotations) {
+ // Operation failed (some other thread wrote to this.annotations first)
+ goto retry;
+ }
+ }
+ }
+
+ public void RemoveAnnotations(Type type)
+ {
+ if (type == null)
+ throw new ArgumentNullException("type");
+ retry: // Retry until successful
+ object oldAnnotations = this.annotations;
+ AnnotationList list = oldAnnotations as AnnotationList;
+ if (list != null) {
+ lock (list)
+ list.RemoveAll(obj => type.IsInstanceOfType(obj));
+ } else if (type.IsInstanceOfType(oldAnnotations)) {
+ if (Interlocked.CompareExchange(ref this.annotations, null, oldAnnotations) != oldAnnotations) {
+ // Operation failed (some other thread wrote to this.annotations first)
+ goto retry;
+ }
+ }
+ }
+
+ public T Annotation() where T: class
+ {
+ object annotations = this.annotations;
+ AnnotationList list = annotations as AnnotationList;
+ if (list != null) {
+ lock (list) {
+ foreach (object obj in list) {
+ T t = obj as T;
+ if (t != null)
+ return t;
+ }
+ return null;
+ }
+ } else {
+ return annotations as T;
+ }
+ }
+
+ public object Annotation(Type type)
+ {
+ if (type == null)
+ throw new ArgumentNullException("type");
+ object annotations = this.annotations;
+ AnnotationList list = annotations as AnnotationList;
+ if (list != null) {
+ lock (list) {
+ foreach (object obj in list) {
+ if (type.IsInstanceOfType(obj))
+ return obj;
+ }
+ }
+ } else {
+ if (type.IsInstanceOfType(annotations))
+ return annotations;
+ }
+ return null;
+ }
+
+ public IEnumerable Annotations() where T: class
+ {
+ object annotations = this.annotations;
+ AnnotationList list = annotations as AnnotationList;
+ if (list != null) {
+ List result = new List();
+ lock (list) {
+ foreach (object obj in list) {
+ T t = obj as T;
+ if (t != null)
+ result.Add(t);
+ }
+ }
+ return result;
+ } else {
+ T t = annotations as T;
+ if (t != null)
+ return new T[] { t };
+ else
+ return Enumerable.Empty();
+ }
+ }
+
+ public IEnumerable Annotations(Type type)
+ {
+ if (type == null)
+ throw new ArgumentNullException("type");
+ object annotations = this.annotations;
+ AnnotationList list = annotations as AnnotationList;
+ if (list != null) {
+ List result = new List();
+ lock (list) {
+ foreach (object obj in list) {
+ if (type.IsInstanceOfType(obj))
+ result.Add(obj);
+ }
+ }
+ return result;
+ } else {
+ if (type.IsInstanceOfType(annotations))
+ return new object[] { annotations };
+ else
+ return Enumerable.Empty();
+ }
+ }
+ #endregion
+
public abstract S AcceptVisitor (AstVisitor visitor, T data);
public static class Roles