Generate UID Extension Problem

I am hoping to make a simple extension that generates a UID (20 characters)
0Oq3NsAEKOvWodXvAXEW

I converted the working javascript code to java using an online tool.

JAVASCRIPT
generatePushID=function(){
		var r="0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz",e=0,t=[];
		return function(){var o=(new Date).getTime(),n=o===e;e=o;for(var a=new Array(8),h=7;h>=0;h--)a[h]=r.charAt(o%64),o=Math.floor(o/64);
		if(0!==o)throw new Error("convert entire timestamp");
		var f=a.join("");if(n){for(h=11;h>=0&&63===t[h];h--)t[h]=0;t[h]++}else for(h=0;h<12;h++)t[h]=Math.floor(64*Math.random());
		for(h=0;h<12;h++)f+=r.charAt(t[h]);
		return f}}();

function getUID() {
		for(var newID,success=!1;!success;)20==(newID=generatePushID()).length&&(newID,success=!0);
		return newID;
	}
Extension Code
package uk.co.metricrat.generateuid;

import com.google.appinventor.components.annotations.SimpleFunction;
import com.google.appinventor.components.runtime.AndroidNonvisibleComponent;
import com.google.appinventor.components.runtime.ComponentContainer;
import java.util.*;

public class GenerateUID extends AndroidNonvisibleComponent {

  public GenerateUID(ComponentContainer container) {
    super(container.$form());
  }

  @SimpleFunction(description = "generates a unique alphanumeric 'timestamped' UID")
  public String UID() {

    final String CHARACTERS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
    long lastTime = 0;
    int[] lastRandChars = new int[12];
    long currentTime = new Date().getTime();
    boolean duplicateTime = (currentTime == lastTime);
    String err;

    lastTime = currentTime;

    char[] idChars = new char[20];
      for (int i = 7; i >= 0; i--) {
        idChars[i] = CHARACTERS.charAt((int) (currentTime % 64));
        currentTime = currentTime / 64;
      }
      if (currentTime != 0) {
        throw new RuntimeException("Convert entire timestamp");
      }
      String id = new String(idChars, 0, 8);
      int i;
      if (duplicateTime) {
        for (i = 11; i >= 0 && lastRandChars[i] == 63; i--) {
          lastRandChars[i] = 0;
        }
        lastRandChars[i]++;
      } else {
        for ( i = 0; i < 12; i++) {
          lastRandChars[i] = (int) (64 * Math.random());
        }
      }
      for ( i = 0; i < 12; i++) {
        id += CHARACTERS.charAt(lastRandChars[i]);
      }
      return id;
    }
  }

Built the extension with RUSH, it either works, does nothing, or generates a run time error:
length=62;index=62 - or index=63

