├── .gitignore ├── .settings ├── org.eclipse.m2e.core.prefs └── org.eclipse.jdt.core.prefs ├── .classpath ├── .project ├── LICENSE ├── TODO.md ├── pom.xml ├── src └── main │ └── java │ └── com │ └── simularity │ └── os │ └── javapengine │ ├── exception │ ├── SyntaxErrorException.java │ ├── PengineException.java │ ├── PengineNotReadyException.java │ ├── PengineNotAvailableException.java │ └── CouldNotCreateException.java │ ├── package.html │ ├── example │ ├── Basic.java │ └── ManualAsk.java │ ├── Proof.java │ ├── PengineState.java │ ├── Query.java │ ├── PengineBuilder.java │ └── Pengine.java ├── overview.html ├── protocoltests.txt ├── PrologBluffers.md └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | *.class 2 | bin/ 3 | target/ 4 | doc/ 5 | *.*~ 6 | -------------------------------------------------------------------------------- /.settings/org.eclipse.m2e.core.prefs: -------------------------------------------------------------------------------- 1 | activeProfiles= 2 | eclipse.preferences.version=1 3 | resolveWorkspaceProjects=true 4 | version=1 5 | -------------------------------------------------------------------------------- /.classpath: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | JavaPengine 4 | 5 | 6 | 7 | 8 | 9 | org.eclipse.jdt.core.javabuilder 10 | 11 | 12 | 13 | 14 | org.eclipse.m2e.core.maven2Builder 15 | 16 | 17 | 18 | 19 | 20 | org.eclipse.m2e.core.maven2Nature 21 | org.eclipse.jdt.core.javanature 22 | 23 | 24 | -------------------------------------------------------------------------------- /.settings/org.eclipse.jdt.core.prefs: -------------------------------------------------------------------------------- 1 | eclipse.preferences.version=1 2 | org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled 3 | org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8 4 | org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve 5 | org.eclipse.jdt.core.compiler.compliance=1.8 6 | org.eclipse.jdt.core.compiler.debug.lineNumber=generate 7 | org.eclipse.jdt.core.compiler.debug.localVariable=generate 8 | org.eclipse.jdt.core.compiler.debug.sourceFile=generate 9 | org.eclipse.jdt.core.compiler.problem.assertIdentifier=error 10 | org.eclipse.jdt.core.compiler.problem.enumIdentifier=error 11 | org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning 12 | org.eclipse.jdt.core.compiler.source=1.8 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015 Simularity, Inc. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | TODO list 2 | --------- 3 | 4 | * desk check all code 5 | * remove cruft and clean up comments 6 | * finish the README 7 | * abort - a more 'violent' stop - not only do I want you to quit after this answer, I want you to quit sending output (ie ctrl-c in interactor) 8 | * handle multithreading 9 | * write the tests below 10 | * ask option in create 11 | * Move to Simularity's new organization on github. waiting on Ray to create and add me to the organization. 12 | * Deploy to Maven Central. Waiting until we move to Simularity's github organization. 13 | 14 | Next Revision 15 | ------------- 16 | 17 | * clean up IO 18 | * ping - add support for creating a keepalive thread 19 | * callbacks for errors - instead of handling exceptions, have a way to register exception handlers to make life easier for 20 | the Java programmer 21 | * destroy_all 22 | * templates 23 | 24 | Open Source Giveback 25 | -------------------- 26 | 27 | * make all URI endpoints handle JSON 28 | * clean up obsolete documentation 29 | * write some tests maybe 30 | * write an rfc for pltp 31 | 32 | Tests to write and make run 33 | --------------------------- 34 | 35 | * IO test 36 | * stop test 37 | * kill server 38 | * chunking 39 | * alias 40 | * exceed slave limit 41 | 42 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4.0.0 3 | com.simularity.os 4 | javapengine 5 | 0.0.1-SNAPSHOT 6 | JavaPengine 7 | Java interface for SWI-Prolog's Pengines RPC system 8 | 9 | src 10 | 11 | 12 | src 13 | 14 | **/*.java 15 | 16 | 17 | 18 | 19 | 20 | maven-compiler-plugin 21 | 3.3 22 | 23 | 1.8 24 | 1.8 25 | 26 | 27 | 28 | 29 | 30 | http://simularity.com/ 31 | Simularity 32 | 33 | 34 | 35 | javax.json 36 | javax.json-api 37 | 1.0 38 | 39 | 40 | org.glassfish 41 | javax.json 42 | 1.0.4 43 | 44 | 45 | -------------------------------------------------------------------------------- /src/main/java/com/simularity/os/javapengine/exception/SyntaxErrorException.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016 Simularity Inc. 3 | * 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | * 23 | */ 24 | package com.simularity.os.javapengine.exception; 25 | 26 | /** 27 | * @author anniepoo 28 | * 29 | */ 30 | public class SyntaxErrorException extends Exception { 31 | 32 | 33 | /** 34 | * 35 | */ 36 | private static final long serialVersionUID = 6618776336072476401L; 37 | 38 | /** 39 | * @param message 40 | */ 41 | public SyntaxErrorException(String message) { 42 | super(message); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/com/simularity/os/javapengine/exception/PengineException.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016 Simularity Inc. 3 | * 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | * 23 | */ 24 | package com.simularity.os.javapengine.exception; 25 | 26 | /** 27 | * @author anniepoo 28 | * 29 | */ 30 | public class PengineException extends Exception { 31 | 32 | /** 33 | * @param string 34 | */ 35 | public PengineException(String string) { 36 | // TODO Auto-generated constructor stub 37 | } 38 | 39 | /** 40 | * 41 | */ 42 | private static final long serialVersionUID = -8420345616408412643L; 43 | 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/com/simularity/os/javapengine/package.html: -------------------------------------------------------------------------------- 1 | 2 | 24 | 25 | JavaPengine 26 | 27 | 28 |

JavaPengine

29 |

Pengines is a library included in the default install of 30 | SWI-Prolog that allows a remote master to create a sandboxed slave interactor on a remote SWI-Prolog server.

31 |

JavaPengine is a Java library that operates as a pengine master.

