Code Monkey home page Code Monkey logo

java-objective-c-bridge's Introduction

Java-Objective-C-Bridge

Synopsis

A thin bridge that allows for two-way communication from Java to Objective-C.

License

Apache 2.0 License

Requirements

  1. Java 11 or Higher on OS X
  2. JNA

Getting Started

Add the following dependency to your pom.xml:

    <dependency>
        <groupId>ca.weblite</groupId>
        <artifactId>java-objc-bridge</artifactId>
        <version>1.2</version>
    </dependency>

Working with Sources

  1. Check out the project and use mvn clean install (for debug builds) or mvn clean install -Drelease=true (for release builds) to build it
  2. Include it as a Maven depencency in your project:
    <dependency>
        <groupId>ca.weblite</groupId>
        <artifactId>java-objc-bridge</artifactId>
        <version>1.3-SNAPSHOT</version>
    </dependency>

Examples

JavaDocs & Documentation

Contact

Credits

Created by Steve Hannah

java-objective-c-bridge's People

Contributors

andriy-gerasika avatar marcono1234 avatar milend avatar mojo2012 avatar shannah avatar tobium avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

java-objective-c-bridge's Issues

rocococa-runtime dependency

The pom file includes a dependency for rococoa-runtime. What is that for? How heavy is it? And could it be reasonably eliminated without crippling the lib?

@mojo2012

Client.getInstance() and getRawClient() do not perform safe lazy initialization

The static methods Client.getInstance() and getRawClient() do not perform safe lazy initialization:

public static Client getInstance(){
if ( instance == null ){
instance = new Client();
}
return instance;
}

To correct this perform these steps:

  1. Make instance and rawClient volatile
  2. In the access methods, do double checked locking:
    1. Check if instance == null (for increased performance store current value in local var)
    2. If null:
      1. Synchronize
      2. Check again if instance == null (and overwrite local var, see 2.1)
      3. If null: Initialize (and update local var, see 2.1)
    3. Return instance (or local var) value

E.g.:

private static volatile Client instance;

public Client getInstance() {
    Client localInstance = instance;
    if (localInstance == null) {
        // Could also use dedicated lock for `instance` and `rawClient`, but likely not worth it
        synchronized (Client.class) {
            localInstance = instance;
            if (localInstance == null) {
                instance = localInstance = new Client();
            }
        }
    }
    return localInstance;
}

Or alternatively making these methods synchronized would also suffice and the performance decrease (if any) would likely be negligible.
See also "LCK10-J. Use a correct form of the double-checked locking idiom"

For sanity I would also recommend:

  • Implementing a private constructor, currently Client has the public default constructor
  • Making fields coerceInputs and coerceOutputs final and initializing them from the constructor

NSOpenPanel Swing/Cocoa deadlock

If you're calling dispatch_sync there's a chance that it'll freeze the application, especially on the high-end MBPs with dedicated GPU and automatic graphics switching. I guess the Swing/Cocoa EDTs somehow end up waiting for each other, in certain rare cases.

The problem can be solved by using Cocoa dispatch_async and AWT SecondaryLoop instead:
https://docs.oracle.com/javase/8/docs/api/java/awt/SecondaryLoop.html

SecondaryLoop secondaryLoop = Toolkit.getDefaultToolkit().getSystemEventQueue().createSecondaryLoop();

dispatch_async(() -> {
    // do cocoa stuff

    secondaryLoop.exit();
});

// wait for cocoa
secondaryLoop.enter();

There goes a few sleepless nights... I hope this helps someone!

Remove NSObject.methodMap or make it thread-safe

The field NSObject.methodMap is of type HashMap and is accessed without any synchronization:

private static Map<Class,Map<String,Method>> methodMap = new HashMap<Class,Map<String,Method>>();

However, if access it correctly synchronized it would still be a memory leak because the map would constantly grow and is never cleared.
Would it maybe make sense to make this an instance field (i.e. remove static)? Though I am not very familiar with this project and what performance implications this would have.

ClassNotFoundException: ca.weblite.objc.RuntimeMappings$Runtime

I have an objc mapping that looks like this:

public static Object NSURL_URLByResolvingBookmarkData_startAccessingSecurityScopedResource(String text) {
		return Client.getInstance().sendProxy("NSURL", "URLByResolvingBookmarkData:options:relativeToURL:bookmarkDataIsStale:error:", NSData_initWithBase64Encoding(text), 1024, null, false, null).send("startAccessingSecurityScopedResource");
}

But with the latest version of objc I get this issue, and indeed RuntimeMappings does not contain a Runtime (without number) class, but I'm not quite sure what's going on, since the code compiles but doesn't work at runtime:

