├── .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 |
--------------------------------------------------------------------------------