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