Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
459 changes: 459 additions & 0 deletions force-app/main/default/classes/ULID/README.md

Large diffs are not rendered by default.

28 changes: 14 additions & 14 deletions force-app/main/default/classes/ULID/ULID.cls
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,11 @@
*/
public with sharing class ULID {
/**
* This character set is the complete list of allowed characters in
* @description This character set is the complete list of allowed characters in
* a ULID string. It intentionally does not include characters that
* may be ambiguously read, such as i, l, o, and u characters.
*/
private static final List<String> CHARACTERSET = new List<String>{
private static final List<String> CHARACTER_SET = new List<String>{
'0',
'1',
'2',
Expand Down Expand Up @@ -50,23 +50,22 @@ public with sharing class ULID {
'Z'
};
// Calculate this once per transaction to avoid unnecessary math
private static final Long CHARACTERSETSIZE = CHARACTERSET.size();
// This is equal to 2^48-1 and represents the max timestamp
// allowed in a ULID string
private static final Long CHARACTER_SET_SIZE = CHARACTER_SET.size();
// This is equal to 2^48-1 and represents the max timestamp allowed in a ULID string
// private static final Long MAXTIME = 281474976710655L;
// This is the number of digits to encode the timestamp into.
private static final Long TIMELENGTH = 10;
private static final Long TIME_LENGTH = 10;
// This is the number of digits of random character to generate
private static final Integer RANDOMLENGTH = 16;
private static final Integer RANDOM_LENGTH = 16;

/**
* @description Generates a ULID string according to spec.
* https://github.com/ulid/spec
* @return `String`
*/
public static String generate() {
return encodeTimestamp(DateTime.now().getTime(), TIMELENGTH) +
generateRandomString(RANDOMLENGTH);
return encodeTimestamp(Datetime.now().getTime(), TIME_LENGTH) +
generateRandomString(RANDOM_LENGTH);
}

/**
Expand All @@ -76,13 +75,14 @@ public with sharing class ULID {
* @param timeLength how many characters of the timestamp to encode
* @return `String`
*/
@SuppressWarnings('PMD.UnusedLocalVariable')
private static String encodeTimestamp(Long dtSeed, Long timeLength) {
Long modulo;
String retString = '';
for (Long l = timeLength; timeLength > 0; timeLength--) {
modulo = Math.mod(dtSeed, CHARACTERSETSIZE);
retString = CHARACTERSET[modulo.intValue()] + retString;
dtSeed = (dtSeed - modulo) / CHARACTERSETSIZE;
modulo = Math.mod(dtSeed, CHARACTER_SET_SIZE);
retString = CHARACTER_SET[modulo.intValue()] + retString;
dtSeed = (dtSeed - modulo) / CHARACTER_SET_SIZE;
}
return retString;
}
Expand All @@ -108,7 +108,7 @@ public with sharing class ULID {
* @return `String`
*/
private static String fetchRandomCharacterFromCharacterSet() {
Long rand = Math.mod(Math.abs(Crypto.getRandomLong()), CHARACTERSETSIZE);
return CHARACTERSET[rand.intValue()];
Long rand = Math.mod(Math.abs(Crypto.getRandomLong()), CHARACTER_SET_SIZE);
return CHARACTER_SET[rand.intValue()];
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,9 @@ private class PolyfillsTests {
(List<Contact>) SObjectFactory.createSObjects(
new Contact(AccountId = acct.Id),
5,
false
null,
false,
true
Comment on lines +165 to +167
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Image description CodeRabbit

The createSObjects method call has been updated with new parameters. Ensure that the new parameters (null, false, true) are correctly passed and they align with the method's expectations. If these parameters have default values, consider using the method overloading principle to avoid passing them explicitly each time.

- SObjectFactory.createSObjects(new Contact(AccountId = acct.Id), 5, false)
+ SObjectFactory.createSObjects(new Contact(AccountId = acct.Id), 5, null, false, true)

Please also ensure that the changes do not break any existing functionality or tests.

)
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,11 +68,17 @@ public with sharing class EnqueueNextQueueableProcessStep implements System.Fina
switch on context.getResult() {
when SUCCESS {
if (this.processSteps.size() > 0) {
// Remove the first element from the list - this was constructed using the .then() method.
QueueableProcess nextProcessStep = this.processSteps.remove(0);
// Set the remaining steps on the next process step.
nextProcessStep.processSteps = this.processSteps;
// Set the dataPassthrough on the next process step.
nextProcessStep.dataPassthrough = dataPassthrough;
// Set the queueableContextHistory on the next process step.
nextProcessStep.queueableContextHistory = this.queueableContextHistory;
// Set the finalizerContextHistory on the next process step.
nextProcessStep.finalizerContextHistory = this.finalizerContextHistory;
// Enqueue the next process step.
Comment on lines +71 to +81
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Image description CodeRabbit

The logic here seems to be correct, but it's not very efficient. You're directly modifying the processSteps list of the current object and assigning it to the nextProcessStep. This means that all subsequent steps will share the same list instance, which could lead to unexpected behavior if any step modifies the list.

Instead, consider creating a new list that excludes the first element and assign this new list to the nextProcessStep. This way, each step has its own copy of the remaining steps, and modifications in one step won't affect others.

- QueueableProcess nextProcessStep = this.processSteps.remove(0);
- nextProcessStep.processSteps = this.processSteps;
+ QueueableProcess nextProcessStep = this.processSteps[0];
+ nextProcessStep.processSteps = this.processSteps.subList(1, this.processSteps.size());

This change also avoids the need to remove an element from the beginning of the list, which can be an expensive operation for large lists.

System.enqueueJob(nextProcessStep);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,9 +61,11 @@ public abstract class QueueableProcess implements Queueable, Database.AllowsCall

/**
* @description This must be implemented by extending classes. Developers - implement this method with the work you
* want executed asynchronously.
* want executed asynchronously. The returned Object will be passed as dataPassthrough to the next step.
*
* @return Object - data to pass through to the next step in the process
*/
abstract public void execute();
abstract public Object execute();
Comment on lines +64 to +68
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Image description CodeRabbit

The change from void to Object as the return type for the execute() method is a breaking change. This will require all classes extending QueueableProcess to update their implementation of this method to return an Object. Ensure that this change is communicated clearly to all developers who are using this class.

- abstract public void execute();
+ abstract public Object execute();


/**
* @description this is a default implementation of an handleError method. It's called by the finalizer if the
Expand All @@ -85,23 +87,23 @@ public abstract class QueueableProcess implements Queueable, Database.AllowsCall
*/
public virtual void execute(QueueableContext context) {
// if the queueableContextHistory is null, initialize it.
if (this.queueableContextHistory == null) {
this.queueableContextHistory = new List<QueueableContext>();
}
this.queueableContextHistory = this.queueableContextHistory ??
new List<QueueableContext>();
this.queueableContextHistory.add(context);

// Call the abstract method `execute` and capture its return value as the dataPassthrough for the next step.
Object nextDataPassthrough = execute();

// create a new instance of the finalizer class. Note that we're passing in the list of remaining steps and the
// passthrough data.
// returned data from execute() as the passthrough data for the next step.
Finalizer nextStep = new EnqueueNextQueueableProcessStep(
this.processSteps,
this.dataPassthrough,
nextDataPassthrough,
this.queueableContextHistory,
this.finalizerContextHistory
);
// Attach the finalizer to system context. This will take care of enqueueing the next QueueableProcess step in
// the nextStep.
System.attachFinalizer(nextStep);
Comment on lines +90 to 107
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Image description CodeRabbit

The refactoring in these lines improves the code by removing the need for an explicit null check on queueableContextHistory. The use of the null-coalescing operator (??) simplifies the code and makes it more readable. However, the change in how the execute() method is called (now capturing its return value) could potentially introduce bugs if the returned object is not handled correctly in subsequent steps. Make sure to thoroughly test this new behavior.

- if (this.queueableContextHistory == null) {
-    this.queueableContextHistory = new List<QueueableContext>();
- }
- this.queueableContextHistory.add(context);
- Finalizer nextStep = new EnqueueNextQueueableProcessStep(
-    this.processSteps,
-    this.dataPassthrough,
-    this.queueableContextHistory,
-    this.finalizerContextHistory
- );
- System.attachFinalizer(nextStep);
- execute();

+ this.queueableContextHistory = this.queueableContextHistory ??
+    new List<QueueableContext>();
+ this.queueableContextHistory.add(context);
+ Object nextDataPassthrough = execute();
+ Finalizer nextStep = new EnqueueNextQueueableProcessStep(
+    this.processSteps,
+    nextDataPassthrough,
+    this.queueableContextHistory,
+    this.finalizerContextHistory
+ );
+ System.attachFinalizer(nextStep);

// invoke the abstract method `execute`. see the comment above.
execute();
}
}
Loading
Loading