Giter VIP home page Giter VIP logo

jdraft's Introduction

jdraft

Java 8+ Apache License 2

What is it?

a tool to compose, inspect, query & mutate Java source code.

_null.of();              // a null literal
_int.of(1);              // a literal int 1
_expression.of("1 + 2"); // binary expression "1 + 2"
_statement.of("System.out.println(\"x=\"+x);");
_method.of("public void getX(){ return this.x; }");
_class.of("public class Point{ int x, y; }");

//model all of the java sources within a jar file
_archive.of("C:\\Users\\Eric\\Downloads\\guava-28.1-jre-sources.jar");

//model all of the java sources from a github project (head) & maven central sources
_project.of(
    _githubProject.of("https://github.com/org-jdraft/jdraft"),
    _mavenCentral.of("com.github.javaparser", "javaparser-core", "3.15.21")
);

Why?

Normally, Java source code is a String:

String srcCode = "public class Point{ double x; double y = 1.0; }"   

...but passing around Java source code as a String (to be manipulated by a program) is cumbersome.

Simple example: Normally, to write a program that accepts srcCode above as input to modify the types of fields (x & y) from double to float; this program has to:

  • parse the `srcCode` String to build a syntax tree (AST)
  • manipulate AST field `type` nodes in the tree to change to `float`
  • convert the tree back to a String

jdraft makes this type of metaprogramming task easy to write AND read:

// convert srcCode to _class, set all fields as float & return the code as a String 
srcCode = _class.of(srcCode).forFields(f-> f.setType(float.class)).toString();

jdraft is a simple to learn, read & use, and it allows you to build powerful tools when operating on codebases large or small; simple metaprograms can modify code you own & are familiar with or even code or don't "own":

// read in & model the jdraft github project sources 
// update the parameters on all methods for all types to be final
_project modified = _project.of(_githubProject.of("https://github.com/org-jdraft/jdraft"))
                            .forMethods(m-> m.forParameters(p-> p.setFinal()));
 
// compile the resulting source code and return the _classFiles (bytecode)
List<_classFile> _cfs = _runtime.compile( modified);

//write out the modified .java source code to 
_io.out( "C:\\modified\\src\\", _project );

//write out the compiled .class files
_io.out( "C:\\modified\\classes\\", _cfs);
  1. Metaprogramming
  2. Code Generation
  3. Code Inspection
  4. Code Querying
  5. Code Evolution
**_"more improv, less batch job"_**

Comparison tests for related tools:

How to setup and use jdraft

jdraft requires (2) things to compile/build/run:

  1. a JDK 1.8+ (not a JRE)
  2. a current version of javaparser-core
<dependency>
  <groupId>com.github.javaparser</groupId>
  <artifactId>javaparser-core</artifactId>
  <version>3.18.0</version>
</dependency>

How to build jdraft models

  1. build individual jdraft models _class(_c), _field(_x, _y), _method(_getX, _getY, _setX, _setY) from Strings & compose them together:
_class _c = _class.of("package graph;","public class Point{}");
_field _x = _field.of("public double x;");
_field _y = _field.of("public double y;");
_method _getX = _method.of("public double getX(){ return x; }");
_method _setX = _method.of("public void setX(double x){ this.x = x;}");
_method _getY = _method.of("public double getY(){ return y; }");
_method _setY = _method.of("public void setY(double y){ this.y = y;}");

// _draft models compose..add fields and methods to _c:
_c.fields(_x, _y).methods(_getX, _getY, _setX, _setY );

// toString() will return the source code 
System.out.println(_c);
package graph;

public class Point {
   public double x;
   public double y;
   public double getX() { return this.x; }
   public void setX(double x) { this.x = x; }
   public double getY() { return y; }
   public void setY(double y) {this.y = y; }
}
  1. build a jdraft model from source of an existing class (_class.of(Point.class))
    (NOTE: this more preferred mechanism to using Strings above, and it works for Top level Classes, Nested Classes, and Local Classes) :
