/* * Copyright 2014 Igor Maznitsa (http://www.igormaznitsa.com). * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.igormaznitsa.prol.logic; import com.igormaznitsa.prol.io.ProlStreamManager; import com.igormaznitsa.prol.annotations.Consult; import com.igormaznitsa.prol.containers.KnowledgeBase; import com.igormaznitsa.prol.containers.OperatorContainer; import com.igormaznitsa.prol.data.ConvertableToTerm; import com.igormaznitsa.prol.data.NumericTerm; import com.igormaznitsa.prol.data.Term; import com.igormaznitsa.prol.data.TermFloat; import com.igormaznitsa.prol.data.TermInteger; import com.igormaznitsa.prol.data.TermList; import com.igormaznitsa.prol.data.TermStruct; import com.igormaznitsa.prol.exceptions.ProlCriticalError; import com.igormaznitsa.prol.exceptions.ProlException; import com.igormaznitsa.prol.io.DefaultProlStreamManagerImpl; import com.igormaznitsa.prol.io.ProlMemoryPipe; import com.igormaznitsa.prol.io.ProlTextInputStream; import com.igormaznitsa.prol.io.ProlTextOutputStream; import com.igormaznitsa.prol.io.ProlTextReader; import com.igormaznitsa.prol.io.ProlTextWriter; import com.igormaznitsa.prol.libraries.ProlAbstractLibrary; import com.igormaznitsa.prol.libraries.PredicateProcessor; import com.igormaznitsa.prol.libraries.ProlCoreLibrary; import com.igormaznitsa.prol.logic.triggers.ProlTrigger; import com.igormaznitsa.prol.logic.triggers.ProlTriggerType; import com.igormaznitsa.prol.logic.triggers.TriggerEvent; import com.igormaznitsa.prol.parser.ProlConsult; import com.igormaznitsa.prol.parser.ProlTreeBuilder; import com.igormaznitsa.prol.trace.TraceListener; import com.igormaznitsa.prol.utils.Utils; import java.io.IOException; import java.io.PrintWriter; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.IdentityHashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.RejectedExecutionHandler; import java.util.concurrent.ThreadFactory; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.locks.ReentrantLock; import java.util.logging.Level; import java.util.logging.Logger; /** * The class describes the prol engine context * * @author Igor Maznitsa (igor.maznitsa@igormaznitsa.com) */ public final class ProlContext { /** * Inside helper class to make Threads and handle their uncaught exceptions * * @author Igor Maznitsa (igor.maznitsa@igormaznitsa.com) */ private static final class ProlContextInsideThreadFactory implements ThreadFactory, Thread.UncaughtExceptionHandler, RejectedExecutionHandler { private final String ownercontextName; ProlContextInsideThreadFactory(final ProlContext owner) { this.ownercontextName = owner.contextName; } @Override public Thread newThread(final Runnable runner) { final Thread thread = new Thread(runner, "Prol_" + ownercontextName + '_' + runner.toString()); thread.setDaemon(true); thread.setUncaughtExceptionHandler(this); return thread; } @Override public void uncaughtException(final Thread thread, final Throwable exception) { LOG.log(Level.SEVERE, "Uncaught exception detected at " + thread.getName() + '[' + exception.toString() + ']', exception); } @Override public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) { LOG.log(Level.SEVERE, "Rejected execution! {0}''{1}''", new Object[]{r.toString(), ownercontextName}); throw new InternalError("A Prol thread was rejected. [" + ownercontextName + ']'); } } /** * Inside logger, the canonical class name is used as the logger identifier * (ProlContext.class.getCanonicalName()) */ protected static final Logger LOG = Logger.getLogger(ProlContext.class.getCanonicalName()); /** * Message constant for the context halted situation */ private static final String CONTEXT_HALTED_MSG = "Context halted"; /** * The variable contains the context name */ private final String contextName; /** * The constant has the string representation of the engine version */ public static final String ENGINE_VERSION = "1.1.5"; /** * The constant has the string representation of the engine name */ public static final String ENGINE_NAME = "Prol"; /** * The constant has the string representation of the user stream resource * identifier */ private static final String USER_STREAM = "user"; /** * This variable contains the knowledge base object for the context */ private KnowledgeBase knowledgeBase; /** * The locker is being used to make knowledge base operations thread safe */ private final ReentrantLock knowledgeBaseLocker = new ReentrantLock(); /** * The variable contains the library list for the context */ private final List<ProlAbstractLibrary> libraries; /** * The constant has the prol core library instance */ private static final ProlCoreLibrary PROL_CORE_LIBRARY = new ProlCoreLibrary(); /** * The variable contains the stream manager for the context */ private final ProlStreamManager streamManager; /** * The table contains opened input streams */ private final Map<String, ProlTextInputStream> inputStreams; /** * The table contains opened output streams */ private final Map<String, ProlTextOutputStream> outputStreams; /** * The table contains opened memory pipes */ private final Map<String, ProlMemoryPipe> pipes; /** * The table has trigger lists for assert processing */ private final Map<String, List<ProlTrigger>> triggersOnAssert; /** * The table has trigger lists for retract processing */ private final Map<String, List<ProlTrigger>> triggersOnRetract; /** * The table contains current active input stream */ private ProlTextReader currentInputStream; /** * The table contains current active output stream */ private ProlTextWriter currentOutputStream; /** * The flag shows that the context was halted (if it is true) */ private volatile boolean halted; /** * Executor service for inside use, it will be lazy inited */ private ThreadPoolExecutor executorService; /** * Inside map of lockers */ private Map<String, ReentrantLock> lockerTable; /** * This lock is being used to synchronize simultaneous work with the inside * executor and the locker table */ private final ReentrantLock executorAndlockTableLocker = new ReentrantLock(); /** * The list contains searchers allow to find Java Object for Terms or String * names */ private final List<ProlMappedObjectSearcher> mappedObjectSearchers = new ArrayList<ProlMappedObjectSearcher>(); /** * Locker for work with mappedObjects */ private final ReentrantLock mappedObjectLocker = new ReentrantLock(); /** * Locker for IO operations */ private final ReentrantLock ioLocker = new ReentrantLock(); /** * Locker for library operations */ private final ReentrantLock libLocker = new ReentrantLock(); /** * Locker for trigger operations */ private final ReentrantLock triggerLocker = new ReentrantLock(); /** * Inside variable contains the default trace listener for the context */ private TraceListener defaultTraceListener; /** * The variable contains the knowledge base factory which will be used by the * context */ private final KnowledgeBaseFactory knowledgeBaseFactory; /** * Get the default trace listener for the context * * @return the default trace listener, it can be null */ public TraceListener getDefaultTraceListener() { return defaultTraceListener; } /** * Set the default trace listener for the context * * @param traceListener the trace listener which one will be the default trace * listener for the context, it can be null */ public void setDefaultTraceListener(final TraceListener traceListener) { defaultTraceListener = traceListener; } /** * Get the context name which was supplied when the instance was created * * @return the context name as String, must not be null */ public final String getName() { return contextName; } /** * Get the knowledge base factory used by the context * * @return the knowledge base factory */ public KnowledgeBaseFactory getKnowledgeBaseFactory() { return knowledgeBaseFactory; } /** * A constructor allows to use automatically the default prol stream manager * * @param name the name for created context * @throws IOException it will be thrown if there are problems to set default * user input-output streams ('user') * @throws InterruptedException it will be thrown if the thread has been * interrupted * @throws NullPointerException it will be thrown if a needed argument is null * @see DefaultProlStreamManagerImpl */ public ProlContext(final String name) throws IOException, InterruptedException { this(name, DefaultProlStreamManagerImpl.getInstance(), null); } /** * A constructor allows to set the name and a stream manager * * @param name the name for created context * @param streamManager the stream manager to be used bye the context, it can * be null * @throws IOException it will be thrown if there are problems to set default * user input-output streams ('user') * @throws InterruptedException it will be thrown if the thread has been * interrupted * @throws NullPointerException it will be thrown if a needed argument is null * @see DefaultProlStreamManagerImpl */ public ProlContext(final String name, final ProlStreamManager streamManager) throws IOException, InterruptedException { this(name, streamManager, null); } /** * A constructor * * @param name the name for created context * @param streamManager the stream manager for created context, it can be null * @param knowledgebase the knowledge base to be used for the context (the * instance not copy will be used!), it can be null so a MemoryKnowledgeBase * instance will be created and used * @throws IOException it will be thrown if there are problems to set default * user input-output streams ('user') * @throws InterruptedException it will be thrown if the thread has been * interrupted * @throws NullPointerException it will be thrown if a needed argument is null * @see DefaultProlStreamManagerImpl */ public ProlContext(final String name, final ProlStreamManager streamManager, final KnowledgeBase knowledgebase) throws IOException, InterruptedException { this(streamManager, name, DefaultKnowledgeBaseFactory.getInstance()); knowledgeBaseLocker.lock(); try { if (knowledgebase == null) { this.knowledgeBase = knowledgeBaseFactory.makeDefaultKnowledgeBase(this, name + "_kbase"); } else { this.knowledgeBase = knowledgebase; } } finally { knowledgeBaseLocker.unlock(); } addLibrary(PROL_CORE_LIBRARY); } /** * Inside constructor for special purposes * * @param streamManager the stream manager to be used for stream manipulations * of the context, it can be null * @param name the name of the context instance * @param kbfactory a knowledge base factory to be used by the context, must * not be null * @throws IOException it will be thrown if there is any exception during the * stream initialization operations * @see DefaultProlStreamManagerImpl */ private ProlContext(final ProlStreamManager streamManager, final String name, final KnowledgeBaseFactory kbfactory) throws IOException { if (name == null) { throw new NullPointerException("The context name must not be null"); } this.contextName = name; if (streamManager == null) { throw new NullPointerException("The stream manager for a context must be defined"); } if (kbfactory == null) { throw new NullPointerException("The knowledge base factory is null"); } this.knowledgeBaseFactory = kbfactory; this.libraries = new ArrayList<ProlAbstractLibrary>(16); this.streamManager = streamManager; this.inputStreams = new HashMap<String, ProlTextInputStream>(); this.outputStreams = new HashMap<String, ProlTextOutputStream>(); this.pipes = new HashMap<String, ProlMemoryPipe>(); this.triggersOnAssert = new HashMap<String, List<ProlTrigger>>(); this.triggersOnRetract = new HashMap<String, List<ProlTrigger>>(); see(USER_STREAM); tell(USER_STREAM, true); } /** * To change knowledge base for the context with the current knowledge base * factory factory * * @param knowledge_base_id the id of new knowledge base, must not be null * @param knowledge_base_type the knowledge base type, must not be null * @throws IllegalArgumentException if it can't make a knowledge base for such * parameters */ public void changeKnowledgeBase(final String knowledge_base_id, final String knowledge_base_type) { if (knowledge_base_id == null) { throw new NullPointerException("Knowledge base Id is null"); } if (knowledge_base_type == null) { throw new NullPointerException("Knowledge base type is null"); } KnowledgeBase kb = knowledgeBaseFactory.makeKnowledgeBase(this, knowledge_base_id, knowledge_base_type); if (kb == null) { throw new IllegalArgumentException("Can't make knowledge base [" + knowledge_base_id + ',' + knowledge_base_type + ']'); } knowledgeBaseLocker.lock(); try { knowledgeBase = kb; } finally { knowledgeBaseLocker.unlock(); } } /** * Add a searcher into the searcher list. The searches will be used to find * associated java object for a Term or just a string name * * @param searcher the searcher to be added */ public void addMappedObjectSearcher(final ProlMappedObjectSearcher searcher) { if (halted) { throw new IllegalStateException(CONTEXT_HALTED_MSG); } if (searcher != null) { mappedObjectLocker.lock(); try { mappedObjectSearchers.add(searcher); } finally { mappedObjectLocker.unlock(); } } } /** * Remove a searcher from the searcher list * * @param searcher the searcher to be removed from the list */ public void removeMappedObjectFinder(final ProlMappedObjectSearcher searcher) { if (searcher != null) { mappedObjectLocker.lock(); try { mappedObjectSearchers.remove(searcher); } finally { mappedObjectLocker.unlock(); } } } /** * Find the mapped object through registered searchers. * * @param name the string name of searched object, must not be null * @return found Object or null if it is not found */ public Object findMappedObjectForName(final String name) { Object result = null; final ReentrantLock locker = mappedObjectLocker; locker.lock(); try { final Iterator<ProlMappedObjectSearcher> getters = mappedObjectSearchers.iterator(); while (getters.hasNext()) { final ProlMappedObjectSearcher current = getters.next(); result = current.findProlMappedObject(name); if (result != null) { break; } } } finally { locker.unlock(); } return result; } /** * Find the key name for a mapped object * * @param mappedObject the object for which one we are looking for the name * @return the name as a String or null if it is not found */ public String findNameForMappedObject(final Object mappedObject) { String result = null; final ReentrantLock locker = mappedObjectLocker; locker.lock(); try { final Iterator<ProlMappedObjectSearcher> getters = mappedObjectSearchers.iterator(); while (getters.hasNext()) { final ProlMappedObjectSearcher current = getters.next(); result = current.findProlMappedTerm(mappedObject); if (result != null) { break; } } } finally { locker.unlock(); } return result; } /** * Find the mapped object through registered searchers. Remember that only * pure Term object can be used (not any its successor or TermNumber) * * @param term the term for which we should find the mapped object * @return Object if it has been found, else null */ public Object findMappedObjectForTerm(final Term term) { Object result = null; if (term != null && term.getClass() == Term.class) { final String termAsText = term.getText(); result = findMappedObjectForName(termAsText); } return result; } /** * Prove a goal asynchronously which is represented as a String object * * @param goal the goal which should be proven, must not be null * @param traceListener the trace listener for the new goal, can be null * @return the future object for the new created thread * @throws IOException it will be thrown if the string representation is wrong * @throws InterruptedException it will be thrown if the term parsing process * is interrupted * @throws NullPointerException it will be thrown if the goal is null * @throws IllegalStateException if the context is halted */ public Future<?> solveAsynchronously(final String goal, final TraceListener traceListener) throws IOException, InterruptedException { if (goal == null) { throw new NullPointerException("The goal is null"); } final Term term = new ProlTreeBuilder(this).readPhraseAndMakeTree(goal); return this.solveAsynchronously(term, traceListener); } /** * Prove a goal asynchronously. The thread will be added in the asynchronous * daemon (!) thread pool of the context and can be checked through the * "waytasync/0" term * * @param goal the term which should be proven, it will be proven in a loop * until it fails, it must not be null * @param traceListener the trace listener for the goal to be proven, it can * be null * @return the future object for the new created thread * @throws NullPointerException it will be thrown if the goal is null * @throws IllegalStateException if the context is halted */ public Future<?> solveAsynchronously(final Term goal, final TraceListener traceListener) { if (isHalted()) { throw new IllegalStateException("The context is halted"); } if (goal == null) { throw new NullPointerException("The term to prove is null"); } final ProlContext thisContext = this; return getContextExecutorService().submit(new Runnable() { @Override public void run() { final Goal asyncGoal; try { asyncGoal = new Goal(goal, thisContext, traceListener); } catch (Exception ex) { LOG.log(Level.SEVERE, "Can't create a goal from the term \'" + goal.toString() + '\'', ex); return; } try { while (!Thread.currentThread().isInterrupted()) { final Term result = asyncGoal.solve(); if (result == null) { break; } } } catch (InterruptedException ex) { LOG.log(Level.INFO, "Asynchronous thread for \'" + goal.toString() + "\' has been interrupted", ex); } } }); } /** * Getter for the inside context executor service, it will be lazy inited when * the first call detected * * @return an ExecutorService object which can be used for task execution */ public ThreadPoolExecutor getContextExecutorService() { if (executorService == null) { executorAndlockTableLocker.lock(); try { if (executorService == null) { //int cpuNumber = 2; //final OperatingSystemMXBean osBean = ManagementFactory.getOperatingSystemMXBean(); //if (osBean != null) { // cpuNumber = osBean.getAvailableProcessors() + 1; //} final ProlContextInsideThreadFactory threadFactory = new ProlContextInsideThreadFactory(this); executorService = (ThreadPoolExecutor) Executors.newCachedThreadPool(threadFactory); executorService.setRejectedExecutionHandler(threadFactory); } } finally { executorAndlockTableLocker.unlock(); } } return executorService; } /** * Getter for the inside context locker map, it will be lazy inited when the * first call detected. It is direct link to the map! * * @return a Map containing Name-Locker pairs, you must synchronize the Map * for your use */ private Map<String, ReentrantLock> getLockerMap() { if (lockerTable == null) { executorAndlockTableLocker.lock(); try { if (lockerTable == null) { lockerTable = new HashMap<String, ReentrantLock>(); } } finally { executorAndlockTableLocker.unlock(); } } return lockerTable; } public ReentrantLock getLockerForName(final String name) { final Map<String, ReentrantLock> lockMap = getLockerMap(); ReentrantLock locker = null; executorAndlockTableLocker.lock(); try { locker = lockMap.get(name); if (locker == null) { locker = new ReentrantLock(); lockMap.put(name, locker); } } finally { executorAndlockTableLocker.unlock(); } return locker; } /** * Lock an inside context locker for its name * * @param name the locker name, must not be null */ public void lockLockerForName(final String name) { final Map<String, ReentrantLock> lockMap = getLockerMap(); ReentrantLock locker = null; final ReentrantLock exeLocker = executorAndlockTableLocker; exeLocker.lock(); try { locker = lockMap.get(name); if (locker == null) { locker = new ReentrantLock(); lockMap.put(name, locker); } } finally { exeLocker.unlock(); } locker.lock(); } /** * Try lock an inside context locker for its name * * @param name the name of the locker, must not be null * @return true if the locker has been locked successfully else false */ public boolean trylockLockerForName(final String name) { final Map<String, ReentrantLock> lockMap = getLockerMap(); ReentrantLock locker = null; final ReentrantLock exeLocker = executorAndlockTableLocker; exeLocker.lock(); try { locker = lockMap.get(name); if (locker == null) { locker = new ReentrantLock(); lockMap.put(name, locker); } } finally { exeLocker.unlock(); } return locker.tryLock(); } /** * Unlock inside context locker for name * * @param name the locker name, must not be null * @throws IllegalArgumentException it will be thrown if the locker name is * unknown in the context * @throws IllegalMonitorStateException it will be thrown if the locker is * being keept by other thread */ public void unlockLockerForName(final String name) { final Map<String, ReentrantLock> lockMap = getLockerMap(); ReentrantLock locker = null; final ReentrantLock exeLocker = executorAndlockTableLocker; exeLocker.lock(); try { locker = lockMap.get(name); if (locker == null) { throw new IllegalArgumentException("There is not any registered locker for name \'" + name + '\''); } } finally { exeLocker.unlock(); } locker.unlock(); } /** * Get a memory pipe for its name (it starts from the '+' char) * * @param identifier the pipe name identifier, must not be null * @return the memory pipe for the name or null if the pipe is not found */ public final ProlMemoryPipe getMemoryPipeForName(final String identifier) { ioLocker.lock(); try { return pipes.get(identifier); } finally { ioLocker.unlock(); } } /** * Get the stream manager for the context * * @return the stream manager, must not be null */ public final ProlStreamManager getStreamManager() { if (halted) { throw new ProlException(CONTEXT_HALTED_MSG); } ioLocker.lock(); try { return streamManager; } finally { ioLocker.unlock(); } } /** * Get current active output stream * * @return current active output stream, must not be null */ public final ProlTextWriter getCurrentOutStream() { if (halted) { throw new ProlException(CONTEXT_HALTED_MSG); } ioLocker.lock(); try { return currentOutputStream; } finally { ioLocker.unlock(); } } /** * Get current active input stream * * @return current active input stream, must not be null */ public ProlTextReader getCurrentInputStream() { if (halted) { throw new ProlException(CONTEXT_HALTED_MSG); } ioLocker.lock(); try { return currentInputStream; } finally { ioLocker.unlock(); } } /** * Activate an output stream for its identifier if the identifier starts with * '+' it will be opened as a memory pipe * * @param resourceId the identifier of the output stream, must not be null * @param append if true, the output stream will be opened for append new * information at this end * @throws IOException it will be thrown if there will be any transport * problem */ public void tell(final String resourceId, final boolean append) throws IOException { if (halted) { throw new IllegalStateException(CONTEXT_HALTED_MSG); } ioLocker.lock(); try { if (resourceId.length() > 0 && resourceId.charAt(0) == '+') { // it's pipe ProlMemoryPipe out = pipes.get(resourceId); if (out == null) { out = new ProlMemoryPipe(resourceId, this); pipes.put(resourceId, out); } currentOutputStream = out; } else { ProlTextOutputStream out = outputStreams.get(resourceId); if (out == null) { out = new ProlTextOutputStream(resourceId, this, append); outputStreams.put(resourceId, out); } currentOutputStream = out; } } finally { ioLocker.unlock(); } } /** * Activate an input stream for its identifier if the identifier starts with * '+' it will be opened as a memory pipe * * @param resourceId the identifier of the input stream, must not be null * @throws IOException it will be thrown if there will be any transport error */ public void see(final String resourceId) throws IOException { if (halted) { throw new IllegalStateException(CONTEXT_HALTED_MSG); } ioLocker.lock(); try { if (resourceId.length() > 0 && resourceId.charAt(0) == '+') { // it's a pipe ProlMemoryPipe in = pipes.get(resourceId); if (in == null) { in = new ProlMemoryPipe(resourceId, this); pipes.put(resourceId, in); } currentInputStream = in; } else { ProlTextInputStream in = inputStreams.get(resourceId); if (in == null) { in = new ProlTextInputStream(resourceId, this); inputStreams.put(resourceId, in); } currentInputStream = in; } } finally { ioLocker.unlock(); } } /** * Deactivate and close current active input stream (if it is not 'user' * stream) * * @throws IOException it will be thrown if there is any transport problem */ public void seen() throws IOException { if (halted) { throw new IllegalStateException(CONTEXT_HALTED_MSG); } ioLocker.lock(); try { try { if (currentInputStream == null) { return; } if (!currentInputStream.getResourceId().equals(USER_STREAM)) { currentInputStream.close(); if (currentInputStream instanceof ProlMemoryPipe) { // remove the pipe channel pipes.remove(currentInputStream.getResourceId()); } else { inputStreams.remove(currentInputStream.getResourceId()); } } } finally { see(USER_STREAM); } } finally { ioLocker.unlock(); } } /** * Deactivate and close current active output stream (if it is not 'user' * stream) * * @throws IOException it will be thrown if there is any transport problem */ public void told() throws IOException { if (halted) { throw new IllegalStateException(CONTEXT_HALTED_MSG); } ioLocker.lock(); try { try { if (currentOutputStream == null) { return; } if (!currentOutputStream.getResourceId().equals(USER_STREAM)) { if (currentOutputStream instanceof ProlMemoryPipe) { //pipes.remove(currentOutputStream.getResourceId()); ((ProlMemoryPipe) currentOutputStream).closeForWriteOnly(); } else { currentOutputStream.close(); outputStreams.remove(currentOutputStream.getResourceId()); } } } finally { tell(USER_STREAM, true); } } finally { ioLocker.unlock(); } } /** * Add a library into the context and process the consult annotations of the * library. * * @param library the library to be added * @return true if the library has been added successfully, else false (if the * library alreade presented) * @throws IOException it will be thrown if there are problems to read an * outside data * @throws InterruptedException it will be thrown if the thread has been * interrupted * @see com.igormaznitsa.prol.annotations.Consult */ public boolean addLibrary(final ProlAbstractLibrary library) throws IOException, InterruptedException { if (halted) { throw new IllegalStateException(CONTEXT_HALTED_MSG); } if (library == null) { throw new IllegalArgumentException("Library must not be null"); } final ReentrantLock locker = libLocker; locker.lock(); try { if (libraries.contains(library)) { return false; } libraries.add(library); // consult with the library final Consult consult = library.getClass().getAnnotation(Consult.class); if (consult != null) { // check url final String url = consult.URL(); if (url != null && url.length() > 0) { Utils.consultFromURLConnection(url, this); } // check urls final String[] urls = consult.URLs(); if (urls != null && urls.length > 0) { for (int li = 0; li < urls.length; li++) { final String urlToBeProcessed = urls[li]; if (urlToBeProcessed != null && urlToBeProcessed.length() > 0) { Utils.consultFromURLConnection(urlToBeProcessed, this); } } } // check text final String text = consult.Text(); if (text != null && text.length() > 0) { // there is any text new ProlConsult(text, this).consult(); } // check texts final String[] texts = consult.Texts(); if (texts != null) { for (int li = 0; li < texts.length; li++) { new ProlConsult(texts[li], this).consult(); } } } } finally { locker.unlock(); } return true; } /** * Get the knowledge base for the context * * @return the knowledge base for the context, must not be null */ public KnowledgeBase getKnowledgeBase() { knowledgeBaseLocker.lock(); try { return knowledgeBase; } finally { knowledgeBaseLocker.unlock(); } } /** * Remove a library from the context * * @param library the library to be removed from the library list of he * context * @return true if the library has been removed, else false */ public boolean removeLibrary(final ProlAbstractLibrary library) { if (library == null) { throw new IllegalArgumentException("Library must not be null"); } if (halted) { throw new IllegalStateException(CONTEXT_HALTED_MSG); } final ReentrantLock locker = libLocker; locker.lock(); try { return libraries.remove(library); } finally { locker.unlock(); } } /** * Write all dynamic content of the context knowledge base into stream * * @param writer a print writer to be used for output data, must not be null */ public void writeKnowledgeBase(final PrintWriter writer) { if (writer == null) { throw new IllegalArgumentException("Writer must not be null"); } knowledgeBaseLocker.lock(); try { knowledgeBase.write(writer); } finally { knowledgeBaseLocker.unlock(); } } /** * Find first predicate processor compatible with the predicate * * @param predicate the structure for which we will look for a processor, must * not be null * @return found processor object or NULL_PROCESSOR if has not found anything * @see com.igormaznitsa.prol.libraries.PredicateProcessor */ public PredicateProcessor findProcessor(final TermStruct predicate) { final ReentrantLock locker = libLocker; locker.lock(); try { int li = libraries.size() - 1; while (li >= 0) { final ProlAbstractLibrary lib = libraries.get(li); final PredicateProcessor processor = lib.findProcessorForPredicate(predicate); if (processor != null) { return processor; } li--; } return PredicateProcessor.NULL_PROCESSOR; } finally { locker.unlock(); } } /** * Check that in libraries presented zero-arity predicate. * * @param name name of predicate * @return true if presented, false otherwise */ public boolean hasZeroArityPredicateForName(final String name) { final ReentrantLock locker = this.libLocker; locker.lock(); try { int li = this.libraries.size() - 1; boolean result = false; while (li >= 0) { final ProlAbstractLibrary lib = this.libraries.get(li); if (lib.hasZeroArityPredicateForName(name)) { result = true; break; } li--; } return result; } finally { locker.unlock(); } } /** * Check that as minimum one from libraries in the context has a predicate has * a signature * * @param signature the signature to be checked, must not be null * @return true if a predicate has been found, else false */ public boolean hasPredicateAtLibraryForSignature(final String signature) { final ReentrantLock locker = this.libLocker; locker.lock(); try { int li = this.libraries.size() - 1; while (li >= 0) { final ProlAbstractLibrary lib = this.libraries.get(li); if (lib.hasPredicateForSignature(signature)) { return true; } li--; } return false; } finally { locker.unlock(); } } /** * Check that there is a system operator for a name * * @param name the name to be checked, must not not null * @return true if there is such a system operator else false */ public boolean isSystemOperator(final String name) { final ReentrantLock locker = this.libLocker; locker.lock(); try { int li = libraries.size() - 1; while (li >= 0) { final ProlAbstractLibrary lib = libraries.get(li); if (lib.isSystemOperator(name)) { return true; } li--; } return false; } finally { locker.unlock(); } } /** * Find system operator for name in libraries * * @param name the name to be looked for * @return found operator container for the name or null if there is not any * with such name */ public OperatorContainer getSystemOperatorForName(final String name) { final ReentrantLock locker = libLocker; locker.lock(); try { int li = libraries.size() - 1; while (li >= 0) { final ProlAbstractLibrary lib = libraries.get(li); final OperatorContainer result = lib.findSystemOperatorForName(name); if (result != null) { return result; } li--; } return null; } finally { locker.unlock(); } } /** * Check that a library has a system operator start with a substring * * @param str the substring to be checked, must not be null * @return true if there is such operator, else false */ public boolean hasSystemOperatorStartsWith(final String str) { final ReentrantLock locker = libLocker; locker.lock(); try { int li = libraries.size() - 1; while (li >= 0) { final ProlAbstractLibrary lib = libraries.get(li); if (lib.hasSyatemOperatorStartsWith(str)) { return true; } li--; } return false; } finally { locker.unlock(); } } /** * Halt the context and release its keeping resources * * @throws IllegalStateException if the context has been halted already */ public void halt() { if (halted) { throw new IllegalStateException("Context already halted"); } else { halted = true; } // stop all async threads executorAndlockTableLocker.lock(); try { if (executorService != null) { executorService.shutdownNow(); } } finally { executorAndlockTableLocker.unlock(); } // notify all triggers that the context is halting triggerLocker.lock(); try { final IdentityHashMap<ProlTrigger, Set<?>> notifiedTriggers = new IdentityHashMap<ProlTrigger, Set<?>>();// it is being used to save the set of already notified triggers, we should notify each object only one time for (final Entry<String, List<ProlTrigger>> mapentry : triggersOnAssert.entrySet()) { for (final ProlTrigger trigger : mapentry.getValue()) { try { if (!notifiedTriggers.containsKey(trigger)) { trigger.onContextHalting(this); } } catch (Throwable ex) { LOG.log(Level.SEVERE, "Exception during a context halting notification", ex); } finally { notifiedTriggers.put(trigger, Collections.emptySet()); } } } for (final Entry<String, List<ProlTrigger>> mapentry : triggersOnRetract.entrySet()) { for (final ProlTrigger trigger : mapentry.getValue()) { try { if (!notifiedTriggers.containsKey(trigger)) { trigger.onContextHalting(this); } } catch (Throwable ex) { LOG.log(Level.SEVERE, "Exception during a context halting notification", ex); } finally { notifiedTriggers.put(trigger, Collections.emptySet()); } } } notifiedTriggers.clear(); triggersOnAssert.clear(); triggersOnRetract.clear(); } finally { triggerLocker.unlock(); } ioLocker.lock(); libLocker.lock(); try { try { final Iterator<ProlMemoryPipe> memPipes = pipes.values().iterator(); while (memPipes.hasNext()) { try { memPipes.next().close(); } catch (Throwable thr) { } } final Iterator<ProlTextInputStream> inStreams = inputStreams.values().iterator(); while (inStreams.hasNext()) { try { inStreams.next().close(); } catch (Throwable thr) { } } inputStreams.clear(); final Iterator<ProlTextOutputStream> outStreams = outputStreams.values().iterator(); while (outStreams.hasNext()) { try { outStreams.next().close(); } catch (Throwable thr) { } } outputStreams.clear(); getContextExecutorService().shutdownNow(); currentInputStream = null; currentOutputStream = null; } finally { // notify all libraries that the context is halted for (ProlAbstractLibrary library : libraries) { try { library.contextHasBeenHalted(this); } catch (Exception ex) { LOG.log(Level.SEVERE, "library.contextHasBeenHalted();", ex); } } } } finally { libLocker.unlock(); ioLocker.unlock(); } } /** * Check that the context has been halted * * @return true if the context has been halted, else false */ public boolean isHalted() { return halted; } /** * Allows to convert a java object into its Prol representation. null will be * converted as TermList.NULLLIST Term will be just returned as th result * ConvertableToTerm will be asked for Term and the term will be returned as * the result String will be converted as Term Number (integer or float only) * will be converted as NumericTerm Collection will be converted as TermList * Object[] will be converted as TermStruct, the first element will be used as * the functor (it's toString() value), empty array will be converted ass * TermList.NULLLIST * * @param object the object to be converted into a Prol representation, can be * null * @return a Term object which is a Prol compatible representation of the Java * object * @throws IllegalArgumentException it will be thrown if the object has an * unsupported type * @throws NullPointerException it will be thrown if any successor of * ConvertableToTerm will return null as result */ public Term objectAsTerm(final Object object) { Term result = null; if (object == null) { // return NULLLIST result = TermList.NULLLIST; } else if (object instanceof Term) { result = (Term) object; } else if (object instanceof ConvertableToTerm) { final ConvertableToTerm cterm = (ConvertableToTerm) object; result = cterm.asProlTerm(); if (result == null) { throw new NullPointerException("asProlTerm() returned null [" + object.toString() + ']'); } } else if (object instanceof String) { // atom or mapped object result = new Term((String) object); } else if (object instanceof Number) { if (object instanceof Integer) { result = new TermInteger(((Integer) object).intValue()); } else if (object instanceof Float) { result = new TermFloat(((Float) object).floatValue()); } else { throw new IllegalArgumentException("Unsupported number format."); } } else if (object instanceof Collection) { // list final Collection<?> lst = (Collection) object; if (lst.isEmpty()) { result = TermList.NULLLIST; } else { TermList accumulator = null; // fill the list for (Object item : lst) { if (accumulator == null) { accumulator = new TermList(objectAsTerm(item)); result = accumulator; // the first list } else { accumulator = TermList.appendItem(accumulator, objectAsTerm(item)); } } } } else if (object instanceof Object[]) { // struct final Object[] array = (Object[]) object; final int arrlen = array.length; if (arrlen == 0) { // as null list result = TermList.NULLLIST; } else { final Term functor = new Term(array[0].toString()); if (arrlen == 1) { result = new TermStruct(functor); } else { final Term[] terms = new Term[arrlen - 1]; for (int li = 1; li < arrlen; li++) { terms[li - 1] = objectAsTerm(array[li]); } result = new TermStruct(functor, terms); } } } else { throw new IllegalArgumentException("Unsupported object to be represented as a Term"); } return result; } /** * Convert a Term into its Java representation Term will be converted as * java.lang.String or as an mapped Java object if it is found in the context * NumericTerm will be converted as java.lang.Number object TermList will be * converted as List<Object> * TermStruct will be converted as Object[] where the first element if the * functor (it will be converted as other Term so you can use mapped objects) * * @param term the term to be converted, must not be null * @return a Java object represents the Term * @throws IllegalArgumentException it will be thrown if a non-instantiate * variable has been detected */ public Object termAsObject(final Term term) { final Term cterm = Utils.getTermFromElement(term); Object result = null; switch (cterm.getTermType()) { case Term.TYPE_ATOM: { if (cterm instanceof NumericTerm) { // as numeric value result = ((TermInteger) cterm).getNumericValue(); } else { // find mapped object final String termtext = cterm.getText(); result = findMappedObjectForName(termtext); if (result == null) { // there is not any mapped object, so return the text result = termtext; } } } break; case Term.TYPE_LIST: { // make List<Object> final List<Object> list = new ArrayList<Object>(); TermList tlist = (TermList) cterm; while (tlist.isNullList()) { list.add(termAsObject(tlist.getHead())); final Term tail = tlist.getTail(); if (tail.getTermType() == Term.TYPE_LIST) { tlist = (TermList) tail; } else { list.add(termAsObject(tail)); break; } } result = list; } break; case Term.TYPE_OPERATORS: case Term.TYPE_OPERATOR: { // just as text result = cterm.getText(); } break; case Term.TYPE_STRUCT: { // struct final TermStruct sterm = (TermStruct) cterm; final int size = sterm.getArity() + 1; final Object[] array = new Object[size]; // the first element is the term array[0] = termAsObject(sterm.getFunctor()); // other elements for (int li = 1; li < size; li++) { array[li] = termAsObject(sterm.getElement(li - 1)); } result = array; } break; case Term.TYPE_VAR: { // non instantiate variable throw new IllegalArgumentException("It is non instantiate variable \'" + cterm.getText() + "\'"); } default: { // new type detected throw new ProlCriticalError("Unsupported term type"); } } return result; } /** * Register a notification trigger * * @param trigger the trigger to be registered, must not be null */ public void registerTrigger(final ProlTrigger trigger) { if (halted) { throw new IllegalStateException(CONTEXT_HALTED_MSG); } final Map<String, ProlTriggerType> signatures = trigger.getSignatures(); triggerLocker.lock(); try { for (Entry<String, ProlTriggerType> entry : signatures.entrySet()) { String signature = Utils.validateSignature(entry.getKey()); if (signature == null) { throw new IllegalArgumentException("unsupported signature format [" + entry.getKey() + ']'); } signature = Utils.normalizeSignature(signature); final ProlTriggerType triggerType = entry.getValue(); List<ProlTrigger> triggerListAssert = null; List<ProlTrigger> triggerListRetract = null; if (triggerType == ProlTriggerType.TRIGGER_ASSERT || triggerType == ProlTriggerType.TRIGGER_ASSERT_RETRACT) { triggerListAssert = triggersOnAssert.get(signature); if (triggerListAssert == null) { triggerListAssert = new ArrayList<ProlTrigger>(); triggersOnAssert.put(signature, triggerListAssert); } } if (triggerType == ProlTriggerType.TRIGGER_RETRACT || triggerType == ProlTriggerType.TRIGGER_ASSERT_RETRACT) { triggerListRetract = triggersOnRetract.get(signature); if (triggerListRetract == null) { triggerListRetract = new ArrayList<ProlTrigger>(); triggersOnRetract.put(signature, triggerListRetract); } } if (triggerListAssert != null) { triggerListAssert.add(trigger); LOG.info("Registered handler as TRIGGER_ASSERT " + " for \'" + signature + "\', the handler is " + trigger.toString()); } if (triggerListRetract != null) { triggerListRetract.add(trigger); LOG.info("Registered handler as TRIGGER_RETRACT " + " for \'" + signature + "\', the handler is " + trigger.toString()); } } } finally { triggerLocker.unlock(); } } /** * Unregister a notification trigger * * @param trigger the trigger to be removed from the inside trigger list, must * not be null */ public void unregisterTrigger(final ProlTrigger trigger) { triggerLocker.lock(); try { // remove the trigger from both maps //assert Iterator<Entry<String, List<ProlTrigger>>> iterator = triggersOnAssert.entrySet().iterator(); while (iterator.hasNext()) { final Entry<String, List<ProlTrigger>> entry = iterator.next(); final List<ProlTrigger> lst = entry.getValue(); if (lst.remove(trigger)) { if (lst.isEmpty()) { iterator.remove(); } } } //retract iterator = triggersOnRetract.entrySet().iterator(); while (iterator.hasNext()) { final Entry<String, List<ProlTrigger>> entry = iterator.next(); final List<ProlTrigger> lst = entry.getValue(); if (lst.remove(trigger)) { if (lst.isEmpty()) { iterator.remove(); } } } } finally { triggerLocker.unlock(); } } /** * Check that there is any registered trigger for the event type+signature * pair * * @param normalizedSignature the normalized signature to be checked, must not * be null * @param observedEvent the event type to be checked, must not be null * @return true if there is any registered trigger for the pair, else false */ public boolean hasRegisteredTriggersForSignature(final String normalizedSignature, final ProlTriggerType observedEvent) { triggerLocker.lock(); try { boolean result = false; switch (observedEvent) { case TRIGGER_ASSERT: { result = triggersOnAssert.containsKey(normalizedSignature); } break; case TRIGGER_RETRACT: { result = triggersOnRetract.containsKey(normalizedSignature); } break; case TRIGGER_ASSERT_RETRACT: { result = triggersOnAssert.containsKey(normalizedSignature); if (!result) { result = triggersOnRetract.containsKey(normalizedSignature); } } break; default: { throw new IllegalArgumentException("Unsupported observed event [" + observedEvent.name() + ']'); } } return result; } finally { triggerLocker.unlock(); } } /** * Notify all registered triggers which are registered for the signature+event * type pair * * @param normalizedSignature the normalized signature, must not be null * @param observedEvent the detected trigger type, must not be null */ public void notifyTriggersForSignature(final String normalizedSignature, final ProlTriggerType observedEvent) { ProlTrigger[] triggersToProcess = null; triggerLocker.lock(); try { List<ProlTrigger> listOfTriggers = null; switch (observedEvent) { case TRIGGER_ASSERT: { listOfTriggers = triggersOnAssert.get(normalizedSignature); } break; case TRIGGER_RETRACT: { listOfTriggers = triggersOnRetract.get(normalizedSignature); } break; case TRIGGER_ASSERT_RETRACT: { final List<ProlTrigger> trigAssert = triggersOnAssert.get(normalizedSignature); final List<ProlTrigger> trigRetract = triggersOnRetract.get(normalizedSignature); if (trigAssert != null && trigRetract == null) { listOfTriggers = trigAssert; } else if (trigAssert == null && trigRetract != null) { listOfTriggers = trigRetract; } else { listOfTriggers = new ArrayList<ProlTrigger>(trigAssert); listOfTriggers.addAll(trigRetract); } } break; default: { throw new IllegalArgumentException("Unsupported trigger event [" + observedEvent.name()); } } if (listOfTriggers != null) { triggersToProcess = listOfTriggers.toArray(new ProlTrigger[listOfTriggers.size()]); } } finally { triggerLocker.unlock(); } if (triggersToProcess != null) { final TriggerEvent event = new TriggerEvent(this, normalizedSignature, observedEvent); for (int li = 0; li < triggersToProcess.length; li++) { final ProlTrigger trigger = triggersToProcess[li]; try { trigger.onTriggerEvent(event); } catch (Exception ex) { LOG.log(Level.SEVERE, "Exception during a trigger processing [" + trigger.toString() + ']', ex); } } } } @Override public String toString() { final StringBuilder builder = new StringBuilder("ProlContext(").append(contextName).append(')').append('[').append(super.toString()).append(']'); return builder.toString(); } /** * Allows to make copy of the context and its knowledge base * * @return new context containing snapshot of current knowledge base * @throws IOException it will be thrown if there is any exception during * initialization of IO streams */ public ProlContext makeCopy() throws IOException { final ProlContext newContext = new ProlContext(streamManager, this.contextName + "_copy", knowledgeBaseFactory); newContext.libraries.addAll(libraries); knowledgeBaseLocker.lock(); try { newContext.knowledgeBase = knowledgeBase == null ? null : knowledgeBase.makeCopy(newContext); } finally { knowledgeBaseLocker.unlock(); } return newContext; } }