From ba3e1e147847e0eb4c683e35ceda0a64b47f9584 Mon Sep 17 00:00:00 2001 From: Kaplas Date: Sat, 10 Apr 2021 19:44:59 +0200 Subject: [PATCH 1/4] Implement ICloneable in Node and formats. --- src/Yarhl.Media/Text/Po.cs | 18 +++++++++++++++- src/Yarhl.Media/Text/PoEntry.cs | 7 +++++- src/Yarhl.Media/Text/PoHeader.cs | 15 ++++++++++++- src/Yarhl/FileSystem/Node.cs | 24 ++++++++++++++++++++- src/Yarhl/FileSystem/NodeContainerFormat.cs | 17 ++++++++++++++- src/Yarhl/IO/BinaryFormat.cs | 5 ++++- 6 files changed, 80 insertions(+), 6 deletions(-) diff --git a/src/Yarhl.Media/Text/Po.cs b/src/Yarhl.Media/Text/Po.cs index 75d1187e..d5792eaf 100644 --- a/src/Yarhl.Media/Text/Po.cs +++ b/src/Yarhl.Media/Text/Po.cs @@ -27,7 +27,7 @@ namespace Yarhl.Media.Text /// /// Portable Object format for translations. /// - public class Po : IFormat + public class Po : IFormat, ICloneable { readonly IList entries; readonly ReadOnlyCollection readonlyEntries; @@ -134,6 +134,22 @@ public PoEntry FindEntry(string original, string context = null) return searchEntries.ContainsKey(key) ? searchEntries[key] : null; } + /// + public object Clone() + { + Po clone = new Po(); + if (header != null) { + clone.header = (PoHeader)header.Clone(); + } + + foreach (PoEntry entry in entries) + { + clone.Add((PoEntry)entry.Clone()); + } + + return clone; + } + static string GetKey(PoEntry entry) { return GetKey(entry.Original, entry.Context); diff --git a/src/Yarhl.Media/Text/PoEntry.cs b/src/Yarhl.Media/Text/PoEntry.cs index f5795111..6a51b2af 100644 --- a/src/Yarhl.Media/Text/PoEntry.cs +++ b/src/Yarhl.Media/Text/PoEntry.cs @@ -19,10 +19,12 @@ // SOFTWARE. namespace Yarhl.Media.Text { + using System; + /// /// Entry in PO formats. Represents a translation unit. /// - public class PoEntry + public class PoEntry : ICloneable { /// /// Initializes a new instance of the class. @@ -115,5 +117,8 @@ public PoEntry(string original) /// /// The previous original content. public string PreviousOriginal { get; set; } + + /// + public object Clone() => this.MemberwiseClone(); } } diff --git a/src/Yarhl.Media/Text/PoHeader.cs b/src/Yarhl.Media/Text/PoHeader.cs index f4603d07..ee8949fd 100644 --- a/src/Yarhl.Media/Text/PoHeader.cs +++ b/src/Yarhl.Media/Text/PoHeader.cs @@ -25,7 +25,7 @@ namespace Yarhl.Media.Text /// /// Header for PO translation format. /// - public class PoHeader + public class PoHeader : ICloneable { /// /// Initializes a new instance of the class. @@ -121,5 +121,18 @@ public PoHeader(string id, string reporter, string lang) /// /// The dictionary for the metadata. public IDictionary Extensions { get; private set; } + + /// + public object Clone() + { + PoHeader clone = (PoHeader)this.MemberwiseClone(); + clone.Extensions = new Dictionary(); + foreach (KeyValuePair extension in Extensions) + { + clone.Extensions[extension.Key] = extension.Value; + } + + return clone; + } } } diff --git a/src/Yarhl/FileSystem/Node.cs b/src/Yarhl/FileSystem/Node.cs index fbfd5fcc..e6e3a780 100644 --- a/src/Yarhl/FileSystem/Node.cs +++ b/src/Yarhl/FileSystem/Node.cs @@ -20,13 +20,14 @@ namespace Yarhl.FileSystem { using System; + using System.Collections.Generic; using Yarhl.FileFormat; using Yarhl.IO; /// /// Node in the FileSystem with an associated format. /// - public class Node : NavigableNode + public class Node : NavigableNode, ICloneable { /// /// Initializes a new instance of the class. @@ -275,6 +276,27 @@ public Node TransformWith(Type converterType) return this; } + /// + public object Clone() + { + if (Format != null && !(Format is ICloneable)) { + throw new InvalidOperationException("Format does not implement ICloneable interface."); + } + + IFormat newFormat = null; + if (Format != null) { + newFormat = (IFormat)(Format as ICloneable).Clone(); + } + + var newNode = new Node(this.Name, newFormat); + foreach (KeyValuePair tag in Tags) + { + newNode.Tags[tag.Key] = tag.Value; + } + + return newNode; + } + /// /// Releases all resource used by the /// object. diff --git a/src/Yarhl/FileSystem/NodeContainerFormat.cs b/src/Yarhl/FileSystem/NodeContainerFormat.cs index d3f8dfc8..ce677100 100644 --- a/src/Yarhl/FileSystem/NodeContainerFormat.cs +++ b/src/Yarhl/FileSystem/NodeContainerFormat.cs @@ -26,7 +26,7 @@ namespace Yarhl.FileSystem /// /// Node container format for unpack / pack files. /// - public class NodeContainerFormat : IFormat, IDisposable + public class NodeContainerFormat : IFormat, IDisposable, ICloneable { bool manageRoot; @@ -96,6 +96,21 @@ public void MoveChildrenTo(Node newNode, bool mergeContainers = false) manageRoot = false; } + /// + public object Clone() + { + var newFormat = new NodeContainerFormat(); + + // Just copy the first generation children. + // The rest will be copied recursively if necessary. + foreach (Node node in Root.Children) { + var newNode = (Node)node.Clone(); + newFormat.Root.Add(newNode); + } + + return newFormat; + } + /// /// Releases all resource used by the object. /// diff --git a/src/Yarhl/IO/BinaryFormat.cs b/src/Yarhl/IO/BinaryFormat.cs index d7ea1b02..e42828bd 100644 --- a/src/Yarhl/IO/BinaryFormat.cs +++ b/src/Yarhl/IO/BinaryFormat.cs @@ -25,7 +25,7 @@ namespace Yarhl.IO /// /// Binary format. /// - public class BinaryFormat : IBinary, IDisposable + public class BinaryFormat : IBinary, IDisposable, ICloneable { /// /// Initializes a new instance of the class. @@ -93,6 +93,9 @@ public BinaryFormat(Stream stream, long offset, long length) private set; } + /// + public object Clone() => new BinaryFormat(DataStreamFactory.FromStream(Stream, 0, Stream.Length)); + /// /// Releases all resource used by the object. /// From b27cfcf56adbc42e62ea49e12e6ba19e6ea3e5f2 Mon Sep 17 00:00:00 2001 From: Kaplas Date: Sat, 10 Apr 2021 19:45:39 +0200 Subject: [PATCH 2/4] Add tests. --- .../FileSystem/NodeContainerFormatTests.cs | 25 ++++++++++++++++ src/Yarhl.UnitTests/FileSystem/NodeTests.cs | 30 +++++++++++++++++++ src/Yarhl.UnitTests/IO/BinaryFormatTests.cs | 13 ++++++++ .../Media/Text/PoEntryTests.cs | 29 ++++++++++++++++++ .../Media/Text/PoHeaderTests.cs | 28 +++++++++++++++++ src/Yarhl.UnitTests/Media/Text/PoTests.cs | 14 +++++++++ 6 files changed, 139 insertions(+) diff --git a/src/Yarhl.UnitTests/FileSystem/NodeContainerFormatTests.cs b/src/Yarhl.UnitTests/FileSystem/NodeContainerFormatTests.cs index ad39bf59..ec30d15e 100644 --- a/src/Yarhl.UnitTests/FileSystem/NodeContainerFormatTests.cs +++ b/src/Yarhl.UnitTests/FileSystem/NodeContainerFormatTests.cs @@ -183,6 +183,31 @@ public void MoveChildrenToNodeCanMergeContainers() Assert.That(folder3.Children.Count, Is.Zero); } + [Test] + public void Clone() + { + using NodeContainerFormat format = new NodeContainerFormat(); + + using Node child1 = NodeFactory.CreateContainer("child1"); + using Node child2 = new Node("child2"); + using Node grandchild = new Node("grandchild"); + + format.Root.Add(child1); + format.Root.Add(child2); + child1.Add(grandchild); + + using NodeContainerFormat clone = (NodeContainerFormat)format.Clone(); + + Node child1Clone = clone.Root.Children["child1"]; + Node child2Clone = clone.Root.Children["child2"]; + Node grandchildClone = child1Clone.Children["grandchild"]; + + Assert.AreNotSame(format, clone); + Assert.AreNotSame(child1, child1Clone); + Assert.AreNotSame(child2, child2Clone); + Assert.AreNotSame(grandchild, grandchildClone); + } + protected override NodeContainerFormat CreateDummyFormat() { return new NodeContainerFormat(); diff --git a/src/Yarhl.UnitTests/FileSystem/NodeTests.cs b/src/Yarhl.UnitTests/FileSystem/NodeTests.cs index cfd8e081..def680ee 100644 --- a/src/Yarhl.UnitTests/FileSystem/NodeTests.cs +++ b/src/Yarhl.UnitTests/FileSystem/NodeTests.cs @@ -667,6 +667,36 @@ public void DisposeTwiceDoesNotThrow() Assert.DoesNotThrow(node.Dispose); } + [Test] + public void NotCloneableFormatThrowsException() + { + using Node node = new Node("test", new StringFormatTest("3")); + Assert.Throws(() => _ = node.Clone()); + } + + [Test] + public void CloneNodeHasTags() + { + using Node node = NodeFactory.FromMemory("test"); + node.Tags["TestTag"] = 23; + + using Node clone = (Node)node.Clone(); + + Assert.AreNotSame(node, clone); + Assert.AreEqual(1, clone.Tags.Count); + Assert.AreEqual(23, clone.Tags["TestTag"]); + } + + [Test] + public void CloneNullFormatNode() + { + using Node node = new Node("test"); + using Node clone = (Node)node.Clone(); + + Assert.AreNotSame(node, clone); + Assert.IsNull(clone.Format); + } + [PartNotDiscoverable] public class PrivateConverter : IConverter, diff --git a/src/Yarhl.UnitTests/IO/BinaryFormatTests.cs b/src/Yarhl.UnitTests/IO/BinaryFormatTests.cs index b60aa61a..55821a70 100644 --- a/src/Yarhl.UnitTests/IO/BinaryFormatTests.cs +++ b/src/Yarhl.UnitTests/IO/BinaryFormatTests.cs @@ -180,6 +180,19 @@ public void MemoryConstructor() format.Dispose(); } + [Test] + public void Clone() + { + byte[] data = { 0x01, 0x02, 0x03 }; + using DataStream stream = DataStreamFactory.FromArray(data, 0, data.Length); + using BinaryFormat format = new BinaryFormat(stream); + + using BinaryFormat clone = (BinaryFormat)format.Clone(); + + Assert.AreNotSame(format, clone); + Assert.IsTrue(format.Stream.Compare(clone.Stream)); + } + protected override BinaryFormat CreateDummyFormat() { using DataStream stream = new DataStream(); diff --git a/src/Yarhl.UnitTests/Media/Text/PoEntryTests.cs b/src/Yarhl.UnitTests/Media/Text/PoEntryTests.cs index 66e4fcf0..df11491b 100644 --- a/src/Yarhl.UnitTests/Media/Text/PoEntryTests.cs +++ b/src/Yarhl.UnitTests/Media/Text/PoEntryTests.cs @@ -81,5 +81,34 @@ public void TextShowsOriginalOrTranslated() entry.Translated = "translated"; Assert.That(entry.Text, Is.EqualTo(entry.Translated)); } + + [Test] + public void Clone() + { + PoEntry entry = new PoEntry { + Original = "test0", + Translated = "test1", + Context = "test2", + TranslatorComment = "test3", + ExtractedComments = "test4", + Reference = "test5", + Flags = "test6", + PreviousContext = "test7", + PreviousOriginal = "test8", + }; + + PoEntry clone = (PoEntry)entry.Clone(); + + Assert.AreNotSame(entry, clone); + Assert.AreEqual("test0", clone.Original); + Assert.AreEqual("test1", clone.Translated); + Assert.AreEqual("test2", clone.Context); + Assert.AreEqual("test3", clone.TranslatorComment); + Assert.AreEqual("test4", clone.ExtractedComments); + Assert.AreEqual("test5", clone.Reference); + Assert.AreEqual("test6", clone.Flags); + Assert.AreEqual("test7", clone.PreviousContext); + Assert.AreEqual("test8", clone.PreviousOriginal); + } } } diff --git a/src/Yarhl.UnitTests/Media/Text/PoHeaderTests.cs b/src/Yarhl.UnitTests/Media/Text/PoHeaderTests.cs index f4a4706f..6b71bd25 100644 --- a/src/Yarhl.UnitTests/Media/Text/PoHeaderTests.cs +++ b/src/Yarhl.UnitTests/Media/Text/PoHeaderTests.cs @@ -74,5 +74,33 @@ public void TestProperties() Assert.AreEqual("test8", header.PluralForms); Assert.That(header.Extensions["X-MyExt"], Is.EqualTo("the value")); } + + [Test] + public void Clone() + { + var header = new PoHeader("myID", "yo", "us") { + ProjectIdVersion = "test1", + ReportMsgidBugsTo = "test2", + CreationDate = "test3", + RevisionDate = "test4", + LastTranslator = "test5", + LanguageTeam = "test6", + Language = "test7", + PluralForms = "test8", + }; + header.Extensions["X-MyExt"] = "the value"; + + PoHeader clone = (PoHeader)header.Clone(); + Assert.AreNotSame(header, clone); + Assert.AreEqual("test1", clone.ProjectIdVersion); + Assert.AreEqual("test2", clone.ReportMsgidBugsTo); + Assert.AreEqual("test3", clone.CreationDate); + Assert.AreEqual("test4", clone.RevisionDate); + Assert.AreEqual("test5", clone.LastTranslator); + Assert.AreEqual("test6", clone.LanguageTeam); + Assert.AreEqual("test7", clone.Language); + Assert.AreEqual("test8", clone.PluralForms); + Assert.That(clone.Extensions["X-MyExt"], Is.EqualTo("the value")); + } } } diff --git a/src/Yarhl.UnitTests/Media/Text/PoTests.cs b/src/Yarhl.UnitTests/Media/Text/PoTests.cs index 0c47a375..2b133892 100644 --- a/src/Yarhl.UnitTests/Media/Text/PoTests.cs +++ b/src/Yarhl.UnitTests/Media/Text/PoTests.cs @@ -225,6 +225,20 @@ public void FindEntryNullOriginalThrows() Assert.That(() => po.FindEntry(string.Empty), Throws.ArgumentNullException); } + [Test] + public void Clone() + { + var header = new PoHeader("id", "reporter", "lang"); + var po = new Po(header); + po.Add(new PoEntry("t1")); + po.Add(new PoEntry("t2")); + + var clone = (Po)po.Clone(); + + Assert.AreNotSame(po, clone); + Assert.AreEqual(2, clone.Entries.Count); + } + protected override Po CreateDummyFormat() { return new Po(); From d26e2c187ad280da6e7dde1604c7ba079f23f3b0 Mon Sep 17 00:00:00 2001 From: Kaplas Date: Sun, 11 Apr 2021 11:54:27 +0200 Subject: [PATCH 3/4] Replace ICloneable with our custom DeepClone interface. --- src/Yarhl.Media/Text/Po.cs | 8 +-- src/Yarhl.Media/Text/PoEntry.cs | 25 ++++++++-- src/Yarhl.Media/Text/PoHeader.cs | 39 +++++++++------ .../FileSystem/NodeContainerFormatTests.cs | 2 +- src/Yarhl.UnitTests/FileSystem/NodeTests.cs | 12 +++-- src/Yarhl.UnitTests/IO/BinaryFormatTests.cs | 2 +- .../Media/Text/PoEntryTests.cs | 9 +++- .../Media/Text/PoHeaderTests.cs | 8 ++- src/Yarhl.UnitTests/Media/Text/PoTests.cs | 2 +- src/Yarhl/FileFormat/ICloneableFormat.cs | 38 ++++++++++++++ src/Yarhl/FileSystem/Node.cs | 49 ++++++++++--------- src/Yarhl/FileSystem/NodeContainerFormat.cs | 6 +-- src/Yarhl/IO/BinaryFormat.cs | 11 ++++- 13 files changed, 154 insertions(+), 57 deletions(-) create mode 100644 src/Yarhl/FileFormat/ICloneableFormat.cs diff --git a/src/Yarhl.Media/Text/Po.cs b/src/Yarhl.Media/Text/Po.cs index d5792eaf..d09fb5c7 100644 --- a/src/Yarhl.Media/Text/Po.cs +++ b/src/Yarhl.Media/Text/Po.cs @@ -27,7 +27,7 @@ namespace Yarhl.Media.Text /// /// Portable Object format for translations. /// - public class Po : IFormat, ICloneable + public class Po : ICloneableFormat { readonly IList entries; readonly ReadOnlyCollection readonlyEntries; @@ -135,16 +135,16 @@ public PoEntry FindEntry(string original, string context = null) } /// - public object Clone() + public virtual object DeepClone() { Po clone = new Po(); if (header != null) { - clone.header = (PoHeader)header.Clone(); + clone.header = new PoHeader(header); } foreach (PoEntry entry in entries) { - clone.Add((PoEntry)entry.Clone()); + clone.Add(new PoEntry(entry)); } return clone; diff --git a/src/Yarhl.Media/Text/PoEntry.cs b/src/Yarhl.Media/Text/PoEntry.cs index 6a51b2af..133c9e67 100644 --- a/src/Yarhl.Media/Text/PoEntry.cs +++ b/src/Yarhl.Media/Text/PoEntry.cs @@ -24,7 +24,7 @@ namespace Yarhl.Media.Text /// /// Entry in PO formats. Represents a translation unit. /// - public class PoEntry : ICloneable + public class PoEntry { /// /// Initializes a new instance of the class. @@ -45,6 +45,26 @@ public PoEntry(string original) Translated = string.Empty; } + /// + /// Initializes a new instance of the class. + /// + /// The entry to copy. + public PoEntry(PoEntry entry) + { + if (entry == null) + throw new ArgumentNullException(nameof(entry)); + + Original = entry.Original; + Translated = entry.Translated; + Context = entry.Context; + TranslatorComment = entry.TranslatorComment; + ExtractedComments = entry.ExtractedComments; + Reference = entry.Reference; + Flags = entry.Flags; + PreviousContext = entry.PreviousContext; + PreviousOriginal = entry.PreviousOriginal; + } + /// /// Gets or sets the original content to translate. /// @@ -117,8 +137,5 @@ public PoEntry(string original) /// /// The previous original content. public string PreviousOriginal { get; set; } - - /// - public object Clone() => this.MemberwiseClone(); } } diff --git a/src/Yarhl.Media/Text/PoHeader.cs b/src/Yarhl.Media/Text/PoHeader.cs index ee8949fd..eac1870c 100644 --- a/src/Yarhl.Media/Text/PoHeader.cs +++ b/src/Yarhl.Media/Text/PoHeader.cs @@ -25,7 +25,7 @@ namespace Yarhl.Media.Text /// /// Header for PO translation format. /// - public class PoHeader : ICloneable + public class PoHeader { /// /// Initializes a new instance of the class. @@ -50,6 +50,30 @@ public PoHeader(string id, string reporter, string lang) CreationDate = DateTime.Now.ToShortDateString(); } + /// + /// Initializes a new instance of the class. + /// + /// The header to copy. + public PoHeader(PoHeader header) + : this() + { + if (header == null) + throw new ArgumentNullException(nameof(header)); + + ProjectIdVersion = header.ProjectIdVersion; + ReportMsgidBugsTo = header.ReportMsgidBugsTo; + Language = header.Language; + CreationDate = header.CreationDate; + RevisionDate = header.RevisionDate; + LastTranslator = header.LastTranslator; + LanguageTeam = header.LanguageTeam; + PluralForms = header.PluralForms; + foreach (KeyValuePair extension in header.Extensions) + { + Extensions[extension.Key] = extension.Value; + } + } + /// /// Gets the type of the content and encoding. /// @@ -121,18 +145,5 @@ public PoHeader(string id, string reporter, string lang) /// /// The dictionary for the metadata. public IDictionary Extensions { get; private set; } - - /// - public object Clone() - { - PoHeader clone = (PoHeader)this.MemberwiseClone(); - clone.Extensions = new Dictionary(); - foreach (KeyValuePair extension in Extensions) - { - clone.Extensions[extension.Key] = extension.Value; - } - - return clone; - } } } diff --git a/src/Yarhl.UnitTests/FileSystem/NodeContainerFormatTests.cs b/src/Yarhl.UnitTests/FileSystem/NodeContainerFormatTests.cs index ec30d15e..4e5e8758 100644 --- a/src/Yarhl.UnitTests/FileSystem/NodeContainerFormatTests.cs +++ b/src/Yarhl.UnitTests/FileSystem/NodeContainerFormatTests.cs @@ -196,7 +196,7 @@ public void Clone() format.Root.Add(child2); child1.Add(grandchild); - using NodeContainerFormat clone = (NodeContainerFormat)format.Clone(); + using NodeContainerFormat clone = (NodeContainerFormat)format.DeepClone(); Node child1Clone = clone.Root.Children["child1"]; Node child2Clone = clone.Root.Children["child2"]; diff --git a/src/Yarhl.UnitTests/FileSystem/NodeTests.cs b/src/Yarhl.UnitTests/FileSystem/NodeTests.cs index def680ee..ae88c845 100644 --- a/src/Yarhl.UnitTests/FileSystem/NodeTests.cs +++ b/src/Yarhl.UnitTests/FileSystem/NodeTests.cs @@ -667,11 +667,17 @@ public void DisposeTwiceDoesNotThrow() Assert.DoesNotThrow(node.Dispose); } + [Test] + public void CloneNullNodeThrowsException() + { + Assert.Throws(() => _ = new Node((Node)null)); + } + [Test] public void NotCloneableFormatThrowsException() { using Node node = new Node("test", new StringFormatTest("3")); - Assert.Throws(() => _ = node.Clone()); + Assert.Throws(() => _ = new Node(node)); } [Test] @@ -680,7 +686,7 @@ public void CloneNodeHasTags() using Node node = NodeFactory.FromMemory("test"); node.Tags["TestTag"] = 23; - using Node clone = (Node)node.Clone(); + using Node clone = new Node(node); Assert.AreNotSame(node, clone); Assert.AreEqual(1, clone.Tags.Count); @@ -691,7 +697,7 @@ public void CloneNodeHasTags() public void CloneNullFormatNode() { using Node node = new Node("test"); - using Node clone = (Node)node.Clone(); + using Node clone = new Node(node); Assert.AreNotSame(node, clone); Assert.IsNull(clone.Format); diff --git a/src/Yarhl.UnitTests/IO/BinaryFormatTests.cs b/src/Yarhl.UnitTests/IO/BinaryFormatTests.cs index 55821a70..78aa93d3 100644 --- a/src/Yarhl.UnitTests/IO/BinaryFormatTests.cs +++ b/src/Yarhl.UnitTests/IO/BinaryFormatTests.cs @@ -187,7 +187,7 @@ public void Clone() using DataStream stream = DataStreamFactory.FromArray(data, 0, data.Length); using BinaryFormat format = new BinaryFormat(stream); - using BinaryFormat clone = (BinaryFormat)format.Clone(); + using BinaryFormat clone = (BinaryFormat)format.DeepClone(); Assert.AreNotSame(format, clone); Assert.IsTrue(format.Stream.Compare(clone.Stream)); diff --git a/src/Yarhl.UnitTests/Media/Text/PoEntryTests.cs b/src/Yarhl.UnitTests/Media/Text/PoEntryTests.cs index df11491b..8aaaf4aa 100644 --- a/src/Yarhl.UnitTests/Media/Text/PoEntryTests.cs +++ b/src/Yarhl.UnitTests/Media/Text/PoEntryTests.cs @@ -19,6 +19,7 @@ // SOFTWARE. namespace Yarhl.UnitTests.Media.Text { + using System; using NUnit.Framework; using Yarhl.Media.Text; @@ -82,6 +83,12 @@ public void TextShowsOriginalOrTranslated() Assert.That(entry.Text, Is.EqualTo(entry.Translated)); } + [Test] + public void CloneNullThrowsException() + { + Assert.Throws(() => _ = new PoEntry((PoEntry)null)); + } + [Test] public void Clone() { @@ -97,7 +104,7 @@ public void Clone() PreviousOriginal = "test8", }; - PoEntry clone = (PoEntry)entry.Clone(); + PoEntry clone = new PoEntry(entry); Assert.AreNotSame(entry, clone); Assert.AreEqual("test0", clone.Original); diff --git a/src/Yarhl.UnitTests/Media/Text/PoHeaderTests.cs b/src/Yarhl.UnitTests/Media/Text/PoHeaderTests.cs index 6b71bd25..fbcbb992 100644 --- a/src/Yarhl.UnitTests/Media/Text/PoHeaderTests.cs +++ b/src/Yarhl.UnitTests/Media/Text/PoHeaderTests.cs @@ -75,6 +75,12 @@ public void TestProperties() Assert.That(header.Extensions["X-MyExt"], Is.EqualTo("the value")); } + [Test] + public void CloneNullThrowsException() + { + Assert.Throws(() => _ = new PoHeader(null)); + } + [Test] public void Clone() { @@ -90,7 +96,7 @@ public void Clone() }; header.Extensions["X-MyExt"] = "the value"; - PoHeader clone = (PoHeader)header.Clone(); + PoHeader clone = new PoHeader(header); Assert.AreNotSame(header, clone); Assert.AreEqual("test1", clone.ProjectIdVersion); Assert.AreEqual("test2", clone.ReportMsgidBugsTo); diff --git a/src/Yarhl.UnitTests/Media/Text/PoTests.cs b/src/Yarhl.UnitTests/Media/Text/PoTests.cs index 2b133892..e0e59878 100644 --- a/src/Yarhl.UnitTests/Media/Text/PoTests.cs +++ b/src/Yarhl.UnitTests/Media/Text/PoTests.cs @@ -233,7 +233,7 @@ public void Clone() po.Add(new PoEntry("t1")); po.Add(new PoEntry("t2")); - var clone = (Po)po.Clone(); + var clone = (Po)po.DeepClone(); Assert.AreNotSame(po, clone); Assert.AreEqual(2, clone.Entries.Count); diff --git a/src/Yarhl/FileFormat/ICloneableFormat.cs b/src/Yarhl/FileFormat/ICloneableFormat.cs new file mode 100644 index 00000000..bde4e6dc --- /dev/null +++ b/src/Yarhl/FileFormat/ICloneableFormat.cs @@ -0,0 +1,38 @@ +// Copyright (c) 2019 SceneGate + +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: + +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +namespace Yarhl.FileFormat +{ + /// + /// Cloneable format interface. + /// + public interface ICloneableFormat : IFormat + { + /// + /// Deep clones a format into a new object. + /// + /// The cloned format. + /// + /// A 'deep copy' must create the necessary copies, in the way that + /// any change in the original node (or any of it's member hierarchy) won't affect + /// the cloned node. + /// + object DeepClone(); + } +} diff --git a/src/Yarhl/FileSystem/Node.cs b/src/Yarhl/FileSystem/Node.cs index e6e3a780..c128e70d 100644 --- a/src/Yarhl/FileSystem/Node.cs +++ b/src/Yarhl/FileSystem/Node.cs @@ -27,7 +27,7 @@ namespace Yarhl.FileSystem /// /// Node in the FileSystem with an associated format. /// - public class Node : NavigableNode, ICloneable + public class Node : NavigableNode { /// /// Initializes a new instance of the class. @@ -49,6 +49,32 @@ public Node(string name, IFormat initialFormat) ChangeFormat(initialFormat); } + /// + /// Initializes a new instance of the class. + /// + /// The original node. + /// It makes a copy of the original node. + /// The original format is deep copied. See for details. + public Node(Node node) + : this(node != null ? node.Name : string.Empty) + { + if (node!.Format != null && !(node.Format is ICloneableFormat)) + throw new InvalidOperationException("Format does not implement ICloneableFormat interface."); + + ICloneableFormat newFormat = null; + if (node.Format != null) { + var oldFormat = node.Format as ICloneableFormat; + newFormat = (ICloneableFormat)oldFormat!.DeepClone(); + } + + ChangeFormat(newFormat); + + foreach (KeyValuePair tag in node.Tags) + { + Tags[tag.Key] = tag.Value; + } + } + /// /// Gets the current format of the node. /// @@ -276,27 +302,6 @@ public Node TransformWith(Type converterType) return this; } - /// - public object Clone() - { - if (Format != null && !(Format is ICloneable)) { - throw new InvalidOperationException("Format does not implement ICloneable interface."); - } - - IFormat newFormat = null; - if (Format != null) { - newFormat = (IFormat)(Format as ICloneable).Clone(); - } - - var newNode = new Node(this.Name, newFormat); - foreach (KeyValuePair tag in Tags) - { - newNode.Tags[tag.Key] = tag.Value; - } - - return newNode; - } - /// /// Releases all resource used by the /// object. diff --git a/src/Yarhl/FileSystem/NodeContainerFormat.cs b/src/Yarhl/FileSystem/NodeContainerFormat.cs index ce677100..ae8cff3e 100644 --- a/src/Yarhl/FileSystem/NodeContainerFormat.cs +++ b/src/Yarhl/FileSystem/NodeContainerFormat.cs @@ -26,7 +26,7 @@ namespace Yarhl.FileSystem /// /// Node container format for unpack / pack files. /// - public class NodeContainerFormat : IFormat, IDisposable, ICloneable + public class NodeContainerFormat : IDisposable, ICloneableFormat { bool manageRoot; @@ -97,14 +97,14 @@ public void MoveChildrenTo(Node newNode, bool mergeContainers = false) } /// - public object Clone() + public virtual object DeepClone() { var newFormat = new NodeContainerFormat(); // Just copy the first generation children. // The rest will be copied recursively if necessary. foreach (Node node in Root.Children) { - var newNode = (Node)node.Clone(); + var newNode = new Node(node); newFormat.Root.Add(newNode); } diff --git a/src/Yarhl/IO/BinaryFormat.cs b/src/Yarhl/IO/BinaryFormat.cs index e42828bd..e2c46961 100644 --- a/src/Yarhl/IO/BinaryFormat.cs +++ b/src/Yarhl/IO/BinaryFormat.cs @@ -21,11 +21,12 @@ namespace Yarhl.IO { using System; using System.IO; + using Yarhl.FileFormat; /// /// Binary format. /// - public class BinaryFormat : IBinary, IDisposable, ICloneable + public class BinaryFormat : IBinary, IDisposable, ICloneableFormat { /// /// Initializes a new instance of the class. @@ -94,7 +95,13 @@ public BinaryFormat(Stream stream, long offset, long length) } /// - public object Clone() => new BinaryFormat(DataStreamFactory.FromStream(Stream, 0, Stream.Length)); + public virtual object DeepClone() + { + DataStream newStream = DataStreamFactory.FromMemory(); + Stream.WriteTo(newStream); + + return new BinaryFormat(newStream); + } /// /// Releases all resource used by the object. From 905113aed41290933f048593df02aae5c46794cb Mon Sep 17 00:00:00 2001 From: Kaplas Date: Sat, 8 May 2021 19:06:49 +0200 Subject: [PATCH 4/4] Add comments --- src/Yarhl/FileSystem/Node.cs | 6 +++++- src/Yarhl/IO/BinaryFormat.cs | 7 ++++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/Yarhl/FileSystem/Node.cs b/src/Yarhl/FileSystem/Node.cs index c128e70d..2e8ec92e 100644 --- a/src/Yarhl/FileSystem/Node.cs +++ b/src/Yarhl/FileSystem/Node.cs @@ -54,7 +54,11 @@ public Node(string name, IFormat initialFormat) /// /// The original node. /// It makes a copy of the original node. - /// The original format is deep copied. See for details. + /// The original format is deep copied. See for details. + /// + /// If the node has children, it must be a to clone them. + /// In other case, the format must implement and clone the children explicitly. + /// public Node(Node node) : this(node != null ? node.Name : string.Empty) { diff --git a/src/Yarhl/IO/BinaryFormat.cs b/src/Yarhl/IO/BinaryFormat.cs index e2c46961..a93520cc 100644 --- a/src/Yarhl/IO/BinaryFormat.cs +++ b/src/Yarhl/IO/BinaryFormat.cs @@ -94,7 +94,12 @@ public BinaryFormat(Stream stream, long offset, long length) private set; } - /// + /// + /// Makes a copy of the format stream into memory + /// and returns a new object. + /// + /// The stream is copied into memory, so it is limited to 2GB size. + /// The cloned . public virtual object DeepClone() { DataStream newStream = DataStreamFactory.FromMemory();