001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.gui.preferences.plugin;
003
004import java.io.File;
005import java.util.ArrayList;
006import java.util.Collection;
007import java.util.Comparator;
008import java.util.HashMap;
009import java.util.HashSet;
010import java.util.LinkedList;
011import java.util.List;
012import java.util.Locale;
013import java.util.Map;
014import java.util.Map.Entry;
015import java.util.Set;
016
017import org.openstreetmap.josm.Main;
018import org.openstreetmap.josm.gui.util.ChangeNotifier;
019import org.openstreetmap.josm.plugins.PluginException;
020import org.openstreetmap.josm.plugins.PluginHandler;
021import org.openstreetmap.josm.plugins.PluginInformation;
022
023/**
024 * The plugin model behind a {@code PluginListPanel}.
025 */
026public class PluginPreferencesModel extends ChangeNotifier {
027    // remember the initial list of active plugins
028    private final Set<String> currentActivePlugins;
029    private final List<PluginInformation> availablePlugins = new ArrayList<>();
030    private String filterExpression;
031    private final List<PluginInformation> displayedPlugins = new ArrayList<>();
032    private final Map<PluginInformation, Boolean> selectedPluginsMap = new HashMap<>();
033    // plugins that still require an update/download
034    private final Set<String> pendingDownloads = new HashSet<>();
035
036    /**
037     * Constructs a new {@code PluginPreferencesModel}.
038     */
039    public PluginPreferencesModel() {
040        currentActivePlugins = new HashSet<>();
041        currentActivePlugins.addAll(Main.pref.getCollection("plugins", currentActivePlugins));
042    }
043
044    /**
045     * Filters the list of displayed plugins.
046     * @param filter The filter used against plugin name, description or version
047     */
048    public void filterDisplayedPlugins(String filter) {
049        if (filter == null) {
050            displayedPlugins.clear();
051            displayedPlugins.addAll(availablePlugins);
052            this.filterExpression = null;
053            return;
054        }
055        displayedPlugins.clear();
056        for (PluginInformation pi: availablePlugins) {
057            if (pi.matches(filter)) {
058                displayedPlugins.add(pi);
059            }
060        }
061        filterExpression = filter;
062        fireStateChanged();
063    }
064
065    /**
066     * Sets the list of available plugins.
067     * @param available The available plugins
068     */
069    public void setAvailablePlugins(Collection<PluginInformation> available) {
070        availablePlugins.clear();
071        if (available != null) {
072            availablePlugins.addAll(available);
073        }
074        availablePluginsModified();
075    }
076
077    protected final void availablePluginsModified() {
078        sort();
079        filterDisplayedPlugins(filterExpression);
080        Set<String> activePlugins = new HashSet<>();
081        activePlugins.addAll(Main.pref.getCollection("plugins", activePlugins));
082        for (PluginInformation pi: availablePlugins) {
083            if (selectedPluginsMap.get(pi) == null && activePlugins.contains(pi.name)) {
084                selectedPluginsMap.put(pi, Boolean.TRUE);
085            }
086        }
087        fireStateChanged();
088    }
089
090    protected void updateAvailablePlugin(PluginInformation other) {
091        if (other != null) {
092            PluginInformation pi = getPluginInformation(other.name);
093            if (pi == null) {
094                availablePlugins.add(other);
095                return;
096            }
097            pi.updateFromPluginSite(other);
098        }
099    }
100
101    /**
102     * Updates the list of plugin information objects with new information from
103     * plugin update sites.
104     *
105     * @param fromPluginSite plugin information read from plugin update sites
106     */
107    public void updateAvailablePlugins(Collection<PluginInformation> fromPluginSite) {
108        for (PluginInformation other: fromPluginSite) {
109            updateAvailablePlugin(other);
110        }
111        availablePluginsModified();
112    }
113
114    /**
115     * Replies the list of selected plugin information objects
116     *
117     * @return the list of selected plugin information objects
118     */
119    public List<PluginInformation> getSelectedPlugins() {
120        List<PluginInformation> ret = new LinkedList<>();
121        for (PluginInformation pi: availablePlugins) {
122            if (selectedPluginsMap.get(pi) == null) {
123                continue;
124            }
125            if (selectedPluginsMap.get(pi)) {
126                ret.add(pi);
127            }
128        }
129        return ret;
130    }
131
132    /**
133     * Replies the list of selected plugin information objects
134     *
135     * @return the list of selected plugin information objects
136     */
137    public Set<String> getSelectedPluginNames() {
138        Set<String> ret = new HashSet<>();
139        for (PluginInformation pi: getSelectedPlugins()) {
140            ret.add(pi.name);
141        }
142        return ret;
143    }
144
145    /**
146     * Sorts the list of available plugins
147     */
148    protected void sort() {
149        availablePlugins.sort(Comparator.comparing(
150                o -> o.getName() == null ? "" : o.getName().toLowerCase(Locale.ENGLISH)));
151    }
152
153    /**
154     * Replies the list of plugin informations to display.
155     *
156     * @return the list of plugin informations to display
157     */
158    public List<PluginInformation> getDisplayedPlugins() {
159        return displayedPlugins;
160    }
161
162    /**
163     * Replies the set of plugins waiting for update or download.
164     *
165     * @return the set of plugins waiting for update or download
166     */
167    public Set<PluginInformation> getPluginsScheduledForUpdateOrDownload() {
168        Set<PluginInformation> ret = new HashSet<>();
169        for (String plugin: pendingDownloads) {
170            PluginInformation pi = getPluginInformation(plugin);
171            if (pi == null) {
172                continue;
173            }
174            ret.add(pi);
175        }
176        return ret;
177    }
178
179    /**
180     * Sets whether the plugin is selected or not.
181     *
182     * @param name the name of the plugin
183     * @param selected true, if selected; false, otherwise
184     */
185    public void setPluginSelected(String name, boolean selected) {
186        PluginInformation pi = getPluginInformation(name);
187        if (pi != null) {
188            selectedPluginsMap.put(pi, selected);
189            if (pi.isUpdateRequired()) {
190                pendingDownloads.add(pi.name);
191            }
192        }
193        if (!selected) {
194            pendingDownloads.remove(name);
195        }
196    }
197
198    /**
199     * Removes all the plugin in {@code plugins} from the list of plugins
200     * with a pending download
201     *
202     * @param plugins the list of plugins to clear for a pending download
203     */
204    public void clearPendingPlugins(Collection<PluginInformation> plugins) {
205        if (plugins != null) {
206            for (PluginInformation pi: plugins) {
207                pendingDownloads.remove(pi.name);
208            }
209        }
210    }
211
212    /**
213     * Replies the plugin info with the name <code>name</code>. null, if no
214     * such plugin info exists.
215     *
216     * @param name the name. If null, replies null.
217     * @return the plugin info.
218     */
219    public PluginInformation getPluginInformation(String name) {
220        for (PluginInformation pi: availablePlugins) {
221            if (pi.getName() != null && pi.getName().equals(name))
222                return pi;
223        }
224        return null;
225    }
226
227    /**
228     * Initializes the model from preferences
229     */
230    public void initFromPreferences() {
231        Collection<String> enabledPlugins = Main.pref.getCollection("plugins", null);
232        if (enabledPlugins == null) {
233            this.selectedPluginsMap.clear();
234            return;
235        }
236        for (String name: enabledPlugins) {
237            PluginInformation pi = getPluginInformation(name);
238            if (pi == null) {
239                continue;
240            }
241            setPluginSelected(name, true);
242        }
243    }
244
245    /**
246     * Replies true if the plugin with name <code>name</code> is currently
247     * selected in the plugin model
248     *
249     * @param name the plugin name
250     * @return true if the plugin is selected; false, otherwise
251     */
252    public boolean isSelectedPlugin(String name) {
253        PluginInformation pi = getPluginInformation(name);
254        if (pi == null || selectedPluginsMap.get(pi) == null)
255            return false;
256        return selectedPluginsMap.get(pi);
257    }
258
259    /**
260     * Replies the set of plugins which have been added by the user to
261     * the set of activated plugins.
262     *
263     * @return the set of newly activated plugins
264     */
265    public List<PluginInformation> getNewlyActivatedPlugins() {
266        List<PluginInformation> ret = new LinkedList<>();
267        for (Entry<PluginInformation, Boolean> entry: selectedPluginsMap.entrySet()) {
268            PluginInformation pi = entry.getKey();
269            boolean selected = entry.getValue();
270            if (selected && !currentActivePlugins.contains(pi.name)) {
271                ret.add(pi);
272            }
273        }
274        return ret;
275    }
276
277    /**
278     * Replies the set of plugins which have been removed by the user from
279     * the set of deactivated plugins.
280     *
281     * @return the set of newly deactivated plugins
282     */
283    public List<PluginInformation> getNewlyDeactivatedPlugins() {
284        List<PluginInformation> ret = new LinkedList<>();
285        for (PluginInformation pi: availablePlugins) {
286            if (!currentActivePlugins.contains(pi.name)) {
287                continue;
288            }
289            if (selectedPluginsMap.get(pi) == null || !selectedPluginsMap.get(pi)) {
290                ret.add(pi);
291            }
292        }
293        return ret;
294    }
295
296    /**
297     * Replies the set of all available plugins.
298     *
299     * @return the set of all available plugins
300     */
301    public List<PluginInformation> getAvailablePlugins() {
302        return new LinkedList<>(availablePlugins);
303    }
304
305    /**
306     * Replies the set of plugin names which have been added by the user to
307     * the set of activated plugins.
308     *
309     * @return the set of newly activated plugin names
310     */
311    public Set<String> getNewlyActivatedPluginNames() {
312        Set<String> ret = new HashSet<>();
313        List<PluginInformation> plugins = getNewlyActivatedPlugins();
314        for (PluginInformation pi: plugins) {
315            ret.add(pi.name);
316        }
317        return ret;
318    }
319
320    /**
321     * Replies true if the set of active plugins has been changed by the user
322     * in this preference model. He has either added plugins or removed plugins
323     * being active before.
324     *
325     * @return true if the collection of active plugins has changed
326     */
327    public boolean isActivePluginsChanged() {
328        Set<String> newActivePlugins = getSelectedPluginNames();
329        return !newActivePlugins.equals(currentActivePlugins);
330    }
331
332    /**
333     * Refreshes the local version field on the plugins in <code>plugins</code> with
334     * the version in the manifest of the downloaded "jar.new"-file for this plugin.
335     *
336     * @param plugins the collections of plugins to refresh
337     */
338    public void refreshLocalPluginVersion(Collection<PluginInformation> plugins) {
339        if (plugins != null) {
340            for (PluginInformation pi : plugins) {
341                File downloadedPluginFile = PluginHandler.findUpdatedJar(pi.name);
342                if (downloadedPluginFile == null) {
343                    continue;
344                }
345                try {
346                    PluginInformation newinfo = new PluginInformation(downloadedPluginFile, pi.name);
347                    PluginInformation oldinfo = getPluginInformation(pi.name);
348                    if (oldinfo != null) {
349                        oldinfo.updateLocalInfo(newinfo);
350                    }
351                } catch (PluginException e) {
352                    Main.error(e);
353                }
354            }
355        }
356    }
357}