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