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