java.lang.ClassNotFoundException: ca.weblite.objc.RuntimeMappings$Runtime
	at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:581)
	at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:178)
	at ca.weblite.objc.RuntimeUtils.objc_msgSend(RuntimeUtils.java:315)
	at ca.weblite.objc.RuntimeUtils.msg(RuntimeUtils.java:351)
	at ca.weblite.objc.RuntimeUtils.msg(RuntimeUtils.java:790)
	at ca.weblite.objc.Client.send(Client.java:161)
	at ca.weblite.objc.Client.sendProxy(Client.java:354)
	at ca.weblite.objc.Client.sendProxy(Client.java:381)
	at net.filebot.platform.mac.MacAppUtilities.NSURL_URLByResolvingBookmarkData_startAccessingSecurityScopedResource(MacAppUtilities.java:69)
...

Remove logging in native code

The native code uses logging (NSLog) quite extensively. I am not familiar with Objective-C, but the documentation for this function says:

Logs an error message to the Apple System Log facility.

However, it is currently used for debug logging as well. And it is used in cases where a Java exception is thrown anyways so logging seems redundant (instead the exception message should be improved, if necessary).

Therefore I would recommend completely removing logging.

Note: It appears the build process was slightly incorrect, using an outdated libjcocoa.dylib, see 8266cd9. Therefore you might not have seen logging output yet.

Remove redundant primitive boxing in RuntimeUtils.getAsReferenceWrapper(...)

RuntimeUtils.getAsReferenceWrapper(...) contains multiple redundant boxing and unboxing calls, e.g.:

val = new Double(Double.parseDouble((String)val)).doubleValue();

There is no need to call new Double(...).doubleValue(); same applies to all other primitive parsing methods used in that class.
However, since is most cases you are assigning the result to an Object variable, it would be easiest to replace the parseX calls with the valueOf​(String) variants (e.g. Double.valueOf(String)) which already return a boxed value:

- val = new Double(Double.parseDouble((String)val)).doubleValue();
+ val = Double.valueOf((String) val);

Additionally the code checks !primitive.class.isInstance(val) and then performs val = ((Number) val).primitiveValue(), e.g.:


This performs unnecessary unboxing for boxed primitives, e.g. Integer, because val is of type Object and therefore Java is automatically boxing the primitive again:
Integer -> int -> Integer

Map basic objects like NSRect

I'm trying to show a simple empty window on screen - but right now I'm totally stuck calling NSWindow.initWithContentRect:styleMask:backing:defer:. I think the problem is my NSRect implementation:

public class NSRect extends Structure implements Structure.ByValue {
    public NSPoint origin;
    public NSSize size;

    ...

    @Override
	protected List<String> getFieldOrder() {
		return Arrays.asList("origin", "size");
	}
}

I have no clue what goes wrong here, but this is the stack:

2019-06-02 22:03:22.641 java[53279:2512944] *** +[WLJavaProxy restorableStateKeyPaths]: unrecognized selector sent to class 0x1143803b8
2019-06-02 22:03:22.645 java[53279:2512944] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** +[WLJavaProxy restorableStateKeyPaths]: unrecognized selector sent to class 0x1143803b8'
*** First throw call stack:
(
	0   CoreFoundation                      0x00007fff41da9cfd __exceptionPreprocess + 256
	1   libobjc.A.dylib                     0x00007fff6c44fa17 objc_exception_throw + 48
	2   Foundation                          0x00007fff44123639 +[NSProxy init] + 0
	3   CoreFoundation                      0x00007fff41d4bb8f ___forwarding___ + 1485
	4   CoreFoundation                      0x00007fff41d4b538 _CF_forwarding_prep_0 + 120
	5   AppKit                              0x00007fff3f3629e8 -[NSResponder(NSPersistentUISupport) _changePersistentKeyPathObservationTo:] + 48
	6   jna17962736570655425700.tmp         0x000000011443de74 ffi_call_unix64 + 76
	7   ???                                 0x00007ffee8311758 0x0 + 140732793952088
)
libc++abi.dylib: terminating with uncaught exception of type NSException

It would be nice to have basic types already mapped!

Furthermore, it would be awesome to have better logging in case something goes wrong, like what are the selector arguments that were passed (on the objc-side).

Thanks for any help.

Problems updating to Java 11

Hi there,

I'm currently trying to update a project to Java 11, but ran into issues I can't solve by myself.

In the Objective-C code, I have a class containing a property defined as:

@property (readonly, strong) NSString *identifier;

This is how the bridging code in Java looks like:

