diff --git a/src/redmine-net-api/Extensions/CollectionExtensions.cs b/src/redmine-net-api/Extensions/CollectionExtensions.cs index 5a14bfe3..60703635 100755 --- a/src/redmine-net-api/Extensions/CollectionExtensions.cs +++ b/src/redmine-net-api/Extensions/CollectionExtensions.cs @@ -17,6 +17,7 @@ limitations under the License. using System; using System.Collections.Generic; using System.Text; +using Redmine.Net.Api.Types; namespace Redmine.Net.Api.Extensions { @@ -30,8 +31,9 @@ public static class CollectionExtensions /// /// /// The list to clone. + /// /// - public static IList Clone(this IList listToClone) where T : ICloneable + public static IList Clone(this IList listToClone, bool resetId) where T : ICloneable { if (listToClone == null) { @@ -43,7 +45,7 @@ public static IList Clone(this IList listToClone) where T : ICloneable for (var index = 0; index < listToClone.Count; index++) { var item = listToClone[index]; - clonedList.Add((T) item.Clone()); + clonedList.Add(item.Clone(resetId)); } return clonedList; diff --git a/src/redmine-net-api/ICloneableOfT.cs b/src/redmine-net-api/ICloneableOfT.cs new file mode 100644 index 00000000..dd58fde2 --- /dev/null +++ b/src/redmine-net-api/ICloneableOfT.cs @@ -0,0 +1,14 @@ +namespace Redmine.Net.Api; + +/// +/// +/// +/// +public interface ICloneable +{ + /// + /// + /// + /// + internal T Clone(bool resetId); +} \ No newline at end of file diff --git a/src/redmine-net-api/Types/Attachment.cs b/src/redmine-net-api/Types/Attachment.cs index f831d647..bc394f77 100644 --- a/src/redmine-net-api/Types/Attachment.cs +++ b/src/redmine-net-api/Types/Attachment.cs @@ -33,6 +33,7 @@ namespace Redmine.Net.Api.Types [XmlRoot(RedmineKeys.ATTACHMENT)] public sealed class Attachment : Identifiable + , ICloneable { #region Properties /// @@ -266,5 +267,39 @@ public override int GetHashCode() Author={Author}, CreatedOn={CreatedOn?.ToString("u", CultureInfo.InvariantCulture)}]"; + /// + /// + /// + /// + public new Attachment Clone(bool resetId) + { + if (resetId) + { + return new Attachment + { + FileName = FileName, + FileSize = FileSize, + ContentType = ContentType, + Description = Description, + ContentUrl = ContentUrl, + ThumbnailUrl = ThumbnailUrl, + Author = Author?.Clone(false), + CreatedOn = CreatedOn + }; + } + + return new Attachment + { + Id = Id, + FileName = FileName, + FileSize = FileSize, + ContentType = ContentType, + Description = Description, + ContentUrl = ContentUrl, + ThumbnailUrl = ThumbnailUrl, + Author = Author?.Clone(true), + CreatedOn = CreatedOn + }; + } } } \ No newline at end of file diff --git a/src/redmine-net-api/Types/ChangeSet.cs b/src/redmine-net-api/Types/ChangeSet.cs index bf07bc83..dae77667 100644 --- a/src/redmine-net-api/Types/ChangeSet.cs +++ b/src/redmine-net-api/Types/ChangeSet.cs @@ -33,6 +33,7 @@ namespace Redmine.Net.Api.Types [DebuggerDisplay("{" + nameof(DebuggerDisplay) + ",nq}")] [XmlRoot(RedmineKeys.CHANGE_SET)] public sealed class ChangeSet : IXmlSerializable, IJsonSerializable, IEquatable + ,ICloneable { #region Properties /// @@ -157,6 +158,23 @@ public bool Equals(ChangeSet other) && CommittedOn == other.CommittedOn; } + /// + /// + /// + /// + /// + /// + public ChangeSet Clone(bool resetId) + { + return new ChangeSet() + { + User = User, + Comments = Comments, + Revision = Revision, + CommittedOn = CommittedOn, + }; + } + /// /// /// diff --git a/src/redmine-net-api/Types/CustomFieldValue.cs b/src/redmine-net-api/Types/CustomFieldValue.cs index 5cc91ae5..592bb4f4 100644 --- a/src/redmine-net-api/Types/CustomFieldValue.cs +++ b/src/redmine-net-api/Types/CustomFieldValue.cs @@ -34,6 +34,7 @@ public class CustomFieldValue : IXmlSerializable ,IJsonSerializable ,IEquatable + ,ICloneable { /// /// @@ -205,10 +206,9 @@ public override int GetHashCode() /// /// /// - public object Clone() + public CustomFieldValue Clone(bool resetId) { - var customFieldValue = new CustomFieldValue {Info = Info}; - return customFieldValue; + return new CustomFieldValue { Info = Info }; } #endregion diff --git a/src/redmine-net-api/Types/Detail.cs b/src/redmine-net-api/Types/Detail.cs index 35e3fcc4..a17a0d9a 100644 --- a/src/redmine-net-api/Types/Detail.cs +++ b/src/redmine-net-api/Types/Detail.cs @@ -34,6 +34,7 @@ public sealed class Detail : IXmlSerializable ,IJsonSerializable ,IEquatable + ,ICloneable { /// /// @@ -182,6 +183,16 @@ public bool Equals(Detail other) && string.Equals(NewValue, other.NewValue, StringComparison.OrdinalIgnoreCase); } + /// + /// + /// + /// + /// + public Detail Clone(bool resetId) + { + return new Detail(Name, Property, OldValue, NewValue); + } + /// /// /// diff --git a/src/redmine-net-api/Types/Identifiable.cs b/src/redmine-net-api/Types/Identifiable.cs index 24124e0c..e5123673 100644 --- a/src/redmine-net-api/Types/Identifiable.cs +++ b/src/redmine-net-api/Types/Identifiable.cs @@ -33,6 +33,7 @@ namespace Redmine.Net.Api.Types /// [DebuggerDisplay("{" + nameof(DebuggerDisplay) + ",nq}")] public abstract class Identifiable : IXmlSerializable, IJsonSerializable, IEquatable + , ICloneable> where T : Identifiable { #region Properties @@ -158,5 +159,13 @@ public override int GetHashCode() /// private string DebuggerDisplay => $"Id={Id.ToString(CultureInfo.InvariantCulture)}"; + /// + /// + /// + /// + public virtual Identifiable Clone(bool resetId) + { + throw new NotImplementedException(); + } } } \ No newline at end of file diff --git a/src/redmine-net-api/Types/IdentifiableName.cs b/src/redmine-net-api/Types/IdentifiableName.cs index d9deafde..b4717d4e 100644 --- a/src/redmine-net-api/Types/IdentifiableName.cs +++ b/src/redmine-net-api/Types/IdentifiableName.cs @@ -29,6 +29,7 @@ namespace Redmine.Net.Api.Types /// [DebuggerDisplay("{" + nameof(DebuggerDisplay) + ",nq}")] public class IdentifiableName : Identifiable + , ICloneable { /// /// @@ -224,5 +225,18 @@ public override int GetHashCode() /// /// private string DebuggerDisplay => $"[{nameof(IdentifiableName)}: {base.ToString()}, Name={Name}]"; + + /// + /// + /// + /// + public new IdentifiableName Clone(bool resetId) + { + return new IdentifiableName + { + Id = Id, + Name = Name + }; + } } } \ No newline at end of file diff --git a/src/redmine-net-api/Types/Issue.cs b/src/redmine-net-api/Types/Issue.cs index 8559b3ce..9e97f373 100644 --- a/src/redmine-net-api/Types/Issue.cs +++ b/src/redmine-net-api/Types/Issue.cs @@ -40,6 +40,7 @@ namespace Redmine.Net.Api.Types [XmlRoot(RedmineKeys.ISSUE)] public sealed class Issue : Identifiable + ,ICloneable { #region Properties /// @@ -588,36 +589,51 @@ public override int GetHashCode() } #endregion - #region Implementation of IClonable + #region Implementation of IClonable /// /// /// /// - public object Clone() + public new Issue Clone(bool resetId) { var issue = new Issue { - AssignedTo = AssignedTo, - Author = Author, - Category = Category, - CustomFields = CustomFields, + Project = Project?.Clone(false), + Tracker = Tracker?.Clone(false), + Status = Status?.Clone(false), + Priority = Priority?.Clone(false), + Author = Author?.Clone(false), + Category = Category?.Clone(false), + Subject = Subject, Description = Description, - DoneRatio = DoneRatio, + StartDate = StartDate, DueDate = DueDate, - SpentHours = SpentHours, + DoneRatio = DoneRatio, + IsPrivate = IsPrivate, EstimatedHours = EstimatedHours, - Priority = Priority, - StartDate = StartDate, - Status = Status, - Subject = Subject, - Tracker = Tracker, - Project = Project, - FixedVersion = FixedVersion, + TotalEstimatedHours = TotalEstimatedHours, + SpentHours = SpentHours, + TotalSpentHours = TotalSpentHours, + AssignedTo = AssignedTo?.Clone(false), + FixedVersion = FixedVersion?.Clone(false), Notes = Notes, - Watchers = Watchers + PrivateNotes = PrivateNotes, + CreatedOn = CreatedOn, + UpdatedOn = UpdatedOn, + ClosedOn = ClosedOn, + ParentIssue = ParentIssue?.Clone(false), + CustomFields = CustomFields?.Clone(false), + Journals = Journals?.Clone(false), + Attachments = Attachments?.Clone(false), + Relations = Relations?.Clone(false), + Children = Children?.Clone(false), + Watchers = Watchers?.Clone(false), + Uploads = Uploads?.Clone(false), }; + return issue; } + #endregion /// @@ -659,6 +675,5 @@ public IdentifiableName AsParent() Children={Children.Dump()}, Uploads={Uploads.Dump()}, Watchers={Watchers.Dump()}]"; - } } \ No newline at end of file diff --git a/src/redmine-net-api/Types/IssueChild.cs b/src/redmine-net-api/Types/IssueChild.cs index 317aa80f..f92e3fd4 100644 --- a/src/redmine-net-api/Types/IssueChild.cs +++ b/src/redmine-net-api/Types/IssueChild.cs @@ -31,6 +31,7 @@ namespace Redmine.Net.Api.Types [DebuggerDisplay("{" + nameof(DebuggerDisplay) + ",nq}")] [XmlRoot(RedmineKeys.ISSUE)] public sealed class IssueChild : Identifiable + ,ICloneable { #region Properties /// @@ -173,12 +174,12 @@ public override int GetHashCode() /// /// /// - public new IssueChild Clone() + public new IssueChild Clone(bool resetId) { return new IssueChild { Id = Id, - Tracker = Tracker, + Tracker = Tracker?.Clone(false), Subject = Subject }; } diff --git a/src/redmine-net-api/Types/IssueCustomField.cs b/src/redmine-net-api/Types/IssueCustomField.cs index 5c570862..31d012cc 100644 --- a/src/redmine-net-api/Types/IssueCustomField.cs +++ b/src/redmine-net-api/Types/IssueCustomField.cs @@ -34,6 +34,7 @@ namespace Redmine.Net.Api.Types public sealed class IssueCustomField : IdentifiableName ,IEquatable + ,ICloneable, IValue { #region Properties /// @@ -270,16 +271,37 @@ public override int GetHashCode() } #endregion - #region Implementation of IClonable + #region Implementation of IClonable /// /// /// /// - public object Clone() + public new IssueCustomField Clone(bool resetId) { - var issueCustomField = new IssueCustomField { Multiple = Multiple, Values = Values }; - return issueCustomField; + IssueCustomField clone; + if (resetId) + { + clone = new IssueCustomField(); + } + else + { + clone = new IssueCustomField + { + Id = Id, + }; + } + + clone.Name = Name; + clone.Multiple = Multiple; + + if (Values != null) + { + clone.Values = new List(Values); + } + + return clone; } + #endregion #region Implementation of IValue diff --git a/src/redmine-net-api/Types/IssueRelation.cs b/src/redmine-net-api/Types/IssueRelation.cs index 84c9c501..16571f7e 100644 --- a/src/redmine-net-api/Types/IssueRelation.cs +++ b/src/redmine-net-api/Types/IssueRelation.cs @@ -34,6 +34,7 @@ namespace Redmine.Net.Api.Types [XmlRoot(RedmineKeys.RELATION)] public sealed class IssueRelation : Identifiable + ,ICloneable { #region Properties /// @@ -284,5 +285,30 @@ public override int GetHashCode() Type={Type:G}, Delay={Delay?.ToString(CultureInfo.InvariantCulture)}]"; + /// + /// + /// + /// + public new IssueRelation Clone(bool resetId) + { + if (resetId) + { + return new IssueRelation + { + IssueId = IssueId, + IssueToId = IssueToId, + Type = Type, + Delay = Delay + }; + } + return new IssueRelation + { + Id = Id, + IssueId = IssueId, + IssueToId = IssueToId, + Type = Type, + Delay = Delay + }; + } } } \ No newline at end of file diff --git a/src/redmine-net-api/Types/Journal.cs b/src/redmine-net-api/Types/Journal.cs index 6f776b24..325eedbd 100644 --- a/src/redmine-net-api/Types/Journal.cs +++ b/src/redmine-net-api/Types/Journal.cs @@ -33,6 +33,7 @@ namespace Redmine.Net.Api.Types [XmlRoot(RedmineKeys.JOURNAL)] public sealed class Journal : Identifiable + ,ICloneable { #region Properties /// @@ -253,5 +254,32 @@ public override int GetHashCode() /// private string DebuggerDisplay => $"[{nameof(Journal)}: {ToString()}, User={User}, Notes={Notes}, CreatedOn={CreatedOn?.ToString("u", CultureInfo.InvariantCulture)}, Details={Details.Dump()}]"; + /// + /// + /// + /// + public new Journal Clone(bool resetId) + { + if (resetId) + { + return new Journal + { + User = User?.Clone(false), + Notes = Notes, + CreatedOn = CreatedOn, + PrivateNotes = PrivateNotes, + Details = Details?.Clone(false) + }; + } + return new Journal + { + Id = Id, + User = User?.Clone(false), + Notes = Notes, + CreatedOn = CreatedOn, + PrivateNotes = PrivateNotes, + Details = Details?.Clone(false) + }; + } } } \ No newline at end of file diff --git a/src/redmine-net-api/Types/TimeEntry.cs b/src/redmine-net-api/Types/TimeEntry.cs index a6691bdb..afb8d110 100644 --- a/src/redmine-net-api/Types/TimeEntry.cs +++ b/src/redmine-net-api/Types/TimeEntry.cs @@ -34,6 +34,7 @@ namespace Redmine.Net.Api.Types [DebuggerDisplay("{" + nameof(DebuggerDisplay) + ",nq}")] [XmlRoot(RedmineKeys.TIME_ENTRY)] public sealed class TimeEntry : Identifiable + , ICloneable { #region Properties private string comments; @@ -303,7 +304,7 @@ public override int GetHashCode() /// /// /// - public object Clone() + public new TimeEntry Clone(bool resetId) { var timeEntry = new TimeEntry { diff --git a/src/redmine-net-api/Types/Upload.cs b/src/redmine-net-api/Types/Upload.cs index 00beb32f..ca2ac4ee 100644 --- a/src/redmine-net-api/Types/Upload.cs +++ b/src/redmine-net-api/Types/Upload.cs @@ -32,6 +32,7 @@ namespace Redmine.Net.Api.Types [DebuggerDisplay("{" + nameof(DebuggerDisplay) + ",nq}")] [XmlRoot(RedmineKeys.UPLOAD)] public sealed class Upload : IXmlSerializable, IJsonSerializable, IEquatable + , ICloneable { #region Properties /// @@ -234,5 +235,19 @@ public override int GetHashCode() /// private string DebuggerDisplay => $"[Upload: Token={Token}, FileName={FileName}, ContentType={ContentType}, Description={Description}]"; + /// + /// + /// + /// + public Upload Clone(bool resetId) + { + return new Upload + { + Token = Token, + FileName = FileName, + ContentType = ContentType, + Description = Description + }; + } } } \ No newline at end of file diff --git a/src/redmine-net-api/Types/Watcher.cs b/src/redmine-net-api/Types/Watcher.cs index c3949e37..d413cab1 100644 --- a/src/redmine-net-api/Types/Watcher.cs +++ b/src/redmine-net-api/Types/Watcher.cs @@ -27,6 +27,7 @@ namespace Redmine.Net.Api.Types [DebuggerDisplay("{" + nameof(DebuggerDisplay) + ",nq}")] [XmlRoot(RedmineKeys.USER)] public sealed class Watcher : Identifiable + ,ICloneable ,IValue { #region Implementation of IValue @@ -37,16 +38,23 @@ public sealed class Watcher : Identifiable #endregion - #region Implementation of ICloneable + #region Implementation of ICloneable /// /// /// /// - public object Clone() + public new Watcher Clone(bool resetId) { - var watcher = new Watcher { Id = Id }; - return watcher; + if (resetId) + { + return new Watcher(); + } + return new Watcher + { + Id = Id + }; } + #endregion /// @@ -54,6 +62,5 @@ public object Clone() /// /// private string DebuggerDisplay => $"[{nameof(Watcher)}: {ToString()}]"; - } } \ No newline at end of file