Skip to content

Commit

Permalink
Merge pull request #482 from exadel-inc/EAK-427
Browse files Browse the repository at this point in the history
[EAK-427] Fix sorting of fields and handlers with before="..." and after="..."
  • Loading branch information
smiakchilo authored Nov 9, 2023
2 parents 354c39a + 14e1fe3 commit 046e5cf
Show file tree
Hide file tree
Showing 6 changed files with 238 additions and 154 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@
*/
package com.exadel.aem.toolkit.plugin.utils.ordering;

import java.util.ArrayList;
import java.util.List;

/**
* Presents an abstraction of an entity able to be managed by an ordering routine
* @param <T> Type of the entity
Expand All @@ -23,9 +26,10 @@ class Orderable<T> {
private final T value;
private final String id;
private final int rank;
private boolean placeAnnotated;

private Orderable<T> before;
private Orderable<T> after;
private List<Orderable<T>> before = new ArrayList<>();
private List<Orderable<T>> after = new ArrayList<>();

private int positionInAllNodes;

Expand Down Expand Up @@ -71,31 +75,31 @@ public int getRank() {
* Retrieves the {@code before} reference hint associated with this instance
* @return {@code Orderable} object
*/
public Orderable<T> getBefore() {
public List<Orderable<T>> getBefore() {
return before;
}

/**
* Assigns to the current instance an {@code Orderable} object that would represent its {@code before} hint
* @param before {@code Orderable} instance
*/
void setBefore(Orderable<T> before) { // package-friendly setter for test cases
void setBefore(List<Orderable<T>> before) { // package-friendly setter for test cases
this.before = before;
}

/**
* Retrieves the {@code after} reference hint associated with this instance
* @return {@code Orderable} object
*/
public Orderable<T> getAfter() {
public List<Orderable<T>> getAfter() {
return after;
}

/**
* Assigns to the current instance an {@code Orderable} object that would represent its {@code after} hint
* @param after {@code Orderable} instance
*/
public void setAfter(Orderable<T> after) { // package-friendly setter for test cases
public void setAfter(List<Orderable<T>> after) { // package-friendly setter for test cases
this.after = after;
}

Expand Down Expand Up @@ -123,6 +127,22 @@ public void setPosition(int position) {
this.positionInAllNodes = position;
}

/**
* Gets whether the field was annotated with Place annotation
* @return boolean value
*/
public boolean isPlaceAnnotated() {
return placeAnnotated;
}

/**
* Sets the flag that field annotated with Place annotation
* @param placeAnnotated Boolean value signifying the field is annotated with Place annotation
*/
public void setPlaceAnnotated(boolean placeAnnotated) {
this.placeAnnotated = placeAnnotated;
}

/**
* {@inheritDoc}
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,11 +78,17 @@ public static <T> List<T> sortHandlers(List<T> handlers) {
Handles handles = orderableHandlers.get(i).getValue().getClass().getDeclaredAnnotation(Handles.class);
if (!_Default.class.equals(handles.before())) {
Orderable<T> before = findSibling(handles.before().getName(), orderableHandlers);
orderableHandlers.get(i).setBefore(before);
orderableHandlers.get(i).getBefore().add(before);
if (before != null) {
before.getAfter().add(orderableHandlers.get(i));
}
}
if (!_Default.class.equals(handles.after())) {
Orderable<T> after = findSibling(handles.after().getName(), orderableHandlers);
orderableHandlers.get(i).setAfter(after);
orderableHandlers.get(i).getAfter().add(after);
if (after != null) {
after.getBefore().add(0, orderableHandlers.get(i));
}
}
}

Expand Down Expand Up @@ -120,7 +126,10 @@ public static List<Source> sortMembers(List<Source> sources) {
sources.get(i).adaptTo(MemberSource.class).getDeclaringClass()
),
list);
list.get(i).setBefore(before);
list.get(i).getBefore().add(before);
if (before != null) {
before.getAfter().add(list.get(i));
}
}
ClassMember classMemberAfter = place.after();
if (StringUtils.isNotBlank(classMemberAfter.value())) {
Expand All @@ -130,7 +139,10 @@ public static List<Source> sortMembers(List<Source> sources) {
sources.get(i).adaptTo(MemberSource.class).getDeclaringClass()
),
list);
list.get(i).setAfter(after);
list.get(i).getAfter().add(after);
if (after != null) {
after.getBefore().add(0, list.get(i));
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
package com.exadel.aem.toolkit.plugin.utils.ordering;

import java.util.ArrayList;
import java.util.Comparator;
import java.util.Deque;
import java.util.LinkedList;
import java.util.List;
Expand All @@ -29,123 +28,116 @@
class TopologicalSorter<T> {

private final List<Orderable<T>> nodes;
private final List<List<Orderable<T>>> adjacencyList;

/**
* Initializes a class instance
* @param nodes Collection of entities to be sorted
*/
TopologicalSorter(List<Orderable<T>> nodes) {
this.nodes = nodes;
this.adjacencyList = new ArrayList<>(this.nodes.size());
for (int i = 0; i < this.nodes.size(); i++) {
this.nodes.get(i).setPosition(i);
this.adjacencyList.add(new ArrayList<>());
}
initAdjacencyList();
}

/**
* Performs the main sorting routine
* @return List of entities with the sorting applied
*/
public List<Orderable<T>> topologicalSort() {
// Array to store how many edges are incoming to the i node
int[] inDegrees = new int[this.nodes.size()];
// Deque for bfs that store nodes in special order for bfs
Deque<Orderable<T>> deque = new LinkedList<>();
// List to store sorted order
List<Orderable<T>> sortedOrder = new ArrayList<>(this.nodes.size());
List<Orderable<T>> sorted = new ArrayList<>();

// Loop to count how many edges are incoming to the i node
for (int i = 0; i < this.nodes.size(); i++) {
for (Orderable<T> node : this.adjacencyList.get(i)) {
inDegrees[node.getPosition()]++;
}
}
Orderable<T> orderable = nodes.get(i);
Deque<Orderable<T>> temp = new LinkedList<>();

// Loop to init dequeue with nodes, that do not have incoming edges
for (int i = 0; i < this.nodes.size(); i++) {
if (inDegrees[i] == 0) {
deque.addLast(nodes.get(i));
Deque<Orderable<T>> after = after(orderable, new ArrayList<>());
while (!after.isEmpty()) {
Orderable<T> tOrderable = after.removeLast();
if (!sorted.contains(tOrderable) && !temp.contains(tOrderable)) {
temp.addFirst(tOrderable);
}
}
}

// Check if entire graph is loop
if (deque.isEmpty()) {
// Set that the first node do not have incoming nodes
inDegrees[0] = 0;
// Add the first node to deque to start bfs from the first node
deque.addLast(nodes.get(0));
// Remove all edges incoming to the first node
this.adjacencyList.forEach(list -> list.remove(nodes.get(0)));
}

// Start of bfs
while (!deque.isEmpty()) {
Orderable<T> currentNode = deque.pollFirst();
sortedOrder.add(currentNode);

int indexOfCurrentNode = currentNode.getPosition();

// Iterate a trough all neighbors for the current node (that means bfs)
for (Orderable<T> adjacent : this.adjacencyList.get(indexOfCurrentNode)) {
int indexOfNeighborNode = adjacent.getPosition();
inDegrees[indexOfNeighborNode]--;
if (inDegrees[indexOfNeighborNode] == 0) {
deque.addLast(adjacent);
Deque<Orderable<T>> before = before(orderable, new ArrayList<>());
while (!before.isEmpty()) {
Orderable<T> tOrderable = before.removeFirst();
if (!sorted.contains(tOrderable) && !temp.contains(tOrderable)) {
temp.addLast(tOrderable);
}
}
}

// Check if not all nodes are visited
if (sortedOrder.size() != this.nodes.size()) {
sortedOrder.addAll(sortLoop(inDegrees));
if (!sorted.contains(orderable) && !temp.contains(orderable)) {
temp.addLast(orderable);
}

sorted.addAll(temp);
}
return sortedOrder;
return sorted;
}

/**
* Initializes the collection of ordered lists used to represent a finite graph
* Called by {@link TopologicalSorter#topologicalSort()} to get all entities connected to
* the entity as 'after' relationship
* @param orderable Orderable entity
* @param values List of all already used entities in sort
* @return Deque of connected entities
*/
private void initAdjacencyList() {
for (int i = 0; i < this.nodes.size(); i++) {
Orderable<T> currNode = this.nodes.get(i);
Orderable<T> before = currNode.getBefore();
Orderable<T> after = currNode.getAfter();
// Check for null and self-loop
if (before != null && !before.equals(currNode)) {
this.adjacencyList.get(i).add(before);
}
// Check for null, self-loop and simple cycle, e.g. 1->2 and 2->1
if (after != null
&& !after.equals(currNode)
&& !this.adjacencyList.get(after.getPosition()).contains(currNode)) {
this.adjacencyList.get(after.getPosition()).add(currNode);
private Deque<Orderable<T>> after(Orderable<T> orderable, List<Orderable<T>> values) {
Deque<Orderable<T>> deque = new LinkedList<>();
if (orderable == null) {
return deque;
}
for (Orderable<T> orderable1 : orderable.getAfter()) {
if (!values.contains(orderable1)) {
values.add(orderable1);
Deque<Orderable<T>> after = after(orderable1, values);
while (!after.isEmpty()) {
deque.addFirst(after.removeLast());
}
Deque<Orderable<T>> before = before(orderable1, values);
while (!before.isEmpty()) {
Orderable<T> tOrderable = before.removeFirst();
if (!deque.contains(tOrderable)) {
deque.addLast(tOrderable);
}
}
}
}
// Sort every list to keep the fallback order (that is, by ranking, if there's ranking specified,
// and then alphabetically)
for (int i = 0; i < this.nodes.size(); i++) {
this.adjacencyList.get(i).sort(Comparator.<Orderable<T>>comparingInt(Orderable::getRank).thenComparing(Orderable::getId));
if (!deque.contains(orderable)) {
deque.addLast(orderable);
}
return deque;
}

/**
* Called by {@link TopologicalSorter#topologicalSort()} when there's no possibility to process all the nodes
* in a single run (due to a loop-like relation when e.g. two nodes refer to each other in their "before" hints).
* This method collects the nodes that are involved in a loop-like relation and composes a separate graph in order
* to perform another sorting run for these nodes separately
* @param inDegrees Array of integer values defining the number of incoming edges
* @return List of entities with the sorting applied
* Called by {@link TopologicalSorter#topologicalSort()} to get all entities connected to
* the entity as 'before' relationship
* @param orderable Orderable entity
* @param values List of all already used entities in sort
* @return Deque of connected entities
*/
private List<Orderable<T>> sortLoop(int[] inDegrees) {
List<Orderable<T>> loopNodes = new ArrayList<>();
for (int i = 0; i < this.nodes.size(); i++) {
if (inDegrees[i] != 0) {
loopNodes.add(this.nodes.get(i));
private Deque<Orderable<T>> before(Orderable<T> orderable, List<Orderable<T>> values) {
Deque<Orderable<T>> deque = new LinkedList<>();
if (orderable == null) {
return deque;
}
for (Orderable<T> orderable1 : orderable.getBefore()) {
if (!values.contains(orderable1)) {
values.add(orderable1);
Deque<Orderable<T>> after = after(orderable1, values);
while (!after.isEmpty()) {
deque.addFirst(after.removeLast());
}
Deque<Orderable<T>> before = before(orderable1, values);
while (!before.isEmpty()) {
Orderable<T> tOrderable = before.removeFirst();
if (!deque.contains(tOrderable)) {
deque.addLast(tOrderable);
}
}
}
}
return new TopologicalSorter<>(loopNodes).topologicalSort();
if (!deque.contains(orderable)) {
deque.addFirst(orderable);
}
return deque;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ public void accept(Source source, Target target) {
}
}

@Handles(value = WidgetAnnotationForOrderingTest.class, before = CustomHandler3.class, after = CustomHandler1.class)
@Handles(value = WidgetAnnotationForOrderingTest.class, before = CustomHandler2.class, after = CustomHandler1.class)
public static class CustomHandler1 implements Handler {

@Override
Expand All @@ -78,7 +78,7 @@ public void accept(Source source, Target target) {
}
}

@Handles(value = WidgetAnnotationForOrderingTest.class, before = CustomHandler0.class, after = CustomHandler3.class)
@Handles(value = WidgetAnnotationForOrderingTest.class, after = CustomHandler3.class)
public static class CustomHandler3 implements Handler {

@Override
Expand Down
Loading

0 comments on commit 046e5cf

Please sign in to comment.