package org.jactr.core.production.request; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Iterator; import javolution.util.FastList; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.jactr.core.chunk.IChunk; import org.jactr.core.chunk.ISymbolicChunk; import org.jactr.core.chunktype.IChunkType; import org.jactr.core.model.IModel; import org.jactr.core.production.VariableBindings; import org.jactr.core.production.condition.CannotMatchException; import org.jactr.core.production.condition.match.ChunkTypeMatchFailure; import org.jactr.core.production.condition.match.LogicMatchFailure; import org.jactr.core.production.condition.match.SlotMatchFailure; import org.jactr.core.production.condition.match.UnresolvedVariablesMatchFailure; import org.jactr.core.slot.DefaultConditionalSlot; import org.jactr.core.slot.IConditionalSlot; import org.jactr.core.slot.ILogicalSlot; import org.jactr.core.slot.IMutableVariableNameSlot; import org.jactr.core.slot.ISlot; import org.jactr.core.slot.ISlotContainer; import org.jactr.core.slot.IUniqueSlotContainer; import org.jactr.core.slot.IVariableNameSlot; /* * default logging */ /** * basic slot based request */ public class SlotBasedRequest implements IRequest, ISlotContainer { /** * Logger definition */ static private final transient Log LOGGER = LogFactory .getLog(SlotBasedRequest.class); protected Collection<ISlot> _slots; protected Collection<ISlot> _unresolved; private boolean _locked = false; @SuppressWarnings("unchecked") public SlotBasedRequest() { this(Collections.EMPTY_LIST); } public SlotBasedRequest(Collection<? extends ISlot> slots) { _slots = new ArrayList<ISlot>(Math.max(slots.size(), 5)); for (ISlot slot : slots) addSlot(slot); // if (slot instanceof ILogicalSlot) // _slots.add(new DefaultLogicalSlot(slot)); // else // _slots.add(new DefaultVariableConditionalSlot(slot)); } /** * returns the number of slots in this container that match those in the * provided container. * * @param container * @return */ public int countMatches(IChunk chunk, VariableBindings bindings) { ISymbolicChunk sc = chunk.getSymbolicChunk(); String name = sc.getName(); FastList<ISlot> slots = FastList.newInstance(); getSlots(slots); int count = 0; for (ISlot slot : slots) try { resolveSlot(slot, bindings, name, sc); count++; } catch (Exception e) { } return count; } protected boolean resolveSlot(ISlot slot, VariableBindings bindings, String slotContainerName, IUniqueSlotContainer container) throws CannotMatchException { if (slot instanceof IConditionalSlot) return resolveConditionalSlot((IConditionalSlot) slot, bindings, slotContainerName, container); else if (slot instanceof ILogicalSlot) return resolveLogicalSlot((ILogicalSlot) slot, bindings, slotContainerName, container); if (LOGGER.isWarnEnabled()) LOGGER .warn(String .format( "A slot other than conditional or logical was attempted to resolve? %s", slot)); return false; } /** * resolve a logical slot. We do this by recursing down and only checking on * the return result and exceptions based on the logical condition * * @param slotToResolve * @param bindings * @param slotContainerName * @param container * @return * @throws CannotMatchException */ protected boolean resolveLogicalSlot(ILogicalSlot slotToResolve, VariableBindings bindings, String slotContainerName, IUniqueSlotContainer container) throws CannotMatchException { FastList<ISlot> slots = FastList.newInstance(); int op = slotToResolve.getOperator(); try { slotToResolve.getSlots(slots); boolean anyIsResolved = false; CannotMatchException anyException = null; for (ISlot slot : slots) try { boolean resolved = resolveSlot(slot, bindings, slotContainerName, container); // for or if (resolved) anyIsResolved = true; // any unresolved w/ AND if (!resolved && op == ILogicalSlot.AND) return false; } catch (CannotMatchException cme) { anyException = cme; if (op == ILogicalSlot.AND) throw cme; } if (op == ILogicalSlot.OR) { /* * if any was resolved, we return true. we ignore the CME */ if (LOGGER.isDebugEnabled()) LOGGER.debug(String.format("%s was%s resolved", slotToResolve, anyIsResolved ? "" : "nt")); if (!anyIsResolved) throw anyException; return anyIsResolved; } else if (op == ILogicalSlot.AND) { /* * we short circuit AND above. */ if (LOGGER.isDebugEnabled()) LOGGER.debug(String.format( "No exceptions or unresolved slots for %s", slotToResolve)); } else if (op == ILogicalSlot.NOT) /* * NOTs are expecting an exception., but they should still be resolved */ if (anyException == null) throw new CannotMatchException(new LogicMatchFailure(container, slotToResolve)); // otherwise return true; } finally { FastList.recycle(slots); } } /** * attempt to resolve a single (non logical) slot. this will attempt to * resolve variable names and values, in addition to extending the binding set * (if there is a container to match against). Impossible matching errors may * result in cannot match. * * @param slotToResolve * @param model * @param bindings * @param slotContainer * @return true if it is fully resolved, false if it cannot be resolved at * present */ protected boolean resolveConditionalSlot(IConditionalSlot slotToResolve, VariableBindings bindings, String slotContainerName, IUniqueSlotContainer slotContainer) throws CannotMatchException { /* * the name is a variable.. */ if (slotToResolve instanceof IMutableVariableNameSlot && ((IMutableVariableNameSlot) slotToResolve).isVariableName()) { String variableName = slotToResolve.getName().toLowerCase(); /* * if we can resolve, do so, otherwise, mark as unresolved for now */ if (bindings.isBound(variableName)) { Object obj = bindings.get(variableName); ((IMutableVariableNameSlot) slotToResolve).setName(obj.toString()); if (LOGGER.isDebugEnabled()) LOGGER.debug(String.format("slot name %s resolved to %s", variableName, obj.toString())); } else { if (LOGGER.isDebugEnabled()) LOGGER.debug(String.format("slot name %s unresolved", variableName)); return false; } } /* * resolve the value using the variable bindings */ /* * the simple case is pure binding, no value slots to test against. If it is * not already bound, we cannot resolve */ if (slotContainer == null) { if (slotToResolve.isVariableValue()) { String variableName = ((String) slotToResolve.getValue()).toLowerCase(); if (bindings.isBound(variableName)) { Object obj = bindings.get(variableName); slotToResolve.setValue(obj); if (LOGGER.isDebugEnabled()) LOGGER.debug(String.format("Resolved %s %s = %s", slotToResolve.getName(), variableName, obj)); } else { if (LOGGER.isDebugEnabled()) LOGGER.debug(String.format("%s %s unresolved ", slotToResolve.getName(), variableName)); return false; } } } else { /* * the variable has not been bound yet. we have a container if it contains * a match, we will set the value and continue. Otherwise, we return false * (unresolved) */ /* * snag the matching value slot in the container */ ISlot valueSlot = null; try { valueSlot = slotContainer.getSlot(slotToResolve.getName()); if (valueSlot == null) { if (LOGGER.isDebugEnabled()) LOGGER.debug(String.format("%s does not contain slot %s", slotContainerName, slotToResolve.getName())); throw new CannotMatchException(new SlotMatchFailure(slotContainer, slotToResolve)); } } catch (CannotMatchException cme) { throw cme; } catch (Exception e) { /* * chunks and types will throw an exception if they dont have the slot, * but buffers will not.. */ if (LOGGER.isDebugEnabled()) LOGGER.debug(String.format("%s does not contain slot %s", slotContainerName, slotToResolve.getName())); throw new CannotMatchException(new SlotMatchFailure(slotContainer, slotToResolve)); } boolean locallyBound = false; String variableName = null; if (slotToResolve.isVariableValue()) { /* * the conditional is variablized. is it bound already? if so, resolve. * We will test after this logic */ variableName = ((String) slotToResolve.getValue()).toLowerCase(); if (bindings.isBound(variableName)) { Object value = bindings.get(variableName); slotToResolve.setValue(value); // test // test moved down below // if (!slotToResolve.matchesCondition(valueSlot.getValue())) // { // // cleanup. normally this isn't needed unless we're nested in // // logicals // throw new CannotMatchException(new SlotMatchFailure(null, // slotContainer, slotToResolve, valueSlot, // bindings.getSource(variableName))); // } } else if (slotToResolve.getCondition() == IConditionalSlot.EQUALS) /* * since the slot was not previously bound, we will do so here. */ if (slotToResolve.matchesCondition(valueSlot.getValue())) { locallyBound = true; bindings.bind(variableName, valueSlot.getValue(), valueSlot); if (LOGGER.isDebugEnabled()) LOGGER.debug(String.format("Bound %s %s", variableName, valueSlot.getValue())); } else /* * it doesn't match, this is likely a slotName =toBeBound, but * container.slotName==null */ throw new CannotMatchException(new SlotMatchFailure(slotContainer, slotToResolve, valueSlot)); } /* * Or final test of the slot value against the conditional. */ if (!slotToResolve.isVariableValue()) /* * this will not test ISA correctly. */ if (slotToResolve.getName().equalsIgnoreCase(ISlot.ISA)) { /* * isa s must have a chunk as the container and a chunk type as the * value/ */ if (!(slotContainer instanceof ISymbolicChunk)) throw new CannotMatchException(new SlotMatchFailure(slotContainer, slotToResolve, valueSlot)); if (!(slotToResolve.getValue() instanceof IChunkType)) throw new CannotMatchException(new SlotMatchFailure(slotContainer, slotToResolve, valueSlot)); ISymbolicChunk chunk = (ISymbolicChunk) slotContainer; IChunkType ct = (IChunkType) slotToResolve.getValue(); if (!chunk.isA(ct)) throw new CannotMatchException(new ChunkTypeMatchFailure(ct, chunk.getParentChunk())); } else if (!slotToResolve.matchesCondition(valueSlot.getValue())) { if (locallyBound) bindings.unbind(variableName); throw new CannotMatchException(new SlotMatchFailure(slotContainer, slotToResolve, valueSlot)); } } return true; } /** * attempt to resolve all the bindings, returning the number of unresolved. * This is the main entry call for the {@link IRequest} class. An alternative * entry call is available for those that want to bind against a specific slot * container * {@link #bind(IModel, String, IUniqueSlotContainer, VariableBindings, boolean)} */ public int bind(IModel model, VariableBindings bindings, boolean iterativeCall) throws CannotMatchException { if (_unresolved == null) { _unresolved = FastList.newInstance(); _unresolved.addAll(getConditionalAndLogicalSlots()); } for (Iterator<ISlot> slots = _unresolved.iterator(); slots.hasNext();) { ISlot slot = slots.next(); if (resolveSlot(slot, bindings, null, null)) { if (LOGGER.isDebugEnabled()) LOGGER.debug(String.format("Resolved %s", slot)); slots.remove(); } } if (_unresolved.size() > 0 && !iterativeCall) { /* * package the CME */ Collection<IConditionalSlot> unresolved = new ArrayList<IConditionalSlot>( _unresolved.size()); for (ISlot slot : _unresolved) if (slot instanceof IConditionalSlot) unresolved.add((IConditionalSlot) slot); throw new CannotMatchException(new UnresolvedVariablesMatchFailure( unresolved, bindings.getVariables(), null)); } return _unresolved.size(); } /** * bind the slot values in this request against those slots contained in the * container. This allows us to generally bind against anything that contains * a slot (chunk, chunktype, or buffer for queries) * * @param model * @param container * @param bindings * @param iterativeCall * @return * @throws CannotMatchException */ public int bind(IModel model, String containerName, IUniqueSlotContainer container, VariableBindings bindings, boolean iterativeCall) throws CannotMatchException { if (_unresolved == null) { _unresolved = FastList.newInstance(); _unresolved.addAll(getConditionalAndLogicalSlots()); } for (Iterator<ISlot> slots = _unresolved.iterator(); slots.hasNext();) { ISlot slot = slots.next(); if (resolveSlot(slot, bindings, containerName, container)) { slots.remove(); if (LOGGER.isDebugEnabled()) LOGGER.debug(String.format("Resolved %s", slot)); } } if (_unresolved.size() > 0 && !iterativeCall) { /* * package the CME */ Collection<IConditionalSlot> unresolved = new ArrayList<IConditionalSlot>( _unresolved.size()); for (ISlot slot : _unresolved) if (slot instanceof IConditionalSlot) unresolved.add((IConditionalSlot) slot); throw new CannotMatchException(new UnresolvedVariablesMatchFailure( unresolved, bindings.getVariables(), null)); } return _unresolved.size(); } /** * attempt to resolve the slot values using a container * * @param model * @param containerName * @param container * @param bindings * @param slots * @throws CannotMatchException */ public void bindSlots(IModel model, String containerName, IUniqueSlotContainer container, VariableBindings bindings, Collection<ISlot> slots) throws CannotMatchException { for (ISlot slot : slots) resolveSlot(slot, bindings, containerName, container); } /** * bind and resolve as many slots in the collection as possible. * * @throws CannotMatchException * if there is a critical binding error */ public void bindSlots(IModel model, VariableBindings bindings, Collection<ISlot> slots) throws CannotMatchException { for (ISlot slot : slots) resolveSlot(slot, bindings, null, null); } @Override public SlotBasedRequest clone() { return new SlotBasedRequest(_slots); } protected void setLocked(boolean locked) { if (_locked != locked) { _locked = locked; if (_locked) _slots = Collections.unmodifiableCollection(_slots); else _slots = new ArrayList<ISlot>(_slots); } } public void addSlot(ISlot slot) { if (_locked) throw new RuntimeException("Cannot modify a locked slot container"); // _slots.add(slot.clone()); if (slot instanceof IVariableNameSlot) _slots.add(slot.clone()); else if (slot instanceof ILogicalSlot) _slots.add(slot.clone()); else /* * why not just do slot.clone here? if this is a standard slot, we want to * create it as a conditional. */ _slots.add(new DefaultConditionalSlot(slot)); } // TODO: // possible breakage here. used to return all slots, now returns all slots // except for logic slots public Collection<? extends IConditionalSlot> getConditionalSlots() { Collection<IConditionalSlot> slots = FastList.newInstance(); for (ISlot slot : _slots) if (slot instanceof IConditionalSlot) slots.add((IConditionalSlot) slot); return slots; // return Collections.unmodifiableCollection(_slots); } public Collection<? extends ISlot> getConditionalAndLogicalSlots() { Collection<ISlot> slots = FastList.newInstance(); for (ISlot slot : _slots) if (slot instanceof IConditionalSlot || slot instanceof ILogicalSlot) slots.add(slot); // else LOGGER.error("Ignoring slot " + slot + // " because not conditional or logical"); return slots; } public Collection<? extends ISlot> getSlots() { if (!_locked) return _slots; return Collections.unmodifiableCollection(_slots); } public void removeSlot(ISlot slot) { if (_locked) throw new RuntimeException("Cannot modify a locked slot container"); _slots.remove(slot); } public Collection<ISlot> getSlots(Collection<ISlot> container) { if (container == null) if (_slots != null) container = new ArrayList<ISlot>(_slots.size() + 1); else container = new ArrayList<ISlot>(); container.addAll(_slots); return container; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + (_slots == null ? 0 : _slots.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; SlotBasedRequest other = (SlotBasedRequest) obj; if (_slots == null) { if (other._slots != null) return false; } else if (!_slots.equals(other._slots)) return false; return true; } }