Copyright header update, 2015 edition
[deliverable/tracecompass.git] / org.eclipse.tracecompass.tmf.ui / src / org / eclipse / tracecompass / tmf / ui / views / uml2sd / core / Lifeline.java
CommitLineData
73005152 1/**********************************************************************
ed902a2b 2 * Copyright (c) 2005, 2014 IBM Corporation, Ericsson
73005152
BH
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the Eclipse Public License v1.0
5 * which accompanies this distribution, and is available at
6 * http://www.eclipse.org/legal/epl-v10.html
abbdd66a
AM
7 *
8 * Contributors:
c8422608
AM
9 * IBM - Initial API and implementation
10 * Bernd Hufmann - Updated for TMF
73005152 11 **********************************************************************/
c8422608 12
2bdf0193 13package org.eclipse.tracecompass.tmf.ui.views.uml2sd.core;
73005152
BH
14
15import java.util.ArrayList;
16import java.util.List;
17
2bdf0193
AM
18import org.eclipse.tracecompass.tmf.ui.views.uml2sd.drawings.IColor;
19import org.eclipse.tracecompass.tmf.ui.views.uml2sd.drawings.IGC;
20import org.eclipse.tracecompass.tmf.ui.views.uml2sd.drawings.IImage;
21import org.eclipse.tracecompass.tmf.ui.views.uml2sd.preferences.ISDPreferences;
22import org.eclipse.tracecompass.tmf.ui.views.uml2sd.preferences.SDViewPref;
73005152
BH
23
24/**
25 * Lifeline is the UML2 lifeline graphical representation.<br>
26 * Each lifeline owns a set of event occurrences. An event occurrence is the base element in UML2 to set an event in a
27 * sequence diagram.<br>
28 * Event occurrence define the drawing order of graph node along a lifeline. In this lifeline implementation, event
29 * occurrences are just integer index. The event occurrences with the same value on different lifelines will correspond
30 * the same y coordinate value.
abbdd66a 31 *
df0b8ff4 32 * @version 1.0
73005152 33 * @author sveyrier
abbdd66a 34 *
73005152
BH
35 */
36public class Lifeline extends GraphNode {
df0b8ff4
BH
37 // ------------------------------------------------------------------------
38 // Constants
39 // ------------------------------------------------------------------------
40 /**
41 * The life line tag.
42 */
43 public static final String LIFELINE_TAG = "Lifeline"; //$NON-NLS-1$
73005152 44
df0b8ff4
BH
45 // ------------------------------------------------------------------------
46 // Attribute
47 // ------------------------------------------------------------------------
73005152
BH
48 /**
49 * The lifeline position in the containing frame
50 */
cab6c8ff 51 private int fIndexInFrame = 0;
73005152
BH
52 /**
53 * The frame where the lifeline is drawn
54 */
cab6c8ff 55 private Frame fFrame = null;
73005152
BH
56 /**
57 * The current event occurrence created in the lifeline
58 */
cab6c8ff 59 private int fEventOccurrence = 0;
df0b8ff4
BH
60 /**
61 * The lifeline category.
62 */
cab6c8ff 63 private int fCategory = -1;
df0b8ff4
BH
64 /**
65 * Flag whether lifeline has time information available or not
66 */
cab6c8ff 67 private boolean fHasTimeInfo = false;
73005152 68
df0b8ff4
BH
69 // ------------------------------------------------------------------------
70 // Constructors
71 // ------------------------------------------------------------------------
72 /**
73 * Default constructor
74 */
75 public Lifeline() {
cab6c8ff 76 setColorPrefId(ISDPreferences.PREF_LIFELINE);
df0b8ff4
BH
77 }
78
79 // ------------------------------------------------------------------------
80 // Methods
81 // ------------------------------------------------------------------------
11252342 82
73005152
BH
83 @Override
84 public int getX() {
eb63f5ff 85 return Metrics.FRAME_H_MARGIN + Metrics.LIFELINE_H_MAGIN + (fIndexInFrame - 1) * Metrics.swimmingLaneWidth();
73005152
BH
86 }
87
88 @Override
89 public int getY() {
90 return 2 * Metrics.FRAME_NAME_H_MARGIN + Metrics.LIFELINE_VT_MAGIN / 2 + Metrics.getFrameFontHeigth() + Metrics.getLifelineHeaderFontHeigth() + Metrics.FRAME_V_MARGIN + 2 * Metrics.LIFELINE_HEARDER_TEXT_V_MARGIN;
91 }
92
93 @Override
94 public int getWidth() {
95 return Metrics.getLifelineWidth();
96 }
97
98 @Override
99 public int getHeight() {
100 // Set room for two text lines
101 return Metrics.getLifelineFontHeigth()/** 2 */
102 + 2 * Metrics.LIFELINE_NAME_H_MARGIN;
103 }
104
73005152
BH
105 /**
106 * Set the lifeline category for this lifeline.
abbdd66a 107 *
73005152
BH
108 * @param arrayIndex the index of the category to use
109 * @see Frame#setLifelineCategories(LifelineCategories[])
110 */
111 public void setCategory(int arrayIndex) {
eb63f5ff 112 fCategory = arrayIndex;
73005152
BH
113 }
114
cab6c8ff
BH
115 /**
116 * Gets the lifeline category for this lifeline.
117 *
118 * @return arrayIndex the index of the category to use
119 * @since 2.0
120 */
121 public int getCategory() {
122 return fCategory;
123 }
124
73005152
BH
125 /**
126 * Returns the tooltip text for the lifeline. It is the combination between the category name(if any) and the
127 * lifeline name
abbdd66a 128 *
73005152
BH
129 * @return the tooltip text
130 */
131 public String getToolTipText() {
eb63f5ff
BH
132 if (fCategory >= 0) {
133 LifelineCategories[] categories = fFrame.getLifelineCategories();
134 if (fCategory < categories.length) {
135 return categories[fCategory].getName() + " " + getName(); //$NON-NLS-1$
df0b8ff4 136 }
df0b8ff4 137 }
abbdd66a 138 return ""; //$NON-NLS-1$
73005152
BH
139 }
140
141 /**
142 * Returns the index of the first visible Execution Occurrence in the execution occurrence array.<br>
143 * Execution Occurrences are Y ordered in this array
abbdd66a 144 *
73005152
BH
145 * @return the first visible Execution Occurrence
146 */
147 public int getExecOccurrenceDrawIndex() {
cab6c8ff 148 if (!hasChildren()) {
73005152 149 return 0;
df0b8ff4 150 }
cab6c8ff
BH
151 if (getIndexes().get(BasicExecutionOccurrence.EXEC_OCC_TAG) != null) {
152 return getIndexes().get(BasicExecutionOccurrence.EXEC_OCC_TAG).intValue();
df0b8ff4
BH
153 }
154 return 0;
73005152
BH
155 }
156
157 /**
158 * Set the frame on which this lifeline must be drawn
abbdd66a 159 *
73005152 160 * @param parentFrame
a0a88f65 161 * Parent frame
73005152
BH
162 */
163 protected void setFrame(Frame parentFrame) {
eb63f5ff
BH
164 fFrame = parentFrame;
165 if (fHasTimeInfo) {
166 fFrame.setHasTimeInfo(true);
73005152 167 }
eb63f5ff
BH
168 if (fFrame.getMaxEventOccurrence() < getEventOccurrence() + 1) {
169 fFrame.setMaxEventOccurrence(getEventOccurrence() + 1);
df0b8ff4 170 }
73005152
BH
171 }
172
173 /**
174 * Returns the frame which this lifeline is drawn
abbdd66a 175 *
73005152
BH
176 * @return the Frame
177 */
178 protected Frame getFrame() {
eb63f5ff 179 return fFrame;
73005152
BH
180 }
181
182 /**
183 * Set the lifeline position index in the containing frame
abbdd66a 184 *
73005152
BH
185 * @param index the lifeline X position
186 */
73005152 187 protected void setIndex(int index) {
eb63f5ff 188 fIndexInFrame = index;
73005152
BH
189 }
190
191 /**
192 * Returns the lifeline position in de the containing frame
abbdd66a 193 *
73005152
BH
194 * @return the X position
195 */
196 public int getIndex() {
eb63f5ff 197 return fIndexInFrame;
73005152
BH
198 }
199
200 /**
201 * Set the lifeline event occurrence to the value given in parameter This only change the current event occurrence,
202 * greater event created on this lifeline are still valid and usable. This also need to inform the frame of the
203 * operation mostly to store in the frame the greater event found in the diagram (used to determine the frame
204 * height)
abbdd66a 205 *
0d9a6d76 206 * @param eventOcc the new current event occurrence
73005152
BH
207 */
208 public void setCurrentEventOccurrence(int eventOcc) {
eb63f5ff
BH
209 if ((fFrame != null) && (fFrame.getMaxEventOccurrence() < eventOcc)) {
210 fFrame.setMaxEventOccurrence(eventOcc);
df0b8ff4 211 }
eb63f5ff 212 fEventOccurrence = eventOcc;
73005152
BH
213 }
214
215 /**
216 * Returns the last created event occurrence along the lifeline.
abbdd66a 217 *
73005152
BH
218 * @return the current event occurrence
219 */
220 public int getEventOccurrence() {
eb63f5ff 221 return fEventOccurrence;
73005152
BH
222 }
223
224 /**
225 * Creates a new event occurrence along the lifeline.
abbdd66a 226 *
73005152
BH
227 * @return the new created event occurrence
228 */
229 public int getNewEventOccurrence() {
eb63f5ff
BH
230 setCurrentEventOccurrence(fEventOccurrence + 1);
231 return fEventOccurrence;
73005152
BH
232 }
233
234 /**
235 * Adds the execution occurrence given in parameter to the lifeline.<br>
236 * A Execution occurrence is never drawn in the frame instead it is added to a lifeline
abbdd66a 237 *
73005152
BH
238 * @param exec the execution occurrence to add
239 */
240 public void addExecution(BasicExecutionOccurrence exec) {
241 exec.setLifeline(this);
242 addNode(exec);
cab6c8ff
BH
243 if ((fFrame != null) && (fFrame.getMaxEventOccurrence() < exec.getEndOccurrence())) {
244 fFrame.setMaxEventOccurrence(exec.getEndOccurrence());
df0b8ff4 245 }
73005152
BH
246 }
247
df0b8ff4
BH
248 /**
249 * Set whether lifeline has time information available or not.
250 * @param value The value to set
251 */
73005152 252 protected void setTimeInfo(boolean value) {
eb63f5ff
BH
253 fHasTimeInfo = value;
254 if ((fFrame != null) && value) {
255 fFrame.setHasTimeInfo(value);
df0b8ff4 256 }
73005152
BH
257 }
258
259 /**
df0b8ff4 260 * Returns true if at least one execution occurrence has time info.
abbdd66a 261 *
73005152
BH
262 * @return true if at least one execution occurrence has time info
263 */
264 public boolean hasTimeInfo() {
eb63f5ff 265 return fHasTimeInfo;
73005152
BH
266 }
267
268 /**
df0b8ff4 269 * Returns the list of execution occurrence on this lifeline.
abbdd66a 270 *
73005152
BH
271 * @return the execution occurrence list
272 */
273 public List<GraphNode> getExecutions() {
cab6c8ff
BH
274 if (hasChildren()) {
275 return getNodeMap().get(BasicExecutionOccurrence.EXEC_OCC_TAG);
df0b8ff4 276 }
507b1336 277 return new ArrayList<>();
73005152
BH
278 }
279
280 @Override
eb63f5ff 281 public boolean contains(int xValue, int yValue) {
73005152
BH
282 int x = getX();
283 int y = getY();
284 int width = getWidth();
285 int height = getHeight();
286
eb63f5ff 287 if (fFrame == null) {
73005152 288 return false;
df0b8ff4 289 }
abbdd66a 290 if (GraphNode.contains(x, y, width, height, xValue, yValue)) {
73005152
BH
291 return true;
292 }
abbdd66a 293 if (GraphNode.contains(x + Metrics.getLifelineWidth() / 2 - Metrics.EXECUTION_OCCURRENCE_WIDTH / 2, y + height, Metrics.EXECUTION_OCCURRENCE_WIDTH, (Metrics.getMessageFontHeigth() + Metrics.getMessagesSpacing()) * fFrame.getMaxEventOccurrence()
eb63f5ff 294 + Metrics.LIFELINE_VB_MAGIN - 4, xValue, yValue)) {
73005152
BH
295 return true;
296 }
297
298 height = Metrics.getLifelineFontHeigth() + 2 * Metrics.LIFELINE_HEARDER_TEXT_V_MARGIN;
299 int hMargin = (Metrics.LIFELINE_VT_MAGIN - height) / 2;
300
301 if (hMargin >= 2) {
eb63f5ff 302 if (fFrame.getVisibleAreaY() < y - height - hMargin) {
abbdd66a 303 if (GraphNode.contains(x - Metrics.LIFELINE_SPACING / 2 + 1, y - height - hMargin, Metrics.swimmingLaneWidth() - 2, height + 1, xValue, yValue)) {
73005152 304 return true;
df0b8ff4 305 }
73005152 306 } else {
abbdd66a 307 if (GraphNode.contains(x - Metrics.LIFELINE_SPACING / 2 + 1, fFrame.getVisibleAreaY(), Metrics.swimmingLaneWidth() - 2, height, xValue, yValue)) {
73005152 308 return true;
df0b8ff4 309 }
73005152
BH
310 }
311 }
eb63f5ff 312 if (getNodeAt(xValue, yValue) != null) {
73005152 313 return true;
df0b8ff4 314 }
73005152
BH
315 return false;
316 }
317
318 /**
319 * Returns the lifeline visibility for the given visible area
abbdd66a 320 *
df0b8ff4
BH
321 * @param vx The x coordinate of the visible area
322 * @param vy The y coordinate of the visible area
323 * @param vwidth The width of the visible area
324 * @param vheight The height of the visible area
73005152
BH
325 * @return true if visible false otherwise
326 */
327 @Override
328 public boolean isVisible(int vx, int vy, int vwidth, int vheight) {
329 int x = getX();
330 int width = getWidth();
df0b8ff4 331 if (((x >= vx) && (x <= vx + vwidth)) || ((x + width >= vx) && (x <= vx))) {
73005152 332 return true;
df0b8ff4 333 }
73005152
BH
334 return false;
335 }
336
df0b8ff4 337 /**
abbdd66a
AM
338 * Draws the name within the graphical context.
339 *
df0b8ff4
BH
340 * @param context The graphical context.
341 */
73005152 342 protected void drawName(IGC context) {
3145ec83
BH
343 ISDPreferences pref = SDViewPref.getInstance();
344
73005152
BH
345 int x = getX();
346 int y = getY();
347 int height = Metrics.getLifelineHeaderFontHeigth() + 2 * Metrics.LIFELINE_HEARDER_TEXT_V_MARGIN;
348 int hMargin = Metrics.LIFELINE_VT_MAGIN / 4;// (Metrics.LIFELINE_NAME_H_MARGIN)/2;
349
350 context.setLineStyle(context.getLineSolidStyle());
3145ec83
BH
351 context.setBackground(pref.getBackGroundColor(ISDPreferences.PREF_LIFELINE_HEADER));
352 context.setForeground(pref.getForeGroundColor(ISDPreferences.PREF_LIFELINE_HEADER));
353 context.setFont(pref.getFont(ISDPreferences.PREF_LIFELINE_HEADER));
73005152 354 if (hMargin >= 0) {
eb63f5ff 355 if (fFrame.getVisibleAreaY() < y - height - hMargin) {
73005152
BH
356 context.fillRectangle(x - Metrics.LIFELINE_SPACING / 2 + 1, y - height - hMargin, Metrics.swimmingLaneWidth() - 2, height);
357 context.drawRectangle(x - Metrics.LIFELINE_SPACING / 2 + 1, y - height - hMargin, Metrics.swimmingLaneWidth() - 2, height);
3145ec83 358 context.setForeground(pref.getFontColor(ISDPreferences.PREF_LIFELINE_HEADER));
73005152
BH
359 context.drawTextTruncatedCentred(getName(), x + Metrics.LIFELINE_NAME_V_MARGIN - Metrics.LIFELINE_SPACING / 2 + 1, y - height - hMargin, Metrics.swimmingLaneWidth() - 2 * Metrics.LIFELINE_NAME_V_MARGIN - 2, height, true);
360 } else {
eb63f5ff
BH
361 context.fillRectangle(x - Metrics.LIFELINE_SPACING / 2 + 1, fFrame.getVisibleAreaY(), Metrics.swimmingLaneWidth() - 2, height);
362 context.drawRectangle(x - Metrics.LIFELINE_SPACING / 2 + 1, fFrame.getVisibleAreaY(), Metrics.swimmingLaneWidth() - 2, height);
3145ec83 363 context.setForeground(pref.getFontColor(ISDPreferences.PREF_LIFELINE_HEADER));
eb63f5ff 364 context.drawTextTruncatedCentred(getName(), x - Metrics.LIFELINE_SPACING / 2 + Metrics.LIFELINE_NAME_V_MARGIN + 1, fFrame.getVisibleAreaY(), Metrics.swimmingLaneWidth() - 2 * Metrics.LIFELINE_NAME_V_MARGIN - 2, height, true);
73005152
BH
365 }
366 }
367 }
368
369 /**
370 * Force the lifeline to be drawn at the given coordinate
abbdd66a 371 *
73005152 372 * @param context - the context to draw into
0d9a6d76
FC
373 * @param x - the x coordinate
374 * @param y - the y coordinate
73005152
BH
375 */
376 public void draw(IGC context, int x, int y) {
abbdd66a 377
3145ec83 378 ISDPreferences pref = SDViewPref.getInstance();
abbdd66a 379
73005152
BH
380 // Set the draw color depending if the lifeline must be selected or not
381 context.setLineWidth(Metrics.NORMAL_LINE_WIDTH);
382 if (isSelected()) {
3145ec83
BH
383 if (pref.useGradienColor()) {
384 context.setGradientColor(pref.getBackGroundColor(ISDPreferences.PREF_LIFELINE));
73005152 385 }
3145ec83
BH
386 context.setBackground(pref.getBackGroundColorSelection());
387 context.setForeground(pref.getForeGroundColorSelection());
73005152 388 } else {
3145ec83
BH
389 if (pref.useGradienColor()) {
390 context.setGradientColor(pref.getBackGroundColor(ISDPreferences.PREF_LIFELINE));
391 context.setBackground(pref.getBackGroundColor(ISDPreferences.PREF_FRAME));
df0b8ff4 392 } else {
3145ec83 393 context.setBackground(pref.getBackGroundColor(ISDPreferences.PREF_LIFELINE));
df0b8ff4 394 }
3145ec83 395 context.setForeground(pref.getForeGroundColor(ISDPreferences.PREF_LIFELINE));
73005152
BH
396 }
397 // Store the lifeline coordinates to save some calls
398 int width = getWidth();
399 int height = getHeight();
400
401 // Draw the rectangle which contain the lifeline name
3145ec83 402 if (pref.useGradienColor()) {
73005152
BH
403 context.fillGradientRectangle(x, y, width, height / 2 - 7, true);
404 context.fillRectangle(x, y + height / 2 - 8, width, +height / 2 - 5);
405 context.fillGradientRectangle(x, y + height, width, -height / 2 + 6, true);
df0b8ff4 406 } else {
73005152 407 context.fillRectangle(x, y, width, height);
df0b8ff4 408 }
73005152
BH
409 context.drawRectangle(x, y, width, height);
410
eb63f5ff
BH
411 if (fCategory >= 0) {
412 LifelineCategories[] categories = fFrame.getLifelineCategories();
413 if (fCategory < categories.length) {
414 IImage image = categories[fCategory].getImage();
df0b8ff4 415 if (image != null) {
73005152 416 context.drawImage(image, x, y, width, height);
df0b8ff4 417 }
73005152
BH
418 }
419 }
420
421 // Draw the lifeline label into the rectangle
422 // The label is truncated if it cannot fit
423 IColor temp = context.getForeground();
3145ec83
BH
424 context.setFont(pref.getFont(ISDPreferences.PREF_LIFELINE));
425 context.setForeground(pref.getFontColor(ISDPreferences.PREF_LIFELINE));
73005152
BH
426 context.drawTextTruncatedCentred(getName(), x + Metrics.LIFELINE_NAME_V_MARGIN, y, Metrics.getLifelineWidth() - 2 * Metrics.LIFELINE_NAME_V_MARGIN, height, true);
427
428 context.setLineStyle(context.getLineDashStyle());
429 context.setForeground(temp);
430 int oldStyle = context.getLineStyle();
431
432 // Now draw the lifeline vertical line
433 // this line height depends on a stop assignment
434 // if there is no stop the line is drawn to the bottom of the frame
435
436 // by default set the height to reach the frame bottom
eb63f5ff 437 int dashedLineEnd = y + height + (Metrics.getMessageFontHeigth() + Metrics.getMessagesSpacing()) * fFrame.getMaxEventOccurrence() + Metrics.LIFELINE_VB_MAGIN;
73005152
BH
438 /*
439 * if (stop != null) { dashedLineEnd = stop.getY(); }
440 */
441
442 if (isSelected()) {
3145ec83 443 context.setForeground(pref.getBackGroundColorSelection());
73005152
BH
444 context.setLineWidth(5);
445 context.drawLine(x + Metrics.getLifelineWidth() / 2, y + height, x + Metrics.getLifelineWidth() / 2, dashedLineEnd - 4);
3145ec83 446 context.setForeground(pref.getForeGroundColorSelection());
73005152
BH
447 }
448
449 context.setLineWidth(Metrics.NORMAL_LINE_WIDTH);
450 context.drawLine(x + Metrics.getLifelineWidth() / 2, y + height, x + Metrics.getLifelineWidth() / 2, dashedLineEnd - 4);
451 context.drawLine(x + Metrics.getLifelineWidth() / 2, y + height, x + Metrics.getLifelineWidth() / 2, dashedLineEnd - 4);
452 context.setLineStyle(oldStyle);
453
454 context.setLineStyle(context.getLineSolidStyle());
455
df0b8ff4 456 if (hasFocus()) {
73005152 457 drawFocus(context);
df0b8ff4 458 }
73005152
BH
459
460 super.drawChildenNodes(context);
461 }
462
463 /**
464 * Draws the select execution occurrence region using the given color
abbdd66a 465 *
73005152
BH
466 * @param context the graphical context
467 * @param startEvent the region start
468 * @param nbEvent the region height
469 * @param color the color to use
470 */
471 public void highlightExecOccurrenceRegion(IGC context, int startEvent, int nbEvent, IColor color) {
472 IColor backupColor = context.getBackground();
473 context.setBackground(color);
474 int x = getX() + Metrics.getLifelineWidth() / 2 - Metrics.EXECUTION_OCCURRENCE_WIDTH / 2;
475 int y = getY() + getHeight() + (Metrics.getMessageFontHeigth() + Metrics.getMessagesSpacing()) * startEvent;
476 int width = Metrics.EXECUTION_OCCURRENCE_WIDTH;
477 int height = ((Metrics.getMessageFontHeigth() + Metrics.getMessagesSpacing())) * nbEvent;
478 context.fillRectangle(x, y, width, height);
479 context.setBackground(backupColor);
480 }
481
482 @Override
483 public void draw(IGC context) {
484 draw(context, getX(), getY());
485 }
486
487 @Override
488 public String getArrayId() {
489 return LIFELINE_TAG;
490 }
491
492 @Override
493 public boolean positiveDistanceToPoint(int x, int y) {
abbdd66a 494 if (getX() > x - Metrics.swimmingLaneWidth()) {
73005152 495 return true;
abbdd66a 496 }
73005152
BH
497 return false;
498 }
499
500 @Override
501 public GraphNode getNodeAt(int x, int y) {
502 int vy = 0;
503 int vh = 0;
504 if (getFrame() != null) {
505 vy = getFrame().getVisibleAreaY();
506 vh = getFrame().getVisibleAreaHeight();
df0b8ff4 507 } else {
73005152 508 return null;
df0b8ff4
BH
509 }
510 if (getExecutions() == null) {
73005152 511 return null;
df0b8ff4 512 }
73005152 513 for (int i = getExecOccurrenceDrawIndex(); i < getExecutions().size(); i++) {
abbdd66a 514 GraphNode node = getExecutions().get(i);
73005152 515 if (node.getHeight() < 0) {
df0b8ff4 516 if (node.getY() + node.getHeight() > vy + vh) {
73005152 517 break;
df0b8ff4 518 }
73005152 519 } else {
df0b8ff4 520 if (node.getY() > vy + vh) {
73005152 521 break;
df0b8ff4 522 }
73005152
BH
523 }
524 if (node.contains(x, y)) {
525 GraphNode internal = node.getNodeAt(x, y);
df0b8ff4 526 if (internal != null) {
73005152 527 return internal;
df0b8ff4
BH
528 }
529 return node;
73005152
BH
530 }
531 }
532 return null;
533 }
534}
This page took 0.138393 seconds and 5 git commands to generate.