Improve TmfTrace test coverage
[deliverable/tracecompass.git] / org.eclipse.linuxtools.tmf.core / src / org / eclipse / linuxtools / tmf / core / experiment / TmfExperiment.java
CommitLineData
8c8bf09f 1/*******************************************************************************
0316808c 2 * Copyright (c) 2009, 2010, 2012 Ericsson
ce2388e0 3 *
8c8bf09f
ASL
4 * All rights reserved. This program and the accompanying materials are
5 * made 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
ce2388e0 8 *
8c8bf09f
ASL
9 * Contributors:
10 * Francois Chouinard - Initial API and implementation
0316808c 11 * Francois Chouinard - Updated as per TMF Trace Model 1.0
8c8bf09f
ASL
12 *******************************************************************************/
13
6c13869b 14package org.eclipse.linuxtools.tmf.core.experiment;
8c8bf09f 15
a1091415 16import org.eclipse.core.resources.IFile;
12c155f5 17import org.eclipse.core.resources.IProject;
828e5592 18import org.eclipse.core.resources.IResource;
72f1e62a 19import org.eclipse.linuxtools.tmf.core.event.ITmfEvent;
4df4581d 20import org.eclipse.linuxtools.tmf.core.event.ITmfTimestamp;
6c13869b
FC
21import org.eclipse.linuxtools.tmf.core.event.TmfTimeRange;
22import org.eclipse.linuxtools.tmf.core.event.TmfTimestamp;
0316808c 23import org.eclipse.linuxtools.tmf.core.exceptions.TmfTraceException;
6c13869b
FC
24import org.eclipse.linuxtools.tmf.core.request.ITmfDataRequest;
25import org.eclipse.linuxtools.tmf.core.request.ITmfEventRequest;
1b70b6dc 26import org.eclipse.linuxtools.tmf.core.signal.TmfEndSynchSignal;
6c13869b
FC
27import org.eclipse.linuxtools.tmf.core.signal.TmfExperimentDisposedSignal;
28import org.eclipse.linuxtools.tmf.core.signal.TmfExperimentRangeUpdatedSignal;
29import org.eclipse.linuxtools.tmf.core.signal.TmfExperimentSelectedSignal;
30import org.eclipse.linuxtools.tmf.core.signal.TmfExperimentUpdatedSignal;
31import org.eclipse.linuxtools.tmf.core.signal.TmfSignalHandler;
c32744d6 32import org.eclipse.linuxtools.tmf.core.signal.TmfTraceUpdatedSignal;
6c13869b 33import org.eclipse.linuxtools.tmf.core.trace.ITmfContext;
0316808c 34import org.eclipse.linuxtools.tmf.core.trace.ITmfEventParser;
6c13869b
FC
35import org.eclipse.linuxtools.tmf.core.trace.ITmfLocation;
36import org.eclipse.linuxtools.tmf.core.trace.ITmfTrace;
0316808c
FC
37import org.eclipse.linuxtools.tmf.core.trace.TmfCheckpointIndexer;
38import org.eclipse.linuxtools.tmf.core.trace.TmfTrace;
8c8bf09f
ASL
39
40/**
cbdacf03
FC
41 * TmfExperiment presents a time-ordered, unified view of a set of TmfTraces
42 * that are part of a tracing experiment.
8c8bf09f 43 */
0316808c 44public class TmfExperiment<T extends ITmfEvent> extends TmfTrace<T> implements ITmfEventParser<T> {
8c8bf09f 45
c32744d6
FC
46 // ------------------------------------------------------------------------
47 // Constants
48 // ------------------------------------------------------------------------
49
50 // The index page size
51 private static final int DEFAULT_INDEX_PAGE_SIZE = 5000;
52
8c8bf09f
ASL
53 // ------------------------------------------------------------------------
54 // Attributes
55 // ------------------------------------------------------------------------
56
a79913eb 57 // The currently selected experiment
c32744d6 58 protected static TmfExperiment<?> fCurrentExperiment = null;
e31e01e8 59
a79913eb 60 // The set of traces that constitute the experiment
c32744d6 61 protected ITmfTrace<T>[] fTraces;
8c8bf09f 62
828e5592 63 // Flag to initialize only once
c32744d6 64 protected boolean fInitialized = false;
828e5592 65
a1091415 66 // The experiment bookmarks file
c32744d6 67 protected IFile fBookmarksFile;
a1091415 68
07671572
FC
69// // The properties resource
70// private IResource fResource;
828e5592 71
8c8bf09f
ASL
72 // ------------------------------------------------------------------------
73 // Constructors
74 // ------------------------------------------------------------------------
75
12c155f5 76 @Override
cbdacf03 77 public boolean validate(final IProject project, final String path) {
12c155f5
FC
78 return true;
79 }
80
81 @Override
07671572
FC
82 public void initTrace(final IResource resource, final String path, final Class<T> type) {
83// fResource = resource;
84 try {
85 super.initTrace(resource, path, type);
86 } catch (TmfTraceException e) {
07671572
FC
87 e.printStackTrace();
88 }
96c6806f
PT
89 }
90
8c8bf09f
ASL
91 /**
92 * @param type
93 * @param id
94 * @param traces
95 * @param epoch
96 * @param indexPageSize
0316808c 97 * @throws TmfTraceException
8c8bf09f 98 */
cbdacf03 99 public TmfExperiment(final Class<T> type, final String id, final ITmfTrace<T>[] traces, final ITmfTimestamp epoch,
0316808c
FC
100 final int indexPageSize)
101 {
a4115405 102 this(type, id, traces, TmfTimestamp.ZERO, indexPageSize, false);
a79913eb 103 }
cb866e08 104
0316808c
FC
105 @SuppressWarnings({ "unchecked", "rawtypes" })
106 public TmfExperiment(final Class<T> type, final String path, final ITmfTrace<T>[] traces, final ITmfTimestamp epoch,
107 final int indexPageSize, final boolean preIndexExperiment)
108 {
109 setCacheSize(indexPageSize);
110 setStreamingInterval(0);
07671572 111 setIndexer(new TmfCheckpointIndexer(this, indexPageSize));
0316808c
FC
112 setParser(this);
113 try {
114 super.initialize(null, path, type);
115 } catch (TmfTraceException e) {
116 e.printStackTrace();
117 }
8c8bf09f 118
a79913eb 119 fTraces = traces;
a87cc4ef 120 setTimeRange(TmfTimeRange.NULL_RANGE);
8c8bf09f 121
a87cc4ef
FC
122 if (preIndexExperiment) {
123 getIndexer().buildIndex(true);
a87cc4ef 124 }
a79913eb 125 }
8c8bf09f
ASL
126
127 /**
128 * @param type
129 * @param id
130 * @param traces
0316808c 131 * @throws TmfTraceException
8c8bf09f 132 */
cbdacf03 133 public TmfExperiment(final Class<T> type, final String id, final ITmfTrace<T>[] traces) {
a4115405 134 this(type, id, traces, TmfTimestamp.ZERO, DEFAULT_INDEX_PAGE_SIZE);
8c8bf09f
ASL
135 }
136
137 /**
138 * @param type
139 * @param id
140 * @param traces
141 * @param indexPageSize
0316808c 142 * @throws TmfTraceException
8c8bf09f 143 */
cbdacf03 144 public TmfExperiment(final Class<T> type, final String id, final ITmfTrace<T>[] traces, final int indexPageSize) {
a4115405 145 this(type, id, traces, TmfTimestamp.ZERO, indexPageSize);
8c8bf09f 146 }
a79913eb 147
8c8bf09f 148 /**
ff4ed569 149 * Clears the experiment
8c8bf09f
ASL
150 */
151 @Override
12c155f5 152 @SuppressWarnings("rawtypes")
a79913eb
FC
153 public synchronized void dispose() {
154
cbdacf03 155 final TmfExperimentDisposedSignal<T> signal = new TmfExperimentDisposedSignal<T>(this, this);
a79913eb 156 broadcast(signal);
cbdacf03 157 if (fCurrentExperiment == this)
09d11238 158 fCurrentExperiment = null;
a79913eb
FC
159
160 if (fTraces != null) {
cbdacf03 161 for (final ITmfTrace trace : fTraces)
a79913eb 162 trace.dispose();
a79913eb
FC
163 fTraces = null;
164 }
2fb2eb37 165 super.dispose();
8c8bf09f
ASL
166 }
167
168 // ------------------------------------------------------------------------
e31e01e8 169 // Accessors
8c8bf09f
ASL
170 // ------------------------------------------------------------------------
171
cbdacf03
FC
172 public static void setCurrentExperiment(final TmfExperiment<?> experiment) {
173 if (fCurrentExperiment != null && fCurrentExperiment != experiment)
09d11238 174 fCurrentExperiment.dispose();
a79913eb 175 fCurrentExperiment = experiment;
f6b14ce2
FC
176 }
177
e31e01e8 178 public static TmfExperiment<?> getCurrentExperiment() {
a79913eb 179 return fCurrentExperiment;
8c8bf09f
ASL
180 }
181
12c155f5 182 public ITmfTrace<T>[] getTraces() {
a79913eb 183 return fTraces;
8c8bf09f
ASL
184 }
185
8c8bf09f 186 /**
cbdacf03
FC
187 * Returns the timestamp of the event at the requested index. If none,
188 * returns null.
a79913eb 189 *
0d9a6d76
FC
190 * @param index the event index (rank)
191 * @return the corresponding event timestamp
8c8bf09f 192 */
cbdacf03 193 public ITmfTimestamp getTimestamp(final int index) {
0316808c 194 final ITmfContext context = seekEvent(index);
c32744d6 195 final ITmfEvent event = getNext(context);
a79913eb 196 return (event != null) ? event.getTimestamp() : null;
8c8bf09f
ASL
197 }
198
8c8bf09f
ASL
199 // ------------------------------------------------------------------------
200 // TmfProvider
201 // ------------------------------------------------------------------------
a79913eb 202
a79913eb 203 @Override
cbdacf03 204 public ITmfContext armRequest(final ITmfDataRequest<T> request) {
ce2388e0 205 ITmfTimestamp timestamp = (request instanceof ITmfEventRequest<?>) ? ((ITmfEventRequest<T>) request).getRange().getStartTime() : null;
07671572
FC
206 if (TmfTimestamp.BIG_BANG.equals(timestamp) || request.getIndex() > 0) {
207 timestamp = null;
208 }
209
0316808c 210 ITmfContext context = null;
07671572 211 if (timestamp != null) { // Seek by timestamp
a79913eb
FC
212 context = seekEvent(timestamp);
213 ((ITmfEventRequest<T>) request).setStartIndex((int) context.getRank());
07671572
FC
214 } else { // Seek by rank
215 context = seekEvent(request.getIndex());
216 }
217
a79913eb
FC
218 return context;
219 }
220
a79913eb 221 // ------------------------------------------------------------------------
9f584e4c
FC
222 // ITmfTrace trace positioning
223 // ------------------------------------------------------------------------
224
a79913eb
FC
225 // Returns a brand new context based on the location provided
226 // and initializes the event queues
227 @Override
7e6347b0 228 public synchronized TmfExperimentContext seekEvent(final ITmfLocation<?> location) {
a79913eb 229 // Validate the location
cbdacf03 230 if (location != null && !(location instanceof TmfExperimentLocation))
a79913eb 231 return null; // Throw an exception?
8f50c396 232
cbdacf03 233 if (fTraces == null)
a79913eb 234 return null;
8f50c396 235
a79913eb 236 // Instantiate the location
cbdacf03 237 final TmfExperimentLocation expLocation = (location == null) ? new TmfExperimentLocation(new TmfLocationArray(
0316808c 238 new ITmfLocation<?>[fTraces.length])) : (TmfExperimentLocation) location.clone();
8f50c396 239
a79913eb 240 // Create and populate the context's traces contexts
0316808c 241 final TmfExperimentContext context = new TmfExperimentContext(new ITmfContext[fTraces.length]);
9b635e61 242
a79913eb
FC
243 for (int i = 0; i < fTraces.length; i++) {
244 // Get the relevant trace attributes
0316808c 245 final ITmfLocation<?> traceLocation = expLocation.getLocation().getLocations()[i];
7e6347b0 246 context.getContexts()[i] = fTraces[i].seekEvent(traceLocation);
0316808c 247 expLocation.getLocation().getLocations()[i] = context.getContexts()[i].getLocation().clone();
c32744d6 248 context.getEvents()[i] = fTraces[i].getNext(context.getContexts()[i]);
a79913eb 249 }
8f50c396 250
a79913eb
FC
251 // Finalize context
252 context.setLocation(expLocation);
253 context.setLastTrace(TmfExperimentContext.NO_TRACE);
0316808c 254 context.setRank(ITmfContext.UNKNOWN_RANK);
a79913eb
FC
255 return context;
256 }
9f584e4c 257
c76c54bb 258 @Override
0316808c
FC
259 public ITmfContext seekEvent(final double ratio) {
260 final ITmfContext context = seekEvent((long) (ratio * getNbEvents()));
c76c54bb
FC
261 return context;
262 }
263
a79913eb 264 @Override
cbdacf03
FC
265 public double getLocationRatio(final ITmfLocation<?> location) {
266 if (location instanceof TmfExperimentLocation)
7e6347b0 267 return (double) seekEvent(location).getRank() / getNbEvents();
c76c54bb
FC
268 return 0;
269 }
270
a79913eb
FC
271 @Override
272 public ITmfLocation<?> getCurrentLocation() {
a87cc4ef
FC
273 ITmfLocation<?>[] locations = new ITmfLocation<?>[fTraces.length];
274 for (int i = 0; i < fTraces.length; i++) {
275 locations[i] = fTraces[i].getCurrentLocation();
276 }
277 return new TmfExperimentLocation(new TmfLocationArray(locations));
a79913eb 278 }
c76c54bb 279
07671572
FC
280 /* (non-Javadoc)
281 * @see org.eclipse.linuxtools.tmf.core.trace.ITmfTrace#readNextEvent(org.eclipse.linuxtools.tmf.core.trace.ITmfContext)
282 */
283 @Override
c32744d6 284 public synchronized T getNext(final ITmfContext context) {
a0a4901e 285 final ITmfContext previousContext = (TmfExperimentContext) context.clone();
c32744d6 286 final T event = parseEvent(context);
07671572 287 if (event != null) {
a0a4901e 288 updateAttributes(previousContext, event.getTimestamp());
07671572
FC
289
290 TmfExperimentContext expContext = (TmfExperimentContext) context;
291 int trace = expContext.getLastTrace();
292 if (trace != TmfExperimentContext.NO_TRACE) {
293 TmfExperimentLocation location = (TmfExperimentLocation) expContext.getLocation();
294 location.getLocation().getLocations()[trace] = expContext.getContexts()[trace].getLocation();
295 }
296
297 context.increaseRank();
298 processEvent(event);
299 }
300 return event;
301 }
a79913eb 302
ce2388e0
FC
303 /* (non-Javadoc)
304 * @see org.eclipse.linuxtools.tmf.trace.ITmfTrace#parseEvent(org.eclipse.linuxtools .tmf.trace.TmfContext)
a79913eb 305 */
0316808c
FC
306 @SuppressWarnings("unchecked")
307 @Override
a87cc4ef 308 public T parseEvent(ITmfContext context) {
a79913eb
FC
309
310 // Validate the context
cbdacf03 311 if (!(context instanceof TmfExperimentContext))
a79913eb 312 return null; // Throw an exception?
a79913eb 313
a87cc4ef 314 TmfExperimentContext expContext = (TmfExperimentContext) context;
a79913eb 315
a87cc4ef 316 // If an event was consumed previously, first get the next one from that trace
cbdacf03 317 final int lastTrace = expContext.getLastTrace();
a79913eb 318 if (lastTrace != TmfExperimentContext.NO_TRACE) {
cbdacf03 319 final ITmfContext traceContext = expContext.getContexts()[lastTrace];
c32744d6 320 expContext.getEvents()[lastTrace] = fTraces[lastTrace].getNext(traceContext);
a79913eb 321 expContext.setLastTrace(TmfExperimentContext.NO_TRACE);
a79913eb
FC
322 }
323
324 // Scan the candidate events and identify the "next" trace to read from
325 int trace = TmfExperimentContext.NO_TRACE;
a4115405 326 ITmfTimestamp timestamp = TmfTimestamp.BIG_CRUNCH;
0316808c 327 for (int i = 0; i < fTraces.length; i++) {
cbdacf03 328 final ITmfEvent event = expContext.getEvents()[i];
a79913eb 329 if (event != null && event.getTimestamp() != null) {
cbdacf03 330 final ITmfTimestamp otherTS = event.getTimestamp();
a79913eb
FC
331 if (otherTS.compareTo(timestamp, true) < 0) {
332 trace = i;
333 timestamp = otherTS;
334 }
335 }
336 }
a87cc4ef
FC
337
338 T event = null;
07671572 339 if (trace != TmfExperimentContext.NO_TRACE) {
a87cc4ef 340 event = (T) expContext.getEvents()[trace];
07671572 341 }
a87cc4ef 342
a87cc4ef
FC
343 expContext.setLastTrace(trace);
344 return event;
a79913eb
FC
345 }
346
bcbea6a6 347 /* (non-Javadoc)
a79913eb
FC
348 * @see java.lang.Object#toString()
349 */
350 @Override
3b38ea61 351 @SuppressWarnings("nls")
a79913eb
FC
352 public String toString() {
353 return "[TmfExperiment (" + getName() + ")]";
354 }
8c8bf09f
ASL
355
356 // ------------------------------------------------------------------------
357 // Indexing
358 // ------------------------------------------------------------------------
359
1b70b6dc 360 private synchronized void initializeStreamingMonitor() {
cbdacf03 361 if (fInitialized)
828e5592 362 return;
828e5592
PT
363 fInitialized = true;
364
1b70b6dc 365 if (getStreamingInterval() == 0) {
0316808c 366 final ITmfContext context = seekEvent(0);
cbdacf03
FC
367 final ITmfEvent event = getNext(context);
368 if (event == null)
1b70b6dc 369 return;
cbdacf03 370 final TmfTimeRange timeRange = new TmfTimeRange(event.getTimestamp().clone(), TmfTimestamp.BIG_CRUNCH);
828e5592
PT
371 final TmfExperimentRangeUpdatedSignal signal = new TmfExperimentRangeUpdatedSignal(this, this, timeRange);
372
373 // Broadcast in separate thread to prevent deadlock
374 new Thread() {
375 @Override
376 public void run() {
377 broadcast(signal);
378 }
379 }.start();
1b70b6dc
PT
380 return;
381 }
382
bcbea6a6
FC
383 final Thread thread = new Thread("Streaming Monitor for experiment " + getName()) { ////$NON-NLS-1$
384 private ITmfTimestamp safeTimestamp = null;
385 private TmfTimeRange timeRange = null;
1b70b6dc
PT
386
387 @Override
388 public void run() {
389 while (!fExecutor.isShutdown()) {
390 if (!isIndexingBusy()) {
a4115405
FC
391 ITmfTimestamp startTimestamp = TmfTimestamp.BIG_CRUNCH;
392 ITmfTimestamp endTimestamp = TmfTimestamp.BIG_BANG;
cbdacf03
FC
393 for (final ITmfTrace<T> trace : fTraces) {
394 if (trace.getStartTime().compareTo(startTimestamp) < 0)
1b70b6dc 395 startTimestamp = trace.getStartTime();
cbdacf03 396 if (trace.getStreamingInterval() != 0 && trace.getEndTime().compareTo(endTimestamp) > 0)
1b70b6dc 397 endTimestamp = trace.getEndTime();
1b70b6dc 398 }
cbdacf03 399 if (safeTimestamp != null && safeTimestamp.compareTo(getTimeRange().getEndTime(), false) > 0)
1b70b6dc 400 timeRange = new TmfTimeRange(startTimestamp, safeTimestamp);
cbdacf03 401 else
1b70b6dc 402 timeRange = null;
1b70b6dc
PT
403 safeTimestamp = endTimestamp;
404 if (timeRange != null) {
cbdacf03 405 final TmfExperimentRangeUpdatedSignal signal =
1b70b6dc
PT
406 new TmfExperimentRangeUpdatedSignal(TmfExperiment.this, TmfExperiment.this, timeRange);
407 broadcast(signal);
408 }
409 }
410 try {
411 Thread.sleep(getStreamingInterval());
cbdacf03 412 } catch (final InterruptedException e) {
1b70b6dc
PT
413 e.printStackTrace();
414 }
415 }
416 }
417 };
418 thread.start();
419 }
420
cbdacf03
FC
421 /*
422 * (non-Javadoc)
423 *
1b70b6dc
PT
424 * @see org.eclipse.linuxtools.tmf.trace.ITmfTrace#getStreamingInterval()
425 */
426 @Override
427 public long getStreamingInterval() {
428 long interval = 0;
cbdacf03 429 for (final ITmfTrace<T> trace : fTraces)
1b70b6dc 430 interval = Math.max(interval, trace.getStreamingInterval());
1b70b6dc
PT
431 return interval;
432 }
433
a79913eb 434 /*
cbdacf03
FC
435 * The experiment holds the globally ordered events of its set of traces. It
436 * is expected to provide access to each individual event by index i.e. it
437 * must be possible to request the Nth event of the experiment.
a79913eb 438 *
cbdacf03
FC
439 * The purpose of the index is to keep the information needed to rapidly
440 * restore the traces contexts at regular intervals (every INDEX_PAGE_SIZE
441 * event).
a79913eb 442 */
8c8bf09f 443
a87cc4ef 444// protected int fIndexPageSize;
a79913eb 445 protected boolean fIndexing = false;
c32744d6 446// protected TmfTimeRange fIndexingPendingRange = TmfTimeRange.NULL_RANGE;
e31e01e8 447
1b70b6dc
PT
448 private Integer fEndSynchReference;
449
a79913eb 450 protected boolean isIndexingBusy() {
07671572 451 return fIndexing;
a79913eb
FC
452 }
453
a79913eb 454
c32744d6
FC
455// protected void notifyListeners() {
456// broadcast(new TmfExperimentUpdatedSignal(this, this));
457// }
a79913eb 458
8c8bf09f
ASL
459 // ------------------------------------------------------------------------
460 // Signal handlers
461 // ------------------------------------------------------------------------
462
c32744d6
FC
463 @TmfSignalHandler
464 public void traceUpdated(final TmfTraceUpdatedSignal signal) {
465 if (signal.getTrace() == this) {
466 broadcast(new TmfExperimentUpdatedSignal(this, this));
467 }
468 }
469
8c8bf09f 470 @TmfSignalHandler
cbdacf03
FC
471 public void experimentSelected(final TmfExperimentSelectedSignal<T> signal) {
472 final TmfExperiment<?> experiment = signal.getExperiment();
a79913eb
FC
473 if (experiment == this) {
474 setCurrentExperiment(experiment);
6e85c58d 475 fEndSynchReference = Integer.valueOf(signal.getReference());
a79913eb 476 }
8c8bf09f
ASL
477 }
478
1b70b6dc 479 @TmfSignalHandler
cbdacf03 480 public void endSync(final TmfEndSynchSignal signal) {
1b70b6dc
PT
481 if (fEndSynchReference != null && fEndSynchReference.intValue() == signal.getReference()) {
482 fEndSynchReference = null;
483 initializeStreamingMonitor();
484 }
1b70b6dc
PT
485 }
486
8c8bf09f 487 @TmfSignalHandler
cbdacf03 488 public void experimentUpdated(final TmfExperimentUpdatedSignal signal) {
8c8bf09f
ASL
489 }
490
1b70b6dc 491 @TmfSignalHandler
cbdacf03 492 public void experimentRangeUpdated(final TmfExperimentRangeUpdatedSignal signal) {
0316808c 493 if (signal.getExperiment() == this) {
0316808c
FC
494 getIndexer().buildIndex(false);
495 }
1b70b6dc
PT
496 }
497
12c155f5
FC
498 @Override
499 public String getPath() {
500 // TODO Auto-generated method stub
501 return null;
502 }
503
828e5592 504 /**
a1091415 505 * Set the file to be used for bookmarks on this experiment
cbdacf03 506 *
a1091415 507 * @param file the bookmarks file
828e5592 508 */
cbdacf03 509 public void setBookmarksFile(final IFile file) {
a1091415
PT
510 fBookmarksFile = file;
511 }
512
513 /**
514 * Get the file used for bookmarks on this experiment
cbdacf03 515 *
a1091415
PT
516 * @return the bookmarks file or null if none is set
517 */
518 public IFile getBookmarksFile() {
519 return fBookmarksFile;
520 }
521
07671572
FC
522// /*
523// * (non-Javadoc)
524// *
525// * @see org.eclipse.linuxtools.tmf.core.trace.ITmfTrace#getResource()
526// */
527// @Override
528// public IResource getResource() {
529// return fResource;
530// }
4dc47e28 531}
This page took 0.077989 seconds and 5 git commands to generate.