32 | 33 | 34 | -------------------------------------------------------------------------------- /src/main/java/com/simularity/os/javapengine/exception/PengineNotReadyException.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016 Simularity Inc. 3 | * 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | * 23 | */ 24 | package com.simularity.os.javapengine.exception; 25 | 26 | /** 27 | * @author anniepoo 28 | * 29 | * the user has attempted to perform some operation on the pengine when it wasn't ready to 30 | * perform that operation 31 | * 32 | */ 33 | public class PengineNotReadyException extends PengineException { 34 | 35 | 36 | /** 37 | * 38 | */ 39 | private static final long serialVersionUID = 1838967274556465606L; 40 | 41 | /** 42 | * @param string 43 | */ 44 | public PengineNotReadyException(String string) { 45 | super(string); 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/com/simularity/os/javapengine/exception/PengineNotAvailableException.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016 Simularity Inc. 3 | * 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | * 23 | */ 24 | package com.simularity.os.javapengine.exception; 25 | 26 | /** 27 | * @author anniepoo 28 | * 29 | * thrown when something has made the pengine unavailable, eg. network failure or 30 | * the server becoming unavailable. 31 | * 32 | */ 33 | public class PengineNotAvailableException extends PengineNotReadyException { 34 | 35 | /** 36 | * 37 | */ 38 | private static final long serialVersionUID = 4178487621832092850L; 39 | 40 | /** 41 | * @param string 42 | */ 43 | public PengineNotAvailableException(String string) { 44 | super(string); 45 | // TODO Auto-generated constructor stub 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/com/simularity/os/javapengine/example/Basic.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016 Simularity Inc. 3 | * 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | * 23 | */ 24 | package com.simularity.os.javapengine.example; 25 | 26 | import java.net.MalformedURLException; 27 | 28 | import com.simularity.os.javapengine.Pengine; 29 | import com.simularity.os.javapengine.PengineBuilder; 30 | import com.simularity.os.javapengine.exception.CouldNotCreateException; 31 | 32 | /** 33 | * @author anniepoo 34 | * 35 | */ 36 | public final class Basic { 37 | 38 | /** 39 | * 40 | */ 41 | public Basic() { 42 | 43 | } 44 | // TODO lots more testing of PengineOptions and create 45 | 46 | /** 47 | * @param args 48 | */ 49 | public static void main(String[] args) { 50 | // TODO Auto-generated method stub 51 | PengineBuilder po = new PengineBuilder(); 52 | 53 | try { 54 | po.setServer("http://localhost:9900/"); 55 | Pengine p = po.newPengine(); 56 | p.dumpStateDebug(); 57 | } catch (MalformedURLException e) { 58 | System.err.println("Bad URL" + e.getMessage()); 59 | e.printStackTrace(); 60 | } catch (CouldNotCreateException e) { 61 | System.err.println("cannot make pengine" + e.getMessage()); 62 | e.printStackTrace(); 63 | } 64 | } 65 | 66 | } 67 | -------------------------------------------------------------------------------- /src/main/java/com/simularity/os/javapengine/exception/CouldNotCreateException.java: -------------------------------------------------------------------------------- 1 | package com.simularity.os.javapengine.exception; 2 | 3 | /* 4 | * Copyright (c) 2015 Simularity, Inc. 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. 23 | * 24 | */ 25 | /** 26 | * This is thrown whenever a Pengine is requested, but cannot be created. 27 | * 28 | * JavaPengine is designed to reduce the amount of exception handling the user needs to do. 29 | * 30 | * The most common reason a Pengine cannot be created is inability to contact the server. 31 | * The second most common reason is a bad set of PengineOptions, either bad values or because 32 | * clone throws CloneNotSupportedException. 33 | * 34 | * @author Anne Ogborn 35 | * 36 | * TODO implement the java sourceinfo and the serverNotReachable flag 37 | * no, even better, make some subclasses of this and throw them 38 | * 39 | */ 40 | public class CouldNotCreateException extends PengineException { 41 | private String message; 42 | 43 | /** 44 | * Usually only the library creates this exception. 45 | * 46 | * @param message the message returned by getMessage() 47 | */ 48 | public CouldNotCreateException(String message) { 49 | super(message); 50 | this.message = message; 51 | } 52 | 53 | /** 54 | * This returns a descriptive message. 55 | * 56 | * @see java.lang.Throwable#getMessage() 57 | */ 58 | @Override 59 | public String getMessage() { 60 | return message; 61 | } 62 | 63 | /** 64 | * Convert to a string - just returns the message passed to constructor. 65 | * 66 | * @see java.lang.Throwable#toString() 67 | */ 68 | @Override 69 | public String toString() { 70 | return message; 71 | } 72 | 73 | /** 74 | * Serialization ID 75 | */ 76 | private static final long serialVersionUID = 2600990953636107225L; 77 | 78 | } 79 | -------------------------------------------------------------------------------- /src/main/java/com/simularity/os/javapengine/Proof.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016 Simularity Inc. 3 | * 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | * 23 | */ 24 | package com.simularity.os.javapengine; 25 | 26 | import javax.json.JsonNumber; 27 | import javax.json.JsonObject; 28 | import javax.json.JsonString; 29 | import javax.json.JsonValue; 30 | import javax.json.JsonValue.ValueType; 31 | 32 | /** 33 | * @author anniepoo 34 | * 35 | */ 36 | public class Proof { 37 | JsonObject json; 38 | 39 | /** 40 | * @param jsonValue 41 | */ 42 | Proof(JsonObject jsonValue) { 43 | json = jsonValue; 44 | } 45 | 46 | /* (non-Javadoc) 47 | * @see java.lang.Object#toString() 48 | */ 49 | @Override 50 | public String toString() { 51 | return json.toString(); 52 | } 53 | 54 | public JsonValue getValue(String key) { 55 | return json.get(key); 56 | } 57 | 58 | public JsonObject getValues() { 59 | return json; 60 | } 61 | 62 | public String getString(String key) { 63 | switch (json.get(key).getValueType()) { 64 | case STRING: 65 | return ((JsonString)json.get(key)).getString(); 66 | default: 67 | return json.get(key).toString(); 68 | } 69 | } 70 | 71 | public int getInt(String key) { 72 | if (json.get(key).getValueType() == ValueType.NUMBER) 73 | return ((JsonNumber)json.get(key)).intValueExact(); 74 | else 75 | return Integer.parseInt(getString(key)); 76 | } 77 | 78 | public int getNearestInt(String key) { 79 | if (json.get(key).getValueType() == ValueType.NUMBER) 80 | return ((JsonNumber)json.get(key)).intValue(); 81 | else 82 | return Integer.parseInt(getString(key)); 83 | } 84 | 85 | public double getDouble(String key) { 86 | if (json.get(key).getValueType() == ValueType.NUMBER) 87 | return ((JsonNumber)json.get(key)).doubleValue(); 88 | else 89 | return Double.parseDouble(getString(key)); 90 | } 91 | } 92 | /* 93 | {"X":{"args":["taco"],"functor":"a"}} 94 | {"X":"b"} 95 | {"X":"c"} 96 | 97 | querying true gives an empty objectg 98 | 99 | {} 100 | */ -------------------------------------------------------------------------------- /overview.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Overview of JavaPengine 4 | 5 | 6 |

JavaPengines

7 |

JavaPengines is a client library for Torbjörn Lager's Pengines distributed computing library for 8 | SWI-Prolog 9 |

Installation

10 |

This library, which is available as a jar from Maven Central with groupID com.simularity and ArtifactID com.simularity.os.javapengine, or as sources at Github.

11 |

JavaPengine requires javax.json

12 |

Understanding JavaPengine

13 |

JavaPengine is a thin wrapper around Pengines, and so best use requires knowledge of the Pengines system, although only minimal understanding of pengines or of Prolog is sufficient to make basic queries

14 |

Unlike imperative programs, in Prolog one constructs a "knowledgebase" of rules about a world, and then asks the system to find proofs of propositions, called queries. So there are two parts to a Prolog program - the rules, and the query.

15 |

SWI-Prolog is probably the most widely used implementation of Prolog, particularly suitable for large real world projects.

16 |

Pengines extends SWI-Prolog to provide a distributed computing environment. Pengines exposes an HTTP based protocol that allows a remote system to submit queries and even alter the knowledgebase.

17 |

This is not unlike how web servers supply Javascript to the browser to execute, with the client in role of the server and the pengine server in role of the web browser.

18 |

Obviously doing this in an unconstrained way would be a huge security issue. So pengines executes the remote-supplied code in a sandbox.

19 |

Each created pengine creates, effectively, it's own Prolog sub-VM. Different pengine slaves see different knowledgebases. The pengine's knowledgebase is a combination of what the server chooses to expose, and what the client supplies. The server supplied part includes the safe parts of the standard Prolog libraries. For a complete explanation of this process, watch my video from Strange Loop

20 |

This provides lots of benefits. The remote client has a full, Turing complete language available for writing queries. Need some complex natural language scoring function computed to decide whether you need that row of data? Do it on the server.

21 |

The remote client can also store data on the server between invocations. Need to hang onto an intermediate stage query? Leave it on the server. Need to do graph traversal looking things? Do it on the server. Have a really complicated query you don't want to transmit for each query? Leave the code for it on the server.

22 |

Life Cycle

23 |

A slave pengine is created, used for zero or more queries, and then destroyed, either explicitly or by timing out.

24 |

A common, but not universal, pattern is to create a pengine, query it once, then destroy it. So JavaPengine supports this by allowing you to just make a query and repeatedly ask for the answers.

25 |

Prolog queries may return a single answer, but they can also fail (return no answer) or return many answers. This is fundamental to the SLD resolution mechanism at the core of Prolog. But often we know that queries will return a single answer, and we call this being deterministic or det. Those other queries are nondeterministic, or nondet.

26 |

JavaPengine has special support for making deterministic queries, and for making a single query and then destroying the pengine. This support is not only for convenience, but for efficiency.

27 |

In it's most basic use, the pengines protocol requires one HTTP request to create the pengine, one to initiate a query and get the first answer, one for each answer thereafter, and one to destroy the pengine.

28 |

But the pengines protocol allows the client to send the first query with the create message. This saves an HTTP round trip. The protocol also contains a flag that says 'destroy this pengine at the end of the first query'. This saves another round trip. For a pengine that is used for a single deterministic query, this reduces the number of HTTP requests from 3 to 1, a factor of 3 reduction in network traffic.

29 |

The getQuery and getProof methods use these optimizations internally. Their use when appropriate can reduce network traffic.

30 |

API

31 |

TODO will write this when it's more ready

32 |

Templates

33 |

TODO

34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /src/main/java/com/simularity/os/javapengine/example/ManualAsk.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016 Simularity Inc. 3 | * 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | * 23 | */ 24 | package com.simularity.os.javapengine.example; 25 | 26 | import java.net.MalformedURLException; 27 | 28 | import com.simularity.os.javapengine.Pengine; 29 | import com.simularity.os.javapengine.PengineBuilder; 30 | import com.simularity.os.javapengine.Proof; 31 | import com.simularity.os.javapengine.Query; 32 | import com.simularity.os.javapengine.exception.CouldNotCreateException; 33 | import com.simularity.os.javapengine.exception.PengineNotReadyException; 34 | 35 | /** 36 | * @author anniepoo 37 | * 38 | */ 39 | public abstract class ManualAsk { 40 | 41 | /** 42 | * 43 | */ 44 | private ManualAsk() { 45 | // class only exists to call main on 46 | } 47 | 48 | /** 49 | * @param args 50 | */ 51 | public static void main(String[] args) { 52 | 53 | PengineBuilder po = new PengineBuilder(); 54 | try { 55 | po.setServer("http://localhost:9900/"); 56 | Pengine p = po.newPengine(); 57 | p.dumpStateDebug(); 58 | for(Query q = p.ask("member(X, [a(taco),2,c])"); q.hasNext() ; ) { 59 | Proof proof = q.next(); 60 | 61 | System.out.println(proof.toString()); 62 | } 63 | p.dumpStateDebug(); 64 | 65 | System.err.println("now make one that sticks around"); 66 | po.setDestroy(false); 67 | Pengine p2 = po.newPengine(); 68 | p2.dumpStateDebug(); 69 | for(Query q = p2.ask("member(X, [a(taco),2,c])"); q.hasNext() ; ) { 70 | Proof proof = q.next(); 71 | 72 | System.out.println(proof.toString()); 73 | } 74 | 75 | System.out.println("that felt good, lets do another one"); 76 | 77 | for(Query q = p2.ask("(between(1,5, X), Y is 2 * X)"); q.hasNext() ; ) { 78 | Proof proof = q.next(); 79 | 80 | System.out.println(proof.toString()); 81 | } 82 | 83 | System.err.println("Now lets try stopping a query"); 84 | 85 | for(Query q = p2.ask("between(1,5, X)"); q.hasNext() ; ) { 86 | Proof proof = q.next(); 87 | 88 | System.out.println(proof.toString()); 89 | 90 | if(proof.getNearestInt("X") >= 3) { 91 | System.out.println("ok, enough of that"); 92 | q.stop(); 93 | } 94 | } 95 | System.out.println("whew, glad thats over. Lets see if the pengines still functioning"); 96 | 97 | for(Query q = p2.ask("member(X, [a(taco),2,c])"); q.hasNext() ; ) { 98 | Proof proof = q.next(); 99 | 100 | System.out.println(proof.toString()); 101 | } 102 | 103 | p2.dumpStateDebug(); 104 | p2.destroy(); 105 | p2.dumpStateDebug(); 106 | 107 | po.setSrctext("speak(X,Y) :- pengine_output(X), between(1,3,Y)."); 108 | po.setAlias("ioengine"); 109 | po.setDestroy(false); 110 | 111 | Pengine io = po.newPengine(); 112 | 113 | for(Query q = io.ask("(pengine_output('cabbages and kings'), between(1,3,X))"); q.hasNext() ; ) { 114 | Proof proof = q.next(); 115 | System.out.println(proof.toString()); 116 | 117 | if (proof.getNearestInt("X") < 3) { // change this to a larger number to see it hang 118 | // this hangs on the last proof 119 | // This is referred to in https://github.com/SWI-Prolog/pengines/issues/19 120 | String out = io.getOutput(); 121 | if(out != null) { 122 | System.out.println("I got " + out + " from the server"); 123 | } 124 | } 125 | } 126 | System.out.println("End of query"); 127 | 128 | io.destroy(); 129 | 130 | } catch (MalformedURLException e) { 131 | System.err.println("Bad URL" + e.getMessage()); 132 | e.printStackTrace(); 133 | } catch (CouldNotCreateException e) { 134 | System.err.println("cannot make pengine" + e.getMessage()); 135 | e.printStackTrace(); 136 | } catch (PengineNotReadyException e) { 137 | System.err.println("pengine not ready" + e.getMessage()); 138 | e.printStackTrace(); 139 | } 140 | } 141 | 142 | 143 | } 144 | -------------------------------------------------------------------------------- /src/main/java/com/simularity/os/javapengine/PengineState.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016 Simularity Inc. 3 | * 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | * 23 | */ 24 | package com.simularity.os.javapengine; 25 | 26 | import java.util.ArrayList; 27 | 28 | import com.simularity.os.javapengine.exception.PengineNotReadyException; 29 | /** 30 | * @author anniepoo 31 | * 32 | */ 33 | public class PengineState { 34 | public static enum PSt { 35 | NOT_CREATED, // state 0 in Torbjorns diagram 36 | IDLE, // state 2 in Torbjorns diagram 37 | ASK, // state 3 in Torbjorns diagram 38 | DESTROYED // state 1, but we don't provide a way to get to state 2 from here 39 | 40 | }; 41 | 42 | private static class Transition { 43 | public PSt from; 44 | public PSt to; 45 | 46 | Transition(PSt f, PSt t) { 47 | from = f; 48 | to = t; 49 | } 50 | 51 | /** 52 | * @see java.lang.Object#hashCode() 53 | */ 54 | @Override 55 | public int hashCode() { 56 | 57 | return super.hashCode() + to.hashCode() + from.hashCode(); 58 | } 59 | 60 | /** 61 | * @see java.lang.Object#equals(java.lang.Object) 62 | */ 63 | @Override 64 | public boolean equals(Object obj) { 65 | if(obj == null || !(obj instanceof Transition)) 66 | return false; 67 | 68 | Transition ot = (Transition)obj; 69 | 70 | return (ot.from.equals(this.from) && ot.to.equals(this.to)); 71 | } 72 | } 73 | 74 | private static ArrayList allowed = new ArrayList(); 75 | 76 | static { 77 | allowed.add(new Transition(PSt.NOT_CREATED, PSt.IDLE)); 78 | allowed.add(new Transition(PSt.NOT_CREATED, PSt.ASK)); 79 | allowed.add(new Transition(PSt.IDLE, PSt.ASK)); 80 | allowed.add(new Transition(PSt.ASK, PSt.IDLE)); 81 | allowed.add(new Transition(PSt.IDLE, PSt.DESTROYED)); 82 | allowed.add(new Transition(PSt.ASK, PSt.DESTROYED)); 83 | allowed.add(new Transition(PSt.NOT_CREATED, PSt.DESTROYED)); // if we can't create it 84 | } 85 | 86 | private PSt state = PSt.NOT_CREATED; 87 | 88 | /** 89 | * 90 | */ 91 | public PengineState() { 92 | super(); 93 | } 94 | 95 | public void setState(PSt newstate) throws PengineNotReadyException { 96 | if(newstate.equals(state)) 97 | return; 98 | 99 | if(allowed.contains(new Transition(this.state, newstate))) { 100 | state = newstate; 101 | } else { 102 | throw new PengineNotReadyException("Darn it can't transition from" + this.state.toString() + " to " + newstate.toString()); 103 | } 104 | } 105 | 106 | public PSt getState() { 107 | return state; 108 | } 109 | 110 | /** 111 | * @param destroyed 112 | */ 113 | public boolean isIn(PSt aState) { 114 | return this.state.equals(aState); 115 | 116 | } 117 | 118 | /** 119 | * throw an IllegalStateException if we're not in 120 | * 121 | * @param aState 122 | * @throws PengineNotReadyException 123 | */ 124 | public void must_be_in(PSt aState) throws PengineNotReadyException { 125 | if(!isIn(aState)) 126 | throw new PengineNotReadyException("Should be in " + aState.toString() + ", but is in " + state.toString()); 127 | 128 | } 129 | 130 | /** 131 | * @param aState 132 | * @param anotherState 133 | */ 134 | public void must_be_in(PSt aState, PSt anotherState) { 135 | if(!isIn(aState) && !isIn(anotherState)) 136 | throw new IllegalStateException("Should be in " + aState.toString() + ", but is in " + state.toString()); 137 | 138 | } 139 | 140 | /** 141 | * pengines can always be destroyed via this method 142 | * 143 | */ 144 | void destroy() { 145 | this.state = PSt.DESTROYED; 146 | 147 | } 148 | 149 | /** 150 | * 151 | */ 152 | public void dumpDebugState() { 153 | System.err.println("state " + this.state.toString()); 154 | 155 | } 156 | 157 | /* (non-Javadoc) 158 | * @see java.lang.Object#hashCode() 159 | */ 160 | @Override 161 | public int hashCode() { 162 | return this.state.hashCode(); 163 | } 164 | 165 | /* (non-Javadoc) 166 | * @see java.lang.Object#equals(java.lang.Object) 167 | */ 168 | @Override 169 | public boolean equals(Object obj) { 170 | if(obj == this) 171 | return true; 172 | 173 | if(obj instanceof PengineState) { 174 | return ( ((PengineState)obj).getState().equals(this.state)); 175 | } 176 | 177 | if(obj instanceof PSt) { 178 | return obj.equals(this.state); 179 | } 180 | return false; 181 | } 182 | 183 | /* (non-Javadoc) 184 | * @see java.lang.Object#toString() 185 | */ 186 | @Override 187 | public String toString() { 188 | return state.toString(); 189 | } 190 | 191 | 192 | } 193 | -------------------------------------------------------------------------------- /src/main/java/com/simularity/os/javapengine/Query.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016 Simularity Inc. 3 | * 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | * 23 | */ 24 | package com.simularity.os.javapengine; 25 | 26 | import java.util.Iterator; 27 | import java.util.Vector; 28 | 29 | import javax.json.JsonArray; 30 | import javax.json.JsonObject; 31 | import javax.json.JsonValue; 32 | 33 | import com.simularity.os.javapengine.exception.PengineNotReadyException; 34 | 35 | /** 36 | * @author anniepoo 37 | * 38 | * is this a public class, or a 39 | */ 40 | public class Query { 41 | 42 | private boolean hasMore = true; // there are more answers on the server 43 | private Pengine p; 44 | private Vector availProofs = new Vector(); 45 | 46 | // TODO Query must call the pengine back when it's returned it's last answer so the pengine can let go 47 | 48 | /** 49 | * @param pengine 50 | * @param ask 51 | * @param queryMaster 52 | * @throws PengineNotReadyException 53 | */ 54 | Query(Pengine pengine, String ask, boolean queryMaster) throws PengineNotReadyException { 55 | p = pengine; 56 | 57 | if(queryMaster) { 58 | p.doAsk(this, ask); 59 | } 60 | } 61 | 62 | /** 63 | * @param pengine 64 | * @param query 65 | * @param template 66 | */ 67 | Query(Pengine pengine, String query, String template) { 68 | // TODO Auto-generated constructor stub 69 | } 70 | 71 | /** 72 | * @param pengine 73 | * @param ask 74 | * @throws PengineNotReadyException 75 | */ 76 | Query(Pengine pengine, String ask) throws PengineNotReadyException { 77 | p = pengine; 78 | 79 | p.doAsk(this, ask); 80 | } 81 | 82 | /** 83 | * return the next proof, or null if not available 84 | * 85 | * Note that it is theoretically impossible to always know that there are more 86 | * proofs available. Caller always needs to be ready to handle a null 87 | * 88 | * It is guaranteed that if you get a null from this the query is done and will 89 | * never return a non-null in the future. 90 | * 91 | * @return the next proof, or null if not available 92 | * @throws PengineNotReadyException 93 | */ 94 | public Proof next() throws PengineNotReadyException { 95 | // the was data available 96 | if(!availProofs.isEmpty()) { 97 | JsonObject data = availProofs.get(0); 98 | availProofs.remove(0); 99 | if(!hasMore && availProofs.isEmpty()) 100 | p.iAmFinished(this); 101 | 102 | return new Proof(data); 103 | } 104 | 105 | // we don't have any available and the server's done 106 | if(!hasMore) { 107 | return null; 108 | } 109 | 110 | // try to get more from the server 111 | p.doNext(this); 112 | 113 | // if we now have data, we have to do just like above 114 | if(!availProofs.isEmpty()) { 115 | JsonObject data = availProofs.get(0); 116 | availProofs.remove(0); 117 | if(!hasMore && availProofs.isEmpty()) 118 | p.iAmFinished(this); 119 | return new Proof(data); 120 | } else { // we asked for data and didn't get it, the server must be done 121 | if(hasMore)System.err.println("Why is hasMore true here?"); 122 | 123 | return null; 124 | } 125 | } 126 | 127 | // TODO make version with template 128 | 129 | /** 130 | * signal the query that there are no more Proofs of the query available. 131 | */ 132 | void noMore() { 133 | if(!hasMore) // must never call iAmFinished more than once 134 | return; 135 | 136 | hasMore = false; 137 | if(availProofs.isEmpty()) 138 | p.iAmFinished(this); 139 | 140 | // we might be held externally, waiting to deliver last Proof or no-more-Proof result 141 | } 142 | 143 | /** 144 | * @param newDataPoints 145 | */ 146 | void addNewData(JsonArray newDataPoints) { 147 | for(Iterator iter = newDataPoints.listIterator(); iter.hasNext() ; availProofs.add( ((JsonObject)iter.next()))); 148 | } 149 | 150 | // TODO make this a real iterator 151 | 152 | /** 153 | * @return 154 | */ 155 | public boolean hasNext() { 156 | return hasMore || !availProofs.isEmpty(); 157 | } 158 | 159 | /** 160 | * 161 | */ 162 | public void dumpDebugState() { 163 | if(this.hasMore) 164 | System.err.println("has more solutions"); 165 | else 166 | System.err.println("no more solutions"); 167 | 168 | System.err.println("availproofs" + this.availProofs.toString()); 169 | System.err.println("pengine is " + this.p.getID()); 170 | } 171 | 172 | /** 173 | * @throws PengineNotReadyException 174 | * 175 | */ 176 | public void stop() throws PengineNotReadyException { 177 | p.doStop(); 178 | 179 | hasMore = false; 180 | availProofs.clear(); 181 | 182 | p.iAmFinished(this); 183 | } 184 | } 185 | -------------------------------------------------------------------------------- /protocoltests.txt: -------------------------------------------------------------------------------- 1 | /* { "answer": { 2 | "data": ["a" ], 3 | "event":"success", 4 | "id":"2204d4b2-de94-4946-965c-e7aaf12a1a8b", 5 | "more":true, 6 | "projection": ["X" ], 7 | "time":2.2752000000000015e-5 8 | }, 9 | "event":"create", 10 | "id":"2204d4b2-de94-4946-965c-e7aaf12a1a8b", 11 | "slave_limit":3 12 | } 13 | 14 | if no first solution job.add("ask", "member(X, [])"); 15 | { 16 | "answer":{ 17 | "data":{ 18 | "event":"failure", 19 | "id":"c7e9e0c5-84b6-4faa-bbcb-1e1139df1206", 20 | "time":0.000019857999999999985 21 | }, 22 | "event":"destro. 23 | y", 24 | "id":"c7e9e0c5-84b6-4faa-bbcb-1e1139df1206" 25 | }, 26 | "event":"create", 27 | "id":"c7e9e0c5-84b6-4faa-bbcb-1e1139df1206", 28 | "slave_limit":3 29 | } 30 | 31 | What happens if job.add("ask", "member(X, ][])"); (syntax invalid)? 32 | it returns 500 33 | (document that it also 500's if it throws an uncaught exception) 34 | 35 | what if we don't use template? 36 | { 37 | "answer": { 38 | "data": [ {"X":"a"} ], 39 | "event":"success", 40 | "id":"4f856b3a-a2a5-493c-8241-baf1a5a5ed28", 41 | "more":true, 42 | "projection": ["X" ], 43 | "time":1.5222999999999973e-5 44 | }, 45 | "event":"create", 46 | "id":"4f856b3a-a2a5-493c-8241-baf1a5a5ed28", 47 | "slave_limit":3} 48 | 49 | with more than one variable a couple fields change 50 | job.add("ask", "member((X,Y), [(a,3),(b,4),(c,5)])"); 51 | // job.add("template", "X"); 52 | "data": [ {"X":"a", "Y":3} ], 53 | "projection": ["X", "Y" ], 54 | 55 | 56 | non atom response 57 | job.add("ask", "member(X, [(a,3),(b,4),(c,5)])"); 58 | job.add("template", "X"); 59 | { "answer": { 60 | "data": [ {"args": ["a", 3 ], "functor":","} ], 61 | "event":"success", 62 | "id":"514e0608-5925-4774-a408-4d5d03344c5f", 63 | "more":true, 64 | "projection": ["X" ], 65 | "time":1.6837000000000015e-5 }, 66 | "event":"create", 67 | "id":"514e0608-5925-4774-a408-4d5d03344c5f", 68 | "slave_limit":3} 69 | 70 | now I'll set the type to json-s in the create 71 | and it just 500's 72 | 73 | Try it with chunking set to 2 74 | job.add("ask", "member((X,Y), [(a,3),(b,4),(c,5)])"); 75 | 76 | { 77 | "answer": { 78 | "data": [ {"X":"a", "Y":3}, {"X":"b", "Y":4} ], 79 | "event":"success", 80 | "id":"b648885b-cf65-49f4-a028-756bb2579d2b", 81 | "more":true, 82 | "projection": ["X", "Y" ], 83 | "time":1.6732999999999896e-5 84 | }, 85 | "event":"create", 86 | "id":"b648885b-cf65-49f4-a028-756bb2579d2b", 87 | "slave_limit":3 88 | } 89 | 90 | what if the value bound is compound? I changed a in the example to a(taco) 91 | "data": [ {"X": {"args": ["taco" ], "functor":"a"}, "Y":3}, {"X":"b", "Y":4} ], 92 | */ 93 | 94 | /* 95 | * This project has involved a lot of experimenting, as hte protocol is poorly documented. As such, 96 | * I'm retaining my notes here 97 | */ 98 | // //pengine/create 99 | // application/json 100 | // see server_url/4, line 1529 of pengines.pl 101 | /* 102 | * HTTP/1.1 200 OK 103 | Server: nginx/1.4.6 (Ubuntu) 104 | Date: Wed, 20 Apr 2016 20:53:39 GMT 105 | Content-Type: text/x-prolog; charset=UTF-8 106 | Content-Length: 65 107 | Connection: close 108 | 109 | http://www.oracle.com/technetwork/articles/java/json-1973242.html 110 | 111 | Asks response looks like 112 | success('8eb2ec31-fd63-43a2-a80b-4552b6d505d7', 113 | [q(b)], 114 | 1.1307000000002065e-5, 115 | true) 116 | 117 | the last item is whether there are more 118 | 119 | then when you get the last one, and it does a destroy (I guess?) 120 | 121 | destroy('8eb2ec31-fd63-43a2-a80b-4552b6d505d7', 122 | success('8eb2ec31-fd63-43a2-a80b-4552b6d505d7', 123 | [q(c)], 124 | 2.1917999999999244e-5, 125 | false)) 126 | 127 | 128 | from pengines.pl http_pengine_create/1 129 | 130 | % HTTP POST handler for =/pengine/create=. This API accepts the 131 | % pengine creation parameters both as =application/json= and as 132 | % =www-form-encoded=. 133 | 134 | Looks like event_to_json we want the lang to be json-s maybe? certainly need to look into it. 135 | For the moment, just json 136 | 137 | Next uri looks like 138 | 139 | '/pengine/send?id=421ec554-f15c-4798-b10d-897831727bef 140 | (it's the same id,event,format handler in http_pengine_send, so same possible args) 141 | 142 | and returns this strange critter 143 | 144 | 145 | {"data": 146 | {"data":[{"X":"c"}], 147 | "event":"success", 148 | "id":"4c53430f-9570-4e05-9036f6b5fc63dd8c", 149 | "more":false, 150 | "time":0.00003951999999999992 151 | }, 152 | "event":"destroy", 153 | "id":"4c53430f-9570-4e05-9036-f6b5fc63dd8c" 154 | } 155 | 156 | 157 | 158 | 159 | */ -------------------------------------------------------------------------------- /PrologBluffers.md: -------------------------------------------------------------------------------- 1 | 2 | ## A non-Prolog Programmer's Guide to Prolog Queries 3 | 4 | TODO is this really a good idea? 5 | 6 | If you know Prolog you can skip this section. If you know Prolog and are pissed because this section misses the whole point of the language, yup, it does, I know that, it delivers what Pengine users need and no more. 7 | 8 | If not, don't worry. Being a Prolog programmer isn't necessary to write JavaPengine client code or construct basic JavaPengine queries. This section should give you enough understanding to make most Pengines queries. 9 | 10 | ### Prolog code is predicates 11 | 12 | All Prolog code is 'predicates', which are a bit like functions, but have important differences. 13 | 14 | The set of all predicates that can be seen by a pengine is called it's knowledgebase. The Pengine server has a different knowledgebase, and some parts overlap, but the pengine has it's own knowledgebase. 15 | 16 | Queries are just a predicate call, like this 17 | 18 | --- 19 | employee(12345, 'Bob Smith', manager) 20 | --- 21 | 22 | Prolog runs this and says whether it can prove this is true. That is, 23 | "Can you prove that there's an employee with number 12345 named Bob Smith who'se a manager?" 24 | 25 | This is usually not that useful, of course. 26 | 27 | Far more useful is 28 | 29 | ---- 30 | employee(EmpNumber, 'Bob Smith', manager) 31 | ---- 32 | 33 | This asks "can you prove that there's an employee with number EmpNumber named Bob Smith who'se a manager, and if so, what would EmpNumber 34 | have to be to make it true?" 35 | 36 | And we get back EmpNumber has to be 12345. 37 | 38 | This means we can do any SELECT/WHERE type clause. 39 | 40 | ### Variables 41 | 42 | EmpNumber is a variable. If you put a variable in a position it gets filled in with a value that makes the whole thing true. 43 | Variables must start with an upper case letter. By convention, they're CamelCase. 44 | 45 | ---- 46 | employee(12345, Name, Position) 47 | ---- 48 | 49 | This query returns the Name and Position of employee 12345. 50 | 51 | ### Constants 52 | 53 | manager is a constant, just like 12345. It's an _atom_. Atoms must start with a lowercase letter. By convention atoms_are_lowercase_with_underscores. 54 | 55 | Single quoted strings are also atoms. foo and 'foo' are identical in Prolog. This is useful because atoms are also used as strings. 56 | 57 | Besides numbers and atoms, double quoted strings are a type (they're strings in SWI-Prolog, and lists of integer ASCII codes in traditional Prologs). 58 | 59 | There are three compound types. Lists like [1,2,3,4] and compound terms like foo(3). This latter is _not_ a function call. It's more like a record. SWI-Prolog has also added _dicts_, which are key-value map structures with atom keys, like +pt{x:4.5, y:6.9}+. 60 | 61 | 62 | ---- 63 | employee(12000 + 345, Name, Position) 64 | ---- 65 | 66 | This is NOT the same as the previous query. Arguments aren't evaluated in Prolog! The employee predicate will see '+'(12000,345) as it's first argument. The + operator is just syntactic sugar for '+'(12000,345), a compound term . 67 | 68 | So, this process of matching up possible solutions is one big difference between Prolog predicates and most language's functions. 69 | 70 | ### Multiple Results 71 | 72 | The second is that Prolog will give you not just one answer. You get all the ways Prolog can figure out to fullfill the query (all the proofs). 73 | 74 | If we query 75 | 76 | ---- 77 | employee(Number, Name, manager) 78 | ---- 79 | 80 | and we have several managers, Prolog will give us all the different combinations that satisfy Number and Name. JavaPengines Query class acts like an iterator - successive items give successive proofs. 81 | 82 | ### Stringing together predicates 83 | 84 | You can put several predicates in a query and string them together with commas, surrounding the whole thing with parens. This restricts the solutions to only those that satisfy all the parts. 85 | 86 | ---- 87 | (employee(Number, Name, Position), member(Position, [manager, assistant_manager]) 88 | ---- 89 | 90 | This is true for all combinations of Number and Name that describe managers or assistant managers. 91 | 92 | member is a library predicate in SWI-Prolog, and is available in the sandbox. It's true when it's first argument is a member of the list made up of it's second argument. 93 | 94 | By stringing together predicates with commas we can make quite complex queries. 95 | 96 | Suppose we have another predicate salary. 97 | 98 | ---- 99 | salary(12345, 122000). 100 | salary(34625, 56000). 101 | ---- 102 | 103 | We can find out Bob Smith's salary 104 | 105 | ---- 106 | (employee(Number, 'Bob Smith', _), salary(Number, Salary)) 107 | ---- 108 | 109 | Which is true only when Number is Bob's number, and salary is his Salary. 110 | 111 | This example also introduces the underscore. We really weren't interested in Bob's position, so we used a special variable, the underscore, which means 'I don't care what you put in here'. 112 | 113 | ### Moving Forward 114 | 115 | You can do a lot more with Prolog. We wanted to give users enough to make simple queries. 116 | 117 | Prolog is a full fledged computer language. There are large commercial applications written entirely in Prolog, everything from CAD systems to stock trading systems. 118 | 119 | You can do a lot more with Pengines. You can place additional code in your pengine and then query your own code. You have the same power at your disposal as a programmer working locally in Prolog. Pengines makes a good 'glue' to hold large systems together. 120 | 121 | If you want to learn Prolog, a good starting place is the book "Programming in Prolog" by William Clocksin and Chris Mellish. An online resource is [http://lpn.swi-prolog.org/](Learn Prolog Now), an interactive Prolog course (that uses Pengines, by the way). The ##prolog channel on freenode.net IRC is often helpful. 122 | 123 | the website for [http://swi-prolog.org](SWI-Prolog) is the authority on SWI-Prolog. The search tool is your gateway to discovering the extensive SWI-Prolog libraries. 124 | 125 | If you want to experiment with Prolog, one particularly easy way is via the [http://swish.swi-prolog.org](SWISH online tool). This IDE in a web page environment is a great way to learn. It's implemented using pengines, so you're using the same environment you'll be using to query Pengines. 126 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | #JavaPengines 2 | 3 | A Java language client for Torbjörn Lager's _Pengines_ distributed computing library for 4 | _[SWI-Prolog](http://swi-prolog.org)_ . 5 | 6 | JavaPengines is a Java language implementation of the _Pengines_ protocol, a lightweight, powerful query and update protocol for a _Pengines_ server. 7 | 8 | Pengines servers dramatically reduce the complexity of performing RPC and sharing _knowledge_ in a mixed technology environment. They can act effectively as 'glue' in a complex system. 9 | 10 | Currently there are clients for JavaScript (in browser), SWI-Prolog, and Java. We expect clients for Ruby, Python, and NodeJS to be written in the next few months. 11 | 12 | The query language for Pengines is _Prolog_, and so unsurprisingly, the only current implementation of the Pengines server ships with the _[SWI-Prolog](http://swi-prolog.org)_ environment. SWI-Prolog is probably the most widely used implementation of Prolog, particularly suitable for large _real world_ projects. 13 | 14 | 15 | ## What You need to know to use JavaPengines 16 | 17 | You do *not* need to be an expert in Prolog. The section below should teach you enough to make basic Pengines queries. 18 | 19 | You do need to know Java, obviously. 20 | 21 | You would also do well to familiarize yourself with the javax.json library. 22 | 23 | ##Installation 24 | 25 | JavaPengines is available as a jar from Maven Central with groupID com.simularity and ArtifactID com.simularity.os.javapengine, or as sources at [Github](http://www.github.com/Simularity/Javapengine/). 26 | 27 | JavaPengines requires _javax.json_, which is available from Maven Central. 28 | 29 | 30 | ## Understanding JavaPengine 31 | 32 | JavaPengine is a thin wrapper around [http://pengines.swi-prolog.org/docs/index.html](Pengines), and so use requires knowledge of the Pengines system, although only minimal understanding of pengines or of Prolog is sufficient to make basic queries. 33 | 34 | The Pengine architecture is simple. The client requests the server to create a Pengine _slave_. The client then sends one or more queries to the slave, and then tells the server to destroy the pengine. 35 | 36 | For efficiency, a query can be sent along with the create query. The pengine can be told to destroy itself at the end of the first query. So a Pengine can be created, queried, and destroyed in as little as a single HTTP request. 37 | 38 | The queries are simply Prolog code. So the entire power of the Prolog language is available to the client. 39 | 40 | Obviously the Pengine server must _sandbox_ the query. So some Prolog library predicates (e.g. shell) are unavailable. But, as much as is consistent with security, the standard Prolog libraries are available to the Pengine slave. 41 | 42 | Additionally, Pengine servers usually expose some special predicates (Prolog 'functions' are called predicates). So, for example, a Prolog server could expose a predicate that allows a user to set their profile (presumably also passing some authentication). 43 | 44 | Because the Pengine can last longer than one query, the client can store information on the server between queries. This can significantly reduce network traffic during complex graph queries. 45 | 46 | ### The Pengines Architecture 47 | 48 | Unlike imperative programs, in Prolog one constructs a "knowledgebase" of rules about a world, and then asks the system to find proofs of propositions, called queries. So there are two parts to a Prolog program - the rules, and the query. 49 | 50 | Pengines extends SWI-Prolog to provide a distributed computing environment. Pengines exposes an HTTP based protocol that allows a remote system to submit queries and even alter the knowledgebase. 51 | 52 | This is not unlike how web servers supply JavaScript to the browser to execute, with the client in role of the server and the pengine server in role of the web browser. 53 | 54 | Each created pengine creates, effectively, it's own Prolog sub-VM. Different pengine slaves see different knowledgebases. 55 | 56 | The pengine's knowledgebase is a combination of what the server chooses to expose, and what the client supplies. The server supplied part includes the safe parts of the standard Prolog libraries. 57 | 58 | For a complete explanation of this process, watch [https://www.youtube.com/watch?v=JmOHV5IlPyU](my video from Strange Loop) 59 | 60 | This provides lots of benefits. The remote client has a full, Turing complete language available for writing queries. Need some complex natural language scoring function computed to decide whether you need that row of data? Do it on the server. 61 | 62 | The remote client can also store data on the server between invocations. Need to hang onto an intermediate stage query? Leave it on the server. Need to do graph traversal looking things? Do it on the server. Have a really complicated query you don't want to transmit for each query? Leave the code for it on the server. 63 | 64 | ### Life Cycle 65 | 66 | A slave pengine is created, used for zero or more queries, and then destroyed, either explicitly or by timing out. 67 | A common, but not universal, pattern is to create a pengine, query it once, then destroy it. So JavaPengine supports this by allowing you to just make a query and repeatedly ask for the answers. 68 | 69 | Making a Pengine for a single query is so common that it is the default for PengineBuilder. To retain the Pengine you must call setDestroy(false) on the PengineBuilder. 70 | 71 | Prolog queries may return a single answer, but they can also fail (return no answer) or return many answers. This is fundamental to the SLD resolution mechanism at the core of Prolog. 72 | 73 | In it's most basic use, the pengines protocol requires one HTTP request to create the pengine, one to initiate a query and get the first answer, one for each answer thereafter, and one to destroy the pengine. 74 | 75 | But the pengines protocol allows the client to send the first query with the create message. This saves an HTTP round trip. The protocol also contains a flag that says 'destroy this pengine at the end of the first query'. This saves another round trip. For a pengine that is used for a single deterministic query, this reduces the number of HTTP requests from 3 to 1, a factor of 3 reduction in network traffic. 76 | 77 | The getQuery and getProof methods use these optimizations internally. Their use when appropriate can reduce network traffic. 78 | 79 | API 80 | --- 81 | 82 | Making a Pengine starts with `com.simularity.os.javapengine.PengineBuilder`. 83 | 84 | Make a new PengineBuilder object, set some values on it, and then user it to make one or more Pengines. 85 | 86 | the `setDestroy(boolean)` method is particularly important to set. By default it's true, and when the first query done on the pengine has returned it's last result, the pengine destroys itself. 87 | 88 | Some arguments to create a Pengine change with each Pengine, like ask. Some are usually constant, like the server's name. It can be useful to have a prototype PengineBuilder around and clone it, then change values on the clone before making the Pengine. 89 | 90 | Call `newPengine()` to get a new Pengine. 91 | 92 | If you supplied an ask to the PengineBuilder via `setAsk(String)`, the Pengine will already be executing a query. You can get the query via `getCurrentQuery()`. 93 | 94 | If not, you'll need to start a query with `ask(String)`. 95 | 96 | Both of these return a `com.simularity.os.javapengine.Query` object. 97 | 98 | if the `hasNext()` method returns true, the query _may_ have more answers. `next()` returns the next Proof, or null if there are none. 99 | 100 | The Proof object has a key-value map that maps variables in the query to values. So 101 | 102 | --- 103 | member(X, [a,b,c]) 104 | --- 105 | 106 | would result in `{ "X": "a"}` as the first proof, `{ "X": "b"}` as the second, and `{ "X": "c"}` as the last. 107 | 108 | There are convenience methods for extracting common Java types from the JSON structure. 109 | 110 | If you want to stop getting solutions before they're exhausted, Query has a `stop()` method. 111 | 112 | After you have stopped or exhausted the solutions, you can start another query. Each Pengine can be used for only one query at a time. 113 | 114 | When you are done with the Pengine, call destroy() on it. This will happen automatically if you left setDestroy set to true. 115 | 116 | 117 | 118 | 119 | 120 | 121 | -------------------------------------------------------------------------------- /src/main/java/com/simularity/os/javapengine/PengineBuilder.java: -------------------------------------------------------------------------------- 1 | package com.simularity.os.javapengine; 2 | 3 | import java.io.UnsupportedEncodingException; 4 | import java.net.MalformedURLException; 5 | import java.net.URI; 6 | import java.net.URISyntaxException; 7 | import java.net.URL; 8 | import java.net.URLEncoder; 9 | 10 | import javax.json.Json; 11 | import javax.json.JsonBuilderFactory; 12 | import javax.json.JsonObjectBuilder; 13 | 14 | import com.simularity.os.javapengine.exception.CouldNotCreateException; 15 | import com.simularity.os.javapengine.exception.PengineNotReadyException; 16 | 17 | /* 18 | * Copyright (c) 2016 Simularity Inc. 19 | * 20 | 21 | Permission is hereby granted, free of charge, to any person obtaining a copy 22 | of this software and associated documentation files (the "Software"), to deal 23 | in the Software without restriction, including without limitation the rights 24 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 25 | copies of the Software, and to permit persons to whom the Software is 26 | furnished to do so, subject to the following conditions: 27 | 28 | The above copyright notice and this permission notice shall be included in 29 | all copies or substantial portions of the Software. 30 | 31 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 32 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 33 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 34 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 35 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 36 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 37 | THE SOFTWARE. 38 | * 39 | */ 40 | public final class PengineBuilder implements Cloneable { 41 | private URL server = null; 42 | private String application = "sandbox"; 43 | private String ask = null; 44 | private int chunk = 1; 45 | private boolean destroy = true; 46 | private String srctext = null; 47 | private URL srcurl = null; 48 | private final String format = "json"; 49 | private String alias = null; 50 | 51 | 52 | /** 53 | * 54 | */ 55 | public PengineBuilder() { 56 | super(); 57 | } 58 | 59 | /** 60 | * @see java.lang.Object#clone() 61 | */ 62 | @Override 63 | public final PengineBuilder clone() throws CloneNotSupportedException { 64 | // TODO Auto-generated method stub 65 | return (PengineBuilder)super.clone(); 66 | } 67 | 68 | /** 69 | * Get the actual URL to request from 70 | * 71 | * 72 | * @param action the action to request - create, send, etc - as a string. Note this is the URI endpoint name, not the pengine action 73 | * @return the URL to perform the request to 74 | * 75 | * @throws PengineNotReadyException 76 | */ 77 | URL getActualURL(String action) throws PengineNotReadyException { 78 | StringBuffer msg = new StringBuffer("none"); 79 | 80 | if(server == null) { 81 | throw new PengineNotReadyException("Cannot get actual URL without setting server"); 82 | } 83 | try { 84 | URI uribase = server.toURI(); 85 | if (uribase.isOpaque()) { 86 | throw new PengineNotReadyException("Cannot get actual URL without setting server"); 87 | } 88 | 89 | URI relative = new URI("/pengine/" + action); 90 | 91 | URI fulluri = uribase.resolve(relative); 92 | msg.append(fulluri.toString()); 93 | return fulluri.toURL(); 94 | } catch (MalformedURLException e) { 95 | throw new PengineNotReadyException("Cannot form actual URL for action " + action + " from uri " + msg.toString()); 96 | } catch (URISyntaxException e) { 97 | throw new PengineNotReadyException("URISyntaxException in getActualURL"); 98 | } 99 | } 100 | 101 | /** 102 | * Get the actual URL to request from 103 | * 104 | * @param action the action to request - create, send, etc - as a string. Note this is the URI endpoint name, not the pengine action 105 | * @param id the pengine ID 106 | * @return the created URL 107 | * 108 | * @throws PengineNotReadyException 109 | */ 110 | URL getActualURL(String action, String id) throws PengineNotReadyException { 111 | StringBuffer msg = new StringBuffer("none"); 112 | 113 | if(server == null) { 114 | throw new PengineNotReadyException("Cannot get actual URL without setting server"); 115 | } 116 | try { 117 | URI uribase = server.toURI(); 118 | if (uribase.isOpaque()) { 119 | throw new PengineNotReadyException("Cannot get actual URL without setting server"); 120 | } 121 | 122 | URI relative; 123 | try { 124 | relative = new URI("/pengine/" + action + "?format=json&id=" + URLEncoder.encode(id, "UTF-8")); 125 | } catch (UnsupportedEncodingException e) { 126 | // stupid checked exception 127 | e.printStackTrace(); 128 | return null; 129 | } 130 | 131 | URI fulluri = uribase.resolve(relative); 132 | 133 | msg.append(fulluri.toString()); 134 | return fulluri.toURL(); 135 | } catch (MalformedURLException e) { 136 | throw new PengineNotReadyException("Cannot form actual URL for action " + action + " from uri " + msg.toString()); 137 | } catch (URISyntaxException e) { 138 | throw new PengineNotReadyException("URISyntaxException in getActualURL"); 139 | } 140 | } 141 | 142 | /** 143 | * @return a string representation of the request body for the create action 144 | */ 145 | String getRequestBodyCreate() { 146 | JsonBuilderFactory factory = Json.createBuilderFactory(null); 147 | JsonObjectBuilder job = factory.createObjectBuilder(); 148 | 149 | if(!this.destroy) { 150 | job.add("destroy", "false"); 151 | } 152 | if(this.chunk > 1) { 153 | job.add("chunk", this.chunk); 154 | } 155 | job.add("format", this.format); 156 | 157 | if(this.srctext != null) { 158 | job.add("srctext", this.srctext); 159 | } 160 | if(this.srcurl != null) { 161 | job.add("srcurl", this.srcurl.toString()); 162 | } 163 | 164 | // test protocol 165 | // job.add("ask", "member((X,Y), [(a(taco),3),(b,4),(c,5)])"); 166 | // job.add("template", "X"); 167 | 168 | // this will be a json object with fields for options 169 | // sample, as a prolog dict 170 | //_{ src_text:"\n q(X) :- p(X).\n p(a). p(b). p(c).\n "} 171 | return job.build().toString(); 172 | } 173 | 174 | /** 175 | * @param urlstring String that represents the server URL - this does not contain the /pengines/create extension 176 | * @throws MalformedURLException if the string can't be turned into an URL 177 | */ 178 | public void setServer(String urlstring) throws MalformedURLException { 179 | server = new URL(urlstring); 180 | } 181 | 182 | /** 183 | * @param server the server base URL - this does not contain the /pengines/create extension 184 | */ 185 | public void setServer(URL server) { 186 | this.server = server; 187 | } 188 | 189 | /** 190 | * @return the server 191 | */ 192 | public URL getServer() { 193 | return server; 194 | } 195 | 196 | 197 | /** 198 | * @return the application name 199 | */ 200 | public String getApplication() { 201 | return application; 202 | } 203 | 204 | /** 205 | * @param application the application to set 206 | */ 207 | public void setApplication(String application) { 208 | this.application = application; 209 | } 210 | 211 | /** 212 | * @return the query that will be sent along with the create, or null if none 213 | */ 214 | public String getAsk() { 215 | return ask; 216 | } 217 | 218 | /** 219 | * @param ask the query to be sent along with the create, or null to not send one 220 | */ 221 | public void setAsk(String ask) { 222 | this.ask = ask; 223 | } 224 | 225 | /** 226 | * @return the number of answers to return in one HTTP request 227 | */ 228 | public int getChunk() { 229 | return chunk; 230 | } 231 | 232 | /** 233 | * @param chunk the max number of answers to return in one HTTP request - defaults to 1 234 | */ 235 | public void setChunk(int chunk) { 236 | this.chunk = chunk; 237 | } 238 | 239 | /** 240 | * @return true if we will destroy the pengine at the close of the first query 241 | */ 242 | public boolean isDestroy() { 243 | return destroy; 244 | } 245 | 246 | /** 247 | * @param destroy Destroy the pengine when the first query concludes? 248 | */ 249 | public void setDestroy(boolean destroy) { 250 | this.destroy = destroy; 251 | } 252 | 253 | /** 254 | * @return the srctext @see setSrctext 255 | */ 256 | public String getSrctext() { 257 | return srctext; 258 | } 259 | 260 | /** 261 | * @param srctext Additional Prolog code, which must be safe, to be included in the pengine's knowledgebase 262 | */ 263 | public void setSrctext(String srctext) { 264 | this.srctext = srctext; 265 | } 266 | 267 | /** 268 | * @return the URL of some additional Prolog code, which must be safe, to be included in the pengine's knowledgebase 269 | */ 270 | public URL getSrcurl() { 271 | return srcurl; 272 | } 273 | 274 | /** 275 | * @param srcurl the srcurl to set 276 | */ 277 | public void setSrcurl(URL srcurl) { 278 | this.srcurl = srcurl; 279 | } 280 | 281 | /** 282 | * @return the alias or null 283 | */ 284 | public String getAlias() { 285 | return alias; 286 | } 287 | 288 | /** 289 | * @param alias a string name to refer to the pengine by (remove by passing this null) 290 | */ 291 | public void setAlias(String alias) { 292 | this.alias = alias; 293 | } 294 | 295 | // todo synchronize all this, so we can't change during the pengine create time 296 | 297 | /* ====================================== Implement PengineFactory =========================== */ 298 | public Pengine newPengine() throws CouldNotCreateException { 299 | return new Pengine(this); 300 | } 301 | 302 | /** 303 | * return the POST body for a /pengines/ask request of ask 304 | * 305 | * @param id The pengine id that is transmitting 306 | * @param ask The Prolog query 307 | * @return the body 308 | */ 309 | public String getRequestBodyAsk(String id, String ask) { 310 | 311 | StringBuilder sb = new StringBuilder(); 312 | 313 | sb.append("ask("); 314 | sb.append(ask); 315 | sb.append(",[])."); // TODO template, chunk go here 316 | return sb.toString(); 317 | 318 | //request_uri('/pengine/send?id=d401db37-61b3-4d5b-9c17-9588d274ef7e'), 319 | // http_pengine_send requires id, event, and format l 320 | // format is prolog (that's the default) 321 | // EventString is optional, probably from body 322 | // calls read_event 323 | //% Read the sent event. The event is a Prolog term that is either in 324 | // the =event= parameter or as a posted document. 325 | // the body of the request looks like 326 | // ask(q(A),[template(A)]) 327 | } 328 | 329 | /** 330 | * @return 331 | */ 332 | public String getRequestBodyNext() { 333 | return "next."; 334 | } 335 | 336 | /** 337 | * @return 338 | */ 339 | public String getRequestBodyDestroy() { 340 | return "destroy."; 341 | } 342 | 343 | /** 344 | * 345 | */ 346 | public void dumpDebugState() { 347 | System.err.println("--- PengineBuilder ----"); 348 | System.err.println("alias " + this.alias); 349 | System.err.println("application " + this.application); 350 | System.err.println("ask " + this.ask); 351 | System.err.println("chunk size " + Integer.toString(this.chunk)); 352 | if(this.destroy) 353 | System.err.println("destroy at end of query"); 354 | else 355 | System.err.println("retain at end of query"); 356 | 357 | System.err.println("server " + this.server); 358 | System.err.println("srctext " + this.srctext); 359 | System.err.println("srcurl " + this.srcurl); 360 | System.err.println("--- end PengineBuilder ---"); 361 | } 362 | 363 | /** 364 | * @return 365 | */ 366 | public String getRequestBodyStop() { 367 | return "stop."; 368 | } 369 | 370 | /** 371 | * @return 372 | */ 373 | public String getRequestBodyPullResponse() { 374 | return "pull_response."; 375 | } 376 | 377 | 378 | /* eventually we have this 379 | public Query newPengineOnce(String ask) { 380 | return newPengineOnce(this.po, ask); 381 | } 382 | 383 | public Query newPengineOnce(PengineOptions po, String ask) { 384 | 385 | } 386 | 387 | public Proof newPengineOnceDet(PengineOptions po) { 388 | 389 | } 390 | // consider removing ask from PengineOptions, force it to be passed in to PengineBuilder to get an initial ask. 391 | // it's cleaner conceptually 392 | // also consider passing destroy explicitly as an optional parameter 393 | public Proof newPengineOnceDet(PengineOptions po, String ask) { 394 | 395 | } 396 | 397 | 398 | */ 399 | 400 | } 401 | -------------------------------------------------------------------------------- /src/main/java/com/simularity/os/javapengine/Pengine.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016 Simularity Inc. 3 | * 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | * 23 | */ 24 | package com.simularity.os.javapengine; 25 | 26 | import java.io.BufferedReader; 27 | import java.io.DataOutputStream; 28 | import java.io.IOException; 29 | import java.io.InputStreamReader; 30 | import java.io.StringReader; 31 | import java.net.HttpURLConnection; 32 | /* 33 | * Copyright (c) 2015 Simularity, Inc. 34 | 35 | Permission is hereby granted, free of charge, to any person obtaining a copy 36 | of this software and associated documentation files (the "Software"), to deal 37 | in the Software without restriction, including without limitation the rights 38 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 39 | copies of the Software, and to permit persons to whom the Software is 40 | furnished to do so, subject to the following conditions: 41 | 42 | The above copyright notice and this permission notice shall be included in 43 | all copies or substantial portions of the Software. 44 | 45 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 46 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 47 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 48 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 49 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 50 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 51 | THE SOFTWARE. 52 | * 53 | */ 54 | import java.net.URL; 55 | import java.util.Vector; 56 | 57 | import javax.json.Json; 58 | import javax.json.JsonObject; 59 | import javax.json.JsonReader; 60 | import javax.json.JsonReaderFactory; 61 | import javax.json.JsonString; 62 | 63 | import com.simularity.os.javapengine.PengineState.PSt; 64 | import com.simularity.os.javapengine.exception.CouldNotCreateException; 65 | import com.simularity.os.javapengine.exception.PengineNotAvailableException; 66 | import com.simularity.os.javapengine.exception.PengineNotReadyException; 67 | import com.simularity.os.javapengine.exception.SyntaxErrorException; 68 | 69 | /** 70 | * A reference to a remote pengine slave. 71 | * 72 | * To make one use {@link PengineBuilder} 73 | * 74 | * Lifecycle: 75 | * 76 | * Create using {@link PengineBuilder} 77 | * Use for one or more Queries 78 | * Destroy 79 | * 80 | * If you have destroy set to true in PengineBuilder, the Pengine will be destroyed automatically at the end of the query. 81 | * 82 | * @author Anne Ogborn 83 | * 84 | */ 85 | public final class Pengine { 86 | // we copy the passed in object to make it immutable 87 | private final PengineBuilder po; 88 | private final String pengineID; 89 | 90 | private PengineState state = new PengineState(); 91 | 92 | /** 93 | * Pengines are created, used, and destroyed. 94 | * If the Pengine has been destroyed you can't use it any more. 95 | * 96 | * @return true iff I've been destroyed 97 | */ 98 | public boolean isDestroyed() { 99 | return state.isIn(PengineState.PSt.DESTROYED); 100 | } 101 | 102 | /** 103 | * If the Pengine is currently servicing the query, this will return the current Query. 104 | * if not, it returns null. 105 | * 106 | * Using the interactor metaphor, this returns null if it's at the ?- prompt, and the Query 107 | * if it's at the 'blinking cursor waiting for ; or .' state 108 | * 109 | * @return the current Query or null if there isn't one 110 | * 111 | */ 112 | public Query getCurrentQuery() { 113 | return currentQuery; 114 | } 115 | 116 | // the current query, or null 117 | private Query currentQuery = null; 118 | private int slave_limit = -1; 119 | private Vector availOutput = new Vector(); 120 | 121 | /** 122 | * Create a new pengine object from a {@link PengineBuilder}. 123 | * 124 | * @param poo the PengineBuilder that's creating this Pengine 125 | * 126 | * @throws CouldNotCreateException if for any reason the pengine cannot be created 127 | */ 128 | Pengine(final PengineBuilder poo) throws CouldNotCreateException { 129 | try { 130 | this.po = (PengineBuilder) poo.clone(); 131 | } catch (CloneNotSupportedException e) { 132 | state.destroy(); 133 | throw new CouldNotCreateException("PengineBuilder must be clonable"); 134 | } 135 | 136 | try { 137 | pengineID = create(po); 138 | } catch (PengineNotReadyException e) { 139 | state.destroy(); 140 | throw new CouldNotCreateException("Pengine wasnt ready????"); 141 | } 142 | } 143 | 144 | /** 145 | * Low level famulus to abstract out some of the HTTP handling common to all requests 146 | * 147 | * @param url The actual url to httpRequest 148 | * @param contentType The value string of the Content-Type header 149 | * @param body the body of the POST request 150 | * @return the returned JSON object 151 | * 152 | * @throws CouldNotCreateException 153 | * @throws IOException 154 | */ 155 | private JsonObject penginePost( 156 | URL url, 157 | String contentType, 158 | String body 159 | ) throws IOException { 160 | StringBuffer response; 161 | 162 | try { 163 | HttpURLConnection con = (HttpURLConnection) url.openConnection(); 164 | // above should get us an HttpsURLConnection if it's https://... 165 | 166 | //add request header 167 | con.setRequestMethod("POST"); 168 | con.setRequestProperty("User-Agent", "JavaPengine"); 169 | con.setRequestProperty("Accept", "application/json"); 170 | con.setRequestProperty("Accept-Language", "en-US,en;q=0.5"); 171 | con.setRequestProperty("Content-type", contentType); 172 | 173 | // Send post request 174 | con.setDoOutput(true); 175 | DataOutputStream wr = new DataOutputStream(con.getOutputStream()); 176 | try { 177 | wr.writeBytes(body); 178 | wr.flush(); 179 | } finally { 180 | wr.close(); 181 | } 182 | 183 | int responseCode = con.getResponseCode(); 184 | if(responseCode < 200 || responseCode > 299) { 185 | throw new IOException("bad response code (if 500, query was invalid? query threw Prolog exception?) " + Integer.toString(responseCode) + " " + url.toString() + " " + body); 186 | } 187 | 188 | BufferedReader in = new BufferedReader( 189 | new InputStreamReader(con.getInputStream())); 190 | String inputLine; 191 | response = new StringBuffer(); 192 | try { 193 | while ((inputLine = in.readLine()) != null) { 194 | response.append(inputLine); 195 | } 196 | } finally { 197 | in.close(); 198 | } 199 | 200 | JsonReaderFactory jrf = Json.createReaderFactory(null); 201 | JsonReader jr = jrf.createReader(new StringReader(response.toString())); 202 | JsonObject respObject = jr.readObject(); 203 | 204 | return respObject; 205 | } catch (IOException e) { 206 | state.destroy(); 207 | throw e; 208 | } 209 | } 210 | 211 | 212 | /** 213 | * does the actual creation, as a famulus of the constructor. 214 | * 215 | * @param po the cloned PengineOptions 216 | * @return the ID of the created pengine 217 | * 218 | * @throws CouldNotCreateException if we can't make the pengine 219 | * @throws PengineNotReadyException 220 | */ 221 | private String create(PengineBuilder po) throws CouldNotCreateException, PengineNotReadyException { 222 | state.must_be_in(PSt.NOT_CREATED); 223 | 224 | try{ 225 | JsonObject respObject = penginePost( 226 | po.getActualURL("create"), 227 | "application/json", 228 | po.getRequestBodyCreate()); 229 | 230 | if(respObject.containsKey("slave_limit")) { 231 | this.slave_limit = respObject.getJsonNumber("slave_limit").intValue(); 232 | } 233 | 234 | JsonString eventjson = (JsonString)respObject.get("event"); 235 | String evtstr = eventjson.getString(); 236 | 237 | if(evtstr.equals("destroy")) { 238 | state.setState(PSt.DESTROYED); 239 | } else if(evtstr.equals("create")) { 240 | state.setState(PSt.IDLE); 241 | } else { 242 | throw new CouldNotCreateException("create request event was" + evtstr + " must be create or destroy"); 243 | } 244 | 245 | if(po.getAsk() != null) { 246 | this.currentQuery = new Query(this, po.getAsk(), false); 247 | state.setState(PSt.ASK); 248 | } 249 | 250 | if(respObject.containsKey("answer")) { 251 | handleAnswer(respObject.getJsonObject("answer")); 252 | } 253 | 254 | String id = ((JsonString)respObject.get("id")).getString(); 255 | if(id == null) 256 | throw new CouldNotCreateException("no pengine id in create message"); 257 | return id; 258 | } catch (IOException e) { 259 | state.destroy(); 260 | throw new CouldNotCreateException(e.getMessage()); 261 | } catch(SyntaxErrorException e) { 262 | state.destroy(); 263 | throw new CouldNotCreateException(e.getMessage()); 264 | } 265 | } 266 | 267 | /** 268 | * @return the slave_limit 269 | */ 270 | public int getSlave_limit() { 271 | return slave_limit; 272 | } 273 | 274 | /** 275 | * handle the result of a send 276 | * 277 | * @param jsonObject 278 | * @throws SyntaxErrorException 279 | */ 280 | private void handleAnswer(JsonObject answer) throws SyntaxErrorException { 281 | try { 282 | if(answer.containsKey("event")) { 283 | switch( ((JsonString)answer.get("event")).getString()) { 284 | case "success": 285 | if(answer.containsKey("data")) { 286 | currentQuery.addNewData(answer.getJsonArray("data")); 287 | } 288 | if(answer.containsKey("more")) { 289 | if(!answer.getBoolean("more")) { 290 | currentQuery.noMore(); 291 | } 292 | } 293 | break; 294 | 295 | case "destroy": 296 | if(answer.containsKey("data")) { 297 | // if it contains a data key, then strangely, it's an 'answer' structure 298 | handleAnswer(answer.getJsonObject("data")); 299 | } 300 | if(currentQuery != null) 301 | currentQuery.noMore(); 302 | state.setState(PSt.DESTROYED); 303 | break; 304 | 305 | case "failure": 306 | currentQuery.noMore(); 307 | break; 308 | 309 | case "stop": 310 | currentQuery.noMore(); 311 | break; 312 | 313 | case "error": 314 | throw new SyntaxErrorException("Error - probably invalid Prolog query?"); 315 | 316 | case "output": 317 | String data = answer.getString("data"); 318 | availOutput.add(data); 319 | break; 320 | 321 | case "died": 322 | // returned by pull_response if we're after the death 323 | state.setState(PSt.DESTROYED); 324 | break; // report that the pengine 325 | 326 | default: 327 | throw new SyntaxErrorException("Bad event in answer" + ((JsonString)answer.get("event")).getString()); 328 | } 329 | } 330 | } catch (PengineNotReadyException e) { 331 | throw new SyntaxErrorException(e.getMessage()); 332 | } 333 | } 334 | 335 | /** 336 | * 337 | */ 338 | public void dumpStateDebug() { 339 | System.err.println(this.pengineID); 340 | System.err.println("slave_limit " + this.slave_limit); 341 | if(this.currentQuery != null) 342 | this.currentQuery.dumpDebugState(); 343 | this.po.dumpDebugState(); 344 | this.state.dumpDebugState(); 345 | 346 | } 347 | /* 348 | 349 | * @param query 350 | * @param template 351 | * @return 352 | * @throws PengineNotReadyException 353 | 354 | public Query ask(String query, String template) throws PengineNotReadyException { 355 | state.must_be_in(PSt.IDLE); 356 | 357 | if(this.currentQuery != null) 358 | throw new PengineNotReadyException("Have not extracted all answers from previous query (or stopped it)"); 359 | 360 | this.currentQuery = new Query(this, query, template); 361 | 362 | return this.currentQuery; 363 | } 364 | */ 365 | 366 | 367 | /** 368 | * @param query 369 | * @return 370 | * @throws PengineNotReadyException 371 | */ 372 | public Query ask(String query) throws PengineNotReadyException { 373 | state.must_be_in(PSt.IDLE); 374 | 375 | if(this.currentQuery != null) 376 | throw new PengineNotReadyException("Have not extracted all answers from previous query (or stopped it)"); 377 | 378 | this.currentQuery = new Query(this, query); 379 | 380 | return this.currentQuery; 381 | } 382 | 383 | /** 384 | * @param ask 385 | * @param query 386 | * @throws CouldNotCreateException 387 | * 388 | */ 389 | void doAsk(Query query, String ask) throws PengineNotReadyException { 390 | state.must_be_in(PSt.IDLE); 391 | if(currentQuery == null) 392 | this.currentQuery = query; 393 | else 394 | throw new PengineNotReadyException("You already have a query in process"); 395 | 396 | state.setState(PSt.ASK); 397 | try { 398 | JsonObject answer = penginePost( 399 | po.getActualURL("send", this.getID()), 400 | "application/x-prolog; charset=UTF-8", 401 | po.getRequestBodyAsk(this.getID(), ask)); 402 | 403 | handleAnswer(answer); 404 | } catch (IOException e) { 405 | state.destroy(); 406 | throw new PengineNotAvailableException(e.getMessage()); 407 | } catch(SyntaxErrorException e) { 408 | state.destroy(); 409 | throw new PengineNotAvailableException(e.getMessage()); 410 | } 411 | 412 | } 413 | 414 | /** 415 | * the query will not use the pengine again 416 | * 417 | * 418 | * @param query 419 | */ 420 | void iAmFinished(Query query) { 421 | if(query.equals(this.currentQuery)) 422 | this.currentQuery = null; 423 | 424 | if(state.equals(PSt.ASK)) { 425 | try { 426 | state.setState(PSt.IDLE); 427 | } catch (PengineNotReadyException e) { 428 | System.err.println("Internal error in iAmFinished"); 429 | e.printStackTrace(); 430 | } 431 | } 432 | } 433 | 434 | /** 435 | * @param query 436 | * @throws PengineNotReadyException 437 | */ 438 | void doNext(Query query) throws PengineNotReadyException { 439 | state.must_be_in(PSt.ASK); 440 | if(!query.equals(currentQuery)) { 441 | throw new PengineNotReadyException("Cannot advance more than one query - finish one before starting next"); 442 | } 443 | 444 | try { 445 | JsonObject respObject = penginePost( 446 | po.getActualURL("send", this.getID()), 447 | "application/x-prolog; charset=UTF-8", 448 | po.getRequestBodyNext()); 449 | 450 | handleAnswer(respObject); 451 | } catch (IOException e) { 452 | state.destroy(); 453 | throw new PengineNotAvailableException(e.getMessage()); 454 | } catch(SyntaxErrorException e) { 455 | state.destroy(); 456 | throw new PengineNotAvailableException(e.getMessage()); 457 | } 458 | } 459 | 460 | /** 461 | * @return 462 | */ 463 | public String getID() { 464 | state.must_be_in(PSt.ASK, PSt.IDLE); 465 | return this.pengineID; 466 | } 467 | 468 | /** 469 | * Destroy the pengine. 470 | * this makes a best attempt to destroy the pengine. 471 | * 472 | * after calling destroy you should not further release the pengine 473 | */ 474 | public void destroy() { 475 | if(state.isIn(PSt.DESTROYED)) 476 | return; 477 | 478 | if(state.isIn(PSt.NOT_CREATED)) { 479 | state.destroy(); 480 | return; 481 | } 482 | 483 | state.must_be_in(PSt.ASK, PSt.IDLE); 484 | 485 | try { 486 | JsonObject respObject = penginePost( 487 | po.getActualURL("send", this.getID()), 488 | "application/x-prolog; charset=UTF-8", 489 | po.getRequestBodyDestroy()); 490 | 491 | handleAnswer(respObject); 492 | } catch (IOException e) { 493 | // for various reasons the pengine can be already destroyed. We ignore the errors 494 | 495 | //e.printStackTrace(); 496 | //throw new PengineNotAvailableException(e.getMessage()); 497 | } catch(SyntaxErrorException e) { 498 | //e.printStackTrace(); 499 | //throw new PengineNotAvailableException(e.getMessage()); 500 | } catch (PengineNotReadyException e) { 501 | //e.printStackTrace(); 502 | } finally { 503 | state.destroy(); 504 | } 505 | } 506 | 507 | protected void finalize() { 508 | destroy(); 509 | } 510 | 511 | /** 512 | * @throws PengineNotReadyException 513 | * 514 | */ 515 | void doStop() throws PengineNotReadyException { 516 | state.must_be_in(PSt.ASK); 517 | 518 | try { 519 | JsonObject respObject = penginePost( 520 | po.getActualURL("send", this.getID()), 521 | "application/x-prolog; charset=UTF-8", 522 | po.getRequestBodyStop()); 523 | 524 | handleAnswer(respObject); // we might destroy it 525 | } catch (IOException e) { 526 | state.destroy(); 527 | throw new PengineNotAvailableException(e.getMessage()); 528 | } catch(SyntaxErrorException e) { 529 | state.destroy(); 530 | throw new PengineNotAvailableException(e.getMessage()); 531 | } 532 | } 533 | 534 | /** 535 | * 536 | * @return a string off the serve, if it has one to give 537 | * 538 | * 539 | * @throws PengineNotReadyException 540 | */ 541 | void doPullResponse() throws PengineNotReadyException { 542 | if(!state.isIn(PSt.IDLE) && !state.isIn(PSt.ASK)) 543 | return; 544 | 545 | try { 546 | JsonObject respObject = penginePost( 547 | po.getActualURL("pull_response", this.getID()), 548 | "application/x-prolog; charset=UTF-8", 549 | po.getRequestBodyPullResponse()); 550 | 551 | handleAnswer(respObject); // we might destroy it 552 | } catch (IOException e) { 553 | state.destroy(); 554 | throw new PengineNotAvailableException(e.getMessage()); 555 | } catch(SyntaxErrorException e) { 556 | state.destroy(); 557 | throw new PengineNotAvailableException(e.getMessage()); 558 | } 559 | } 560 | 561 | /** 562 | * return one piece of pending output, if any. 563 | * If it doesn't have any to return, it returns null 564 | * 565 | * @deprecated If you call it when the pengine's not got output it opens a connection that never closes, so using this is definitely not recommended 566 | * 567 | * @return output string from slave, or null 568 | * 569 | * @throws PengineNotReadyException if the pengine isn't in communication. You need to consume (or at least fetch) all output before the engine is destroyed 570 | */ 571 | public String getOutput() throws PengineNotReadyException { 572 | if(!this.availOutput.isEmpty()) { 573 | String out = this.availOutput.firstElement(); 574 | this.availOutput.remove(0); 575 | return out; 576 | } 577 | 578 | if(state.isIn(PSt.ASK) || state.isIn(PSt.IDLE)) 579 | doPullResponse(); 580 | 581 | if(!this.availOutput.isEmpty()) { 582 | String out = this.availOutput.firstElement(); 583 | this.availOutput.remove(0); 584 | return out; 585 | } 586 | 587 | return null; 588 | } 589 | } 590 | --------------------------------------------------------------------------------