public String getId() { return proxy.sendString("identifier"); }

While calling this method in Java 8 works perfectly, it throws an exception in Java 11:

Exception in thread "main" java.lang.ClassCastException: class ca.weblite.objc.Proxy cannot be cast to class java.lang.String (ca.weblite.objc.Proxy is in unnamed module of loader 'app'; java.lang.String is in module java.base of loader 'bootstrap') at ca.weblite.objc.Proxy.sendString(Proxy.java:229) at ca.weblite.objc.Proxy.sendString(Proxy.java:233) at net.test.someproject.SomeClass.getId(ObjCCommand.java:22) at net.test.someotherproject.TestMain.main(TestMain.java:275)

If I used instead proxy.send("identifier"), it returns a Proxy object in Java 11. In Java 8, this is a String object.

Thanks for your help!

Max

Catch all errors in native code and throw Java exceptions

Hi there,

I'm trying to implement a generic error handling in the native library like this:

-(void) throwJavaException:(JNIEnv *)env withMessage: (const char *)msg
{
    // You can put your own exception here
    jclass c = (*env)->FindClass(env, "java/lang/RuntimeException");
    
    (*env)->ThrowNew(env, c, msg);
}

And wrapped all functions/methods/selectors in WLJavaProxy.m into this try catch:

@try
    {
         ....
    } @catch (NSException *e) {
        [self throwJavaException: env withMessage: [[e reason] UTF8String] ];
        NSLog(@"Exception: %@", e);
    }

The hope was that for any error that occurred in native code, the VM would not crash but rather show a RuntimeException.

Is this the correct approach to do this?
It would be nice to have such a thing to prevent crashes!

Deploy to maven central

We should deploy the lib to maven-central. I have an account and can setup a proper build toolchain, if you want

RuntimeUtils.getAsReferenceWrapper(...) can cause precision loss

The method RuntimeUtils.getAsReferenceWrapper(...) can cause precision loss:

`RuntimeUtils.rt` and `initialized` field should be final (and private / removed?)

The RuntimeUtils class has the public fields rt and loaded:

public static Runtime rt = Runtime.INSTANCE;

public static boolean loaded = false;

These field should likely be final to prevent users from reassigning them.
And maybe rt should also be private (or be completely removed) because having two fields providing the same instance might be irritating.

Also note that RuntimeUtils should have an explicit private constructor because it currently has a public default constructor.

Mac M1 Support

Hi,
is it possible to upgrade the library to include Mac M1 support:

  • JNA v5.7 has Mac M1 support
  • Zulu OpenJDK (incl. JavaFX 8 on Mac M1)

Update or remove overview.html

It appears overview.html is not referenced anywhere and is quite outdated, e.g. it still lists the following requirement:

Java SE6 or Higher

It looks like commit 28f4679 accidentially removed it from the source folder, therefore it is not part of the javadoc anymore.

Would it make sense to move relevant information from it to the README and then remove overview.html, or to update it and add it to resources folder so it is part of the javadoc?

Connect to travis-CI

We should think about adding a proper travis ci config and setup an account there.

Add support for running inside ARM64 JRE

Hi,

thank you for this great library! :-)

Unfortunately it does not load on the new macOS 13:

SEVERE: java.lang.UnsatisfiedLinkError: Can't load library: /Users/dom/Library/Caches/JNA/temp/jna11470923764084623985.tmp
Oct 28, 2022 1:33:15 PM  
SEVERE: 	at java.base/java.lang.ClassLoader.loadLibrary(ClassLoader.java:2633)
Oct 28, 2022 1:33:15 PM  

and

SEVERE: java.lang.NoClassDefFoundError: Could not initialize class ca.weblite.objc.RuntimeUtils
Oct 28, 2022 1:33:15 PM  
SEVERE: 	at [email protected]/ca.weblite.objc.Client.sendPointer(Client.java:340)
Oct 28, 2022 1:33:15 PM  
SEVERE: 	at [email protected]/ca.weblite.objc.NSObject.init(NSObject.java:193)
Oct 28, 2022 1:33:15 PM  
SEVERE: 	at [email protected]/ca.weblite.objc.NSObject.<init>(NSObject.java:122)
Oct 28, 2022 1:33:15 PM  

Do you have an idea what might cause this and how to fix it?

Thank you very much!

Can't get it to work

Hey guys,
I need to work with some Objective-C classes from Java-8.
I'm working with MacOs10.11

If I try the sample (high level api) code, it works fine.
This is the code:

// Obtain reference to Singleton instance of Objective-C client
Client c = Client.getInstance();

