Generate reference links for painless API (#22775)

Adds "Appending B. Painless API Reference", a reference of all classes
and methods available from Painless. Removes links to java packages
because they contain methods that we don't expose and don't contain
methods that we do expose (the ones in Augmentation). Instead this
generates a list of every class and every exposed method using the same
type information available to the
interpreter/compiler/whatever-we-call-it. From there you can jump to
the relevant docs.

Right now you build all the asciidoc files by running
```
gradle generatePainlessApi
```

These files are expected to be committed because we build the docs
without running `gradle`.

Also changes the output of `Debug.explain` so that it is easy to
search for the class in the generated reference documentation.

You can also run it in an IDE safely if you pass the path to the
directory in which to generate the docs as the first parameter. It'll
blow away the entire directory an recreate it from scratch so be careful.

And then you can build the docs by running something like:
```
../docs/build_docs.pl --out ../built_docs/ --doc docs/reference/index.asciidoc --open
```

That is, if you have checked out https://github.com/elastic/docs in
`../docs`. Wait a minute or two and your browser will pop open in with
all of Elasticsearch's reference documentation. If you go to
`http://localhost:8000/painless-api-reference.html` you can see this
list. Or you can get there by following the links to `Modules` and
`Scripting` and `Painless` and then clicking the link in the paragraphs
below titled `Appendix B. Painless API Reference`.

I like having these in asciidoc because we can deep link to them from the
rest of the guide with constructs like
`<<painless-api-reference-Object-hashCode-0>>` and
`<<painless-api-reference->>` and we get link checking. Then the only
brittle link maintenance bit is the link generation for javadoc. Which
sucks. But I think it is important that we link to the methods directly
so they are easy to find.

Relates to #22720
This commit is contained in:
Nik Everett 2017-01-26 10:39:19 -05:00 committed by GitHub
parent 1fa2734566
commit 8a2d424d68
16 changed files with 968 additions and 138 deletions

View File

@ -20,6 +20,27 @@ release-state can be: released | prerelease | unreleased
:issue: https://github.com/elastic/elasticsearch/issues/
:pull: https://github.com/elastic/elasticsearch/pull/
///////
Javadoc roots used to generate links from Painless's API reference
///////
:java8-javadoc: https://docs.oracle.com/javase/8/docs/api
:java9-javadoc: http://download.java.net/java/jigsaw/docs/api
:joda-time-javadoc: http://www.joda.org/joda-time/apidocs
:lucene-core-javadoc: http://lucene.apache.org/core/6_4_0/core
ifeval::["{release-state}"=="unreleased"]
:elasticsearch-javadoc: https://snapshots.elastic.co/javadoc/org/elasticsearch/elasticsearch/{version}-SNAPSHOT
:painless-javadoc: https://snapshots.elastic.co/javadoc/org/elasticsearch/painless/lang-painless/{version}-SNAPSHOT
endif::[]
ifeval::["{release-state}"!="unreleased"]
:elasticsearch-javadoc: https://artifacts.elastic.co/javadoc/org/elasticsearch/elasticsearch/{version}
:painless-javadoc: https://artifacts.elastic.co/javadoc/org/elasticsearch/painless/lang-painless/{version}
endif::[]
include::getting-started.asciidoc[]
include::setup.asciidoc[]
@ -62,4 +83,6 @@ include::glossary.asciidoc[]
include::release-notes.asciidoc[]
//////
include::painless-api-reference.asciidoc[]
include::redirects.asciidoc[]

View File

@ -38,15 +38,16 @@ POST /hockey/player/1/_explain
// we have to override it to get a normal shaped response
Which shows that the class of `doc.first` is
`org.elasticsearch.index.fielddata.ScriptDocValues$Longs` by responding with:
`org.elasticsearch.index.fielddata.ScriptDocValues.Longs` by responding with:
[source,js]
---------------------------------------------------------
{
"error": {
"type": "script_exception",
"class": "org.elasticsearch.index.fielddata.ScriptDocValues$Longs",
"to_string": "[1, 9, 27]",
"painless_class": "org.elasticsearch.index.fielddata.ScriptDocValues.Longs",
"java_class": "org.elasticsearch.index.fielddata.ScriptDocValues$Longs",
...
},
"status": 500
@ -54,7 +55,7 @@ Which shows that the class of `doc.first` is
---------------------------------------------------------
// TESTRESPONSE[s/\.\.\./"script_stack": $body.error.script_stack, "script": $body.error.script, "lang": $body.error.lang, "caused_by": $body.error.caused_by, "root_cause": $body.error.root_cause, "reason": $body.error.reason/]
You can use the same trick to see that `_source` is a `java.util.LinkedHashMap`
You can use the same trick to see that `_source` is a `LinkedHashMap`
in the `_update` API:
[source,js]
@ -78,8 +79,9 @@ The response looks like:
"reason": "failed to execute script",
"caused_by": {
"type": "script_exception",
"class": "java.util.LinkedHashMap",
"to_string": "{gp=[26, 82, 1], last=gaudreau, assists=[17, 46, 0], first=johnny, goals=[9, 27, 1]}",
"painless_class": "LinkedHashMap",
"java_class": "java.util.LinkedHashMap",
...
}
},
@ -90,19 +92,5 @@ The response looks like:
// TESTRESPONSE[s/\.\.\./"script_stack": $body.error.caused_by.script_stack, "script": $body.error.caused_by.script, "lang": $body.error.caused_by.lang, "caused_by": $body.error.caused_by.caused_by, "reason": $body.error.caused_by.reason/]
// TESTRESPONSE[s/"to_string": ".+"/"to_string": $body.error.caused_by.to_string/]
// TODO we should build some javadoc like mashup so people don't have to jump through these hoops.
Once you have the class of an object you can go
https://github.com/elastic/elasticsearch/tree/{branch}/modules/lang-painless/src/main/resources/org/elasticsearch/painless[here]
and check the available methods. Painless uses a strict whitelist to prevent
scripts that don't work well with Elasticsearch and all whitelisted methods
are listed in a file named after the package of the object (everything before
the last `.`). So `java.util.Map` is listed in a file named `java.util.txt`
starting on the line that looks like `class Map -> java.util.Map {`.
With the list of whitelisted methods in hand you can turn to either
https://docs.oracle.com/javase/8/docs/api/[Javadoc],
https://github.com/elastic/elasticsearch/tree/{branch}[Elasticsearch's source tree]
or, for whitelisted methods ending in `*`, the
https://github.com/elastic/elasticsearch/blob/{branch}/modules/lang-painless/src/main/java/org/elasticsearch/painless/Augmentation.java[Augmentation]
class.
Once you have a class you can go to <<painless-api-reference>> to see a list of
available methods.

View File

@ -10,8 +10,9 @@ default.
The Painless syntax is similar to http://groovy-lang.org/index.html[Groovy].
You can use Painless anywhere a script can be used in Elasticsearch--simply set the `lang` parameter
to `painless`.
You can use Painless anywhere a script can be used in Elasticsearch. It is the
default if you don't set the `lang` parameter but if you want to be explicit you
can set the `lang` parameter to `painless`.
[[painless-features]]
[float]
@ -19,7 +20,8 @@ to `painless`.
* Fast performance: https://benchmarks.elastic.co/index.html#search_qps_scripts[several times faster] than the alternatives.
* Safety: Fine-grained <<painless-api, whitelist>> with method call/field granularity.
* Safety: Fine-grained whitelist with method call/field granularity. See
<<painless-api-reference>> for a complete list of available classes and methods.
* Optional typing: Variables and parameters can use explicit types or the dynamic `def` type.
@ -320,28 +322,3 @@ Note: all of the `_update_by_query` examples above could really do with a
<<query-dsl-script-query>> it wouldn't be as efficient as using any other query
because script queries aren't able to use the inverted index to limit the
documents that they have to check.
[float]
[[painless-api]]
== Painless API
The following Java packages are available for use in the Painless language:
* https://docs.oracle.com/javase/8/docs/api/java/lang/package-summary.html[java.lang]
* https://docs.oracle.com/javase/8/docs/api/java/math/package-summary.html[java.math]
* https://docs.oracle.com/javase/8/docs/api/java/text/package-summary.html[java.text]
* https://docs.oracle.com/javase/8/docs/api/java/time/package-summary.html[java.time]
* https://docs.oracle.com/javase/8/docs/api/java/time/chrono/package-summary.html[java.time.chrono]
* https://docs.oracle.com/javase/8/docs/api/java/time/format/package-summary.html[java.time.format]
* https://docs.oracle.com/javase/8/docs/api/java/time/temporal/package-summary.html[java.time.temporal]
* https://docs.oracle.com/javase/8/docs/api/java/time/zone/package-summary.html[java.time.zone]
* https://docs.oracle.com/javase/8/docs/api/java/util/package-summary.html[java.util]
* https://docs.oracle.com/javase/8/docs/api/java/util/function/package-summary.html[java.util.function]
* https://docs.oracle.com/javase/8/docs/api/java/util/regex/package-summary.html[java.util.regex]
* https://docs.oracle.com/javase/8/docs/api/java/util/stream/package-summary.html[java.util.stream]
Note that unsafe classes and methods are not included, there is no support for:
* Manipulation of processes and threads
* Input/Output
* Reflection

View File

@ -0,0 +1,17 @@
["appendix",id="painless-api-reference"]
= Painless API Reference
<<modules-scripting-painless,Painless>> has a strict whitelist for methods and
classes to make sure that all painless scripts are secure and fast. Most of
these methods are exposed directly from the JRE while others are part of
Elasticsearch or Painless itself. Below is a list of all available methods
grouped under the classes on which you can call them. Clicking on the method
name takes you to the documentation for the method.
NOTE: Methods defined in the JRE also have a `(java 9)` link which can be used
to see the method's documentation in Java 9 while clicking on the method's name
goes to the Java 8 documentation. Usually these aren't different but it is
worth going to the version that matches the version of Java you are using to
run Elasticsearch just in case.
include::painless-api-reference/index.asciidoc[]

View File

@ -0,0 +1,22 @@
////
Automatically generated by PainlessDocGenerator. Do not edit.
Rebuild by running `gradle generatePainlessApi`.
////
[[painless-api-reference-List]]++List++::
* ++[[painless-api-reference-List-add-2]]void link:{java8-javadoc}/java/util/List.html#add%2Dint%2Djava.lang.Object%2D[add](int, def)++ (link:{java9-javadoc}/java/util/List.html#add%2Dint%2Djava.lang.Object%2D[java 9])
* ++[[painless-api-reference-List-addAll-2]]boolean link:{java8-javadoc}/java/util/List.html#addAll%2Dint%2Djava.util.Collection%2D[addAll](int, <<painless-api-reference-Collection,Collection>>)++ (link:{java9-javadoc}/java/util/List.html#addAll%2Dint%2Djava.util.Collection%2D[java 9])
* ++[[painless-api-reference-List-equals-1]]boolean link:{java8-javadoc}/java/util/List.html#equals%2Djava.lang.Object%2D[equals](<<painless-api-reference-Object,Object>>)++ (link:{java9-javadoc}/java/util/List.html#equals%2Djava.lang.Object%2D[java 9])
* ++[[painless-api-reference-List-get-1]]def link:{java8-javadoc}/java/util/List.html#get%2Dint%2D[get](int)++ (link:{java9-javadoc}/java/util/List.html#get%2Dint%2D[java 9])
* ++[[painless-api-reference-List-getLength-0]]int link:{painless-javadoc}/org/elasticsearch/painless/api/Augmentation.html#getLength%2Djava.util.List%2D[getLength]()++
* ++[[painless-api-reference-List-hashCode-0]]int link:{java8-javadoc}/java/util/List.html#hashCode%2D%2D[hashCode]()++ (link:{java9-javadoc}/java/util/List.html#hashCode%2D%2D[java 9])
* ++[[painless-api-reference-List-indexOf-1]]int link:{java8-javadoc}/java/util/List.html#indexOf%2Djava.lang.Object%2D[indexOf](def)++ (link:{java9-javadoc}/java/util/List.html#indexOf%2Djava.lang.Object%2D[java 9])
* ++[[painless-api-reference-List-lastIndexOf-1]]int link:{java8-javadoc}/java/util/List.html#lastIndexOf%2Djava.lang.Object%2D[lastIndexOf](def)++ (link:{java9-javadoc}/java/util/List.html#lastIndexOf%2Djava.lang.Object%2D[java 9])
* ++[[painless-api-reference-List-listIterator-0]]<<painless-api-reference-ListIterator,ListIterator>> link:{java8-javadoc}/java/util/List.html#listIterator%2D%2D[listIterator]()++ (link:{java9-javadoc}/java/util/List.html#listIterator%2D%2D[java 9])
* ++[[painless-api-reference-List-listIterator-1]]<<painless-api-reference-ListIterator,ListIterator>> link:{java8-javadoc}/java/util/List.html#listIterator%2Dint%2D[listIterator](int)++ (link:{java9-javadoc}/java/util/List.html#listIterator%2Dint%2D[java 9])
* ++[[painless-api-reference-List-remove-1]]def link:{java8-javadoc}/java/util/List.html#remove%2Dint%2D[remove](int)++ (link:{java9-javadoc}/java/util/List.html#remove%2Dint%2D[java 9])
* ++[[painless-api-reference-List-replaceAll-1]]void link:{java8-javadoc}/java/util/List.html#replaceAll%2Djava.util.function.UnaryOperator%2D[replaceAll](<<painless-api-reference-UnaryOperator,UnaryOperator>>)++ (link:{java9-javadoc}/java/util/List.html#replaceAll%2Djava.util.function.UnaryOperator%2D[java 9])
* ++[[painless-api-reference-List-set-2]]def link:{java8-javadoc}/java/util/List.html#set%2Dint%2Djava.lang.Object%2D[set](int, def)++ (link:{java9-javadoc}/java/util/List.html#set%2Dint%2Djava.lang.Object%2D[java 9])
* ++[[painless-api-reference-List-sort-1]]void link:{java8-javadoc}/java/util/List.html#sort%2Djava.util.Comparator%2D[sort](<<painless-api-reference-Comparator,Comparator>>)++ (link:{java9-javadoc}/java/util/List.html#sort%2Djava.util.Comparator%2D[java 9])
* ++[[painless-api-reference-List-subList-2]]<<painless-api-reference-List,List>> link:{java8-javadoc}/java/util/List.html#subList%2Dint%2Dint%2D[subList](int, int)++ (link:{java9-javadoc}/java/util/List.html#subList%2Dint%2Dint%2D[java 9])
* Inherits methods from ++<<painless-api-reference-Collection,Collection>>++, ++<<painless-api-reference-Iterable,Iterable>>++, ++<<painless-api-reference-Object,Object>>++

View File

@ -0,0 +1,46 @@
////
Automatically generated by PainlessDocGenerator. Do not edit.
Rebuild by running `gradle generatePainlessApi`.
////
[[painless-api-reference-Math]]++Math++::
** [[painless-api-reference-Math-E]]static double link:{java8-javadoc}/java/lang/Math.html#E[E] (link:{java9-javadoc}/java/lang/Math.html#E[java 9])
** [[painless-api-reference-Math-PI]]static double link:{java8-javadoc}/java/lang/Math.html#PI[PI] (link:{java9-javadoc}/java/lang/Math.html#PI[java 9])
* ++[[painless-api-reference-Math-IEEEremainder-2]]static double link:{java8-javadoc}/java/lang/Math.html#IEEEremainder%2Ddouble%2Ddouble%2D[IEEEremainder](double, double)++ (link:{java9-javadoc}/java/lang/Math.html#IEEEremainder%2Ddouble%2Ddouble%2D[java 9])
* ++[[painless-api-reference-Math-abs-1]]static double link:{java8-javadoc}/java/lang/Math.html#abs%2Ddouble%2D[abs](double)++ (link:{java9-javadoc}/java/lang/Math.html#abs%2Ddouble%2D[java 9])
* ++[[painless-api-reference-Math-acos-1]]static double link:{java8-javadoc}/java/lang/Math.html#acos%2Ddouble%2D[acos](double)++ (link:{java9-javadoc}/java/lang/Math.html#acos%2Ddouble%2D[java 9])
* ++[[painless-api-reference-Math-asin-1]]static double link:{java8-javadoc}/java/lang/Math.html#asin%2Ddouble%2D[asin](double)++ (link:{java9-javadoc}/java/lang/Math.html#asin%2Ddouble%2D[java 9])
* ++[[painless-api-reference-Math-atan-1]]static double link:{java8-javadoc}/java/lang/Math.html#atan%2Ddouble%2D[atan](double)++ (link:{java9-javadoc}/java/lang/Math.html#atan%2Ddouble%2D[java 9])
* ++[[painless-api-reference-Math-atan2-2]]static double link:{java8-javadoc}/java/lang/Math.html#atan2%2Ddouble%2Ddouble%2D[atan2](double, double)++ (link:{java9-javadoc}/java/lang/Math.html#atan2%2Ddouble%2Ddouble%2D[java 9])
* ++[[painless-api-reference-Math-cbrt-1]]static double link:{java8-javadoc}/java/lang/Math.html#cbrt%2Ddouble%2D[cbrt](double)++ (link:{java9-javadoc}/java/lang/Math.html#cbrt%2Ddouble%2D[java 9])
* ++[[painless-api-reference-Math-ceil-1]]static double link:{java8-javadoc}/java/lang/Math.html#ceil%2Ddouble%2D[ceil](double)++ (link:{java9-javadoc}/java/lang/Math.html#ceil%2Ddouble%2D[java 9])
* ++[[painless-api-reference-Math-copySign-2]]static double link:{java8-javadoc}/java/lang/Math.html#copySign%2Ddouble%2Ddouble%2D[copySign](double, double)++ (link:{java9-javadoc}/java/lang/Math.html#copySign%2Ddouble%2Ddouble%2D[java 9])
* ++[[painless-api-reference-Math-cos-1]]static double link:{java8-javadoc}/java/lang/Math.html#cos%2Ddouble%2D[cos](double)++ (link:{java9-javadoc}/java/lang/Math.html#cos%2Ddouble%2D[java 9])
* ++[[painless-api-reference-Math-cosh-1]]static double link:{java8-javadoc}/java/lang/Math.html#cosh%2Ddouble%2D[cosh](double)++ (link:{java9-javadoc}/java/lang/Math.html#cosh%2Ddouble%2D[java 9])
* ++[[painless-api-reference-Math-exp-1]]static double link:{java8-javadoc}/java/lang/Math.html#exp%2Ddouble%2D[exp](double)++ (link:{java9-javadoc}/java/lang/Math.html#exp%2Ddouble%2D[java 9])
* ++[[painless-api-reference-Math-expm1-1]]static double link:{java8-javadoc}/java/lang/Math.html#expm1%2Ddouble%2D[expm1](double)++ (link:{java9-javadoc}/java/lang/Math.html#expm1%2Ddouble%2D[java 9])
* ++[[painless-api-reference-Math-floor-1]]static double link:{java8-javadoc}/java/lang/Math.html#floor%2Ddouble%2D[floor](double)++ (link:{java9-javadoc}/java/lang/Math.html#floor%2Ddouble%2D[java 9])
* ++[[painless-api-reference-Math-hypot-2]]static double link:{java8-javadoc}/java/lang/Math.html#hypot%2Ddouble%2Ddouble%2D[hypot](double, double)++ (link:{java9-javadoc}/java/lang/Math.html#hypot%2Ddouble%2Ddouble%2D[java 9])
* ++[[painless-api-reference-Math-log-1]]static double link:{java8-javadoc}/java/lang/Math.html#log%2Ddouble%2D[log](double)++ (link:{java9-javadoc}/java/lang/Math.html#log%2Ddouble%2D[java 9])
* ++[[painless-api-reference-Math-log10-1]]static double link:{java8-javadoc}/java/lang/Math.html#log10%2Ddouble%2D[log10](double)++ (link:{java9-javadoc}/java/lang/Math.html#log10%2Ddouble%2D[java 9])
* ++[[painless-api-reference-Math-log1p-1]]static double link:{java8-javadoc}/java/lang/Math.html#log1p%2Ddouble%2D[log1p](double)++ (link:{java9-javadoc}/java/lang/Math.html#log1p%2Ddouble%2D[java 9])
* ++[[painless-api-reference-Math-max-2]]static double link:{java8-javadoc}/java/lang/Math.html#max%2Ddouble%2Ddouble%2D[max](double, double)++ (link:{java9-javadoc}/java/lang/Math.html#max%2Ddouble%2Ddouble%2D[java 9])
* ++[[painless-api-reference-Math-min-2]]static double link:{java8-javadoc}/java/lang/Math.html#min%2Ddouble%2Ddouble%2D[min](double, double)++ (link:{java9-javadoc}/java/lang/Math.html#min%2Ddouble%2Ddouble%2D[java 9])
* ++[[painless-api-reference-Math-nextAfter-2]]static double link:{java8-javadoc}/java/lang/Math.html#nextAfter%2Ddouble%2Ddouble%2D[nextAfter](double, double)++ (link:{java9-javadoc}/java/lang/Math.html#nextAfter%2Ddouble%2Ddouble%2D[java 9])
* ++[[painless-api-reference-Math-nextDown-1]]static double link:{java8-javadoc}/java/lang/Math.html#nextDown%2Ddouble%2D[nextDown](double)++ (link:{java9-javadoc}/java/lang/Math.html#nextDown%2Ddouble%2D[java 9])
* ++[[painless-api-reference-Math-nextUp-1]]static double link:{java8-javadoc}/java/lang/Math.html#nextUp%2Ddouble%2D[nextUp](double)++ (link:{java9-javadoc}/java/lang/Math.html#nextUp%2Ddouble%2D[java 9])
* ++[[painless-api-reference-Math-pow-2]]static double link:{java8-javadoc}/java/lang/Math.html#pow%2Ddouble%2Ddouble%2D[pow](double, double)++ (link:{java9-javadoc}/java/lang/Math.html#pow%2Ddouble%2Ddouble%2D[java 9])
* ++[[painless-api-reference-Math-random-0]]static double link:{java8-javadoc}/java/lang/Math.html#random%2D%2D[random]()++ (link:{java9-javadoc}/java/lang/Math.html#random%2D%2D[java 9])
* ++[[painless-api-reference-Math-rint-1]]static double link:{java8-javadoc}/java/lang/Math.html#rint%2Ddouble%2D[rint](double)++ (link:{java9-javadoc}/java/lang/Math.html#rint%2Ddouble%2D[java 9])
* ++[[painless-api-reference-Math-round-1]]static long link:{java8-javadoc}/java/lang/Math.html#round%2Ddouble%2D[round](double)++ (link:{java9-javadoc}/java/lang/Math.html#round%2Ddouble%2D[java 9])
* ++[[painless-api-reference-Math-scalb-2]]static double link:{java8-javadoc}/java/lang/Math.html#scalb%2Ddouble%2Dint%2D[scalb](double, int)++ (link:{java9-javadoc}/java/lang/Math.html#scalb%2Ddouble%2Dint%2D[java 9])
* ++[[painless-api-reference-Math-signum-1]]static double link:{java8-javadoc}/java/lang/Math.html#signum%2Ddouble%2D[signum](double)++ (link:{java9-javadoc}/java/lang/Math.html#signum%2Ddouble%2D[java 9])
* ++[[painless-api-reference-Math-sin-1]]static double link:{java8-javadoc}/java/lang/Math.html#sin%2Ddouble%2D[sin](double)++ (link:{java9-javadoc}/java/lang/Math.html#sin%2Ddouble%2D[java 9])
* ++[[painless-api-reference-Math-sinh-1]]static double link:{java8-javadoc}/java/lang/Math.html#sinh%2Ddouble%2D[sinh](double)++ (link:{java9-javadoc}/java/lang/Math.html#sinh%2Ddouble%2D[java 9])
* ++[[painless-api-reference-Math-sqrt-1]]static double link:{java8-javadoc}/java/lang/Math.html#sqrt%2Ddouble%2D[sqrt](double)++ (link:{java9-javadoc}/java/lang/Math.html#sqrt%2Ddouble%2D[java 9])
* ++[[painless-api-reference-Math-tan-1]]static double link:{java8-javadoc}/java/lang/Math.html#tan%2Ddouble%2D[tan](double)++ (link:{java9-javadoc}/java/lang/Math.html#tan%2Ddouble%2D[java 9])
* ++[[painless-api-reference-Math-tanh-1]]static double link:{java8-javadoc}/java/lang/Math.html#tanh%2Ddouble%2D[tanh](double)++ (link:{java9-javadoc}/java/lang/Math.html#tanh%2Ddouble%2D[java 9])
* ++[[painless-api-reference-Math-toDegrees-1]]static double link:{java8-javadoc}/java/lang/Math.html#toDegrees%2Ddouble%2D[toDegrees](double)++ (link:{java9-javadoc}/java/lang/Math.html#toDegrees%2Ddouble%2D[java 9])
* ++[[painless-api-reference-Math-toRadians-1]]static double link:{java8-javadoc}/java/lang/Math.html#toRadians%2Ddouble%2D[toRadians](double)++ (link:{java9-javadoc}/java/lang/Math.html#toRadians%2Ddouble%2D[java 9])
* ++[[painless-api-reference-Math-ulp-1]]static double link:{java8-javadoc}/java/lang/Math.html#ulp%2Ddouble%2D[ulp](double)++ (link:{java9-javadoc}/java/lang/Math.html#ulp%2Ddouble%2D[java 9])
* Inherits methods from ++<<painless-api-reference-Object,Object>>++

View File

@ -0,0 +1,338 @@
////
Automatically generated by PainlessDocGenerator. Do not edit.
Rebuild by running `gradle generatePainlessApi`.
////
include::AbstractChronology.asciidoc[]
include::AbstractCollection.asciidoc[]
include::AbstractList.asciidoc[]
include::AbstractMap.asciidoc[]
include::AbstractMap.SimpleEntry.asciidoc[]
include::AbstractMap.SimpleImmutableEntry.asciidoc[]
include::AbstractQueue.asciidoc[]
include::AbstractSequentialList.asciidoc[]
include::AbstractSet.asciidoc[]
include::Annotation.asciidoc[]
include::Appendable.asciidoc[]
include::ArithmeticException.asciidoc[]
include::ArrayDeque.asciidoc[]
include::ArrayIndexOutOfBoundsException.asciidoc[]
include::ArrayList.asciidoc[]
include::ArrayStoreException.asciidoc[]
include::Arrays.asciidoc[]
include::AttributedCharacterIterator.asciidoc[]
include::AttributedCharacterIterator.Attribute.asciidoc[]
include::AttributedString.asciidoc[]
include::Base64.asciidoc[]
include::Base64.Decoder.asciidoc[]
include::Base64.Encoder.asciidoc[]
include::BaseStream.asciidoc[]
include::BiConsumer.asciidoc[]
include::BiFunction.asciidoc[]
include::BiPredicate.asciidoc[]
include::Bidi.asciidoc[]
include::BigDecimal.asciidoc[]
include::BigInteger.asciidoc[]
include::BinaryOperator.asciidoc[]
include::BitSet.asciidoc[]
include::Boolean.asciidoc[]
include::BooleanSupplier.asciidoc[]
include::BreakIterator.asciidoc[]
include::Byte.asciidoc[]
include::BytesRef.asciidoc[]
include::Calendar.asciidoc[]
include::Calendar.Builder.asciidoc[]
include::CharSequence.asciidoc[]
include::Character.asciidoc[]
include::Character.Subset.asciidoc[]
include::Character.UnicodeBlock.asciidoc[]
include::Character.UnicodeScript.asciidoc[]
include::CharacterIterator.asciidoc[]
include::ChoiceFormat.asciidoc[]
include::ChronoField.asciidoc[]
include::ChronoLocalDate.asciidoc[]
include::ChronoLocalDateTime.asciidoc[]
include::ChronoPeriod.asciidoc[]
include::ChronoUnit.asciidoc[]
include::ChronoZonedDateTime.asciidoc[]
include::Chronology.asciidoc[]
include::ClassCastException.asciidoc[]
include::ClassNotFoundException.asciidoc[]
include::Clock.asciidoc[]
include::CloneNotSupportedException.asciidoc[]
include::CollationElementIterator.asciidoc[]
include::CollationKey.asciidoc[]
include::Collator.asciidoc[]
include::Collection.asciidoc[]
include::Collections.asciidoc[]
include::Collector.asciidoc[]
include::Collector.Characteristics.asciidoc[]
include::Collectors.asciidoc[]
include::Comparable.asciidoc[]
include::Comparator.asciidoc[]
include::ConcurrentModificationException.asciidoc[]
include::Consumer.asciidoc[]
include::Currency.asciidoc[]
include::Date.asciidoc[]
include::DateFormat.asciidoc[]
include::DateFormat.Field.asciidoc[]
include::DateFormatSymbols.asciidoc[]
include::DateTimeException.asciidoc[]
include::DateTimeFormatter.asciidoc[]
include::DateTimeFormatterBuilder.asciidoc[]
include::DateTimeParseException.asciidoc[]
include::DayOfWeek.asciidoc[]
include::Debug.asciidoc[]
include::DecimalFormat.asciidoc[]
include::DecimalFormatSymbols.asciidoc[]
include::DecimalStyle.asciidoc[]
include::Deque.asciidoc[]
include::Dictionary.asciidoc[]
include::Double.asciidoc[]
include::DoubleBinaryOperator.asciidoc[]
include::DoubleConsumer.asciidoc[]
include::DoubleFunction.asciidoc[]
include::DoublePredicate.asciidoc[]
include::DoubleStream.asciidoc[]
include::DoubleStream.Builder.asciidoc[]
include::DoubleSummaryStatistics.asciidoc[]
include::DoubleSupplier.asciidoc[]
include::DoubleToIntFunction.asciidoc[]
include::DoubleToLongFunction.asciidoc[]
include::DoubleUnaryOperator.asciidoc[]
include::DuplicateFormatFlagsException.asciidoc[]
include::Duration.asciidoc[]
include::EmptyStackException.asciidoc[]
include::Enum.asciidoc[]
include::EnumConstantNotPresentException.asciidoc[]
include::Enumeration.asciidoc[]
include::Era.asciidoc[]
include::EventListener.asciidoc[]
include::EventListenerProxy.asciidoc[]
include::EventObject.asciidoc[]
include::Exception.asciidoc[]
include::FieldPosition.asciidoc[]
include::Float.asciidoc[]
include::Format.asciidoc[]
include::Format.Field.asciidoc[]
include::FormatFlagsConversionMismatchException.asciidoc[]
include::FormatStyle.asciidoc[]
include::Formattable.asciidoc[]
include::FormattableFlags.asciidoc[]
include::Formatter.asciidoc[]
include::Formatter.BigDecimalLayoutForm.asciidoc[]
include::FormatterClosedException.asciidoc[]
include::Function.asciidoc[]
include::GregorianCalendar.asciidoc[]
include::HashMap.asciidoc[]
include::HashSet.asciidoc[]
include::Hashtable.asciidoc[]
include::HijrahChronology.asciidoc[]
include::HijrahDate.asciidoc[]
include::HijrahEra.asciidoc[]
include::IdentityHashMap.asciidoc[]
include::IllegalAccessException.asciidoc[]
include::IllegalArgumentException.asciidoc[]
include::IllegalFormatCodePointException.asciidoc[]
include::IllegalFormatConversionException.asciidoc[]
include::IllegalFormatException.asciidoc[]
include::IllegalFormatFlagsException.asciidoc[]
include::IllegalFormatPrecisionException.asciidoc[]
include::IllegalFormatWidthException.asciidoc[]
include::IllegalMonitorStateException.asciidoc[]
include::IllegalStateException.asciidoc[]
include::IllegalThreadStateException.asciidoc[]
include::IllformedLocaleException.asciidoc[]
include::IndexOutOfBoundsException.asciidoc[]
include::InputMismatchException.asciidoc[]
include::Instant.asciidoc[]
include::InstantiationException.asciidoc[]
include::IntBinaryOperator.asciidoc[]
include::IntConsumer.asciidoc[]
include::IntFunction.asciidoc[]
include::IntPredicate.asciidoc[]
include::IntStream.asciidoc[]
include::IntStream.Builder.asciidoc[]
include::IntSummaryStatistics.asciidoc[]
include::IntSupplier.asciidoc[]
include::IntToDoubleFunction.asciidoc[]
include::IntToLongFunction.asciidoc[]
include::IntUnaryOperator.asciidoc[]
include::Integer.asciidoc[]
include::InterruptedException.asciidoc[]
include::IsoChronology.asciidoc[]
include::IsoEra.asciidoc[]
include::IsoFields.asciidoc[]
include::Iterable.asciidoc[]
include::Iterator.asciidoc[]
include::JapaneseChronology.asciidoc[]
include::JapaneseDate.asciidoc[]
include::JapaneseEra.asciidoc[]
include::JulianFields.asciidoc[]
include::LinkedHashMap.asciidoc[]
include::LinkedHashSet.asciidoc[]
include::LinkedList.asciidoc[]
include::List.asciidoc[]
include::ListIterator.asciidoc[]
include::LocalDate.asciidoc[]
include::LocalDateTime.asciidoc[]
include::LocalTime.asciidoc[]
include::Locale.asciidoc[]
include::Locale.Builder.asciidoc[]
include::Locale.Category.asciidoc[]
include::Locale.FilteringMode.asciidoc[]
include::Locale.LanguageRange.asciidoc[]
include::Long.asciidoc[]
include::LongBinaryOperator.asciidoc[]
include::LongConsumer.asciidoc[]
include::LongFunction.asciidoc[]
include::LongPredicate.asciidoc[]
include::LongStream.asciidoc[]
include::LongStream.Builder.asciidoc[]
include::LongSummaryStatistics.asciidoc[]
include::LongSupplier.asciidoc[]
include::LongToDoubleFunction.asciidoc[]
include::LongToIntFunction.asciidoc[]
include::LongUnaryOperator.asciidoc[]
include::Map.asciidoc[]
include::Map.Entry.asciidoc[]
include::Matcher.asciidoc[]
include::Math.asciidoc[]
include::MathContext.asciidoc[]
include::MessageFormat.asciidoc[]
include::MessageFormat.Field.asciidoc[]
include::MinguoChronology.asciidoc[]
include::MinguoDate.asciidoc[]
include::MinguoEra.asciidoc[]
include::MissingFormatArgumentException.asciidoc[]
include::MissingFormatWidthException.asciidoc[]
include::MissingResourceException.asciidoc[]
include::Month.asciidoc[]
include::MonthDay.asciidoc[]
include::NavigableMap.asciidoc[]
include::NavigableSet.asciidoc[]
include::NegativeArraySizeException.asciidoc[]
include::NoSuchElementException.asciidoc[]
include::NoSuchFieldException.asciidoc[]
include::NoSuchMethodException.asciidoc[]
include::Normalizer.asciidoc[]
include::Normalizer.Form.asciidoc[]
include::NullPointerException.asciidoc[]
include::Number.asciidoc[]
include::NumberFormat.asciidoc[]
include::NumberFormat.Field.asciidoc[]
include::NumberFormatException.asciidoc[]
include::ObjDoubleConsumer.asciidoc[]
include::ObjIntConsumer.asciidoc[]
include::ObjLongConsumer.asciidoc[]
include::Object.asciidoc[]
include::Objects.asciidoc[]
include::Observable.asciidoc[]
include::Observer.asciidoc[]
include::OffsetDateTime.asciidoc[]
include::OffsetTime.asciidoc[]
include::Optional.asciidoc[]
include::OptionalDouble.asciidoc[]
include::OptionalInt.asciidoc[]
include::OptionalLong.asciidoc[]
include::ParseException.asciidoc[]
include::ParsePosition.asciidoc[]
include::Pattern.asciidoc[]
include::Period.asciidoc[]
include::Predicate.asciidoc[]
include::PrimitiveIterator.asciidoc[]
include::PrimitiveIterator.OfDouble.asciidoc[]
include::PrimitiveIterator.OfInt.asciidoc[]
include::PrimitiveIterator.OfLong.asciidoc[]
include::PriorityQueue.asciidoc[]
include::Queue.asciidoc[]
include::Random.asciidoc[]
include::RandomAccess.asciidoc[]
include::ReflectiveOperationException.asciidoc[]
include::ResolverStyle.asciidoc[]
include::RoundingMode.asciidoc[]
include::RuleBasedCollator.asciidoc[]
include::RuntimeException.asciidoc[]
include::SecurityException.asciidoc[]
include::Set.asciidoc[]
include::Short.asciidoc[]
include::SignStyle.asciidoc[]
include::SimpleDateFormat.asciidoc[]
include::SimpleTimeZone.asciidoc[]
include::SortedMap.asciidoc[]
include::SortedSet.asciidoc[]
include::Spliterator.asciidoc[]
include::Spliterator.OfDouble.asciidoc[]
include::Spliterator.OfInt.asciidoc[]
include::Spliterator.OfLong.asciidoc[]
include::Spliterator.OfPrimitive.asciidoc[]
include::Spliterators.asciidoc[]
include::Stack.asciidoc[]
include::StackTraceElement.asciidoc[]
include::Stream.asciidoc[]
include::Stream.Builder.asciidoc[]
include::StrictMath.asciidoc[]
include::String.asciidoc[]
include::StringBuffer.asciidoc[]
include::StringBuilder.asciidoc[]
include::StringCharacterIterator.asciidoc[]
include::StringIndexOutOfBoundsException.asciidoc[]
include::StringJoiner.asciidoc[]
include::StringTokenizer.asciidoc[]
include::Supplier.asciidoc[]
include::System.asciidoc[]
include::Temporal.asciidoc[]
include::TemporalAccessor.asciidoc[]
include::TemporalAdjuster.asciidoc[]
include::TemporalAdjusters.asciidoc[]
include::TemporalAmount.asciidoc[]
include::TemporalField.asciidoc[]
include::TemporalQueries.asciidoc[]
include::TemporalQuery.asciidoc[]
include::TemporalUnit.asciidoc[]
include::TextStyle.asciidoc[]
include::ThaiBuddhistChronology.asciidoc[]
include::ThaiBuddhistDate.asciidoc[]
include::ThaiBuddhistEra.asciidoc[]
include::TimeZone.asciidoc[]
include::ToDoubleBiFunction.asciidoc[]
include::ToDoubleFunction.asciidoc[]
include::ToIntBiFunction.asciidoc[]
include::ToIntFunction.asciidoc[]
include::ToLongBiFunction.asciidoc[]
include::ToLongFunction.asciidoc[]
include::TooManyListenersException.asciidoc[]
include::TreeMap.asciidoc[]
include::TreeSet.asciidoc[]
include::TypeNotPresentException.asciidoc[]
include::UUID.asciidoc[]
include::UnaryOperator.asciidoc[]
include::UnknownFormatConversionException.asciidoc[]
include::UnknownFormatFlagsException.asciidoc[]
include::UnsupportedOperationException.asciidoc[]
include::UnsupportedTemporalTypeException.asciidoc[]
include::ValueRange.asciidoc[]
include::Vector.asciidoc[]
include::WeekFields.asciidoc[]
include::Year.asciidoc[]
include::YearMonth.asciidoc[]
include::ZoneId.asciidoc[]
include::ZoneOffset.asciidoc[]
include::ZoneOffsetTransition.asciidoc[]
include::ZoneOffsetTransitionRule.asciidoc[]
include::ZoneOffsetTransitionRule.TimeDefinition.asciidoc[]
include::ZoneRules.asciidoc[]
include::ZoneRulesException.asciidoc[]
include::ZoneRulesProvider.asciidoc[]
include::ZonedDateTime.asciidoc[]
include::org.elasticsearch.common.geo.GeoPoint.asciidoc[]
include::org.elasticsearch.index.fielddata.ScriptDocValues.Booleans.asciidoc[]
include::org.elasticsearch.index.fielddata.ScriptDocValues.BytesRefs.asciidoc[]
include::org.elasticsearch.index.fielddata.ScriptDocValues.Doubles.asciidoc[]
include::org.elasticsearch.index.fielddata.ScriptDocValues.GeoPoints.asciidoc[]
include::org.elasticsearch.index.fielddata.ScriptDocValues.Longs.asciidoc[]
include::org.elasticsearch.index.fielddata.ScriptDocValues.Strings.asciidoc[]
include::org.elasticsearch.index.mapper.IpFieldMapper.IpFieldType.IpScriptDocValues.asciidoc[]
include::org.elasticsearch.painless.FeatureTest.asciidoc[]
include::org.joda.time.ReadableDateTime.asciidoc[]
include::org.joda.time.ReadableInstant.asciidoc[]

View File

@ -53,7 +53,8 @@ integTest {
}
}
// Build
/* Build Javadoc for the Java classes in Painless's public API that are in the
* Painless plugin */
task apiJavadoc(type: Javadoc) {
source = sourceSets.main.allJava
include '**/org/elasticsearch/painless/api/'
@ -64,3 +65,10 @@ task apiJavadocJar(type: Jar) {
from apiJavadoc
}
assemble.dependsOn apiJavadocJar
// Reference documentation for Painless's public API.
task generatePainlessApi(type: JavaExec) {
main = 'org.elasticsearch.painless.PainlessDocGenerator'
classpath = sourceSets.test.runtimeClasspath
args file('../../docs/reference/painless-api-reference')
}

View File

@ -1,69 +0,0 @@
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.painless;
import org.elasticsearch.script.ScriptException;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.TreeMap;
import static java.util.Collections.singletonList;
/**
* Utility methods for debugging painless scripts that are accessible to painless scripts.
*/
public class Debug {
private Debug() {}
/**
* Throw an {@link Error} that "explains" an object.
*/
public static void explain(Object objectToExplain) throws PainlessExplainError {
throw new PainlessExplainError(objectToExplain);
}
/**
* Thrown by {@link Debug#explain(Object)} to explain an object. Subclass of {@linkplain Error} so it cannot be caught by painless
* scripts.
*/
public static class PainlessExplainError extends Error {
private final Object objectToExplain;
public PainlessExplainError(Object objectToExplain) {
this.objectToExplain = objectToExplain;
}
Object getObjectToExplain() {
return objectToExplain;
}
/**
* Headers to be added to the {@link ScriptException} for structured rendering.
*/
Map<String, List<String>> getMetadata() {
Map<String, List<String>> metadata = new TreeMap<>();
metadata.put("es.class", singletonList(objectToExplain == null ? "null" : objectToExplain.getClass().getName()));
metadata.put("es.to_string", singletonList(Objects.toString(objectToExplain)));
return metadata;
}
}
}

View File

@ -34,6 +34,7 @@ import java.nio.charset.StandardCharsets;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
@ -462,16 +463,22 @@ public final class Definition {
}
public static final class RuntimeClass {
private final Struct struct;
public final Map<MethodKey, Method> methods;
public final Map<String, MethodHandle> getters;
public final Map<String, MethodHandle> setters;
private RuntimeClass(final Map<MethodKey, Method> methods,
private RuntimeClass(final Struct struct, final Map<MethodKey, Method> methods,
final Map<String, MethodHandle> getters, final Map<String, MethodHandle> setters) {
this.struct = struct;
this.methods = Collections.unmodifiableMap(methods);
this.getters = Collections.unmodifiableMap(getters);
this.setters = Collections.unmodifiableMap(setters);
}
public Struct getStruct() {
return struct;
}
}
/** Returns whether or not a non-array type exists. */
@ -504,6 +511,11 @@ public final class Definition {
return INSTANCE.runtimeMap.get(clazz);
}
/** Collection of all simple types. Used by {@code PainlessDocGenerator} to generate an API reference. */
static Collection<Type> allSimpleTypes() {
return INSTANCE.simpleTypesMap.values();
}
// INTERNAL IMPLEMENTATION:
private final Map<Class<?>, RuntimeClass> runtimeMap;
@ -1048,7 +1060,7 @@ public final class Definition {
}
}
runtimeMap.put(struct.clazz, new RuntimeClass(methods, getters, setters));
runtimeMap.put(struct.clazz, new RuntimeClass(struct, methods, getters, setters));
}
/** computes the functional interface method for a class, or returns null */

