@tutao/tutanota-utils 3.93.5 → 3.94.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ArrayUtils.d.ts +44 -39
- package/dist/ArrayUtils.js +220 -229
- package/dist/AsyncResult.d.ts +17 -15
- package/dist/AsyncResult.js +33 -21
- package/dist/CollectionUtils.d.ts +1 -1
- package/dist/CollectionUtils.js +1 -1
- package/dist/DateUtils.d.ts +18 -18
- package/dist/DateUtils.js +40 -38
- package/dist/Encoding.d.ts +25 -25
- package/dist/Encoding.js +175 -181
- package/dist/LazyLoaded.d.ts +32 -32
- package/dist/LazyLoaded.js +65 -66
- package/dist/MapUtils.d.ts +4 -4
- package/dist/MapUtils.js +24 -25
- package/dist/MathUtils.d.ts +2 -2
- package/dist/MathUtils.js +2 -2
- package/dist/PromiseMap.d.ts +8 -4
- package/dist/PromiseMap.js +49 -50
- package/dist/PromiseUtils.d.ts +19 -16
- package/dist/PromiseUtils.js +72 -76
- package/dist/SortedArray.d.ts +11 -11
- package/dist/SortedArray.js +30 -30
- package/dist/StringUtils.d.ts +14 -14
- package/dist/StringUtils.js +31 -36
- package/dist/TypeRef.d.ts +11 -10
- package/dist/TypeRef.js +15 -12
- package/dist/Utils.d.ts +59 -53
- package/dist/Utils.js +207 -227
- package/dist/index.d.ts +145 -19
- package/dist/index.js +140 -14
- package/dist/tsbuildinfo +1 -1
- package/package.json +2 -2
package/dist/Utils.js
CHANGED
|
@@ -1,100 +1,94 @@
|
|
|
1
|
-
import { TypeRef } from "./TypeRef.js"
|
|
1
|
+
import { TypeRef } from "./TypeRef.js"
|
|
2
2
|
export function defer() {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
3
|
+
let ret = {}
|
|
4
|
+
ret.promise = new Promise((resolve, reject) => {
|
|
5
|
+
ret.resolve = resolve
|
|
6
|
+
ret.reject = reject
|
|
7
|
+
})
|
|
8
|
+
return ret
|
|
9
9
|
}
|
|
10
10
|
export function deferWithHandler(handler) {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
11
|
+
const deferred = {}
|
|
12
|
+
deferred.promise = new Promise((resolve, reject) => {
|
|
13
|
+
deferred.resolve = resolve
|
|
14
|
+
deferred.reject = reject
|
|
15
|
+
}).then(handler)
|
|
16
|
+
return deferred
|
|
17
17
|
}
|
|
18
18
|
export async function asyncFind(array, finder) {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
19
|
+
for (let i = 0; i < array.length; i++) {
|
|
20
|
+
const item = array[i]
|
|
21
|
+
if (await finder(item, i, array.length)) {
|
|
22
|
+
return item
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return null
|
|
26
26
|
}
|
|
27
27
|
export async function asyncFindAndMap(array, finder) {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
28
|
+
for (let i = 0; i < array.length; i++) {
|
|
29
|
+
const item = array[i]
|
|
30
|
+
const mapped = await finder(item, i, array.length)
|
|
31
|
+
if (mapped) {
|
|
32
|
+
return mapped
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return null
|
|
36
36
|
}
|
|
37
37
|
/**
|
|
38
38
|
* Calls an executor function for slices of nbrOfElementsInGroup items of the given array until the executor function returns false.
|
|
39
39
|
*/
|
|
40
40
|
export function executeInGroups(array, nbrOfElementsInGroup, executor) {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
}
|
|
41
|
+
if (array.length > 0) {
|
|
42
|
+
let nextSlice = Math.min(array.length, nbrOfElementsInGroup)
|
|
43
|
+
return executor(array.slice(0, nextSlice)).then(doContinue => {
|
|
44
|
+
if (doContinue) {
|
|
45
|
+
return executeInGroups(array.slice(nextSlice), nbrOfElementsInGroup, executor)
|
|
46
|
+
}
|
|
47
|
+
})
|
|
48
|
+
} else {
|
|
49
|
+
return Promise.resolve()
|
|
50
|
+
}
|
|
52
51
|
}
|
|
53
52
|
export function neverNull(object) {
|
|
54
|
-
|
|
53
|
+
return object
|
|
55
54
|
}
|
|
56
55
|
export function assertNotNull(object, message = "null") {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
56
|
+
if (object == null) {
|
|
57
|
+
throw new Error("AssertNotNull failed : " + message)
|
|
58
|
+
}
|
|
59
|
+
return object
|
|
61
60
|
}
|
|
62
61
|
export function isNotNull(t) {
|
|
63
|
-
|
|
62
|
+
return t != null
|
|
64
63
|
}
|
|
65
64
|
export function assert(assertion, message) {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
65
|
+
if (!resolveMaybeLazy(assertion)) {
|
|
66
|
+
throw new Error(`Assertion failed: ${message}`)
|
|
67
|
+
}
|
|
69
68
|
}
|
|
70
69
|
export function downcast(object) {
|
|
71
|
-
|
|
70
|
+
return object
|
|
72
71
|
}
|
|
73
72
|
export function clone(instance) {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
return copy;
|
|
94
|
-
}
|
|
95
|
-
else {
|
|
96
|
-
return instance;
|
|
97
|
-
}
|
|
73
|
+
if (instance instanceof Uint8Array) {
|
|
74
|
+
return downcast(instance.slice())
|
|
75
|
+
} else if (instance instanceof Array) {
|
|
76
|
+
return downcast(instance.map(i => clone(i)))
|
|
77
|
+
} else if (instance instanceof Date) {
|
|
78
|
+
return new Date(instance.getTime())
|
|
79
|
+
} else if (instance instanceof TypeRef) {
|
|
80
|
+
return instance
|
|
81
|
+
} else if (instance instanceof Object) {
|
|
82
|
+
// Can only pass null or Object, cannot pass undefined
|
|
83
|
+
const copy = Object.create(Object.getPrototypeOf(instance) || null)
|
|
84
|
+
Object.assign(copy, instance)
|
|
85
|
+
for (let key of Object.keys(copy)) {
|
|
86
|
+
copy[key] = clone(copy[key])
|
|
87
|
+
}
|
|
88
|
+
return copy
|
|
89
|
+
} else {
|
|
90
|
+
return instance
|
|
91
|
+
}
|
|
98
92
|
}
|
|
99
93
|
/**
|
|
100
94
|
* Function which accepts another function. On first invocation
|
|
@@ -102,18 +96,17 @@ export function clone(instance) {
|
|
|
102
96
|
* on consequent invocations.
|
|
103
97
|
*/
|
|
104
98
|
export function lazyMemoized(source) {
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
};
|
|
99
|
+
// Using separate variable for tracking because value can be undefined and we want to the function call only once
|
|
100
|
+
let cached = false
|
|
101
|
+
let value
|
|
102
|
+
return () => {
|
|
103
|
+
if (cached) {
|
|
104
|
+
return value
|
|
105
|
+
} else {
|
|
106
|
+
cached = true
|
|
107
|
+
return (value = source())
|
|
108
|
+
}
|
|
109
|
+
}
|
|
117
110
|
}
|
|
118
111
|
/**
|
|
119
112
|
* Returns a cached version of {@param fn}.
|
|
@@ -122,44 +115,43 @@ export function lazyMemoized(source) {
|
|
|
122
115
|
* Only remembers the last argument.
|
|
123
116
|
*/
|
|
124
117
|
export function memoized(fn) {
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
118
|
+
let lastArg
|
|
119
|
+
let lastResult
|
|
120
|
+
let didCache = false
|
|
121
|
+
return arg => {
|
|
122
|
+
if (!didCache || arg !== lastArg) {
|
|
123
|
+
lastArg = arg
|
|
124
|
+
didCache = true
|
|
125
|
+
lastResult = fn(arg)
|
|
126
|
+
}
|
|
127
|
+
return lastResult
|
|
128
|
+
}
|
|
136
129
|
}
|
|
137
130
|
/**
|
|
138
131
|
* Function which returns what was passed into it
|
|
139
132
|
*/
|
|
140
133
|
export function identity(t) {
|
|
141
|
-
|
|
134
|
+
return t
|
|
142
135
|
}
|
|
143
136
|
/**
|
|
144
137
|
* Function which does nothing.
|
|
145
138
|
*/
|
|
146
|
-
export function noOp() {
|
|
147
|
-
}
|
|
139
|
+
export function noOp() {}
|
|
148
140
|
/**
|
|
149
141
|
* Return a function, which executed {@param toThrottle} only after it is not invoked for {@param timeout} ms.
|
|
150
142
|
* Executes function with the last passed arguments
|
|
151
143
|
* @return {Function}
|
|
152
144
|
*/
|
|
153
145
|
export function debounce(timeout, toThrottle) {
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
146
|
+
let timeoutId
|
|
147
|
+
let toInvoke
|
|
148
|
+
return downcast((...args) => {
|
|
149
|
+
if (timeoutId) {
|
|
150
|
+
clearTimeout(timeoutId)
|
|
151
|
+
}
|
|
152
|
+
toInvoke = toThrottle.bind(null, ...args)
|
|
153
|
+
timeoutId = setTimeout(toInvoke, timeout)
|
|
154
|
+
})
|
|
163
155
|
}
|
|
164
156
|
/**
|
|
165
157
|
* Returns a debounced function. When invoked for the first time, will just invoke
|
|
@@ -169,113 +161,102 @@ export function debounce(timeout, toThrottle) {
|
|
|
169
161
|
* but ones in the middle (which happen too often) are discarded.}
|
|
170
162
|
*/
|
|
171
163
|
export function debounceStart(timeout, toThrottle) {
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
});
|
|
164
|
+
let timeoutId
|
|
165
|
+
let lastInvoked = 0
|
|
166
|
+
return downcast((...args) => {
|
|
167
|
+
if (Date.now() - lastInvoked < timeout) {
|
|
168
|
+
timeoutId && clearTimeout(timeoutId)
|
|
169
|
+
timeoutId = setTimeout(() => {
|
|
170
|
+
timeoutId = null
|
|
171
|
+
toThrottle.apply(null, args)
|
|
172
|
+
}, timeout)
|
|
173
|
+
} else {
|
|
174
|
+
toThrottle.apply(null, args)
|
|
175
|
+
}
|
|
176
|
+
lastInvoked = Date.now()
|
|
177
|
+
})
|
|
187
178
|
}
|
|
188
179
|
export function randomIntFromInterval(min, max) {
|
|
189
|
-
|
|
180
|
+
return Math.floor(Math.random() * (max - min + 1) + min)
|
|
190
181
|
}
|
|
191
182
|
export function errorToString(error) {
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
183
|
+
let errorString = error.name ? error.name : "?"
|
|
184
|
+
if (error.message) {
|
|
185
|
+
errorString += `\n Error message: ${error.message}`
|
|
186
|
+
}
|
|
187
|
+
if (error.stack) {
|
|
188
|
+
// the error id is included in the stacktrace
|
|
189
|
+
errorString += `\nStacktrace: \n${error.stack}`
|
|
190
|
+
}
|
|
191
|
+
return errorString
|
|
201
192
|
}
|
|
202
193
|
/**
|
|
203
194
|
* Like {@link Object.entries} but preserves the type of the key and value
|
|
204
195
|
*/
|
|
205
196
|
export function objectEntries(object) {
|
|
206
|
-
|
|
197
|
+
return downcast(Object.entries(object))
|
|
207
198
|
}
|
|
208
199
|
/**
|
|
209
200
|
* modified deepEquals from ospec is only needed as long as we use custom classes (TypeRef) and Date is not properly handled
|
|
210
201
|
*/
|
|
211
202
|
export function deepEqual(a, b) {
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
}
|
|
246
|
-
return true;
|
|
247
|
-
}
|
|
248
|
-
if (a.valueOf() === b.valueOf())
|
|
249
|
-
return true;
|
|
250
|
-
}
|
|
251
|
-
return false;
|
|
203
|
+
if (a === b) return true
|
|
204
|
+
if (xor(a === null, b === null) || xor(a === undefined, b === undefined)) return false
|
|
205
|
+
if (typeof a === "object" && typeof b === "object") {
|
|
206
|
+
const aIsArgs = isArguments(a),
|
|
207
|
+
bIsArgs = isArguments(b)
|
|
208
|
+
if (a.length === b.length && ((a instanceof Array && b instanceof Array) || (aIsArgs && bIsArgs))) {
|
|
209
|
+
const aKeys = Object.getOwnPropertyNames(a),
|
|
210
|
+
bKeys = Object.getOwnPropertyNames(b)
|
|
211
|
+
if (aKeys.length !== bKeys.length) return false
|
|
212
|
+
for (let i = 0; i < aKeys.length; i++) {
|
|
213
|
+
if (!hasOwn.call(b, aKeys[i]) || !deepEqual(a[aKeys[i]], b[aKeys[i]])) return false
|
|
214
|
+
}
|
|
215
|
+
return true
|
|
216
|
+
}
|
|
217
|
+
if (a instanceof Date && b instanceof Date) return a.getTime() === b.getTime()
|
|
218
|
+
if (a instanceof Object && b instanceof Object && !aIsArgs && !bIsArgs) {
|
|
219
|
+
for (let i in a) {
|
|
220
|
+
if (!(i in b) || !deepEqual(a[i], b[i])) return false
|
|
221
|
+
}
|
|
222
|
+
for (let i in b) {
|
|
223
|
+
if (!(i in a)) return false
|
|
224
|
+
}
|
|
225
|
+
return true
|
|
226
|
+
}
|
|
227
|
+
if (typeof Buffer === "function" && a instanceof Buffer && b instanceof Buffer) {
|
|
228
|
+
for (let i = 0; i < a.length; i++) {
|
|
229
|
+
if (a[i] !== b[i]) return false
|
|
230
|
+
}
|
|
231
|
+
return true
|
|
232
|
+
}
|
|
233
|
+
if (a.valueOf() === b.valueOf()) return true
|
|
234
|
+
}
|
|
235
|
+
return false
|
|
252
236
|
}
|
|
253
237
|
function xor(a, b) {
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
238
|
+
const aBool = !!a
|
|
239
|
+
const bBool = !!b
|
|
240
|
+
return (aBool && !bBool) || (bBool && !aBool)
|
|
257
241
|
}
|
|
258
242
|
function isArguments(a) {
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
}
|
|
266
|
-
const hasOwn = {}.hasOwnProperty;
|
|
243
|
+
if ("callee" in a) {
|
|
244
|
+
for (let i in a) if (i === "callee") return false
|
|
245
|
+
return true
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
const hasOwn = {}.hasOwnProperty
|
|
267
249
|
/**
|
|
268
250
|
* returns an array of top-level properties that are in both objA and objB, but differ in value
|
|
269
251
|
* does not handle functions or circular references
|
|
270
252
|
* treats undefined and null as equal
|
|
271
253
|
*/
|
|
272
254
|
export function getChangedProps(objA, objB) {
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
.filter(k => !deepEqual(objA[k], objB[k]));
|
|
255
|
+
if (objA == null || objB == null || objA === objB) return []
|
|
256
|
+
return Object.keys(objA)
|
|
257
|
+
.filter(k => Object.keys(objB).includes(k))
|
|
258
|
+
.filter(k => ![null, undefined].includes(objA[k]) || ![null, undefined].includes(objB[k]))
|
|
259
|
+
.filter(k => !deepEqual(objA[k], objB[k]))
|
|
279
260
|
}
|
|
280
261
|
/**
|
|
281
262
|
* Disallow set, delete and clear on Map.
|
|
@@ -284,76 +265,75 @@ export function getChangedProps(objA, objB) {
|
|
|
284
265
|
* @return {unknown}
|
|
285
266
|
*/
|
|
286
267
|
export function freezeMap(myMap) {
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
268
|
+
function mapSet(key, value) {
|
|
269
|
+
throw new Error("Can't add property " + key + ", map is not extensible")
|
|
270
|
+
}
|
|
271
|
+
function mapDelete(key) {
|
|
272
|
+
throw new Error("Can't delete property " + key + ", map is frozen")
|
|
273
|
+
}
|
|
274
|
+
function mapClear() {
|
|
275
|
+
throw new Error("Can't clear map, map is frozen")
|
|
276
|
+
}
|
|
277
|
+
const anyMap = downcast(myMap)
|
|
278
|
+
anyMap.set = mapSet
|
|
279
|
+
anyMap.delete = mapDelete
|
|
280
|
+
anyMap.clear = mapClear
|
|
281
|
+
Object.freeze(anyMap)
|
|
282
|
+
return anyMap
|
|
302
283
|
}
|
|
303
284
|
export function addressDomain(senderAddress) {
|
|
304
|
-
|
|
285
|
+
return senderAddress.slice(senderAddress.lastIndexOf("@") + 1)
|
|
305
286
|
}
|
|
306
287
|
/**
|
|
307
288
|
* Ignores the fact that Object.keys returns also not owned properties.
|
|
308
289
|
*/
|
|
309
290
|
export function typedKeys(obj) {
|
|
310
|
-
|
|
291
|
+
return downcast(Object.keys(obj))
|
|
311
292
|
}
|
|
312
293
|
/**
|
|
313
294
|
* Ignores the fact that Object.keys returns also not owned properties.
|
|
314
295
|
*/
|
|
315
296
|
export function typedEntries(obj) {
|
|
316
|
-
|
|
297
|
+
return downcast(Object.entries(obj))
|
|
317
298
|
}
|
|
318
299
|
/**
|
|
319
300
|
* Ignores the fact that Object.keys returns also not owned properties.
|
|
320
301
|
*/
|
|
321
302
|
export function typedValues(obj) {
|
|
322
|
-
|
|
303
|
+
return downcast(Object.values(obj))
|
|
323
304
|
}
|
|
324
305
|
export function resolveMaybeLazy(maybe) {
|
|
325
|
-
|
|
306
|
+
return typeof maybe === "function" ? maybe() : maybe
|
|
326
307
|
}
|
|
327
308
|
export function getAsLazy(maybe) {
|
|
328
|
-
|
|
309
|
+
return typeof maybe === "function" ? downcast(maybe) : () => maybe
|
|
329
310
|
}
|
|
330
311
|
export function mapLazily(maybe, mapping) {
|
|
331
|
-
|
|
312
|
+
return () => mapping(resolveMaybeLazy(maybe))
|
|
332
313
|
}
|
|
333
314
|
/**
|
|
334
315
|
* Stricter version of parseInt() from MDN. parseInt() allows some arbitrary characters at the end of the string.
|
|
335
316
|
* Returns NaN in case there's anything non-number in the string.
|
|
336
317
|
*/
|
|
337
318
|
export function filterInt(value) {
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
}
|
|
319
|
+
if (/^\d+$/.test(value)) {
|
|
320
|
+
return parseInt(value, 10)
|
|
321
|
+
} else {
|
|
322
|
+
return NaN
|
|
323
|
+
}
|
|
344
324
|
}
|
|
345
325
|
export function insideRect(point, rect) {
|
|
346
|
-
|
|
326
|
+
return point.x >= rect.left && point.x < rect.right && point.y >= rect.top && point.y < rect.bottom
|
|
347
327
|
}
|
|
348
328
|
/**
|
|
349
329
|
* If val is non null, returns the result of val passed to action, else null
|
|
350
330
|
*/
|
|
351
331
|
export function mapNullable(val, action) {
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
332
|
+
if (val != null) {
|
|
333
|
+
const result = action(val)
|
|
334
|
+
if (result != null) {
|
|
335
|
+
return result
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
return null
|
|
359
339
|
}
|