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 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 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}