class Point{
    public double x;
    public double y;
    public double getX() { return this.x; }
    public double getY() { return this.y; }
    public void setX(double x){ this.x = x; }
    public void setY(double y){ this.y = y; }
} 
_class _c = _class.of(Point.class);
  1. build _draft models from the source of an anonymous Object:
_class _c = _class.of("graph.Point", new Object(){
    public double x;
    public double y;
    public double getX(){
        return x;
    }  
    public void setX(double x){
        this.x = x;
    }
    public double getY(){
        return y;
    }  
    public void setY(double y){
        this.y = y;
    }
});
  1. build _draft models with @macros (@_get & @_set auto generate getX(),getY(),setX() & setY() methods)
_class _c = _class.of("graph.Point", 
    new @_get @_set Object(){ public double x,y;});

How to run (compile, load, eval) the _draft models/source at runtime

// the @_dto @macro will generate getX(),getY(),setX(),setY(), equals(), hashCode() & toString()
// methods as well as a no-arg constructor in the `_draft` model
_class _c = _class.of("graph.Point", new @_dto Object(){
     public double x,y;
     });
// add the distance method to `_point`
_point.method(new Object(){
    public double distanceTo( double x, double y ){
        return Math.sqrt((this.y - y) * (this.y - y) + (this.x - x) * (this.x - x));
    }
    @_remove double x, y;
});

//compile, load and use classes at runtime:
_runtime _r = _runtime.of(_c);

Query Java source code

jdraft's People

Contributors

edefazio avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

Forkers

doytsujin

jdraft's Issues

Print feature tree

Instead of just printing a tree of (unlabeled) nodes

