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