package org.jactr.tools.misc; /* * default logging */ import java.util.Map; import java.util.TreeMap; import java.util.concurrent.CompletableFuture; import java.util.concurrent.Future; import java.util.concurrent.locks.Lock; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.jactr.core.buffer.IActivationBuffer; import org.jactr.core.chunk.IChunk; import org.jactr.core.chunk.ISymbolicChunk; import org.jactr.core.chunktype.IChunkType; import org.jactr.core.concurrent.ModelCycleExecutor; import org.jactr.core.concurrent.ModelCycleExecutor.When; import org.jactr.core.model.IModel; import org.jactr.core.module.declarative.IDeclarativeModule; import org.jactr.core.slot.IMutableSlot; /** * safe way to manipulate the source chunk of a buffer. This will be executed * via {@link ExecutionUtilities} so that the changes occur on the model thread * either before or after the normal production cycle sequence. * * @author harrison */ public class ChunkUtilities { /** * Logger definition */ static private final transient Log LOGGER = LogFactory .getLog(ChunkUtilities.class); /** * manipulate using * {@link ExecutionUtilities#executeNow(org.jactr.core.model.IModel, Runnable)} * * @param buffer * @param modifier * @return */ static public Future<Boolean> manipulateChunkNow(IActivationBuffer buffer, final IChunkModifier modifier) { return ExecutionUtilities.executeNow(buffer.getModel(), new ModifyRunnable( buffer, modifier)); } /** * modify using * {@link ExecutionUtilities#executeLater(org.jactr.core.model.IModel, Runnable)} * * @param buffer * @param modifier * @return */ static public Future<Boolean> manipulateChunkLater(IActivationBuffer buffer, IChunkModifier modifier) { return ExecutionUtilities.executeLater(buffer.getModel(), new ModifyRunnable(buffer, modifier)); } /** * Standard search and returned named, if found, otherwise create (and * possibly encode). This is not atomic, merely streamlined. * * @param chunkName * @param model * @param type * @param modifier * @param encode * @return */ static public CompletableFuture<IChunk> getOrCreate(String chunkName, IModel model, IChunkType type, IChunkModifier modifier, boolean encode) { final IDeclarativeModule decM = model.getDeclarativeModule(); CompletableFuture<IChunk> find = decM.getChunk(chunkName); CompletableFuture<IChunk> create = find.thenCompose((c) -> { if (c == null) return decM.createChunk(type, chunkName); else return CompletableFuture.completedFuture(c); }); CompletableFuture<IChunk> modify = create.thenApply((c) -> { if (!c.isEncoded() && modifier != null) modifier.modify(c, null); return c; }); CompletableFuture<IChunk> encoder = modify.thenCompose((c) -> { if (encode && !c.isEncoded()) return decM.addChunk(c); else return CompletableFuture.completedFuture(c); }); return encoder; } /** * Functionally a single call to create and set the slot values of a chunk. * Merely attach a callback to process when complete, or (and this is not * recommended) call get(). <br/> * <br/> * <code> * ChunkUtilities.createAndConfigure(type, name, slots).thenAccept((c)->ChunkUtilities.addToBuffer(c,buffer)); * </code> * * @param type * @param nameTemplate * @param slotValues * @return */ static public CompletableFuture<IChunk> createAndConfigure(IChunkType type, String nameTemplate, Map<String, Object> slotValues) { return createAndConfigure(type, nameTemplate, new MapChunkModifier( slotValues)); } static public CompletableFuture<IChunk> createAndConfigure(IChunkType type, String nameTemplate, final IChunkModifier modifier) { final IDeclarativeModule decM = type.getModel().getDeclarativeModule(); /* * first, we create the chunk */ CompletableFuture<IChunk> creation = decM.createChunk(type, nameTemplate); /* * then, we immediately set the slot values */ CompletableFuture<IChunk> modified = creation .thenApply((c) -> configureChunk(c, modifier)); return modified; } static public CompletableFuture<IChunk> encode(IChunk chunk) { if (chunk.isEncoded()) return CompletableFuture.completedFuture(chunk); else { IDeclarativeModule decM = chunk.getModel().getDeclarativeModule(); return decM.addChunk(chunk); } } /** * Create configure and encode. * * @param type * @param nameTemplate * @param slotValues * @return */ static public CompletableFuture<IChunk> createConfigureAndEncode( final IChunkType type, String nameTemplate, Map<String, Object> slotValues) { return createConfigureAndEncode(type, nameTemplate, new MapChunkModifier( slotValues)); } static public CompletableFuture<IChunk> createConfigureAndEncode( final IChunkType type, String nameTemplate, IChunkModifier modifier) { /* * create and configure */ CompletableFuture<IChunk> modified = createAndConfigure(type, nameTemplate, modifier); /* * then immediate add it */ CompletableFuture<IChunk> encode = modified.thenCompose((c) -> { return encode(c); }); return encode; } /** * @param chunk * @param buffer * @return */ static public CompletableFuture<IChunk> addToBuffer(final IChunk chunk, final IActivationBuffer buffer) { CompletableFuture<IChunk> added = CompletableFuture.supplyAsync(() -> { return buffer.addSourceChunk(chunk); }, new ModelCycleExecutor(chunk.getModel(), When.ASAP)); return added; } static public CompletableFuture<Void> removeFromBuffer(final IChunk chunk, final IActivationBuffer buffer) { CompletableFuture<Void> removed = CompletableFuture.runAsync( () -> { if (!buffer.getSourceChunks().contains(chunk)) throw new IllegalStateException(String.format( "%s is not currently in %s", chunk.getSymbolicChunk().getName(), buffer.getName())); buffer.removeSourceChunk(chunk); }, new ModelCycleExecutor(chunk.getModel(), When.ASAP)); return removed; } static public CompletableFuture<Void> modifyInBuffer(final IChunk chunk, final IActivationBuffer buffer, final IChunkModifier modifier) { CompletableFuture<Void> modified = CompletableFuture.runAsync( () -> { if (!buffer.getSourceChunks().contains(chunk)) throw new IllegalStateException(String.format( "%s is not currently in %s", chunk.getSymbolicChunk().getName(), buffer.getName())); if (chunk.isEncoded() && !chunk.isMutable()) throw new IllegalStateException(String.format( "Chunk %s is encoded and not mutable, cannot modify.", chunk .getSymbolicChunk().getName())); configureChunk(chunk, modifier); }, new ModelCycleExecutor(chunk.getModel(), When.ASAP)); return modified; } static private IChunk configureChunk(IChunk chunk, IChunkModifier modifier) { Lock l = chunk.getWriteLock(); try { l.lock(); modifier.modify(chunk, null); } finally { l.unlock(); } return chunk; } /** * convenience interface for manipulative a chunk safely * * @author harrison */ @FunctionalInterface public interface IChunkModifier { public void modify(IChunk chunk, IActivationBuffer buffer); } static private class MapChunkModifier implements IChunkModifier { private TreeMap<String, Object> _slots; public MapChunkModifier(Map<String, Object> slots) { _slots = new TreeMap<String, Object>(slots); } @Override public void modify(IChunk chunk, IActivationBuffer buffer) { ISymbolicChunk sc = chunk.getSymbolicChunk(); // for (Map.Entry<String, Object> slot : _slots.entrySet()) // try // { // IMutableSlot mSlot = (IMutableSlot) sc.getSlot(slot.getKey()); // mSlot.setValue(slot.getValue()); // } // catch (Exception e) // { // LOGGER.error( // String.format("Failed to set %s.%s = %s", sc.getName(), // slot.getKey(), slot.getValue()), e); // } _slots .forEach((k, v) -> { try { IMutableSlot mSlot = (IMutableSlot) sc.getSlot(k); mSlot.setValue(v); } catch (Exception e) { LOGGER.error( String.format("Failed to set %s.%s = %s", sc.getName(), k, v), e); } }); } } static private class ModifyRunnable implements Runnable { private final IActivationBuffer _buffer; private final IChunkModifier _modifier; public ModifyRunnable(IActivationBuffer buffer, IChunkModifier modifier) { _buffer = buffer; _modifier = modifier; } public void run() { IChunk chunk = _buffer.getSourceChunk(); if (chunk == null) { if (LOGGER.isWarnEnabled()) LOGGER.warn(String.format( "No chunk available in %s, ignoring request", _buffer)); return; } if (chunk.isEncoded() && !chunk.isMutable()) { if (LOGGER.isWarnEnabled()) LOGGER.warn(String.format( "%s is encoded and not immutable, cannot modify", chunk)); return; } /* * now write lock and do our thing */ try { chunk.getWriteLock().lock(); _modifier.modify(chunk, _buffer); } finally { chunk.getWriteLock().unlock(); } } } }