View File

@ -0,0 +1,72 @@
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.painless;
import org.elasticsearch.painless.api.Debug;
import org.elasticsearch.script.ScriptException;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import static java.util.Collections.singletonList;
/**
* Thrown by {@link Debug#explain(Object)} to explain an object. Subclass of {@linkplain Error} so it cannot be caught by painless
* scripts.
*/
public class PainlessExplainError extends Error {
private final Object objectToExplain;
public PainlessExplainError(Object objectToExplain) {
this.objectToExplain = objectToExplain;
}
Object getObjectToExplain() {
return objectToExplain;
}
/**
* Headers to be added to the {@link ScriptException} for structured rendering.
*/
Map<String, List<String>> getHeaders() {
Map<String, List<String>> headers = new TreeMap<>();
String toString = "null";
String javaClassName = null;
String painlessClassName = null;
if (objectToExplain != null) {
toString = objectToExplain.toString();
javaClassName = objectToExplain.getClass().getName();
Definition.RuntimeClass runtimeClass = Definition.getRuntimeClass(objectToExplain.getClass());
if (runtimeClass != null) {
painlessClassName = runtimeClass.getStruct().name;
}
}
headers.put("es.to_string", singletonList(toString));
if (painlessClassName != null) {
headers.put("es.painless_class", singletonList(painlessClassName));
}
if (javaClassName != null) {
headers.put("es.java_class", singletonList(javaClassName));
}
return headers;
}
}

