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
28
29
30
31 final class DependentPlugins {
32 private final SortedMap<Plugin, PluginDependencies.Type> plugins;
33
34
35
36
37
38
39
40
41
42
43
44
45
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
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
93
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
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
120
121
122
123
124
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
149
150
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
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
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
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
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
230 iter.remove();
231 }
232
233 break;
234 }
235 }
236 if (addToQueue) {
237 queue.addLast(newDep);
238 }
239 }
240 }
241 }