Git Product home page Git Product logo

java-uuid-generator's People

Contributors

alexey-anufriev avatar branchpredictor avatar chadwilson avatar chenzhang22 avatar codylerum avatar cowtowncoder avatar dependabot[bot] avatar divinenickname avatar fwdekker avatar hboutemy avatar hellblazer avatar k163377 avatar magdel avatar maia-everett avatar mazurkin avatar mukham12 avatar pgalbraith avatar phated avatar squireofsoftware avatar sullis avatar worldtiki 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  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

java-uuid-generator's Issues

UUID version 7 implementation sorting incorrect?

I tried running the following code to ensure that the ordering of the UUID v7 is correct:

val test = (0..20).map { Generators.timeBasedEpochGenerator().generate() }
    val sorted = test.sorted()

    test.forEachIndexed { index, uuid ->
        println( uuid == sorted[index])
    }

expect the equality check returns false for almost all entries. I have tried to run the same test with the library com.github.f4b6a3:uuid-creator:5.2.0 and there the checks always return true using the following code:

val test = (0..20).map { UuidCreator.getTimeOrderedEpoch() }
    val sorted = test.sorted()

    test.forEachIndexed { index, uuid ->
        println( uuid == sorted[index])
    }

Is this a mistake in the implementation?

Generators class not found

I used the following dependency in my project

<dependency>
            <groupId>com.fasterxml.uuid</groupId>
            <artifactId>java-uuid-generator</artifactId>
            <version>3.1.3</version>
</dependency>

After compiling the project , I could find the jar also in following location

.m2\repository\com\fasterxml\uuid\java-uuid-generator\3.1.3

But, When I try to use the command, I get "Generators cannot be resolved"

UUID uuiD= Generators.timeBasedGenerator().generate();
Am I missing Something?

Confusing comment wrt synchronization

The readme is very clear:

Generators are fully thread-safe, so a single instance may be shared among multiple threads.

