View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  
20  package org.apache.myfaces.tobago.internal.config;
21  
22  import org.slf4j.Logger;
23  import org.slf4j.LoggerFactory;
24  
25  import java.lang.invoke.MethodHandles;
26  import java.util.ArrayList;
27  import java.util.List;
28  
29  public class TobagoConfigSorter {
30  
31    private static final Logger LOG = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
32  
33    private final List<Vertex> vertices = new ArrayList<>();
34  
35    public TobagoConfigSorter(final List<TobagoConfigFragment> fragmentList) {
36      for (TobagoConfigFragment tobagoConfigFragment : fragmentList) {
37        vertices.add(new Vertex(tobagoConfigFragment));
38      }
39    }
40  
41    /**
42     * Topological sorting with setup and cycle check.
43     *
44     * @throws IllegalStateException When detecting a cycle.
45     */
46    public List<TobagoConfigFragment> topologicalSort() {
47  
48      createEdges();
49      checkCycles();
50  
51      List<TobagoConfigFragment> result = new ArrayList<>();
52  
53      for (Vertex vertex : vertices) {
54        topologicalSort0(vertex, result);
55      }
56  
57      logResult(result);
58  
59      return result;
60    }
61  
62    /**
63     * Internal recursive method for the topological sort.
64     */
65    private void topologicalSort0(Vertex vertex, List<TobagoConfigFragment> result) {
66      if (vertex.isVisited()) {
67        return;
68      }
69  
70      vertex.setVisited(true);
71  
72      // recursion for all vertices adjacent to this vertex
73      for (Vertex adjacent : vertex.getAdjacencyList()) {
74        topologicalSort0(adjacent, result);
75      }
76  
77      result.add(vertex.getFragment());
78    }
79  
80    private void logResult(List<TobagoConfigFragment> result) {
81      if (LOG.isInfoEnabled()) {
82        StringBuilder builder = new StringBuilder("Order of the Tobago config files: ");
83        for (TobagoConfigFragment fragment : result) {
84          final String name = fragment.getName();
85          if (LOG.isDebugEnabled()) {
86            builder.append("name=");
87            if (name == null) {
88              builder.append("<unnamed>");
89            } else {
90              builder.append("'");
91              builder.append(name);
92              builder.append("'");
93            }
94            builder.append(" url='");
95            builder.append(fragment.getUrl());
96            builder.append("'");
97          } else {
98            builder.append(name);
99            builder.append(", ");
100         }
101       }
102       LOG.info(builder.toString());
103     }
104   }
105 
106 
107   private void createEdges() {
108 
109     // collecting all relations, which are relevant for us. We don't need "before" and "after" of unknown names.
110     for (final Vertex vertex : vertices) {
111       final TobagoConfigFragment current = vertex.getFragment();
112 
113       for (final String befores : current.getBefore()) {
114         final TobagoConfigFragment before = findByName(befores);
115         if (before != null) {
116           findVertex(before).addAdjacent(findVertex(current));
117         }
118       }
119       for (final String afters : current.getAfter()) {
120         final TobagoConfigFragment after = findByName(afters);
121         if (after != null) {
122           findVertex(current).addAdjacent(findVertex(after));
123         }
124       }
125     }
126   }
127 
128   /**
129    * Cycle detection: if the base in reachable form its own, than there is a cycle.
130    *
131    * @throws IllegalStateException When detecting a cycle.
132    */
133   private void checkCycles() {
134     LOG.debug("Cycle detection:");
135     for (Vertex vertex : vertices) {
136       if (LOG.isDebugEnabled()) {
137         LOG.debug("Checking reachable vertices from base {}", vertex.getFragment());
138       }
139       checkCycles0(vertex, vertex);
140     }
141   }
142 
143   private void checkCycles0(final Vertex vertex, final Vertex base) {
144     if (LOG.isDebugEnabled()) {
145       LOG.debug("vertex: {}", vertex.toString());
146       LOG.debug("base:   {}", base.getFragment().toString());
147     }
148     for (Vertex adjacent : vertex.getAdjacencyList()) {
149       if (base == adjacent) {
150         throw new IllegalStateException("Cycle detected name='" + vertex + "' base=" + base + "! ");
151       }
152       checkCycles0(adjacent, base);
153     }
154   }
155 
156   private Vertex findVertex(final TobagoConfigFragment fragment) {
157     for (Vertex vertex : vertices) {
158       if (vertex.getFragment() == fragment) {
159         return vertex;
160       }
161     }
162     throw new RuntimeException("Problem with sorting! This might be a bug.");
163   }
164 
165   private TobagoConfigFragment findByName(final String name) {
166     for (final Vertex vertex : vertices) {
167       TobagoConfigFragment fragment = vertex.getFragment();
168       if (name.equals(fragment.getName())) {
169         return fragment;
170       }
171     }
172     return null;
173   }
174 
175   private static class Vertex {
176 
177     private final TobagoConfigFragment fragment;
178     private final List<Vertex> adjacencyList;
179     private boolean visited;
180 
181     private Vertex(final TobagoConfigFragment fragment) {
182       this.fragment = fragment;
183       this.adjacencyList = new ArrayList<>();
184     }
185 
186     public boolean isVisited() {
187       return visited;
188     }
189 
190     public void setVisited(boolean visited) {
191       this.visited = visited;
192     }
193 
194     public TobagoConfigFragment getFragment() {
195       return fragment;
196     }
197 
198     public void addAdjacent(Vertex higher) {
199       adjacencyList.add(higher);
200     }
201 
202     public List<Vertex> getAdjacencyList() {
203       return adjacencyList;
204     }
205 
206     @Override
207     public String toString() {
208       StringBuilder builder = new StringBuilder();
209       builder.append(fragment);
210       builder.append(" -> [");
211       for (Vertex vertex : adjacencyList) {
212         builder.append(vertex.getFragment());
213         builder.append(", ");
214       }
215       if (builder.charAt(builder.length() - 1) == ' ') {
216         builder.delete(builder.length() - 2, builder.length());
217       }
218       builder.append("]");
219       return builder.toString();
220     }
221   }
222 }