1   /**
2    * Distribution License:
3    * JSword is free software; you can redistribute it and/or modify it under
4    * the terms of the GNU Lesser General Public License, version 2.1 or later
5    * as published by the Free Software Foundation. This program is distributed
6    * in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
7    * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
8    * See the GNU Lesser General Public License for more details.
9    *
10   * The License is available on the internet at:
11   *       http://www.gnu.org/copyleft/lgpl.html
12   * or by writing to:
13   *      Free Software Foundation, Inc.
14   *      59 Temple Place - Suite 330
15   *      Boston, MA 02111-1307, USA
16   *
17   * Copyright: 2005-2013
18   *     The copyright to this program is held by its authors.
19   *
20   */
21  package org.crosswire.jsword.book.install;
22  
23  import java.io.IOException;
24  import java.net.URI;
25  import java.util.Collections;
26  import java.util.Iterator;
27  import java.util.LinkedHashMap;
28  import java.util.List;
29  import java.util.Map;
30  import java.util.Set;
31  import java.util.concurrent.CopyOnWriteArrayList;
32  
33  import org.crosswire.common.util.CWProject;
34  import org.crosswire.common.util.FileUtil;
35  import org.crosswire.common.util.NetUtil;
36  import org.crosswire.common.util.PluginUtil;
37  import org.crosswire.common.util.PropertyMap;
38  import org.crosswire.common.util.Reporter;
39  import org.slf4j.Logger;
40  import org.slf4j.LoggerFactory;
41  
42  /**
43   * A manager to abstract out the non-view specific book installation tasks.
44   * 
45   * @see gnu.lgpl.License The GNU Lesser General Public License for details.
46   * @author Joe Walker
47   * @author DM Smith
48   */
49  public final class InstallManager {
50      /**
51       * Simple ctor
52       */
53      public InstallManager() {
54          listeners = new CopyOnWriteArrayList<InstallerListener>();
55          installers = new LinkedHashMap<String, Installer>();
56  
57          try {
58              PropertyMap sitemap = PluginUtil.getPlugin(getClass());
59              factories = PluginUtil.getImplementorsMap(InstallerFactory.class);
60              int i = 0;
61              for (String def = sitemap.get(PREFIX + ++i); def != null; def = sitemap.get(PREFIX + ++i)) {
62                  try {
63                      String[] parts = def.split(",", 3);
64                      String type = parts[0];
65                      String name = parts[1];
66                      String rest = parts[2];
67  
68                      Class<InstallerFactory> clazz = factories.get(type);
69                      if (clazz == null) {
70                          log.warn("Unable to get class for {}", type);
71                      } else {
72                          InstallerFactory ifactory = clazz.newInstance();
73                          Installer installer = ifactory.createInstaller(rest);
74  
75                          internalAdd(name, installer);
76                      }
77                  } catch (InstantiationException e) {
78                      Reporter.informUser(this, e);
79                  } catch (IllegalAccessException e) {
80                      Reporter.informUser(this, e);
81                  }
82              }
83          } catch (IOException ex) {
84              Reporter.informUser(this, ex);
85          }
86      }
87  
88      /**
89       * Save all properties to the user's local area. Uses the same property name
90       * so as to override it.
91       */
92      public void save() {
93          PropertyMap props = new PropertyMap();
94          StringBuilder buf = new StringBuilder();
95          int i = 1;
96          for (String name : installers.keySet()) {
97              Installer installer = installers.get(name);
98              // Clear the buffer
99              buf.delete(0, buf.length());
100             buf.append(installer.getType());
101             buf.append(',');
102             buf.append(name);
103             buf.append(',');
104             buf.append(installer.getInstallerDefinition());
105             props.put(PREFIX + i++, buf.toString());
106         }
107         URI outputURI = CWProject.instance().getWritableURI(getClass().getName(), FileUtil.EXTENSION_PLUGIN);
108         try {
109             NetUtil.storeProperties(props, outputURI, "Saved Installer Sites");
110         } catch (IOException e) {
111             log.error("Failed to save installers", e);
112         }
113     }
114 
115     /**
116      * The names of all the known InstallerFactories
117      * 
118      * @return the set of all installer factory names
119      */
120     public Set<String> getInstallerFactoryNames() {
121         return Collections.unmodifiableSet(factories.keySet());
122     }
123 
124     /**
125      * Find the registered name of the InstallerFactory that created the given
126      * installer. There isn't a nice way to do this right now so we just do a
127      * trawl through all the known factories looking!
128      * 
129      * @param installer the installer
130      * @return the name of the factory for the installer
131      */
132     public String getFactoryNameForInstaller(Installer installer) {
133         Class<? extends Installer> match = installer.getClass();
134         for (String name : factories.keySet()) {
135             Class<InstallerFactory> factclazz = factories.get(name);
136             try {
137                 InstallerFactory ifactory = factclazz.newInstance();
138                 Class<? extends Installer> clazz = ifactory.createInstaller().getClass();
139                 if (clazz == match) {
140                     return name;
141                 }
142             } catch (InstantiationException e) {
143                 log.warn("Failed to instantiate installer factory: {}={}", name, factclazz.getName(), e);
144             } catch (IllegalAccessException e) {
145                 log.warn("Failed to instantiate installer factory: {}={}", name, factclazz.getName(), e);
146             }
147         }
148 
149         log.warn("Failed to find factory name for {} among the {} factories.", installer, Integer.toString(factories.size()));
150         return null;
151     }
152 
153     /**
154      * Find the registered name of the Installer. There isn't a nice way to do
155      * this right now so we just do a trawl through all the known factories
156      * looking!
157      * 
158      * @param installer the installer
159      * @return the name of the installer
160      */
161     public String getInstallerNameForInstaller(Installer installer) {
162         for (String name : installers.keySet()) {
163             Installer test = installers.get(name);
164             if (installer.equals(test)) {
165                 return name;
166             }
167         }
168 
169         log.warn("Failed to find installer name for {} among the {} installers.", installer, Integer.toString(installers.size()));
170         for (String name : installers.keySet()) {
171             Installer test = installers.get(name);
172             log.warn("  it isn't equal to {}", test.getInstallerDefinition());
173         }
174         return null;
175     }
176 
177     /**
178      * Find the InstallerFactory associated with the given name.
179      * 
180      * @param name
181      *            The InstallerFactory name to look-up
182      * @return The found InstallerFactory or null if the name was not found
183      */
184     public InstallerFactory getInstallerFactory(String name) {
185         Class<InstallerFactory> clazz = factories.get(name);
186         try {
187             return clazz.newInstance();
188         } catch (InstantiationException e) {
189             assert false : e;
190         } catch (IllegalAccessException e) {
191             assert false : e;
192         }
193         return null;
194     }
195 
196     /**
197      * Accessor for the known installers
198      * @return a map of named installers
199      */
200     public Map<String, Installer> getInstallers() {
201         return Collections.unmodifiableMap(installers);
202     }
203 
204     /**
205      * Find an installer by name
206      * 
207      * @param name
208      *            The name of the installer to find
209      * @return The installer or null if none was found by the given name
210      */
211     public Installer getInstaller(String name) {
212         return installers.get(name);
213     }
214 
215     /**
216      * Add an installer to our list of installers
217      * 
218      * @param name
219      *            The name by which we reference the installer
220      * @param installer
221      *            The installer to add
222      */
223     public void addInstaller(String name, Installer installer) {
224         assert installer != null;
225         assert name != null;
226 
227         removeInstaller(name);
228 
229         internalAdd(name, installer);
230         fireInstallersChanged(this, installer, true);
231     }
232 
233     /**
234      * InstallManager is a Map, however we demand that both names and installers
235      * are unique (so we can lookup a name from an installer)
236      * 
237      * @param name
238      *            The name of the new installer
239      * @param installer
240      *            The installer to associate with the given name
241      */
242     private void internalAdd(String name, Installer installer) {
243         Iterator<String> it = installers.keySet().iterator();
244         while (it.hasNext()) {
245             String tname = it.next();
246             Installer tinstaller = installers.get(tname);
247 
248             if (tinstaller.equals(installer)) {
249                 // We have a dupe - remove the old name
250                 log.warn("duplicate installers: {}={}. removing {}", name, tname, tname);
251 
252                 // Can't call removeInstaller while iterating.
253                 it.remove();
254                 fireInstallersChanged(this, tinstaller, false);
255             }
256         }
257 
258         installers.put(name, installer);
259     }
260 
261     /**
262      * Remove an installer from our list
263      * 
264      * @param name
265      *            The name by which this installer is referenced
266      */
267     public void removeInstaller(String name) {
268         if (installers.containsKey(name)) {
269             Installer old = installers.remove(name);
270             fireInstallersChanged(this, old, false);
271         }
272     }
273 
274     /**
275      * Remove a BibleListener from our list of listeners
276      * 
277      * @param li
278      *            The old listener
279      */
280     public void addInstallerListener(InstallerListener li) {
281         listeners.add(li);
282     }
283 
284     /**
285      * Add a BibleListener to our list of listeners
286      * 
287      * @param li
288      *            The new listener
289      */
290     public void removeBooksListener(InstallerListener li) {
291         listeners.remove(li);
292     }
293 
294     /**
295      * Kick of an event sequence
296      * 
297      * @param source
298      *            The event source
299      * @param installer
300      *            The meta-data of the changed Bible
301      * @param added
302      *            Is it added?
303      */
304     protected void fireInstallersChanged(Object source, Installer installer, boolean added) {
305         InstallerEvent ev = new InstallerEvent(source, installer, added);
306         for (InstallerListener listener : listeners) {
307             if (added) {
308                 listener.installerAdded(ev);
309             } else {
310                 listener.installerRemoved(ev);
311             }
312         }
313     }
314 
315     /**
316      * The prefix for the keys in the installer property file.
317      */
318     private static final String PREFIX = "Installer.";
319 
320     /**
321      * The map of installer factories
322      */
323     private Map<String, Class<InstallerFactory>> factories;
324 
325     /**
326      * The list of discovered installers
327      */
328     private Map<String, Installer> installers;
329 
330     /**
331      * The list of listeners
332      */
333     private List<InstallerListener> listeners;
334 
335     /**
336      * The log stream
337      */
338     private static final Logger log = LoggerFactory.getLogger(InstallManager.class);
339 }
340