View Javadoc
1   package com.atlassian.plugin.manager;
2   
3   import com.atlassian.plugin.Plugin;
4   import com.atlassian.plugin.PluginDependencies;
5   import com.google.common.collect.ArrayListMultimap;
6   import com.google.common.collect.Maps;
7   import com.google.common.collect.Multimap;
8   import com.google.common.collect.Sets;
9   import io.atlassian.fugue.Pair;
10  
11  import javax.annotation.Nonnull;
12  import java.util.ArrayDeque;
13  import java.util.ArrayList;
14  import java.util.Collection;
15  import java.util.Deque;
16  import java.util.EnumSet;
17  import java.util.Iterator;
18  import java.util.List;
19  import java.util.Map;
20  import java.util.Set;
21  import java.util.SortedMap;
22  import java.util.TreeMap;
23  
24  import static io.atlassian.fugue.Pair.pair;
25  
26  /**
27   * A collection of dependent plugins
28   *
29   * @since v4.0
30   */
31  final class DependentPlugins {
32      private final SortedMap<Plugin, PluginDependencies.Type> plugins;
33  
34      /**
35       * Obtain the plugins which require, possibly indirectly, given plugins specified by keys.
36       * <p>
37       * The plugins with given keys is not included in the returned map.
38       * <p>
39       * Plugins mapped by their most significant dependency type, with the following order of significance:
40       * {@link PluginDependencies.Type#MANDATORY},{@link PluginDependencies.Type#OPTIONAL}, {@link
41       * PluginDependencies.Type#DYNAMIC}.
42       *
43       * @param rootPluginKeys  plugin keys to list dependents of.
44       * @param pluginList      a List of plugins
45       * @param dependencyTypes only include plugins with these dependency types
46       */
47      public DependentPlugins(final Collection<String> rootPluginKeys, final Iterable<Plugin> pluginList, final Set<PluginDependencies.Type> dependencyTypes) {
48          if (dependencyTypes.isEmpty()) {
49              throw new IllegalArgumentException("Dependency types must be provided");
50          }
51          plugins = new TreeMap<>();
52          final PluginDependencies.Type leastSignificantType = getLeastSignificantType(dependencyTypes);
53          buildPluginDependencies(rootPluginKeys, dependencyTypes, dependencyMap(pluginList, leastSignificantType));
54      }
55  
56      private void buildPluginDependencies(final Collection<String> rootPluginKeys,
57                                           final Set<PluginDependencies.Type> dependencyTypes,
58                                           final Multimap<String, Pair<Plugin, PluginDependencies.Type>> dependencies) {
59          // A queue of the plugin keys with capped dependency type we have yet to explore dependencies of
60          final DependencyQueue queue = new DependencyQueue();
61  
62          final Set<CappedDep> visited = Sets.newHashSet();
63          for (String rootPluginKey : rootPluginKeys) {
64              queue.addLast(new CappedDep(rootPluginKey, PluginDependencies.Type.MANDATORY));
65              for (PluginDependencies.Type type : PluginDependencies.Type.values()) {
66                  visited.add(new CappedDep(rootPluginKey, type));
67              }
68          }
69  
70          while (!queue.isEmpty()) {
71              final CappedDep currentPlugin = queue.removeFirst();
72              for (final Pair<Plugin, PluginDependencies.Type> pluginWithDependencyType : dependencies.get(currentPlugin.key)) {
73                  final PluginDependencies.Type dependencyType = currentPlugin.cap(pluginWithDependencyType.right());
74  
75                  if (dependencyTypes.contains(dependencyType)) {
76                      final Plugin dependentPlugin = pluginWithDependencyType.left();
77                      final String dependentPluginKey = dependentPlugin.getKey();
78  
79                      final CappedDep newDep = new CappedDep(dependentPluginKey, dependencyType);
80                      if (visited.add(newDep)) {
81                          this.add(dependentPlugin, dependencyType);
82  
83                          queue.addLast(newDep);
84                      }
85                  }
86  
87              }
88          }
89      }
90  
91      /**
92       * Compute dependencies map from a key to each plugin that requires it,
93       * cutting out dependencies less significant than the leastSignificantType
94       */
95      private Multimap<String, Pair<Plugin, PluginDependencies.Type>> dependencyMap(final Iterable<Plugin> pluginList, final PluginDependencies.Type leastSignificantType) {
96          final Multimap<String, Pair<Plugin, PluginDependencies.Type>> dependencies = ArrayListMultimap.create();
97          for (final Plugin p : pluginList) {
98              for (final Map.Entry<String, PluginDependencies.Type> keyType : p.getDependencies().getByPluginKey().entries()) {
99                  if (!keyType.getValue().lessSignificant(leastSignificantType)) {
100                     // Cut out dependencies that less significant that we want
101                     dependencies.put(keyType.getKey(), pair(p, keyType.getValue()));
102                 }
103             }
104         }
105         return dependencies;
106     }
107 
108     private PluginDependencies.Type getLeastSignificantType(final Set<PluginDependencies.Type> dependencyTypes) {
109         PluginDependencies.Type leastSignificantType = PluginDependencies.Type.MANDATORY;
110         for (final PluginDependencies.Type type : dependencyTypes) {
111             if (type.lessSignificant(leastSignificantType)) {
112                 leastSignificantType = type;
113             }
114         }
115         return leastSignificantType;
116     }
117 
118     /**
119      * Add <code>plugin/dependencyType</code> to <code>map</code>.
120      * <p>
121      * Dependency significance will be followed when deciding to overwrite an existing type, with the following order:
122      * {@link PluginDependencies.Type#MANDATORY},
123      * {@link PluginDependencies.Type#OPTIONAL},
124      * {@link PluginDependencies.Type#DYNAMIC}.
125      */
126     private void add(final Plugin plugin, final PluginDependencies.Type dependencyType) {
127         final PluginDependencies.Type existingDependencyType = plugins.get(plugin);
128         if (existingDependencyType == null || existingDependencyType.lessSignificant(dependencyType)) {
129             plugins.put(plugin, dependencyType);
130         }
131     }
132 
133     public Iterable<String> toPluginKeyDependencyTypes(final Set<PluginDependencies.Type> dependencyTypes) {
134         final List<String> output = new ArrayList<>();
135         for (final Map.Entry<Plugin, PluginDependencies.Type> entry : plugins.entrySet()) {
136             if (dependencyTypes.contains(entry.getValue())) {
137                 output.add(entry.getKey().getKey() + "(" + entry.getValue() + ")");
138             }
139         }
140         return output;
141     }
142 
143     public Iterable<String> toPluginKeyDependencyTypes() {
144         return toPluginKeyDependencyTypes(EnumSet.allOf(PluginDependencies.Type.class));
145     }
146 
147     /**
148      * Get all plugins
149      *
150      * @return a {@link Set} of plugins
151      */
152     public Set<Plugin> get() {
153         return plugins.keySet();
154     }
155 
156     public Set<Plugin> getByTypes(final Set<PluginDependencies.Type> dependencyTypes) {
157         return Maps.filterEntries(plugins, input -> dependencyTypes.contains(input.getValue())).keySet();
158     }
159 
160     /**
161      * A dependecy with a cap on transient dependency type
162      */
163     private static class CappedDep {
164         @Nonnull
165         final public String key;
166         @Nonnull
167         final public PluginDependencies.Type cap;
168 
169         public CappedDep(final String key, final PluginDependencies.Type cap) {
170             this.key = key;
171             this.cap = cap;
172         }
173 
174         // Cap the depType by significance
175         public PluginDependencies.Type cap(final PluginDependencies.Type depType) {
176             return depType.lessSignificant(cap) ? depType : cap;
177         }
178 
179         @Override
180         public boolean equals(final Object o) {
181             if (this == o) {
182                 return true;
183             }
184             if (o == null || getClass() != o.getClass()) {
185                 return false;
186             }
187 
188             final CappedDep cappedDep = (CappedDep) o;
189 
190             if (!key.equals(cappedDep.key)) {
191                 return false;
192             }
193             return cap == cappedDep.cap;
194         }
195 
196         @Override
197         public int hashCode() {
198             int result = key.hashCode();
199             result = 31 * result + cap.hashCode();
200             return result;
201         }
202     }
203 
204     /**
205      * A queue of dependencies
206      */
207     private static class DependencyQueue {
208         private final Deque<CappedDep> queue = new ArrayDeque<>();
209 
210         public CappedDep removeFirst() {
211             return queue.removeFirst();
212         }
213 
214         public boolean isEmpty() {
215             return queue.isEmpty();
216         }
217 
218         /**
219          * Add, replace or do nothing depending on the new dependency significance
220          */
221         public void addLast(final CappedDep newDep) {
222             boolean addToQueue = true;
223             for (final Iterator<CappedDep> iter = queue.iterator(); iter.hasNext(); ) {
224                 final CappedDep next = iter.next();
225 
226                 if (next.key.equals(newDep.key)) {
227                     addToQueue = next.cap(newDep.cap) != newDep.cap;
228                     if (addToQueue) {
229                         // remove if we have the same key lined up with less significant dependency
230                         iter.remove();
231                     }
232                     // only one should exist in the queue
233                     break;
234                 }
235             }
236             if (addToQueue) {
237                 queue.addLast(newDep);
238             }
239         }
240     }
241 }