Compare commits

..

No commits in common. "master" and "v1.1.0" have entirely different histories.

35 changed files with 463 additions and 2122 deletions

3
.gitignore vendored
View File

@ -1,4 +1,3 @@
.gradle/
.idea/
build/
out/
build/

142
README.md

File diff suppressed because one or more lines are too long

View File

@ -1,25 +1,20 @@
import org.apache.tools.ant.filters.ReplaceTokens
plugins {
id 'java'
id 'maven-publish'
id('com.github.hierynomus.license') version '0.14.0'
id('io.github.spencerpark.jupyter-kernel-installer') version '2.1.0'
id('com.github.jk1.dependency-license-report')
id 'com.github.hierynomus.license' version '0.14.0'
id 'io.github.spencerpark.jupyter-kernel-installer' version '1.1.5'
}
import org.apache.tools.ant.filters.ReplaceTokens
import com.github.jk1.license.render.*
import com.github.jk1.license.filter.*
import io.github.spencerpark.gradle.*
apply plugin: 'java'
apply plugin: 'maven-publish'
group = 'io.github.spencerpark'
version = '1.3.0'
version = '1.1.0-SNAPSHOT'
wrapper {
gradleVersion = '4.8.1'
distributionType = Wrapper.DistributionType.ALL
task wrapper(type: Wrapper) {
gradleVersion = '4.2.1'
}
// Add the license header to source files
license {
header = file('LICENSE')
exclude '**/*.json'
@ -30,22 +25,6 @@ license {
}
build.dependsOn 'licenseFormat'
// Configures the license report generated for the dependencies.
licenseReport {
excludeGroups = []
renderers = [
// Generate a pretty HTML report that groups dependencies by their license.
new NewInventoryHtmlReportRenderer('dependencies.html'),
// TODO make sure ci verifies that all licenses are know to be allowed to redistribute before publishing
new JsonReportRenderer('dependencies.json')
]
// Group same licenses despite names being slightly different (ex. Apache 2.0 vs Apache version 2)
filters = [new LicenseBundleNormalizer()]
configurations = ['compile']
}
compileJava {
sourceCompatibility = 1.9
targetCompatibility = 1.9
@ -60,19 +39,16 @@ configurations {
}
repositories {
mavenCentral()
maven {
url = 'https://oss.sonatype.org/content/repositories/snapshots/'
}
mavenCentral()
mavenLocal()
}
dependencies {
shade group: 'io.github.spencerpark', name: 'jupyter-jvm-basekernel', version: '2.3.0'
shade group: 'org.apache.ivy', name: 'ivy', version: '2.5.0-rc1'
//shade group: 'org.apache.maven', name: 'maven-settings-builder', version: '3.6.0'
shade group: 'org.apache.maven', name: 'maven-model-builder', version: '3.6.0'
shade group: 'io.github.spencerpark', name: 'jupyter-jvm-basekernel', version: '2.2.1-SNAPSHOT'
shade group: 'org.jboss.shrinkwrap.resolver', name: 'shrinkwrap-resolver-impl-maven', version: '3.1.3'
testCompile group: 'junit', name: 'junit', version: '4.12'
}
@ -80,7 +56,7 @@ dependencies {
jar {
//Include all shaded dependencies in the jar
from configurations.shade
.collect { it.isDirectory() ? it : zipTree(it) }
.collect {it.isDirectory() ? it : zipTree(it)}
manifest {
attributes('Main-class': 'io.github.spencerpark.ijava.IJava')
@ -109,45 +85,4 @@ jupyter {
kernelDisplayName = 'Java'
kernelLanguage = 'java'
kernelInterruptMode = 'message'
kernelParameters {
list('classpath', 'IJAVA_CLASSPATH') {
separator = PATH_SEPARATOR
description = '''A file path separator delimited list of classpath entries that should be available to the user code. **Important:** no matter what OS, this should use forward slash "/" as the file separator. Also each path may actually be a simple glob.'''
}
list('comp-opts', 'IJAVA_COMPILER_OPTS') {
separator = ' '
description = '''A space delimited list of command line options that would be passed to the `javac` command when compiling a project. For example `-parameters` to enable retaining parameter names for reflection.'''
}
list('startup-scripts-path', 'IJAVA_STARTUP_SCRIPTS_PATH') {
separator = PATH_SEPARATOR
description = '''A file path seperator delimited list of `.jshell` scripts to run on startup. This includes ijava-jshell-init.jshell and ijava-display-init.jshell. **Important:** no matter what OS, this should use forward slash "/" as the file separator. Also each path may actually be a simple glob.'''
}
string('startup-script', 'IJAVA_STARTUP_SCRIPT') {
description = '''A block of java code to run when the kernel starts up. This may be something like `import my.utils;` to setup some default imports or even `void sleep(long time) { try {Thread.sleep(time); } catch (InterruptedException e) { throw new RuntimeException(e); }}` to declare a default utility method to use in the notebook.'''
}
string('timeout', 'IJAVA_TIMEOUT') {
aliases NO_TIMEOUT: '-1'
description = '''A duration specifying a timeout (in milliseconds by default) for a _single top level statement_. If less than `1` then there is no timeout. If desired a time may be specified with a `TimeUnit` may be given following the duration number (ex `"30 SECONDS"`).'''
}
}
}
installKernel {
kernelInstallPath = commandLineSpecifiedPath(userInstallPath)
}
zipKernel {
installers {
with 'python'
}
from(generateLicenseReport.outputFolder) {
into 'dependency-licenses'
}
}
zipKernel.dependsOn 'generateLicenseReport'
}

View File

@ -1,13 +0,0 @@
plugins {
id 'groovy'
}
repositories {
maven {
url = 'https://plugins.gradle.org/m2/'
}
}
dependencies {
compile group: 'com.github.jk1.dependency-license-report', name: 'com.github.jk1.dependency-license-report.gradle.plugin', version: '1.1'
}

View File

@ -1,7 +0,0 @@
package io.github.spencerpark.gradle
class DeclaredModuleInfo {
String projectUrl
String license
String licenseUrl
}

View File

@ -1,91 +0,0 @@
package io.github.spencerpark.gradle
import com.github.jk1.license.*
import groovy.json.JsonParserType
import groovy.json.JsonSlurper
abstract class InventoryReportRenderer {
/**
* Collect declared from a JSON object with maven coordinates as keys and {@link DeclaredModuleInfo}
* as values
* @param declarations the stream to parse the json object from
* @return the collected declarations
*/
static Map<String, DeclaredModuleInfo> parseDeclarations(InputStream declarations) {
def rawDeclarations = new JsonSlurper()
.setType(JsonParserType.LAX)
.parse(declarations)
assert rawDeclarations instanceof Map: "Declaration spec must be a json object"
return rawDeclarations.collectEntries([:]) { coords, spec ->
assert spec instanceof Map: "Declaration spec for $coords is not an object"
DeclaredModuleInfo info = new DeclaredModuleInfo()
spec.forEach { String key, val ->
assert val instanceof String: "Declaration spec for $coords::$key must be a string"
assert info.hasProperty(key), "Declaration spec for $coords has unknown key $key"
info[key] = val
}
assert info.projectUrl: "Declaration missing required key: projectUrl"
assert info.license: "Declaration missing required key: license"
assert info.licenseUrl: "Declaration missing required key: licenseUrl"
return [(coords): info]
}
}
static Map<String, List<ModuleData>> collectModulesByLicenseName(ProjectData data, Map<String, DeclaredModuleInfo> declared) {
Map<String, List<ModuleData>> modulesByLicense = [:]
def addModule = { String licenseName, ModuleData module ->
String coords = module.with { "$group:$name:$version" }
if (licenseName == "Unknown" && declared.containsKey(coords))
licenseName = declared[coords].license
modulesByLicense.compute(licenseName) { k, modules ->
return (modules ?: []) << module
}
}
data.allDependencies.each { module ->
if (module.poms.isEmpty()) {
addModule(module.licenseFiles.isEmpty() ? "Unknown" : "Embedded", module)
return
}
PomData pom = module.poms.first()
if (pom.licenses.isEmpty()) {
addModule(module.licenseFiles.isEmpty() ? "Unknown" : "Embedded", module)
} else {
pom.licenses.each { License license ->
addModule(license.name, module)
}
}
}
return modulesByLicense
}
// imported modules are things declared as dependencies but not actually included via gradle means. For
// example a javascript dependency.
static Map<String, Map<String, List<ImportedModuleData>>> collectModulesByLicenseFromImported(ProjectData data) {
Map<String, Map<String, List<ImportedModuleData>>> externalByModulesByLicense = [:]
data.importedModules.each { ImportedModuleBundle module ->
Map<String, List<ImportedModuleData>> modulesByLicense = [:]
module.modules.each { ImportedModuleData moduleData ->
modulesByLicense.compute(moduleData.license) { k, modules ->
return (modules ?: []) << moduleData
}
}
externalByModulesByLicense[module.name] = modulesByLicense
}
return externalByModulesByLicense
}
}

View File

@ -1,267 +0,0 @@
package io.github.spencerpark.gradle
import com.github.jk1.license.*
import com.github.jk1.license.render.ReportRenderer
import groovy.text.SimpleTemplateEngine
import groovy.text.StreamingTemplateEngine
class NewInventoryHtmlReportRenderer implements ReportRenderer {
private final String name
private final String fileName
private final Map<String, DeclaredModuleInfo> declared
private final Map<String, String> colors = [
accent : '#F37726',
primary : 'white',
accentBg : '#616262',
primaryBg: '#989798',
darkText : '#4E4E4E',
lightText: '#e8e5e5',
]
private Writer output
private int counter
NewInventoryHtmlReportRenderer(String fileName = 'index.html', String name = null, File declarationsFileName = null, Map<String, String> colors = [:]) {
this.name = name
this.fileName = fileName
if (declarationsFileName)
declared = InventoryReportRenderer.parseDeclarations(declarationsFileName.newInputStream())
else
declared = [:]
this.colors.putAll(colors)
}
@Override
void render(ProjectData data) {
this.counter = 0
def project = data.project
def name = project.name
LicenseReportExtension config = project.licenseReport
def outFile = new File(config.outputDir, fileName)
def stylesheet = NewInventoryHtmlReportRenderer.class.getResourceAsStream("/license-report.template.css").withReader {
new SimpleTemplateEngine()
.createTemplate(it)
.make(this.colors)
.writeTo(new StringWriter())
.toString()
}
def template = NewInventoryHtmlReportRenderer.class.getResourceAsStream("/license-report.template.html").withReader {
new StreamingTemplateEngine().createTemplate(it)
}
def binding = [
stylesheet : stylesheet,
name : name,
project : project,
inventory : InventoryReportRenderer.collectModulesByLicenseName(data, declared),
externalInventories : InventoryReportRenderer.collectModulesByLicenseFromImported(data),
serializeHref : { String... values ->
values.findAll { it != null }.collect { it.replaceAll(/\s/, '_') }.join('_')
},
printDependency : this.&printDependency,
printImportedDependency: this.&printImportedDependency,
]
outFile.withWriter {
template.make(binding).writeTo(it)
}
}
void tag(Map<String, String> attrs = [:], String name, def children) {
output << "<$name ${attrs.collect { k, v -> "$k=\"$v\"" }.join(" ")}>\n"
if (children.respondsTo("call"))
children.call()
else
text(children)
output << "</$name>\n"
}
void div(Map<String, String> attrs = [:], def children) {
tag(attrs, "div", children)
}
void p(Map<String, String> attrs = [:], def children) {
tag(attrs, "p", children)
}
void a(Map<String, String> attrs = [:], def children) {
tag(attrs, "a", children)
}
void strong(Map<String, String> attrs = [:], def children) {
tag(attrs, "strong", children)
}
void ul(Map<String, String> attrs = [:], def children) {
tag(attrs, "ul", children)
}
void li(Map<String, String> attrs = [:], def children) {
tag(attrs, "li", children)
}
void text(def contents) {
if (contents != null)
output << String.valueOf(contents)
}
void renderDependencyProperty(String label, def children) {
div(class: "dependency-prop") {
tag("label") { text(label) }
div(class: "dependency-value", children)
}
}
void renderDependencyTitle(ModuleData data) {
p(class: "title") {
strong(class: "index", "${++counter}.")
if (data.group) {
strong("Group: ")
text(data.group)
}
if (data.name) {
strong("Name: ")
text(data.name)
}
if (data.version) {
strong("Version: ")
text(data.version)
}
}
}
void renderDependencyTitle(ImportedModuleData data) {
p(class: "title") {
strong(class: "index", ++counter)
if (data.name) {
strong("Name: ")
text(data.name)
}
if (data.version) {
strong("Version: ")
text(data.version)
}
}
}
void renderDependencyProjectUrl(ModuleData data) {
String coords = data.with { "$group:$name:$version" }
ManifestData manifest = data.manifests.isEmpty() ? null : data.manifests.first()
PomData pomData = data.poms.isEmpty() ? null : data.poms.first()
if (manifest?.url && pomData?.projectUrl && manifest.url == pomData.projectUrl) {
renderDependencyProperty("Project URL") {
a(href: manifest.url, { text(manifest.url) })
}
} else if (manifest?.url || pomData?.projectUrl) {
if (manifest?.url) {
renderDependencyProperty("Manifest Project URL") {
a(href: manifest.url, { text(manifest.url) })
}
}
if (pomData?.projectUrl) {
renderDependencyProperty("POM Project URL") {
a(href: pomData.projectUrl, { text(pomData.projectUrl) })
}
}
} else if (declared.containsKey(coords)) {
renderDependencyProperty("Project URL") {
a(href: declared[coords].projectUrl, { text(declared[coords].projectUrl) })
}
}
}
void renderReferencedLicenses(ModuleData data) {
String coords = data.with { "$group:$name:$version" }
ManifestData manifest = data.manifests.isEmpty() ? null : data.manifests.first()
PomData pomData = data.poms.isEmpty() ? null : data.poms.first()
if (manifest?.license || pomData?.licenses) {
if (manifest?.license) {
if (manifest.license.startsWith("http")) {
renderDependencyProperty("Manifest license URL") {
a(href: manifest.license, { text(manifest.license) })
}
} else if (manifest.hasPackagedLicense) {
renderDependencyProperty("Packaged License File") {
a(href: manifest.license, { text(manifest.url) })
}
} else {
renderDependencyProperty("Manifest License") {
text("${manifest.license} (Not Packaged)")
}
}
}
if (pomData?.licenses) {
pomData.licenses.each { License license ->
if (license.url) {
renderDependencyProperty("POM License") {
text("${license.name} - ")
if (license.url.startsWith("http"))
a(href: license.url, { text(license.url) })
else
text(license.url)
}
} else {
renderDependencyProperty("POM License", { text(license.name) })
}
}
}
} else if (declared.containsKey(coords)) {
renderDependencyProperty("License URL") {
a(href: declared[coords].licenseUrl, { text(declared[coords].license) })
}
}
}
void renderIncludedLicenses(ModuleData data) {
if (!data.licenseFiles.isEmpty() && !data.licenseFiles.first().fileDetails.isEmpty()) {
renderDependencyProperty("Embedded license files") {
ul {
data.licenseFiles.first().fileDetails.each {
def file = it.file
li {
a(href: file, { text(file) })
}
}
}
}
}
}
private void printDependency(Writer out, ModuleData data) {
this.output = out
div(class: "dependency") {
renderDependencyTitle(data)
renderDependencyProjectUrl(data)
renderReferencedLicenses(data)
renderIncludedLicenses(data)
}
}
private printImportedDependency(Writer out, ImportedModuleData data) {
this.output = out
div(class: "dependency") {
renderDependencyTitle(data)
renderDependencyProperty("Project URL") {
a(href: data.projectUrl, { text(data.projectUrl) })
}
renderDependencyProperty("License URL") {
a(href: data.licenseUrl, { text(data.license) })
}
}
}
}

View File

@ -1,151 +0,0 @@
@media print {
.inventory {
display: none;
}
.content {
position: static !important;
}
}
html, body, section {
height: 100%;
}
body {
font-family: sans-serif;
line-height: 125%;
margin: 0;
background: $primaryBg;
}
.header {
/* #22aa44 */
background: $accent;
color: $darkText;
padding: 2em 1em 1em 1em;
}
.header h1 {
font-size: 16pt;
margin: 0.5em 0;
}
.header h2 {
font-size: 10pt;
margin: 0;
}
.container {
}
.inventory {
background: $accentBg;
color: $lightText;
padding: 0;
position: fixed;
left: 0;
top: 0;
height: 100%;
width: 25%;
overflow: auto;
}
.inventory ul {
margin: 0;
padding: 0;
}
.inventory li {
list-style: none;
padding: 0;
margin: 0;
}
.inventory li a {
width: 100%;
box-sizing: border-box;
color: $lightText;
text-decoration: none;
display: flex;
flex-direction: row;
padding: 0.938em 0.750em;
}
.inventory li a:hover {
background: rgba(50, 50, 50, 0.5);
color: white;
}
.inventory .section-heading {
background: rgba(50, 50, 50, 0.25);
padding-left: 0.5em;
margin: 0;
padding-top: 1em;
padding-bottom: 1em;
}
.license .license-name {
flex-grow: 1;
}
.license .badge {
background: $accent;
padding: 0.625em 0.938em;
border-radius: 1.250em;
color: $darkText;
display: inline-table;
}
.content {
padding: 0 1rem;
position: absolute;
top: 0;
bottom: 0;
left: 25%;
width: 75%;
box-sizing: border-box;
}
.content h1 {
color: $darkText;
background-color: $accent;
padding: 0.67em;
margin: 0 -1rem;
}
.dependency {
background: white;
padding: 1em;
margin-bottom: 1em;
}
.dependency:hover {
box-shadow: dimgrey 0.2em 0.2em 0.2em 0em;
}
.dependency .index {
font-size: larger;
font-weight: lighter;
}
.dependency-prop {
padding: 0.3em;
}
.dependency-prop:hover {
background: rgba(50, 50, 50, 0.25);
}
.dependency-prop label {
font-weight: bold;
}
.dependency-value {
padding-left: 1em;
}
.dependency-value ul {
margin-top: 0;
margin-bottom: 0;
}

View File

@ -1,71 +0,0 @@
<html>
<head>
<title>Dependency License Report for $name</title>
<style>
<% out.println stylesheet %>
</style>
</head>
<body>
<div class="container">
<!-- INVENTORY -->
<div class="inventory">
<div class="header">
<h1>${project.name} ${!"unspecified".equals(project.version) ? project.version : ""}</h1>
<h2>Dependency License Report</h2>
<h2 class="timestamp">
<em>${new Date().format("yyyy-MM-dd HH:mm:ss z")}</em>
</h2>
</div>
<h3 class="section-heading">${name}</h3>
<ul>
<% inventory.keySet().sort().each { license -> %>
<li>
<a class="license" href="#${serializeHref.call(name, license)}">
<span class="license-name">${license}</span>
<span class="badge">${inventory[license].size()}</span>
</a>
</li>
<% } %>
</ul>
<% externalInventories.each { name, modules -> %>
<h3 class="section-heading">${name}</h3>
<ul>
<% modules.each { license, dependencies -> %>
<li>
<a class="license" href="#${serializeHref.call(name, license)}">
<span class="license-name">${license}</span>
<span class="badge">${dependencies.size()}</span>
</a>
</li>
<% } %>
</ul>
<% } %>
</div>
<!-- DETAILS -->
<div class="content">
<h1>${name}</h1>
<% inventory.keySet().sort().each { license -> %>
<a id="${serializeHref.call(name, license)}"></a>
<h2>${license}</h2>
<% inventory[license].sort({ a, b -> a.group <=> b.group }).each { data ->
printDependency.call(out, data)
} %>
<% } %>
<% externalInventories.keySet().sort().each { String name -> %>
<h1>${name}</h1>
<% externalInventories[name].each { String license, dependencies -> %>
<a id="${serializeHref.call(name, license)}"></a>
<% dependencies.each { importedData ->
printImportedDependency.call(out, importedData)
} %>
<% } %>
<% } %>
</div>
</div>
</body>
</html>

View File

@ -4,21 +4,21 @@ One of the many great things about the Jupyter front ends is the support for [`d
## Notebook functions
IJava injects 2 functions into the user space for displaying data: `display` and `render`. Most use cases should prefer the former but there is a necessary case for `render` that is outline below. In addition the `updateDisplay` function can be used to update a previously displayed object. All are defined in the runtime [Display](/src/main/java/io/github/spencerpark/ijava/runtime/Display.java) class.
IJava injects 2 functions into the user space for displaying data: `display` and `render`. Most use cases should prefer the former but there is a necessary case for `render` that is outline below. Both are defined in [ijava-display-init.jshell](/src/main/resources/ijava-display-init.jshell).
All display/render functions include a `text/plain` representation in their output. By default this is the `String.valueOf(Object)` value but it can be overridden.
### `String display(Object o)`
### `display(Object o)`
Display an object as it's **preferred** types. If you don't want a specific type it is best to let the object decide how it is best represented.
The object is rendered and published on the display stream. An id is returned which can be used to `updateDisplay` if desired.
The object is rendered and published on the display stream.
### `String display(Object o, String... as)`
### `display(Object o, String... as)`
Display an object as the **requested** types. In this case the object attempts to be rendered as the desired mime types given in `as`. No promises though, if a type is unsupported it will simply not appear in the output.
The object is rendered and published on the display stream. An id is returned which can be used to `updateDisplay` if desired.
The object is rendered and published on the display stream.
This is useful when a type has many potential representations but not all are preferred. For example a `CharSequence` has many representations but only the `text/plain` is preferred. To display it as executable javascript we can use the following:
@ -26,20 +26,13 @@ This is useful when a type has many potential representations but not all are pr
display("alert('Hello from IJava!');", "application/javascript");
```
Since there is the potential that some front ends don't support a given format many can be given and the front end chooses the best. For example, to display as html and markdown:
Since there is the potential that some front ends don't support a given format many can be given and the front end chooses the best.
```java
display("<b>Bold</b>", "text/html", "text/markdown");
```
This will trigger a display message with values for `text/html`, `text/markdown`, and the implicit `text/plain`.
### `DisplayData render(Object o)`
### `render(Object o)`
Renders an object as it's **preferred** types and returns it's rendered format. Similar to `display(Object o)` but without publishing the result.
### `DisplayData render(Object o, String... as)`
### `render(Object o, String... as)`
Renders an object as the **requested** types and returns it's rendered format. Similar to `display(Object o, String... as)` but without publishing the result.
@ -53,19 +46,4 @@ render(md, "text/markdown")
This will result in the `Out[_]` result to be the pretty `text/markdown` representation rather than the boring `text/plain` representation.
### `void updateDisplay(String id, Object o)`
Renders an object as it's **preferred** types and updates an existing display with the given id to contain the new rendered object. Similar to `display(Object o)` but updates an existing displayed object instead of appending a new one.
### `void updateDisplay(String id, Object o, String... as)`
Renders an object as it's **requested** types and updates an existing display with the given id to contain the new rendered object. Similar to `display(Object o, String... as)` but updates an existing displayed object instead of appending a new one.
```java
String id = display("<b>Countdown:</b> 3", "text/html");
for (int i = 3; i >= 0; i--) {
updateDisplay(id, "<b>Countdown:</b> " + i, "text/html");
Thread.sleep(1000L);
}
render("<b>Liftoff!</b>", "text/html")
```

View File

@ -1,22 +0,0 @@
# Kernel
All code running in IJava flows through the kernel. This makes it the place to register magics, add things to the classpath, and perform many jupyter related operations.
## Notebook functions
IJava injects a function for getting the active kernel instance and additional helpers for making use of the kernel at runtime. These are defined in the runtime [Kernel](/src/main/java/io/github/spencerpark/ijava/runtime/Kernel.java) class.
### `JavaKernel getKernelInstance()`
Get a reference to the current kernel. It may return null if called outside of a kernel context but should be considered `@NonNull` when inside a notebook or similar. The kernel api has lots of goodies, look at the [JavaKernel](/src/main/java/io/github/spencerpark/ijava/runtime/Kernel.java) class for more information. Specifically there is access to adding to the classpath, getting the magics registry and maven resolver, and access to eval.
### `Object eval(String expr) throws Exception`
The `eval` function provides full access to the code evaluation mechanism of the kernel. It evaluates the code in the _same_ scope as the kernel and **returns an object**. This object is an object that lives in the kernel!
The given expression can be anything you would write in a cell, including magics.
```java
(int) eval("1 + 2") + 3
```

View File

@ -44,18 +44,7 @@ Add jars to the notebook classpath.
###### Line magic
* **arguments**:
* _varargs_ list of simple glob paths to jars on the local file system. If a glob matches a directory all files in that directory will be added.
### classpath
Add entries to the notebook classpath.
###### Line magic
* **arguments**:
* _varargs_ list of simple glob paths to entries on the local file system. This includes directories or jars.
* _varargs_ list of simple glob paths to jars on the local file system

Binary file not shown.

View File

@ -1,4 +1,4 @@
distributionUrl=https\://services.gradle.org/distributions/gradle-4.8.1-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-4.2.1-bin.zip
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStorePath=wrapper/dists

0
gradlew vendored Executable file → Normal file
View File

View File

@ -46,6 +46,9 @@ public class IJava {
public static final String STARTUP_SCRIPT_KEY = "IJAVA_STARTUP_SCRIPT";
public static final String DEFAULT_SHELL_INIT_RESOURCE_PATH = "ijava-jshell-init.jshell";
public static final String MAGICS_INIT_RESOURCE_PATH = "ijava-magics-init.jshell";
public static final String DISPLAY_INIT_RESOURCE_PATH = "ijava-display-init.jshell";
public static final String EVAL_INIT_RESOURCE_PATH = "ijava-eval-init.jshell";
public static final String VERSION;

View File

@ -24,38 +24,30 @@
package io.github.spencerpark.ijava;
import io.github.spencerpark.ijava.execution.*;
import io.github.spencerpark.ijava.magics.ClasspathMagics;
import io.github.spencerpark.ijava.magics.MavenResolver;
import io.github.spencerpark.jupyter.kernel.BaseKernel;
import io.github.spencerpark.jupyter.kernel.LanguageInfo;
import io.github.spencerpark.jupyter.kernel.ReplacementOptions;
import io.github.spencerpark.jupyter.kernel.display.DisplayData;
import io.github.spencerpark.jupyter.kernel.magic.*;
import io.github.spencerpark.jupyter.kernel.magic.registry.Magics;
import io.github.spencerpark.jupyter.kernel.magic.common.Load;
import io.github.spencerpark.jupyter.kernel.util.CharPredicate;
import io.github.spencerpark.jupyter.kernel.util.GlobFinder;
import io.github.spencerpark.jupyter.kernel.util.StringStyler;
import io.github.spencerpark.jupyter.kernel.util.TextColor;
import io.github.spencerpark.jupyter.messages.Header;
import jdk.jshell.*;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Base64;
import java.util.LinkedList;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;
public class JavaKernel extends BaseKernel {
public static String completeCodeSignifier() {
return BaseKernel.IS_COMPLETE_YES;
}
public static String invalidCodeSignifier() {
return BaseKernel.IS_COMPLETE_BAD;
}
public static String maybeCompleteCodeSignifier() {
return BaseKernel.IS_COMPLETE_MAYBE;
}
private static final CharPredicate IDENTIFIER_CHAR = CharPredicate.builder()
.inRange('a', 'z')
.inRange('A', 'Z')
@ -81,6 +73,9 @@ public class JavaKernel extends BaseKernel {
.addClasspathFromString(System.getenv(IJava.CLASSPATH_KEY))
.compilerOptsFromString(System.getenv(IJava.COMPILER_OPTS_KEY))
.startupScript(IJava.resource(IJava.DEFAULT_SHELL_INIT_RESOURCE_PATH))
.startupScript(IJava.resource(IJava.MAGICS_INIT_RESOURCE_PATH))
.startupScript(IJava.resource(IJava.DISPLAY_INIT_RESOURCE_PATH))
.startupScript(IJava.resource(IJava.EVAL_INIT_RESOURCE_PATH))
.startupScriptFiles(System.getenv(IJava.STARTUP_SCRIPTS_KEY))
.startupScript(System.getenv(IJava.STARTUP_SCRIPT_KEY))
.timeoutFromString(System.getenv(IJava.TIMEOUT_DURATION_KEY))
@ -93,13 +88,26 @@ public class JavaKernel extends BaseKernel {
this.magicsTransformer = new MagicsSourceTransformer();
this.magics = new Magics();
this.magics.registerMagics(this.mavenResolver);
this.magics.registerMagics(new ClasspathMagics(this::addToClasspath));
this.magics.registerMagics(new Load(List.of(".jsh", ".jshell", ".java", ".ijava"), this::eval));
this.magics.registerLineMagic("jars", args -> {
List<String> jars = args.stream()
.map(GlobFinder::new)
.flatMap(g -> {
try {
return StreamSupport.stream(g.computeMatchingPaths().spliterator(), false);
} catch (IOException e) {
throw new RuntimeException("Exception resolving jar glob", e);
}
})
.map(p -> p.toAbsolutePath().toString())
.collect(Collectors.toList());
jars.forEach(this::addToClasspath);
return jars;
});
this.languageInfo = new LanguageInfo.Builder("Java")
.version(Runtime.version().toString())
.mimetype("text/x-java-source")
.fileExtension(".jshell")
.fileExtension(".java")
.pygments("java")
.codemirror("java")
.build();
@ -161,12 +169,8 @@ public class JavaKernel extends BaseKernel {
return formatIncompleteSourceException((IncompleteSourceException) e);
} else if (e instanceof EvalException) {
return formatEvalException((EvalException) e);
} else if (e instanceof UnresolvedReferenceException) {
return formatUnresolvedReferenceException(((UnresolvedReferenceException) e));
} else if (e instanceof EvaluationTimeoutException) {
return formatEvaluationTimeoutException((EvaluationTimeoutException) e);
} else if (e instanceof EvaluationInterruptedException) {
return formatEvaluationInterruptedException((EvaluationInterruptedException) e);
} else {
fmt.addAll(super.formatError(e));
}
@ -180,12 +184,8 @@ public class JavaKernel extends BaseKernel {
Snippet snippet = event.snippet();
this.evaluator.getShell().diagnostics(snippet)
.forEach(d -> {
// If has line information related, highlight that span
if (d.getStartPosition() >= 0 && d.getEndPosition() >= 0)
fmt.addAll(this.errorStyler.highlightSubstringLines(snippet.source(),
(int) d.getStartPosition(), (int) d.getEndPosition()));
else
fmt.addAll(this.errorStyler.primaryLines(snippet.source()));
fmt.addAll(this.errorStyler.highlightSubstringLines(snippet.source(),
(int) d.getStartPosition(), (int) d.getEndPosition()));
// Add the error message
for (String line : StringStyler.splitLines(d.getMessage(null))) {
@ -233,23 +233,6 @@ public class JavaKernel extends BaseKernel {
return fmt;
}
private List<String> formatUnresolvedReferenceException(UnresolvedReferenceException e) {
List<String> fmt = new ArrayList<>();
DeclarationSnippet snippet = e.getSnippet();
List<String> unresolvedDependencies = this.evaluator.getShell().unresolvedDependencies(snippet)
.collect(Collectors.toList());
if (!unresolvedDependencies.isEmpty()) {
fmt.addAll(this.errorStyler.primaryLines(snippet.source()));
fmt.add(this.errorStyler.secondary("Unresolved dependencies:"));
unresolvedDependencies.forEach(dep ->
fmt.add(this.errorStyler.secondary(" - " + dep)));
}
return fmt;
}
private List<String> formatEvaluationTimeoutException(EvaluationTimeoutException e) {
List<String> fmt = new ArrayList<>(this.errorStyler.primaryLines(e.getSource()));
@ -262,14 +245,6 @@ public class JavaKernel extends BaseKernel {
return fmt;
}
private List<String> formatEvaluationInterruptedException(EvaluationInterruptedException e) {
List<String> fmt = new ArrayList<>(this.errorStyler.primaryLines(e.getSource()));
fmt.add(this.errorStyler.secondary("Evaluation interrupted."));
return fmt;
}
public Object evalRaw(String expr) throws Exception {
expr = this.magicsTransformer.transformMagics(expr);
@ -351,21 +326,16 @@ public class JavaKernel extends BaseKernel {
.distinct()
.collect(Collectors.toList());
return new ReplacementOptions(options, replaceStart[0], at);
return new ReplacementOptions(options, replaceStart[0], at + 1);
}
@Override
public String isComplete(String code) {
return this.evaluator.isComplete(code);
return super.isComplete(code);
}
@Override
public void onShutdown(boolean isRestarting) {
this.evaluator.shutdown();
}
@Override
public void interrupt() {
this.evaluator.interrupt();
}
}

View File

@ -0,0 +1,247 @@
package io.github.spencerpark.ijava;
import io.github.spencerpark.jupyter.kernel.magic.registry.CellMagic;
import io.github.spencerpark.jupyter.kernel.magic.registry.LineMagic;
import org.apache.maven.model.io.xpp3.MavenXpp3Reader;
import org.jboss.shrinkwrap.resolver.api.maven.ConfigurableMavenResolverSystem;
import org.jboss.shrinkwrap.resolver.api.maven.Maven;
import org.jboss.shrinkwrap.resolver.api.maven.PomEquippedResolveStage;
import org.jboss.shrinkwrap.resolver.api.maven.ScopeType;
import org.jboss.shrinkwrap.resolver.api.maven.repository.MavenRemoteRepositories;
import org.jboss.shrinkwrap.resolver.api.maven.repository.MavenRemoteRepository;
import org.jboss.shrinkwrap.resolver.api.maven.strategy.TransitiveStrategy;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.*;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import java.io.*;
import java.net.URL;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.*;
import java.util.function.Consumer;
import java.util.logging.Level;
import java.util.logging.Logger;
public class MavenResolver {
private final Consumer<String> addToClasspath;
private final List<MavenRemoteRepository> repos;
public MavenResolver(Consumer<String> addToClasspath) {
this.addToClasspath = addToClasspath;
this.repos = new LinkedList<>();
}
public void addRemoteRepo(String name, String url) {
this.repos.add(MavenRemoteRepositories.createRemoteRepository(name, url, "default"));
}
public void addRemoteRepo(String name, URL url) {
this.repos.add(MavenRemoteRepositories.createRemoteRepository(name, url, "default"));
}
public List<File> resolveMavenDependency(String canonical) {
ConfigurableMavenResolverSystem resolver = Maven.configureResolver()
.withClassPathResolution(true)
.withMavenCentralRepo(true);
this.repos.forEach(resolver::withRemoteRepo);
return resolver.resolve(canonical)
.using(TransitiveStrategy.INSTANCE)
.asList(File.class);
}
private String solidifyPartialPOM(String rawIn) throws ParserConfigurationException, IOException, SAXException, TransformerException {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setValidating(false);
DocumentBuilder builder = factory.newDocumentBuilder();
// Wrap in a dummy tag to allow fragments
InputStream inStream = new SequenceInputStream(Collections.enumeration(Arrays.asList(
new ByteArrayInputStream("<ijava>".getBytes(Charset.forName("utf-8"))),
new ByteArrayInputStream(rawIn.getBytes(Charset.forName("utf-8"))),
new ByteArrayInputStream("</ijava>".getBytes(Charset.forName("utf-8")))
)));
Document doc = builder.parse(inStream);
NodeList rootChildren = doc.getDocumentElement().getChildNodes();
// If input was a single "project" tag then we don't touch it. It is assumed
// to be complete.
if (rootChildren.getLength() == 1 && "project".equalsIgnoreCase(rootChildren.item(0).getNodeName()))
return this.writeDOM(new DOMSource(rootChildren.item(0)));
// Put the pieces together and fill in the blanks.
Document fixed = builder.newDocument();
Node project = fixed.appendChild(fixed.createElement("project"));
Node dependencies = project.appendChild(fixed.createElement("dependencies"));
Node repositories = project.appendChild(fixed.createElement("repositories"));
boolean setModelVersion = false;
boolean setGroupId = false;
boolean setArtifactId = false;
boolean setVersion = false;
for (int i = 0; i < rootChildren.getLength(); i++) {
Node child = rootChildren.item(i);
switch (child.getNodeName()) {
case "modelVersion":
setModelVersion = true;
this.appendChildInNewDoc(child, fixed, project);
break;
case "groupId":
setGroupId = true;
this.appendChildInNewDoc(child, fixed, project);
break;
case "artifactId":
setArtifactId = true;
this.appendChildInNewDoc(child, fixed, project);
break;
case "version":
setVersion = true;
this.appendChildInNewDoc(child, fixed, project);
break;
case "dependency":
this.appendChildInNewDoc(child, fixed, dependencies);
break;
case "repository":
this.appendChildInNewDoc(child, fixed, repositories);
break;
case "dependencies":
// Add all dependencies to the collecting tag
NodeList dependencyChildren = child.getChildNodes();
for (int j = 0; j < dependencyChildren.getLength(); j++)
this.appendChildInNewDoc(dependencyChildren.item(j), fixed, dependencies);
break;
case "repositories":
// Add all repositories to the collecting tag
NodeList repositoryChildren = child.getChildNodes();
for (int j = 0; j < repositoryChildren.getLength(); j++)
this.appendChildInNewDoc(repositoryChildren.item(j), fixed, repositories);
break;
default:
this.appendChildInNewDoc(child, fixed, project);
break;
}
}
if (!setModelVersion) {
Node modelVersion = project.appendChild(fixed.createElement("modelVersion"));
modelVersion.setTextContent("4.0.0");
}
if (!setGroupId) {
Node groupId = project.appendChild(fixed.createElement("groupId"));
groupId.setTextContent("ijava.notebook");
}
if (!setArtifactId) {
Node artifactId = project.appendChild(fixed.createElement("artifactId"));
artifactId.setTextContent("cell");
}
if (!setVersion) {
Node version = project.appendChild(fixed.createElement("version"));
version.setTextContent("1");
}
return this.writeDOM(new DOMSource(fixed));
}
private void appendChildInNewDoc(Node oldNode, Document doc, Node newParent) {
Node newNode = oldNode.cloneNode(true);
doc.adoptNode(newNode);
newParent.appendChild(newNode);
}
private String writeDOM(Source src) throws TransformerException, UnsupportedEncodingException {
Transformer idTransformer = TransformerFactory.newInstance().newTransformer();
idTransformer.setOutputProperty(OutputKeys.INDENT, "yes");
ByteArrayOutputStream out = new ByteArrayOutputStream();
Result dest = new StreamResult(out);
idTransformer.transform(src, dest);
return out.toString("utf-8");
}
public void addJarsToClasspath(Iterable<String> jars) {
jars.forEach(this.addToClasspath);
}
@LineMagic(aliases = { "addMavenDependency", "maven" })
public void addMavenDependencies(List<String> args) {
for (String arg : args) {
this.addJarsToClasspath(
this.resolveMavenDependency(arg).stream()
.map(File::getAbsolutePath)
::iterator
);
}
}
@LineMagic(aliases = { "mavenRepo" })
public void addMavenRepo(List<String> args) {
if (args.size() != 2)
throw new IllegalArgumentException("Expected 2 arguments: repository id and url. Got: " + args);
String id = args.get(0);
String url = args.get(1);
this.addRemoteRepo(id, url);
}
@CellMagic
public void loadFromPOM(List<String> args, String body) throws Exception {
try {
Path tempPom = Files.createTempFile("ijava-maven-", ".pom");
String rawPom = this.solidifyPartialPOM(body);
Files.write(tempPom, rawPom.getBytes(Charset.forName("utf-8")));
List<String> loadArgs = new ArrayList<>(args.size() + 1);
loadArgs.add(tempPom.toAbsolutePath().toString());
loadArgs.addAll(args);
this.loadFromPOM(loadArgs);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@LineMagic
public void loadFromPOM(List<String> args) {
if (args.isEmpty())
throw new IllegalArgumentException("Loading from POM requires at least the path to the POM file");
String pomFile = args.get(0);
List<String> scopes = args.subList(1, args.size());
PomEquippedResolveStage stage = Maven.resolver().loadPomFromFile(pomFile);
if (scopes.isEmpty())
stage = stage.importCompileAndRuntimeDependencies();
else
stage = stage.importDependencies(scopes.stream().map(ScopeType::fromScopeType).toArray(ScopeType[]::new));
this.addJarsToClasspath(
stage.resolve()
.withTransitivity()
.asList(File.class).stream()
.map(File::getAbsolutePath)
::iterator
);
}
}

View File

@ -23,17 +23,11 @@
*/
package io.github.spencerpark.ijava.execution;
import io.github.spencerpark.ijava.JavaKernel;
import jdk.jshell.*;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class CodeEvaluator {
private static final Pattern WHITESPACE_PREFIX = Pattern.compile("(?:^|\r?\n)(?<ws>\\s*).*$");
private static final Pattern LAST_LINE = Pattern.compile("(?:^|\r?\n)(?<last>.*)$");
private static final String NO_MAGIC_RETURN = "\"__NO_MAGIC_RETURN\"";
private final JShell shell;
@ -44,8 +38,6 @@ public class CodeEvaluator {
private boolean isInitialized = false;
private final List<String> startupScripts;
private final String indentation = " ";
public CodeEvaluator(JShell shell, IJavaExecutionControlProvider executionControlProvider, String executionControlID, List<String> startupScripts) {
this.shell = shell;
this.executionControlProvider = executionControlProvider;
@ -83,16 +75,8 @@ public class CodeEvaluator {
String key = event.value();
if (key == null) continue;
Snippet.SubKind subKind = event.snippet().subKind();
// Only executable snippets make their way through the machinery we have setup in the
// IJavaExecutionControl. Declarations for example simply take their default value without
// being executed.
Object value = subKind.isExecutable()
? executionControl.takeResult(key)
: event.value();
switch (subKind) {
Object value = executionControl.takeResult(key);
switch (event.snippet().subKind()) {
case VAR_VALUE_SUBKIND:
case OTHER_EXPRESSION_SUBKIND:
case TEMP_VAR_EXPRESSION_SUBKIND:
@ -109,17 +93,8 @@ public class CodeEvaluator {
if (event.causeSnippet() == null) {
JShellException e = event.exception();
if (e != null) {
if (e instanceof EvalException) {
switch (((EvalException) e).getExceptionClassName()) {
case IJavaExecutionControl.EXECUTION_TIMEOUT_NAME:
throw new EvaluationTimeoutException(executionControl.getTimeoutDuration(), executionControl.getTimeoutUnit(), code.trim());
case IJavaExecutionControl.EXECUTION_INTERRUPTED_NAME:
throw new EvaluationInterruptedException(code.trim());
default:
throw e;
}
}
if (e instanceof EvalException && IJavaExecutionControl.EXECUTION_TIMEOUT_NAME.equals(((EvalException) e).getExceptionClassName()))
throw new EvaluationTimeoutException(executionControl.getTimeoutDuration(), executionControl.getTimeoutUnit(), code.trim());
throw e;
}
@ -152,80 +127,8 @@ public class CodeEvaluator {
return lastEvalResult;
}
private String computeIndentation(String partialStatement) {
// Find the indentation of the last line
Matcher m = WHITESPACE_PREFIX.matcher(partialStatement);
String currentIndentation = m.find() ? m.group("ws") : "";
m = LAST_LINE.matcher(partialStatement);
if (!m.find())
throw new Error("Pattern broken. Every string should have a last line.");
// If a brace or paren was opened on the last line and not closed, indent some more.
String lastLine = m.group("last");
int newlyOpenedBraces = -1;
int newlyOpenedParens = -1;
for (int i = 0; i < lastLine.length(); i++) {
switch (lastLine.charAt(i)) {
case '}':
// Ignore closing if one has not been opened on this line yet
if (newlyOpenedBraces == -1) continue;
// Otherwise close an opened one from this line
newlyOpenedBraces--;
break;
case ')':
// Same as for braces, but with the parens
if (newlyOpenedParens == -1) continue;
newlyOpenedParens--;
break;
case '{':
// A brace was opened on this line!
// If the first then get out og the -1 special case with an extra addition
if (newlyOpenedBraces == -1) newlyOpenedBraces++;
newlyOpenedBraces++;
break;
case '(':
if (newlyOpenedParens == -1) newlyOpenedParens++;
newlyOpenedParens++;
break;
}
}
return newlyOpenedBraces > 0 || newlyOpenedParens > 0
? currentIndentation + this.indentation
: currentIndentation;
}
public String isComplete(String code) {
SourceCodeAnalysis.CompletionInfo info = this.sourceAnalyzer.analyzeCompletion(code);
while (info.completeness().isComplete())
info = analyzeCompletion(info.remaining());
switch (info.completeness()) {
case UNKNOWN:
// Unknown means "bad code" and the only way to see if is complete is
// to execute it.
return JavaKernel.invalidCodeSignifier();
case COMPLETE:
case COMPLETE_WITH_SEMI:
case EMPTY:
return JavaKernel.completeCodeSignifier();
case CONSIDERED_INCOMPLETE:
case DEFINITELY_INCOMPLETE:
// Compute the indent of the last line and match it
return this.computeIndentation(info.remaining());
default:
// For completeness, return an "I don't know" if we somehow get down here
return JavaKernel.maybeCompleteCodeSignifier();
}
}
public void interrupt() {
IJavaExecutionControl executionControl =
this.executionControlProvider.getRegisteredControlByID(this.executionControlID);
if (executionControl != null)
executionControl.interrupt();
this.shell.stop();
}
public void shutdown() {

View File

@ -30,13 +30,13 @@ import java.io.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.regex.Pattern;
public class CodeEvaluatorBuilder {
private static final Pattern PATH_SPLITTER = Pattern.compile(File.pathSeparator, Pattern.LITERAL);
private static final Pattern BLANK = Pattern.compile("^\\s*$");
private static final int BUFFER_SIZE = 1024;
private static final OutputStream STDOUT = new LazyOutputStreamDelegate(() -> System.out);
@ -59,10 +59,7 @@ public class CodeEvaluatorBuilder {
public CodeEvaluatorBuilder addClasspathFromString(String classpath) {
if (classpath == null) return this;
if (BLANK.matcher(classpath).matches()) return this;
Collections.addAll(this.classpath, PATH_SPLITTER.split(classpath));
return this;
}
@ -148,7 +145,6 @@ public class CodeEvaluatorBuilder {
public CodeEvaluatorBuilder startupScriptFiles(String paths) {
if (paths == null) return this;
if (BLANK.matcher(paths).matches()) return this;
for (String glob : PATH_SPLITTER.split(paths)) {
GlobFinder resolver = new GlobFinder(glob);
@ -203,8 +199,6 @@ public class CodeEvaluatorBuilder {
.build();
for (String cp : this.classpath) {
if (BLANK.matcher(cp).matches()) continue;
GlobFinder resolver = new GlobFinder(cp);
try {
for (Path entry : resolver.computeMatchingPaths())

View File

@ -1,19 +0,0 @@
package io.github.spencerpark.ijava.execution;
public class EvaluationInterruptedException extends Exception {
private final String source;
public EvaluationInterruptedException(String source) {
this.source = source;
}
public String getSource() {
return source;
}
@Override
public String getMessage() {
return String.format("Evaluator was interrupted while executing: '%s'",
this.source);
}
}

View File

@ -25,7 +25,6 @@ package io.github.spencerpark.ijava.execution;
import jdk.jshell.EvalException;
import jdk.jshell.execution.DirectExecutionControl;
import jdk.jshell.spi.SPIResolutionException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
@ -42,18 +41,10 @@ import java.util.concurrent.atomic.AtomicInteger;
public class IJavaExecutionControl extends DirectExecutionControl {
/**
* A special "class name" for a {@link jdk.jshell.spi.ExecutionControl.UserException} such that it may be
* identified after serialization into an {@link jdk.jshell.EvalException} via {@link
* EvalException#getExceptionClassName()}.
* identified after serialization into an {@link jdk.jshell.EvalException} via {@link EvalException#getExceptionClassName()}.
*/
public static final String EXECUTION_TIMEOUT_NAME = "Execution Timeout"; // Has spaces to not collide with a class name
/**
* A special "class name" for a {@link jdk.jshell.spi.ExecutionControl.UserException} such that it may be
* identified after serialization into an {@link jdk.jshell.EvalException} via {@link
* EvalException#getExceptionClassName()}
*/
public static final String EXECUTION_INTERRUPTED_NAME = "Execution Interrupted";
private static final Object NULL = new Object();
private static final AtomicInteger EXECUTOR_THREAD_ID = new AtomicInteger(0);
@ -63,7 +54,6 @@ public class IJavaExecutionControl extends DirectExecutionControl {
private final long timeoutTime;
private final TimeUnit timeoutUnit;
private final ConcurrentMap<String, Future<Object>> running = new ConcurrentHashMap<>();
private final Map<String, Object> results = new ConcurrentHashMap<>();
public IJavaExecutionControl() {
@ -91,28 +81,17 @@ public class IJavaExecutionControl extends DirectExecutionControl {
return result == NULL ? null : result;
}
private Object execute(String key, Method doitMethod) throws TimeoutException, Exception {
private Object execute(Method doitMethod) throws TimeoutException, Exception {
Future<Object> runningTask = this.executor.submit(() -> doitMethod.invoke(null));
this.running.put(key, runningTask);
try {
if (this.timeoutTime > 0)
return runningTask.get(this.timeoutTime, this.timeoutUnit);
return runningTask.get();
} catch (CancellationException e) {
// If canceled this means that stop() or interrupt() was invoked.
if (this.executor.isShutdown())
// If the executor is shutdown, the situation is the former in which
// case the protocol is to throw an ExecutionControl.StoppedException.
throw new StoppedException();
else
// The execution was purposely interrupted.
throw new UserException(
"Execution interrupted.",
EXECUTION_INTERRUPTED_NAME,
e.getStackTrace()
);
// If canceled this means that stop() was invoked in which case the protocol is to
// throw an ExecutionControl.StoppedException.
throw new StoppedException();
} catch (ExecutionException e) {
// The execution threw an exception. The actual exception is the cause of the ExecutionException.
Throwable cause = e.getCause();
@ -122,18 +101,15 @@ public class IJavaExecutionControl extends DirectExecutionControl {
}
if (cause == null)
throw new UserException("null", "Unknown Invocation Exception", e.getStackTrace());
else if (cause instanceof SPIResolutionException)
throw new ResolutionException(((SPIResolutionException) cause).id(), cause.getStackTrace());
else
throw new UserException(String.valueOf(cause.getMessage()), String.valueOf(cause.getClass().getName()), cause.getStackTrace());
} catch (TimeoutException e) {
this.stop();
throw new UserException(
String.format("Execution timed out after configured timeout of %d %s.", this.timeoutTime, this.timeoutUnit.toString().toLowerCase()),
EXECUTION_TIMEOUT_NAME,
e.getStackTrace()
new StackTraceElement[]{} // The trace is irrelevant because it is in the kernel space not the user space so leave it blank.
);
} finally {
this.running.remove(key, runningTask);
}
}
@ -149,17 +125,12 @@ public class IJavaExecutionControl extends DirectExecutionControl {
*/
@Override
protected String invoke(Method doitMethod) throws Exception {
Object value = this.execute(doitMethod);
String id = UUID.randomUUID().toString();
Object value = this.execute(id, doitMethod);
this.results.put(id, value);
return id;
}
public void interrupt() {
this.running.forEach((id, future) ->
future.cancel(true));
}
@Override
public void stop() throws EngineTerminationException, InternalException {
this.executor.shutdownNow();

View File

@ -1,26 +1,3 @@
/*
* The MIT License (MIT)
*
* Copyright (c) 2018 Spencer Park
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package io.github.spencerpark.ijava.execution;
import io.github.spencerpark.jupyter.kernel.magic.CellMagicParseContext;
@ -38,7 +15,7 @@ public class MagicsSourceTransformer {
private final MagicParser parser;
public MagicsSourceTransformer() {
this.parser = new MagicParser("(?<=(?:^|=))\\s*%", "%%");
this.parser = new MagicParser("%", "%%");
}
public String transformMagics(String source) {

View File

@ -1,56 +0,0 @@
package io.github.spencerpark.ijava.magics;
import io.github.spencerpark.jupyter.kernel.magic.registry.LineMagic;
import io.github.spencerpark.jupyter.kernel.util.GlobFinder;
import java.io.IOException;
import java.util.List;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;
public class ClasspathMagics {
private final Consumer<String> addToClasspath;
public ClasspathMagics(Consumer<String> addToClasspath) {
this.addToClasspath = addToClasspath;
}
@LineMagic
public List<String> jars(List<String> args) {
List<String> jars = args.stream()
.map(GlobFinder::new)
.flatMap(g -> {
try {
return StreamSupport.stream(g.computeMatchingFiles().spliterator(), false);
} catch (IOException e) {
throw new RuntimeException("Exception resolving jar glob", e);
}
})
.map(p -> p.toAbsolutePath().toString())
.collect(Collectors.toList());
jars.forEach(this.addToClasspath);
return jars;
}
@LineMagic
public List<String> classpath(List<String> args) {
List<String> paths = args.stream()
.map(GlobFinder::new)
.flatMap(g -> {
try {
return StreamSupport.stream(g.computeMatchingPaths().spliterator(), false);
} catch (IOException e) {
throw new RuntimeException("Exception resolving jar glob", e);
}
})
.map(p -> p.toAbsolutePath().toString())
.collect(Collectors.toList());
paths.forEach(this.addToClasspath);
return paths;
}
}

View File

@ -1,524 +0,0 @@
/*
* The MIT License (MIT)
*
* Copyright (c) 2018 Spencer Park
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package io.github.spencerpark.ijava.magics;
import io.github.spencerpark.ijava.magics.dependencies.CommonRepositories;
import io.github.spencerpark.ijava.magics.dependencies.Maven;
import io.github.spencerpark.ijava.magics.dependencies.MavenToIvy;
import io.github.spencerpark.jupyter.kernel.magic.registry.CellMagic;
import io.github.spencerpark.jupyter.kernel.magic.registry.LineMagic;
import io.github.spencerpark.jupyter.kernel.magic.registry.MagicsArgs;
import org.apache.ivy.Ivy;
import org.apache.ivy.core.module.descriptor.DefaultModuleDescriptor;
import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
import org.apache.ivy.core.module.id.ModuleRevisionId;
import org.apache.ivy.core.report.ArtifactDownloadReport;
import org.apache.ivy.core.report.ResolveReport;
import org.apache.ivy.core.resolve.ResolveOptions;
import org.apache.ivy.core.settings.IvySettings;
import org.apache.ivy.plugins.parser.m2.PomModuleDescriptorParser;
import org.apache.ivy.plugins.repository.url.URLResource;
import org.apache.ivy.plugins.resolver.ChainResolver;
import org.apache.ivy.plugins.resolver.DependencyResolver;
import org.apache.ivy.util.DefaultMessageLogger;
import org.apache.ivy.util.Message;
import org.apache.ivy.util.MessageLogger;
import org.apache.maven.model.Model;
import org.apache.maven.model.building.ModelBuildingException;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.*;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import java.io.*;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.text.ParseException;
import java.util.*;
import java.util.function.Consumer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
public class MavenResolver {
private static final String DEFAULT_RESOLVER_NAME = "default";
/**
* "master" includes the artifact published by the module.
* "runtime" includes the dependencies required for the module to run and
* extends "compile" which is the dependencies required to compile the module.
*/
private static final String[] DEFAULT_RESOLVE_CONFS = { "master", "runtime" };
/**
* The ivy artifact type corresponding to a binary artifact for a module.
*/
private static final String JAR_TYPE = "jar";
/**
* The ivy artifact type corresponding to a source code artifact for a module. This
* is still usually a ".jar" file but that corresponds to the "ext" not the "type".
*/
private static final String SOURCE_TYPE = "source";
/**
* The ivy artifact type corresponding to a javadoc (HTML) artifact for a module. This
* is still usually a ".jar" file but that corresponds to the "ext" not the "type".
*/
private static final String JAVADOC_TYPE = "javadoc";
private static final Pattern IVY_MRID_PATTERN = Pattern.compile(
"^(?<organization>[-\\w/._+=]*)#(?<name>[-\\w/._+=]+)(?:#(?<branch>[-\\w/._+=]+))?;(?<revision>[-\\w/._+=,\\[\\]{}():@]+)$"
);
private static final Pattern MAVEN_MRID_PATTERN = Pattern.compile(
"^(?<group>[^:\\s]+):(?<artifact>[^:\\s]+)(?::(?<packaging>[^:\\s]*)(?::(?<classifier>[^:\\s]+))?)?:(?<version>[^:\\s]+)$"
);
private final Consumer<String> addToClasspath;
private final List<DependencyResolver> repos;
public MavenResolver(Consumer<String> addToClasspath) {
this.addToClasspath = addToClasspath;
this.repos = new LinkedList<>();
this.repos.add(CommonRepositories.mavenCentral());
this.repos.add(CommonRepositories.mavenLocal());
}
public void addRemoteRepo(String name, String url) {
if (DEFAULT_RESOLVER_NAME.equals(name))
throw new IllegalArgumentException("Illegal repository name, cannot use '" + DEFAULT_RESOLVER_NAME + "'.");
this.repos.add(CommonRepositories.maven(name, url));
}
private ChainResolver searchAllReposResolver(Set<String> repos) {
ChainResolver resolver = new ChainResolver();
resolver.setName(DEFAULT_RESOLVER_NAME);
this.repos.stream()
.filter(r -> repos == null || repos.contains(r.getName().toLowerCase()))
.forEach(resolver::add);
if (repos != null) {
Set<String> resolverNames = resolver.getResolvers().stream()
.map(d -> d.getName().toLowerCase())
.collect(Collectors.toSet());
repos.removeAll(resolverNames);
repos.forEach(r -> {
try {
URL url = new URL(r);
resolver.add(CommonRepositories.maven("from-" + url.getHost(), r));
} catch (MalformedURLException e) {
// Ignore as we will assume that a bad url was a name
}
});
}
return resolver;
}
private static ModuleRevisionId parseCanonicalArtifactName(String canonical) {
Matcher m = IVY_MRID_PATTERN.matcher(canonical);
if (m.matches()) {
return ModuleRevisionId.newInstance(
m.group("organization"),
m.group("name"),
m.group("branch"),
m.group("revision")
);
}
m = MAVEN_MRID_PATTERN.matcher(canonical);
if (m.matches()) {
String packaging = m.group("packaging");
String classifier = m.group("classifier");
return ModuleRevisionId.newInstance(
m.group("group"),
m.group("artifact"),
m.group("version"),
packaging == null
? Collections.emptyMap()
: classifier == null
? Map.of("ext", packaging)
: Map.of("ext", packaging, "m:classifier", classifier)
);
}
throw new IllegalArgumentException("Cannot resolve '" + canonical + "' as maven or ivy coordinates.");
}
/**
* Create an ivy instance with the specified verbosity. The instance is relatively plain.
*
* @param verbosity the verbosity level.
* <ol start="0">
* <li>ERROR</li>
* <li>WANRING</li>
* <li>INFO</li>
* <li>VERBOSE</li>
* <li>DEBUG</li>
* </ol>
*
* @return the fresh ivy instance.
*/
private Ivy createDefaultIvyInstance(int verbosity) {
MessageLogger logger = new DefaultMessageLogger(verbosity);
// Set the default logger since not all things log to the ivy instance.
Message.setDefaultLogger(logger);
Ivy ivy = new Ivy();
ivy.getLoggerEngine().setDefaultLogger(logger);
ivy.setSettings(new IvySettings());
ivy.bind();
return ivy;
}
// TODO support multiple at once. This is necessary for conflict resolution with multiple overlapping dependencies.
// TODO support classpath resolution
public List<File> resolveMavenDependency(String canonical, Set<String> repos, int verbosity) throws IOException, ParseException {
ChainResolver rootResolver = this.searchAllReposResolver(repos);
Ivy ivy = this.createDefaultIvyInstance(verbosity);
IvySettings settings = ivy.getSettings();
settings.addResolver(rootResolver);
rootResolver.setCheckmodified(true);
settings.setDefaultResolver(rootResolver.getName());
ivy.getLoggerEngine().info("Searching for dependencies in: " + rootResolver.getResolvers());
ResolveOptions resolveOptions = new ResolveOptions();
resolveOptions.setTransitive(true);
resolveOptions.setDownload(true);
ModuleRevisionId artifactIdentifier = MavenResolver.parseCanonicalArtifactName(canonical);
DefaultModuleDescriptor containerModule = DefaultModuleDescriptor.newCallerInstance(
artifactIdentifier,
DEFAULT_RESOLVE_CONFS,
true, // Transitive
repos != null // Changing - the resolver will set this based on SNAPSHOT since they are all m2 compatible
// but if `repos` is specified, we want to force a lookup.
);
ResolveReport resolved = ivy.resolve(containerModule, resolveOptions);
if (resolved.hasError()) {
MessageLogger logger = ivy.getLoggerEngine();
Arrays.stream(resolved.getAllArtifactsReports())
.forEach(r -> {
logger.error("download " + r.getDownloadStatus() + ": " + r.getArtifact() + " of " + r.getType());
if (r.getArtifactOrigin() == null)
logger.error("\tCouldn't find artifact.");
else
logger.error("\tfrom: " + r.getArtifactOrigin());
});
// TODO better error...
throw new RuntimeException("Error resolving '" + canonical + "'. " + resolved.getAllProblemMessages());
}
return Arrays.stream(resolved.getAllArtifactsReports())
.filter(a -> JAR_TYPE.equalsIgnoreCase(a.getType()))
.map(ArtifactDownloadReport::getLocalFile)
.collect(Collectors.toList());
}
private File convertPomToIvy(Ivy ivy, File pomFile) throws IOException, ParseException {
PomModuleDescriptorParser parser = PomModuleDescriptorParser.getInstance();
URL pomUrl = pomFile.toURI().toURL();
ModuleDescriptor pomModule = parser.parseDescriptor(new IvySettings(), pomFile.toURI().toURL(), false);
File tempIvyFile = File.createTempFile("ijava-ivy-", ".xml").getAbsoluteFile();
tempIvyFile.deleteOnExit();
parser.toIvyFile(pomUrl.openStream(), new URLResource(pomUrl), tempIvyFile, pomModule);
MessageLogger logger = ivy.getLoggerEngine();
logger.info(new String(Files.readAllBytes(tempIvyFile.toPath()), Charset.forName("utf8")));
return tempIvyFile;
}
private void addPomReposToIvySettings(IvySettings settings, File pomFile) throws ModelBuildingException {
Model mavenModel = Maven.getInstance().readEffectiveModel(pomFile).getEffectiveModel();
ChainResolver pomRepos = MavenToIvy.createChainForModelRepositories(mavenModel);
pomRepos.setName(DEFAULT_RESOLVER_NAME);
settings.addResolver(pomRepos);
settings.setDefaultResolver(DEFAULT_RESOLVER_NAME);
}
private List<File> resolveFromIvyFile(Ivy ivy, File ivyFile, List<String> scopes) throws IOException, ParseException {
ResolveOptions resolveOptions = new ResolveOptions();
resolveOptions.setTransitive(true);
resolveOptions.setDownload(true);
resolveOptions.setConfs(!scopes.isEmpty()
? scopes.toArray(new String[0])
: DEFAULT_RESOLVE_CONFS
);
ResolveReport resolved = ivy.resolve(ivyFile, resolveOptions);
if (resolved.hasError())
// TODO better error...
throw new RuntimeException("Error resolving '" + ivyFile + "'. " + resolved.getAllProblemMessages());
return Arrays.stream(resolved.getAllArtifactsReports())
.map(ArtifactDownloadReport::getLocalFile)
.collect(Collectors.toList());
}
private String solidifyPartialPOM(String rawIn) throws ParserConfigurationException, IOException, SAXException, TransformerException {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setValidating(false);
DocumentBuilder builder = factory.newDocumentBuilder();
// Wrap in a dummy tag to allow fragments
InputStream inStream = new SequenceInputStream(Collections.enumeration(Arrays.asList(
new ByteArrayInputStream("<ijava>".getBytes(Charset.forName("utf-8"))),
new ByteArrayInputStream(rawIn.getBytes(Charset.forName("utf-8"))),
new ByteArrayInputStream("</ijava>".getBytes(Charset.forName("utf-8")))
)));
Document doc = builder.parse(inStream);
NodeList rootChildren = doc.getDocumentElement().getChildNodes();
// If input was a single "project" tag then we don't touch it. It is assumed
// to be complete.
if (rootChildren.getLength() == 1 && "project".equalsIgnoreCase(rootChildren.item(0).getNodeName()))
return this.writeDOM(new DOMSource(rootChildren.item(0)));
// Put the pieces together and fill in the blanks.
Document fixed = builder.newDocument();
Node project = fixed.appendChild(fixed.createElement("project"));
Node dependencies = project.appendChild(fixed.createElement("dependencies"));
Node repositories = project.appendChild(fixed.createElement("repositories"));
boolean setModelVersion = false;
boolean setGroupId = false;
boolean setArtifactId = false;
boolean setVersion = false;
for (int i = 0; i < rootChildren.getLength(); i++) {
Node child = rootChildren.item(i);
switch (child.getNodeName()) {
case "modelVersion":
setModelVersion = true;
this.appendChildInNewDoc(child, fixed, project);
break;
case "groupId":
setGroupId = true;
this.appendChildInNewDoc(child, fixed, project);
break;
case "artifactId":
setArtifactId = true;
this.appendChildInNewDoc(child, fixed, project);
break;
case "version":
setVersion = true;
this.appendChildInNewDoc(child, fixed, project);
break;
case "dependency":
this.appendChildInNewDoc(child, fixed, dependencies);
break;
case "repository":
this.appendChildInNewDoc(child, fixed, repositories);
break;
case "dependencies":
// Add all dependencies to the collecting tag
NodeList dependencyChildren = child.getChildNodes();
for (int j = 0; j < dependencyChildren.getLength(); j++)
this.appendChildInNewDoc(dependencyChildren.item(j), fixed, dependencies);
break;
case "repositories":
// Add all repositories to the collecting tag
NodeList repositoryChildren = child.getChildNodes();
for (int j = 0; j < repositoryChildren.getLength(); j++)
this.appendChildInNewDoc(repositoryChildren.item(j), fixed, repositories);
break;
default:
this.appendChildInNewDoc(child, fixed, project);
break;
}
}
if (!setModelVersion) {
Node modelVersion = project.appendChild(fixed.createElement("modelVersion"));
modelVersion.setTextContent("4.0.0");
}
if (!setGroupId) {
Node groupId = project.appendChild(fixed.createElement("groupId"));
groupId.setTextContent("ijava.notebook");
}
if (!setArtifactId) {
Node artifactId = project.appendChild(fixed.createElement("artifactId"));
artifactId.setTextContent("cell");
}
if (!setVersion) {
Node version = project.appendChild(fixed.createElement("version"));
version.setTextContent("1");
}
return this.writeDOM(new DOMSource(fixed));
}
private void appendChildInNewDoc(Node oldNode, Document doc, Node newParent) {
Node newNode = oldNode.cloneNode(true);
doc.adoptNode(newNode);
newParent.appendChild(newNode);
}
private String writeDOM(Source src) throws TransformerException, UnsupportedEncodingException {
Transformer idTransformer = TransformerFactory.newInstance().newTransformer();
idTransformer.setOutputProperty(OutputKeys.INDENT, "yes");
ByteArrayOutputStream out = new ByteArrayOutputStream();
Result dest = new StreamResult(out);
idTransformer.transform(src, dest);
return out.toString("utf-8");
}
public void addJarsToClasspath(Iterable<String> jars) {
jars.forEach(this.addToClasspath);
}
@LineMagic(aliases = { "addMavenDependency", "maven" })
public void addMavenDependencies(List<String> args) {
MagicsArgs schema = MagicsArgs.builder()
.varargs("deps")
.keyword("from")
.flag("verbose", 'v')
.onlyKnownKeywords()
.onlyKnownFlags()
.build();
Map<String, List<String>> vals = schema.parse(args);
List<String> deps = vals.get("deps");
List<String> from = vals.get("from");
int verbosity = vals.get("verbose").size();
Set<String> repos = from.isEmpty() ? null : new LinkedHashSet<>(from);
for (String dep : deps) {
try {
this.addJarsToClasspath(
this.resolveMavenDependency(dep, repos, verbosity).stream()
.map(File::getAbsolutePath)
::iterator
);
} catch (IOException | ParseException e) {
throw new RuntimeException(e);
}
}
}
@LineMagic(aliases = { "mavenRepo" })
public void addMavenRepo(List<String> args) {
MagicsArgs schema = MagicsArgs.builder().required("id").required("url").build();
Map<String, List<String>> vals = schema.parse(args);
String id = vals.get("id").get(0);
String url = vals.get("url").get(0);
this.addRemoteRepo(id, url);
}
@CellMagic
public void loadFromPOM(List<String> args, String body) throws Exception {
try {
File tempPomPath = File.createTempFile("ijava-maven-", ".pom").getAbsoluteFile();
tempPomPath.deleteOnExit();
String rawPom = this.solidifyPartialPOM(body);
Files.write(tempPomPath.toPath(), rawPom.getBytes(Charset.forName("utf-8")));
List<String> loadArgs = new ArrayList<>(args.size() + 1);
loadArgs.add(tempPomPath.getAbsolutePath());
loadArgs.addAll(args);
this.loadFromPOM(loadArgs);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@LineMagic
public void loadFromPOM(List<String> args) {
if (args.isEmpty())
throw new IllegalArgumentException("Loading from POM requires at least the path to the POM file");
MagicsArgs schema = MagicsArgs.builder()
.required("pomPath")
.varargs("scopes")
.flag("verbose", 'v')
.onlyKnownKeywords().onlyKnownFlags().build();
Map<String, List<String>> vals = schema.parse(args);
String pomPath = vals.get("pomPath").get(0);
List<String> scopes = vals.get("scopes");
int verbosity = vals.get("verbose").size();
File pomFile = new File(pomPath);
try {
Ivy ivy = this.createDefaultIvyInstance(verbosity);
IvySettings settings = ivy.getSettings();
File ivyFile = this.convertPomToIvy(ivy, pomFile);
this.addPomReposToIvySettings(settings, pomFile);
this.addJarsToClasspath(
this.resolveFromIvyFile(ivy, ivyFile, scopes).stream()
.map(File::getAbsolutePath)
::iterator
);
} catch (IOException | ParseException | ModelBuildingException e) {
throw new RuntimeException(e);
}
}
}

View File

@ -1,56 +0,0 @@
package io.github.spencerpark.ijava.magics.dependencies;
import org.apache.ivy.plugins.resolver.DependencyResolver;
import org.apache.ivy.plugins.resolver.IBiblioResolver;
import org.xml.sax.SAXException;
import java.io.IOException;
import java.nio.file.Path;
public class CommonRepositories {
protected static final String MAVEN_PATTERN_PREFIX = "[organisation]/[module]/[revision]/[artifact]-[revision](-[classifier])";
protected static final String MAVEN_ARTIFACT_PATTERN = MAVEN_PATTERN_PREFIX + ".[ext]";
protected static final String MAVEN_POM_PATTERN = MAVEN_PATTERN_PREFIX + ".pom";
public static DependencyResolver maven(String name, String urlRaw) {
IBiblioResolver resolver = new IBiblioResolver();
resolver.setM2compatible(true);
resolver.setUseMavenMetadata(true);
resolver.setUsepoms(true);
resolver.setRoot(urlRaw);
resolver.setName(name);
return resolver;
}
public static DependencyResolver mavenCentral() {
return CommonRepositories.maven("maven-central", "https://repo.maven.apache.org/maven2/");
}
public static DependencyResolver jcenter() {
return CommonRepositories.maven("jcenter", "https://jcenter.bintray.com/");
}
public static DependencyResolver mavenLocal() {
IBiblioResolver resolver = new IBiblioResolver();
resolver.setM2compatible(true);
resolver.setUseMavenMetadata(true);
resolver.setUsepoms(true);
resolver.setName("maven-local");
Path localRepoPath;
try {
localRepoPath = Maven.getInstance().getConfiguredLocalRepositoryPath();
} catch (IOException e) {
throw new RuntimeException("Error reading maven settings. " + e.getLocalizedMessage(), e);
} catch (SAXException e) {
throw new RuntimeException("Error parsing maven settings. " + e.getLocalizedMessage(), e);
}
resolver.setRoot("file:///" + localRepoPath.toString());
return resolver;
}
}

View File

@ -1,214 +0,0 @@
package io.github.spencerpark.ijava.magics.dependencies;
import org.apache.maven.building.StringSource;
import org.apache.maven.model.building.*;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Collections;
import java.util.Map;
import java.util.Properties;
import java.util.function.Function;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class Maven {
private static final Pattern MAVEN_VAR_PATTERN = Pattern.compile("\\$\\{(?<name>[^}*])}");
private static final Maven INSTANCE = new Maven(new Properties(), Collections.emptyMap());
public static Maven getInstance() {
return INSTANCE;
}
// User provider environment overrides.
private final Properties properties;
private final Map<String, String> environment;
public Maven(Properties properties, Map<String, String> environment) {
this.properties = properties;
this.environment = environment;
}
private String getProperty(String name, String def) {
String val = this.environment.get(name);
if (val != null)
return val;
val = System.getProperty(name);
return val != null ? val : def;
}
private String getProperty(String name) {
return this.getProperty(name, null);
}
private String getEnv(String name, String def) {
String val = this.environment.get(name);
if (val != null)
return val;
val = System.getenv(name);
return val != null ? val : def;
}
private String getEnv(String name) {
return this.getEnv(name, null);
}
public Path getUserSystemHomePath() {
String home = this.getProperty("user.home");
return Paths.get(home).toAbsolutePath();
}
private String replaceMavenVars(String raw) {
StringBuilder replaced = new StringBuilder();
Matcher matcher = MAVEN_VAR_PATTERN.matcher(raw);
while (matcher.find())
matcher.appendReplacement(replaced,
System.getProperty(matcher.group("name"), ""));
matcher.appendTail(replaced);
return replaced.toString();
}
// Thanks gradle!
private Path getUserHomePath() {
return this.getUserSystemHomePath().resolve(".m2");
}
private Path getGlobalHomePath() {
String envM2Home = this.getEnv("M2_HOME");
return envM2Home != null
? Paths.get(envM2Home).toAbsolutePath() : null;
}
private Path getUserSettingsPath() {
return this.getUserHomePath().resolve("settings.xml");
}
private Path getGlobalSettingsPath() {
Path sysHome = this.getGlobalHomePath();
return sysHome != null ? sysHome.resolve("conf").resolve("settings.xml") : null;
}
private Path getDefaultLocalRepoPath() {
return this.getUserHomePath().resolve("repository");
}
private Path readConfiguredLocalRepositoryPath(Path settingsXmlPath) throws IOException, SAXException {
if (!Files.isRegularFile(settingsXmlPath))
return null;
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setValidating(false);
DocumentBuilder builder;
try {
builder = factory.newDocumentBuilder();
} catch (ParserConfigurationException e) {
// We are configuring the factory, the configuration will be fine...
e.printStackTrace();
return null;
}
try (InputStream in = Files.newInputStream(settingsXmlPath)) {
Document settingsDoc = builder.parse(in);
NodeList settings = settingsDoc.getElementsByTagName("settings");
if (settings.getLength() == 0)
return null;
for (int i = 0; i < settings.getLength(); i++) {
Node setting = settings.item(i);
switch (setting.getNodeName()) {
case "localRepository":
String localRepository = setting.getTextContent();
localRepository = this.replaceMavenVars(localRepository);
return Paths.get(localRepository);
}
}
}
return null;
}
// TODO just use the effective settings
public Path getConfiguredLocalRepositoryPath() throws IOException, SAXException {
Path userSettingsXmlPath = this.getUserSettingsPath();
Path path = this.readConfiguredLocalRepositoryPath(userSettingsXmlPath);
if (path == null) {
Path globalSettingsXmlPath = this.getGlobalSettingsPath();
if (globalSettingsXmlPath != null)
path = this.readConfiguredLocalRepositoryPath(globalSettingsXmlPath);
}
return path == null ? this.getDefaultLocalRepoPath() : path;
}
/*public SettingsBuildingResult readEffectiveSettings() throws SettingsBuildingException {
DefaultSettingsBuilder settingsBuilder = new DefaultSettingsBuilderFactory().newInstance();
SettingsBuildingRequest request = new DefaultSettingsBuildingRequest();
request.setSystemProperties(System.getProperties());
request.setUserProperties(this.properties);
request.setUserSettingsFile(this.getUserSettingsPath().toFile());
Path globalSettingsPath = this.getGlobalSettingsPath();
request.setGlobalSettingsFile(globalSettingsPath != null ? globalSettingsPath.toFile() : null);
return settingsBuilder.build(request);
}*/
public ModelBuildingResult readEffectiveModel(CharSequence pom) throws ModelBuildingException {
return this.readEffectiveModel(req ->
req.setModelSource((ModelSource) new StringSource(pom))
);
}
public ModelBuildingResult readEffectiveModel(File pom) throws ModelBuildingException {
return this.readEffectiveModel(req ->
req.setPomFile(pom)
);
}
private ModelBuildingResult readEffectiveModel(Function<ModelBuildingRequest, ModelBuildingRequest> configuration) throws ModelBuildingException {
DefaultModelBuilder modelBuilder = new DefaultModelBuilderFactory().newInstance();
ModelBuildingRequest request = new DefaultModelBuildingRequest();
request.setSystemProperties(System.getProperties());
request.setUserProperties(this.properties);
// Allow force selection of active profile
// request.setActiveProfileIds()
// request.setInactiveProfileIds()
// Better error messages for bad poms
request.setLocationTracking(true);
// Don't run plugins, in most cases this is what we want. I don't know of any
// that would affect the POM.
request.setProcessPlugins(false);
request = configuration.apply(request);
return modelBuilder.build(request);
}
}

View File

@ -1,42 +0,0 @@
package io.github.spencerpark.ijava.magics.dependencies;
import org.apache.ivy.plugins.resolver.ChainResolver;
import org.apache.ivy.plugins.resolver.DependencyResolver;
import org.apache.maven.model.Model;
import org.apache.maven.model.Repository;
import org.apache.maven.model.building.ModelBuildingException;
import java.io.File;
import java.util.List;
import java.util.stream.Collectors;
public class MavenToIvy {
public static List<DependencyResolver> getRepositoriesFromModel(CharSequence pom) throws ModelBuildingException {
return MavenToIvy.getRepositoriesFromModel(Maven.getInstance().readEffectiveModel(pom).getEffectiveModel());
}
public static List<DependencyResolver> getRepositoriesFromModel(File pom) throws ModelBuildingException {
return MavenToIvy.getRepositoriesFromModel(Maven.getInstance().readEffectiveModel(pom).getEffectiveModel());
}
public static List<DependencyResolver> getRepositoriesFromModel(Model model) {
return model.getRepositories().stream()
.map(MavenToIvy::convertRepository)
.collect(Collectors.toList());
}
public static DependencyResolver convertRepository(Repository repository) {
return CommonRepositories.maven(repository.getId(), repository.getUrl());
}
public static ChainResolver createChainForModelRepositories(Model model) {
ChainResolver resolver = new ChainResolver();
// Maven central is always an implicit repository.
resolver.add(CommonRepositories.mavenCentral());
MavenToIvy.getRepositoriesFromModel(model).forEach(resolver::add);
return resolver;
}
}

View File

@ -1,90 +0,0 @@
package io.github.spencerpark.ijava.runtime;
import io.github.spencerpark.ijava.JavaKernel;
import io.github.spencerpark.jupyter.kernel.display.DisplayData;
import java.util.UUID;
public class Display {
public static DisplayData render(Object o) {
JavaKernel kernel = Kernel.getKernelInstance();
if (kernel != null) {
return kernel.getRenderer().render(o);
} else {
throw new RuntimeException("No IJava kernel running");
}
}
public static DisplayData render(Object o, String... as) {
JavaKernel kernel = Kernel.getKernelInstance();
if (kernel != null) {
return kernel.getRenderer().renderAs(o, as);
} else {
throw new RuntimeException("No IJava kernel running");
}
}
public static String display(Object o) {
JavaKernel kernel = Kernel.getKernelInstance();
if (kernel != null) {
DisplayData data = kernel.getRenderer().render(o);
String id = data.getDisplayId();
if (id == null) {
id = UUID.randomUUID().toString();
data.setDisplayId(id);
}
kernel.display(data);
return id;
} else {
throw new RuntimeException("No IJava kernel running");
}
}
public static String display(Object o, String... as) {
JavaKernel kernel = Kernel.getKernelInstance();
if (kernel != null) {
DisplayData data = kernel.getRenderer().renderAs(o, as);
String id = data.getDisplayId();
if (id == null) {
id = UUID.randomUUID().toString();
data.setDisplayId(id);
}
kernel.display(data);
return id;
} else {
throw new RuntimeException("No IJava kernel running");
}
}
public static void updateDisplay(String id, Object o) {
JavaKernel kernel = Kernel.getKernelInstance();
if (kernel != null) {
DisplayData data = kernel.getRenderer().render(o);
kernel.getIO().display.updateDisplay(id, data);
} else {
throw new RuntimeException("No IJava kernel running");
}
}
public static void updateDisplay(String id, Object o, String... as) {
JavaKernel kernel = Kernel.getKernelInstance();
if (kernel != null) {
DisplayData data = kernel.getRenderer().renderAs(o, as);
kernel.getIO().display.updateDisplay(id, data);
} else {
throw new RuntimeException("No IJava kernel running");
}
}
}

View File

@ -1,20 +0,0 @@
package io.github.spencerpark.ijava.runtime;
import io.github.spencerpark.ijava.IJava;
import io.github.spencerpark.ijava.JavaKernel;
public class Kernel {
public static JavaKernel getKernelInstance() {
return IJava.getKernelInstance();
}
public static Object eval(String expr) throws Exception {
JavaKernel kernel = getKernelInstance();
if (kernel != null) {
return kernel.evalRaw(expr);
} else {
throw new RuntimeException("No IJava kernel running");
}
}
}

View File

@ -1,41 +0,0 @@
package io.github.spencerpark.ijava.runtime;
import io.github.spencerpark.ijava.IJava;
import io.github.spencerpark.ijava.JavaKernel;
import io.github.spencerpark.jupyter.kernel.magic.registry.UndefinedMagicException;
import java.util.List;
public class Magics {
public static <T> T lineMagic(String name, List<String> args) {
JavaKernel kernel = IJava.getKernelInstance();
if (kernel != null) {
try {
return kernel.getMagics().applyLineMagic(name, args);
} catch (UndefinedMagicException e) {
throw e;
} catch (Exception e) {
throw new RuntimeException(String.format("Exception occurred while running line magic '%s': %s", name, e.getMessage()), e);
}
} else {
throw new RuntimeException("No IJava kernel running");
}
}
public static <T> T cellMagic(String name, List<String> args, String body) {
JavaKernel kernel = IJava.getKernelInstance();
if (kernel != null) {
try {
return kernel.getMagics().applyCellMagic(name, args, body);
} catch (UndefinedMagicException e) {
throw e;
} catch (Exception e) {
throw new RuntimeException(String.format("Exception occurred while running cell magic '%s': %s", name, e.getMessage()), e);
}
} else {
throw new RuntimeException("No IJava kernel running");
}
}
}

View File

@ -0,0 +1,41 @@
io.github.spencerpark.jupyter.kernel.display.DisplayData render(Object o) {
io.github.spencerpark.ijava.JavaKernel kernel = io.github.spencerpark.ijava.IJava.getKernelInstance();
if (kernel != null) {
return kernel.getRenderer().render(o);
} else {
throw new RuntimeException("No IJava kernel running");
}
}
io.github.spencerpark.jupyter.kernel.display.DisplayData render(Object o, String... as) {
io.github.spencerpark.ijava.JavaKernel kernel = io.github.spencerpark.ijava.IJava.getKernelInstance();
if (kernel != null) {
return kernel.getRenderer().renderAs(o, as);
} else {
throw new RuntimeException("No IJava kernel running");
}
}
void display(Object o) {
io.github.spencerpark.ijava.JavaKernel kernel = io.github.spencerpark.ijava.IJava.getKernelInstance();
if (kernel != null) {
io.github.spencerpark.jupyter.kernel.display.DisplayData data = kernel.getRenderer().render(o);
kernel.display(data);
} else {
throw new RuntimeException("No IJava kernel running");
}
}
void display(Object o, String... as) {
io.github.spencerpark.ijava.JavaKernel kernel = io.github.spencerpark.ijava.IJava.getKernelInstance();
if (kernel != null) {
io.github.spencerpark.jupyter.kernel.display.DisplayData data = kernel.getRenderer().renderAs(o, as);
kernel.display(data);
} else {
throw new RuntimeException("No IJava kernel running");
}
}

View File

@ -0,0 +1,9 @@
Object eval(String expr) throws Exception {
io.github.spencerpark.ijava.JavaKernel kernel = io.github.spencerpark.ijava.IJava.getKernelInstance();
if (kernel != null) {
return kernel.evalRaw(expr);
} else {
throw new RuntimeException("No IJava kernel running");
}
}

View File

@ -6,10 +6,6 @@ import java.util.concurrent.*;
import java.util.prefs.*;
import java.util.regex.*;
import static io.github.spencerpark.ijava.runtime.Display.*;
import static io.github.spencerpark.ijava.runtime.Kernel.*;
import static io.github.spencerpark.ijava.runtime.Magics.*;
public void printf(String format, Object... args) {
System.out.printf(format, args);
}

View File

@ -0,0 +1,31 @@
<T> T lineMagic(String name, java.util.List<String> args) {
io.github.spencerpark.ijava.JavaKernel kernel = io.github.spencerpark.ijava.IJava.getKernelInstance();
if (kernel != null) {
try {
return kernel.getMagics().applyLineMagic(name, args);
} catch (io.github.spencerpark.jupyter.kernel.magic.registry.UndefinedMagicException e) {
throw e;
} catch (Exception e) {
throw new RuntimeException(String.format("Exception occurred while running line magic '%s': %s", name, e.getMessage()), e);
}
} else {
throw new RuntimeException("No IJava kernel running");
}
}
<T> T cellMagic(String name, java.util.List<String> args, String body) {
io.github.spencerpark.ijava.JavaKernel kernel = io.github.spencerpark.ijava.IJava.getKernelInstance();
if (kernel != null) {
try {
return kernel.getMagics().applyCellMagic(name, args, body);
} catch (io.github.spencerpark.jupyter.kernel.magic.registry.UndefinedMagicException e) {
throw e;
} catch (Exception e) {
throw new RuntimeException(String.format("Exception occurred while running cell magic '%s': %s", name, e.getMessage()), e);
}
} else {
throw new RuntimeException("No IJava kernel running");
}
}