Auto status change to "Under Review"
@@ -1,52 +1,52 | |||
|
1 | 1 | using System; |
|
2 | 2 | using Implab.Components; |
|
3 | 3 | |
|
4 | 4 | namespace Implab.Test.Mock { |
|
5 | 5 | class MockRunnableComponent : RunnableComponent { |
|
6 | 6 | public MockRunnableComponent(bool initialized) : base(initialized) { |
|
7 | 7 | } |
|
8 | 8 | |
|
9 | 9 | public MockRunnableComponent(bool initialized, bool reusable) : base(initialized, reusable) { |
|
10 | 10 | } |
|
11 | 11 | |
|
12 | 12 | public Action MockInit { |
|
13 | 13 | get; |
|
14 | 14 | set; |
|
15 | 15 | } |
|
16 | 16 | |
|
17 | 17 | public Func<IPromise> MockStart { |
|
18 | 18 | get; |
|
19 | 19 | set; |
|
20 | 20 | } |
|
21 | 21 | |
|
22 | 22 | public Func<IPromise> MockStop { |
|
23 | 23 | get; |
|
24 | 24 | set; |
|
25 | 25 | } |
|
26 | 26 | |
|
27 |
public Action<bool |
|
|
27 | public Action<bool> MockDispose { | |
|
28 | 28 | get; |
|
29 | 29 | set; |
|
30 | 30 | } |
|
31 | 31 | |
|
32 | 32 | protected override IPromise OnStart() { |
|
33 | 33 | return MockStart != null ? Safe.Run(MockStart).Chain(base.OnStart) : Safe.Run(base.OnStart); |
|
34 | 34 | } |
|
35 | 35 | |
|
36 | 36 | protected override IPromise OnStop() { |
|
37 | 37 | return MockStop != null ? Safe.Run(MockStop).Chain(base.OnStop) : Safe.Run(base.OnStop); |
|
38 | 38 | } |
|
39 | 39 | |
|
40 | 40 | protected override void OnInitialize() { |
|
41 | 41 | if (MockInit != null) |
|
42 | 42 | MockInit(); |
|
43 | 43 | } |
|
44 | 44 | |
|
45 |
protected override void Dispose(bool disposing |
|
|
45 | protected override void Dispose(bool disposing) { | |
|
46 | 46 | if (MockDispose != null) |
|
47 |
MockDispose(disposing |
|
|
48 |
base.Dispose(disposing |
|
|
47 | MockDispose(disposing); | |
|
48 | base.Dispose(disposing); | |
|
49 | 49 | } |
|
50 | 50 | } |
|
51 | 51 | } |
|
52 | 52 |
@@ -1,230 +1,228 | |||
|
1 | 1 | using System; |
|
2 | 2 | using System.Reflection; |
|
3 | 3 | using System.Threading; |
|
4 | 4 | using Implab.Parallels; |
|
5 | 5 | using Implab.Components; |
|
6 | 6 | using Implab.Test.Mock; |
|
7 | 7 | |
|
8 | 8 | #if MONO |
|
9 | 9 | |
|
10 | 10 | using NUnit.Framework; |
|
11 | 11 | using TestClassAttribute = NUnit.Framework.TestFixtureAttribute; |
|
12 | 12 | using TestMethodAttribute = NUnit.Framework.TestAttribute; |
|
13 | 13 | using AssertFailedException = NUnit.Framework.AssertionException; |
|
14 | 14 | #else |
|
15 | 15 | |
|
16 | 16 | using Microsoft.VisualStudio.TestTools.UnitTesting; |
|
17 | 17 | |
|
18 | 18 | #endif |
|
19 | 19 | |
|
20 | 20 | namespace Implab.Test { |
|
21 | 21 | [TestClass] |
|
22 | 22 | public class RunnableComponentTests { |
|
23 | 23 | |
|
24 | 24 | static void ShouldThrow(Action action) { |
|
25 | 25 | try { |
|
26 | 26 | action(); |
|
27 | 27 | Assert.Fail(); |
|
28 | 28 | } catch (AssertFailedException) { |
|
29 | 29 | throw; |
|
30 | 30 | } catch { |
|
31 | 31 | } |
|
32 | 32 | } |
|
33 | 33 | |
|
34 | 34 | |
|
35 | 35 | |
|
36 | 36 | [TestMethod] |
|
37 | 37 | public void NormalFlowTest() { |
|
38 | 38 | var comp = new MockRunnableComponent(false); |
|
39 | 39 | |
|
40 | 40 | Assert.AreEqual(ExecutionState.Created, comp.State); |
|
41 | 41 | |
|
42 | 42 | comp.Initialize(); |
|
43 | 43 | |
|
44 | 44 | Assert.AreEqual(ExecutionState.Ready, comp.State); |
|
45 | 45 | |
|
46 | 46 | comp.Start().Join(1000); |
|
47 | 47 | |
|
48 | 48 | Assert.AreEqual(ExecutionState.Running, comp.State); |
|
49 | 49 | |
|
50 | 50 | comp.Stop().Join(1000); |
|
51 | 51 | |
|
52 | 52 | Assert.AreEqual(ExecutionState.Disposed, comp.State); |
|
53 | 53 | |
|
54 | 54 | } |
|
55 | 55 | |
|
56 | 56 | [TestMethod] |
|
57 | 57 | public void InitFailTest() { |
|
58 | 58 | var comp = new MockRunnableComponent(false) { |
|
59 | 59 | MockInit = () => { |
|
60 | 60 | throw new Exception("BAD"); |
|
61 | 61 | } |
|
62 | 62 | }; |
|
63 | 63 | |
|
64 | 64 | ShouldThrow(() => comp.Start()); |
|
65 | 65 | ShouldThrow(() => comp.Stop()); |
|
66 | 66 | Assert.AreEqual(ExecutionState.Created, comp.State); |
|
67 | 67 | |
|
68 | 68 | ShouldThrow(comp.Initialize); |
|
69 | 69 | |
|
70 | 70 | Assert.AreEqual(ExecutionState.Failed, comp.State); |
|
71 | 71 | |
|
72 | 72 | ShouldThrow(() => comp.Start()); |
|
73 | 73 | ShouldThrow(() => comp.Stop()); |
|
74 | 74 | Assert.AreEqual(ExecutionState.Failed, comp.State); |
|
75 | 75 | |
|
76 | 76 | comp.Dispose(); |
|
77 | 77 | Assert.AreEqual(ExecutionState.Disposed, comp.State); |
|
78 | 78 | } |
|
79 | 79 | |
|
80 | 80 | [TestMethod] |
|
81 | 81 | public void DisposedTest() { |
|
82 | 82 | |
|
83 | 83 | var comp = new MockRunnableComponent(false); |
|
84 | 84 | comp.Dispose(); |
|
85 | 85 | |
|
86 | 86 | ShouldThrow(() => comp.Start()); |
|
87 | 87 | ShouldThrow(() => comp.Stop()); |
|
88 | 88 | ShouldThrow(comp.Initialize); |
|
89 | 89 | |
|
90 | 90 | Assert.AreEqual(ExecutionState.Disposed, comp.State); |
|
91 | 91 | } |
|
92 | 92 | |
|
93 | 93 | [TestMethod] |
|
94 | 94 | public void ShouldCallDisposeOnStop() { |
|
95 | 95 | var comp = new MockRunnableComponent(true); |
|
96 | 96 | |
|
97 | 97 | bool disposed = false; |
|
98 |
comp.MockDispose = (disposing |
|
|
98 | comp.MockDispose = (disposing) => { | |
|
99 | 99 | disposed = true; |
|
100 | 100 | }; |
|
101 | 101 | |
|
102 | 102 | comp.Start().Join(1000); |
|
103 | 103 | comp.Stop().Join(1000); |
|
104 | 104 | |
|
105 | 105 | ShouldThrow(() => comp.Start()); |
|
106 | 106 | ShouldThrow(() => comp.Stop()); |
|
107 | 107 | ShouldThrow(comp.Initialize); |
|
108 | 108 | |
|
109 | 109 | Assert.AreEqual(ExecutionState.Disposed, comp.State); |
|
110 | 110 | Assert.IsTrue(disposed); |
|
111 | 111 | } |
|
112 | 112 | |
|
113 | 113 | [TestMethod] |
|
114 | 114 | public void ShouldNotCallDisposeOnStop() { |
|
115 | 115 | var comp = new MockRunnableComponent(true, true); |
|
116 | 116 | |
|
117 | 117 | bool disposed = false; |
|
118 |
comp.MockDispose = (disposing |
|
|
118 | comp.MockDispose = (disposing) => { | |
|
119 | 119 | disposed = true; |
|
120 | 120 | }; |
|
121 | 121 | |
|
122 | 122 | comp.Start().Join(1000); |
|
123 | 123 | comp.Stop().Join(1000); |
|
124 | 124 | |
|
125 | 125 | Assert.AreEqual(ExecutionState.Ready, comp.State); |
|
126 | 126 | Assert.IsFalse(disposed); |
|
127 | 127 | } |
|
128 | 128 | |
|
129 | 129 | [TestMethod] |
|
130 | 130 | public void SelfDisposeOnStop() { |
|
131 | 131 | var comp = new MockRunnableComponent(true, true); |
|
132 | 132 | |
|
133 | 133 | bool disposed = false; |
|
134 | Exception lastError = null; | |
|
135 | comp.MockDispose = (disposing, error) => { | |
|
134 | comp.MockDispose = (disposing) => { | |
|
136 | 135 | disposed = true; |
|
137 | lastError = error; | |
|
138 | 136 | }; |
|
139 | 137 | |
|
140 | 138 | comp.Start().Join(1000); |
|
141 | 139 | comp.Stop().Join(1000); |
|
142 | 140 | |
|
143 | 141 | Assert.AreEqual(ExecutionState.Ready, comp.State); |
|
144 | 142 | Assert.IsFalse(disposed); |
|
145 | 143 | |
|
146 | 144 | comp.MockStop = () => { |
|
147 | 145 | comp.Dispose(); |
|
148 | 146 | return Promise.Success; |
|
149 | 147 | }; |
|
150 | 148 | |
|
151 | 149 | comp.Start().Join(1000); |
|
152 | 150 | comp.Stop().Join(1000); |
|
153 | 151 | |
|
154 | 152 | Assert.AreEqual(ExecutionState.Disposed, comp.State); |
|
155 | 153 | Assert.IsTrue(disposed); |
|
156 | 154 | } |
|
157 | 155 | |
|
158 | 156 | [TestMethod] |
|
159 | 157 | public void StartCancelTest() { |
|
160 | 158 | var comp = new MockRunnableComponent(true) { |
|
161 | 159 | MockStart = () => PromiseHelper.Sleep(100000, 0) |
|
162 | 160 | }; |
|
163 | 161 | |
|
164 | 162 | var p = comp.Start(); |
|
165 | 163 | Assert.AreEqual(ExecutionState.Starting, comp.State); |
|
166 | 164 | p.Cancel(); |
|
167 | 165 | ShouldThrow(() => p.Join(1000)); |
|
168 | 166 | Assert.AreEqual(ExecutionState.Failed, comp.State); |
|
169 | 167 | |
|
170 | 168 | Assert.IsTrue(comp.LastError is OperationCanceledException); |
|
171 | 169 | |
|
172 | 170 | comp.Dispose(); |
|
173 | 171 | } |
|
174 | 172 | |
|
175 | 173 | [TestMethod] |
|
176 | 174 | public void StartStopTest() { |
|
177 | 175 | var stop = new Signal(); |
|
178 | 176 | var comp = new MockRunnableComponent(true) { |
|
179 | 177 | MockStart = () => PromiseHelper.Sleep(100000, 0), |
|
180 | 178 | MockStop = () => AsyncPool.RunThread(stop.Wait) |
|
181 | 179 | }; |
|
182 | 180 | |
|
183 | 181 | var p1 = comp.Start(); |
|
184 | 182 | var p2 = comp.Stop(); |
|
185 | 183 | // should enter stopping state |
|
186 | 184 | |
|
187 | 185 | ShouldThrow(p1.Join); |
|
188 | 186 | Assert.IsTrue(p1.IsCancelled); |
|
189 | 187 | Assert.AreEqual(ExecutionState.Stopping, comp.State); |
|
190 | 188 | |
|
191 | 189 | stop.Set(); |
|
192 | 190 | p2.Join(1000); |
|
193 | 191 | Assert.AreEqual(ExecutionState.Disposed, comp.State); |
|
194 | 192 | } |
|
195 | 193 | |
|
196 | 194 | [TestMethod] |
|
197 | 195 | public void StartStopFailTest() { |
|
198 | 196 | var comp = new MockRunnableComponent(true) { |
|
199 | 197 | MockStart = () => PromiseHelper.Sleep(100000, 0).Then(null,null,x => { throw new Exception("I'm dead"); }) |
|
200 | 198 | }; |
|
201 | 199 | |
|
202 | 200 | comp.Start(); |
|
203 | 201 | var p = comp.Stop(); |
|
204 | 202 | // if Start fails to cancel, should fail to stop |
|
205 | 203 | ShouldThrow(() => p.Join(1000)); |
|
206 | 204 | Assert.AreEqual(ExecutionState.Failed, comp.State); |
|
207 | 205 | Assert.IsNotNull(comp.LastError); |
|
208 | 206 | Assert.AreEqual("I'm dead", comp.LastError.Message); |
|
209 | 207 | } |
|
210 | 208 | |
|
211 | 209 | [TestMethod] |
|
212 | 210 | public void StopCancelTest() { |
|
213 | 211 | var comp = new MockRunnableComponent(true) { |
|
214 | 212 | MockStop = () => PromiseHelper.Sleep(100000, 0) |
|
215 | 213 | }; |
|
216 | 214 | |
|
217 | 215 | comp.Start(); |
|
218 | 216 | var p = comp.Stop(); |
|
219 | 217 | Assert.AreEqual(ExecutionState.Stopping, comp.State); |
|
220 | 218 | p.Cancel(); |
|
221 | 219 | ShouldThrow(() => p.Join(1000)); |
|
222 | 220 | Assert.AreEqual(ExecutionState.Failed, comp.State); |
|
223 | 221 | Assert.IsTrue(comp.LastError is OperationCanceledException); |
|
224 | 222 | |
|
225 | 223 | comp.Dispose(); |
|
226 | 224 | } |
|
227 | 225 | |
|
228 | 226 | } |
|
229 | 227 | } |
|
230 | 228 |
@@ -1,103 +1,105 | |||
|
1 | 1 | using Implab.Diagnostics; |
|
2 | 2 | using System; |
|
3 | using System.Diagnostics.CodeAnalysis; | |
|
3 | 4 | using System.Threading; |
|
4 | 5 | |
|
5 | 6 | namespace Implab.Components { |
|
6 | 7 | /// <summary> |
|
7 | 8 | /// Base class the objects which support disposing. |
|
8 | 9 | /// </summary> |
|
9 | 10 | public class Disposable : IDisposable { |
|
10 | 11 | |
|
11 | 12 | int m_disposed; |
|
12 | 13 | |
|
13 | 14 | public event EventHandler Disposed; |
|
14 | 15 | |
|
15 | 16 | public bool IsDisposed { |
|
16 | 17 | get { |
|
17 | 18 | Thread.MemoryBarrier(); |
|
18 | 19 | return m_disposed != 0; |
|
19 | 20 | } |
|
20 | 21 | } |
|
21 | 22 | |
|
22 | 23 | /// <summary> |
|
23 | 24 | /// Asserts the object is not disposed. |
|
24 | 25 | /// </summary> |
|
25 | 26 | /// <exception cref="ObjectDisposedException">The object is disposed</exception> |
|
26 | 27 | /// <remarks> |
|
27 | 28 | /// Π£ΡΠΏΠ΅ΡΠ½Π°Ρ ΠΏΡΠΎΠ²Π΅ΡΠΊΠ° ΡΠΎΠ³ΠΎ, ΡΡΠΎ ΠΎΠ±ΡΠ΅ΠΊΡ Π½Π΅ ΠΎΡΠ²ΠΎΠ±ΠΎΠΆΠ΄Π΅Π½ Π΅ΡΠ΅ Π½Π΅ Π³Π°ΡΠ°Π½ΡΠΈΡΡΠ΅Ρ, ΡΡΠΎ ΠΎΠ½ Π½Π΅ |
|
28 | 29 | /// Π±ΡΠ΄Π΅Ρ ΠΎΡΠ²ΠΎΠ±ΠΎΠΆΠ΄Π΅Π½ ΡΡΠ°Π·Ρ ΠΏΠΎΡΠ»Π΅ Π½Π΅Π΅, ΠΏΠΎΡΡΠΎΠΌΡ ΠΌΠ΅ΡΠΎΠ΄Ρ ΠΈΡΠΏΠΎΠ»ΡΠ·ΡΡΡΠΈΠ΅ ΠΏΡΠΎΠ²Π΅ΡΠΊΡ Π΄ΠΎΠ»ΠΆΠ½Ρ |
|
29 | 30 | /// ΡΡΠΈΡΡΠ²Π°ΡΡ, ΡΡΠΎ ΠΎΠ±ΡΠ΅ΠΊΡ ΠΌΠΎΠΆΠ΅Ρ Π±ΡΡΡ ΠΎΡΠ²ΠΎΠ±ΠΎΠΆΠ΄Π΅Π½ ΠΈΠ· ΠΏΠ°ΡΠ°Π»Π»Π΅Π»ΡΠ½ΠΎΠ³ΠΎ ΠΏΠΎΡΠΎΠΊΠ°. |
|
30 | 31 | /// ΠΠ°Π½Π½Ρ ΠΌΠ΅ΡΠΎΠ΄ ΡΠ»ΡΠΆΠΈΡ Π΄Π»Ρ ΡΠΏΡΠΎΡΠ΅Π½ΠΈΡ ΠΎΡΠ»Π°Π΄ΠΊΠΈ ΠΎΡΠΈΠ±ΠΎΠΊ ΠΏΡΠΈ ΠΈΡΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°Π½ΠΈΠΈ ΠΎΠ±ΡΠ΅ΠΊΡΠ° ΠΏΠΎΡΠ»Π΅ Π΅Π³ΠΎ |
|
31 | 32 | /// ΠΎΡΠ²ΠΎΠ±ΠΎΠΆΠ΄Π΅Π½ΠΈΡ. |
|
32 | 33 | /// </remarks> |
|
33 | 34 | /// <example> |
|
34 | 35 | /// // ΠΏΡΠΈΠΌΠ΅Ρ ΡΠΈΠ½Ρ ΡΠΎΠ½ΠΈΠ·ΠΈΡΠΎΠ²Π°Π½Π½ΠΎΠ³ΠΎ ΠΎΡΠ²ΠΎΠ±ΠΎΠΆΠ΄Π΅Π½ΠΈΡ ΡΠ΅ΡΡΡΡΠΎΠ² |
|
35 | 36 | /// class FileStore : Disposable { |
|
36 | 37 | /// readonly TextWriter m_file; |
|
37 | 38 | /// readonly obejct m_sync = new object(); |
|
38 | 39 | /// |
|
39 | 40 | /// public FileStore(string file) { |
|
40 | 41 | /// m_file = new TextWriter(File.OpenWrite(file)); |
|
41 | 42 | /// } |
|
42 | 43 | /// |
|
43 | 44 | /// public void Write(string text) { |
|
44 | 45 | /// lock(m_sync) { |
|
45 | 46 | /// AssertNotDisposed(); |
|
46 | 47 | /// m_file.Write(text); |
|
47 | 48 | /// } |
|
48 | 49 | /// } |
|
49 | 50 | /// |
|
50 | 51 | /// protected override void Dispose(bool disposing) { |
|
51 | 52 | /// if (disposing) |
|
52 | 53 | /// lock(m_sync) { |
|
53 | 54 | /// m_file.Dipose(); |
|
54 | 55 | /// base.Dispose(true); |
|
55 | 56 | /// } |
|
56 | 57 | /// else |
|
57 | 58 | /// base.Dispose(false); |
|
58 | 59 | /// } |
|
59 | 60 | /// } |
|
60 | 61 | /// <example> |
|
61 | 62 | protected void AssertNotDisposed() { |
|
62 | 63 | Thread.MemoryBarrier(); |
|
63 | 64 | if (m_disposed != 0) |
|
64 | 65 | throw new ObjectDisposedException(ToString()); |
|
65 | 66 | } |
|
66 | 67 | /// <summary> |
|
67 | 68 | /// ΠΡΠ·ΡΠ²Π°Π΅Ρ ΡΠΎΠ±ΡΡΠΈΠ΅ <see cref="Disposed"/> |
|
68 | 69 | /// </summary> |
|
69 | 70 | /// <param name="disposing">ΠΡΠΈΠ·Π½Π°ΠΊ ΡΠΎΠ³ΠΎ, ΡΡΠΎ Π½ΡΠΆΠ½ΠΎ ΠΎΡΠ²ΠΎΠ±ΠΎΠ΄ΠΈΡΡ ΡΠ΅ΡΡΡΡΡ, ΠΈΠ½Π°ΡΠ΅ Π΄Π°Π½Π½ΡΠΉ ΠΌΠ΅ΡΠΎΠ΄ |
|
70 | 71 | /// Π²ΡΠ·Π²Π°Π½ ΡΠ±ΠΎΡΡΠΈΠΊΠΎΠΌ ΠΌΡΡΠΎΡΠ° ΠΈ Π½ΡΠΆΠ½ΠΎ ΠΎΡΠ²ΠΎΠ±ΠΎΠΆΠ΄Π°ΡΡ Π’ΠΠΠ¬ΠΠ Π½Π΅ΡΠΏΡΠ°Π²Π»ΡΠ΅ΠΌΡΠ΅ ΡΠ΅ΡΡΡΡΡ Π’ΠΠΠ¬ΠΠ ΡΡΠΎΠ³ΠΎ |
|
71 | 72 | /// ΠΎΠ±ΡΠ΅ΠΊΡΠ°.</param> |
|
72 | 73 | /// <remarks> |
|
73 | 74 | /// ΠΠ°Π½Π½ΡΠΉ ΠΌΠ΅ΡΠΎΠ΄ Π²ΡΠ·ΡΠ²Π°Π΅ΡΡΡ Π³Π°ΡΠ°Π½ΡΠΈΡΠΎΠ²Π°Π½Π½ΠΎ ΠΎΠ΄ΠΈΠ½ ΡΠ°Π· Π΄Π°ΠΆΠ΅ ΠΏΡΠΈ ΠΎΠ΄Π½ΠΎΠ²ΡΠ΅ΠΌΠ΅Π½Π½ΠΎΠΌ Π²ΡΠ·ΠΎΠ²Π΅ <see cref="Dispose()"/> |
|
74 | 75 | /// ΠΈΠ· Π½Π΅ΡΠΊΠΎΠ»ΡΠΊΠΈΡ ΠΏΠΎΡΠΎΠΊΠΎΠ². |
|
75 | 76 | /// </remarks> |
|
76 | 77 | protected virtual void Dispose(bool disposing) { |
|
77 | 78 | if (disposing) { |
|
78 | 79 | EventHandler temp = Disposed; |
|
79 | 80 | if (temp != null) |
|
80 | 81 | temp(this, EventArgs.Empty); |
|
81 | 82 | } |
|
82 | 83 | } |
|
83 | 84 | |
|
85 | [SuppressMessage("Microsoft.Design", "CA1063:ImplementIDisposableCorrectly", Justification = "Dipose(bool) and GC.SuppessFinalize are called")] | |
|
84 | 86 | public void Dispose() { |
|
85 | 87 | if (Interlocked.Increment(ref m_disposed) == 1) { |
|
86 | 88 | Dispose(true); |
|
87 | 89 | GC.SuppressFinalize(this); |
|
88 | 90 | } |
|
89 | 91 | } |
|
90 | 92 | |
|
91 | 93 | /// <summary> |
|
92 | 94 | /// ΠΠ°ΠΏΠΈΡΡΠ²Π°Π΅Ρ ΡΠΎΠΎΠ±ΡΠ΅Π½ΠΈΠ΅ ΠΎΠ± ΡΡΠ΅ΡΠΊΠ΅ ΠΎΠ±ΡΠ΅ΠΊΡΠ°. |
|
93 | 95 | /// </summary> |
|
94 | 96 | protected virtual void ReportObjectLeaks() { |
|
95 | 97 | TraceLog.TraceWarning("The object is marked as disposable but isn't disposed properly: {0}", this); |
|
96 | 98 | } |
|
97 | 99 | |
|
98 | 100 | ~Disposable() { |
|
99 | 101 | Dispose(false); |
|
100 | 102 | ReportObjectLeaks(); |
|
101 | 103 | } |
|
102 | 104 | } |
|
103 | 105 | } No newline at end of file |
@@ -1,22 +1,30 | |||
|
1 | 1 | using System; |
|
2 | 2 | |
|
3 | 3 | namespace Implab.Components { |
|
4 | public interface IRunnable { | |
|
4 | /// <summary> | |
|
5 | /// Interface for the component which performs a long running task. | |
|
6 | /// </summary> | |
|
7 | /// <remarks> | |
|
8 | /// <para>The component also should implement <see cref="IDisposable"/> interface to be able to release used resources.</para> | |
|
9 | /// <para>All methods of this interface must be a thread safe. If the operation is not applicable in the current state the | |
|
10 | /// method should throw an exception and keep the current state unchanged.</para> | |
|
11 | /// </remarks> | |
|
12 | public interface IRunnable : IDisposable { | |
|
5 | 13 | /// <summary> |
|
6 |
/// Starts this instance |
|
|
14 | /// Starts this instance | |
|
7 | 15 | /// </summary> |
|
8 | 16 | IPromise Start(); |
|
9 | 17 | |
|
10 | 18 | /// <summary> |
|
11 | /// Stops this instance. After the instance is stopped it can't be started again, stopping should be treated as gracefull and async dispose. | |
|
19 | /// Stops this instance, after the instance is stopped it can move to Failed, Ready or Disposed state, in case with the last it can't be reused. | |
|
12 | 20 | /// </summary> |
|
13 | 21 | IPromise Stop(); |
|
14 | 22 | |
|
15 | 23 | ExecutionState State { get; } |
|
16 | 24 | |
|
17 | 25 | event EventHandler<StateChangeEventArgs> StateChanged; |
|
18 | 26 | |
|
19 | 27 | Exception LastError { get; } |
|
20 | 28 | } |
|
21 | 29 | } |
|
22 | 30 |
@@ -1,155 +1,155 | |||
|
1 | 1 | using System; |
|
2 | 2 | using System.Threading; |
|
3 | 3 | using Implab.Diagnostics; |
|
4 | 4 | |
|
5 | 5 | namespace Implab.Components { |
|
6 | 6 | public class PollingComponent : RunnableComponent { |
|
7 | 7 | readonly Timer m_timer; |
|
8 | 8 | readonly Func<Func<ICancellationToken, IPromise>, IPromise> m_dispatcher; |
|
9 | 9 | readonly TimeSpan m_interval; |
|
10 | 10 | |
|
11 | 11 | readonly object m_lock = new object(); |
|
12 | 12 | |
|
13 | 13 | ActionTask m_pending; |
|
14 | 14 | |
|
15 | 15 | protected PollingComponent(TimeSpan interval, Func<Func<ICancellationToken, IPromise>, IPromise> dispatcher, bool initialized) : base(initialized) { |
|
16 | 16 | m_timer = new Timer(OnInternalTick); |
|
17 | 17 | |
|
18 | 18 | m_interval = interval; |
|
19 | 19 | m_dispatcher = dispatcher; |
|
20 | 20 | } |
|
21 | 21 | |
|
22 | 22 | protected override IPromise OnStart() { |
|
23 | 23 | m_timer.Change(TimeSpan.Zero, m_interval); |
|
24 | 24 | |
|
25 | 25 | return base.OnStart(); |
|
26 | 26 | } |
|
27 | 27 | |
|
28 | 28 | void OnInternalTick(object state) { |
|
29 | 29 | if (StartTick()) { |
|
30 | 30 | try { |
|
31 | 31 | if (m_dispatcher != null) { |
|
32 | 32 | var result = m_dispatcher(OnTick); |
|
33 | 33 | m_pending.CancellationRequested(result.Cancel); |
|
34 | 34 | AwaitTick(result); |
|
35 | 35 | } else { |
|
36 | 36 | AwaitTick(OnTick(m_pending)); |
|
37 | 37 | } |
|
38 | 38 | } catch (Exception error) { |
|
39 | 39 | HandleTickError(error); |
|
40 | 40 | } |
|
41 | 41 | } |
|
42 | 42 | } |
|
43 | 43 | |
|
44 | 44 | /// <summary> |
|
45 | 45 | /// Checks wheather there is no running handler in the component and marks that the handler is starting. |
|
46 | 46 | /// </summary> |
|
47 | 47 | /// <returns>boolean value, true - the new tick handler may be invoked, false - a tick handler is already running or a component isn't running.</returns> |
|
48 | 48 | /// <remarks> |
|
49 | 49 | /// If the component is stopping no new handlers can be run. Every successful call to this method must be completed with either AwaitTick or HandleTickError handlers. |
|
50 | 50 | /// </remarks> |
|
51 | 51 | protected virtual bool StartTick() { |
|
52 | 52 | lock (m_lock) { |
|
53 | 53 | if (State != ExecutionState.Running || m_pending != null) |
|
54 | 54 | return false; |
|
55 | 55 | // actually the component may be in a not running state here (stopping, disposed or what ever), |
|
56 | 56 | // but OnStop method also locks on the same object and will handle operation in m_pending |
|
57 | 57 | m_pending = new ActionTask( |
|
58 | 58 | () => { |
|
59 | 59 | // only one operation is running, it's safe to assing m_pending from it |
|
60 | 60 | m_pending = null; |
|
61 | 61 | }, |
|
62 | 62 | ex => { |
|
63 | 63 | try { |
|
64 | 64 | OnTickError(ex); |
|
65 | 65 | // Analysis disable once EmptyGeneralCatchClause |
|
66 | 66 | } catch { |
|
67 | 67 | } finally { |
|
68 | 68 | m_pending = null; |
|
69 | 69 | } |
|
70 | 70 | // suppress error |
|
71 | 71 | }, |
|
72 | 72 | ex => { |
|
73 | 73 | try { |
|
74 | 74 | OnTickCancel(ex); |
|
75 | 75 | // Analysis disable once EmptyGeneralCatchClause |
|
76 | 76 | } catch { |
|
77 | 77 | } finally { |
|
78 | 78 | m_pending = null; |
|
79 | 79 | } |
|
80 | 80 | // supress cancellation |
|
81 | 81 | }, |
|
82 | 82 | false |
|
83 | 83 | ); |
|
84 | 84 | return true; |
|
85 | 85 | } |
|
86 | 86 | } |
|
87 | 87 | |
|
88 | 88 | /// <summary> |
|
89 | 89 | /// Awaits the tick. |
|
90 | 90 | /// </summary> |
|
91 | 91 | /// <param name="tick">Tick.</param> |
|
92 | 92 | /// <remarks> |
|
93 | 93 | /// This method is called only after StartTick method and m_pending will hold the promise which should be fulfilled. |
|
94 | 94 | /// </remarks> |
|
95 | 95 | void AwaitTick(IPromise tick) { |
|
96 | 96 | if (tick == null) { |
|
97 | 97 | m_pending.Resolve(); |
|
98 | 98 | } else { |
|
99 | 99 | tick.On( |
|
100 | 100 | m_pending.Resolve, |
|
101 | 101 | m_pending.Reject, |
|
102 | 102 | m_pending.CancelOperation |
|
103 | 103 | ); |
|
104 | 104 | } |
|
105 | 105 | } |
|
106 | 106 | |
|
107 | 107 | /// <summary> |
|
108 | 108 | /// Handles the tick error. |
|
109 | 109 | /// </summary> |
|
110 | 110 | /// <remarks> |
|
111 | 111 | /// This method is called only after StartTick method and m_pending will hold the promise which should be fulfilled. |
|
112 | 112 | /// </remarks> |
|
113 | 113 | void HandleTickError(Exception error) { |
|
114 | 114 | m_pending.Reject(error); |
|
115 | 115 | } |
|
116 | 116 | |
|
117 | 117 | protected virtual void OnTickError(Exception error) { |
|
118 | 118 | } |
|
119 | 119 | |
|
120 | 120 | protected virtual void OnTickCancel(Exception error) { |
|
121 | 121 | } |
|
122 | 122 | |
|
123 | 123 | /// <summary> |
|
124 | 124 | /// Invoked when the timer ticks, use this method to implement your logic |
|
125 | 125 | /// </summary> |
|
126 | 126 | protected virtual IPromise OnTick(ICancellationToken cancellationToken) { |
|
127 | 127 | return Promise.Success; |
|
128 | 128 | } |
|
129 | 129 | |
|
130 | 130 | protected override IPromise OnStop() { |
|
131 | 131 | m_timer.Change(-1, -1); |
|
132 | 132 | |
|
133 | 133 | // the component is in the stopping state |
|
134 | 134 | lock (m_lock) { |
|
135 | 135 | // after this lock no more pending operations could be created |
|
136 | 136 | var pending = m_pending; |
|
137 | 137 | // m_pending could be fulfilled and set to null already |
|
138 | 138 | if (pending != null) { |
|
139 | 139 | pending.Cancel(); |
|
140 | 140 | return pending.Then(base.OnStop); |
|
141 | 141 | } |
|
142 | 142 | } |
|
143 | 143 | |
|
144 | 144 | return base.OnStop(); |
|
145 | 145 | } |
|
146 | 146 | |
|
147 |
protected override void Dispose(bool disposing |
|
|
147 | protected override void Dispose(bool disposing) { | |
|
148 | 148 | if (disposing) |
|
149 |
|
|
|
149 | m_timer.Dispose(); | |
|
150 | 150 | |
|
151 |
base.Dispose(disposing |
|
|
151 | base.Dispose(disposing); | |
|
152 | 152 | } |
|
153 | 153 | } |
|
154 | 154 | } |
|
155 | 155 |
@@ -1,375 +1,368 | |||
|
1 | 1 | using System; |
|
2 | ||
|
2 | using System.Diagnostics.CodeAnalysis; | |
|
3 | ||
|
3 | 4 | namespace Implab.Components { |
|
4 | 5 | public abstract class RunnableComponent : IDisposable, IRunnable, IInitializable { |
|
5 | 6 | enum Commands { |
|
6 | 7 | Ok = 0, |
|
7 | 8 | Fail, |
|
8 | 9 | Init, |
|
9 | 10 | Start, |
|
10 | 11 | Stop, |
|
11 | 12 | Dispose, |
|
12 | 13 | Reset, |
|
13 | 14 | Last = Reset |
|
14 | 15 | } |
|
15 | 16 | |
|
16 | 17 | class StateMachine { |
|
17 | 18 | static readonly ExecutionState[,] _transitions; |
|
18 | 19 | |
|
19 | 20 | static StateMachine() { |
|
20 | 21 | _transitions = new ExecutionState[(int)ExecutionState.Last + 1, (int)Commands.Last + 1]; |
|
21 | 22 | |
|
22 | 23 | Edge(ExecutionState.Created, ExecutionState.Initializing, Commands.Init); |
|
23 | 24 | Edge(ExecutionState.Created, ExecutionState.Disposed, Commands.Dispose); |
|
24 | 25 | |
|
25 | 26 | Edge(ExecutionState.Initializing, ExecutionState.Ready, Commands.Ok); |
|
26 | 27 | Edge(ExecutionState.Initializing, ExecutionState.Failed, Commands.Fail); |
|
27 | 28 | |
|
28 | 29 | Edge(ExecutionState.Ready, ExecutionState.Starting, Commands.Start); |
|
29 | 30 | Edge(ExecutionState.Ready, ExecutionState.Disposed, Commands.Dispose); |
|
30 | 31 | |
|
31 | 32 | Edge(ExecutionState.Starting, ExecutionState.Running, Commands.Ok); |
|
32 | 33 | Edge(ExecutionState.Starting, ExecutionState.Failed, Commands.Fail); |
|
33 | 34 | Edge(ExecutionState.Starting, ExecutionState.Stopping, Commands.Stop); |
|
34 | 35 | Edge(ExecutionState.Starting, ExecutionState.Disposed, Commands.Dispose); |
|
35 | 36 | |
|
36 | 37 | Edge(ExecutionState.Running, ExecutionState.Failed, Commands.Fail); |
|
37 | 38 | Edge(ExecutionState.Running, ExecutionState.Stopping, Commands.Stop); |
|
38 | 39 | Edge(ExecutionState.Running, ExecutionState.Disposed, Commands.Dispose); |
|
39 | 40 | |
|
40 | 41 | Edge(ExecutionState.Stopping, ExecutionState.Failed, Commands.Fail); |
|
41 | 42 | Edge(ExecutionState.Stopping, ExecutionState.Ready, Commands.Ok); |
|
42 | 43 | Edge(ExecutionState.Stopping, ExecutionState.Disposed, Commands.Dispose); |
|
43 | 44 | |
|
44 | 45 | Edge(ExecutionState.Failed, ExecutionState.Disposed, Commands.Dispose); |
|
45 | 46 | Edge(ExecutionState.Failed, ExecutionState.Initializing, Commands.Reset); |
|
46 | 47 | } |
|
47 | 48 | |
|
48 | 49 | static void Edge(ExecutionState s1, ExecutionState s2, Commands cmd) { |
|
49 | 50 | _transitions[(int)s1, (int)cmd] = s2; |
|
50 | 51 | } |
|
51 | 52 | |
|
52 | 53 | public ExecutionState State { |
|
53 | 54 | get; |
|
54 | 55 | private set; |
|
55 | 56 | } |
|
56 | 57 | |
|
57 | 58 | public StateMachine(ExecutionState initial) { |
|
58 | 59 | State = initial; |
|
59 | 60 | } |
|
60 | 61 | |
|
61 | 62 | public bool Move(Commands cmd) { |
|
62 | 63 | var next = _transitions[(int)State, (int)cmd]; |
|
63 | 64 | if (next == ExecutionState.Undefined) |
|
64 | 65 | return false; |
|
65 | 66 | State = next; |
|
66 | 67 | return true; |
|
67 | 68 | } |
|
68 | 69 | } |
|
69 | 70 | |
|
70 | 71 | IPromise m_pending; |
|
71 | 72 | Exception m_lastError; |
|
72 | 73 | |
|
73 | 74 | readonly StateMachine m_stateMachine; |
|
74 | 75 | readonly bool m_reusable; |
|
75 | 76 | public event EventHandler<StateChangeEventArgs> StateChanged; |
|
76 | 77 | |
|
77 | 78 | /// <summary> |
|
78 | 79 | /// Initializes component state. |
|
79 | 80 | /// </summary> |
|
80 | 81 | /// <param name="initialized">If set, the component initial state is <see cref="ExecutionState.Ready"/> and the component is ready to start, otherwise initialization is required.</param> |
|
81 | 82 | /// <param name="reusable">If set, the component may start after it has been stopped, otherwise the component is disposed after being stopped.</param> |
|
82 | 83 | protected RunnableComponent(bool initialized, bool reusable) { |
|
83 | 84 | m_stateMachine = new StateMachine(initialized ? ExecutionState.Ready : ExecutionState.Created); |
|
84 | 85 | m_reusable = reusable; |
|
85 | 86 | DisposeTimeout = 10000; |
|
86 | 87 | } |
|
87 | 88 | |
|
88 | 89 | /// <summary> |
|
89 | 90 | /// Initializes component state. The component created with this constructor is not reusable, i.e. it will be disposed after stop. |
|
90 | 91 | /// </summary> |
|
91 | 92 | /// <param name="initialized">If set, the component initial state is <see cref="ExecutionState.Ready"/> and the component is ready to start, otherwise initialization is required.</param> |
|
92 | 93 | protected RunnableComponent(bool initialized) : this(initialized, false) { |
|
93 | 94 | } |
|
94 | 95 | |
|
95 | 96 | /// <summary> |
|
96 | 97 | /// Gets or sets the timeout to wait for the pending operation to complete. If the pending operation doesn't finish than the component will be disposed anyway. |
|
97 | 98 | /// </summary> |
|
98 | 99 | protected int DisposeTimeout { |
|
99 | 100 | get; |
|
100 | 101 | set; |
|
101 | 102 | } |
|
102 | 103 | |
|
103 | 104 | void ThrowInvalidCommand(Commands cmd) { |
|
104 | 105 | if (m_stateMachine.State == ExecutionState.Disposed) |
|
105 | 106 | throw new ObjectDisposedException(ToString()); |
|
106 | 107 | |
|
107 | 108 | throw new InvalidOperationException(String.Format("Command {0} is not allowed in the state {1}", cmd, m_stateMachine.State)); |
|
108 | 109 | } |
|
109 | 110 | |
|
110 | 111 | bool MoveIfInState(Commands cmd, IPromise pending, Exception error, ExecutionState state) { |
|
111 | 112 | ExecutionState prev, current; |
|
112 | 113 | lock (m_stateMachine) { |
|
113 | 114 | if (m_stateMachine.State != state) |
|
114 | 115 | return false; |
|
115 | 116 | |
|
116 | 117 | prev = m_stateMachine.State; |
|
117 | 118 | if (!m_stateMachine.Move(cmd)) |
|
118 | 119 | ThrowInvalidCommand(cmd); |
|
119 | 120 | current = m_stateMachine.State; |
|
120 | 121 | |
|
121 | 122 | m_pending = pending; |
|
122 | 123 | m_lastError = error; |
|
123 | 124 | } |
|
124 | 125 | if (prev != current) |
|
125 | 126 | OnStateChanged(prev, current, error); |
|
126 | 127 | return true; |
|
127 | 128 | } |
|
128 | 129 | |
|
129 | 130 | bool MoveIfPending(Commands cmd, IPromise pending, Exception error, IPromise expected) { |
|
130 | 131 | ExecutionState prev, current; |
|
131 | 132 | lock (m_stateMachine) { |
|
132 | 133 | if (m_pending != expected) |
|
133 | 134 | return false; |
|
134 | 135 | prev = m_stateMachine.State; |
|
135 | 136 | if (!m_stateMachine.Move(cmd)) |
|
136 | 137 | ThrowInvalidCommand(cmd); |
|
137 | 138 | current = m_stateMachine.State; |
|
138 | 139 | m_pending = pending; |
|
139 | 140 | m_lastError = error; |
|
140 | 141 | } |
|
141 | 142 | if (prev != current) |
|
142 | 143 | OnStateChanged(prev, current, error); |
|
143 | 144 | return true; |
|
144 | 145 | } |
|
145 | 146 | |
|
146 | 147 | IPromise Move(Commands cmd, IPromise pending, Exception error) { |
|
147 | 148 | ExecutionState prev, current; |
|
148 | 149 | IPromise ret; |
|
149 | 150 | lock (m_stateMachine) { |
|
150 | 151 | prev = m_stateMachine.State; |
|
151 | 152 | if (!m_stateMachine.Move(cmd)) |
|
152 | 153 | ThrowInvalidCommand(cmd); |
|
153 | 154 | current = m_stateMachine.State; |
|
154 | 155 | |
|
155 | 156 | ret = m_pending; |
|
156 | 157 | m_pending = pending; |
|
157 | 158 | m_lastError = error; |
|
158 | 159 | |
|
159 | 160 | } |
|
160 | 161 | if(prev != current) |
|
161 | 162 | OnStateChanged(prev, current, error); |
|
162 | 163 | return ret; |
|
163 | 164 | } |
|
164 | 165 | |
|
165 | 166 | protected virtual void OnStateChanged(ExecutionState previous, ExecutionState current, Exception error) { |
|
166 | 167 | var h = StateChanged; |
|
167 | 168 | if (h != null) |
|
168 | 169 | h(this, new StateChangeEventArgs { |
|
169 | 170 | State = current, |
|
170 | 171 | LastError = error |
|
171 | 172 | }); |
|
172 | 173 | } |
|
173 | 174 | |
|
174 | 175 | /// <summary> |
|
175 | 176 | /// Moves the component from running to failed state. |
|
176 | 177 | /// </summary> |
|
177 | 178 | /// <param name="error">The exception which is describing the error.</param> |
|
178 | 179 | protected bool Fail(Exception error) { |
|
179 | 180 | return MoveIfInState(Commands.Fail, null, error, ExecutionState.Running); |
|
180 | 181 | } |
|
181 | 182 | |
|
182 | 183 | /// <summary> |
|
183 | 184 | /// Tries to reset <see cref="ExecutionState.Failed"/> state to <see cref="ExecutionState.Ready"/>. |
|
184 | 185 | /// </summary> |
|
185 | 186 | /// <returns>True if component is reset to <see cref="ExecutionState.Ready"/>, false if the componet wasn't |
|
186 | 187 | /// in <see cref="ExecutionState.Failed"/> state.</returns> |
|
187 | 188 | /// <remarks> |
|
188 | 189 | /// This method checks the current state of the component and if it's in <see cref="ExecutionState.Failed"/> |
|
189 | 190 | /// moves component to <see cref="ExecutionState.Initializing"/>. |
|
190 | 191 | /// The <see cref="OnResetState()"/> is called and if this method completes succesfully the component moved |
|
191 | 192 | /// to <see cref="ExecutionState.Ready"/> state, otherwise the component is moved to <see cref="ExecutionState.Failed"/> |
|
192 | 193 | /// state. If <see cref="OnResetState()"/> throws an exception it will be propagated by this method to the caller. |
|
193 | 194 | /// </remarks> |
|
194 | 195 | protected bool ResetState() { |
|
195 | 196 | if (!MoveIfInState(Commands.Reset, null, null, ExecutionState.Failed)) |
|
196 | 197 | return false; |
|
197 | 198 | |
|
198 | 199 | try { |
|
199 | 200 | OnResetState(); |
|
200 | 201 | Move(Commands.Ok, null, null); |
|
201 | 202 | return true; |
|
202 | 203 | } catch (Exception err) { |
|
203 | 204 | Move(Commands.Fail, null, err); |
|
204 | 205 | throw; |
|
205 | 206 | } |
|
206 | 207 | } |
|
207 | 208 | |
|
208 | 209 | /// <summary> |
|
209 | 210 | /// This method is called by <see cref="ResetState"/> to reinitialize component in the failed state. |
|
210 | 211 | /// </summary> |
|
211 | 212 | /// <remarks> |
|
212 | 213 | /// Default implementation throws <see cref="NotImplementedException"/> which will cause the component |
|
213 | 214 | /// fail to reset it's state and it left in <see cref="ExecutionState.Failed"/> state. |
|
214 | 215 | /// If this method doesn't throw exceptions the component is moved to <see cref="ExecutionState.Ready"/> state. |
|
215 | 216 | /// </remarks> |
|
216 | 217 | protected virtual void OnResetState() { |
|
217 | 218 | throw new NotImplementedException(); |
|
218 | 219 | } |
|
219 | 220 | |
|
220 | 221 | IPromise InvokeAsync(Commands cmd, Func<IPromise> action, Action<IPromise, IDeferred> chain) { |
|
221 | 222 | IPromise promise = null; |
|
222 | 223 | IPromise prev; |
|
223 | 224 | |
|
224 | 225 | var task = new ActionChainTask(action, null, null, true); |
|
225 | 226 | |
|
226 | 227 | Action<Exception> errorOrCancel = e => { |
|
227 | 228 | if (e == null) |
|
228 | 229 | e = new OperationCanceledException(); |
|
229 | 230 | MoveIfPending(Commands.Fail, null, e, promise); |
|
230 | 231 | throw new PromiseTransientException(e); |
|
231 | 232 | }; |
|
232 | 233 | |
|
233 | 234 | promise = task.Then( |
|
234 | 235 | () => MoveIfPending(Commands.Ok, null, null, promise), |
|
235 | 236 | errorOrCancel, |
|
236 | 237 | errorOrCancel |
|
237 | 238 | ); |
|
238 | 239 | |
|
239 | 240 | prev = Move(cmd, promise, null); |
|
240 | 241 | |
|
241 | 242 | if (prev == null) |
|
242 | 243 | task.Resolve(); |
|
243 | 244 | else |
|
244 | 245 | chain(prev, task); |
|
245 | 246 | |
|
246 | 247 | return promise; |
|
247 | 248 | } |
|
248 | 249 | |
|
249 | 250 | |
|
250 | 251 | #region IInitializable implementation |
|
251 | 252 | |
|
252 | 253 | public void Initialize() { |
|
253 | 254 | Move(Commands.Init, null, null); |
|
254 | 255 | |
|
255 | 256 | try { |
|
256 | 257 | OnInitialize(); |
|
257 | 258 | Move(Commands.Ok, null, null); |
|
258 | 259 | } catch (Exception err) { |
|
259 | 260 | Move(Commands.Fail, null, err); |
|
260 | 261 | throw; |
|
261 | 262 | } |
|
262 | 263 | } |
|
263 | 264 | |
|
264 | 265 | protected virtual void OnInitialize() { |
|
265 | 266 | } |
|
266 | 267 | |
|
267 | 268 | #endregion |
|
268 | 269 | |
|
269 | 270 | #region IRunnable implementation |
|
270 | 271 | |
|
271 | 272 | public IPromise Start() { |
|
272 | 273 | return InvokeAsync(Commands.Start, OnStart, null); |
|
273 | 274 | } |
|
274 | 275 | |
|
275 | 276 | protected virtual IPromise OnStart() { |
|
276 | 277 | return Promise.Success; |
|
277 | 278 | } |
|
278 | 279 | |
|
279 | 280 | public IPromise Stop() { |
|
280 | 281 | var pending = InvokeAsync(Commands.Stop, OnStop, StopPending); |
|
281 | 282 | return m_reusable ? pending : pending.Then(Dispose); |
|
282 | 283 | } |
|
283 | 284 | |
|
284 | 285 | protected virtual IPromise OnStop() { |
|
285 | 286 | return Promise.Success; |
|
286 | 287 | } |
|
287 | 288 | |
|
288 | 289 | /// <summary> |
|
289 | 290 | /// Stops the current operation if one exists. |
|
290 | 291 | /// </summary> |
|
291 | 292 | /// <param name="current">Current.</param> |
|
292 | 293 | /// <param name="stop">Stop.</param> |
|
293 | 294 | protected virtual void StopPending(IPromise current, IDeferred stop) { |
|
294 | 295 | if (current == null) { |
|
295 | 296 | stop.Resolve(); |
|
296 | 297 | } else { |
|
297 | 298 | // ΡΠ²ΡΠ·Π²Π°Π΅ΠΌ ΡΠ΅ΠΊΡΡΡΡ ΠΎΠΏΠ΅ΡΠ°ΡΠΈΡ Ρ ΠΎΠΏΠ΅ΡΠ°ΡΠΈΠ΅ΠΉ ΠΎΡΡΠ°Π½ΠΎΠ²ΠΊΠΈ |
|
298 | 299 | current.On( |
|
299 | 300 | stop.Resolve, // Π΅ΡΠ»ΠΈ ΡΠ΅ΠΊΡΡΠ°Ρ ΠΎΠΏΠ΅ΡΠ°ΡΠΈΡ Π·Π°Π²Π΅ΡΡΠΈΠ»Π°ΡΡ, ΡΠΎ ΠΌΠΎΠΆΠ½ΠΎ Π½Π°ΡΠΈΠ½Π°ΡΡ ΠΎΡΡΠ°Π½ΠΎΠ²ΠΊΡ |
|
300 | 301 | stop.Reject, // Π΅ΡΠ»ΠΈ ΡΠ΅ΠΊΡΡΠ°Ρ ΠΎΠΏΠ΅ΡΠ°ΡΠΈΡ Π΄Π°Π»Π° ΠΎΡΠΈΠ±ΠΊΡ - ΡΠΎ Π²ΡΠ΅ ΠΏΠ»ΠΎΡ ΠΎ, Π½Π΅Π»ΡΠ·Ρ ΠΏΡΠΎΠ΄ΠΎΠ»ΠΆΠ°ΡΡ |
|
301 | 302 | e => stop.Resolve() // Π΅ΡΠ»ΠΈ ΡΠ΅ΠΊΡΡΠ°Ρ ΠΎΡΠΌΠ΅Π½ΠΈΠ»Π°ΡΡ, ΡΠΎ ΠΌΠΎΠΆΠ½ΠΎ Π½Π°ΡΠΈΠ½Π°ΡΡ ΠΎΡΡΠ°Π½ΠΎΠ²ΠΊΡ |
|
302 | 303 | ); |
|
303 | 304 | // ΠΏΠΎΡΡΠ»Π°Π΅ΠΌ ΡΠ΅ΠΊΡΡΠ΅ΠΉ ΠΎΠΏΠ΅ΡΠ°ΡΠΈΠΈ ΡΠΈΠ³Π½Π°Π» ΠΎΡΡΠ°Π½ΠΎΠ²ΠΊΠΈ |
|
304 | 305 | current.Cancel(); |
|
305 | 306 | } |
|
306 | 307 | } |
|
307 | 308 | |
|
308 | 309 | public ExecutionState State { |
|
309 | 310 | get { |
|
310 | 311 | return m_stateMachine.State; |
|
311 | 312 | } |
|
312 | 313 | } |
|
313 | 314 | |
|
314 | 315 | public Exception LastError { |
|
315 | 316 | get { |
|
316 | 317 | return m_lastError; |
|
317 | 318 | } |
|
318 | 319 | } |
|
319 | 320 | |
|
320 | 321 | #endregion |
|
321 | 322 | |
|
322 | 323 | #region IDisposable implementation |
|
323 | 324 | |
|
324 | 325 | /// <summary> |
|
325 | 326 | /// Releases all resource used by the <see cref="Implab.Components.RunnableComponent"/> object. |
|
326 | 327 | /// </summary> |
|
327 | 328 | /// <remarks> |
|
328 | 329 | /// <para>Will not try to stop the component, it will just release all resources. |
|
329 | 330 | /// To cleanup the component gracefully use <see cref="Stop()"/> method.</para> |
|
330 | 331 | /// <para> |
|
331 | 332 | /// In normal cases the <see cref="Dispose()"/> method shouldn't be called, the call to the <see cref="Stop()"/> |
|
332 | 333 | /// method is sufficient to cleanup the component. Call <see cref="Dispose()"/> only to cleanup after errors, |
|
333 | 334 | /// especially if <see cref="Stop"/> method is failed. Using this method insted of <see cref="Stop()"/> may |
|
334 | 335 | /// lead to the data loss by the component. |
|
335 | 336 | /// </para></remarks> |
|
337 | [SuppressMessage("Microsoft.Design", "CA1063:ImplementIDisposableCorrectly", Justification = "Dipose(bool) and GC.SuppessFinalize are called")] | |
|
336 | 338 | public void Dispose() { |
|
337 | 339 | IPromise pending; |
|
338 | 340 | |
|
339 | 341 | lock (m_stateMachine) { |
|
340 | 342 | if (m_stateMachine.State == ExecutionState.Disposed) |
|
341 | 343 | return; |
|
342 |
|
|
|
344 | Move(Commands.Dispose, null, null); | |
|
343 | 345 | } |
|
344 | 346 | |
|
345 | 347 | GC.SuppressFinalize(this); |
|
346 | if (pending != null) { | |
|
347 | pending.Cancel(); | |
|
348 | pending.Timeout(DisposeTimeout).On( | |
|
349 | () => Dispose(true, null), | |
|
350 | err => Dispose(true, err), | |
|
351 | reason => Dispose(true, new OperationCanceledException("The operation is cancelled", reason)) | |
|
352 | ); | |
|
353 | } else { | |
|
354 | Dispose(true, null); | |
|
355 | } | |
|
348 | Dispose(true); | |
|
356 | 349 | } |
|
357 | 350 | |
|
358 | 351 | ~RunnableComponent() { |
|
359 |
Dispose(false |
|
|
352 | Dispose(false); | |
|
360 | 353 | } |
|
361 | 354 | |
|
362 | 355 | #endregion |
|
363 | 356 | |
|
364 | 357 | /// <summary> |
|
365 | 358 | /// Releases all resources used by the component, called automatically, override this method to implement your cleanup. |
|
366 | 359 | /// </summary> |
|
367 | 360 | /// <param name="disposing">true if this method is called during normal dispose process.</param> |
|
368 |
/// <param name=" |
|
|
369 |
protected virtual void Dispose(bool disposing |
|
|
361 | /// <param name="pending">The operation which is currenty pending</param> | |
|
362 | protected virtual void Dispose(bool disposing) { | |
|
370 | 363 | |
|
371 | 364 | } |
|
372 | 365 | |
|
373 | 366 | } |
|
374 | 367 | } |
|
375 | 368 |
@@ -1,293 +1,293 | |||
|
1 | 1 | using System; |
|
2 | 2 | using System.Diagnostics; |
|
3 | 3 | using System.IO; |
|
4 | 4 | using Implab.Automaton; |
|
5 | 5 | using Implab.Automaton.RegularExpressions; |
|
6 | 6 | using System.Linq; |
|
7 | 7 | using Implab.Components; |
|
8 | 8 | using System.Collections.Generic; |
|
9 | 9 | |
|
10 | 10 | namespace Implab.Formats.JSON { |
|
11 | 11 | /// <summary> |
|
12 | 12 | /// Pull ΠΏΠ°ΡΡΠ΅Ρ JSON Π΄Π°Π½Π½ΡΡ . |
|
13 | 13 | /// </summary> |
|
14 | 14 | /// <remarks> |
|
15 | 15 | /// Π‘Π»Π΅Π΄ΡΠ΅Ρ ΠΎΡΠΌΠ΅ΡΠΈΡΡ ΠΎΡΠ΄Π΅Π»ΡΠ½ΡΡ ΠΈΠ½ΡΠ΅ΡΠΏΡΠ΅ΡΠ°ΡΠΈΡ ΡΠ²ΠΎΠΉΡΡΠ²Π° <see cref="Level"/>, |
|
16 | 16 | /// ΠΎΠ½ΠΎ ΠΎΠ·Π½Π°ΡΠ°Π΅Ρ ΡΠ΅ΠΊΡΡΠΈΠΉ ΡΡΠΎΠ²Π΅Π½Ρ Π²Π»ΠΎΠΆΠ΅Π½Π½ΠΎΡΡΠΈ ΠΎΠ±ΡΠ΅ΠΊΡΠΎΠ², ΠΎΠ΄Π½Π°ΠΊΠΎ Π·Π°ΠΊΡΡΠ²Π°ΡΡΠΈΠΉ |
|
17 | 17 | /// ΡΠ»Π΅ΠΌΠ΅Π½Ρ ΠΎΠ±ΡΠ΅ΠΊΡΠ° ΠΈ ΠΌΠ°ΡΡΠΈΠ²Π° ΠΈΠΌΠ΅Π΅Ρ ΡΡΠΎΠ²Π΅Π½Ρ ΠΌΠ΅Π½ΡΡΠ΅, ΡΠ΅ΠΌ ΡΠ°ΠΌ ΠΎΠ±ΡΠ΅ΠΊΡ. |
|
18 | 18 | /// <code> |
|
19 | 19 | /// { // Level = 1 |
|
20 | 20 | /// "name" : "Peter", // Level = 1 |
|
21 | 21 | /// "address" : { // Level = 2 |
|
22 | 22 | /// city : "Stern" // Level = 2 |
|
23 | 23 | /// } // Level = 1 |
|
24 | 24 | /// } // Level = 0 |
|
25 | 25 | /// </code> |
|
26 | 26 | /// </remarks> |
|
27 | 27 | public class JSONParser : Disposable { |
|
28 | 28 | |
|
29 | 29 | enum MemberContext { |
|
30 | 30 | MemberName, |
|
31 | 31 | MemberValue |
|
32 | 32 | } |
|
33 | 33 | |
|
34 | 34 | #region Parser rules |
|
35 | 35 | struct ParserContext { |
|
36 | 36 | readonly int[,] m_dfa; |
|
37 | 37 | int m_state; |
|
38 | 38 | |
|
39 | 39 | readonly JSONElementContext m_elementContext; |
|
40 | 40 | |
|
41 | 41 | public ParserContext(int[,] dfa, int state, JSONElementContext context) { |
|
42 | 42 | m_dfa = dfa; |
|
43 | 43 | m_state = state; |
|
44 | 44 | m_elementContext = context; |
|
45 | 45 | } |
|
46 | 46 | |
|
47 | 47 | public bool Move(JsonTokenType token) { |
|
48 | 48 | var next = m_dfa[m_state, (int)token]; |
|
49 | 49 | if (next == AutomatonConst.UNREACHABLE_STATE) |
|
50 | 50 | return false; |
|
51 | 51 | m_state = next; |
|
52 | 52 | return true; |
|
53 | 53 | } |
|
54 | 54 | |
|
55 | 55 | public JSONElementContext ElementContext { |
|
56 | 56 | get { return m_elementContext; } |
|
57 | 57 | } |
|
58 | 58 | } |
|
59 | 59 | |
|
60 | 60 | static readonly ParserContext _jsonContext; |
|
61 | 61 | static readonly ParserContext _objectContext; |
|
62 | 62 | static readonly ParserContext _arrayContext; |
|
63 | 63 | |
|
64 | 64 | static JSONParser() { |
|
65 | 65 | |
|
66 | 66 | var valueExpression = MakeToken(JsonTokenType.BeginArray, JsonTokenType.BeginObject, JsonTokenType.Literal, JsonTokenType.Number, JsonTokenType.String); |
|
67 | 67 | var memberExpression = MakeToken(JsonTokenType.String).Cat(MakeToken(JsonTokenType.NameSeparator)).Cat(valueExpression); |
|
68 | 68 | |
|
69 | 69 | var objectExpression = memberExpression |
|
70 | 70 | .Cat( |
|
71 | 71 | MakeToken(JsonTokenType.ValueSeparator) |
|
72 | 72 | .Cat(memberExpression) |
|
73 | 73 | .EClosure() |
|
74 | 74 | ) |
|
75 | 75 | .Optional() |
|
76 | 76 | .Cat(MakeToken(JsonTokenType.EndObject)) |
|
77 | 77 | .End(); |
|
78 | 78 | |
|
79 | 79 | var arrayExpression = valueExpression |
|
80 | 80 | .Cat( |
|
81 | 81 | MakeToken(JsonTokenType.ValueSeparator) |
|
82 | 82 | .Cat(valueExpression) |
|
83 | 83 | .EClosure() |
|
84 | 84 | ) |
|
85 | 85 | .Optional() |
|
86 | 86 | .Cat(MakeToken(JsonTokenType.EndArray)) |
|
87 | 87 | .End(); |
|
88 | 88 | |
|
89 | 89 | var jsonExpression = valueExpression.End(); |
|
90 | 90 | |
|
91 | 91 | _jsonContext = CreateParserContext(jsonExpression, JSONElementContext.None); |
|
92 | 92 | _objectContext = CreateParserContext(objectExpression, JSONElementContext.Object); |
|
93 | 93 | _arrayContext = CreateParserContext(arrayExpression, JSONElementContext.Array); |
|
94 | 94 | } |
|
95 | 95 | |
|
96 | 96 | static Token MakeToken(params JsonTokenType[] input) { |
|
97 | 97 | return Token.New( input.Select(t => (int)t).ToArray() ); |
|
98 | 98 | } |
|
99 | 99 | |
|
100 | 100 | static ParserContext CreateParserContext(Token expr, JSONElementContext context) { |
|
101 | 101 | |
|
102 | 102 | var dfa = new DFATable(); |
|
103 | 103 | var builder = new RegularExpressionVisitor(dfa); |
|
104 | 104 | expr.Accept(builder); |
|
105 | 105 | builder.BuildDFA(); |
|
106 | 106 | |
|
107 | 107 | return new ParserContext(dfa.CreateTransitionTable(), dfa.InitialState, context); |
|
108 | 108 | } |
|
109 | 109 | |
|
110 | 110 | #endregion |
|
111 | 111 | |
|
112 | 112 | readonly JSONScanner m_scanner; |
|
113 | 113 | MemberContext m_memberContext; |
|
114 | 114 | |
|
115 | 115 | JSONElementType m_elementType; |
|
116 | 116 | object m_elementValue; |
|
117 | 117 | string m_memberName = String.Empty; |
|
118 | 118 | |
|
119 | 119 | Stack<ParserContext> m_stack = new Stack<ParserContext>(); |
|
120 | 120 | ParserContext m_context = _jsonContext; |
|
121 | 121 | |
|
122 | 122 | /// <summary> |
|
123 | 123 | /// Π‘ΠΎΠ·Π΄Π°Π΅Ρ Π½ΠΎΠ²ΡΠΉ ΠΏΠ°ΡΡΠ΅Ρ Π½Π° ΠΎΡΠ½ΠΎΠ²Π΅ ΡΡΡΠΎΠΊΠΈ, ΡΠΎΠ΄Π΅ΡΠΆΠ°ΡΠ΅ΠΉ JSON |
|
124 | 124 | /// </summary> |
|
125 | 125 | /// <param name="text"></param> |
|
126 | 126 | public JSONParser(string text) { |
|
127 | 127 | Safe.ArgumentNotEmpty(text, "text"); |
|
128 | 128 | m_scanner = new JSONScanner(text); |
|
129 | 129 | } |
|
130 | 130 | |
|
131 | 131 | /// <summary> |
|
132 | 132 | /// Π‘ΠΎΠ·Π΄Π°Π΅Ρ Π½ΠΎΠ²ΡΠΉ ΡΠΊΠ·Π΅ΠΌΠΏΠ»ΡΡ ΠΏΠ°ΡΡΠ΅ΡΠ°, Π½Π° ΠΎΡΠ½ΠΎΠ²Π΅ ΡΠ΅ΠΊΡΡΠΎΠ²ΠΎΠ³ΠΎ ΠΏΠΎΡΠΎΠΊΠ°. |
|
133 | 133 | /// </summary> |
|
134 | 134 | /// <param name="reader">Π’Π΅ΠΊΡΡΠΎΠ²ΡΠΉ ΠΏΠΎΡΠΎΠΊ.</param> |
|
135 | 135 | public JSONParser(TextReader reader) { |
|
136 | 136 | Safe.ArgumentNotNull(reader, "reader"); |
|
137 | 137 | m_scanner = new JSONScanner(reader); |
|
138 | 138 | } |
|
139 | 139 | |
|
140 | 140 | public int Level { |
|
141 | 141 | get { return m_stack.Count; } |
|
142 | 142 | } |
|
143 | 143 | |
|
144 | 144 | /// <summary> |
|
145 | 145 | /// Π’ΠΈΠΏ ΡΠ΅ΠΊΡΡΠ΅Π³ΠΎ ΡΠ»Π΅ΠΌΠ΅Π½ΡΠ° Π½Π° ΠΊΠΎΡΠΎΡΠΎΠΌ ΡΡΠΎΠΈΡ ΠΏΠ°ΡΡΠ΅Ρ. |
|
146 | 146 | /// </summary> |
|
147 | 147 | public JSONElementType ElementType { |
|
148 | 148 | get { return m_elementType; } |
|
149 | 149 | } |
|
150 | 150 | |
|
151 | 151 | /// <summary> |
|
152 | 152 | /// ΠΠΌΡ ΡΠ»Π΅ΠΌΠ΅Π½ΡΠ° - ΠΈΠΌΡ ΡΠ²ΠΎΠΉΡΡΠ²Π° ΡΠΎΠ΄ΠΈΡΠ΅Π»ΡΡΠΊΠΎΠ³ΠΎ ΠΊΠΎΠ½ΡΠ΅ΠΉΠ½Π΅ΡΠ°. ΠΠ»Ρ ΡΠ»Π΅ΠΌΠ΅Π½ΡΠΎΠ² ΠΌΠ°ΡΡΠΈΠ²ΠΎΠ² ΠΈ ΠΊΠΎΡΠ½Π΅Π²ΠΎΠ³ΠΎ Π²ΡΠ΅Π³Π΄Π° |
|
153 | 153 | /// ΠΏΡΡΡΠ°Ρ ΡΡΡΠΎΠΊΠ°. |
|
154 | 154 | /// </summary> |
|
155 | 155 | public string ElementName { |
|
156 | 156 | get { return m_memberName; } |
|
157 | 157 | } |
|
158 | 158 | |
|
159 | 159 | /// <summary> |
|
160 | 160 | /// ΠΠ½Π°ΡΠ΅Π½ΠΈΠ΅ ΡΠ»Π΅ΠΌΠ΅Π½ΡΠ°. Π’ΠΎΠ»ΡΠΊΠΎ Π΄Π»Ρ ΡΠ»Π΅ΠΌΠ΅Π½ΡΠΎΠ² ΡΠΈΠΏΠ° <see cref="JSONElementType.Value"/>, Π΄Π»Ρ ΠΎΡΡΠ°Π»ΡΠ½ΡΡ <c>null</c> |
|
161 | 161 | /// </summary> |
|
162 | 162 | public object ElementValue { |
|
163 | 163 | get { return m_elementValue; } |
|
164 | 164 | } |
|
165 | 165 | |
|
166 | 166 | /// <summary> |
|
167 | 167 | /// Π§ΠΈΡΠ°Π΅Ρ ΡΠ»Π΅ΡΡΠ΄ΡΡΠΈΠΉ ΠΎΠ±ΡΠ΅ΠΊΡ ΠΈΠ· ΠΏΠΎΡΠΎΠΊΠ° |
|
168 | 168 | /// </summary> |
|
169 | 169 | /// <returns><c>true</c> - ΠΎΠΏΠ΅ΡΠ°ΡΠΈΡ ΡΡΠ΅Π½ΠΈΡ ΠΏΡΠΎΡΠ»Π° ΡΡΠΏΠ΅ΡΠ½ΠΎ, <c>false</c> - ΠΊΠΎΠ½Π΅Ρ Π΄Π°Π½Π½ΡΡ </returns> |
|
170 | 170 | public bool Read() { |
|
171 | 171 | object tokenValue; |
|
172 | 172 | JsonTokenType tokenType; |
|
173 | 173 | |
|
174 | 174 | m_memberName = String.Empty; |
|
175 | 175 | |
|
176 | 176 | while (m_scanner.ReadToken(out tokenValue, out tokenType)) { |
|
177 | 177 | if(!m_context.Move(tokenType)) |
|
178 | 178 | UnexpectedToken(tokenValue, tokenType); |
|
179 | 179 | |
|
180 | 180 | switch (tokenType) { |
|
181 | 181 | case JsonTokenType.BeginObject: |
|
182 | 182 | m_stack.Push(m_context); |
|
183 | 183 | m_context = _objectContext; |
|
184 | 184 | |
|
185 | 185 | m_elementValue = null; |
|
186 | 186 | m_memberContext = MemberContext.MemberName; |
|
187 | 187 | m_elementType = JSONElementType.BeginObject; |
|
188 | 188 | return true; |
|
189 | 189 | case JsonTokenType.EndObject: |
|
190 | 190 | if (m_stack.Count == 0) |
|
191 | 191 | UnexpectedToken(tokenValue, tokenType); |
|
192 | 192 | m_context = m_stack.Pop(); |
|
193 | 193 | |
|
194 | 194 | m_elementValue = null; |
|
195 | 195 | m_elementType = JSONElementType.EndObject; |
|
196 | 196 | return true; |
|
197 | 197 | case JsonTokenType.BeginArray: |
|
198 | 198 | m_stack.Push(m_context); |
|
199 | 199 | m_context = _arrayContext; |
|
200 | 200 | |
|
201 | 201 | m_elementValue = null; |
|
202 | 202 | m_memberContext = MemberContext.MemberValue; |
|
203 | 203 | m_elementType = JSONElementType.BeginArray; |
|
204 | 204 | return true; |
|
205 | 205 | case JsonTokenType.EndArray: |
|
206 | 206 | if (m_stack.Count == 0) |
|
207 | 207 | UnexpectedToken(tokenValue, tokenType); |
|
208 | 208 | m_context = m_stack.Pop(); |
|
209 | 209 | |
|
210 | 210 | m_elementValue = null; |
|
211 | 211 | m_elementType = JSONElementType.EndArray; |
|
212 | 212 | return true; |
|
213 | 213 | case JsonTokenType.String: |
|
214 | 214 | if (m_memberContext == MemberContext.MemberName) { |
|
215 | 215 | m_memberName = (string)tokenValue; |
|
216 | 216 | break; |
|
217 | 217 | } |
|
218 | 218 | m_elementType = JSONElementType.Value; |
|
219 | 219 | m_elementValue = tokenValue; |
|
220 | 220 | return true; |
|
221 | 221 | case JsonTokenType.Number: |
|
222 | 222 | m_elementType = JSONElementType.Value; |
|
223 | 223 | m_elementValue = tokenValue; |
|
224 | 224 | return true; |
|
225 | 225 | case JsonTokenType.Literal: |
|
226 | 226 | m_elementType = JSONElementType.Value; |
|
227 | 227 | m_elementValue = ParseLiteral((string)tokenValue); |
|
228 | 228 | return true; |
|
229 | 229 | case JsonTokenType.NameSeparator: |
|
230 | 230 | m_memberContext = MemberContext.MemberValue; |
|
231 | 231 | break; |
|
232 | 232 | case JsonTokenType.ValueSeparator: |
|
233 | 233 | m_memberContext = m_context.ElementContext == JSONElementContext.Object ? MemberContext.MemberName : MemberContext.MemberValue; |
|
234 | 234 | break; |
|
235 | 235 | default: |
|
236 | 236 | UnexpectedToken(tokenValue, tokenType); |
|
237 | 237 | break; |
|
238 | 238 | } |
|
239 | 239 | } |
|
240 | 240 | if (m_context.ElementContext != JSONElementContext.None) |
|
241 | 241 | throw new ParserException("Unexpedted end of data"); |
|
242 | 242 | |
|
243 | 243 | EOF = true; |
|
244 | 244 | |
|
245 | 245 | return false; |
|
246 | 246 | } |
|
247 | 247 | |
|
248 | 248 | object ParseLiteral(string literal) { |
|
249 | 249 | switch (literal) { |
|
250 | 250 | case "null": |
|
251 | 251 | return null; |
|
252 | 252 | case "false": |
|
253 | 253 | return false; |
|
254 | 254 | case "true": |
|
255 | 255 | return true; |
|
256 | 256 | default: |
|
257 | 257 | UnexpectedToken(literal, JsonTokenType.Literal); |
|
258 | 258 | return null; // avoid compliler error |
|
259 | 259 | } |
|
260 | 260 | } |
|
261 | 261 | |
|
262 | 262 | void UnexpectedToken(object value, JsonTokenType tokenType) { |
|
263 | 263 | throw new ParserException(String.Format("Unexpected token {0}: '{1}'", tokenType, value)); |
|
264 | 264 | } |
|
265 | 265 | |
|
266 | 266 | |
|
267 | 267 | /// <summary> |
|
268 | 268 | /// ΠΡΠΈΠ·Π½Π°ΠΊ ΠΊΠΎΠ½ΡΠ° ΠΏΠΎΡΠΎΠΊΠ° |
|
269 | 269 | /// </summary> |
|
270 | 270 | public bool EOF { |
|
271 | 271 | get; |
|
272 | 272 | private set; |
|
273 | 273 | } |
|
274 | 274 | |
|
275 | 275 | protected override void Dispose(bool disposing) { |
|
276 | 276 | if (disposing) |
|
277 |
|
|
|
277 | m_scanner.Dispose(); | |
|
278 | 278 | } |
|
279 | 279 | |
|
280 | 280 | /// <summary> |
|
281 | 281 | /// ΠΠ΅ΡΠ΅Ρ ΠΎΠ΄ΠΈΡ Π² ΠΊΠΎΠ½Π΅Ρ ΡΠ΅ΠΊΡΡΠ΅Π³ΠΎ ΠΎΠ±ΡΠ΅ΠΊΡΠ°. |
|
282 | 282 | /// </summary> |
|
283 | 283 | public void SeekElementEnd() { |
|
284 | 284 | var level = Level - 1; |
|
285 | 285 | |
|
286 | 286 | Debug.Assert(level >= 0); |
|
287 | 287 | |
|
288 | 288 | while (Level != level) |
|
289 | 289 | Read(); |
|
290 | 290 | } |
|
291 | 291 | } |
|
292 | 292 | |
|
293 | 293 | } |
@@ -1,109 +1,109 | |||
|
1 | 1 | using System; |
|
2 | 2 | using System.Globalization; |
|
3 | 3 | using Implab.Automaton; |
|
4 | 4 | using System.Text; |
|
5 | 5 | using Implab.Components; |
|
6 | 6 | using System.IO; |
|
7 | 7 | |
|
8 | 8 | namespace Implab.Formats.JSON { |
|
9 | 9 | /// <summary> |
|
10 | 10 | /// Π‘ΠΊΠ°Π½Π½Π΅Ρ (Π»Π΅ΠΊΡΠ΅Ρ), ΡΠ°Π·Π±ΠΈΠ²Π°ΡΡΠΈΠΉ ΠΏΠΎΡΠΎΠΊ ΡΠΈΠΌΠ²ΠΎΠ»ΠΎΠ² Π½Π° ΡΠΎΠΊΠ΅Π½Ρ JSON. |
|
11 | 11 | /// </summary> |
|
12 | 12 | public class JSONScanner : Disposable { |
|
13 | 13 | readonly StringBuilder m_builder = new StringBuilder(); |
|
14 | 14 | |
|
15 | 15 | readonly ScannerContext<JSONGrammar.TokenType> m_jsonContext = JSONGrammar.Instance.JsonExpression; |
|
16 | 16 | readonly ScannerContext<JSONGrammar.TokenType> m_stringContext = JSONGrammar.Instance.JsonStringExpression; |
|
17 | 17 | |
|
18 | 18 | |
|
19 | 19 | readonly TextScanner m_scanner; |
|
20 | 20 | |
|
21 | 21 | /// <summary> |
|
22 | 22 | /// Π‘ΠΎΠ·Π΄Π°Π΅Ρ Π½ΠΎΠ²ΡΠΉ ΡΠΊΠ·Π΅ΠΌΠΏΠ»ΡΡ ΡΠΊΠ°Π½Π½Π΅ΡΠ° |
|
23 | 23 | /// </summary> |
|
24 | 24 | public JSONScanner(string text) { |
|
25 | 25 | Safe.ArgumentNotEmpty(text, "text"); |
|
26 | 26 | |
|
27 | 27 | m_scanner = new StringScanner(text); |
|
28 | 28 | } |
|
29 | 29 | |
|
30 | 30 | public JSONScanner(TextReader reader, int bufferMax, int chunkSize) { |
|
31 | 31 | Safe.ArgumentNotNull(reader, "reader"); |
|
32 | 32 | |
|
33 | 33 | m_scanner = new ReaderScanner(reader, bufferMax, chunkSize); |
|
34 | 34 | } |
|
35 | 35 | |
|
36 | 36 | public JSONScanner(TextReader reader) : this(reader, 1024*1024, 1024){ |
|
37 | 37 | } |
|
38 | 38 | |
|
39 | 39 | /// <summary> |
|
40 | 40 | /// Π§ΠΈΡΠ°Π΅Ρ ΡΠ»Π΅Π΄ΡΡΡΠΈΠΉ Π»Π΅ΠΊΡΠΈΡΠ΅ΡΠΊΠΈΠΉ ΡΠ»Π΅ΠΌΠ΅Π½Ρ ΠΈΠ· Π²Ρ ΠΎΠ΄Π½ΡΡ Π΄Π°Π½Π½ΡΡ . |
|
41 | 41 | /// </summary> |
|
42 | 42 | /// <param name="tokenValue">ΠΠΎΠ·Π²ΡΠ°ΡΠ°Π΅Ρ Π·Π½Π°ΡΠ΅Π½ΠΈΠ΅ ΠΏΡΠΎΡΠΈΡΠ°Π½Π½ΠΎΠ³ΠΎ ΡΠΎΠΊΠ΅Π½Π°.</param> |
|
43 | 43 | /// <param name="tokenType">ΠΠΎΠ·Π²ΡΠ°ΡΠ°Π΅Ρ ΡΠΈΠΏ ΠΏΡΠΎΡΠΈΡΠ°Π½Π½ΠΎΠ³ΠΎ ΡΠΎΠΊΠ΅Π½Π°.</param> |
|
44 | 44 | /// <returns><c>true</c> - ΡΡΠ΅Π½ΠΈΠ΅ ΠΏΡΠΎΠΈΠ·Π²Π΅Π΄Π΅Π½ΠΎ ΡΡΠΏΠ΅ΡΠ½ΠΎ. <c>false</c> - Π΄ΠΎΡΡΠΈΠ³Π½ΡΡ ΠΊΠΎΠ½Π΅Ρ Π²Ρ ΠΎΠ΄Π½ΡΡ Π΄Π°Π½Π½ΡΡ </returns> |
|
45 | 45 | /// <remarks>Π ΡΠ»ΡΡΠ΅ Π΅ΡΠ»ΠΈ ΡΠΎΠΊΠ΅Π½ Π½Π΅ ΡΠ°ΡΠΏΠΎΠ·Π½Π°Π΅ΡΡΡ, Π²ΠΎΠ·Π½ΠΈΠΊΠ°Π΅Ρ ΠΈΡΠΊΠ»ΡΡΠ΅Π½ΠΈΠ΅. ΠΠ½Π°ΡΠ΅Π½ΠΈΡ ΡΠΎΠΊΠ΅Π½ΠΎΠ² ΠΎΠ±ΡΠ°Π±Π°ΡΡΠ²Π°ΡΡΡΡ, Ρ.Π΅. |
|
46 | 46 | /// Π² ΡΡΡΠΎΠΊΠ°Ρ ΠΎΠ±ΡΠ°Π±Π°ΡΡΠ²Π°ΡΡΡΡ ΡΠΊΡΠ°Π½ΠΈΡΠΎΠ²Π°Π½Π½ΡΠ΅ ΡΠΈΠΌΠ²ΠΎΠ»Ρ, ΡΠΈΡΠ»Π° ΡΡΠ°Π½ΠΎΠ²ΡΡΡ ΡΠΈΠΏΠ° double.</remarks> |
|
47 | 47 | public bool ReadToken(out object tokenValue, out JsonTokenType tokenType) { |
|
48 | 48 | JSONGrammar.TokenType[] tag; |
|
49 | 49 | while (m_jsonContext.Execute(m_scanner, out tag)) { |
|
50 | 50 | switch (tag[0]) { |
|
51 | 51 | case JSONGrammar.TokenType.StringBound: |
|
52 | 52 | tokenValue = ReadString(); |
|
53 | 53 | tokenType = JsonTokenType.String; |
|
54 | 54 | break; |
|
55 | 55 | case JSONGrammar.TokenType.Number: |
|
56 | 56 | tokenValue = Double.Parse(m_scanner.GetTokenValue(), CultureInfo.InvariantCulture); |
|
57 | 57 | tokenType = JsonTokenType.Number; |
|
58 | 58 | break; |
|
59 | 59 | case JSONGrammar.TokenType.Whitespace: |
|
60 | 60 | continue; |
|
61 | 61 | default: |
|
62 | 62 | tokenType = (JsonTokenType)tag[0]; |
|
63 | 63 | tokenValue = m_scanner.GetTokenValue(); |
|
64 | 64 | break; |
|
65 | 65 | } |
|
66 | 66 | return true; |
|
67 | 67 | } |
|
68 | 68 | tokenValue = null; |
|
69 | 69 | tokenType = JsonTokenType.None; |
|
70 | 70 | return false; |
|
71 | 71 | } |
|
72 | 72 | |
|
73 | 73 | string ReadString() { |
|
74 | 74 | int pos = 0; |
|
75 | 75 | var buf = new char[6]; // the buffer for unescaping chars |
|
76 | 76 | |
|
77 | 77 | JSONGrammar.TokenType[] tag; |
|
78 | 78 | m_builder.Clear(); |
|
79 | 79 | |
|
80 | 80 | while (m_stringContext.Execute(m_scanner, out tag)) { |
|
81 | 81 | switch (tag[0]) { |
|
82 | 82 | case JSONGrammar.TokenType.StringBound: |
|
83 | 83 | return m_builder.ToString(); |
|
84 | 84 | case JSONGrammar.TokenType.UnescapedChar: |
|
85 | 85 | m_scanner.CopyTokenTo(m_builder); |
|
86 | 86 | break; |
|
87 | 87 | case JSONGrammar.TokenType.EscapedUnicode: // \xXXXX - unicode escape sequence |
|
88 | 88 | m_scanner.CopyTokenTo(buf, 0); |
|
89 | 89 | m_builder.Append(StringTranslator.TranslateHexUnicode(buf, 2)); |
|
90 | 90 | pos++; |
|
91 | 91 | break; |
|
92 | 92 | case JSONGrammar.TokenType.EscapedChar: // \t - escape sequence |
|
93 | 93 | m_scanner.CopyTokenTo(buf, 0); |
|
94 | 94 | m_builder.Append(StringTranslator.TranslateEscapedChar(buf[1])); |
|
95 | 95 | break; |
|
96 | 96 | } |
|
97 | 97 | |
|
98 | 98 | } |
|
99 | 99 | |
|
100 | 100 | throw new ParserException("Unexpected end of data"); |
|
101 | 101 | } |
|
102 | 102 | |
|
103 | 103 | protected override void Dispose(bool disposing) { |
|
104 | 104 | if (disposing) |
|
105 |
|
|
|
105 | m_scanner.Dispose(); | |
|
106 | 106 | base.Dispose(disposing); |
|
107 | 107 | } |
|
108 | 108 | } |
|
109 | 109 | } |
@@ -1,420 +1,448 | |||
|
1 | 1 | using System.Threading; |
|
2 | 2 | using System; |
|
3 | 3 | using Implab.Diagnostics; |
|
4 | 4 | using System.Collections.Generic; |
|
5 | 5 | using System.Linq; |
|
6 | 6 | |
|
7 | 7 | namespace Implab { |
|
8 | 8 | public static class PromiseExtensions { |
|
9 | 9 | public static IPromise<T> DispatchToCurrentContext<T>(this IPromise<T> that) { |
|
10 | 10 | Safe.ArgumentNotNull(that, "that"); |
|
11 | 11 | var context = SynchronizationContext.Current; |
|
12 | 12 | if (context == null) |
|
13 | 13 | return that; |
|
14 | 14 | |
|
15 | 15 | var p = new SyncContextPromise<T>(context); |
|
16 | 16 | p.CancellationRequested(that.Cancel); |
|
17 | 17 | |
|
18 | 18 | that.On( |
|
19 | 19 | p.Resolve, |
|
20 | 20 | p.Reject, |
|
21 | 21 | p.CancelOperation |
|
22 | 22 | ); |
|
23 | 23 | return p; |
|
24 | 24 | } |
|
25 | 25 | |
|
26 | 26 | public static IPromise<T> DispatchToContext<T>(this IPromise<T> that, SynchronizationContext context) { |
|
27 | 27 | Safe.ArgumentNotNull(that, "that"); |
|
28 | 28 | Safe.ArgumentNotNull(context, "context"); |
|
29 | 29 | |
|
30 | 30 | var p = new SyncContextPromise<T>(context); |
|
31 | 31 | p.CancellationRequested(that.Cancel); |
|
32 | 32 | |
|
33 | 33 | that.On( |
|
34 | 34 | p.Resolve, |
|
35 | 35 | p.Reject, |
|
36 | 36 | p.CancelOperation |
|
37 | 37 | ); |
|
38 | 38 | return p; |
|
39 | 39 | } |
|
40 | 40 | |
|
41 | 41 | /// <summary> |
|
42 | 42 | /// Ensures the dispatched. |
|
43 | 43 | /// </summary> |
|
44 | 44 | /// <returns>The dispatched.</returns> |
|
45 | 45 | /// <param name="that">That.</param> |
|
46 | 46 | /// <param name="head">Head.</param> |
|
47 | 47 | /// <param name="cleanup">Cleanup.</param> |
|
48 | 48 | /// <typeparam name="TPromise">The 1st type parameter.</typeparam> |
|
49 | 49 | /// <typeparam name="T">The 2nd type parameter.</typeparam> |
|
50 | 50 | public static TPromise EnsureDispatched<TPromise, T>(this TPromise that, IPromise<T> head, Action<T> cleanup) where TPromise : IPromise { |
|
51 | 51 | Safe.ArgumentNotNull(that, "that"); |
|
52 | 52 | Safe.ArgumentNotNull(head, "head"); |
|
53 | 53 | |
|
54 | 54 | that.On(() => head.On(cleanup), PromiseEventType.Cancelled); |
|
55 | 55 | |
|
56 | 56 | return that; |
|
57 | 57 | } |
|
58 | 58 | |
|
59 | 59 | /// <summary> |
|
60 | 60 | /// Adds a cancellation point to the chain of promises. When a cancellation request reaches the cancellation point the operation is |
|
61 | 61 | /// cancelled immediatelly, and the request is passed towards. If the operation at the higher level can not be cancelled is't result |
|
62 | 62 | /// will be collected with <paramref name="cleanup"/> callback. |
|
63 | 63 | /// </summary> |
|
64 | 64 | /// <typeparam name="T">The type of the promise result.</typeparam> |
|
65 | 65 | /// <param name="that">The promise to which the cancellation point should be attached.</param> |
|
66 | 66 | /// <param name="cleanup">The callback which is used to cleanup the result of the operation if the cancellation point is cancelled already.</param> |
|
67 | 67 | /// <returns>The promise</returns> |
|
68 | 68 | public static IPromise<T> CancellationPoint<T>(this IPromise<T> that, Action<T> cleanup) { |
|
69 | 69 | var meduim = new Promise<T>(); |
|
70 | 70 | |
|
71 | 71 | that.On(meduim.Resolve, meduim.Reject, meduim.CancelOperation); |
|
72 | 72 | |
|
73 | 73 | meduim.CancellationRequested(that.Cancel); |
|
74 | 74 | meduim.CancellationRequested(meduim.CancelOperation); |
|
75 | 75 | |
|
76 | 76 | if (cleanup != null) |
|
77 | 77 | meduim.On((Action<T>)null, null, (e) => { |
|
78 | 78 | that.On(cleanup); |
|
79 | 79 | }); |
|
80 | 80 | |
|
81 | 81 | return meduim; |
|
82 | 82 | } |
|
83 | 83 | |
|
84 | 84 | public static AsyncCallback AsyncCallback<T>(this Promise<T> that, Func<IAsyncResult, T> callback) { |
|
85 | 85 | Safe.ArgumentNotNull(that, "that"); |
|
86 | 86 | Safe.ArgumentNotNull(callback, "callback"); |
|
87 | 87 | var op = TraceContext.Instance.CurrentOperation; |
|
88 | 88 | return ar => { |
|
89 | 89 | TraceContext.Instance.EnterLogicalOperation(op, false); |
|
90 | 90 | try { |
|
91 | 91 | that.Resolve(callback(ar)); |
|
92 | 92 | } catch (Exception err) { |
|
93 | 93 | that.Reject(err); |
|
94 | 94 | } finally { |
|
95 | 95 | TraceContext.Instance.Leave(); |
|
96 | 96 | } |
|
97 | 97 | }; |
|
98 | 98 | } |
|
99 | 99 | |
|
100 | 100 | static void CancelByTimeoutCallback(object cookie) { |
|
101 | 101 | ((ICancellable)cookie).Cancel(new TimeoutException()); |
|
102 | 102 | } |
|
103 | 103 | |
|
104 | 104 | /// <summary> |
|
105 | 105 | /// Cancells promise after the specified timeout is elapsed. |
|
106 | 106 | /// </summary> |
|
107 | 107 | /// <param name="that">The promise to cancel on timeout.</param> |
|
108 | 108 | /// <param name="milliseconds">The timeout in milliseconds.</param> |
|
109 | 109 | /// <typeparam name="TPromise">The 1st type parameter.</typeparam> |
|
110 | 110 | public static TPromise Timeout<TPromise>(this TPromise that, int milliseconds) where TPromise : IPromise { |
|
111 | 111 | Safe.ArgumentNotNull(that, "that"); |
|
112 | 112 | var timer = new Timer(CancelByTimeoutCallback, that, milliseconds, -1); |
|
113 | 113 | that.On(timer.Dispose, PromiseEventType.All); |
|
114 | 114 | return that; |
|
115 | 115 | } |
|
116 | 116 | |
|
117 | 117 | public static IPromise PromiseAll(this IEnumerable<IPromise> that) { |
|
118 | 118 | Safe.ArgumentNotNull(that, "that"); |
|
119 | 119 | return PromiseAll(that.ToList()); |
|
120 | 120 | } |
|
121 | 121 | |
|
122 | 122 | public static IPromise<T[]> PromiseAll<T>(this IEnumerable<IPromise<T>> that) { |
|
123 | return PromiseAll(that, null); | |
|
124 | } | |
|
125 | ||
|
126 | public static IPromise<T[]> PromiseAll<T>(this IEnumerable<IPromise<T>> that, Action<T> cleanup) { | |
|
123 | 127 | Safe.ArgumentNotNull(that, "that"); |
|
124 | return PromiseAll(that.ToList()); | |
|
128 | return PromiseAll(that.ToList(), cleanup); | |
|
125 | 129 | } |
|
126 | 130 | |
|
127 | 131 | public static IPromise PromiseAll(this ICollection<IPromise> that) { |
|
128 | 132 | Safe.ArgumentNotNull(that, "that"); |
|
129 | 133 | |
|
130 | 134 | int count = that.Count; |
|
131 | 135 | int errors = 0; |
|
132 | 136 | var medium = new Promise(); |
|
133 | 137 | |
|
134 | 138 | if (count == 0) { |
|
135 | 139 | medium.Resolve(); |
|
136 | 140 | return medium; |
|
137 | 141 | } |
|
138 | 142 | |
|
139 | 143 | medium.On(() => { |
|
140 | 144 | foreach (var p2 in that) |
|
141 | 145 | p2.Cancel(); |
|
142 | 146 | }, PromiseEventType.ErrorOrCancel); |
|
143 | 147 | |
|
144 | 148 | foreach (var p in that) |
|
145 | 149 | p.On( |
|
146 | 150 | () => { |
|
147 | 151 | if (Interlocked.Decrement(ref count) == 0) |
|
148 | 152 | medium.Resolve(); |
|
149 | 153 | }, |
|
150 | 154 | error => { |
|
151 | 155 | if (Interlocked.Increment(ref errors) == 1) |
|
152 | 156 | medium.Reject( |
|
153 | 157 | new Exception("The dependency promise is failed", error) |
|
154 | 158 | ); |
|
155 | 159 | }, |
|
156 | 160 | reason => { |
|
157 | 161 | if (Interlocked.Increment(ref errors) == 1) |
|
158 | 162 | medium.Cancel( |
|
159 | 163 | new Exception("The dependency promise is cancelled") |
|
160 | 164 | ); |
|
161 | 165 | } |
|
162 | 166 | ); |
|
163 | 167 | |
|
164 | 168 | return medium; |
|
165 | 169 | } |
|
166 | 170 | |
|
167 | public static IPromise<T[]> PromiseAll<T>(this ICollection<IPromise<T>> that) { | |
|
171 | public static IPromise<T[]> PromiseAll<T>(this ICollection<IPromise<T>> that) { | |
|
172 | return PromiseAll(that, null); | |
|
173 | } | |
|
174 | ||
|
175 | /// <summary> | |
|
176 | /// Creates a new promise which will be satisfied when all promises are satisfied. | |
|
177 | /// </summary> | |
|
178 | /// <typeparam name="T"></typeparam> | |
|
179 | /// <param name="that"></param> | |
|
180 | /// <param name="cleanup">A callback used to cleanup already resolved promises in case of an error</param> | |
|
181 | /// <returns></returns> | |
|
182 | public static IPromise<T[]> PromiseAll<T>(this ICollection<IPromise<T>> that, Action<T> cleanup) { | |
|
168 | 183 | Safe.ArgumentNotNull(that, "that"); |
|
184 | ||
|
185 | int count = that.Count; | |
|
169 | 186 | |
|
170 |
i |
|
|
187 | if (count == 0) | |
|
188 | return Promise<T[]>.FromResult(new T[0]); | |
|
189 | ||
|
171 | 190 | int errors = 0; |
|
172 | 191 | var medium = new Promise<T[]>(); |
|
173 | 192 | var results = new T[that.Count]; |
|
174 | 193 | |
|
175 | 194 | medium.On(() => { |
|
176 | foreach (var p2 in that) | |
|
177 | p2.Cancel(); | |
|
195 | foreach (var p2 in that) { | |
|
196 | p2.Cancel(); | |
|
197 | if (cleanup != null) | |
|
198 | p2.On(cleanup); | |
|
199 | } | |
|
178 | 200 | }, PromiseEventType.ErrorOrCancel); |
|
179 | 201 | |
|
180 | 202 | int i = 0; |
|
181 | 203 | foreach (var p in that) { |
|
182 | 204 | var idx = i; |
|
183 | 205 | p.On( |
|
184 | 206 | x => { |
|
185 | 207 | results[idx] = x; |
|
186 | 208 | if (Interlocked.Decrement(ref count) == 0) |
|
187 | 209 | medium.Resolve(results); |
|
188 | 210 | }, |
|
189 | 211 | error => { |
|
190 | 212 | if (Interlocked.Increment(ref errors) == 1) |
|
191 | 213 | medium.Reject( |
|
192 | 214 | new Exception("The dependency promise is failed", error) |
|
193 | 215 | ); |
|
194 | 216 | }, |
|
195 | 217 | reason => { |
|
196 | 218 | if (Interlocked.Increment(ref errors) == 1) |
|
197 | 219 | medium.Cancel( |
|
198 | 220 | new Exception("The dependency promise is cancelled", reason) |
|
199 | 221 | ); |
|
200 | 222 | } |
|
201 | 223 | ); |
|
202 | 224 | i++; |
|
203 | 225 | } |
|
204 | 226 | |
|
205 | 227 | return medium; |
|
206 | 228 | } |
|
207 | 229 | |
|
208 | 230 | public static IPromise Then(this IPromise that, Action success, Action<Exception> error, Action<Exception> cancel) { |
|
209 | 231 | Safe.ArgumentNotNull(that, "that"); |
|
210 | 232 | |
|
211 | 233 | var d = new ActionTask(success, error, cancel, false); |
|
212 | 234 | that.On(d.Resolve, d.Reject, d.CancelOperation); |
|
213 | 235 | d.CancellationRequested(that.Cancel); |
|
214 | 236 | return d; |
|
215 | 237 | } |
|
216 | 238 | |
|
217 | 239 | public static IPromise Then(this IPromise that, Action success, Action<Exception> error) { |
|
218 | 240 | return Then(that, success, error, null); |
|
219 | 241 | } |
|
220 | 242 | |
|
221 | 243 | public static IPromise Then(this IPromise that, Action success) { |
|
222 | 244 | return Then(that, success, null, null); |
|
223 | 245 | } |
|
224 | 246 | |
|
225 | 247 | public static IPromise<T> Then<T>(this IPromise that, Func<T> success, Func<Exception, T> error, Func<Exception, T> cancel) { |
|
226 | 248 | Safe.ArgumentNotNull(that, "that"); |
|
227 | 249 | |
|
228 | 250 | var d = new FuncTask<T>(success, error, cancel, false); |
|
229 | 251 | that.On(d.Resolve, d.Reject, d.CancelOperation); |
|
230 | 252 | d.CancellationRequested(that.Cancel); |
|
231 | 253 | return d; |
|
232 | 254 | } |
|
233 | 255 | |
|
234 | 256 | public static IPromise<T> Then<T>(this IPromise that, Func<T> success, Func<Exception, T> error) { |
|
235 | 257 | return Then(that, success, error, null); |
|
236 | 258 | } |
|
237 | 259 | |
|
238 | 260 | public static IPromise<T> Then<T>(this IPromise that, Func<T> success) { |
|
239 | 261 | return Then(that, success, null, null); |
|
240 | 262 | } |
|
241 | 263 | |
|
242 | 264 | public static IPromise<T2> Then<T, T2>(this IPromise<T> that, Func<T, T2> success, Func<Exception, T2> error, Func<Exception, T2> cancel) { |
|
243 | 265 | Safe.ArgumentNotNull(that, "that"); |
|
244 | 266 | var d = new FuncTask<T, T2>(success, error, cancel, false); |
|
245 | 267 | that.On(d.Resolve, d.Reject, d.CancelOperation); |
|
246 | 268 | d.CancellationRequested(that.Cancel); |
|
247 | 269 | return d; |
|
248 | 270 | } |
|
249 | 271 | |
|
250 | 272 | public static IPromise<T> Then<T>(this IPromise<T> that, Action<T> success, Func<Exception, T> error, Func<Exception, T> cancel) { |
|
251 | 273 | Safe.ArgumentNotNull(that, "that"); |
|
252 | 274 | var d = new FuncTask<T, T>( |
|
253 | 275 | x => { |
|
254 | 276 | success(x); |
|
255 | 277 | return x; |
|
256 | 278 | }, |
|
257 | 279 | error, |
|
258 | 280 | cancel, |
|
259 | 281 | false |
|
260 | 282 | ); |
|
261 | 283 | that.On(d.Resolve, d.Reject, d.CancelOperation); |
|
262 | 284 | d.CancellationRequested(that.Cancel); |
|
263 | 285 | return d; |
|
264 | 286 | } |
|
265 | 287 | |
|
266 | 288 | public static IPromise<T> Then<T>(this IPromise<T> that, Action<T> success, Func<Exception, T> error) { |
|
267 | 289 | return Then(that, success, error, null); |
|
268 | 290 | } |
|
269 | 291 | |
|
270 | 292 | public static IPromise<T> Then<T>(this IPromise<T> that, Action<T> success) { |
|
271 | 293 | return Then(that, success, null, null); |
|
272 | 294 | } |
|
273 | 295 | |
|
274 | 296 | public static IPromise<T2> Then<T, T2>(this IPromise<T> that, Func<T, T2> success, Func<Exception, T2> error) { |
|
275 | 297 | return Then(that, success, error, null); |
|
276 | 298 | } |
|
277 | 299 | |
|
278 | 300 | public static IPromise<T2> Then<T, T2>(this IPromise<T> that, Func<T, T2> success) { |
|
279 | 301 | return Then(that, success, null, null); |
|
280 | 302 | } |
|
281 | 303 | |
|
282 | 304 | public static IPromise<T> Always<T>(this IPromise<T> that, Action handler) { |
|
283 | 305 | Func<Exception, T> errorOrCancel; |
|
284 | 306 | if (handler != null) |
|
285 | 307 | errorOrCancel = e => { |
|
286 | 308 | handler(); |
|
287 | 309 | throw new PromiseTransientException(e); |
|
288 | 310 | }; |
|
289 | 311 | else |
|
290 | 312 | errorOrCancel = null; |
|
291 | 313 | |
|
292 | 314 | return Then( |
|
293 | 315 | that, |
|
294 | 316 | x => { |
|
295 | 317 | handler(); |
|
296 | 318 | return x; |
|
297 | 319 | }, |
|
298 | 320 | errorOrCancel, |
|
299 | 321 | errorOrCancel); |
|
300 | 322 | } |
|
301 | 323 | |
|
302 | 324 | public static IPromise Always(this IPromise that, Action handler) { |
|
303 | 325 | Action<Exception> errorOrCancel; |
|
304 | 326 | if (handler != null) |
|
305 | 327 | errorOrCancel = e => { |
|
306 | 328 | handler(); |
|
307 | 329 | throw new PromiseTransientException(e); |
|
308 | 330 | }; |
|
309 | 331 | else |
|
310 | 332 | errorOrCancel = null; |
|
311 | 333 | |
|
312 | 334 | return Then( |
|
313 | 335 | that, |
|
314 | 336 | handler, |
|
315 | 337 | errorOrCancel, |
|
316 | 338 | errorOrCancel); |
|
317 | 339 | } |
|
318 | 340 | |
|
319 | 341 | public static IPromise Error(this IPromise that, Action<Exception> handler, bool handleCancellation) { |
|
320 | 342 | Action<Exception> errorOrCancel; |
|
321 | 343 | if (handler != null) |
|
322 | 344 | errorOrCancel = e => { |
|
323 | 345 | handler(e); |
|
324 | 346 | throw new PromiseTransientException(e); |
|
325 | 347 | }; |
|
326 | 348 | else |
|
327 | 349 | errorOrCancel = null; |
|
328 | 350 | |
|
329 | 351 | return Then(that, null, errorOrCancel, handleCancellation ? errorOrCancel : null); |
|
330 | 352 | } |
|
331 | 353 | |
|
332 | 354 | public static IPromise Error(this IPromise that, Action<Exception> handler) { |
|
333 | 355 | return Error(that, handler, false); |
|
334 | 356 | } |
|
335 | 357 | |
|
336 | 358 | public static IPromise<T> Error<T>(this IPromise<T> that, Action<Exception> handler, bool handleCancellation) { |
|
337 | 359 | Func<Exception, T> errorOrCancel; |
|
338 | 360 | if (handler != null) |
|
339 | 361 | errorOrCancel = e => { |
|
340 | 362 | handler(e); |
|
341 | 363 | throw new PromiseTransientException(e); |
|
342 | 364 | }; |
|
343 | 365 | else |
|
344 | 366 | errorOrCancel = null; |
|
345 | 367 | |
|
346 | 368 | return Then(that, null, errorOrCancel, handleCancellation ? errorOrCancel : null); |
|
347 | 369 | } |
|
348 | 370 | |
|
349 | 371 | public static IPromise<T> Error<T>(this IPromise<T> that, Action<Exception> handler) { |
|
350 | 372 | return Error(that, handler, false); |
|
351 | 373 | } |
|
352 | 374 | |
|
353 | 375 | #region chain traits |
|
354 | 376 | public static IPromise Chain(this IPromise that, Func<IPromise> success, Func<Exception, IPromise> error, Func<Exception, IPromise> cancel) { |
|
355 | 377 | Safe.ArgumentNotNull(that, "that"); |
|
356 | 378 | |
|
357 | 379 | var d = new ActionChainTask(success, error, cancel, false); |
|
358 | 380 | that.On(d.Resolve, d.Reject, d.CancelOperation); |
|
359 | 381 | d.CancellationRequested(that.Cancel); |
|
360 | 382 | return d; |
|
361 | 383 | } |
|
362 | 384 | |
|
363 | 385 | public static IPromise Chain(this IPromise that, Func<IPromise> success, Func<Exception, IPromise> error) { |
|
364 | 386 | return Chain(that, success, error, null); |
|
365 | 387 | } |
|
366 | 388 | |
|
367 | 389 | public static IPromise Chain(this IPromise that, Func<IPromise> success) { |
|
368 | 390 | return Chain(that, success, null, null); |
|
369 | 391 | } |
|
370 | 392 | |
|
371 | 393 | public static IPromise<T> Chain<T>(this IPromise that, Func<IPromise<T>> success, Func<Exception, IPromise<T>> error, Func<Exception, IPromise<T>> cancel) { |
|
372 | 394 | Safe.ArgumentNotNull(that, "that"); |
|
373 | 395 | |
|
374 | 396 | var d = new FuncChainTask<T>(success, error, cancel, false); |
|
375 | 397 | that.On(d.Resolve, d.Reject, d.CancelOperation); |
|
376 | 398 | if (success != null) |
|
377 | 399 | d.CancellationRequested(that.Cancel); |
|
378 | 400 | return d; |
|
379 | 401 | } |
|
380 | 402 | |
|
381 | 403 | public static IPromise<T> Chain<T>(this IPromise that, Func<IPromise<T>> success, Func<Exception, IPromise<T>> error) { |
|
382 | 404 | return Chain(that, success, error, null); |
|
383 | 405 | } |
|
384 | 406 | |
|
385 | 407 | public static IPromise<T> Chain<T>(this IPromise that, Func<IPromise<T>> success) { |
|
386 | 408 | return Chain(that, success, null, null); |
|
387 | 409 | } |
|
388 | 410 | |
|
389 | 411 | public static IPromise<T2> Chain<T, T2>(this IPromise<T> that, Func<T, IPromise<T2>> success, Func<Exception, IPromise<T2>> error, Func<Exception, IPromise<T2>> cancel) { |
|
390 | 412 | Safe.ArgumentNotNull(that, "that"); |
|
391 | 413 | var d = new FuncChainTask<T, T2>(success, error, cancel, false); |
|
392 | 414 | that.On(d.Resolve, d.Reject, d.CancelOperation); |
|
393 | 415 | if (success != null) |
|
394 | 416 | d.CancellationRequested(that.Cancel); |
|
395 | 417 | return d; |
|
396 | 418 | } |
|
397 | 419 | |
|
398 | 420 | public static IPromise<T2> Chain<T, T2>(this IPromise<T> that, Func<T, IPromise<T2>> success, Func<Exception, IPromise<T2>> error) { |
|
399 | 421 | return Chain(that, success, error, null); |
|
400 | 422 | } |
|
401 | 423 | |
|
402 | 424 | public static IPromise<T2> Chain<T, T2>(this IPromise<T> that, Func<T, IPromise<T2>> success) { |
|
403 | 425 | return Chain(that, success, null, null); |
|
404 | 426 | } |
|
405 | 427 | |
|
406 | 428 | #endregion |
|
407 | 429 | |
|
408 | 430 | |
|
409 | 431 | #if NET_4_5 |
|
410 | 432 | |
|
411 | 433 | public static PromiseAwaiter<T> GetAwaiter<T>(this IPromise<T> that) { |
|
412 | 434 | Safe.ArgumentNotNull(that, "that"); |
|
413 | 435 | |
|
414 | 436 | return new PromiseAwaiter<T>(that); |
|
415 | 437 | } |
|
416 | 438 | |
|
439 | public static PromiseAwaiter GetAwaiter(this IPromise that) { | |
|
440 | Safe.ArgumentNotNull(that, "that"); | |
|
441 | ||
|
442 | return new PromiseAwaiter(that); | |
|
443 | } | |
|
444 | ||
|
417 | 445 | #endif |
|
418 | 446 | } |
|
419 | 447 | } |
|
420 | 448 |
General Comments 3
ok, latest stable version should be in default
You need to be logged in to leave comments.
Login now