That being said, I find this comment rather confusing:

    /* As timer is not synchronized (nor _uuidBytes), need to sync; but most
     * importantly, synchronize on timer which may also be shared between
     * multiple instances

since there's no obvious synchronization in that method, it made me think the comment was meant for the caller.
I know this is not a javadoc comment, but still ...

Build generating unsatisfiable module-info.java

It appears your build generates something like the following...

  // IntelliJ API Decompiler stub source generated from a class file
  // Implementation of methods is not available

module com.fasterxml.uuid {
  requires java.logging;
  requires log4j;

  exports com.fasterxml.uuid;
  exports com.fasterxml.uuid.impl;
}

However, there's no log4j release that exports "log4j" (that I can find)

Add copyright notices, license info

It's necessary to add LICENSE information (separate file?), and also copyright notices, to make it easier for corporate entities to (re)use JUG.

Pedigree?

This is a suggestion to add some documentation around the history of this library. I'm currently working on a project that uses v 2.0.0 of org.safehaus.jug. Is that an earlier version of this same library? Is this an improved version of that or is this a fork with some different motivation? There is a credits page mentioning some contributors but no mention of safehaus.

Also, the http://wiki.fasterxml.com/JugHome link doesn't work...

FileBasedTimestampSynchronizer makes uuid generation a lot slower

It seems that adding the FileBasedTimestampSynchronizer makes generating UUIDs a lot slower...I know intuitively this is kinda expected, but it's over an order of magnitude slower.

I copied the source and slapped some debugging around, it seems that it's not actually running the file sync very much at all, instead it seems to run slowDown() a lot because actDiff is always > 20000 everytime it hits that if block.

I don't really understand the system well enough to understand why adding the time sync would cause this.

Clock sequence hi and lo bytes appear reversed

I may be wrong, but according to my reading of RFC4122, the following lines of code in TimeBasedGenerator.java appear to be storing the clock id in a reversed format. The following code in the TimeBasedGenerator constructor:

uuidBytes[UUIDUtil.BYTE_OFFSET_CLOCK_SEQUENCE] = (byte) clockSeq;
uuidBytes[UUIDUtil.BYTE_OFFSET_CLOCK_SEQUENCE+1] = (byte) (clockSeq >> 8);

should probably look like this:

uuidBytes[UUIDUtil.BYTE_OFFSET_CLOCK_SEQUENCE + 1] = (byte) clockSeq;
uuidBytes[UUIDUtil.BYTE_OFFSET_CLOCK_SEQUENCE] = (byte) (clockSeq >> 8);

UUIDTimer is doing the same thing. Also, in UUIDTimer, the clock sequence should only be 14 bits, so it should look like this:

public int getClockSequence() {
    return (_clockSequence & 0x3FFF);
}

Edit: I know the clock sequence is currently randomly generated. I was experimenting with it not being a random value.

Missing documentation

Couldn't find any JavaDoc or examples. Can they be found somewhere? Or are they missing?

Time based generator with Randomized Node

Would you take a pull request for an additional generator implementation that generates v1 UUIDs but with the node portion replaced with random bits as per section 4.5 of the RFC?

TimeBasedGenerator does not produce increasing UUIDs

In some rare situations TimeBasedGenerator may produce UUIDs that are not increasing.
Consider following sample program:

TimeBasedGenerator generator = new TimeBasedGenerator(new EthernetAddress(0L), new UUIDTimer(new Random(), null));
UUID previous = generator.generate();
while (true) {
    Thread.sleep(1000);
    UUID current = generator.generate();
    System.out.println(current + ", compareTo=" + current.compareTo(previous));
    previous = current;
}

And sample output that shows the problem:

7ef7c38a-bb6e-11e3-9e8f-000000000000, compareTo=1
7f905a0b-bb6e-11e3-9e8f-000000000000, compareTo=1
8028f08c-bb6e-11e3-9e8f-000000000000, compareTo=-1     <------- problem is here
80c1870d-bb6e-11e3-9e8f-000000000000, compareTo=1
815a1d8e-bb6e-11e3-9e8f-000000000000, compareTo=1

As you can see when we hit 63bit boundary for mostSigBits in UUID we get instance that is actually smaller than previous instance.

Ensure correct distinction between variant and version in documentation

The documentation in the README and the code is using the word "variant" where "version" should be used. For example, the README has the following:

UUID uuid = Generators.timeBasedGenerator().generate(); // Variant 1
UUID uuid = Generators.randomBasedGenerator().generate(); // Variant 4
UUID uuid = Generators.nameBasedgenerator().generate("string to hash"); // Variant 5
// With JUG 4.1+: support for https://github.com/uuid6/uuid6-ietf-draft variants 6 and 7:
UUID uuid = Generators.timeBasedReorderedGenerator().generate(); // Variant 6
UUID uuid = Generators.timeBasedEpochGenerator().generate(); // Variant 7

These are referring to the UUID Versions (See: https://www.rfc-editor.org/rfc/rfc4122#section-4.1.3). The Variant of a UUID is distinct from the Version, and is tracked separately (See: https://www.rfc-editor.org/rfc/rfc4122#section-4.1.1). Also see https://www.ietf.org/archive/id/draft-peabody-dispatch-new-uuid-format-04.html#name-variant-and-version-fields.

Guidance on how to handle IllegalStateException from TimeBasedEpochGenerator

I noticed that TimeBasedEpochGenerator may throw

throw new IllegalStateException("overflow on same millisecond");

I'm not clear on how applications should brace for this situation.
(This is more of a theoretical question, I haven't been able to actually trigger it in practice).

I looked at https://www.ietf.org/archive/id/draft-peabody-dispatch-new-uuid-format-04.html#name-uuid-version-7 a bit, and it sure looks like -in general- some corner cases should be handled by the application.

I believe that some comments would be helpful here.

  • what kind of situation would trigger this IllegalStateException? (Is it heavy load? Is is an extremely unlikely unlucky randomness? Is it a combination?)
  • some suggestions on how to application may want to handle this. (Is is simply "retry", or "sleep 1ms and then retry"? Should one try to be clever and try to avoid starvation?)

thank you very much
Gabriel Balan

Time based UUID not using current time stamp

@cowtowncoder , I used the following code Generators.timeBasedGenerator().generate() to generate time based UUID. On the generated UUID, I found the timestamp using Java's UUID.timestamp() , I see the timestamp is not current time . Is there a way to generate UUID with current time stamp?

race condition in RandomBasedGenerator?

The below comment is not strictly true, is it? Because of the wonders of hotspot reordering etc, there is the possibility that a thread will see a non-null _sharedRandom that won't be fully constructed. Would it not be better to use volatile (if you want to maintain the side effects), or an AtomicReference (my preference). Both of these will only work in a 1.5+ JVM, 1.4 and below sync is your only choice.

        /* Could be synchronized, but since side effects are trivial
         * (ie. possibility of generating more than one SecureRandom,
         * of which all but one are dumped) let's not add synchronization
         * overhead.
         */
        rnd = _sharedRandom;
        if (rnd == null) {
            _sharedRandom = rnd = new SecureRandom();
        }

