View Javadoc
1   /*
2    * Copyright 2018 the original author or authors.
3    *
4    *  Licensed under the Apache License, Version 2.0 (the "License");
5    *  you may not use this file except in compliance with the License.
6    *  You may obtain a copy of the License at
7    *
8    *         http://www.apache.org/licenses/LICENSE-2.0
9    *
10   *  Unless required by applicable law or agreed to in writing, software
11   *  distributed under the License is distributed on an "AS IS" BASIS,
12   *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   *  See the License for the specific language governing permissions and
14   *  limitations under the License.
15   */
16  
17  package org.openehealth.ipf.commons.ihe.core.chain;
18  
19  import org.slf4j.Logger;
20  import org.slf4j.LoggerFactory;
21  
22  import java.util.ArrayList;
23  import java.util.Collection;
24  import java.util.Iterator;
25  import java.util.List;
26  import java.util.stream.Collectors;
27  
28  /**
29   * @author Christian Ohr
30   * @author Dmytro Rud
31   */
32  public class ChainUtils {
33  
34      private static final transient Logger LOG = LoggerFactory.getLogger(ChainUtils.class);
35  
36      /**
37       * Extends an initial chain with elements from a custom collection.
38       *
39       * @param initial initial chain, may be empty, but not <code>null</code>.
40       * @param custom  collection of objects to be added to the initial chain,
41       *                may be empty, but not <code>null</code>.
42       * @return merged chain.
43       * @throws ChainException when chain extension fails, e.g. when cyclic dependencies are discovered.
44       */
45      public static <T extends Chainable> List<T> createChain(List<T> initial, Collection<T> custom) {
46          List<T> chain = new ArrayList<>(initial);
47          List<T> unprocessed = new ArrayList<>(custom);
48  
49          List<String> chainIds = chain.stream()
50                  .map(Chainable::getId)
51                  .collect(Collectors.toList());
52  
53          while (!unprocessed.isEmpty()) {
54              boolean successful = false;
55  
56              Iterator<T> iter = unprocessed.iterator();
57              while (iter.hasNext()) {
58                  final T c = iter.next();
59                  final String cid = c.getId();
60  
61                  // check whether element with this ID is already in the chain
62                  if (chainIds.contains(cid)) {
63                      LOG.debug("Element {} is already in the chain, ignore it", cid);
64                      iter.remove();
65                      successful = true;
66                      continue;
67                  }
68  
69                  List<T> unProcessedWithoutC = new ArrayList<>(unprocessed);
70                  unProcessedWithoutC.remove(c);
71                  // check whether this element depends on some other unprocessed ones
72                  List<T> unprocessedDependencies = unProcessedWithoutC.stream()
73                          .filter(other ->
74                                  (c.getBefore().contains(other.getId()) && !(other.getAfter().contains(cid))) ||
75                                  (c.getAfter().contains(other.getId()) && !(other.getBefore().contains(cid)))
76                          )
77                          .collect(Collectors.toList());
78  
79                  if (!unprocessedDependencies.isEmpty()) {
80                      LOG.debug("Element {} depends on {}", cid,
81                              unprocessedDependencies.stream()
82                                      .map(Chainable::getId)
83                                      .collect(Collectors.joining(" ")));
84                      continue;
85                  }
86  
87                  // Look where to insert this element.  When neither "before" nor "after"
88                  // are known -- insert at the end, i.e. directly before the standard Camel
89                  // consumer -- this corresponds to the old behaviour.
90                  int position = chain.size();
91  
92                  List<Integer> beforeIndices = c.getBefore().stream()
93                          .map(chainIds::indexOf)
94                          .filter(value -> value >= 0)
95                          .collect(Collectors.toList());
96  
97                  List<Integer> afterIndices = c.getAfter().stream()
98                          .map(chainIds::indexOf)
99                          .filter(value -> value >= 0)
100                         .collect(Collectors.toList());
101 
102                 int minBeforePosition = 0;
103                 int maxAfterPosition = 0;
104 
105                 if (!beforeIndices.isEmpty()) {
106                     minBeforePosition = beforeIndices.stream().min(Integer::compare).get();
107                     position = minBeforePosition;
108                 }
109 
110                 if (!afterIndices.isEmpty()) {
111                     maxAfterPosition = afterIndices.stream().max(Integer::compare).get();
112                     position = maxAfterPosition + 1;
113                 }
114 
115                 if (!beforeIndices.isEmpty() && !afterIndices.isEmpty() && (minBeforePosition <= maxAfterPosition)) {
116                     throw new ChainException(String.format(
117                             "Cannot place %s between %s and %s in %s",
118                             cid,
119                             chainIds.get(maxAfterPosition),
120                             chainIds.get(minBeforePosition),
121                             chainIds.stream().collect(Collectors.joining(" "))));
122                 }
123 
124                 chain.add(position, c);
125                 chainIds.add(position, cid);
126                 iter.remove();
127                 LOG.debug("Inserted element {} at position {}", cid, position);
128                 successful = true;
129             }
130 
131             if (successful) {
132                 LOG.debug("Iteration result: {} elements in the chain, {} elements left", chain.size(), unprocessed.size());
133             } else {
134                 throw new ChainException("Cannot build a chain, probably there is a dependency loop");
135             }
136         }
137 
138         return chain;
139     }
140 }