diff --git a/plugin/src/main/java/com/exadel/aem/toolkit/plugin/utils/ordering/Orderable.java b/plugin/src/main/java/com/exadel/aem/toolkit/plugin/utils/ordering/Orderable.java index 29b8ffde9..9b44c3fa4 100644 --- a/plugin/src/main/java/com/exadel/aem/toolkit/plugin/utils/ordering/Orderable.java +++ b/plugin/src/main/java/com/exadel/aem/toolkit/plugin/utils/ordering/Orderable.java @@ -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 Type of the entity @@ -23,9 +26,10 @@ class Orderable { private final T value; private final String id; private final int rank; + private boolean placeAnnotated; - private Orderable before; - private Orderable after; + private List> before = new ArrayList<>(); + private List> after = new ArrayList<>(); private int positionInAllNodes; @@ -71,7 +75,7 @@ public int getRank() { * Retrieves the {@code before} reference hint associated with this instance * @return {@code Orderable} object */ - public Orderable getBefore() { + public List> getBefore() { return before; } @@ -79,7 +83,7 @@ public Orderable getBefore() { * Assigns to the current instance an {@code Orderable} object that would represent its {@code before} hint * @param before {@code Orderable} instance */ - void setBefore(Orderable before) { // package-friendly setter for test cases + void setBefore(List> before) { // package-friendly setter for test cases this.before = before; } @@ -87,7 +91,7 @@ void setBefore(Orderable before) { // package-friendly setter for test cases * Retrieves the {@code after} reference hint associated with this instance * @return {@code Orderable} object */ - public Orderable getAfter() { + public List> getAfter() { return after; } @@ -95,7 +99,7 @@ public Orderable getAfter() { * 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 after) { // package-friendly setter for test cases + public void setAfter(List> after) { // package-friendly setter for test cases this.after = after; } @@ -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} */ diff --git a/plugin/src/main/java/com/exadel/aem/toolkit/plugin/utils/ordering/OrderingUtil.java b/plugin/src/main/java/com/exadel/aem/toolkit/plugin/utils/ordering/OrderingUtil.java index 12708c9d4..87360da90 100644 --- a/plugin/src/main/java/com/exadel/aem/toolkit/plugin/utils/ordering/OrderingUtil.java +++ b/plugin/src/main/java/com/exadel/aem/toolkit/plugin/utils/ordering/OrderingUtil.java @@ -78,11 +78,17 @@ public static List sortHandlers(List handlers) { Handles handles = orderableHandlers.get(i).getValue().getClass().getDeclaredAnnotation(Handles.class); if (!_Default.class.equals(handles.before())) { Orderable 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 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)); + } } } @@ -120,7 +126,10 @@ public static List sortMembers(List 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())) { @@ -130,7 +139,10 @@ public static List sortMembers(List 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)); + } } } } diff --git a/plugin/src/main/java/com/exadel/aem/toolkit/plugin/utils/ordering/TopologicalSorter.java b/plugin/src/main/java/com/exadel/aem/toolkit/plugin/utils/ordering/TopologicalSorter.java index 80e901e96..5e5703152 100644 --- a/plugin/src/main/java/com/exadel/aem/toolkit/plugin/utils/ordering/TopologicalSorter.java +++ b/plugin/src/main/java/com/exadel/aem/toolkit/plugin/utils/ordering/TopologicalSorter.java @@ -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; @@ -29,7 +28,6 @@ class TopologicalSorter { private final List> nodes; - private final List>> adjacencyList; /** * Initializes a class instance @@ -37,12 +35,6 @@ class TopologicalSorter { */ TopologicalSorter(List> 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(); } /** @@ -50,102 +42,102 @@ class TopologicalSorter { * @return List of entities with the sorting applied */ public List> 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> deque = new LinkedList<>(); - // List to store sorted order - List> sortedOrder = new ArrayList<>(this.nodes.size()); + List> 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 node : this.adjacencyList.get(i)) { - inDegrees[node.getPosition()]++; - } - } + Orderable orderable = nodes.get(i); + Deque> 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> after = after(orderable, new ArrayList<>()); + while (!after.isEmpty()) { + Orderable 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 currentNode = deque.pollFirst(); - sortedOrder.add(currentNode); - - int indexOfCurrentNode = currentNode.getPosition(); - - // Iterate a trough all neighbors for the current node (that means bfs) - for (Orderable adjacent : this.adjacencyList.get(indexOfCurrentNode)) { - int indexOfNeighborNode = adjacent.getPosition(); - inDegrees[indexOfNeighborNode]--; - if (inDegrees[indexOfNeighborNode] == 0) { - deque.addLast(adjacent); + Deque> before = before(orderable, new ArrayList<>()); + while (!before.isEmpty()) { + Orderable 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 currNode = this.nodes.get(i); - Orderable before = currNode.getBefore(); - Orderable 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> after(Orderable orderable, List> values) { + Deque> deque = new LinkedList<>(); + if (orderable == null) { + return deque; + } + for (Orderable orderable1 : orderable.getAfter()) { + if (!values.contains(orderable1)) { + values.add(orderable1); + Deque> after = after(orderable1, values); + while (!after.isEmpty()) { + deque.addFirst(after.removeLast()); + } + Deque> before = before(orderable1, values); + while (!before.isEmpty()) { + Orderable 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.>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> sortLoop(int[] inDegrees) { - List> loopNodes = new ArrayList<>(); - for (int i = 0; i < this.nodes.size(); i++) { - if (inDegrees[i] != 0) { - loopNodes.add(this.nodes.get(i)); + private Deque> before(Orderable orderable, List> values) { + Deque> deque = new LinkedList<>(); + if (orderable == null) { + return deque; + } + for (Orderable orderable1 : orderable.getBefore()) { + if (!values.contains(orderable1)) { + values.add(orderable1); + Deque> after = after(orderable1, values); + while (!after.isEmpty()) { + deque.addFirst(after.removeLast()); + } + Deque> before = before(orderable1, values); + while (!before.isEmpty()) { + Orderable tOrderable = before.removeFirst(); + if (!deque.contains(tOrderable)) { + deque.addLast(tOrderable); + } + } } } - return new TopologicalSorter<>(loopNodes).topologicalSort(); + if (!deque.contains(orderable)) { + deque.addFirst(orderable); + } + return deque; } } diff --git a/plugin/src/test/com/exadel/aem/toolkit/plugin/handlers/common/cases/handlerordering/ManualHandlerOrderingTestCases.java b/plugin/src/test/com/exadel/aem/toolkit/plugin/handlers/common/cases/handlerordering/ManualHandlerOrderingTestCases.java index 5da094b1e..7fe87afd3 100644 --- a/plugin/src/test/com/exadel/aem/toolkit/plugin/handlers/common/cases/handlerordering/ManualHandlerOrderingTestCases.java +++ b/plugin/src/test/com/exadel/aem/toolkit/plugin/handlers/common/cases/handlerordering/ManualHandlerOrderingTestCases.java @@ -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 @@ -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 diff --git a/plugin/src/test/com/exadel/aem/toolkit/plugin/utils/ordering/TopologicalSorterTest.java b/plugin/src/test/com/exadel/aem/toolkit/plugin/utils/ordering/TopologicalSorterTest.java index 61d65838c..61d6cd94a 100644 --- a/plugin/src/test/com/exadel/aem/toolkit/plugin/utils/ordering/TopologicalSorterTest.java +++ b/plugin/src/test/com/exadel/aem/toolkit/plugin/utils/ordering/TopologicalSorterTest.java @@ -15,6 +15,7 @@ import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Random; @@ -28,19 +29,19 @@ public class TopologicalSorterTest { private static final List CYCLED_GRAPH_SEQUENCE_2 = Arrays.asList( "Handler0", - "Handler5", "Handler6", - "Handler1", + "Handler5", "Handler2", "Handler3", - "Handler4"); + "Handler4", + "Handler1"); private static final List CYCLED_GRAPH_SEQUENCE_1 = Arrays.asList( "Handler0", - "Handler1", "Handler2", - "Handler4", - "Handler3"); + "Handler3", + "Handler1", + "Handler4"); private static final List SIMPLE_CYCLED_GRAPH_SEQUENCE = Arrays.asList( "Handler0", @@ -60,14 +61,26 @@ public class TopologicalSorterTest { private static final List REGULAR_GRAPH_SEQUENCE_2 = Arrays.asList( "Handler1", "Handler3", - "Handler8", - "Handler9", "Handler2", - "Handler5", "Handler4", "Handler6", + "Handler7", + "Handler0", + "Handler9", + "Handler5", + "Handler8"); + + private static final List REGULAR_GRAPH_SEQUENCE_3 = Arrays.asList( + "Handler2", + "Handler3", "Handler0", - "Handler7"); + "Handler1"); + + private static final List REGULAR_GRAPH_SEQUENCE_4 = Arrays.asList( + "Handler0", + "Handler2", + "Handler3", + "Handler1"); // Test, that size will be equal to initial size and ordered list consists of unique nodes @Test @@ -84,11 +97,20 @@ public void testRandomOrderableList() { public void testGraphWithCycleInside1() { List> list = getList(5); - list.get(0).setBefore(list.get(1)); - list.get(1).setBefore(list.get(2)); - list.get(4).setAfter(list.get(1)); - list.get(2).setBefore(list.get(3)); - list.get(3).setBefore(list.get(1)); + list.get(0).getBefore().add(list.get(1)); + list.get(1).getAfter().add(list.get(0)); + + list.get(1).getBefore().add(list.get(2)); + list.get(2).getAfter().add(list.get(1)); + + list.get(2).getBefore().add(list.get(3)); + list.get(3).getAfter().add(list.get(2)); + + list.get(3).getBefore().add(list.get(1)); + list.get(1).getAfter().add(list.get(3)); + + list.get(4).getAfter().add(list.get(1)); + list.get(1).getBefore().add(0, list.get(4)); List answer = getSortedByValues(list); @@ -100,13 +122,26 @@ public void testGraphWithCycleInside1() { public void testGraphWithCycleInside2() { List> list = getList(7); - list.get(0).setBefore(list.get(1)); - list.get(1).setBefore(list.get(2)); - list.get(2).setBefore(list.get(3)); - list.get(3).setBefore(list.get(4)); - list.get(1).setAfter(list.get(3)); - list.get(5).setBefore(list.get(1)); - list.get(6).setBefore(list.get(1)); + list.get(0).getBefore().add(list.get(1)); + list.get(1).getAfter().add(list.get(0)); + + list.get(1).getBefore().add(list.get(2)); + list.get(2).getAfter().add(list.get(1)); + + list.get(1).getAfter().add(list.get(3)); + list.get(3).getBefore().add(0, list.get(1)); + + list.get(2).getBefore().add(list.get(3)); + list.get(3).getAfter().add(list.get(2)); + + list.get(3).getBefore().add(list.get(4)); + list.get(4).getAfter().add(list.get(3)); + + list.get(5).getBefore().add(list.get(1)); + list.get(1).getAfter().add(list.get(5)); + + list.get(6).getBefore().add(list.get(1)); + list.get(1).getAfter().add(list.get(6)); List answer = getSortedByValues(list); @@ -117,10 +152,10 @@ public void testGraphWithCycleInside2() { public void testSimpleCycleGraph() { List> list = getList(4); - list.get(0).setBefore(list.get(1)); - list.get(1).setBefore(list.get(2)); - list.get(2).setBefore(list.get(3)); - list.get(3).setBefore(list.get(0)); + list.get(0).setBefore(Collections.singletonList(list.get(1))); + list.get(1).setBefore(Collections.singletonList(list.get(2))); + list.get(2).setBefore(Collections.singletonList(list.get(3))); + list.get(3).setBefore(Collections.singletonList(list.get(0))); List answer = getSortedByValues(list); @@ -131,18 +166,29 @@ public void testSimpleCycleGraph() { public void testExampleGraph1() { List> list = getList(10); - list.get(1).setBefore(list.get(0)); - list.get(2).setBefore(list.get(6)); - list.get(3).setBefore(list.get(2)); - list.get(4).setAfter(list.get(2)); - list.get(6).setBefore(list.get(0)); - list.get(7).setAfter(list.get(6)); - list.get(9).setBefore(list.get(5)); + list.get(1).getBefore().add(list.get(0)); + list.get(0).getAfter().add(list.get(1)); + + list.get(2).getBefore().add(list.get(6)); + list.get(6).getAfter().add(list.get(2)); + + list.get(3).getBefore().add(list.get(2)); + list.get(2).getAfter().add(list.get(3)); + + list.get(4).getAfter().add(list.get(2)); + list.get(2).getBefore().add(0, list.get(4)); + + list.get(6).getBefore().add(list.get(0)); + list.get(0).getAfter().add(list.get(6)); + + list.get(7).getAfter().add(list.get(6)); + list.get(6).getBefore().add(0, list.get(7)); + + list.get(9).getBefore().add(list.get(5)); + list.get(5).getAfter().add(list.get(9)); - List> answer = getTopologicalSorted(list); List answerValues = getSortedByValues(list); - assertOrdered(answer, list); Assert.assertEquals(REGULAR_GRAPH_SEQUENCE_2, answerValues); } @@ -151,13 +197,41 @@ public void testExampleGraph1() { public void testExampleGraph2() { List> list = getList(7); - List> answer = getTopologicalSorted(list); List answerValues = getSortedByValues(list); - assertOrdered(answer, list); Assert.assertEquals(REGULAR_GRAPH_SEQUENCE_1, answerValues); } + @Test + public void testExampleGraph3() { + List> list = getList(4); + + list.get(2).getBefore().add(list.get(0)); + list.get(0).getAfter().add( list.get(2)); + + list.get(3).getBefore().add(list.get(0)); + list.get(0).getAfter().add(list.get(3)); + + List answerValues = getSortedByValues(list); + + Assert.assertEquals(REGULAR_GRAPH_SEQUENCE_3, answerValues); + } + + @Test + public void testExampleGraph4() { + List> list = getList(4); + + list.get(2).getAfter().add(list.get(0)); + list.get(0).getBefore().add(0, list.get(2)); + + list.get(3).getAfter().add(list.get(0)); + list.get(0).getBefore().add(0, list.get(3)); + + List answerValues = getSortedByValues(list); + + Assert.assertEquals(REGULAR_GRAPH_SEQUENCE_4, answerValues); + } + // Inits list without edges private List> getList(int size) { return IntStream.range(0, size) @@ -184,12 +258,12 @@ private List> getRandomList(int size) { iBefore = random.nextInt(size); } if (mode == 0) { - list.get(i).setAfter(list.get(iAfter)); + list.get(i).setAfter(Collections.singletonList(list.get(iAfter))); } else if (mode == 1) { - list.get(i).setBefore(list.get(iBefore)); + list.get(i).setBefore(Collections.singletonList(list.get(iBefore))); } else { - list.get(i).setBefore(list.get(iBefore)); - list.get(i).setBefore(list.get(iAfter)); + list.get(i).setBefore(Collections.singletonList(list.get(iBefore))); + list.get(i).setBefore(Collections.singletonList(list.get(iAfter))); } } return list; @@ -203,20 +277,6 @@ private List getSortedByValues(List> list) { return getTopologicalSorted(list).stream().map(Orderable::getValue).collect(Collectors.toList()); } - private void assertOrdered(List> answer, List> initial) { - for (Orderable node : initial) { - int currentPosition = answer.indexOf(node); - int beforePosition = node.getBefore() != null - ? answer.indexOf(node.getBefore()) - : initial.size(); - int afterPosition = node.getAfter() != null - ? answer.indexOf(node.getAfter()) - : 0; - Assert.assertTrue(beforePosition >= currentPosition); - Assert.assertTrue(currentPosition >= afterPosition); - } - } - private void assertOnlyUniqueValues(List answer) { Assert.assertEquals(answer.size(), new HashSet<>(answer).size()); } diff --git a/plugin/src/test/resources/handlers/placement/memberOrdering/ordering1/_cq_dialog.xml b/plugin/src/test/resources/handlers/placement/memberOrdering/ordering1/_cq_dialog.xml index 224666127..273cb3023 100644 --- a/plugin/src/test/resources/handlers/placement/memberOrdering/ordering1/_cq_dialog.xml +++ b/plugin/src/test/resources/handlers/placement/memberOrdering/ordering1/_cq_dialog.xml @@ -17,14 +17,14 @@ - - - - + + + +