Need more testing to increase code coverage (at least to 50%)

After enabling JaCoCo based code coverage, CC badge is finally working. Displayed level, 46%, is on low side of usual things so would be good to spend some time increasing code coverage -- I think addition of new UUID versions may have caused coverage to have decreased. Either way, getting to above 50% seems like a useful short-term thing to achieve.

EDIT:

  • As of February 19, 2024, we are at 45.28%.
  • As of February 22, 2024, inching up to 47.71%
  • As of June 7, 2024, up to 54.61%! (thanks to PR #110)
  • ... actually, seems to now exceed 60%, due to #112.

LICENSE file refers to the Java Classmate library instead of Java UUID Generator (JUG)

The LICENSE file says:

This copy of Java ClassMate library is licensed under Apache (Software) License,

while I believe that it should read:

This copy of Java UUID Generator (JUG) library is licensed under Apache (Software) License,

I assume that this is a mistake and the license has been copied and pasted from the Java ClassMate library. I appreciate that the README, POM and release notes all say that JUG is licensed under Apache License, Version 2.0, but for the avoidance of doubt, can you confirm this is the case?

Type 1 UUID getTimestamp() synchronization leads to performance bottleneck

We are using type 1 UUID timestamp based generator - TimeBasedGenerator - which has getTimestamp() method which is synchronized. This is causing some performance bottleneck in our code.

public synchronized long getTimestamp()

My question is, can we instead use TimeBasedEpochGenerator and still get the same functionality (uniqueness) of TimeBasedGenerator UUID?

Add `UUIDUtil.extractTimestamp()` for extracting 64-bit timestamp for all timestamp-based versions

java.util.UUID#timestamp() only works for version 1 UUIDs.

It would be nice to have similar functionality for version 6 UUIDs.
E.g. a static method in TimeBasedReorderedGenerator, something along the lines of

public static long timestamp(UUID uuid)
{
    assert(uuid.version()==6);
    return ((uuid.getMostSignificantBits()>>>4) & ~0xFFF) | (uuid.getMostSignificantBits()&0xFFF);
}

Better yet, a static method in UUIDUtil that returns the number of millis since unix epoch for versions 1, 6 and 7 UUIDs.

thank you very much
Gabriel

Use SLF4J instead of Log4J directly

Having this depend specifically on Log4J can produce a weird situation when used in projects that use other logging frameworks (which is the problem SLF4J, or depending only on API jars, tackle).

As far as I could see, there isn't a lot of complex things going on here that would require direct access to some Log4J-specific features, so I would like to know if you'd be willing to migrate this to SLF4J (or Log4J2-API).

I can port it myself, but I wanted to know your positioning on the matter before forking and creating a PR

Suggestion to add a static method to generate a random based UUID using ThreadLocalRandom (requires JDK 7)

I suggest to add a static method like the following to UUIDUtil (or any other appropriate class):

public static UUID generateThreadLocalRandomUUID() {
ThreadLocalRandom rnd = ThreadLocalRandom.current();
return UUIDUtil.constructUUID(UUIDType.RANDOM_BASED, rnd.nextLong(), rnd.nextLong());
}

Using a generator created by Generators.randomBasedGenerator(ThreadLocalRandom.current()) is probably not a good idea when working in an environment with thread pools (like Java/Jakarta EE), so i came to this solution.

Best regards, Robert

Microsecond / nanosecond part of time based uuid set even if UUIDClock is millisecond precision

Hi

For some legacy stuff that I'm working on, I needed to be able to create UUID's for a given timestamp, so I created the following utility class create time based UUID's and convert them back to Instant:

public class UUIDUtil {
    /**
     * Converts a time based (type 1) uuid into a epoch nanos.
     * Inspiration stolen from https://stackoverflow.com/questions/15179428/how-do-i-extract-a-date-from-a-uuid-using-java
     */
    public static long toEpocNano(UUID uuid) {
        long ts = uuid.timestamp();
        return ((ts / 10l) - 12_219_292_800_000_000l) * 1000;
    }

    /**
     * Converts a time based (type 1) uuid into an Instant.
     */
    public static Instant toInstant(UUID uuid) {
        return Instant.ofEpochSecond(0, toEpocNano(uuid));
    }

    /**
     * Generate a time based UUID that matches the given instant time wise.
     */
    public static UUID timeBased(Instant forInstant) {
        try {
            UUIDTimer timer = new UUIDTimer(new Random(), null, new FixedUUIDClock(forInstant));
            return new TimeBasedGenerator(null, timer).generate();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    @RequiredArgsConstructor
    private static class FixedUUIDClock extends UUIDClock {
        final Instant i;

        @Override
        public long currentTimeMillis() {
            return i.toEpochMilli();
        }
    }
}

But while doing some testing of it I discovered that the generated UUID (when converved back to an Instant) has micro/nano-second precision.

Following unit test fails because of the 22 micros:

@Test
public void testNanos() {
    Instant i = Instant.parse("2022-12-21T12:11:16.979Z");
    UUID uuid = UUIDUtil.timeBased(i);
    // 2022-12-21T12:11:16.979Z -> 2022-12-21T12:11:16.979022Z
    assertThat(UUIDUtil.toInstant(uuid)).isEqualTo(i);
}

Am I doing something wrong here or should the generated UUID not have the same precision as the UUIDClock?

UUIDTimer is not extendable which is not consistent with it's Javadoc

UUIDTimer has synchronized on getTimestamp(), which we want to eliminate.
On the same time, getTimestamp() has a comment:

/**
     * Method that constructs unique timestamp suitable for use for
     * constructing UUIDs. Default implementation is fully synchronized;
     * sub-classes may choose to implemented alternate strategies
     *
     * @return 64-bit timestamp to use for constructing UUID
     */

So, the comment tells something about possible subclasses with relaxed strategy.
On the same time, the whole UUIDTimer class is final and all main methods are final.

Enable "Reproducible Build"

(see https://reproducible-builds.org/docs/jvm/ for details)

Similar to latest (2.14) Jackson builds, it seems reasonable to enable reproducible builds for JUG.
This should be as simple as adding

    <project.build.outputTimestamp>

in pom.xml (and ensuring reasonable recent oss-parent parent pom).
Packaging type should also be changed back to jar to get good timestamps in jar classes.

Add support for Proposed type v8 (fully custom)

So as per:

there are 3 types; this is for implementing variant 8, DIY variant where actual definition of structure is not specified by spec; this can be used for fully custom uuids where semantics are specific to use case / context.

It may be that this just means ability to accept such uuids, or something more, depending on what makes most sense.

Add constants for "Nil UUID", "Max UUID" (from draft "new UUID" spec) in `UUIDUtil`

(note: related to Draft-04 spec at: https://uuid6.github.io/uuid6-ietf-draft/ section 5.4)

So, looks like there will be the counterpart for "null UUID" (all zeroes) wherein all 128 bits are 1s, so-called "Max UUID".
This is specific in section 5.4 of the "new UUID" draft spec (draft-04):

https://www.ietf.org/archive/id/draft-peabody-dispatch-new-uuid-format-04.html

It should probably be defined similar to the existing null UUID (details to be filled)

EthernetAddress.fromInterface() returning a WAN miniport address on Win7 64

On a Win7 64bit system, EthernetAddress.fromInterface() is returning the address of a WAN Miniport instead of the NIC address.

This particular system has 20 entries returned by the NetworkInterface.getNetworkInterfaces() method. Entry 6 is "name:eth0 (WAN Miniport (IPv6))" and has an address of [-22, 50, 32, 82, 65, 83] in the byte array (ea:32:20:52:41:53). Having a length of 6, this is returning from the fromInterface() method as the MAC address of the machine. Entry 10 in the network interface enumeration is for the actual NIC, which never gets evaluated.

Running the msinfo32 utility on this machine and navigating to System Summary > Components > Network > Adapter shows the list of most of the network interface entries returned by the getNetworkInterfaces() method, with the Wan Miniport IPv6 entry above the actual NIC, though the MAC address for the WAN Miniport entry in that utility shows up as "Not Available".

As a current work-around I'm using the following code to get the correct MAC address before handing it to EthernetAddress:

InetAddress address = InetAddress.getLocalHost();
NetworkInterface nwi = NetworkInterface.getByInetAddress(address);
byte mac[] = nwi.getHardwareAddress();

Increase JDK baseline to JDK 8 for `java-uuid-generator` 5.0

It has been a while since I increased the JDK baseline and I think it would be reasonable to finally move to Java 8.
This would make it a little bit easier to maintain things (for example, able to build on JDK 17 which does not support Java 6 target).

This would be doable for version 4.1: I don't think a major version upgrade is warranted, although I am open to being convinced otherwise.

Feel free to add comments, especially if you have anything AGAINST proposal.

performace issue

in my test. Performace of this generator and JDK UUID is almost the same. So, do bother to use this for performace consideration.

`public class UtilsTest {

@Test
public void uuidPerformanceCompare() throws Exception {
    uuidGeneratorPerformanceTest();
    javaUuidGeneratorPerformanceTest();
}

public void uuidGeneratorPerformanceTest() throws Exception {
    final int THREAD_COUNT = 100;
    final CountDownLatch startLatch = new CountDownLatch(THREAD_COUNT + 1);
    final AtomicBoolean stop = new AtomicBoolean(false);

    final AtomicInteger generatedCount = new AtomicInteger(0);

    for (int i = 0; i < THREAD_COUNT; ++i) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                startLatch.countDown();
                try {
                    startLatch.await();
                    while (!stop.get()) {
                        Utils.generateId();
                        generatedCount.incrementAndGet();
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }
    Thread.sleep(100);
    startLatch.countDown();
    long startMs = System.currentTimeMillis();
    System.out.println("starting generating");
    TimeUnit.SECONDS.sleep(5);
    stop.compareAndSet(false, true);
    System.out.println(String.format("java-uuid-generator generate %s in %sms",
            generatedCount.get(), System.currentTimeMillis() - startMs));
}

public void javaUuidGeneratorPerformanceTest() throws Exception {
    final int THREAD_COUNT = 100;
    final CountDownLatch startLatch = new CountDownLatch(THREAD_COUNT + 1);
    final AtomicBoolean stop = new AtomicBoolean(false);

    final AtomicInteger generatedCount = new AtomicInteger(0);

    for (int i = 0; i < THREAD_COUNT; ++i) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                startLatch.countDown();
                try {
                    startLatch.await();
                    while (!stop.get()) {
                        Utils.sanitizeId(UUID.randomUUID().toString());
                        generatedCount.incrementAndGet();
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }
    Thread.sleep(100);
    startLatch.countDown();
    long startMs = System.currentTimeMillis();
    System.out.println("starting generating");
    TimeUnit.SECONDS.sleep(5);
    stop.compareAndSet(false, true);
    System.out.println(String.format("java-sdk-UUID generate %s in %sms",
            generatedCount.get(), System.currentTimeMillis() - startMs));
}

}`

test result:
starting generating java-uuid-generator generate 1010994 in 5004ms starting generating java-sdk-UUID generate 1097096 in 5004ms

Ensure OSGi exports packages with versions

Currently (released version 3.1.1) the OSGi exports doesn't come with any version information. Therefore the framework falls back to the default export version, which is "0.0.0".

This could introduce problems in several cases and is not recommended.

Output from a Felix Shell:

java-uuid-generator [372] exports packages:
-------------------------------------------
com.fasterxml.uuid; version=0.0.0 UNUSED
com.fasterxml.uuid.ext; version=0.0.0 UNUSED
com.fasterxml.uuid.impl; version=0.0.0 UNUSED

Problematic OSGI version range for slf4j dependency

Thank you very much for providing java-uuid-generator!

I tried to update to version 4.0, but it seems like there is a problem with this version range:

org.slf4j;version="[1.7.29,)"

in MANIFEST.MF.

When using org.apache.felix.framework version 5.6.10 this version can not be parsed:

java.util.NoSuchElementException
at java.base/java.util.StringTokenizer.nextToken(StringTokenizer.java:349)
at org.osgi.framework.Version.(Version.java:126)
at org.apache.felix.framework.util.VersionRange.parse(VersionRange.java:93)
at org.apache.felix.framework.util.manifestparser.ManifestParser.normalizeImportClauses(ManifestParser.java:329)
at org.apache.felix.framework.util.manifestparser.ManifestParser.(ManifestParser.java:181)
at org.apache.felix.framework.BundleRevisionImpl.(BundleRevisionImpl.java:117)
at org.apache.felix.framework.BundleImpl.createRevision(BundleImpl.java:1282)
at org.apache.felix.framework.BundleImpl.(BundleImpl.java:113)
at org.apache.felix.framework.Felix.installBundle(Felix.java:3042)
at org.apache.felix.framework.BundleContextImpl.installBundle(BundleContextImpl.java:167)
at org.apache.felix.framework.BundleContextImpl.installBundle(BundleContextImpl.java:140)

I may be wrong, but I think the OSGI spec does not allow a such a version range (see: https://osgi.org/download/r5/osgi.core-5.0.0.pdf, page 27).

When I modify the java-uuid-generator jar and change the version range to org.slf4j;version="[1.7.29,2)" everything works fine.

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.