001: package org.drools.integrationtests;
002:
003: import java.io.IOException;
004: import java.io.InputStreamReader;
005: import java.io.Reader;
006: import java.util.ArrayList;
007: import java.util.List;
008:
009: import junit.framework.Assert;
010: import junit.framework.TestCase;
011:
012: import org.drools.Cheese;
013: import org.drools.FactHandle;
014: import org.drools.Person;
015: import org.drools.PersonInterface;
016: import org.drools.RuleBase;
017: import org.drools.RuleBaseConfiguration;
018: import org.drools.RuleBaseFactory;
019: import org.drools.WorkingMemory;
020: import org.drools.common.DefaultAgenda;
021: import org.drools.common.InternalWorkingMemoryActions;
022: import org.drools.common.RuleFlowGroupImpl;
023: import org.drools.compiler.DrlParser;
024: import org.drools.compiler.DroolsParserException;
025: import org.drools.compiler.PackageBuilder;
026: import org.drools.compiler.ProcessBuilder;
027: import org.drools.event.ActivationCancelledEvent;
028: import org.drools.event.ActivationCreatedEvent;
029: import org.drools.event.AgendaEventListener;
030: import org.drools.event.DefaultAgendaEventListener;
031: import org.drools.integrationtests.helloworld.Message;
032: import org.drools.lang.descr.PackageDescr;
033: import org.drools.rule.Package;
034: import org.drools.ruleflow.common.instance.ProcessInstance;
035: import org.drools.spi.Activation;
036: import org.drools.spi.ActivationGroup;
037: import org.drools.spi.AgendaGroup;
038:
039: public class ExecutionFlowControlTest extends TestCase {
040: protected RuleBase getRuleBase() throws Exception {
041:
042: return RuleBaseFactory.newRuleBase(RuleBase.RETEOO, null);
043: }
044:
045: protected RuleBase getRuleBase(final RuleBaseConfiguration config)
046: throws Exception {
047:
048: return RuleBaseFactory.newRuleBase(RuleBase.RETEOO, config);
049: }
050:
051: public void testSalienceInteger() throws Exception {
052: final PackageBuilder builder = new PackageBuilder();
053: builder.addPackageFromDrl(new InputStreamReader(getClass()
054: .getResourceAsStream("test_salienceIntegerRule.drl")));
055: final Package pkg = builder.getPackage();
056:
057: final RuleBase ruleBase = getRuleBase();
058: ruleBase.addPackage(pkg);
059: final WorkingMemory workingMemory = ruleBase
060: .newStatefulSession();
061:
062: final List list = new ArrayList();
063: workingMemory.setGlobal("list", list);
064:
065: final PersonInterface person = new Person("Edson", "cheese");
066: workingMemory.insert(person);
067:
068: workingMemory.fireAllRules();
069:
070: Assert.assertEquals("Two rules should have been fired", 2, list
071: .size());
072: Assert.assertEquals("Rule 3 should have been fired first",
073: "Rule 3", list.get(0));
074: Assert.assertEquals("Rule 2 should have been fired second",
075: "Rule 2", list.get(1));
076: }
077:
078: public void testSalienceExpression() throws Exception {
079: final PackageBuilder builder = new PackageBuilder();
080: builder
081: .addPackageFromDrl(new InputStreamReader(getClass()
082: .getResourceAsStream(
083: "test_salienceExpressionRule.drl")));
084: final Package pkg = builder.getPackage();
085:
086: final RuleBase ruleBase = getRuleBase();
087: ruleBase.addPackage(pkg);
088: final WorkingMemory workingMemory = ruleBase
089: .newStatefulSession();
090:
091: final List list = new ArrayList();
092: workingMemory.setGlobal("list", list);
093:
094: final PersonInterface person10 = new Person("bob", "cheese", 10);
095: workingMemory.insert(person10);
096:
097: final PersonInterface person20 = new Person("mic", "cheese", 20);
098: workingMemory.insert(person20);
099:
100: workingMemory.fireAllRules();
101:
102: Assert.assertEquals("Two rules should have been fired", 2, list
103: .size());
104: Assert.assertEquals("Rule 3 should have been fired first",
105: "Rule 3", list.get(0));
106: Assert.assertEquals("Rule 2 should have been fired second",
107: "Rule 2", list.get(1));
108: }
109:
110: public void testNoLoop() throws Exception {
111: final PackageBuilder builder = new PackageBuilder();
112: builder.addPackageFromDrl(new InputStreamReader(getClass()
113: .getResourceAsStream("no-loop.drl")));
114: final Package pkg = builder.getPackage();
115:
116: final RuleBase ruleBase = getRuleBase();
117: ruleBase.addPackage(pkg);
118: final WorkingMemory workingMemory = ruleBase
119: .newStatefulSession();
120:
121: final List list = new ArrayList();
122: workingMemory.setGlobal("list", list);
123:
124: final Cheese brie = new Cheese("brie", 12);
125: workingMemory.insert(brie);
126:
127: workingMemory.fireAllRules();
128:
129: Assert.assertEquals(
130: "Should not loop and thus size should be 1", 1, list
131: .size());
132:
133: }
134:
135: public void testLockOnActive() throws Exception {
136: final PackageBuilder builder = new PackageBuilder();
137: builder.addPackageFromDrl(new InputStreamReader(getClass()
138: .getResourceAsStream("test_LockOnActive.drl")));
139: final Package pkg = builder.getPackage();
140:
141: final RuleBase ruleBase = getRuleBase();
142: ruleBase.addPackage(pkg);
143: final WorkingMemory workingMemory = ruleBase
144: .newStatefulSession();
145:
146: final List list = new ArrayList();
147: workingMemory.setGlobal("list", list);
148:
149: // AgendaGroup "group1" is not active, so should receive activation
150: final Cheese brie12 = new Cheese("brie", 12);
151: workingMemory.insert(brie12);
152: final AgendaGroup group1 = workingMemory.getAgenda()
153: .getAgendaGroup("group1");
154: assertEquals(1, group1.size());
155:
156: workingMemory.setFocus("group1");
157: // AgendaqGroup "group1" is now active, so should not receive activations
158: final Cheese brie10 = new Cheese("brie", 10);
159: workingMemory.insert(brie10);
160: assertEquals(1, group1.size());
161:
162: final Cheese cheddar20 = new Cheese("cheddar", 20);
163: workingMemory.insert(cheddar20);
164: final AgendaGroup group2 = workingMemory.getAgenda()
165: .getAgendaGroup("group1");
166: assertEquals(1, group2.size());
167:
168: final RuleFlowGroupImpl rfg = (RuleFlowGroupImpl) workingMemory
169: .getAgenda().getRuleFlowGroup("ruleflow2");
170: rfg.setActive(true);
171: final Cheese cheddar17 = new Cheese("cheddar", 17);
172: workingMemory.insert(cheddar17);
173: assertEquals(1, group2.size());
174: }
175:
176: public void testLockOnActiveWithModify() throws Exception {
177: final PackageBuilder builder = new PackageBuilder();
178: builder
179: .addPackageFromDrl(new InputStreamReader(getClass()
180: .getResourceAsStream(
181: "test_LockOnActiveWithUpdate.drl")));
182: final Package pkg = builder.getPackage();
183:
184: final RuleBase ruleBase = getRuleBase();
185: ruleBase.addPackage(pkg);
186: final WorkingMemory wm = ruleBase.newStatefulSession();
187:
188: final List list = new ArrayList();
189: wm.setGlobal("list", list);
190:
191: final Cheese brie = new Cheese("brie", 13);
192:
193: final Person bob = new Person("bob");
194: bob.setCheese(brie);
195:
196: final Person mic = new Person("mic");
197: mic.setCheese(brie);
198:
199: final Person mark = new Person("mark");
200: mark.setCheese(brie);
201:
202: final FactHandle brieHandle = wm.insert(brie);
203: wm.insert(bob);
204: wm.insert(mic);
205: wm.insert(mark);
206:
207: final DefaultAgenda agenda = (DefaultAgenda) wm.getAgenda();
208: final AgendaGroup group1 = agenda.getAgendaGroup("group1");
209: agenda.setFocus(group1);
210: assertEquals(3, group1.size());
211: agenda.fireNextItem(null);
212: assertEquals(2, group1.size());
213: wm.update(brieHandle, brie);
214: assertEquals(2, group1.size());
215:
216: AgendaGroup group2 = agenda.getAgendaGroup("group2");
217: agenda.setFocus(group2);
218:
219: RuleFlowGroupImpl rfg = (RuleFlowGroupImpl) wm.getAgenda()
220: .getRuleFlowGroup("ruleflow2");
221: assertEquals(3, rfg.size());
222:
223: agenda.activateRuleFlowGroup("ruleflow2");
224: agenda.fireNextItem(null);
225: assertEquals(2, rfg.size());
226: wm.update(brieHandle, brie);
227: assertEquals(2, group2.size());
228: }
229:
230: public void testAgendaGroups() throws Exception {
231: final PackageBuilder builder = new PackageBuilder();
232: builder.addPackageFromDrl(new InputStreamReader(getClass()
233: .getResourceAsStream("test_AgendaGroups.drl")));
234: final Package pkg = builder.getPackage();
235:
236: final RuleBase ruleBase = getRuleBase();
237: ruleBase.addPackage(pkg);
238: final WorkingMemory workingMemory = ruleBase
239: .newStatefulSession();
240:
241: final List list = new ArrayList();
242: workingMemory.setGlobal("list", list);
243:
244: final Cheese brie = new Cheese("brie", 12);
245: workingMemory.insert(brie);
246:
247: workingMemory.fireAllRules();
248:
249: assertEquals(7, list.size());
250:
251: assertEquals("group3", list.get(0));
252: assertEquals("group4", list.get(1));
253: assertEquals("group3", list.get(2));
254: assertEquals("MAIN", list.get(3));
255: assertEquals("group1", list.get(4));
256: assertEquals("group1", list.get(5));
257: assertEquals("MAIN", list.get(6));
258:
259: workingMemory.setFocus("group2");
260: workingMemory.fireAllRules();
261:
262: assertEquals(8, list.size());
263: assertEquals("group2", list.get(7));
264: }
265:
266: public void testActivationGroups() throws Exception {
267: final PackageBuilder builder = new PackageBuilder();
268: builder.addPackageFromDrl(new InputStreamReader(getClass()
269: .getResourceAsStream("test_ActivationGroups.drl")));
270: final Package pkg = builder.getPackage();
271:
272: final RuleBase ruleBase = getRuleBase();
273: ruleBase.addPackage(pkg);
274: final WorkingMemory workingMemory = ruleBase
275: .newStatefulSession();
276:
277: final List list = new ArrayList();
278: workingMemory.setGlobal("list", list);
279:
280: final Cheese brie = new Cheese("brie", 12);
281: workingMemory.insert(brie);
282:
283: final ActivationGroup activationGroup0 = workingMemory
284: .getAgenda().getActivationGroup("activation-group-0");
285: assertEquals(2, activationGroup0.size());
286:
287: final ActivationGroup activationGroup3 = workingMemory
288: .getAgenda().getActivationGroup("activation-group-3");
289: assertEquals(1, activationGroup3.size());
290:
291: final AgendaGroup agendaGroup3 = workingMemory.getAgenda()
292: .getAgendaGroup("agenda-group-3");
293: assertEquals(1, agendaGroup3.size());
294:
295: final AgendaGroup agendaGroupMain = workingMemory.getAgenda()
296: .getAgendaGroup("MAIN");
297: assertEquals(3, agendaGroupMain.size());
298:
299: workingMemory.clearAgendaGroup("agenda-group-3");
300: assertEquals(0, activationGroup3.size());
301: assertEquals(0, agendaGroup3.size());
302:
303: workingMemory.fireAllRules();
304:
305: assertEquals(0, activationGroup0.size());
306:
307: assertEquals(2, list.size());
308: assertEquals("rule0", list.get(0));
309: assertEquals("rule2", list.get(1));
310:
311: }
312:
313: public void testDuration() throws Exception {
314: final PackageBuilder builder = new PackageBuilder();
315: builder.addPackageFromDrl(new InputStreamReader(getClass()
316: .getResourceAsStream("test_Duration.drl")));
317: final Package pkg = builder.getPackage();
318:
319: final RuleBase ruleBase = getRuleBase();
320: ruleBase.addPackage(pkg);
321: final WorkingMemory workingMemory = ruleBase
322: .newStatefulSession();
323:
324: final List list = new ArrayList();
325: workingMemory.setGlobal("list", list);
326:
327: final Cheese brie = new Cheese("brie", 12);
328: final FactHandle brieHandle = workingMemory.insert(brie);
329:
330: workingMemory.fireAllRules();
331:
332: // now check for update
333: assertEquals(0, list.size());
334:
335: // sleep for 300ms
336: Thread.sleep(300);
337:
338: // now check for update
339: assertEquals(1, list.size());
340:
341: }
342:
343: public void testInsertRetractNoloop() throws Exception {
344: // read in the source
345: final Reader reader = new InputStreamReader(getClass()
346: .getResourceAsStream("test_Insert_Retract_Noloop.drl"));
347: final RuleBase ruleBase = loadRuleBase(reader);
348:
349: final WorkingMemory wm = ruleBase.newStatefulSession();
350: wm.insert(new Cheese("stilton", 15));
351:
352: wm.fireAllRules();
353: }
354:
355: public void testDurationWithNoLoop() throws Exception {
356: final PackageBuilder builder = new PackageBuilder();
357: builder.addPackageFromDrl(new InputStreamReader(getClass()
358: .getResourceAsStream("test_Duration_with_NoLoop.drl")));
359: final Package pkg = builder.getPackage();
360:
361: final RuleBase ruleBase = getRuleBase();
362: ruleBase.addPackage(pkg);
363: final WorkingMemory workingMemory = ruleBase
364: .newStatefulSession();
365:
366: final List list = new ArrayList();
367: workingMemory.setGlobal("list", list);
368:
369: final Cheese brie = new Cheese("brie", 12);
370: final FactHandle brieHandle = workingMemory.insert(brie);
371:
372: workingMemory.fireAllRules();
373:
374: // now check for update
375: assertEquals(0, list.size());
376:
377: // sleep for 300ms
378: Thread.sleep(300);
379:
380: // now check for update
381: assertEquals(1, list.size());
382: }
383:
384: public void testFireRuleAfterDuration() throws Exception {
385: final PackageBuilder builder = new PackageBuilder();
386: builder
387: .addPackageFromDrl(new InputStreamReader(getClass()
388: .getResourceAsStream(
389: "test_FireRuleAfterDuration.drl")));
390: final Package pkg = builder.getPackage();
391:
392: final RuleBase ruleBase = getRuleBase();
393: ruleBase.addPackage(pkg);
394: final WorkingMemory workingMemory = ruleBase
395: .newStatefulSession();
396:
397: final List list = new ArrayList();
398: workingMemory.setGlobal("list", list);
399:
400: final Cheese brie = new Cheese("brie", 12);
401: final FactHandle brieHandle = workingMemory.insert(brie);
402:
403: workingMemory.fireAllRules();
404:
405: // now check for update
406: assertEquals(0, list.size());
407:
408: // sleep for 300ms
409: Thread.sleep(300);
410:
411: workingMemory.fireAllRules();
412:
413: // now check for update
414: assertEquals(2, list.size());
415:
416: }
417:
418: public void testUpdateNoLoop() throws Exception {
419: // JBRULES-780, throws a NullPointer or infinite loop if there is an issue
420: final Reader reader = new InputStreamReader(getClass()
421: .getResourceAsStream("test_UpdateNoloop.drl"));
422: final RuleBase ruleBase = loadRuleBase(reader);
423:
424: final WorkingMemory wm = ruleBase.newStatefulSession();
425: wm.insert(new Cheese("stilton", 15));
426:
427: wm.fireAllRules();
428: }
429:
430: public void testUpdateActivationCreationNoLoop() throws Exception {
431: // JBRULES-787, no-loop blocks all dependant tuples for update
432: final Reader reader = new InputStreamReader(getClass()
433: .getResourceAsStream(
434: "test_UpdateActivationCreationNoLoop.drl"));
435: final RuleBase ruleBase = loadRuleBase(reader);
436:
437: final InternalWorkingMemoryActions wm = (InternalWorkingMemoryActions) ruleBase
438: .newStatefulSession();
439: final List created = new ArrayList();
440: final List cancelled = new ArrayList();
441: final AgendaEventListener l = new DefaultAgendaEventListener() {
442: public void activationCreated(ActivationCreatedEvent event,
443: WorkingMemory workingMemory) {
444: created.add(event);
445: }
446:
447: public void activationCancelled(
448: ActivationCancelledEvent event,
449: WorkingMemory workingMemory) {
450: cancelled.add(event);
451: }
452:
453: };
454:
455: wm.addEventListener(l);
456:
457: final Cheese stilton = new Cheese("stilton", 15);
458: final FactHandle stiltonHandle = wm.insert(stilton);
459:
460: final Person p1 = new Person("p1");
461: p1.setCheese(stilton);
462: wm.insert(p1);
463:
464: final Person p2 = new Person("p2");
465: p2.setCheese(stilton);
466: wm.insert(p2);
467:
468: final Person p3 = new Person("p3");
469: p3.setCheese(stilton);
470: wm.insert(p3);
471:
472: assertEquals(3, created.size());
473: assertEquals(0, cancelled.size());
474:
475: final Activation item = ((ActivationCreatedEvent) created
476: .get(2)).getActivation();
477:
478: // simulate a modify inside a consequence
479: wm.update(stiltonHandle, stilton, item.getRule(), item);
480:
481: // the two of the three tuples should re-activate
482: assertEquals(5, created.size());
483: assertEquals(3, cancelled.size());
484: }
485:
486: public void testRuleFlowGroup() throws Exception {
487: final PackageBuilder builder = new PackageBuilder();
488: builder.addPackageFromDrl(new InputStreamReader(getClass()
489: .getResourceAsStream("ruleflowgroup.drl")));
490: final Package pkg = builder.getPackage();
491:
492: final RuleBase ruleBase = getRuleBase();
493: ruleBase.addPackage(pkg);
494:
495: final WorkingMemory workingMemory = ruleBase
496: .newStatefulSession();
497: final List list = new ArrayList();
498: workingMemory.setGlobal("list", list);
499:
500: workingMemory.insert("Test");
501: workingMemory.fireAllRules();
502: assertEquals(0, list.size());
503:
504: workingMemory.getAgenda().activateRuleFlowGroup("Group1");
505: workingMemory.fireAllRules();
506:
507: assertEquals(1, list.size());
508: }
509:
510: public void testRuleFlow() throws Exception {
511: final PackageBuilder builder = new PackageBuilder();
512: builder.addPackageFromDrl(new InputStreamReader(getClass()
513: .getResourceAsStream("ruleflow.drl")));
514: builder.addRuleFlow(new InputStreamReader(getClass()
515: .getResourceAsStream("ruleflow.rfm")));
516: final Package pkg = builder.getPackage();
517: final RuleBase ruleBase = getRuleBase();
518: ruleBase.addPackage(pkg);
519:
520: final WorkingMemory workingMemory = ruleBase
521: .newStatefulSession();
522: final List list = new ArrayList();
523: workingMemory.setGlobal("list", list);
524:
525: workingMemory.fireAllRules();
526: assertEquals(0, list.size());
527:
528: final ProcessInstance processInstance = workingMemory
529: .startProcess("0");
530: assertEquals(ProcessInstance.STATE_ACTIVE, processInstance
531: .getState());
532: workingMemory.fireAllRules();
533: assertEquals(4, list.size());
534: assertEquals("Rule1", list.get(0));
535: assertEquals("Rule3", list.get(1));
536: assertEquals("Rule2", list.get(2));
537: assertEquals("Rule4", list.get(3));
538: assertEquals(ProcessInstance.STATE_COMPLETED, processInstance
539: .getState());
540: }
541:
542: public void testRuleFlowClear() throws Exception {
543: final PackageBuilder builder = new PackageBuilder();
544: builder.addPackageFromDrl(new InputStreamReader(getClass()
545: .getResourceAsStream("test_ruleflowClear.drl")));
546: builder.addRuleFlow(new InputStreamReader(getClass()
547: .getResourceAsStream("test_ruleflowClear.rfm")));
548: final Package pkg = builder.getPackage();
549: final RuleBase ruleBase = getRuleBase();
550: ruleBase.addPackage(pkg);
551:
552: final WorkingMemory workingMemory = ruleBase
553: .newStatefulSession();
554: final List list = new ArrayList();
555: workingMemory.setGlobal("list", list);
556:
557: final List activations = new ArrayList();
558: AgendaEventListener listener = new DefaultAgendaEventListener() {
559: public void activationCancelled(
560: ActivationCancelledEvent event,
561: WorkingMemory workingMemory) {
562: activations.add(event.getActivation());
563: }
564: };
565:
566: workingMemory.addEventListener(listener);
567:
568: assertEquals(0, workingMemory.getAgenda().getRuleFlowGroup(
569: "flowgroup-1").size());
570:
571: // We need to call fireAllRules here to get the InitialFact into the system, to the eval(true)'s kick in
572: workingMemory.fireAllRules();
573:
574: // Now we have 4 in the RuleFlow, but not yet in the agenda
575: assertEquals(4, workingMemory.getAgenda().getRuleFlowGroup(
576: "flowgroup-1").size());
577:
578: // Check they aren't in the Agenda
579: assertEquals(0, workingMemory.getAgenda()
580: .getAgendaGroup("MAIN").size());
581:
582: // Start the process, which shoudl populate the Agenda
583: final ProcessInstance processInstance = workingMemory
584: .startProcess("ruleFlowClear");
585: assertEquals(4, workingMemory.getAgenda()
586: .getAgendaGroup("MAIN").size());
587:
588: // Check we have 0 activation cancellation events
589: assertEquals(0, activations.size());
590:
591: workingMemory.getAgenda().clearRuleFlowGroup("flowgroup-1");
592:
593: // Check the AgendaGroup and RuleFlowGroup are now empty
594: assertEquals(0, workingMemory.getAgenda()
595: .getAgendaGroup("MAIN").size());
596: assertEquals(0, workingMemory.getAgenda().getRuleFlowGroup(
597: "flowgroup-1").size());
598:
599: // Check we have four activation cancellation events
600: assertEquals(4, activations.size());
601: }
602:
603: public void testRuleFlowInPackage() throws Exception {
604: final PackageBuilder builder = new PackageBuilder();
605: builder.addPackageFromDrl(new InputStreamReader(getClass()
606: .getResourceAsStream("ruleflow.drl")));
607: builder.addRuleFlow(new InputStreamReader(getClass()
608: .getResourceAsStream("ruleflow.rfm")));
609: final Package pkg = builder.getPackage();
610:
611: final RuleBase ruleBase = getRuleBase();
612: ruleBase.addPackage(pkg);
613:
614: final WorkingMemory workingMemory = ruleBase
615: .newStatefulSession();
616: final List list = new ArrayList();
617: workingMemory.setGlobal("list", list);
618:
619: workingMemory.fireAllRules();
620: assertEquals(0, list.size());
621:
622: final ProcessInstance processInstance = workingMemory
623: .startProcess("0");
624: assertEquals(ProcessInstance.STATE_ACTIVE, processInstance
625: .getState());
626: workingMemory.fireAllRules();
627: assertEquals(4, list.size());
628: assertEquals("Rule1", list.get(0));
629: assertEquals("Rule3", list.get(1));
630: assertEquals("Rule2", list.get(2));
631: assertEquals("Rule4", list.get(3));
632: assertEquals(ProcessInstance.STATE_COMPLETED, processInstance
633: .getState());
634:
635: }
636:
637: private RuleBase loadRuleBase(final Reader reader)
638: throws IOException, DroolsParserException, Exception {
639: final DrlParser parser = new DrlParser();
640: final PackageDescr packageDescr = parser.parse(reader);
641: if (parser.hasErrors()) {
642: Assert
643: .fail("Error messages in parser, need to sort this our (or else collect error messages)");
644: }
645: // pre build the package
646: final PackageBuilder builder = new PackageBuilder();
647: builder.addPackage(packageDescr);
648: final Package pkg = builder.getPackage();
649:
650: // add the package to a rulebase
651: final RuleBase ruleBase = getRuleBase();
652: ruleBase.addPackage(pkg);
653: // load up the rulebase
654: return ruleBase;
655: }
656:
657: public void testDateEffective() throws Exception {
658: // read in the source
659: final Reader reader = new InputStreamReader(getClass()
660: .getResourceAsStream("test_EffectiveDate.drl"));
661: final RuleBase ruleBase = loadRuleBase(reader);
662:
663: final WorkingMemory workingMemory = ruleBase
664: .newStatefulSession();
665:
666: final List list = new ArrayList();
667: workingMemory.setGlobal("list", list);
668:
669: // go !
670: final Message message = new Message("hola");
671: workingMemory.insert(message);
672: workingMemory.fireAllRules();
673: assertFalse(message.isFired());
674:
675: }
676: }
|