From this I am guessing that the problem is coming from @ line 37

        for (i = 11; i >= 0 && lastRandChars[i] == 63; i--) {

Can anyone see the issue ?

Extension:
uk.co.metricrat.generateuid.aix (4.9 KB)

Alternatively you can simply use UUID.randomUUID()

import java.util.UUID;

If you believe so, then it should be

for (i = 11; i >= 0 && lastRandChars[i] == 63 && i < lastRandChars.length; i--) {
   lastRandChars[i] = 0;
}

Also it isnt very good for use if you transpile from another language to Java.
This often leads to hard to understand complex code.

Agreed :smiley:

1 Like

Thanks for this, still generates an error though.

I will go back to the drawing board...

It is actually a bit hard to debug, maybe you can try writing the code itself in Java from beginning.

Yes, I will put it on my "When I really have nothing better to do" list :wink:

Just wait a sec, I'll solve it :smile:

1 Like

Where it came from:

1 Like

There were many problems with the code you posted, probably transpiling errors. I rewrote the code manually and it works.

  private long lastPushTime = 0;

  private static final char[] CHARS = "-0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz".toCharArray();

  private final char[] lastRandChars = new char[12];

  @SimpleFunction(description = "generates a unique alphanumeric 'timestamped' UID")
  public String UID() {
    long now = System.currentTimeMillis();
    boolean duplicateTime = now == lastPushTime;
    lastPushTime = now;

    // first part
    StringBuilder id = new StringBuilder();
    for (int i = 7; i >= 0; i--) {
      id.append(CHARS[(int) (now % 64)]);
      now = (long) Math.floor((double) now / 64);
    }
    // second part
    if (!duplicateTime) {
      for (int i = 0; i < 12; i++) {
        lastRandChars[i] = (char) Math.floor(Math.random() * 64);
      }
    } else {
      for (int i = 11; i >= 0 && lastRandChars[i] == 63 ; i--) {
        lastRandChars[i] = 0;
      }
    }
    for (int i = 0; i < 12; i++) {
     id.append(CHARS[Math.min(lastRandChars[i], 61)]);
    }
    return id.toString();
  }

1 Like

Also as a note, if you are planning to use the UUID generated from this as unique identifier, I would recommend not, there are well advanced and tested algorithms implemented such as UUID.randomUUID()

This array probably contains 62 elements, so the last index is 61. And you want to get the element from positions 63 and 62.

1 Like

Hmmm, not sure your code is doing the same thing:

Output from your code:

0_ki2oN-J9XmVPNkSUgQ
V8li2oN-ToeEhI0sWYsq
NZli2oN-htWTomp0OkIY
Ovli2oN-WnpmMEDSCKOa
Tomi2oN-xmx_iZeoOmYk
0Hni2oN-mOPJLh0I5Lod
cmni2oN-3wIpjBjqxkEi

Output from the original javascript:

0Oq3l000IuOJMz25ULvh
0Oq3l4ahflyRKo4ovTaV
0Oq3l7tpKsBwXRDjs4YI
0Oq3l7tpKsBwXRDjs4YI
0Oq3lETZWc2mNCW1sFtG
0Oq3lHXhOznsBHxV5j00
0Oq3lKsRIaApqcecmtSj

Maybe I just stick with the webviewstring and html file (couldn't get it to work as a runJavascript)

<!DOCTYPE html>
<html>
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<head>
<title>UID Generator</title>
<style>
</style>
</head>
<body>
<script>
	generatePushID=function(){
		var r="0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz",e=0,t=[];
		return function(){var o=(new Date).getTime(),n=o===e;e=o;for(var a=new Array(8),h=7;h>=0;h--)a[h]=r.charAt(o%64),o=Math.floor(o/64);
		if(0!==o)throw new Error("convert entire timestamp");
		var f=a.join("");if(n){for(h=11;h>=0&&63===t[h];h--)t[h]=0;t[h]++}else for(h=0;h<12;h++)t[h]=Math.floor(64*Math.random());
		for(h=0;h<12;h++)f+=r.charAt(t[h]);
		return f}}();
	function getUID() {
		for(var newID,success=!1;!success;)20==(newID=generatePushID()).length&&(newID,success=!0);
		return newID;
	}
	if(window.AppInventor) {
		window.AppInventor.setWebViewString( getUID() ) ;
	} else {
		alert( getUID() );
	}
</script>
</body>
</html>

image

It is the same thing actually, but in your version of converted Java code, characters such as - and _ are missing from the characters string.

The list generated from your code will not sort alpha-numerically by time though (time is represented by characters...), whereas even without sorting, you can see that the list from the original code is in order.

I wasant sure you wanted it this way :man_shrugging:
I had to do few changes to the code to make it work,

Got working in a runJavascript now

2 Likes

@Kumaraswamy

I revisited your code for the extension you kindly created, to see if I could understand why the output did not "look like" the output from the javascript version.

Running the two side by side I eventually saw that the first eight characters were in reverse!
I added some code to fix this, and now they do look similar. I am not sure though if the remaining 12 characters are also in reverse (although I am not sure if it matters)?

Revised example output:
-NpRDVMcurjmxlt5NC4O //extension
-NpRDVMOBDKzr_7Fwk-a //javascript

Revised extension code:

package uk.co.metricrat.generateuid;

import com.google.appinventor.components.annotations.SimpleFunction;
import com.google.appinventor.components.runtime.AndroidNonvisibleComponent;
import com.google.appinventor.components.runtime.ComponentContainer;
import java.util.*;

public class GenerateUID extends AndroidNonvisibleComponent {

  public GenerateUID(ComponentContainer container) {
    super(container.$form());
  }
  // Timestamp of last push, used to prevent local collisions if you push twice in one ms.
  private long lastPushTime = 0;
  // Modeled after base64 web-safe chars, but ordered by ASCII.
  private static final char[] PUSH_CHARS = "-0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz".toCharArray();
  // Generate 72-bits of randomness which get turned into 12 characters and
  // appended to the timestamp to prevent collisions with other clients.
  // Store the last characters generated because in the event of a collision,
  // Use those same characters except "incremented" by one.
  private final char[] lastRandChars = new char[12];

  @SimpleFunction(description = "generates a unique alphanumeric 'timestamped' UID")
  public String UID() {
    long now = System.currentTimeMillis();
    boolean duplicateTime = now == lastPushTime;
    lastPushTime = now;

    // part one
    StringBuilder id = new StringBuilder();
    for (int i = 7; i >= 0; i--) {
      id.append(PUSH_CHARS[(int) (now % 64)]);
      now = (long) Math.floor((double) now / 64);
    }
    
    // part two
    if (!duplicateTime) {
      for (int i = 0; i < 12; i++) {
        lastRandChars[i] = (char) Math.floor(Math.random() * 64);
      }
    } else {
      // If the timestamp hasn't changed since last push, use the same random number, except incremented by 1.
      int j;
      for ( j = 11; j >= 0 && lastRandChars[j] == 63 ; j--) {
        lastRandChars[j] = 0;
      }
      lastRandChars[j]++;
    }
    // reverse part one of the uid string to ensure alphanumeracy
    StringBuilder idrev = id.reverse();
    for (int i = 0; i < 12; i++) {
      idrev.append(PUSH_CHARS[lastRandChars[i]]);
    }
    return idrev.toString();
  }
  }

Revised extension:
uk.co.metricrat.generateuid.aix (5.1 KB)

1 Like