Commit | Line | Data |
---|---|---|
b1baa808 MK |
1 | /******************************************************************************* |
2 | * Copyright (c) 2012 Ericsson | |
3 | * | |
4 | * All rights reserved. This program and the accompanying materials are made | |
5 | * available under the terms of the Eclipse Public License v1.0 which | |
6 | * accompanies this distribution, and is available at | |
7 | * http://www.eclipse.org/legal/epl-v10.html | |
8 | * | |
9 | * Contributors: Matthew Khouzam - Initial API and implementation | |
10 | *******************************************************************************/ | |
11 | ||
a3fc8213 AM |
12 | package org.eclipse.linuxtools.tmf.core.ctfadaptor; |
13 | ||
a3fc8213 AM |
14 | import org.eclipse.core.resources.IProject; |
15 | import org.eclipse.core.resources.IResource; | |
139d5c1a | 16 | import org.eclipse.core.runtime.CoreException; |
a1a24d68 | 17 | import org.eclipse.core.runtime.IPath; |
aa572e22 MK |
18 | import org.eclipse.linuxtools.ctf.core.event.EventDeclaration; |
19 | import org.eclipse.linuxtools.ctf.core.event.EventDefinition; | |
a3fc8213 AM |
20 | import org.eclipse.linuxtools.ctf.core.trace.CTFReaderException; |
21 | import org.eclipse.linuxtools.ctf.core.trace.CTFTrace; | |
22 | import org.eclipse.linuxtools.tmf.core.component.TmfEventProvider; | |
aa572e22 | 23 | import org.eclipse.linuxtools.tmf.core.event.ITmfEventField; |
a3fc8213 AM |
24 | import org.eclipse.linuxtools.tmf.core.event.ITmfTimestamp; |
25 | import org.eclipse.linuxtools.tmf.core.event.TmfTimeRange; | |
26 | import org.eclipse.linuxtools.tmf.core.event.TmfTimestamp; | |
b4f71e4a | 27 | import org.eclipse.linuxtools.tmf.core.exceptions.TmfTraceException; |
a3fc8213 AM |
28 | import org.eclipse.linuxtools.tmf.core.request.ITmfDataRequest; |
29 | import org.eclipse.linuxtools.tmf.core.request.ITmfEventRequest; | |
30 | import org.eclipse.linuxtools.tmf.core.signal.TmfSignal; | |
31 | import org.eclipse.linuxtools.tmf.core.signal.TmfSignalManager; | |
18ab1d18 | 32 | import org.eclipse.linuxtools.tmf.core.statesystem.IStateSystemQuerier; |
a3fc8213 AM |
33 | import org.eclipse.linuxtools.tmf.core.trace.ITmfContext; |
34 | import org.eclipse.linuxtools.tmf.core.trace.ITmfLocation; | |
35 | import org.eclipse.linuxtools.tmf.core.trace.ITmfTrace; | |
a3fc8213 | 36 | |
b1baa808 MK |
37 | /** |
38 | */ | |
25e48683 | 39 | public class CtfTmfTrace extends TmfEventProvider<CtfTmfEvent> implements ITmfTrace<CtfTmfEvent> { |
a3fc8213 AM |
40 | |
41 | // ------------------------------------------------------------------------ | |
42 | // Constants | |
43 | // ------------------------------------------------------------------------ | |
44 | ||
a3fc8213 AM |
45 | // ------------------------------------------------------------------------ |
46 | // Attributes | |
47 | // ------------------------------------------------------------------------ | |
48 | ||
49 | // the Ctf Trace | |
50 | private CTFTrace fTrace; | |
51 | ||
a3fc8213 AM |
52 | // The number of events collected |
53 | protected long fNbEvents = 0; | |
54 | ||
55 | // The time span of the event stream | |
56 | private ITmfTimestamp fStartTime = TmfTimestamp.BIG_CRUNCH; | |
57 | private ITmfTimestamp fEndTime = TmfTimestamp.BIG_BANG; | |
58 | ||
59 | // The trace resource | |
60 | private IResource fResource; | |
61 | ||
11d6f468 | 62 | /* Reference to the state system assigned to this trace */ |
d26f90fd | 63 | protected IStateSystemQuerier ss = null; |
11d6f468 | 64 | |
a3fc8213 AM |
65 | // ------------------------------------------------------------------------ |
66 | // Constructors | |
67 | // ------------------------------------------------------------------------ | |
68 | ||
69 | public CtfTmfTrace() { | |
70 | super(); | |
71 | } | |
72 | ||
b1baa808 MK |
73 | /** |
74 | * Method initTrace. | |
75 | * @param resource IResource | |
76 | * @param path String | |
77 | * @param eventType Class<CtfTmfEvent> | |
78 | * @throws TmfTraceException | |
79 | * @see org.eclipse.linuxtools.tmf.core.trace.ITmfTrace#initTrace(IResource, String, Class<CtfTmfEvent>) | |
80 | */ | |
a3fc8213 | 81 | @Override |
25e48683 | 82 | public void initTrace(final IResource resource, final String path, final Class<CtfTmfEvent> eventType) |
b4f71e4a | 83 | throws TmfTraceException { |
25e48683 | 84 | this.fResource = resource; |
a3fc8213 AM |
85 | try { |
86 | this.fTrace = new CTFTrace(path); | |
aa572e22 MK |
87 | for( int i =0 ; i< this.fTrace.getNbEventTypes(); i++) { |
88 | EventDeclaration ed = this.fTrace.getEventType(i); | |
89 | ITmfEventField eventField = parseDeclaration(ed); | |
90 | new CtfTmfEventType(ed.getName(), eventField); | |
91 | } | |
25e48683 | 92 | } catch (final CTFReaderException e) { |
a3fc8213 AM |
93 | /* |
94 | * If it failed at the init(), we can assume it's because the file | |
95 | * was not found or was not recognized as a CTF trace. Throw into | |
96 | * the new type of exception expected by the rest of TMF. | |
97 | */ | |
b4f71e4a | 98 | throw new TmfTraceException(e.getMessage()); |
a3fc8213 | 99 | } |
f474d36b | 100 | CtfIterator iterator = new CtfIterator(this, 0, 0); |
57c073c5 | 101 | setStartTime(TmfTimestamp.BIG_BANG); |
f474d36b | 102 | if( !iterator.getLocation().equals(CtfIterator.NULL_LOCATION)) { |
57c073c5 MK |
103 | setStartTime(iterator.getCurrentEvent().getTimestamp()); |
104 | } | |
a3fc8213 | 105 | TmfSignalManager.register(this); |
b1baa808 | 106 | // FIXME this should become a request |
11d6f468 | 107 | buildStateSystem(); |
139d5c1a AM |
108 | |
109 | /* Refresh the project, so it can pick up new files that got created. */ | |
110 | if ( resource != null) { | |
111 | try { | |
112 | resource.getProject().refreshLocal(IResource.DEPTH_INFINITE, null); | |
113 | } catch (CoreException e) { | |
114 | throw new TmfTraceException(e.getMessage()); | |
115 | } | |
116 | } | |
a3fc8213 AM |
117 | } |
118 | ||
aa572e22 MK |
119 | private static ITmfEventField parseDeclaration(EventDeclaration ed) { |
120 | EventDefinition eventDef = ed.createDefinition(null); | |
121 | return new CtfTmfContent(ITmfEventField.ROOT_FIELD_ID, | |
122 | CtfTmfEvent.parseFields(eventDef)); | |
123 | } | |
124 | ||
b1baa808 MK |
125 | /** |
126 | * Method dispose. | |
127 | * @see org.eclipse.linuxtools.tmf.core.component.ITmfComponent#dispose() | |
128 | */ | |
a3fc8213 AM |
129 | @Override |
130 | public void dispose() { | |
131 | TmfSignalManager.deregister(this); | |
132 | } | |
133 | ||
b1baa808 MK |
134 | /** |
135 | * Method broadcast. | |
136 | * @param signal TmfSignal | |
137 | * @see org.eclipse.linuxtools.tmf.core.component.ITmfComponent#broadcast(TmfSignal) | |
138 | */ | |
a3fc8213 | 139 | @Override |
25e48683 | 140 | public void broadcast(final TmfSignal signal) { |
a3fc8213 AM |
141 | TmfSignalManager.dispatchSignal(signal); |
142 | } | |
143 | ||
b1baa808 MK |
144 | /** |
145 | * Method validate. | |
146 | * @param project IProject | |
147 | * @param path String | |
148 | * @return boolean | |
149 | * @see org.eclipse.linuxtools.tmf.core.trace.ITmfTrace#validate(IProject, String) | |
150 | */ | |
a3fc8213 | 151 | @Override |
25e48683 | 152 | public boolean validate(final IProject project, final String path) { |
a3fc8213 AM |
153 | try { |
154 | final CTFTrace temp = new CTFTrace(path); | |
155 | return temp.majortIsSet(); // random test | |
25e48683 | 156 | } catch (final CTFReaderException e) { |
90235d6b AM |
157 | /* Nope, not a CTF trace we can read */ |
158 | return false; | |
a3fc8213 | 159 | } |
a3fc8213 AM |
160 | } |
161 | ||
a3fc8213 AM |
162 | // ------------------------------------------------------------------------ |
163 | // Accessors | |
164 | // ------------------------------------------------------------------------ | |
165 | ||
25e48683 | 166 | /** |
b1baa808 | 167 | * Method getEventType. |
25e48683 | 168 | * @return the trace path |
b1baa808 | 169 | * @see org.eclipse.linuxtools.tmf.core.trace.ITmfTrace#getEventType() |
25e48683 FC |
170 | */ |
171 | @Override | |
13cb5f43 | 172 | public Class<CtfTmfEvent> getEventType() { |
25e48683 FC |
173 | return fType; |
174 | } | |
175 | ||
b1baa808 MK |
176 | /** |
177 | * Method getNbEnvVars. | |
178 | * @return int | |
179 | */ | |
ce2388e0 FC |
180 | public int getNbEnvVars() { |
181 | return this.fTrace.getEnvironment().size(); | |
182 | } | |
183 | ||
184 | ||
b1baa808 MK |
185 | /** |
186 | * Method getEnvNames. | |
187 | * @return String[] | |
188 | */ | |
ce2388e0 | 189 | public String[] getEnvNames() { |
25e48683 | 190 | final String[] s = new String[getNbEnvVars()]; |
ce2388e0 FC |
191 | return this.fTrace.getEnvironment().keySet().toArray(s); |
192 | } | |
193 | ||
b1baa808 MK |
194 | /** |
195 | * Method getEnvValue. | |
196 | * @param key String | |
197 | * @return String | |
198 | */ | |
25e48683 | 199 | public String getEnvValue(final String key) { |
ce2388e0 FC |
200 | return this.fTrace.getEnvironment().get(key); |
201 | } | |
202 | ||
203 | ||
a3fc8213 | 204 | /** |
b1baa808 MK |
205 | |
206 | * @return the trace path * @see org.eclipse.linuxtools.tmf.core.trace.ITmfTrace#getPath() | |
a3fc8213 AM |
207 | */ |
208 | @Override | |
209 | public String getPath() { | |
210 | return this.fTrace.getPath(); | |
211 | } | |
212 | ||
b1baa808 MK |
213 | /** |
214 | * Method getName. | |
215 | * @return String | |
216 | * @see org.eclipse.linuxtools.tmf.core.component.ITmfComponent#getName() | |
217 | */ | |
a3fc8213 AM |
218 | @Override |
219 | public String getName() { | |
a1a24d68 MK |
220 | String traceName = (fResource != null) ? fResource.getName() : null; |
221 | // If no resource was provided, extract the display name the trace path | |
222 | if (traceName == null) { | |
223 | final String path = this.fTrace.getPath(); | |
224 | final int sep = path.lastIndexOf(IPath.SEPARATOR); | |
225 | traceName = (sep >= 0) ? path.substring(sep + 1) : path; | |
11d6f468 | 226 | } |
a1a24d68 | 227 | return traceName; |
a3fc8213 AM |
228 | } |
229 | ||
b1baa808 MK |
230 | /** |
231 | * Method getCacheSize. | |
232 | * @return int | |
233 | * @see org.eclipse.linuxtools.tmf.core.trace.ITmfTrace#getCacheSize() | |
234 | */ | |
a3fc8213 | 235 | @Override |
20658947 | 236 | public int getCacheSize() { |
ce2388e0 | 237 | return 50000; // not true, but it works |
a3fc8213 AM |
238 | } |
239 | ||
b1baa808 MK |
240 | /** |
241 | * Method getNbEvents. | |
242 | * @return long | |
243 | * @see org.eclipse.linuxtools.tmf.core.trace.ITmfTrace#getNbEvents() | |
244 | */ | |
a3fc8213 AM |
245 | @Override |
246 | public long getNbEvents() { | |
247 | return this.fNbEvents; | |
248 | } | |
249 | ||
b1baa808 MK |
250 | /** |
251 | * Method getTimeRange. | |
252 | * @return TmfTimeRange | |
253 | * @see org.eclipse.linuxtools.tmf.core.trace.ITmfTrace#getTimeRange() | |
254 | */ | |
a3fc8213 AM |
255 | @Override |
256 | public TmfTimeRange getTimeRange() { | |
257 | return new TmfTimeRange(this.fStartTime, this.fEndTime); | |
258 | } | |
259 | ||
b1baa808 MK |
260 | /** |
261 | * Method getStartTime. | |
262 | * @return ITmfTimestamp | |
263 | * @see org.eclipse.linuxtools.tmf.core.trace.ITmfTrace#getStartTime() | |
264 | */ | |
a3fc8213 AM |
265 | @Override |
266 | public ITmfTimestamp getStartTime() { | |
267 | return this.fStartTime; | |
268 | } | |
269 | ||
b1baa808 MK |
270 | /** |
271 | * Method getEndTime. | |
272 | * @return ITmfTimestamp | |
273 | * @see org.eclipse.linuxtools.tmf.core.trace.ITmfTrace#getEndTime() | |
274 | */ | |
a3fc8213 AM |
275 | @Override |
276 | public ITmfTimestamp getEndTime() { | |
277 | return this.fEndTime; | |
278 | } | |
279 | ||
b1baa808 | 280 | /** |
f474d36b PT |
281 | * Method getCurrentLocation. This is not applicable in CTF |
282 | * @return null, since the trace has no knowledge of the current location | |
b1baa808 MK |
283 | * @see org.eclipse.linuxtools.tmf.core.trace.ITmfTrace#getCurrentLocation() |
284 | */ | |
a3fc8213 AM |
285 | @Override |
286 | public ITmfLocation<?> getCurrentLocation() { | |
f474d36b | 287 | return null; |
a3fc8213 AM |
288 | } |
289 | ||
a3fc8213 AM |
290 | // ------------------------------------------------------------------------ |
291 | // Operators | |
292 | // ------------------------------------------------------------------------ | |
293 | ||
b1baa808 MK |
294 | /** |
295 | * Method setTimeRange. | |
296 | * @param range TmfTimeRange | |
297 | */ | |
25e48683 | 298 | protected void setTimeRange(final TmfTimeRange range) { |
a3fc8213 AM |
299 | this.fStartTime = range.getStartTime(); |
300 | this.fEndTime = range.getEndTime(); | |
301 | } | |
302 | ||
b1baa808 MK |
303 | /** |
304 | * Method setStartTime. | |
305 | * @param startTime ITmfTimestamp | |
306 | */ | |
25e48683 | 307 | protected void setStartTime(final ITmfTimestamp startTime) { |
a3fc8213 AM |
308 | this.fStartTime = startTime; |
309 | } | |
310 | ||
b1baa808 MK |
311 | /** |
312 | * Method setEndTime. | |
313 | * @param endTime ITmfTimestamp | |
314 | */ | |
25e48683 | 315 | protected void setEndTime(final ITmfTimestamp endTime) { |
a3fc8213 AM |
316 | this.fEndTime = endTime; |
317 | } | |
318 | ||
319 | // ------------------------------------------------------------------------ | |
320 | // TmfProvider | |
321 | // ------------------------------------------------------------------------ | |
322 | ||
b1baa808 MK |
323 | /** |
324 | * Method armRequest. | |
325 | * @param request ITmfDataRequest<CtfTmfEvent> | |
326 | * @return ITmfContext | |
327 | */ | |
a3fc8213 | 328 | @Override |
25e48683 | 329 | public ITmfContext armRequest(final ITmfDataRequest<CtfTmfEvent> request) { |
a3fc8213 | 330 | if ((request instanceof ITmfEventRequest<?>) |
ce2388e0 | 331 | && !TmfTimestamp.BIG_BANG |
25e48683 FC |
332 | .equals(((ITmfEventRequest<CtfTmfEvent>) request) |
333 | .getRange().getStartTime()) | |
334 | && (request.getIndex() == 0)) { | |
335 | final ITmfContext context = seekEvent(((ITmfEventRequest<CtfTmfEvent>) request) | |
ce2388e0 FC |
336 | .getRange().getStartTime()); |
337 | ((ITmfEventRequest<CtfTmfEvent>) request) | |
25e48683 | 338 | .setStartIndex((int) context.getRank()); |
a3fc8213 AM |
339 | return context; |
340 | } | |
341 | return seekEvent(request.getIndex()); | |
342 | } | |
343 | ||
344 | /** | |
345 | * The trace reader keeps its own iterator: the "context" parameter here | |
346 | * will be ignored. | |
ce2388e0 | 347 | * |
a3fc8213 AM |
348 | * If you wish to specify a new context, instantiate a new CtfIterator and |
349 | * seek() it to where you want, and use that to read events. | |
ce2388e0 | 350 | * |
a3fc8213 AM |
351 | * FIXME merge with getNextEvent below once they both use the same parameter |
352 | * type. | |
b1baa808 MK |
353 | * @param context ITmfContext |
354 | * @return CtfTmfEvent | |
a3fc8213 AM |
355 | */ |
356 | @Override | |
25e48683 | 357 | public CtfTmfEvent getNext(final ITmfContext context) { |
aa572e22 | 358 | return readNextEvent(context); |
a3fc8213 AM |
359 | } |
360 | ||
361 | // ------------------------------------------------------------------------ | |
362 | // ITmfTrace | |
363 | // ------------------------------------------------------------------------ | |
364 | ||
b1baa808 MK |
365 | /** |
366 | * Method seekEvent. | |
367 | * @param location ITmfLocation<?> | |
368 | * @return ITmfContext | |
369 | * @see org.eclipse.linuxtools.tmf.core.trace.ITmfTrace#seekEvent(ITmfLocation<?>) | |
370 | */ | |
a3fc8213 | 371 | @Override |
7e6347b0 | 372 | public ITmfContext seekEvent(final ITmfLocation<?> location) { |
ce2388e0 | 373 | CtfLocation currentLocation = (CtfLocation) location; |
11d6f468 | 374 | if (currentLocation == null) { |
ce2388e0 | 375 | currentLocation = new CtfLocation(0L); |
11d6f468 | 376 | } |
f474d36b PT |
377 | CtfIterator context = new CtfIterator(this); |
378 | context.setLocation(currentLocation); | |
379 | context.setRank(ITmfContext.UNKNOWN_RANK); | |
380 | return context; | |
a3fc8213 AM |
381 | } |
382 | ||
b1baa808 MK |
383 | /** |
384 | * Method getLocationRatio. | |
385 | * @param location ITmfLocation<?> | |
386 | * @return double | |
387 | * @see org.eclipse.linuxtools.tmf.core.trace.ITmfTrace#getLocationRatio(ITmfLocation<?>) | |
388 | */ | |
a3fc8213 | 389 | @Override |
25e48683 FC |
390 | public double getLocationRatio(final ITmfLocation<?> location) { |
391 | final CtfLocation curLocation = (CtfLocation) location; | |
f474d36b | 392 | CtfIterator iterator = new CtfIterator(this); |
ce2388e0 FC |
393 | iterator.seek(curLocation.getLocation()); |
394 | return ((double) iterator.getCurrentEvent().getTimestampValue() - iterator | |
395 | .getStartTime()) | |
396 | / (iterator.getEndTime() - iterator.getStartTime()); | |
a3fc8213 AM |
397 | } |
398 | ||
b1baa808 MK |
399 | /** |
400 | * Method getStreamingInterval. | |
401 | * @return long | |
402 | * @see org.eclipse.linuxtools.tmf.core.trace.ITmfTrace#getStreamingInterval() | |
403 | */ | |
a3fc8213 AM |
404 | @Override |
405 | public long getStreamingInterval() { | |
406 | return 0; | |
407 | } | |
408 | ||
b1baa808 MK |
409 | /** |
410 | * Method seekEvent. | |
411 | * @param timestamp ITmfTimestamp | |
412 | * @return ITmfContext | |
413 | * @see org.eclipse.linuxtools.tmf.core.trace.ITmfTrace#seekEvent(ITmfTimestamp) | |
414 | */ | |
a3fc8213 | 415 | @Override |
25e48683 | 416 | public ITmfContext seekEvent(final ITmfTimestamp timestamp) { |
f474d36b PT |
417 | CtfIterator context = new CtfIterator(this); |
418 | context.seek(timestamp.getValue()); | |
419 | context.setRank(ITmfContext.UNKNOWN_RANK); | |
420 | return context; | |
a3fc8213 AM |
421 | } |
422 | ||
423 | /** | |
424 | * Seek by rank | |
b1baa808 MK |
425 | * @param rank long |
426 | * @return ITmfContext | |
427 | * @see org.eclipse.linuxtools.tmf.core.trace.ITmfTrace#seekEvent(long) | |
a3fc8213 AM |
428 | */ |
429 | @Override | |
25e48683 | 430 | public ITmfContext seekEvent(final long rank) { |
f474d36b PT |
431 | CtfIterator context = new CtfIterator(this); |
432 | context.seekRank(rank); | |
433 | context.setRank(rank); | |
434 | return context; | |
a3fc8213 AM |
435 | } |
436 | ||
437 | /** | |
438 | * Seek rank ratio | |
b1baa808 MK |
439 | * @param ratio double |
440 | * @return ITmfContext | |
441 | * @see org.eclipse.linuxtools.tmf.core.trace.ITmfTrace#seekEvent(double) | |
a3fc8213 AM |
442 | */ |
443 | @Override | |
7e6347b0 | 444 | public ITmfContext seekEvent(final double ratio) { |
f474d36b PT |
445 | CtfIterator context = new CtfIterator(this); |
446 | context.seek((long) (this.fNbEvents * ratio)); | |
447 | context.setRank(ITmfContext.UNKNOWN_RANK); | |
448 | return context; | |
a3fc8213 AM |
449 | } |
450 | ||
b1baa808 MK |
451 | /** |
452 | * Method readNextEvent. | |
453 | * @param context ITmfContext | |
454 | * @return CtfTmfEvent | |
455 | * @see org.eclipse.linuxtools.tmf.core.trace.ITmfTrace#readNextEvent(ITmfContext) | |
456 | */ | |
a3fc8213 | 457 | @Override |
b4f71e4a | 458 | public CtfTmfEvent readNextEvent(final ITmfContext context) { |
f474d36b PT |
459 | CtfTmfEvent event = null; |
460 | if (context instanceof CtfIterator) { | |
461 | CtfIterator ctfIterator = (CtfIterator) context; | |
462 | event = ctfIterator.getCurrentEvent(); | |
463 | ctfIterator.advance(); | |
464 | } | |
aa572e22 | 465 | return event; |
a3fc8213 AM |
466 | } |
467 | ||
b1baa808 MK |
468 | /** |
469 | * Method getResource. | |
470 | * @return IResource | |
471 | * @see org.eclipse.linuxtools.tmf.core.trace.ITmfTrace#getResource() | |
472 | */ | |
a3fc8213 AM |
473 | @Override |
474 | public IResource getResource() { | |
475 | return this.fResource; | |
476 | } | |
477 | ||
b1baa808 MK |
478 | /** |
479 | * Method getStateSystem. | |
480 | * @return IStateSystemQuerier | |
481 | */ | |
d26f90fd | 482 | public IStateSystemQuerier getStateSystem() { |
11d6f468 AM |
483 | return this.ss; |
484 | } | |
485 | ||
b1baa808 MK |
486 | /** |
487 | * Method getCTFTrace. | |
488 | * @return CTFTrace | |
489 | */ | |
90235d6b | 490 | CTFTrace getCTFTrace() { |
a3fc8213 AM |
491 | return fTrace; |
492 | } | |
a1a24d68 | 493 | |
8636b448 | 494 | |
d26f90fd AM |
495 | /** |
496 | * Suppressing the warning, because the 'throws' will usually happen in | |
497 | * sub-classes. | |
b1baa808 | 498 | * @throws TmfTraceException |
d26f90fd | 499 | */ |
11d6f468 AM |
500 | protected void buildStateSystem() throws TmfTraceException { |
501 | /* | |
502 | * Nothing is done in the basic implementation, please specify | |
503 | * how/if to build a state system in derived classes. | |
504 | */ | |
505 | return; | |
506 | } | |
ce2388e0 | 507 | |
a3fc8213 | 508 | } |