Skip to content

Commit

Permalink
Merge pull request #115 from AlgebraicJulia/java-intertypes
Browse files Browse the repository at this point in the history
InterTypes export for Java
  • Loading branch information
olynch authored Apr 8, 2024
2 parents 6345d8d + a540be1 commit 6c42d9e
Show file tree
Hide file tree
Showing 20 changed files with 822 additions and 3 deletions.
82 changes: 79 additions & 3 deletions .github/workflows/julia_ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,86 @@ permissions:
statuses: read

jobs:
CI:
test:
if: github.event_name == 'pull_request' || github.event_name == 'push' || (github.event_name == 'workflow_dispatch' && inputs.action == 'test')
uses: AlgebraicJulia/.github/.github/workflows/julia_ci.yml@main
secrets: inherit
name: Julia ${{ matrix.version }} - ${{ matrix.os }} - ${{ matrix.arch }} - ${{ github.event_name }}
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix: # use matrix notation for linux for easily testing more versions/architectures
version: [ "1.10.0" ]
os: [ubuntu-latest]
arch: ["x64"]
include:
# test macOS and Windows with latest Julia only
- os: macOS-latest
arch: x64
version: "1.10.0"
- os: windows-latest
arch: x64
version: "1.10.0"

steps:
- uses: actions/checkout@v4
with:
submodules: true
- name: "Install Graphviz"
uses: ts-graphviz/setup-graphviz@v1
with:
ubuntu-skip-apt-update: "true"
macos-skip-brew-update: "true"
- name: "Set up Julia"
uses: julia-actions/setup-julia@v1
with:
version: ${{ matrix.version }}
arch: ${{ matrix.arch }}
- uses: julia-actions/cache@v1
- uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: '21'
cache: 'gradle'
- uses: julia-actions/julia-buildpkg@v1
- name: "Run tests (windows)"
uses: julia-actions/julia-runtest@v1
if: matrix.os == 'windows-latest'
- name: "Run tests"
uses: julia-actions/julia-runtest@v1
if: matrix.os != 'windows-latest'
env:
JULIA_COPY_STACKS: 1
- uses: julia-actions/julia-processcoverage@v1
- uses: codecov/codecov-action@v3
with:
token: ${{ secrets.CODECOV_TOKEN }}
docs:
name: Documentation
if: ${{ inputs.docs }}
runs-on: ubuntu-latest
env:
# GR backend fix for Plots, discussed in https://github.com/jheinen/GR.jl/issues/422
GKSwstype: nul
steps:
- uses: actions/checkout@v4
- name: "Set up Julia"
uses: julia-actions/setup-julia@v1
with:
version: "1.10.0"
- name: "Install Graphviz"
uses: ts-graphviz/setup-graphviz@v1
with:
ubuntu-skip-apt-update: "true"
macos-skip-brew-update: "true"
- name: "Install LaTeX"
run: |
sudo apt-get update
sudo apt-get install pdf2svg texlive-latex-base texlive-latex-extra texlive-binaries texlive-pictures texlive-luatex
- name: "Install Julia dependencies"
run: julia --project=docs -e 'using Pkg; Pkg.develop(PackageSpec(path=pwd())); Pkg.instantiate();'
- name: "Build and deploy docs"
run: julia --project=docs docs/make.jl
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
CompatHelper:
if: github.event_name == 'schedule'
uses: AlgebraicJulia/.github/.github/workflows/julia_compat.yml@main
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,4 @@ Manifest.toml
test/.CondaPkg
test/intertypes/__pycache__
test/intertypes/*.py
test/intertypes/java/lib/deps
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[submodule "test/intertypes/acsets4j"]
path = test/intertypes/acsets4j
url = [email protected]:AlgebraicJulia/acsets4j
1 change: 1 addition & 0 deletions src/intertypes/InterTypes.jl
Original file line number Diff line number Diff line change
Expand Up @@ -261,5 +261,6 @@ include("jsonschema.jl")
include("sexp.jl")
include("julia.jl")
include("python.jl")
include("java.jl")

end
241 changes: 241 additions & 0 deletions src/intertypes/java.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,241 @@
export JacksonTarget

struct JavaFile
name::String # without .java extension
content::String # without "package xxxx" at the top
end

function tojava(intertype::InterType)
@match intertype begin
I32 => "Integer"
U32 => "Integer"
I64 => "Long"
U64 => "Long"
F64 => "Double"
Boolean => "Boolean"
Str => "String"
Sym => "String"
Binary => "String"
OptionalType(elemtype) => "Optional<$(tojava(elemtype))>"
ObjectType(elemtype) => "LinkedHashMap<String, $(tojava(elemtype))>"
List(elemtype) => "ArrayList<$(tojava(elemtype))>"
Map(keytype, valuetype) => "LinkedHashMap<$(tojava(keytype)), $(tojava(elemtype))>"
Record(_) => error("no native record type for java")
Sum(_) => error("no native sum type for java")
Annot(_, type) => tojava(type)
TypeRef(to) => string(toexpr(to))
end
end

function separate(f, io, xs; separator=", ")
sep = false
for x in xs
if sep
print(io, separator)
end
sep = true
f(io, x)
end
end

RECORD_IMPORTS = """
import java.util.Optional;
import java.util.ArrayList;
import java.util.LinkedHashMap;
"""

function java_record!(io::IO, name, fields; implements=nothing, typename=false)
println(io, RECORD_IMPORTS);
if typename
println(io, "import com.fasterxml.jackson.annotation.JsonTypeName;")
println(io)
println(io, "@JsonTypeName(\"$(name)\")")
end
print(io, "public record ", name, "(")
separate(io, fields) do io, field
print(io, tojava(field.type), " ", field.name)
end
print(io, ")")
if !isnothing(implements)
print(io, " implements ", implements)
end
print(io, " {}")
end

function java_record(name, fields; implements=nothing, typename=false)
b = IOBuffer()
java_record!(b, name, fields; implements, typename)
JavaFile(string(name), String(take!(b)))
end

function java_sumtype_parent!(io, name, variants)
println(io, "import com.fasterxml.jackson.annotation.*;")
println(io, "import com.fasterxml.jackson.annotation.JsonTypeInfo.*;")
println(io)
println(io, "@JsonTypeInfo(use=Id.NAME, include=As.PROPERTY, property=\"_type\")")
println(io, "@JsonSubTypes({")
separate(io, variants; separator=",\n") do io, v
print(io, " @JsonSubTypes.Type(value = $(v.tag).class, name = \"$(v.tag)\")")
end
println(io, "\n})")
print(io, "public sealed interface ", name, " permits ")
separate(io, variants) do io, v
print(io, v.tag)
end
print(io, " {}")
end

function java_sumtype(name, variants)
parent_buffer = IOBuffer()
java_sumtype_parent!(parent_buffer, name, variants)
parentfile = JavaFile("$(name)", String(take!(parent_buffer)))
variantfiles = map(variants) do v
java_record(v.tag, v.fields; implements=name, typename=true)
end
[parentfile; variantfiles]
end

function java_list_literal(f, io, xs)
print(io, "new ArrayList<>(Arrays.asList(")
separate(f, io, xs)
print(io, "))")
end

function java_schema(name, schema::TypedSchema{Symbol, InterType})
io = IOBuffer()
println(io, """
import acsets4j.*;
import java.util.ArrayList;
import java.util.Arrays;
public class $name {
public static Schema schema = new Schema(
""")
java_list_literal(io, objects(schema)) do io, ob
print(io, "new Ob(\"$ob\")")
end
println(io, ",")
java_list_literal(io, homs(schema)) do io, (f, d, c)
print(io, "new Hom(\"$f\", \"$d\", \"$c\")")
end
println(io, ",")
java_list_literal(io, attrtypes(schema)) do io, T
print(io, "new AttrType(\"$T\", $(tojava(schema.typing[T])).class)")
end
println(io, ",")
java_list_literal(io, attrs(schema)) do io, (f, d, c)
print(io, "new Attr(\"$f\", \"$d\", \"$c\")")
end
println(io, ");\n}")
[JavaFile(string(name), String(take!(io)))]
end

function java_abstract_acset(name, parent)
parent = if !isnothing(parent)
parent
else
:ACSet
end
content = """
import acsets4j.*;
public abstract class $(name) extends $parent {}
"""
[JavaFile(string(name), content)]
end

function java_named_acset_type(name, spec)
abstract_type = if !isnothing(spec.abstract_type)
spec.abstract_type
else
:ACSet
end
serializername = string(name, "Serializer")
serializer_content = """
import acsets4j.*;
public class $serializername extends ACSetSerializer<$(name)> { }
"""
serializer = JavaFile(serializername, serializer_content)
deserializername = string(name, "Deserializer")
deserializer_content = """
import java.io.IOException;
import acsets4j.*;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationContext;
public class $deserializername extends ACSetDeserializer<$(name)> {
public $name deserialize(JsonParser jp, DeserializationContext ctxt)
throws IOException, JsonProcessingException {
$(name) acs = new $(name)();
deserializeInto(acs, jp, ctxt);
return acs;
}
}
"""
deserializer = JavaFile(deserializername, deserializer_content)
content = """
import acsets4j.*;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
@JsonSerialize(using = $(serializername).class)
@JsonDeserialize(using = $(deserializername).class)
public class $name extends $abstract_type {
public static Schema schema = $(spec.schemaname).schema;
@Override
public Schema schema() {
return schema;
}
}
"""
class = JavaFile(string(name), content)
[class, serializer, deserializer]
end

function tojava(name, decl::InterTypeDecl)
@match decl begin
Struct(fields) => [java_record(name, fields)]
SumType(variants) => java_sumtype(name, variants)
VariantOf(_) => []
SchemaDecl(schema) => java_schema(name, schema)
AbstractACSetType(parent) => java_abstract_acset(name, parent)
NamedACSetType(spec) => java_named_acset_type(name, spec)
_ => error("unsupported declaration for java: $decl")
end
end

"""
JacksonTarget
Targets the creation of a directory full of `.java` files that use the
Jackson JSON library.
"""
struct JacksonTarget <: LanguageTarget end

function generate_module(mod::InterTypeModule, ::Type{JacksonTarget}, path)
files = JavaFile[]
for (name, decl) in mod.declarations
append!(files, tojava(name, decl))
end
outdir = joinpath(path, string(mod.name))
mkpath(outdir)
for file in files
open(joinpath(outdir, file.name * ".java"), "w") do io
println(io, "package $(mod.name);")
println(io)
for (name, importedmod) in mod.imports
if name != importedmod.name
error("java does not support import aliasing")
end
end
print(io, file.content)
end
end
end
1 change: 1 addition & 0 deletions test/Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ CondaPkg = "992eb4ea-22a4-4c89-a5bb-47a3300528ab"
JSON = "682c06a0-de6a-54ab-a142-c8b1cf79cde6"
JSON3 = "0f8b85d8-7281-11e9-16c2-39a750bddbf1"
JSONSchema = "7d188eb4-7ad8-530c-ae41-71a32a6d4692"
JavaCall = "494afd89-becb-516b-aafa-70d2670c0337"
MLStyle = "d8e11817-5142-5d16-987a-aa16d5891078"
OrderedCollections = "bac558e1-5e72-5ebc-8fee-abe8a469f55d"
Permutations = "2ae35dd2-176d-5d53-8349-f30d82d94d4f"
Expand Down
Loading

0 comments on commit 6c42d9e

Please sign in to comment.