View File

@ -121,9 +121,9 @@ final class ScriptImpl implements ExecutableScript, LeafSearchScript {
public Object run() {
try {
return executable.execute(variables, scorer, doc, aggregationValue);
// Note that it is safe to catch any of the following errors since Painless is stateless.
} catch (Debug.PainlessExplainError e) {
throw convertToScriptException(e, e.getMetadata());
} catch (PainlessExplainError e) {
throw convertToScriptException(e, e.getHeaders());
// Note that it is safe to catch any of the following errors since Painless is stateless.
} catch (PainlessError | BootstrapMethodError | OutOfMemoryError | StackOverflowError | Exception e) {
throw convertToScriptException(e, emptyMap());
}

View File

@ -0,0 +1,36 @@
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.painless.api;
import org.elasticsearch.painless.PainlessExplainError;
/**
* Utility methods for debugging painless scripts that are accessible to painless scripts.
*/
public class Debug {
private Debug() {}
/**
* Throw an {@link Error} that "explains" an object.
*/
public static void explain(Object objectToExplain) throws PainlessExplainError {
throw new PainlessExplainError(objectToExplain);
}
}

View File

@ -60,7 +60,7 @@ class def -> java.lang.Object {
#### Painless debugging API
class Debug -> org.elasticsearch.painless.Debug extends Object {
class Debug -> org.elasticsearch.painless.api.Debug extends Object {
void explain(Object)
}

View File

@ -30,49 +30,54 @@ import java.util.Map;
import static java.util.Collections.singletonList;
import static java.util.Collections.singletonMap;
import static org.hamcrest.Matchers.hasEntry;
import static org.hamcrest.Matchers.hasKey;
import static org.hamcrest.Matchers.not;
public class DebugTests extends ScriptTestCase {
public void testExplain() {
// Debug.explain can explain an object
Object dummy = new Object();
Map<String, Object> params = singletonMap("a", dummy);
Debug.PainlessExplainError e = expectScriptThrows(Debug.PainlessExplainError.class, () -> exec(
"Debug.explain(params.a)", params, true));
PainlessExplainError e = expectScriptThrows(PainlessExplainError.class, () -> exec(
"Debug.explain(params.a)", singletonMap("a", dummy), true));
assertSame(dummy, e.getObjectToExplain());
assertThat(e.getMetadata(), hasEntry("es.class", singletonList("java.lang.Object")));
assertThat(e.getMetadata(), hasEntry("es.to_string", singletonList(dummy.toString())));
assertThat(e.getHeaders(), hasEntry("es.to_string", singletonList(dummy.toString())));
assertThat(e.getHeaders(), hasEntry("es.java_class", singletonList("java.lang.Object")));
assertThat(e.getHeaders(), hasEntry("es.painless_class", singletonList("Object")));
// Null should be ok
e = expectScriptThrows(Debug.PainlessExplainError.class, () -> exec("Debug.explain(null)"));
e = expectScriptThrows(PainlessExplainError.class, () -> exec("Debug.explain(null)"));
assertNull(e.getObjectToExplain());
assertThat(e.getMetadata(), hasEntry("es.class", singletonList("null")));
assertThat(e.getMetadata(), hasEntry("es.to_string", singletonList("null")));
assertThat(e.getHeaders(), hasEntry("es.to_string", singletonList("null")));
assertThat(e.getHeaders(), not(hasKey("es.java_class")));
assertThat(e.getHeaders(), not(hasKey("es.painless_class")));
// You can't catch the explain exception
e = expectScriptThrows(Debug.PainlessExplainError.class, () -> exec(
e = expectScriptThrows(PainlessExplainError.class, () -> exec(
"try {\n"
+ " Debug.explain(params.a)\n"
+ "} catch (Exception e) {\n"
+ " return 1\n"
+ "}", params, true));
+ "}", singletonMap("a", dummy), true));
assertSame(dummy, e.getObjectToExplain());
}
/**
* {@link Debug.PainlessExplainError} doesn't serialize but the headers still make it.
* {@link PainlessExplainError} doesn't serialize but the headers still make it.
*/
public void testPainlessExplainErrorSerialization() throws IOException {
Map<String, Object> params = singletonMap("a", "jumped over the moon");
ScriptException e = expectThrows(ScriptException.class, () -> exec("Debug.explain(params.a)", params, true));
assertEquals(singletonList("java.lang.String"), e.getMetadata("es.class"));
assertEquals(singletonList("jumped over the moon"), e.getMetadata("es.to_string"));
assertEquals(singletonList("java.lang.String"), e.getMetadata("es.java_class"));
assertEquals(singletonList("String"), e.getMetadata("es.painless_class"));
try (BytesStreamOutput out = new BytesStreamOutput()) {
out.writeException(e);
try (StreamInput in = out.bytes().streamInput()) {
ElasticsearchException read = (ScriptException) in.readException();
assertEquals(singletonList("java.lang.String"), read.getMetadata("es.class"));
assertEquals(singletonList("jumped over the moon"), read.getMetadata("es.to_string"));
assertEquals(singletonList("java.lang.String"), read.getMetadata("es.java_class"));
assertEquals(singletonList("String"), read.getMetadata("es.painless_class"));
}
}
}

View File

@ -0,0 +1,355 @@
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.painless;
import org.apache.logging.log4j.Logger;
import org.apache.lucene.util.IOUtils;
import org.elasticsearch.common.io.PathUtils;
import org.elasticsearch.common.logging.ESLoggerFactory;
import org.elasticsearch.painless.Definition.Field;
import org.elasticsearch.painless.Definition.Method;
import org.elasticsearch.painless.Definition.Struct;
import org.elasticsearch.painless.Definition.Type;
import org.elasticsearch.painless.api.Augmentation;
import java.io.IOException;
import java.io.PrintStream;
import java.lang.reflect.Modifier;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.function.Consumer;
import static java.util.Comparator.comparing;
import static java.util.stream.Collectors.toList;
/**
* Generates an API reference from the method and type whitelists in {@link Definition}.
*/
public class PainlessDocGenerator {
private static final Logger logger = ESLoggerFactory.getLogger(PainlessDocGenerator.class);
private static final Comparator<Field> FIELD_NAME = comparing(f -> f.name);
private static final Comparator<Method> METHOD_NAME = comparing(m -> m.name);
private static final Comparator<Method> NUMBER_OF_ARGS = comparing(m -> m.arguments.size());
public static void main(String[] args) throws IOException {
Path apiRootPath = PathUtils.get(args[0]);
// Blow away the last execution and recreate it from scratch
IOUtils.rm(apiRootPath);
Files.createDirectories(apiRootPath);
Path indexPath = apiRootPath.resolve("index.asciidoc");
logger.info("Starting to write [index.asciidoc]");
try (PrintStream indexStream = new PrintStream(
Files.newOutputStream(indexPath, StandardOpenOption.CREATE_NEW, StandardOpenOption.WRITE),
false, StandardCharsets.UTF_8.name())) {
emitGeneratedWarning(indexStream);
List<Type> types = Definition.allSimpleTypes().stream().sorted(comparing(t -> t.name)).collect(toList());
for (Type type : types) {
if (type.sort.primitive) {
// Primitives don't have methods to reference
continue;
}
if ("def".equals(type.name)) {
// def is special but doesn't have any methods all of its own.
continue;
}
indexStream.print("include::");
indexStream.print(type.struct.name);
indexStream.println(".asciidoc[]");
Path typePath = apiRootPath.resolve(type.struct.name + ".asciidoc");
logger.info("Writing [{}.asciidoc]", type.name);
try (PrintStream typeStream = new PrintStream(
Files.newOutputStream(typePath, StandardOpenOption.CREATE_NEW, StandardOpenOption.WRITE),
false, StandardCharsets.UTF_8.name())) {
emitGeneratedWarning(typeStream);
typeStream.print("[[");
emitAnchor(typeStream, type.struct);
typeStream.print("]]++");
typeStream.print(type.name);
typeStream.println("++::");
Consumer<Field> documentField = field -> PainlessDocGenerator.documentField(typeStream, field);
Consumer<Method> documentMethod = method -> PainlessDocGenerator.documentMethod(typeStream, method);
type.struct.staticMembers.values().stream().sorted(FIELD_NAME).forEach(documentField);
type.struct.members.values().stream().sorted(FIELD_NAME).forEach(documentField);
type.struct.staticMethods.values().stream().sorted(METHOD_NAME.thenComparing(NUMBER_OF_ARGS)).forEach(documentMethod);
type.struct.constructors.values().stream().sorted(NUMBER_OF_ARGS).forEach(documentMethod);
Map<String, Struct> inherited = new TreeMap<>();
type.struct.methods.values().stream().sorted(METHOD_NAME.thenComparing(NUMBER_OF_ARGS)).forEach(method -> {
if (method.owner == type.struct) {
documentMethod(typeStream, method);
} else {
inherited.put(method.owner.name, method.owner);
}
});
if (false == inherited.isEmpty()) {
typeStream.print("* Inherits methods from ");
boolean first = true;
for (Struct inheritsFrom : inherited.values()) {
if (first) {
first = false;
} else {
typeStream.print(", ");
}
typeStream.print("++");
emitStruct(typeStream, inheritsFrom);
typeStream.print("++");
}
typeStream.println();
}
}
}
}
logger.info("Done writing [index.asciidoc]");
}
private static void documentField(PrintStream stream, Field field) {
stream.print("** [[");
emitAnchor(stream, field);
stream.print("]]");
if (Modifier.isStatic(field.modifiers)) {
stream.print("static ");
}
emitType(stream, field.type);
stream.print(' ');
String javadocRoot = javadocRoot(field);
emitJavadocLink(stream, javadocRoot, field);
stream.print('[');
stream.print(field.name);
stream.print(']');
if (javadocRoot.equals("java8")) {
stream.print(" (");
emitJavadocLink(stream, "java9", field);
stream.print("[java 9])");
}
stream.println();
}
/**
* Document a method.
*/
private static void documentMethod(PrintStream stream, Method method) {
stream.print("* ++[[");
emitAnchor(stream, method);
stream.print("]]");
if (false == method.augmentation && Modifier.isStatic(method.modifiers)) {
stream.print("static ");
}
if (false == method.name.equals("<init>")) {
emitType(stream, method.rtn);
stream.print(' ');
}
String javadocRoot = javadocRoot(method);
emitJavadocLink(stream, javadocRoot, method);
stream.print('[');
stream.print(methodName(method));
stream.print("](");
boolean first = true;
for (Type arg : method.arguments) {
if (first) {
first = false;
} else {
stream.print(", ");
}
emitType(stream, arg);
}
stream.print(")++");
if (javadocRoot.equals("java8")) {
stream.print(" (");
emitJavadocLink(stream, "java9", method);
stream.print("[java 9])");
}
stream.println();
}
/**
* Anchor text for a {@link Struct}.
*/
private static void emitAnchor(PrintStream stream, Struct struct) {
stream.print("painless-api-reference-");
stream.print(struct.name.replace('.', '-'));
}
/**
* Anchor text for a {@link Method}.
*/
private static void emitAnchor(PrintStream stream, Method method) {
emitAnchor(stream, method.owner);
stream.print('-');
stream.print(methodName(method));
stream.print('-');
stream.print(method.arguments.size());
}
/**
* Anchor text for a {@link Field}.
*/
private static void emitAnchor(PrintStream stream, Field field) {
emitAnchor(stream, field.owner);
stream.print('-');
stream.print(field.name);
}
private static String methodName(Method method) {
return method.name.equals("<init>") ? method.owner.name : method.name;
}
/**
* Emit a {@link Type}. If the type is primitive or an array of primitives this just emits the name of the type. Otherwise this emits an
* internal link with the text.
*/
private static void emitType(PrintStream stream, Type type) {
emitStruct(stream, type.struct);
for (int i = 0; i < type.dimensions; i++) {
stream.print("[]");
}
}
/**
* Emit a {@link Struct}. If the {@linkplain Struct} is primitive or def this just emits the name of the struct. Otherwise this emits an
* internal link with the name.
*/
private static void emitStruct(PrintStream stream, Struct struct) {
if (false == struct.clazz.isPrimitive() && false == struct.name.equals("def")) {
stream.print("<<");
emitAnchor(stream, struct);
stream.print(',');
stream.print(struct.name);
stream.print(">>");
} else {
stream.print(struct.name);
}
}
/**
* Emit an external link to Javadoc for a {@link Method}.
*
* @param root name of the root uri variable
*/
private static void emitJavadocLink(PrintStream stream, String root, Method method) {
stream.print("link:{");
stream.print(root);
stream.print("-javadoc}/");
stream.print((method.augmentation ? Augmentation.class : method.owner.clazz).getName().replace('.', '/'));
stream.print(".html#");
stream.print(methodName(method));
stream.print("%2D");
boolean first = true;
if (method.augmentation) {
first = false;
stream.print(method.owner.clazz.getName());
}
for (Type arg: method.arguments) {
if (first) {
first = false;
} else {
stream.print("%2D");
}
stream.print(arg.struct.clazz.getName());
if (arg.dimensions > 0) {
stream.print(":A");
}
}
stream.print("%2D");
}
/**
* Emit an external link to Javadoc for a {@link Field}.
*
* @param root name of the root uri variable
*/
private static void emitJavadocLink(PrintStream stream, String root, Field field) {
stream.print("link:{");
stream.print(root);
stream.print("-javadoc}/");
stream.print(field.owner.clazz.getName().replace('.', '/'));
stream.print(".html#");
stream.print(field.javaName);
}
/**
* Pick the javadoc root for a {@link Method}.
*/
private static String javadocRoot(Method method) {
if (method.augmentation) {
return "painless";
}
return javadocRoot(method.owner);
}
/**
* Pick the javadoc root for a {@link Field}.
*/
private static String javadocRoot(Field field) {
return javadocRoot(field.owner);
}
/**
* Pick the javadoc root for a {@link Struct}.
*/
private static String javadocRoot(Struct struct) {
String classPackage = struct.clazz.getPackage().getName();
if (classPackage.startsWith("java")) {
return "java8";
}
if (classPackage.startsWith("org.elasticsearch.painless")) {
return "painless";
}
if (classPackage.startsWith("org.elasticsearch")) {
return "elasticsearch";
}
if (classPackage.startsWith("org.joda.time")) {
return "joda-time";
}
if (classPackage.startsWith("org.apache.lucene")) {
return "lucene-core";
}
throw new IllegalArgumentException("Unrecognized packge: " + classPackage);
}
private static void emitGeneratedWarning(PrintStream stream) {
stream.println("////");
stream.println("Automatically generated by PainlessDocGenerator. Do not edit.");
stream.println("Rebuild by running `gradle generatePainlessApi`.");
stream.println("////");
stream.println();
}
}