// Create a new mutable array
Proxy array = c.sendProxy("NSMutableArray", "array");
array.send("addObject:", "Hello");
array.send("addObject:", "World");
array.send("addObject:", "Test String");

assertEquals(3, array.sendInt("count"));

String lastString = array.sendString("lastObject");
assertEquals("Test String", lastString);

But if I try it with other classes, it doesn't work and my application is crashin with this error:

#
# A fatal error has been detected by the Java Runtime Environment:
#
#  SIGSEGV (0xb) at pc=0x00007fff8ba47d32, pid=41313, tid=5891
#
# JRE version: Java(TM) SE Runtime Environment (8.0_66-b17) (build 1.8.0_66-b17)
# Java VM: Java HotSpot(TM) 64-Bit Server VM (25.66-b17 mixed mode bsd-amd64 compressed oops)
# Problematic frame:
# C  [libsystem_c.dylib+0xd32]  strlen+0x12

I need to work with NSPrintInfo, NSPrintOperation and PDFDocument.
That's my code:

Client c = Client.getInstance();
        Proxy nsPrintInfo = c.sendProxy("NSPrintInfo", "printinfo");
        Proxy pdfDocument = c.sendProxy("PDFDocument", "pdfDocument");
        Proxy nsPrintOperation = c.sendProxy("NSPrintOperation", "nsPrintOperation", "pdfDocument");

        nsPrintInfo.send("setTopMargin:", "0.0");
        nsPrintInfo.send("setBottomMargin:", "0.0");
        nsPrintInfo.send("setLeftMargin:", "0.0");
        nsPrintInfo.send("setRightMargin:", "0.0");
        nsPrintInfo.send("setHorizontalPagination:", "NSFitPagination");
        nsPrintInfo.send("setVerticalPagination:", "NSFitPagination");
        nsPrintInfo.send("setPaperSize:", "NSMakeSize(595.275591, 841.88976378)");

        pdfDocument.send("initWithURL:", filename);

        nsPrintOperation.send("setShowsPrintPanel:", "NO");
        nsPrintOperation.send("setShowsProgressPanel:", "NO");

        boolean succeed = nsPrintOperation.sendBoolean("runOperation");

I get the crash with Proxy nsPrintInfo = c.sendProxy("NSPrintInfo", "printinfo");

I hope you can help me.

Thanks :)

Remove Throwable.printStackTrace(...) usage

There are a few methods using Throwable.printStackTrace(...). Often System.err might not be visible or the user will not see it, so throwing an exception is more appropriate:

  • } catch (Exception ex){
    LOG.log(Level.SEVERE, String.format("Method invocation caused exception: %s", method));
    ex.printStackTrace(System.err);
    throw new RuntimeException(ex);
    }

    This is logging, printing the stack trace and rethrowing. Normally it is recommended to only do one of this because everything else is redundant and might only confuse the developer. I would recommend to only throw an exception (ideally a custom exception class) with:
    • A meaningful message
    • ex as cause
  • try {
    throw new RuntimeException("Checking stack trace for val "+val+" and signature "+signature);
    } catch (Exception ex){
    ex.printStackTrace(System.err);
    }

    This could be simplified by direcly calling printStackTrace on the created exception (instead of throwing and then catching it again). However it might be saner to throw an IllegalArgumentException here because null is not a valid argument there?

The printStackTrace calls in the static initializer of RuntimeUtils might make sense since you apparently don't want to throw an exception instead (though this would at least fail early; I doubt that any user is checking RuntimeUtils.loaded), but I think the static intializer should probably not call init(); if loading the native lib failed.

Remove developer specific files

This project contains a few developer-specific files, or files which might not be needed anymore. It is generally recommended to not check these fils in with version control (here Git) because each developer might have different settings.
Instead respective files and folders should be excluded using .gitignore or locally by the developer using --skip-worktree.

Affected:

  • Eclipse settings:
    • /.settings
    • /.classpath
    • /.project
  • /libjcocoa.xcodeproj, or at least some of its sub-directories (?)
  • /src/main/resources/ (?)
    And even if they are needed to build the project, I doubt that they should end up in the final .jar

RuntimeUtils.msg(...) argument validation logic might be faulty

It appears the argument validation logic in RuntimeUtils.msg(...) might be faulty:

if ( numArgs ==2 && numArgs != args.length+2 ){
throw new RuntimeException("Wrong argument count. The selector "+selName(selector)+" requires "+(numArgs-2)+" arguments, but received "+args.length);
}

I am not really familiar with this project, but I assume it should check for numArgs >= 2 (instead of == 2)?
Otherwise the calculation numArgs-2 in the error message is redundant because it would always be 0.

