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