lttng: add names of traces in sync algorithm stats
[deliverable/tracecompass.git] / tmf / org.eclipse.tracecompass.tmf.core / src / org / eclipse / tracecompass / internal / tmf / core / synchronization / SyncAlgorithmFullyIncremental.java
CommitLineData
51c08015 1/*******************************************************************************
ed902a2b 2 * Copyright (c) 2013, 2014 École Polytechnique de Montréal
51c08015
GB
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:
10 * Geneviève Bastien - Initial implementation and API
11 * Francis Giraldeau - Transform computation using synchronization graph
12 *******************************************************************************/
13
2bdf0193 14package org.eclipse.tracecompass.internal.tmf.core.synchronization;
51c08015
GB
15
16import java.io.IOException;
a1ff9910 17import java.io.ObjectInputStream;
51c08015
GB
18import java.io.Serializable;
19import java.math.BigDecimal;
20import java.math.MathContext;
21import java.util.Collection;
22import java.util.LinkedHashMap;
23import java.util.LinkedList;
24import java.util.List;
25import java.util.Map;
26
5dca27ae 27import org.eclipse.tracecompass.common.core.NonNullUtils;
2bdf0193
AM
28import org.eclipse.tracecompass.internal.tmf.core.synchronization.graph.SyncSpanningTree;
29import org.eclipse.tracecompass.tmf.core.event.ITmfEvent;
30import org.eclipse.tracecompass.tmf.core.event.matching.TmfEventDependency;
31import org.eclipse.tracecompass.tmf.core.synchronization.ITmfTimestampTransform;
32import org.eclipse.tracecompass.tmf.core.synchronization.Messages;
33import org.eclipse.tracecompass.tmf.core.synchronization.SynchronizationAlgorithm;
34import org.eclipse.tracecompass.tmf.core.synchronization.TimestampTransformFactory;
35import org.eclipse.tracecompass.tmf.core.timestamp.ITmfTimestamp;
36import org.eclipse.tracecompass.tmf.core.trace.ITmfTrace;
51c08015
GB
37
38/**
39 * Class implementing fully incremental trace synchronization approach as
40 * described in
41 *
42 * Masoume Jabbarifar, Michel Dagenais and Alireza Shameli-Sendi,
43 * "Streaming Mode Incremental Clock Synchronization"
44 *
45 * Since the algorithm itself applies to two traces, it is implemented in a
46 * private class, while this public class manages the synchronization between
47 * all traces.
48 *
49 * @author Geneviève Bastien
50 */
51public class SyncAlgorithmFullyIncremental extends SynchronizationAlgorithm {
52
53 /**
54 * Auto-generated serial UID
55 */
56 private static final long serialVersionUID = -1782788842774838830L;
57
58 private static final MathContext fMc = MathContext.DECIMAL128;
59
60 /** @Serial */
61 private final List<ConvexHull> fSyncs;
62
a1ff9910 63 private transient SyncSpanningTree fTree = null;
51c08015
GB
64
65 /**
66 * Initialization of the attributes
67 */
68 public SyncAlgorithmFullyIncremental() {
69 fSyncs = new LinkedList<>();
70 }
71
72 /**
73 * Function called after all matching has been done, to do any post-match
74 * treatment. For this class, it calculates stats, while the data is
75 * available
76 */
77 @Override
78 public void matchingEnded() {
79 getStats();
80 }
81
82 @Override
83 public void init(Collection<ITmfTrace> traces) {
84 ITmfTrace[] traceArr = traces.toArray(new ITmfTrace[traces.size()]);
85 fSyncs.clear();
86 /* Create a convex hull for all trace pairs */
87 // FIXME: is it necessary to make ConvexHull for every pairs up-front?
88 // The ConvexHull seems to be created on the fly in processMatch().
89 for (int i = 0; i < traceArr.length; i++) {
90 for (int j = i + 1; j < traceArr.length; j++) {
2c2fcd6b 91 if (!traceArr[i].getHostId().equals(traceArr[j].getHostId())) {
649c89d0 92 ConvexHull algo = new ConvexHull(traceArr[i], traceArr[j]);
2c2fcd6b
GB
93 fSyncs.add(algo);
94 }
51c08015
GB
95 }
96 }
97 }
98
99 @Override
100 protected void processMatch(TmfEventDependency match) {
649c89d0
CB
101 ITmfTrace trace1 = match.getSourceEvent().getTrace();
102 ITmfTrace trace2 = match.getDestinationEvent().getTrace();
103 String host1 = trace1.getHostId();
104 String host2 = trace2.getHostId();
51c08015
GB
105
106 /* Process only if source and destination are different */
107 if (host1.equals(host2)) {
108 return;
109 }
110
111 /* Check if a convex hull algorithm already exists for these 2 hosts */
112 ConvexHull algo = null;
113 for (ConvexHull traceSync : fSyncs) {
114 if (traceSync.isForHosts(host1, host2)) {
115 algo = traceSync;
116 }
117 }
118 if (algo == null) {
649c89d0 119 algo = new ConvexHull(trace1, trace2);
51c08015
GB
120 fSyncs.add(algo);
121 }
122 algo.processMatch(match);
123 invalidateSyncGraph();
124 }
125
126 private void invalidateSyncGraph() {
127 fTree = null;
128 }
129
130 @Override
131 public ITmfTimestampTransform getTimestampTransform(ITmfTrace trace) {
132 return getTimestampTransform(trace.getHostId());
133 }
134
135 @Override
136 public ITmfTimestampTransform getTimestampTransform(String hostId) {
137 SyncSpanningTree tree = getSyncTree();
138 return tree.getTimestampTransform(hostId);
139 }
140
141 /**
142 * Each convex hull computes the synchronization between 2 given hosts. A
143 * synchronization can be done on multiple hosts that may not all
144 * communicate with each other. We must use another algorithm to determine
145 * which host will be the reference node and what synchronization formula
146 * will be used between each host and this reference node.
147 *
148 * For example, take traces a, b and c where a and c talk to b but do not
149 * know each other ({@literal a <-> b <-> c}). The convex hulls will contain
150 * the formulae between their 2 traces, but if a is the reference node, then
151 * the resulting formula of c would be the composition of {@literal a <-> b}
152 * and {@literal b <-> c}
153 *
154 * @return The synchronization spanning tree for this synchronization
155 */
156 private SyncSpanningTree getSyncTree() {
157 if (fTree == null) {
dc62dbee 158 fTree = new SyncSpanningTree(getRootNode());
51c08015
GB
159 for (ConvexHull traceSync : fSyncs) {
160 SyncQuality q = traceSync.getQuality();
161 if (q == SyncQuality.ACCURATE || q == SyncQuality.APPROXIMATE) {
162 String from = traceSync.getReferenceHost();
163 String to = traceSync.getOtherHost();
164 fTree.addSynchronization(from, to, traceSync.getTimestampTransform(to), traceSync.getAccuracy());
165 }
166 }
167 }
168 return fTree;
169 }
170
171 @Override
172 public SyncQuality getSynchronizationQuality(ITmfTrace trace1, ITmfTrace trace2) {
173 for (ConvexHull traceSync : fSyncs) {
174 if (traceSync.isForHosts(trace1.getHostId(), trace2.getHostId())) {
175 return traceSync.getQuality();
176 }
177 }
178 return SyncQuality.ABSENT;
179 }
180
181 @Override
182 public boolean isTraceSynced(String hostId) {
183 ITmfTimestampTransform t = getTimestampTransform(hostId);
184 return !t.equals(TimestampTransformFactory.getDefaultTransform());
185 }
186
187 @Override
188 public Map<String, Map<String, Object>> getStats() {
189 /*
190 * TODO: Stats, while still accurate, may be misleading now that the
191 * sync tree changes synchronization formula. The stats should use the
192 * tree instead
193 */
194 Map<String, Map<String, Object>> statmap = new LinkedHashMap<>();
195 for (ConvexHull traceSync : fSyncs) {
196 statmap.put(traceSync.getReferenceHost() + " <==> " + traceSync.getOtherHost(), traceSync.getStats()); //$NON-NLS-1$
197 }
198 return statmap;
199 }
200
201 @Override
202 public String toString() {
a83a4189 203 return getClass().getSimpleName() + ' ' + fSyncs.toString();
51c08015
GB
204 }
205
206 /**
207 * This is the actual synchronization algorithm between two traces using
208 * convex hull
209 */
210 private class ConvexHull implements Serializable {
211
212 private static final long serialVersionUID = 8309351175030935291L;
213
a1ff9910 214 private final String fReferenceHost;
649c89d0 215 private final String fReferenceHostName;
a1ff9910 216 private final String fOtherHost;
649c89d0 217 private final String fOtherHostName;
a1ff9910
GB
218
219 /**
220 * Slopes and ordinate at origin of respectively fLmin, fLmax and the
221 * bisector
222 */
223 private BigDecimal fAlphamin, fBetamax, fAlphamax, fBetamin, fAlpha, fBeta;
224 private int fNbMatches, fNbAccurateMatches;
225 private SyncQuality fQuality;
226
51c08015
GB
227 /**
228 * The list of meaningful points on the upper hull (received by the
229 * reference trace, below in a graph)
230 */
a1ff9910 231 private transient LinkedList<SyncPoint> fUpperBoundList = new LinkedList<>();
51c08015
GB
232 /**
233 * The list of meaninful points on the lower hull (sent by the reference
234 * trace, above in a graph)
235 */
a1ff9910 236 private transient LinkedList<SyncPoint> fLowerBoundList = new LinkedList<>();
51c08015
GB
237
238 /** Points forming the line with maximum slope */
a1ff9910 239 private transient SyncPoint[] fLmax = new SyncPoint[2];
51c08015 240 /** Points forming the line with minimum slope */
a1ff9910 241 private transient SyncPoint[] fLmin = new SyncPoint[2];
51c08015 242
a1ff9910 243 private transient Map<String, Object> fStats = new LinkedHashMap<>();
51c08015
GB
244
245 /**
246 * Initialization of the attributes
247 *
649c89d0
CB
248 * @param trace1
249 * Trace of the first host
250 * @param trace2
251 * Trace of the second host
51c08015 252 */
649c89d0
CB
253 public ConvexHull(ITmfTrace trace1, ITmfTrace trace2) {
254 String host1 = trace1.getHostId();
255 String host2 = trace2.getHostId();
51c08015
GB
256 if (host1.compareTo(host2) > 0) {
257 fReferenceHost = host2;
649c89d0 258 fReferenceHostName = trace2.getName();
51c08015 259 fOtherHost = host1;
649c89d0 260 fOtherHostName = trace1.getName();
51c08015
GB
261 } else {
262 fReferenceHost = host1;
649c89d0 263 fReferenceHostName = trace1.getName();
51c08015 264 fOtherHost = host2;
649c89d0 265 fOtherHostName = trace2.getName();
51c08015 266 }
51c08015
GB
267 fAlpha = BigDecimal.ONE;
268 fAlphamax = BigDecimal.ONE;
269 fAlphamin = BigDecimal.ONE;
270 fBeta = BigDecimal.ZERO;
271 fBetamax = BigDecimal.ZERO;
272 fBetamin = BigDecimal.ZERO;
273 fNbMatches = 0;
274 fNbAccurateMatches = 0;
275 fQuality = SyncQuality.ABSENT; // default quality
276 }
277
278 protected void processMatch(TmfEventDependency match) {
279
280 LinkedList<SyncPoint> boundList, otherBoundList;
281
282 SyncPoint[] line, otherLine;
283 SyncPoint p;
284 int inversionFactor = 1;
285 boolean qualify = false;
286 fNbMatches++;
287
288 /* Initialize data depending on the which hull the match is part of */
289 if (match.getSourceEvent().getTrace().getHostId().compareTo(match.getDestinationEvent().getTrace().getHostId()) > 0) {
290 boundList = fUpperBoundList;
291 otherBoundList = fLowerBoundList;
292 line = fLmin;
293 otherLine = fLmax;
294 p = new SyncPoint(match.getDestinationEvent(), match.getSourceEvent());
295 inversionFactor = 1;
296 } else {
297 boundList = fLowerBoundList;
298 otherBoundList = fUpperBoundList;
299 line = fLmax;
300 otherLine = fLmin;
301 p = new SyncPoint(match.getSourceEvent(), match.getDestinationEvent());
302 inversionFactor = -1;
303 }
304
305 /*
306 * Does the message qualify for the hull, or is in on the wrong side
307 * of the reference line
308 */
309 if ((line[0] == null) || (line[1] == null) || (p.crossProduct(line[0], line[1]) * inversionFactor > 0)) {
310 /*
311 * If message qualifies, verify if points need to be removed
312 * from the hull and add the new point as the maximum reference
313 * point for the line. Also clear the stats that are not good
314 * anymore
315 */
316 fNbAccurateMatches++;
317 qualify = true;
318 removeUselessPoints(p, boundList, inversionFactor);
319 line[1] = p;
320 fStats.clear();
321 }
322
323 /*
324 * Adjust the boundary of the reference line and if one of the
325 * reference point of the other line was removed from the hull, also
326 * adjust the other line
327 */
328 adjustBound(line, otherBoundList, inversionFactor);
329 if ((otherLine[1] != null) && !boundList.contains(otherLine[0])) {
330 adjustBound(otherLine, boundList, inversionFactor * -1);
331 }
332
333 if (qualify) {
334 approximateSync();
335 }
336
337 }
338
339 /**
340 * Calculates slopes and ordinate at origin of fLmax and fLmin to obtain
341 * and approximation of the synchronization at this time
342 */
343 private void approximateSync() {
344
345 /**
346 * Line slopes functions
347 *
348 * Lmax = alpha_max T + beta_min
349 *
350 * Lmin = alpha_min T + beta_max
351 */
352 if ((fLmax[0] != null) || (fLmin[0] != null)) {
353 /**
354 * Do not recalculate synchronization after it is failed. We
355 * keep the last not failed result.
356 */
357 if (getQuality() != SyncQuality.FAIL) {
358 BigDecimal alphamax = fLmax[1].getAlpha(fLmax[0]);
359 BigDecimal alphamin = fLmin[1].getAlpha(fLmin[0]);
360 SyncQuality quality = null;
361
362 if ((fLmax[0] == null) || (fLmin[0] == null)) {
363 quality = SyncQuality.APPROXIMATE;
364 }
365 else if (alphamax.compareTo(alphamin) > 0) {
366 quality = SyncQuality.ACCURATE;
367 } else {
368 /* Lines intersect, not good */
369 quality = SyncQuality.FAIL;
370 }
371 /*
372 * Only calculate sync if this match does not cause failure
373 * of synchronization
374 */
375 if (quality != SyncQuality.FAIL) {
376 fAlphamax = alphamax;
377 fBetamin = fLmax[1].getBeta(fAlphamax);
378 fAlphamin = alphamin;
379 fBetamax = fLmin[1].getBeta(fAlphamin);
380 fAlpha = fAlphamax.add(fAlphamin).divide(BigDecimal.valueOf(2), fMc);
381 fBeta = fBetamin.add(fBetamax).divide(BigDecimal.valueOf(2), fMc);
382 }
383 setQuality(quality);
384 }
385 } else if (((fLmax[0] == null) && (fLmin[1] == null))
386 || ((fLmax[1] == null) && (fLmin[0] == null))) {
387 /* Either there is no upper hull point or no lower hull */
388 setQuality(SyncQuality.INCOMPLETE);
389 }
390 }
391
392 /*
393 * Verify if the line should be adjusted to be more accurate give the
394 * hull
395 */
396 private void adjustBound(SyncPoint[] line, LinkedList<SyncPoint> otherBoundList, int inversionFactor) {
397 SyncPoint minPoint = null, nextPoint;
398 boolean finishedSearch = false;
399
400 /*
401 * Find in the other bound, the origin point of the line, start from
402 * the beginning if the point was lost
403 */
404 int i = Math.max(0, otherBoundList.indexOf(line[0]));
405
406 while ((i < otherBoundList.size() - 1) && !finishedSearch) {
407 minPoint = otherBoundList.get(i);
408 nextPoint = otherBoundList.get(i + 1);
409
410 /*
411 * If the rotation (cross-product) is not optimal, move to next
412 * point as reference for the line (if available)
413 *
414 * Otherwise, the current minPoint is the minPoint of the line
415 */
416 if (minPoint.crossProduct(nextPoint, line[1]) * inversionFactor > 0) {
417 if (nextPoint.getTimeX() < line[1].getTimeX()) {
418 i++;
419 } else {
420 line[0] = null;
421 finishedSearch = true;
422 }
423 } else {
424 line[0] = minPoint;
425 finishedSearch = true;
426 }
427 }
428
429 if (line[0] == null) {
430 line[0] = minPoint;
431 }
432
433 /* Make sure point 0 is before point 1 */
434 if ((line[0] != null) && (line[0].getTimeX() > line[1].getTimeX())) {
435 line[0] = null;
436 }
437 }
438
439 /*
440 * When a point qualifies to be in a hull, we verify if any of the
441 * existing points need to be removed from the hull
442 */
443 private void removeUselessPoints(final SyncPoint p, final LinkedList<SyncPoint> boundList, final int inversionFactor) {
444
445 boolean checkRemove = true;
446
447 while (checkRemove && boundList.size() >= 2) {
448 if (p.crossProduct(boundList.get(boundList.size() - 2), boundList.getLast()) * inversionFactor > 0) {
449 boundList.removeLast();
450 } else {
451 checkRemove = false;
452 }
453 }
454 boundList.addLast(p);
455 }
456
457 public ITmfTimestampTransform getTimestampTransform(String hostId) {
458 if (hostId.equals(fOtherHost) && (getQuality() == SyncQuality.ACCURATE || getQuality() == SyncQuality.APPROXIMATE || getQuality() == SyncQuality.FAIL)) {
459 /* alpha: beta => 1 / fAlpha, -1 * fBeta / fAlpha); */
5dca27ae 460 return TimestampTransformFactory.createLinear(NonNullUtils.checkNotNull(BigDecimal.ONE.divide(fAlpha, fMc)), NonNullUtils.checkNotNull(BigDecimal.valueOf(-1).multiply(fBeta).divide(fAlpha, fMc)));
51c08015
GB
461 }
462 return TimestampTransformFactory.getDefaultTransform();
463 }
464
465 public SyncQuality getQuality() {
466 return fQuality;
467 }
468
469 public BigDecimal getAccuracy() {
470 return fAlphamax.subtract(fAlphamin);
471 }
472
473 public Map<String, Object> getStats() {
474 if (fStats.size() == 0) {
475 String syncQuality;
476 switch (getQuality()) {
477 case ABSENT:
478 syncQuality = Messages.SyncAlgorithmFullyIncremental_absent;
479 break;
480 case ACCURATE:
481 syncQuality = Messages.SyncAlgorithmFullyIncremental_accurate;
482 break;
483 case APPROXIMATE:
484 syncQuality = Messages.SyncAlgorithmFullyIncremental_approx;
485 break;
486 case INCOMPLETE:
487 syncQuality = Messages.SyncAlgorithmFullyIncremental_incomplete;
488 break;
489 case FAIL:
490 default:
491 syncQuality = Messages.SyncAlgorithmFullyIncremental_fail;
492 break;
493 }
494
649c89d0
CB
495 fStats.put(Messages.SyncAlgorithmFullyIncremental_refhost, fReferenceHostName + " (" + fReferenceHost + ")");
496 fStats.put(Messages.SyncAlgorithmFullyIncremental_otherhost, fOtherHostName + " (" + fOtherHost + ")");
51c08015
GB
497 fStats.put(Messages.SyncAlgorithmFullyIncremental_quality, syncQuality);
498 fStats.put(Messages.SyncAlgorithmFullyIncremental_alpha, fAlpha);
499 fStats.put(Messages.SyncAlgorithmFullyIncremental_beta, fBeta);
500 fStats.put(Messages.SyncAlgorithmFullyIncremental_ub, (fUpperBoundList.size() == 0) ? Messages.SyncAlgorithmFullyIncremental_NA : fUpperBoundList.size());
501 fStats.put(Messages.SyncAlgorithmFullyIncremental_lb, (fLowerBoundList.size() == 0) ? Messages.SyncAlgorithmFullyIncremental_NA : fLowerBoundList.size());
502 fStats.put(Messages.SyncAlgorithmFullyIncremental_accuracy, getAccuracy().doubleValue());
503 fStats.put(Messages.SyncAlgorithmFullyIncremental_nbmatch, (fNbMatches == 0) ? Messages.SyncAlgorithmFullyIncremental_NA : fNbMatches);
504 fStats.put(Messages.SyncAlgorithmFullyIncremental_nbacc, (fNbAccurateMatches == 0) ? Messages.SyncAlgorithmFullyIncremental_NA : fNbAccurateMatches);
505 fStats.put(Messages.SyncAlgorithmFullyIncremental_refformula, Messages.SyncAlgorithmFullyIncremental_T_ + fReferenceHost);
506 fStats.put(Messages.SyncAlgorithmFullyIncremental_otherformula, fAlpha + Messages.SyncAlgorithmFullyIncremental_mult + Messages.SyncAlgorithmFullyIncremental_T_ + fReferenceHost + Messages.SyncAlgorithmFullyIncremental_add + fBeta);
507 }
508 return fStats;
509
510 }
511
512 public String getReferenceHost() {
513 return fReferenceHost;
514 }
515
516 public String getOtherHost() {
517 return fOtherHost;
518 }
519
520 public boolean isForHosts(String hostId1, String hostId2) {
521 return ((fReferenceHost.equals(hostId1) && fOtherHost.equals(hostId2)) || (fReferenceHost.equals(hostId2) && fOtherHost.equals(hostId1)));
522 }
523
a1ff9910
GB
524 private void readObject(ObjectInputStream stream)
525 throws IOException, ClassNotFoundException {
526 stream.defaultReadObject();
527
528 /* Initialize transient fields */
529 fUpperBoundList = new LinkedList<>();
530 fLowerBoundList = new LinkedList<>();
531 fLmax = new SyncPoint[2];
532 fLmin = new SyncPoint[2];
533 fStats = new LinkedHashMap<>();
51c08015
GB
534 }
535
536 @SuppressWarnings("nls")
537 @Override
538 public String toString() {
539 StringBuilder b = new StringBuilder();
540 b.append("Between " + fReferenceHost + " and " + fOtherHost + " [");
541 b.append(" alpha " + fAlpha + " beta " + fBeta + " ]");
542 return b.toString();
543 }
544
545 private void setQuality(SyncQuality fQuality) {
546 this.fQuality = fQuality;
547 }
548
549 }
550
551 /**
552 * Private class representing a point to synchronize on a graph. The x axis
553 * is the timestamp of the event from the reference trace while the y axis
554 * is the timestamp of the event on the other trace
555 */
556 private class SyncPoint {
557 private final ITmfTimestamp x, y;
558
559 public SyncPoint(ITmfEvent ex, ITmfEvent ey) {
560 x = ex.getTimestamp();
561 y = ey.getTimestamp();
562 }
563
564 public long getTimeX() {
565 return x.getValue();
566 }
567
568 /**
569 * Calculate a cross product of 3 points:
570 *
571 * If the cross-product < 0, then p, pa, pb are clockwise
572 *
573 * If the cross-product > 0, then p, pa, pb are counter-clockwise
574 *
575 * If cross-product == 0, then they are in a line
576 *
577 * @param pa
578 * First point
579 * @param pb
580 * Second point
581 * @return The cross product
582 */
583 public long crossProduct(SyncPoint pa, SyncPoint pb) {
584 long cp = ((pa.x.getValue() - x.getValue()) * (pb.y.getValue() - y.getValue()) - (pa.y.getValue() - y.getValue()) * (pb.x.getValue() - x.getValue()));
585 return cp;
586 }
587
588 /*
589 * Gets the alpha (slope) between two points
590 */
591 public BigDecimal getAlpha(SyncPoint p1) {
592 if (p1 == null) {
593 return BigDecimal.ONE;
594 }
595 BigDecimal deltay = BigDecimal.valueOf(y.getValue() - p1.y.getValue());
596 BigDecimal deltax = BigDecimal.valueOf(x.getValue() - p1.x.getValue());
597 if (deltax.equals(BigDecimal.ZERO)) {
598 return BigDecimal.ONE;
599 }
600 return deltay.divide(deltax, fMc);
601 }
602
603 /*
604 * Get the beta value (when x = 0) of the line given alpha
605 */
606 public BigDecimal getBeta(BigDecimal alpha) {
607 return BigDecimal.valueOf(y.getValue()).subtract(alpha.multiply(BigDecimal.valueOf(x.getValue()), fMc));
608 }
609
610 @Override
611 public String toString() {
612 return String.format("%s (%s, %s)", this.getClass().getCanonicalName(), x, y); //$NON-NLS-1$
613 }
614 }
615
51c08015 616}
This page took 0.109634 seconds and 5 git commands to generate.