package math.geometry;...}" _codeUnit : (1,1)-(34,3)
├─"package math.geometry;" _package : (1,1)-(1,22)
│ └─"math.geometry" _name : (1,9)-(1,21)
│   └─"math" _name : (1,9)-(1,12)
├─"import java.util.UUID;" _import : (3,1)-(3,22)
│ └─"java.util.UUID" _name : (3,8)-(3,21)
│   └─"java.util" _name : (3,8)-(3,16)
│     └─"java" _name : (3,8)-(3,11)
└─"public class Point {...}" _class : (5,1)-(34,1)
  ├─"public" _modifier : (5,1)-(5,6)
  ├─"Point" _name : (5,14)-(5,18)
  ├─"int x = 0, y = 0;" _field : (7,5)-(7,21)
  │ ├─"x = 0" _field : (7,9)-(7,13)
  │ │ ├─"int" _typeRef : (7,5)-(7,7)
  │ │ ├─"x" _name : (7,9)-(7,9)
  │ │ └─"0" _intExpr : (7,13)-(7,13)
  │ └─"y = 0" _field : (7,16)-(7,20)
  │   ├─"int" _typeRef : (7,5)-(7,7)
  │   ├─"y" _name : (7,16)-(7,16)
  │   └─"0" _intExpr : (7,20)-(7,20)
  ├─"public String toString() {...}" _method : (9,5)-(11,5)
  │ ├─"public" _modifier : (9,5)-(9,10)
  │ ├─"toString" _name : (9,19)-(9,26)
  │ ├─"String" _typeRef : (9,12)-(9,17)
  │ │ └─"String" _name : (9,12)-(9,17)
  │ └─"{...}" _blockStmt : (9,30)-(11,5)
  │   └─"return "(" + x + "," + y + ")";" _returnStmt : (10,9)-(10,39)
  │     └─""(" + x + "," + y + ")"" _binaryExpr : (10,16)-(10,38)
  │       ├─""(" + x + "," + y" _binaryExpr : (10,16)-(10,32)
  │       │ ├─""(" + x + ","" _binaryExpr : (10,16)-(10,28)
  │       │ │ ├─""(" + x" _binaryExpr : (10,16)-(10,22)
  │       │ │ │ ├─""("" _stringExpr : (10,16)-(10,18)
  │       │ │ │ └─"x" _nameExpr : (10,22)-(10,22)
  │       │ │ │   └─"x" _name : (10,22)-(10,22)
  │       │ │ └─"","" _stringExpr : (10,26)-(10,28)
  │       │ └─"y" _nameExpr : (10,32)-(10,32)
  │       │   └─"y" _name : (10,32)-(10,32)
  │       └─"")"" _stringExpr : (10,36)-(10,38)
  ├─"public int getX() {...}" _method : (13,5)-(15,5)
  │ ├─"public" _modifier : (13,5)-(13,10)
  │ ├─"getX" _name : (13,16)-(13,19)
  │ ├─"int" _typeRef : (13,12)-(13,14)
  │ └─"{...}" _blockStmt : (13,23)-(15,5)
  │   └─"return x;" _returnStmt : (14,9)-(14,17)
  │     └─"x" _nameExpr : (14,16)-(14,16)
  │       └─"x" _name : (14,16)-(14,16)
  ├─"public void setX(final int x) {...}" _method : (17,5)-(19,5)
  │ ├─"public" _modifier : (17,5)-(17,10)
  │ ├─"setX" _name : (17,17)-(17,20)
  │ ├─"final int x" _param : (17,22)-(17,32)
  │ │ ├─"final" _modifier : (17,22)-(17,26)
  │ │ ├─"int" _typeRef : (17,28)-(17,30)
  │ │ └─"x" _name : (17,32)-(17,32)
  │ ├─"void" _typeRef : (17,12)-(17,15)
  │ └─"{...}" _blockStmt : (17,35)-(19,5)
  │   └─"this.x = x;" _exprStmt : (18,9)-(18,19)
  │     └─"this.x = x" _assignExpr : (18,9)-(18,18)
  │       ├─"this.x" _fieldAccessExpr : (18,9)-(18,14)
  │       │ ├─"this" _thisExpr : (18,9)-(18,12)
  │       │ └─"x" _name : (18,14)-(18,14)
  │       └─"x" _nameExpr : (18,18)-(18,18)
  │         └─"x" _name : (18,18)-(18,18)
  ├─"public int getY() {...}" _method : (21,5)-(23,5)
  │ ├─"public" _modifier : (21,5)-(21,10)
  │ ├─"getY" _name : (21,16)-(21,19)
  │ ├─"int" _typeRef : (21,12)-(21,14)
  │ └─"{...}" _blockStmt : (21,23)-(23,5)
  │   └─"return y;" _returnStmt : (22,9)-(22,17)
  │     └─"y" _nameExpr : (22,16)-(22,16)
  │       └─"y" _name : (22,16)-(22,16)
  ├─"public void setY(final int y) {...}" _method : (25,5)-(27,5)
  │ ├─"public" _modifier : (25,5)-(25,10)
  │ ├─"setY" _name : (25,17)-(25,20)
  │ ├─"final int y" _param : (25,22)-(25,32)
  │ │ ├─"final" _modifier : (25,22)-(25,26)
  │ │ ├─"int" _typeRef : (25,28)-(25,30)
  │ │ └─"y" _name : (25,32)-(25,32)
  │ ├─"void" _typeRef : (25,12)-(25,15)
  │ └─"{...}" _blockStmt : (25,35)-(27,5)
  │   └─"this.y = y;" _exprStmt : (26,9)-(26,19)
  │     └─"this.y = y" _assignExpr : (26,9)-(26,18)
  │       ├─"this.y" _fieldAccessExpr : (26,9)-(26,14)
  │       │ ├─"this" _thisExpr : (26,9)-(26,12)
  │       │ └─"y" _name : (26,14)-(26,14)
  │       └─"y" _nameExpr : (26,18)-(26,18)
  │         └─"y" _name : (26,18)-(26,18)
  ├─"public static final String ID = UUID.randomUUID().toString();" _field : (29,5)-(29,65)
  │ ├─"public" _modifier : (29,5)-(29,10)
  │ ├─"static" _modifier : (29,12)-(29,17)
  │ ├─"final" _modifier : (29,19)-(29,23)
  │ └─"ID = UUID.randomUUID().toString()" _field : (29,32)-(29,64)
  │   ├─"String" _typeRef : (29,25)-(29,30)
  │   │ └─"String" _name : (29,25)-(29,30)
  │   ├─"ID" _name : (29,32)-(29,33)
  │   └─"UUID.randomUUID().toString()" _methodCallExpr : (29,37)-(29,64)
  │     ├─"UUID.randomUUID()" _methodCallExpr : (29,37)-(29,53)
  │     │ ├─"UUID" _nameExpr : (29,37)-(29,40)
  │     │ │ └─"UUID" _name : (29,37)-(29,40)
  │     │ └─"randomUUID" _name : (29,42)-(29,51)
  │     └─"toString" _name : (29,55)-(29,62)
  └─"public int hashCode() {...}" _method : (31,5)-(33,5)
    ├─"public" _modifier : (31,5)-(31,10)
    ├─"hashCode" _name : (31,16)-(31,23)
    ├─"int" _typeRef : (31,12)-(31,14)
    └─"{...}" _blockStmt : (31,27)-(33,5)
      └─"return 31 * x * y * 31;" _returnStmt : (32,9)-(32,31)
        └─"31 * x * y * 31" _binaryExpr : (32,16)-(32,30)
          ├─"31 * x * y" _binaryExpr : (32,16)-(32,25)
          │ ├─"31 * x" _binaryExpr : (32,16)-(32,21)
          │ │ ├─"31" _intExpr : (32,16)-(32,17)
          │ │ └─"x" _nameExpr : (32,21)-(32,21)
          │ │   └─"x" _name : (32,21)-(32,21)
          │ └─"y" _nameExpr : (32,25)-(32,25)
          │   └─"y" _name : (32,25)-(32,25)
          └─"31" _intExpr : (32,29)-(32,30)

Print a tree where the "edges" are ordered & labeled by feature name
(NOTE: collapse _nameExpr & _name)

package math.geometry;...}" _codeUnit : (1,1)-(34,3)
├─"package math.geometry;" _package : (1,1)-(1,22)
│ └─"math.geometry" _name : (1,9)-(1,21)
├─"import java.util.UUID;" _import : (3,1)-(3,22)
│ └─"java.util.UUID" _name : (3,8)-(3,21)
└─"public class Point {...}" _class : (5,1)-(34,1)
  ├─"public" _modifier : (5,1)-(5,6)
  ├─"Point" _name : (5,14)-(5,18)
  ├─"int x = 0, y = 0;" _field : (7,5)-(7,21)
  │ ├─"x = 0" _field : (7,9)-(7,13)
  │ │ ├─"int" _typeRef : (7,5)-(7,7)
  │ │ ├─"x" _name : (7,9)-(7,9)
  │ │ └─"0" _intExpr : (7,13)-(7,13)
  │ └─"y = 0" _field : (7,16)-(7,20)
  │   ├─"int" _typeRef : (7,5)-(7,7)
  │   ├─"y" _name : (7,16)-(7,16)
  │   └─"0" _intExpr : (7,20)-(7,20)
  ├─"public String toString() {...}" _method : (9,5)-(11,5)
  │ ├─"public" _modifier : (9,5)-(9,10)
  │ ├─"toString" _name : (9,19)-(9,26)
  │ ├─"String" _typeRef : (9,12)-(9,17)
  │ │ └─"String" _name : (9,12)-(9,17)
  │ └─"{...}" _blockStmt : (9,30)-(11,5)
  │   └─"return "(" + x + "," + y + ")";" _returnStmt : (10,9)-(10,39)
  │     └─""(" + x + "," + y + ")"" _binaryExpr : (10,16)-(10,38)
  │       ├─""(" + x + "," + y" _binaryExpr : (10,16)-(10,32)
  │       │ ├─""(" + x + ","" _binaryExpr : (10,16)-(10,28)
  │       │ │ ├─""(" + x" _binaryExpr : (10,16)-(10,22)
  │       │ │ │ ├─""("" _stringExpr : (10,16)-(10,18)
  │       │ │ │ └─"x" _nameExpr : (10,22)-(10,22)
  │       │ │ └─"","" _stringExpr : (10,26)-(10,28)
  │       │ └─"y" _nameExpr : (10,32)-(10,32)
  │       └─"")"" _stringExpr : (10,36)-(10,38)
  ├─"public int getX() {...}" _method : (13,5)-(15,5)
  │ ├─"public" _modifier : (13,5)-(13,10)
  │ ├─"getX" _name : (13,16)-(13,19)
  │ ├─"int" _typeRef : (13,12)-(13,14)
  │ └─"{...}" _blockStmt : (13,23)-(15,5)
  │   └─"return x;" _returnStmt : (14,9)-(14,17)
  │     └─"x" _nameExpr : (14,16)-(14,16)
  ├─"public void setX(final int x) {...}" _method : (17,5)-(19,5)
  │ ├─"public" _modifier : (17,5)-(17,10)
  │ ├─"setX" _name : (17,17)-(17,20)
  │ ├─"final int x" _param : (17,22)-(17,32)
  │ │ ├─"final" _modifier : (17,22)-(17,26)
  │ │ ├─"int" _typeRef : (17,28)-(17,30)
  │ │ └─"x" _name : (17,32)-(17,32)
  │ ├─"void" _typeRef : (17,12)-(17,15)
  │ └─"{...}" _blockStmt : (17,35)-(19,5)
  │   └─"this.x = x;" _exprStmt : (18,9)-(18,19)
  │     └─"this.x = x" _assignExpr : (18,9)-(18,18)
  │       ├─"this.x" _fieldAccessExpr : (18,9)-(18,14)
  │       │ ├─"this" _thisExpr : (18,9)-(18,12)
  │       │ └─"x" _name : (18,14)-(18,14)
  │       └─"x" _nameExpr : (18,18)-(18,18)
  ├─"public int getY() {...}" _method : (21,5)-(23,5)
  │ ├─"public" _modifier : (21,5)-(21,10)
  │ ├─"getY" _name : (21,16)-(21,19)
  │ ├─"int" _typeRef : (21,12)-(21,14)
  │ └─"{...}" _blockStmt : (21,23)-(23,5)
  │   └─"return y;" _returnStmt : (22,9)-(22,17)
  │     └─"y" _nameExpr : (22,16)-(22,16)
  ├─"public void setY(final int y) {...}" _method : (25,5)-(27,5)
  │ ├─"public" _modifier : (25,5)-(25,10)
  │ ├─"setY" _name : (25,17)-(25,20)
  │ ├─"final int y" _param : (25,22)-(25,32)
  │ │ ├─"final" _modifier : (25,22)-(25,26)
  │ │ ├─"int" _typeRef : (25,28)-(25,30)
  │ │ └─"y" _name : (25,32)-(25,32)
  │ ├─"void" _typeRef : (25,12)-(25,15)
  │ └─"{...}" _blockStmt : (25,35)-(27,5)
  │   └─"this.y = y;" _exprStmt : (26,9)-(26,19)
  │     └─"this.y = y" _assignExpr : (26,9)-(26,18)
  │       ├─"this.y" _fieldAccessExpr : (26,9)-(26,14)
  │       │ ├─"this" _thisExpr : (26,9)-(26,12)
  │       │ └─"y" _name : (26,14)-(26,14)
  │       └─"y" _nameExpr : (26,18)-(26,18)
  ├─"public static final String ID = UUID.randomUUID().toString();" _field : (29,5)-(29,65)
  │ ├─"public" _modifier : (29,5)-(29,10)
  │ ├─"static" _modifier : (29,12)-(29,17)
  │ ├─"final" _modifier : (29,19)-(29,23)
  │ └─"ID = UUID.randomUUID().toString()" _field : (29,32)-(29,64)
  │   ├─"String" _typeRef : (29,25)-(29,30)
  │   │ └─"String" _name : (29,25)-(29,30)
  │   ├─"ID" _name : (29,32)-(29,33)
  │   └─"UUID.randomUUID().toString()" _methodCallExpr : (29,37)-(29,64)
  │     ├─"UUID.randomUUID()" _methodCallExpr : (29,37)-(29,53)
  │     │ ├─"UUID" _nameExpr : (29,37)-(29,40)
  │     │ └─"randomUUID" _name : (29,42)-(29,51)
  │     └─"toString" _name : (29,55)-(29,62)
  └─"public int hashCode() {...}" _method : (31,5)-(33,5)
    ├─"public" _modifier : (31,5)-(31,10)
    ├─"hashCode" _name : (31,16)-(31,23)
    ├─"int" _typeRef : (31,12)-(31,14)
    └─"{...}" _blockStmt : (31,27)-(33,5)
      └─"return 31 * x * y * 31;" _returnStmt : (32,9)-(32,31)
        └─"31 * x * y * 31" _binaryExpr : (32,16)-(32,30)
          ├─"31 * x * y" _binaryExpr : (32,16)-(32,25)
          │ ├─"31 * x" _binaryExpr : (32,16)-(32,21)
          │ │ ├─"31" _intExpr : (32,16)-(32,17)
          │ │ └─"x" _nameExpr : (32,21)-(32,21)
          │ └─"y" _nameExpr : (32,25)-(32,25)
          └─"31" _intExpr : (32,29)-(32,30)

Stringified XML config (reading and writing) for Provenance (& reloading)

in order to have provenance of where the source code was read from (and potentially to reread the same source from the location) I want to

BOTH write out an "xml type" explanation from _batch implementations:
<org.jdraft.io._path>C:\temp\code</org.jdraft.io._path>
<org.jdraft.io._archive>C:\temp\src-jars\javaparser-3.16.0-sources.jar</org.jdraft.io._archive>
...same for github
mavenCentral

this way I can "load" or reload the project based on these... (because I have the className and the information for how to read the data)

Succinct Data Structure Representation of _type

This is a long-term goal---
A tool to take existing _types and _members, and convert them into a succinct data structure
or get a succinct data structure and turn it into a _type or _member.

the bidirectional nature succinct data structure to represent _types (_class, _enum, _interface, _annotation)
AND underlying _members (_initBlock, _method, _constructor, etc.)

  • is READ-ONLY
  • NO code formatting
  • can be iterated over (just like a _class.forMethods(m->... )) NOT MUTATED
  • can be walked into (like `Walk.listIn(_sc, Expression.class, e-> out.print(e)) )
  • can return "realized" i.e. return objects (_class, _method,_field) at any nest level

Internally I imagine it'll be similar to bytecode with bytes representing opcodes
and linking to names of things in a Lookup table

the purpose of this, is to make looking through code more memory efficient
(i.e. I should be able to take TONS of code like the source code of Linix) and
query it easily.

Looking through ALL code in a project should be fast & memory efficient
(we'll have probably MULTIPLE INDEXES outside of these types that provide information about the Class internals to speed up queries (i.e. feature hashing and or bloom filters ) and internally
we'll be able to load and sequentially walk the data structure performing analysis and transformations

more info on succinct data structures.
Succinct Data Structure
Feature Hashing
Bloom Filter

Internal specifications capable to synthetically create Java code (for testing/fuzzing)

Each _node type needs to have enough detail in their feature specification to be able to synthesize "random" syntactically correct instances... (we arent worried about uniformly distributed)
for example I should be able to do something like this:

//build & return a new if statement 
_ifStmt _i = Synthesizer.of(_ifStmt.class, seed);

//some example synthesized ifs might be:

//1) an if statement with no curlys (& single statement)
    if(true) System.out.println(1);

//2) an if statement with a body:
   if(call()){
       assert(1==1);
       return 3;
   }
//3) an if statement with else
   if( a & b ){
        return j;
   }else{
        return k;
   }
...etc. etc.

(Or to put it plainly, the specification needs to be rigid enough that I can "iterate" over each feature and synthesize a value that can be combined into syntactically correct instances that can be converted into Java code.)

code synthesis is both a "test" of the specification, to ensure that the interdependencies between elements are modeled, and a way of testing/fuzzing the parsers.

(the synthesized code may not compile, but should be parseable)

the actual implementation of the Synthesier is more about refining the specification than it really is about the synthesis of code

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.