|
1 # Copyright (c) 1999-2009 Nokia Corporation and/or its subsidiary(-ies). |
|
2 # All rights reserved. |
|
3 # This component and the accompanying materials are made available |
|
4 # under the terms of "Eclipse Public License v1.0" |
|
5 # which accompanies this distribution, and is available |
|
6 # at the URL "http://www.eclipse.org/legal/epl-v10.html". |
|
7 # |
|
8 # Initial Contributors: |
|
9 # Nokia Corporation - initial contribution. |
|
10 # |
|
11 # Contributors: |
|
12 # |
|
13 # Description: |
|
14 # |
|
15 |
|
16 #!/usr/bin/perl |
|
17 |
|
18 |
|
19 use strict; |
|
20 |
|
21 my %objectCoordinates = # pretty dumb. could smarten this up a lot. |
|
22 ( |
|
23 "CSocket", [200,100], |
|
24 "CSubConn", [400,100], |
|
25 "CConn", [600,100], |
|
26 "CCommsMgr", [800,100], |
|
27 "CSelectionRequest", [1000,110], # NOT Tier Manager's sub-session hence offset |
|
28 |
|
29 "CFlowFactCtr", [100,150], |
|
30 "CSubConnFactCtr", [300,150], |
|
31 "CConnFactCtr", [500,150], |
|
32 "CMetaConnFactCtr", [700,150], |
|
33 "CTierMgrFactCtr", [900,150], |
|
34 |
|
35 |
|
36 "CIPDefaultSCPrFact", [300,200], |
|
37 "CIPCPrFact", [500,200], |
|
38 "CNetMCPrFact", [700,200], |
|
39 "CNetTierMgrFact", [900,200], |
|
40 |
|
41 "CIPDefaultSCPr", [400,250], |
|
42 "CIPCPr", [600,250], |
|
43 "CNetMCPr", [800,250], |
|
44 "CNetTierMgr", [1000,250], |
|
45 |
|
46 |
|
47 "CIPProtoSCPrFact", [300,300], |
|
48 "CIPProtoCPrFact", [500,300], |
|
49 "CIPProtoMCPrFact", [700,300], |
|
50 "CIPProtoTierMgrFact", [900,300], |
|
51 |
|
52 "CIPProtoDefaultSCPr", [400,350], |
|
53 "CIPProtoCPr", [600,350], |
|
54 "CIPProtoMCPr", [800,350], |
|
55 "CIPProtoTierMgr", [1000,350], |
|
56 |
|
57 |
|
58 "CIPProtoDefaultSCPr-1", [400,400], |
|
59 "CIPProtoCPr-1", [600,400], |
|
60 |
|
61 |
|
62 "CPPPSCPrFact", [300,500], |
|
63 "CPPPCPrFact", [500,500], |
|
64 "CPPPMCPrFact", [700,500], |
|
65 "CPPPTierMgrFact", [900,500], |
|
66 |
|
67 "CPPPSCPr", [400,550], |
|
68 "CPPPCPr", [600,550], |
|
69 "CPPPMCPr", [800,550], |
|
70 "CPPPTierMgr", [1000,550], |
|
71 |
|
72 ); |
|
73 |
|
74 # |
|
75 # constants |
|
76 # |
|
77 my $incrementY = 20; |
|
78 my $topMargin = 20; |
|
79 my $arrowWidth = 10; |
|
80 my $textArrowSpacing = 1; |
|
81 my $messageSpacingAboveLine = 1; |
|
82 my $objectVerticalLineSpacing = 2; |
|
83 my $objectUnderlineToLifelineGap = 2; |
|
84 |
|
85 my $objectsPerRow = 4; |
|
86 my $objectSpacesToSkipBeforeAutoSpacing = 4 * $objectsPerRow - 1; # start on 5th line |
|
87 my $xSpacing = 200; |
|
88 my $ySpacing = 100; |
|
89 |
|
90 my $objectName = 0; |
|
91 my $objectX = 1; |
|
92 my $objectY = 2; |
|
93 my $objectMsgCount = 3; |
|
94 my $objectCreationPointKnown = 4; |
|
95 |
|
96 my $sequenceAction = 0; |
|
97 my $sequenceObjRef = 1; # if action == "t" || "oc" |
|
98 my $sequenceMsgRef = 1; # if action != "t" |
|
99 my $sequenceX = 2; |
|
100 my $sequenceY = 3; |
|
101 |
|
102 my $anims = 1; |
|
103 my $time = 0; |
|
104 my $timeDelta = 500; |
|
105 my $timeDur = $timeDelta * 0.9; |
|
106 |
|
107 my $colouredBoxes = 1; |
|
108 |
|
109 my $animKeys = '1234567890qwertyuiopasdfghjklzxcvbnm'; |
|
110 #$animKeys = ''; # uncomment this line to enable animKeys |
|
111 my $animMessagesPerKey = 5; |
|
112 my $animKeyCounter = 0; |
|
113 my $animKeysFirstElement=1; |
|
114 |
|
115 my @sequences; |
|
116 my @messages; |
|
117 my @objects; |
|
118 |
|
119 if(@ARGV && $ARGV[0] eq '-auto') |
|
120 { |
|
121 shift; |
|
122 $animKeys=''; |
|
123 } |
|
124 |
|
125 # |
|
126 # Data Structures: |
|
127 # |
|
128 # "objects" is array of arrays: |
|
129 # [ <object name> <object X> <object Y> <object message count> <creation point known flag> ] |
|
130 # |
|
131 # "sequences" is an array of arrays: |
|
132 # [ "t" \objects[<n>] "text" ] |
|
133 # [ "[p|r]" \messages[n] \objects[<source>], \objects[<destination>] ] |
|
134 # [ "oc" \objects[<n>] ] |
|
135 |
|
136 |
|
137 # Input file format: |
|
138 # |
|
139 # <action> <arguments> ... |
|
140 # |
|
141 # specifically: |
|
142 # |
|
143 # [P|R] <message name> <source object> <destination object> |
|
144 # T <object> <text> |
|
145 # OC <object> |
|
146 # |
|
147 # actions: |
|
148 # |
|
149 # P Post |
|
150 # R Receive |
|
151 # T Text |
|
152 # OC Object Create |
|
153 # |
|
154 # All fields are space separated text. For example: |
|
155 # |
|
156 # P StartFlow SCPR Flow |
|
157 # |
|
158 |
|
159 while (<>) { |
|
160 die unless s/^(\w+)\s+//; |
|
161 my $action = $1; |
|
162 if ($action eq "t") |
|
163 { |
|
164 # Text |
|
165 # t <object> <text> |
|
166 my $pos; |
|
167 if (s/^([\w\d-]+)\s+//) { $pos = $1; } |
|
168 my $objRef = addObject($pos); |
|
169 chomp; |
|
170 my $text = $_; |
|
171 push @sequences, [$action, $objRef, $text]; |
|
172 } |
|
173 elsif ($action eq "oc") |
|
174 { |
|
175 # Object Create |
|
176 # oc <object> |
|
177 my $objName; |
|
178 if (s/^([\w\d-]+)\s+//) { $objName = $1; } |
|
179 my $objRef = addObject($objName); |
|
180 ${$objRef}->[$objectCreationPointKnown] = 1; |
|
181 chomp; |
|
182 push @sequences, [$action, $objRef]; |
|
183 } |
|
184 else |
|
185 { |
|
186 # Post/Receive |
|
187 # [P|R] <message> <source object> <destination object> |
|
188 split; |
|
189 my $msgRef = addMessage(shift @_); |
|
190 my $srcRef = addObject(shift @_); |
|
191 my $destRef = addObject(shift @_); |
|
192 ${$srcRef}->[$objectMsgCount]++; |
|
193 ${$destRef}->[$objectMsgCount]++; |
|
194 push @sequences, [$action, $msgRef, $srcRef, $destRef]; |
|
195 } |
|
196 } |
|
197 |
|
198 |
|
199 my ($screenWidth,$screenHeight) = calculateObjectPositions(); |
|
200 |
|
201 #my $screenWidth = $objects[$#objects]->[$objectX] + $rightMargin; |
|
202 #my $screenHeight = scalar(@sequences) * ($incrementY + 2) + $topMargin; |
|
203 #my $screenWidth = 500; |
|
204 #my $screenHeight = 500; |
|
205 |
|
206 outputDocHeader($screenWidth, $screenHeight); |
|
207 |
|
208 drawObjectsAtTop(); |
|
209 |
|
210 |
|
211 drawCollaborations(); |
|
212 |
|
213 outputDocFooter(); |
|
214 |
|
215 #################### |
|
216 # Message routines |
|
217 #################### |
|
218 |
|
219 |
|
220 sub drawObjectsAtTop() |
|
221 { |
|
222 my $i; |
|
223 foreach $i (@objects) |
|
224 { |
|
225 if ($i->[$objectCreationPointKnown] == 1) |
|
226 { |
|
227 next; |
|
228 } |
|
229 outputText($i->[$objectX], $i->[$objectName], $i->[$objectY], "middle", "underline","freeze",'',0); |
|
230 } |
|
231 print "\n"; |
|
232 } |
|
233 |
|
234 |
|
235 sub addMessage() |
|
236 { |
|
237 my $obj = $_[0]; |
|
238 my $i; |
|
239 for ($i = 0 ; $i < scalar(@messages) ; ++$i) { |
|
240 if ($messages[$i] eq $obj) { |
|
241 return \$messages[$i]; |
|
242 } |
|
243 } |
|
244 $messages[$i] = $obj; |
|
245 return \$messages[$i]; |
|
246 } |
|
247 |
|
248 sub printMessages() |
|
249 { |
|
250 print "Messages (", scalar(@messages), ") : "; |
|
251 foreach my $msg (@messages) { |
|
252 print $msg, " "; |
|
253 } |
|
254 print "\n"; |
|
255 } |
|
256 |
|
257 ################### |
|
258 # Object routines |
|
259 ################### |
|
260 |
|
261 sub addObject() |
|
262 { |
|
263 my $objName = $_[0]; |
|
264 my $i; |
|
265 for ($i = 0 ; $i < scalar(@objects) ; ++$i) { |
|
266 if ($objects[$i]->[$objectName] eq $objName) { |
|
267 return \$objects[$i]; |
|
268 } |
|
269 } |
|
270 $objects[$i] = [ $objName, 0, 0, 0, 0 ]; |
|
271 return \$objects[$i]; |
|
272 } |
|
273 |
|
274 sub printObjects() |
|
275 { |
|
276 print "Objects (", scalar(@objects), "): "; |
|
277 foreach my $obj (@objects) { |
|
278 print $obj->[0], "(", $obj->[1], ") "; |
|
279 } |
|
280 print "\n"; |
|
281 } |
|
282 |
|
283 sub calculateObjectPositions() |
|
284 { |
|
285 my ($maxX, $maxY) = (0,0); |
|
286 |
|
287 my $xSpacing = 200; |
|
288 my $ySpacing = 100; |
|
289 |
|
290 my $objctr = $objectSpacesToSkipBeforeAutoSpacing; |
|
291 my $i; |
|
292 for ($i = 0 ; $i < scalar(@objects) ; ++$i) |
|
293 { |
|
294 my $coords = $objectCoordinates{$objects[$i][$objectName]}; |
|
295 if(defined $coords) |
|
296 { |
|
297 ($objects[$i][$objectX],$objects[$i][$objectY]) = |
|
298 ($coords->[0], $coords->[1]); |
|
299 } |
|
300 else |
|
301 { |
|
302 $objctr++; |
|
303 # for($objctr = 0 ; $objctr < 50 ; $objctr++) |
|
304 # { |
|
305 my $x = (1+($objctr % $objectsPerRow)) * $xSpacing; |
|
306 my $y = (1+int($objctr / $objectsPerRow)) * $ySpacing; |
|
307 # print STDERR ("X $x Y $y\n"); |
|
308 # } |
|
309 # exit; |
|
310 #my ($x,$y); |
|
311 ($objects[$i][$objectX],$objects[$i][$objectY]) = |
|
312 ($x, $y); |
|
313 } |
|
314 |
|
315 if($objects[$i][$objectX] > $maxX) { $maxX = $objects[$i][$objectX];} |
|
316 if($objects[$i][$objectY] > $maxY) { $maxY = $objects[$i][$objectY];} |
|
317 |
|
318 $objects[$i][$objectX] -= $xSpacing/2; |
|
319 $objects[$i][$objectY] -= $ySpacing/2; |
|
320 |
|
321 print STDERR ("$i $objects[$i][$objectName] $objects[$i][$objectX] $objects[$i][$objectY]\n") |
|
322 } |
|
323 # die("$maxX $maxY"); |
|
324 return ($maxX,$maxY); |
|
325 } |
|
326 |
|
327 sub trimSilentObjects() |
|
328 { |
|
329 # get rid of objects that didn't end up with any messages sent to/from them |
|
330 my $i; |
|
331 for ($i = 0 ; $i < scalar(@objects) ; ) { |
|
332 if ($objects[$i][$objectMsgCount] == 0) |
|
333 { |
|
334 my $j; |
|
335 for ($j = 0 ; $j < scalar(@sequences) ; ) |
|
336 { |
|
337 my $action = $sequences[$j][$sequenceAction]; |
|
338 if ( (($action eq "t") || ($action eq "oc")) && |
|
339 ($sequences[$j][$sequenceObjRef] == \$objects[$i]) ) |
|
340 { |
|
341 splice @sequences, $j, 1; |
|
342 #print "delete sequence $j\n"; |
|
343 #++$j; |
|
344 } |
|
345 else |
|
346 { |
|
347 ++$j; |
|
348 } |
|
349 } |
|
350 splice @objects, $i, 1; |
|
351 #print "delete object $i $objects[$i][$objectName]\n"; |
|
352 #++$i; |
|
353 } |
|
354 else |
|
355 { |
|
356 ++$i; |
|
357 } |
|
358 } |
|
359 } |
|
360 |
|
361 sub drawObject($$$) |
|
362 { |
|
363 my ($x,$y,$name) = @_; |
|
364 outputText($x, $name, $y, "middle", "underline","freeze",qq{opacity="0.9"},0); |
|
365 # outputVerticalLine($x, $y + $objectUnderlineToLifelineGap, $screenHeight - $topOffset); |
|
366 } |
|
367 |
|
368 |
|
369 |
|
370 sub drawCollaborations() |
|
371 { |
|
372 foreach my $ref (@sequences) { |
|
373 my $action = $ref->[$sequenceAction]; |
|
374 if ($action eq "t") |
|
375 { |
|
376 my $objX = ${$ref->[$sequenceObjRef]}->[$objectX]; |
|
377 my $objY = ${$ref->[$sequenceObjRef]}->[$objectY] + $incrementY; |
|
378 my $text = $ref->[2]; |
|
379 print STDERR ("DRAWING $text $objX $objY\n"); |
|
380 outputText($objX, $text, $objY, "middle", "", "freeze",qq{opacity="0.9"},0); |
|
381 $time += $timeDelta/2; |
|
382 } |
|
383 elsif ($action eq "oc") |
|
384 { |
|
385 # if ($objectsDisplayedAtCreationPoint == 1) |
|
386 { |
|
387 my $objX = ${$ref->[$sequenceObjRef]}->[$objectX]; |
|
388 my $objY = ${$ref->[$sequenceObjRef]}->[$objectY]; |
|
389 my $objName = ${$ref->[$sequenceObjRef]}->[$objectName]; |
|
390 drawObject($objX, $objY, $objName); |
|
391 } |
|
392 } |
|
393 else |
|
394 { |
|
395 my $msg = ${$ref->[$sequenceObjRef]}; |
|
396 my $srcX = ${$ref->[$sequenceX]}->[$objectX]; |
|
397 my $srcY = ${$ref->[$sequenceX]}->[$objectY]; |
|
398 my $destX = ${$ref->[$sequenceY]}->[$objectX]; |
|
399 my $destY = ${$ref->[$sequenceY]}->[$objectY]; |
|
400 my $align; |
|
401 if ($action eq "p") { |
|
402 $align = "tail"; |
|
403 } |
|
404 elsif ($action eq "r") { |
|
405 $align = "head"; |
|
406 } |
|
407 outputLabelledLine($srcX, $srcY, $destX, $destY, $msg, $align); |
|
408 |
|
409 $time += $timeDelta; |
|
410 |
|
411 if($animKeys) |
|
412 { |
|
413 $animKeyCounter++; |
|
414 if($animKeyCounter % $animMessagesPerKey == 0) |
|
415 { |
|
416 $time = 0; |
|
417 $animKeysFirstElement=1; |
|
418 } |
|
419 } |
|
420 |
|
421 } |
|
422 print "\n"; |
|
423 } |
|
424 } |
|
425 |
|
426 |
|
427 ####################### |
|
428 # SVG output routines |
|
429 ####################### |
|
430 |
|
431 sub outputDocHeader() |
|
432 { |
|
433 my ($width,$height) = @_; |
|
434 print '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>',"\n"; |
|
435 print '<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">',"\n"; |
|
436 print "<svg height=\"$height\" width=\"$width\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\">\n"; |
|
437 outputDefs(); |
|
438 } |
|
439 |
|
440 sub outputDefs() |
|
441 { |
|
442 print "<defs>\n"; |
|
443 print qq{<marker id="arr" markerHeight="10" markerUnits="strokeWidth" markerWidth="10" orient="auto" refX="10" refY="5" viewBox="0 0 10 10">\n}; |
|
444 print qq{\t<path d="M 0 0 L 10 5 L 0 10 " />\n}; |
|
445 print "</marker>\n"; |
|
446 print "</defs>\n"; |
|
447 } |
|
448 |
|
449 sub outputDocFooter() |
|
450 { |
|
451 print "</svg>\n"; |
|
452 } |
|
453 |
|
454 sub shortenLine($$$$$) |
|
455 { |
|
456 my($x1,$y1,$x2,$y2,$amount) = @_; |
|
457 |
|
458 my $xdiff = $x1-$x2; |
|
459 my $ydiff = $y1-$y2; |
|
460 my $linelen = sqrt(($xdiff*$xdiff) + ($ydiff*$ydiff)); |
|
461 my $factor = 1 - ($amount / ($linelen+1)); |
|
462 |
|
463 if($x2>$x1) |
|
464 { |
|
465 $amount = (1-$factor) * ($x2-$x1); |
|
466 $x1 += $amount; |
|
467 $x2 -= $amount; |
|
468 } |
|
469 else |
|
470 { |
|
471 $amount = (1-$factor) * ($x1-$x2); |
|
472 $x1 -= $amount; |
|
473 $x2 += $amount; |
|
474 } |
|
475 |
|
476 if($y2>$y1) |
|
477 { |
|
478 $amount = (1-$factor) * ($y2-$y1); |
|
479 $y1 += $amount; |
|
480 $y2 -= $amount; |
|
481 } |
|
482 else |
|
483 { |
|
484 $amount = (1-$factor) * ($y1-$y2); |
|
485 $y1 -= $amount; |
|
486 $y2 += $amount; |
|
487 } |
|
488 |
|
489 return ($x1,$y1,$x2,$y2); |
|
490 } |
|
491 |
|
492 sub outputLabelledLine() |
|
493 { |
|
494 my ($x1,$y1,$x2,$y2,$text,$alignment) = @_; |
|
495 |
|
496 # my $scale = 0.9; |
|
497 ($x1,$y1,$x2,$y2) = shortenLine($x1,$y1,$x2,$y2,15); |
|
498 outputLine($x1,$y1,$x2,$y2); |
|
499 |
|
500 my ($textx,$texty); |
|
501 my $anchor; |
|
502 # if (!$alignment || $alignment eq "mid") { |
|
503 $textx = (($x1 + $x2) / 2); |
|
504 $texty = (($y1 + $y2) / 2); |
|
505 $anchor = "middle"; |
|
506 # } |
|
507 # else { |
|
508 # if ($alignment eq "head") { |
|
509 # if ($x1 < $x2) { |
|
510 # $anchor = "end"; |
|
511 # $textx = $x2 - $arrowWidth - $textArrowSpacing; |
|
512 # $texty = $y2 - $arrowWidth - $textArrowSpacing; |
|
513 # } |
|
514 # else { |
|
515 # $anchor = "start"; |
|
516 # $textx = $x2 + $arrowWidth + $textArrowSpacing; |
|
517 # $texty = $y2 + $arrowWidth + $textArrowSpacing; |
|
518 # } |
|
519 # } else { # "tail" |
|
520 # $textx = $x1; |
|
521 # $texty = $y1; |
|
522 # if ($x1 < $x2) { |
|
523 # $anchor = "start"; |
|
524 # } else { |
|
525 # $anchor = "end"; |
|
526 # } |
|
527 # } |
|
528 # } |
|
529 |
|
530 my $rectangleColour = "black"; |
|
531 |
|
532 if($colouredBoxes) |
|
533 { |
|
534 if($alignment eq "head") |
|
535 { |
|
536 $rectangleColour='pink'; |
|
537 } |
|
538 else |
|
539 { |
|
540 $rectangleColour='lightgreen'; |
|
541 } |
|
542 } |
|
543 |
|
544 outputText($textx,$text,$texty, $anchor, "", "remove", "", $rectangleColour); |
|
545 } |
|
546 |
|
547 sub getAnimStr |
|
548 { |
|
549 my ($animFill) = @_; |
|
550 |
|
551 my $beginStr = ''; |
|
552 my $idStr = ''; |
|
553 if($animKeys) |
|
554 { |
|
555 my $char = substr ($animKeys, int($animKeyCounter/$animMessagesPerKey) , 1); |
|
556 if($animKeysFirstElement) # if first in sequence |
|
557 { |
|
558 $animKeysFirstElement=0; |
|
559 $beginStr.=qq{accessKey($char)}; |
|
560 $idStr = qq{ id="anim_$char" }; |
|
561 } |
|
562 else |
|
563 { |
|
564 $beginStr = qq{anim_$char.begin}; |
|
565 if($time) {$beginStr .= qq{ + $time}.'ms'} |
|
566 } |
|
567 } |
|
568 else |
|
569 { |
|
570 $beginStr = $time; |
|
571 $beginStr .= 'ms'; |
|
572 } |
|
573 |
|
574 my $anim = qq{<set $idStr attributeName="visibility" attributeType="CSS" to="visible" begin="$beginStr" dur="$timeDur}.qq{ms" fill="$animFill" />}; |
|
575 return $anim; |
|
576 } |
|
577 |
|
578 sub outputText() |
|
579 { |
|
580 my ($x,$text,$y,$anchor,$decoration,$animFill,$otherAttrs,$rectangleColour) = @_; |
|
581 |
|
582 #$rectangleUnder=1; |
|
583 my $attrs = qq{ x="$x" y="$y" $otherAttrs}; |
|
584 |
|
585 if ($decoration) |
|
586 { |
|
587 $attrs .= qq{ text-decoration="$decoration" }; |
|
588 } |
|
589 |
|
590 my $anim=''; |
|
591 |
|
592 if ($anims) |
|
593 { |
|
594 $attrs .= qq{ visibility="hidden" }; |
|
595 $anim = getAnimStr($animFill); |
|
596 } |
|
597 |
|
598 if ($anchor) |
|
599 { |
|
600 $attrs .= qq { text-anchor="$anchor" }; |
|
601 } |
|
602 |
|
603 if($rectangleColour) |
|
604 { |
|
605 my $w = 8*length($text); # guessing |
|
606 my $h = 18; # guessing |
|
607 my $rx = $x - $w/2; |
|
608 my $ry = $y - $h/2 - 4; |
|
609 print qq{<rect x="$rx" y="$ry" width="$w" height="$h" visibility="hidden" style="fill:$rectangleColour" opacity="0.95">$anim</rect>\n}; |
|
610 } |
|
611 print "<text $attrs>$text$anim</text>\n"; |
|
612 } |
|
613 |
|
614 sub outputLine() |
|
615 { |
|
616 my ($x1,$y1,$x2,$y2) = @_; |
|
617 my $animTag=''; |
|
618 my $animAttr=''; |
|
619 if($anims) |
|
620 { |
|
621 $animAttr=qq{visibility="hidden"}; |
|
622 $animTag= getAnimStr('remove'); |
|
623 } |
|
624 print qq{<line stroke="black" marker-end="url(#arr)" $animAttr x1="$x1" y1="$y1" x2="$x2" y2="$y2">$animTag</line>\n}; |
|
625 } |
|
626 |
|
627 sub outputVerticalLine() |
|
628 { |
|
629 my ($x,$y1,$y2) = @_; |
|
630 print qq{<line stroke="black" x1="$x" y1="$y1" x2="$x" y2="$y2" />\n}; |
|
631 } |
|
632 |
|
633 |
|
634 |