It might also be good to throw a more specific exception, e.g. IllegalArgumentException.
(Same for the other methods throwing RuntimeException when illegal arguments are provided)

Crash prevention

I get a lot of vm crashes during development:

A fatal error has been detected by the Java Runtime Environment:
#
#  SIGSEGV (0xb) at pc=0x00007fff5e76069d, pid=85121, tid=775
#
# JRE version: OpenJDK Runtime Environment (11.0.1+13) (build 11.0.1+13)
# Java VM: OpenJDK 64-Bit Server VM (11.0.1+13, mixed mode, tiered, compressed oops, g1 gc, bsd-amd64)
# Problematic frame:
# C  [libobjc.A.dylib+0x669d]  objc_msgSend+0x1d

Obviously those exceptions cannot be caught like I did with in my PR, because they happen in the objective-c runtime library. Would it be possible to wrap the calls within our own library like this here: https://blog.timac.org/2012/1218-simple-code-injection-using-dyld_insert_libraries/

I'm not too familiar with all this objc magic let alone c :-D
Any idea if this might work?

How can we test respondsToSelector: ?

First, I am happy to know this is still alive, as it is awesome!
Low level, but awesome.

My question is, what is the approach to test respondsToSelector: ?
I ask because the most common, and tragic mistake I have hit again and again is the stringly selector calling and missing a trailing colon on a selector that takes an argument.
In pure Objective-C this generally isn't a problem because Xcode corrects it most of the time (it is rare to have valid selectors with and without the colon because even Apple/NeXT learned that was troublesome) also because the exeption thrown will have the selector in the stack trace.
Arguably, even the Objective-C runtime could be more forthright about that, but it is also possible the object or its type are not what one expected.

So, I'd like to add this in for debugging (and potentially raise a Pull Request for making it a debug action to check for respondsToSelector: before each call.)

Any tips?

Avoiding Notarization

Just a little background, I am currently working on a mod for a game, which requires cross-platform speech.

Is there a way to access Objective C classes, the NSSpeechSynthesizer class in particular, without an objective C bridge, i.e. directly?

Perhaps this is a bit out of scope for the project, however, I would like to avoid notarization, if possible, since it would unnecessarily complicate an open source project.

Any help is greatly appreciated.

NSStrings and regular Strings not handled well sometimes

Hi there,

I have the following NSToolbarDelegate selector implemented:

@Msg(selector = "toolbar:itemForItemIdentifier:willBeInsertedIntoToolbar:", signature = "@:@:@:B")
	public Proxy toolbarItemForItemIdentifierwillBeInsertedIntoToolbar(Long toolbar, Proxy itemIdentifier, Long index, Boolean willBeInsertedIntoToolbar) {
...
}

The itemIdentifier should be a NSString (aka Proxy object) but sometimes it is a real java String. This happens at other places too, but I haven't detected a pattern.
I would prefer if every instance of NSString - even something like the NSImageName subclasses/aliases - would be just regular Strings.

Any idea how to fix this?

NSTaggedPointerString mapping

Hi there,

in NSObjectMapping you should also check for strings of type NSTaggedPointerString and probably NSMutableString too. Otherwise the current tests fail because instead of a String a Proxy is returned!

Thoughts of merging into JNA

I've only used this project to do same basic calls into Objective C (the Security Scoped Bookmarks NSURL methods) but it's been working extremely well for me, and getting things done went much more smooth than originally expected.

So first of all congrats on an amazingly simple yet powerful library! I get the feeling it's heavily under-appreciated...

Not sure if this ever came up, nor have I asked twall from JNA about this, but wouldn't your Objective-C-Bridge fit right in with the JNA project?

Improve Exception throwing

Currently the project throws quite a lot of RuntimeExceptions (also in native code). This makes it hard for the user to handle different exception types in different ways and the exception type itself is not very meaningful.

It might be good to redesign the API and create custom exception types, ideally checked ones since when dealing with native code unexpected things might happen and the caller should be required to handle those (?).

Maybe it would be good to target these (breaking) changes for version 2.0.0?

Inject NSObjects into methods called from obj-c

In kakao I register all NSObject subclasses globally. This allows me to get these instances later on when I only have a Proxy object:

@Msg(selector = "outlineView:numberOfChildrenOfItem:", signature = "l@:@@")
public int outlineViewNumberOfChildrenOfItem(Proxy outlineView, Proxy item) {
	return item == null ? root.childCount() : NSObject.<DataNode>getInstance(item.getPeer()).childCount();
}

It would be nice to move that functionality to JOCB's NSObject. We could then use it to directly inject the actual typed instance instead of the proxy.

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.