forked from apex-enterprise-patterns/fflib-apex-common
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #2 from OrtooApps/feature/adding-sobject-fabricator
Feature/adding sobject fabricator
- Loading branch information
Showing
19 changed files
with
3,795 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
146 changes: 146 additions & 0 deletions
146
framework/default/ortoo-core/default/classes/utils/DirectedGraph.cls
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,146 @@ | ||
// Directed Graph algorithm ased on the DirectedGraph implemented by Robert Sösemann: https://github.com/rsoesemann/apex-domainbuilder | ||
public inherited sharing class DirectedGraph | ||
{ | ||
public inherited sharing class GraphContainsCircularReferenceException extends ortoo_Exception {} | ||
// | ||
// TODO: detect and block circular references. We can deal with them independently | ||
// | ||
private Map<Object,Set<Object>> parentsByChildren = new Map<Object, Set<Object>>(); | ||
|
||
/** | ||
* Adds a node to the graph | ||
* | ||
* @param Object The node to add | ||
* @return DirectedGraph Itself, allowing for a fluent interface | ||
*/ | ||
public DirectedGraph addNode( Object node ) | ||
{ | ||
if( ! parentsByChildren.containsKey( node ) ) | ||
{ | ||
parentsByChildren.put( node, new Set<Object>() ); | ||
} | ||
return this; | ||
} | ||
|
||
/** | ||
* Adds a relationship between two nodes | ||
* | ||
* @param Object The child node of the relationship | ||
* @param Object The parent node of the relationship | ||
* @return DirectedGraph Itself, allowing for a fluent interface | ||
*/ | ||
public DirectedGraph addRelationship( Object child, Object parent ) | ||
{ | ||
Contract.requires( parentsByChildren.containsKey( child ), 'addRelationship called with a child that has not been added as a node (' + child + ')' ); | ||
Contract.requires( parentsByChildren.containsKey( parent ), 'addRelationship called with a parent that has not been added as a node (' + parent + ')' ); | ||
parentsByChildren.get( child ).add( parent ); | ||
return this; | ||
} | ||
|
||
/** | ||
* Generates a list of nodes, sorted by their depdencies. | ||
* | ||
* That is, the children first, resolving upwards to the parents. | ||
* No parent appears in the list prior to any of their children. | ||
* | ||
* Algorithm: | ||
* A leaf node is added | ||
* All references to that as a child are removed | ||
* If any parent no longer has any children registered, it is regarded as a leaf node | ||
* Move onto the next leaf node. | ||
* | ||
* Assuming that there are no circular references, | ||
* Eventually, every node will be regarded as a leaf node, and therefore every node will be added | ||
* | ||
* @param Object The child node of the relationship | ||
* @param Object The parent node of the relationship | ||
* @return DirectedGraph Itself, allowing for a fluent interface | ||
*/ | ||
public List<Object> generateSorted() | ||
{ | ||
List<Object> sortedObjects = new List<Object>(); | ||
|
||
while( ! leafNodes.isEmpty() ) | ||
{ | ||
Object currentLeaf = (Object)leafNodes.iterator().next(); | ||
leafNodes.remove( currentLeaf ); | ||
|
||
sortedObjects.add( currentLeaf ); | ||
|
||
for( Object thisParent : parentsByChildren.get( currentLeaf ) ) | ||
{ | ||
if ( childCountsByParents.containsKey( thisParent ) ) | ||
{ | ||
Integer remainingChildrenCount = childCountsByParents.get( thisParent ) - 1; | ||
childCountsByParents.put( thisParent, remainingChildrenCount ); | ||
|
||
if ( remainingChildrenCount == 0 ) | ||
{ | ||
leafNodes.add( thisParent ); | ||
} | ||
} | ||
} | ||
} | ||
|
||
leafNodes = null; // reset the leaf nodes so they will be re-calculated on a subsequent call | ||
childCountsByParents = null; // similar to above | ||
|
||
if ( sortedObjects.size() != allNodes.size() ) | ||
{ | ||
throw new GraphContainsCircularReferenceException( 'The graph contains a circular reference and therefore cannot be resolved.' ); | ||
} | ||
return sortedObjects; | ||
} | ||
|
||
/** | ||
* A reference to the full list of nodes registered on this graph. | ||
*/ | ||
private Set<Object> allNodes | ||
{ | ||
get | ||
{ | ||
return parentsByChildren.keySet(); | ||
} | ||
} | ||
|
||
private Set<Object> leafNodes | ||
{ | ||
get | ||
{ | ||
if ( leafNodes == null ) | ||
{ | ||
leafNodes = new Set<Object>(); | ||
leafNodes.addAll( allNodes ); | ||
leafNodes.removeAll( childCountsByParents.keySet() ); | ||
} | ||
return leafNodes; | ||
} | ||
set; | ||
} | ||
|
||
private Map<Object,Integer> childCountsByParents | ||
{ | ||
get | ||
{ | ||
if ( childCountsByParents == null ) | ||
{ | ||
childCountsByParents = new Map<Object,Integer>(); | ||
|
||
for ( Object thisChild : allNodes ) | ||
{ | ||
for ( Object parent : parentsByChildren.get( thisChild ) ) | ||
{ | ||
if ( ! childCountsByParents.containsKey( parent ) ) | ||
{ | ||
childCountsByParents.put( parent, 0 ); | ||
} | ||
childCountsByParents.put( parent, childCountsByParents.get( parent ) + 1 ); | ||
} | ||
} | ||
|
||
} | ||
return childCountsByParents; | ||
} | ||
set; | ||
} | ||
} |
5 changes: 5 additions & 0 deletions
5
framework/default/ortoo-core/default/classes/utils/DirectedGraph.cls-meta.xml
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
<?xml version="1.0" encoding="UTF-8"?> | ||
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata"> | ||
<apiVersion>52.0</apiVersion> | ||
<status>Active</status> | ||
</ApexClass> |
184 changes: 184 additions & 0 deletions
184
framework/default/ortoo-core/default/classes/utils/tests/DirectedGraphTest.cls
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,184 @@ | ||
@isTest | ||
private without sharing class DirectedGraphTest | ||
{ | ||
@isTest | ||
private static void generateSorted_whenASimpleGraphIsSpecified_willReturnTheNodesInOrder() // NOPMD: Test method name format | ||
{ | ||
DirectedGraph graph = new DirectedGraph() | ||
.addNode( 'Great grandparent' ) | ||
.addNode( 'Grandparent' ) | ||
.addNode( 'Parent' ) | ||
.addNode( 'Child' ) | ||
.addRelationship( 'Child', 'Parent' ) | ||
.addRelationship( 'Parent', 'Grandparent') | ||
.addRelationship( 'Grandparent', 'Great grandparent' ); | ||
|
||
List<Object> expectedNodes = new List<Object> | ||
{ | ||
'Child', | ||
'Parent', | ||
'Grandparent', | ||
'Great grandparent' | ||
}; | ||
|
||
List<Object> returnedNodes = graph.generateSorted(); | ||
|
||
System.assertEquals( expectedNodes, returnedNodes, 'generateSorted, when a simple graph has been built, will return the nodes in child to parent order' ); | ||
} | ||
|
||
@isTest | ||
private static void generateSorted_whenAComplexGraphIsSpecified_willReturnTheNodesInOrder() // NOPMD: Test method name format | ||
{ | ||
DirectedGraph graph = new DirectedGraph() | ||
.addNode( 'Great grandparent' ) | ||
.addNode( 'Grandparent of both parents' ) | ||
.addNode( 'Parent 1' ) | ||
.addNode( 'Parent 2' ) | ||
.addNode( 'Child 1 of Parent 1' ) | ||
.addNode( 'Child 2 of Parent 1' ) | ||
.addNode( 'Child 1 of Parent 2' ) | ||
.addNode( 'Child 2 of Parent 2' ) | ||
.addNode( 'Child of Parents 1 and 2' ) | ||
|
||
.addRelationship( 'Grandparent of both parents', 'Great grandparent' ) | ||
.addRelationship( 'Parent 1', 'Grandparent of both parents' ) | ||
.addRelationship( 'Parent 2', 'Grandparent of both parents' ) | ||
.addRelationship( 'Child 1 of Parent 1', 'Parent 1' ) | ||
.addRelationship( 'Child 2 of Parent 1', 'Parent 1' ) | ||
.addRelationship( 'Child 1 of Parent 2', 'Parent 2' ) | ||
.addRelationship( 'Child 2 of Parent 2', 'Parent 2' ) | ||
.addRelationship( 'Child of Parents 1 and 2', 'Parent 1') | ||
.addRelationship( 'Child of Parents 1 and 2', 'Parent 2' ); | ||
|
||
List<Object> expectedNodes = new List<Object> | ||
{ | ||
'Child 1 of Parent 1', | ||
'Child 2 of Parent 1', | ||
'Child 1 of Parent 2', | ||
'Child 2 of Parent 2', | ||
'Child of Parents 1 and 2', | ||
'Parent 1', | ||
'Parent 2', | ||
'Grandparent of both parents', | ||
'Great grandparent' | ||
}; | ||
|
||
List<Object> returnedNodes = graph.generateSorted(); | ||
|
||
System.assertEquals( expectedNodes, returnedNodes, 'generateSorted, when a complex graph has been built, will return the nodes in child to parent order' ); | ||
} | ||
|
||
@isTest | ||
private static void generateSorted_whenNoGraphIsSpecified_willReturnAnEmptyList() // NOPMD: Test method name format | ||
{ | ||
DirectedGraph graph = new DirectedGraph(); | ||
List<Object> expectedNodes = new List<Object>(); | ||
|
||
List<Object> returnedNodes = graph.generateSorted(); | ||
|
||
System.assertEquals( expectedNodes, returnedNodes, 'generateSorted, when no graph has been built, will return the an empty list' ); | ||
} | ||
|
||
@isTest | ||
private static void generateSorted_whenADuplicatedNodesAreSpecified_willReturnTheUniqueNodesInOrder() // NOPMD: Test method name format | ||
{ | ||
DirectedGraph graph = new DirectedGraph() | ||
.addNode( 'Great grandparent' ) | ||
.addNode( 'Great grandparent' ) | ||
.addNode( 'Great grandparent' ) | ||
.addNode( 'Grandparent' ) | ||
.addNode( 'Grandparent' ) | ||
.addNode( 'Grandparent' ) | ||
.addNode( 'Parent' ) | ||
.addNode( 'Parent' ) | ||
.addNode( 'Child' ) | ||
.addNode( 'Child' ) | ||
.addNode( 'Child' ) | ||
.addNode( 'Child' ) | ||
.addNode( 'Child' ) | ||
.addRelationship( 'Child', 'Parent' ) | ||
.addRelationship( 'Child', 'Parent' ) | ||
.addRelationship( 'Child', 'Parent' ) | ||
.addRelationship( 'Child', 'Parent' ) | ||
.addRelationship( 'Parent', 'Grandparent') | ||
.addRelationship( 'Parent', 'Grandparent') | ||
.addRelationship( 'Grandparent', 'Great grandparent' ); | ||
|
||
List<Object> expectedNodes = new List<Object> | ||
{ | ||
'Child', | ||
'Parent', | ||
'Grandparent', | ||
'Great grandparent' | ||
}; | ||
|
||
List<Object> returnedNodes = graph.generateSorted(); | ||
|
||
System.assertEquals( expectedNodes, returnedNodes, 'generateSorted, when duplicate nodes and relationships are specified, will return the unique nodes in child to parent order' ); | ||
} | ||
|
||
@isTest | ||
private static void generateSorted_whenACircularReference_willThrowAnException() // NOPMD: Test method name format | ||
{ | ||
DirectedGraph graph = new DirectedGraph() | ||
.addNode( 1 ) | ||
.addNode( 2 ) | ||
.addNode( 3 ) | ||
.addRelationship( 1, 2 ) | ||
.addRelationship( 2, 3 ) | ||
.addRelationship( 3, 1 ); | ||
Test.startTest(); | ||
String exceptionMessage; | ||
try | ||
{ | ||
graph.generateSorted(); | ||
} | ||
catch ( Exception e ) | ||
{ | ||
exceptionMessage = e.getMessage(); | ||
} | ||
Test.stopTest(); | ||
|
||
Amoss_Asserts.assertContains( 'The graph contains a circular reference and therefore cannot be resolved', exceptionMessage, 'generateSorted, when a circular reference has been defined, will throw an exception' ); | ||
} | ||
|
||
@isTest | ||
private static void addRelationship_whenGivenAnInvalidChild_willThrowAnException() // NOPMD: Test method name format | ||
{ | ||
DirectedGraph graph = new DirectedGraph() | ||
.addNode( 'Parent' ); | ||
Test.startTest(); | ||
String exceptionMessage; | ||
try | ||
{ | ||
graph.addRelationship( 'UnregisteredChild', 'Parent' ); | ||
} | ||
catch ( Exception e ) | ||
{ | ||
exceptionMessage = e.getMessage(); | ||
} | ||
Test.stopTest(); | ||
|
||
Amoss_Asserts.assertContains( 'addRelationship called with a child that has not been added as a node (UnregisteredChild)', exceptionMessage, 'addRelationship, when given a child that has not previously been added, will throw an exception' ); | ||
} | ||
|
||
@isTest | ||
private static void addRelationship_whenGivenAnInvalidParent_willThrowAnException() // NOPMD: Test method name format | ||
{ | ||
DirectedGraph graph = new DirectedGraph() | ||
.addNode( 'Child' ); | ||
Test.startTest(); | ||
String exceptionMessage; | ||
try | ||
{ | ||
graph.addRelationship( 'Child', 'UnregisteredParent' ); | ||
} | ||
catch ( Exception e ) | ||
{ | ||
exceptionMessage = e.getMessage(); | ||
} | ||
Test.stopTest(); | ||
|
||
Amoss_Asserts.assertContains( 'addRelationship called with a parent that has not been added as a node (UnregisteredParent)', exceptionMessage, 'addRelationship, when given a parent that has not previously been added, will throw an exception' ); | ||
} | ||
} |
5 changes: 5 additions & 0 deletions
5
framework/default/ortoo-core/default/classes/utils/tests/DirectedGraphTest.cls-meta.xml
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
<?xml version="1.0" encoding="UTF-8"?> | ||
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata"> | ||
<apiVersion>52.0</apiVersion> | ||
<status>Active</status> | ||
</ApexClass> |
Oops, something went wrong.