001: /*
002: * Copyright 2005 JBoss Inc
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016:
017: package org.drools.reteoo;
018:
019: import java.util.Arrays;
020:
021: import org.drools.RuleBaseConfiguration;
022: import org.drools.RuntimeDroolsException;
023: import org.drools.common.BetaConstraints;
024: import org.drools.common.EmptyBetaConstraints;
025: import org.drools.common.InternalFactHandle;
026: import org.drools.common.InternalWorkingMemory;
027: import org.drools.rule.Accumulate;
028: import org.drools.spi.AlphaNodeFieldConstraint;
029: import org.drools.spi.PropagationContext;
030: import org.drools.util.ArrayUtils;
031: import org.drools.util.Entry;
032: import org.drools.util.FactEntry;
033: import org.drools.util.Iterator;
034: import org.drools.util.ObjectHashMap.ObjectEntry;
035:
036: /**
037: * AccumulateNode
038: * A beta node capable of doing accumulate logic.
039: *
040: * Created: 04/06/2006
041: * @author <a href="mailto:tirelli@post.com">Edson Tirelli</a>
042: *
043: * @version $Id: AccumulateNode.java 14503 2007-08-23 22:13:32Z tirelli $
044: */
045: public class AccumulateNode extends BetaNode {
046:
047: private static final long serialVersionUID = 400L;
048:
049: private final boolean unwrapRightObject;
050: private final Accumulate accumulate;
051: private final AlphaNodeFieldConstraint[] resultConstraints;
052: private final BetaConstraints resultBinder;
053:
054: /**
055: * Construct.
056: *
057: * @param id
058: * The id for the node
059: * @param leftInput
060: * The left input <code>TupleSource</code>.
061: * @param rightInput
062: * The right input <code>ObjectSource</code>.
063: * @param accumulate
064: * The accumulate conditional element
065: */
066: AccumulateNode(final int id, final TupleSource leftInput,
067: final ObjectSource rightInput, final Accumulate accumulate) {
068: this (id, leftInput, rightInput,
069: new AlphaNodeFieldConstraint[0], EmptyBetaConstraints
070: .getInstance(), EmptyBetaConstraints
071: .getInstance(), accumulate, false);
072: }
073:
074: public AccumulateNode(final int id, final TupleSource leftInput,
075: final ObjectSource rightInput,
076: final AlphaNodeFieldConstraint[] resultConstraints,
077: final BetaConstraints sourceBinder,
078: final BetaConstraints resultBinder,
079: final Accumulate accumulate, final boolean unwrapRightObject) {
080: super (id, leftInput, rightInput, sourceBinder);
081: this .resultBinder = resultBinder;
082: this .resultConstraints = resultConstraints;
083: this .accumulate = accumulate;
084: this .unwrapRightObject = unwrapRightObject;
085: }
086:
087: /**
088: * @inheritDoc
089: *
090: * When a new tuple is asserted into an AccumulateNode, do this:
091: *
092: * 1. Select all matching objects from right memory
093: * 2. Execute the initialization code using the tuple + matching objects
094: * 3. Execute the accumulation code for each combination of tuple+object
095: * 4. Execute the return code
096: * 5. Create a new CalculatedObjectHandle for the resulting object and add it to the tuple
097: * 6. Propagate the tuple
098: *
099: * The initialization, accumulation and return codes, in JBRules, are assembled
100: * into a generated method code and called once for the whole match, as you can see
101: * bellow:
102: *
103: * Object result = this.accumulator.accumulate( ... );
104: *
105: */
106: public void assertTuple(final ReteTuple leftTuple,
107: final PropagationContext context,
108: final InternalWorkingMemory workingMemory) {
109:
110: final AccumulateMemory memory = (AccumulateMemory) workingMemory
111: .getNodeMemory(this );
112:
113: AccumulateResult accresult = new AccumulateResult();
114:
115: if (!workingMemory.isSequential()) {
116: memory.betaMemory.getTupleMemory().add(leftTuple);
117: memory.betaMemory.getCreatedHandles().put(leftTuple,
118: accresult, false);
119: }
120:
121: final Object accContext = this .accumulate.createContext();
122:
123: accresult.context = accContext;
124: this .accumulate.init(memory.workingMemoryContext, accContext,
125: leftTuple, workingMemory);
126:
127: final Iterator it = memory.betaMemory.getFactHandleMemory()
128: .iterator(leftTuple);
129: this .constraints.updateFromTuple(workingMemory, leftTuple);
130:
131: for (FactEntry entry = (FactEntry) it.next(); entry != null; entry = (FactEntry) it
132: .next()) {
133: InternalFactHandle handle = entry.getFactHandle();
134: if (this .constraints
135: .isAllowedCachedLeft(handle.getObject())) {
136: if (this .unwrapRightObject) {
137: // if there is a subnetwork, handle must be unwrapped
138: handle = ((ReteTuple) handle.getObject())
139: .getLastHandle();
140: }
141: this .accumulate.accumulate(memory.workingMemoryContext,
142: accContext, leftTuple, handle, workingMemory);
143: }
144: }
145:
146: final Object result = this .accumulate.getResult(
147: memory.workingMemoryContext, accContext, leftTuple,
148: workingMemory);
149:
150: if (result == null) {
151: throw new RuntimeDroolsException(
152: "Accumulate must not return a null value.");
153: }
154:
155: // First alpha node filters
156: boolean isAllowed = true;
157: for (int i = 0, length = this .resultConstraints.length; i < length; i++) {
158: if (!this .resultConstraints[i].isAllowed(result,
159: workingMemory)) {
160: isAllowed = false;
161: break;
162: }
163: }
164: if (isAllowed) {
165: this .resultBinder.updateFromTuple(workingMemory, leftTuple);
166: if (this .resultBinder.isAllowedCachedLeft(result)) {
167: final InternalFactHandle handle = workingMemory
168: .getFactHandleFactory().newFactHandle(result);
169: accresult.handle = handle;
170:
171: this .sink.propagateAssertTuple(leftTuple, handle,
172: context, workingMemory);
173: }
174: }
175:
176: }
177:
178: /**
179: * @inheritDoc
180: *
181: * As the accumulate node will always propagate the tuple,
182: * it must always also retreat it.
183: *
184: */
185: public void retractTuple(final ReteTuple leftTuple,
186: final PropagationContext context,
187: final InternalWorkingMemory workingMemory) {
188: final AccumulateMemory memory = (AccumulateMemory) workingMemory
189: .getNodeMemory(this );
190: memory.betaMemory.getTupleMemory().remove(leftTuple);
191: final AccumulateResult accresult = (AccumulateResult) memory.betaMemory
192: .getCreatedHandles().remove(leftTuple);
193:
194: // if tuple was propagated
195: if (accresult.handle != null) {
196: this .sink.propagateRetractTuple(leftTuple,
197: accresult.handle, context, workingMemory);
198:
199: // Destroying the acumulate result object
200: workingMemory.getFactHandleFactory().destroyFactHandle(
201: accresult.handle);
202: }
203:
204: }
205:
206: /**
207: * @inheritDoc
208: *
209: * When a new object is asserted into an AccumulateNode, do this:
210: *
211: * 1. Select all matching tuples from left memory
212: * 2. For each matching tuple, call a modify tuple
213: *
214: */
215: public void assertObject(final InternalFactHandle handle,
216: final PropagationContext context,
217: final InternalWorkingMemory workingMemory) {
218:
219: final AccumulateMemory memory = (AccumulateMemory) workingMemory
220: .getNodeMemory(this );
221: memory.betaMemory.getFactHandleMemory().add(handle);
222:
223: if (workingMemory.isSequential()) {
224: // do nothing here, as we know there are no left tuples at this stage in sequential mode.
225: return;
226: }
227:
228: this .constraints.updateFromFactHandle(workingMemory, handle);
229:
230: // need to clone the tuples to avoid concurrent modification exceptions
231: Entry[] tuples = memory.betaMemory.getTupleMemory().toArray();
232: for (int i = 0; i < tuples.length; i++) {
233: ReteTuple tuple = (ReteTuple) tuples[i];
234: if (this .constraints.isAllowedCachedRight(tuple)) {
235: if (this .accumulate.supportsReverse()
236: || context.getType() == PropagationContext.ASSERTION) {
237: modifyTuple(true, tuple, handle, context,
238: workingMemory);
239: } else {
240: // context is MODIFICATION and does not supports reverse
241: this .retractTuple(tuple, context, workingMemory);
242: this .assertTuple(tuple, context, workingMemory);
243: }
244: }
245: }
246: }
247:
248: /**
249: * @inheritDoc
250: *
251: * If an object is retract, call modify tuple for each
252: * tuple match.
253: */
254: public void retractObject(final InternalFactHandle handle,
255: final PropagationContext context,
256: final InternalWorkingMemory workingMemory) {
257: final AccumulateMemory memory = (AccumulateMemory) workingMemory
258: .getNodeMemory(this );
259: if (!memory.betaMemory.getFactHandleMemory().remove(handle)) {
260: return;
261: }
262:
263: this .constraints.updateFromFactHandle(workingMemory, handle);
264: // need to clone the tuples to avoid concurrent modification exceptions
265: Entry[] tuples = memory.betaMemory.getTupleMemory().toArray();
266: for (int i = 0; i < tuples.length; i++) {
267: ReteTuple tuple = (ReteTuple) tuples[i];
268: if (this .constraints.isAllowedCachedRight(tuple)) {
269: if (this .accumulate.supportsReverse()) {
270: this .modifyTuple(false, tuple, handle, context,
271: workingMemory);
272: } else {
273: this .retractTuple(tuple, context, workingMemory);
274: this .assertTuple(tuple, context, workingMemory);
275: }
276: }
277: }
278: }
279:
280: public void modifyTuple(final boolean isAssert,
281: final ReteTuple leftTuple, InternalFactHandle handle,
282: final PropagationContext context,
283: final InternalWorkingMemory workingMemory) {
284:
285: final AccumulateMemory memory = (AccumulateMemory) workingMemory
286: .getNodeMemory(this );
287: AccumulateResult accresult = (AccumulateResult) memory.betaMemory
288: .getCreatedHandles().get(leftTuple);
289:
290: // if tuple was propagated
291: if (accresult.handle != null) {
292: this .sink.propagateRetractTuple(leftTuple,
293: accresult.handle, context, workingMemory);
294:
295: // Destroying the acumulate result object
296: workingMemory.getFactHandleFactory().destroyFactHandle(
297: accresult.handle);
298: accresult.handle = null;
299: }
300:
301: if (this .unwrapRightObject) {
302: // if there is a subnetwork, handle must be unwrapped
303: handle = ((ReteTuple) handle.getObject()).getLastHandle();
304: }
305:
306: if (context.getType() == PropagationContext.ASSERTION) {
307: // assertion
308: if (accresult.context == null) {
309: final Object accContext = this .accumulate
310: .createContext();
311:
312: this .accumulate.init(memory.workingMemoryContext,
313: accContext, leftTuple, workingMemory);
314:
315: accresult.context = accContext;
316: }
317:
318: this .accumulate
319: .accumulate(memory.workingMemoryContext,
320: accresult.context, leftTuple, handle,
321: workingMemory);
322: } else if (context.getType() == PropagationContext.MODIFICATION) {
323: // modification
324: if (isAssert) {
325: this .accumulate.accumulate(memory.workingMemoryContext,
326: accresult.context, leftTuple, handle,
327: workingMemory);
328: } else {
329: this .accumulate.reverse(memory.workingMemoryContext,
330: accresult.context, leftTuple, handle,
331: workingMemory);
332: }
333: } else {
334: // retraction
335: this .accumulate
336: .reverse(memory.workingMemoryContext,
337: accresult.context, leftTuple, handle,
338: workingMemory);
339: }
340:
341: final Object result = this .accumulate.getResult(
342: memory.workingMemoryContext, accresult.context,
343: leftTuple, workingMemory);
344:
345: if (result == null) {
346: throw new RuntimeDroolsException(
347: "Accumulate must not return a null value.");
348: }
349:
350: // First alpha node filters
351: boolean isAllowed = true;
352: for (int i = 0, length = this .resultConstraints.length; i < length; i++) {
353: if (!this .resultConstraints[i].isAllowed(result,
354: workingMemory)) {
355: isAllowed = false;
356: break;
357: }
358: }
359: if (isAllowed) {
360: this .resultBinder.updateFromTuple(workingMemory, leftTuple);
361: if (this .resultBinder.isAllowedCachedLeft(result)) {
362: final InternalFactHandle createdHandle = workingMemory
363: .getFactHandleFactory().newFactHandle(result);
364: accresult.handle = createdHandle;
365:
366: this .sink.propagateAssertTuple(leftTuple,
367: createdHandle, context, workingMemory);
368: }
369: }
370: }
371:
372: public void updateSink(final TupleSink sink,
373: final PropagationContext context,
374: final InternalWorkingMemory workingMemory) {
375: final AccumulateMemory memory = (AccumulateMemory) workingMemory
376: .getNodeMemory(this );
377:
378: final Iterator it = memory.betaMemory.getCreatedHandles()
379: .iterator();
380:
381: for (ObjectEntry entry = (ObjectEntry) it.next(); entry != null; entry = (ObjectEntry) it
382: .next()) {
383: AccumulateResult accresult = (AccumulateResult) entry
384: .getValue();
385: sink.assertTuple(new ReteTuple((ReteTuple) entry.getKey(),
386: accresult.handle), context, workingMemory);
387: }
388: }
389:
390: /* (non-Javadoc)
391: * @see org.drools.reteoo.BaseNode#hashCode()
392: */
393: public int hashCode() {
394: return this .leftInput.hashCode() ^ this .rightInput.hashCode()
395: ^ this .accumulate.hashCode()
396: ^ this .resultBinder.hashCode()
397: ^ ArrayUtils.hashCode(this .resultConstraints);
398: }
399:
400: /* (non-Javadoc)
401: * @see java.lang.Object#equals(java.lang.Object)
402: */
403: public boolean equals(final Object object) {
404: if (this == object) {
405: return true;
406: }
407:
408: if (object == null || !(object instanceof AccumulateNode)) {
409: return false;
410: }
411:
412: final AccumulateNode other = (AccumulateNode) object;
413:
414: if (this .getClass() != other.getClass()
415: || (!this .leftInput.equals(other.leftInput))
416: || (!this .rightInput.equals(other.rightInput))
417: || (!this .constraints.equals(other.constraints))) {
418: return false;
419: }
420:
421: return this .accumulate.equals(other.accumulate)
422: && resultBinder.equals(other.resultBinder)
423: && Arrays.equals(this .resultConstraints,
424: other.resultConstraints);
425: }
426:
427: public String toString() {
428: return "[ " + this .getClass().getName() + "(" + this .id + ") ]";
429: }
430:
431: /**
432: * Creates a BetaMemory for the BetaNode's memory.
433: */
434: public Object createMemory(final RuleBaseConfiguration config) {
435: AccumulateMemory memory = new AccumulateMemory();
436: memory.betaMemory = this .constraints.createBetaMemory(config);
437: memory.workingMemoryContext = this .accumulate
438: .createWorkingMemoryContext();
439: return memory;
440: }
441:
442: public static class AccumulateMemory {
443: private static final long serialVersionUID = 400L;
444:
445: public Object workingMemoryContext;
446: public BetaMemory betaMemory;
447: }
448:
449: private static class AccumulateResult {
450: // keeping attributes public just for performance
451: public InternalFactHandle handle;
452: public Object context;
453: }
454:
455: }
|