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   * © CrossWire Bible Society, 2005 - 2016
18   *
19   */
20  package org.crosswire.jsword.book.install;
21  
22  import java.io.IOException;
23  import java.net.URI;
24  import java.util.Collections;
25  import java.util.Iterator;
26  import java.util.LinkedHashMap;
27  import java.util.List;
28  import java.util.Map;
29  import java.util.Set;
30  import java.util.concurrent.CopyOnWriteArrayList;
31  
32  import org.crosswire.common.util.CWProject;
33  import org.crosswire.common.util.FileUtil;
34  import org.crosswire.common.util.NetUtil;
35  import org.crosswire.common.util.PluginUtil;
36  import org.crosswire.common.util.PropertyMap;
37  import org.crosswire.common.util.Reporter;
38  import org.slf4j.Logger;
39  import org.slf4j.LoggerFactory;
40  
41  /**
42   * A manager to abstract out the non-view specific book installation tasks.
43   * 
44   * @see gnu.lgpl.License The GNU Lesser General Public License for details.
45   * @author Joe Walker
46   * @author DM Smith
47   */
48  public final class InstallManager {
49      /**
50       * Simple ctor
51       */
52      public InstallManager() {
53          listeners = new CopyOnWriteArrayList<InstallerListener>();
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("Unable to get class for {}", type);
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      * @return the set of all installer factory names
118      */
119     public Set<String> getInstallerFactoryNames() {
120         return Collections.unmodifiableSet(factories.keySet());
121     }
122 
123     /**
124      * Find the registered name of the InstallerFactory that created the given
125      * installer. There isn't a nice way to do this right now so we just do a
126      * trawl through all the known factories looking!
127      * 
128      * @param installer the installer
129      * @return the name of the factory for the installer
130      */
131     public String getFactoryNameForInstaller(Installer installer) {
132         Class<? extends Installer> match = installer.getClass();
133         for (String name : factories.keySet()) {
134             Class<InstallerFactory> factclazz = factories.get(name);
135             try {
136                 InstallerFactory ifactory = factclazz.newInstance();
137                 Class<? extends Installer> clazz = ifactory.createInstaller().getClass();
138                 if (clazz == match) {
139                     return name;
140                 }
141             } catch (InstantiationException e) {
142                 log.warn("Failed to instantiate installer factory: {}={}", name, factclazz.getName(), e);
143             } catch (IllegalAccessException e) {
144                 log.warn("Failed to instantiate installer factory: {}={}", name, factclazz.getName(), e);
145             }
146         }
147 
148         log.warn("Failed to find factory name for {} among the {} factories.", installer, Integer.toString(factories.size()));
149         return null;
150     }
151 
152     /**
153      * Find the registered name of the Installer. There isn't a nice way to do
154      * this right now so we just do a trawl through all the known factories
155      * looking!
156      * 
157      * @param installer the installer
158      * @return the name of the installer
159      */
160     public String getInstallerNameForInstaller(Installer installer) {
161         for (String name : installers.keySet()) {
162             Installer test = installers.get(name);
163             if (installer.equals(test)) {
164                 return name;
165             }
166         }
167 
168         log.warn("Failed to find installer name for {} among the {} installers.", installer, Integer.toString(installers.size()));
169         for (String name : installers.keySet()) {
170             Installer test = installers.get(name);
171             log.warn("  it isn't equal to {}", test.getInstallerDefinition());
172         }
173         return null;
174     }
175 
176     /**
177      * Find the InstallerFactory associated with the given name.
178      * 
179      * @param name
180      *            The InstallerFactory name to look-up
181      * @return The found InstallerFactory or null if the name was not found
182      */
183     public InstallerFactory getInstallerFactory(String name) {
184         Class<InstallerFactory> clazz = factories.get(name);
185         try {
186             return clazz.newInstance();
187         } catch (InstantiationException e) {
188             assert false : e;
189         } catch (IllegalAccessException e) {
190             assert false : e;
191         }
192         return null;
193     }
194 
195     /**
196      * Accessor for the known installers
197      * @return a map of named installers
198      */
199     public Map<String, Installer> getInstallers() {
200         return Collections.unmodifiableMap(installers);
201     }
202 
203     /**
204      * Find an installer by name
205      * 
206      * @param name
207      *            The name of the installer to find
208      * @return The installer or null if none was found by the given name
209      */
210     public Installer getInstaller(String name) {
211         return installers.get(name);
212     }
213 
214     /**
215      * Add an installer to our list of installers
216      * 
217      * @param name
218      *            The name by which we reference the installer
219      * @param installer
220      *            The installer to add
221      */
222     public void addInstaller(String name, Installer installer) {
223         assert installer != null;
224         assert name != null;
225 
226         removeInstaller(name);
227 
228         internalAdd(name, installer);
229         fireInstallersChanged(this, installer, true);
230     }
231 
232     /**
233      * InstallManager is a Map, however we demand that both names and installers
234      * are unique (so we can lookup a name from an installer)
235      * 
236      * @param name
237      *            The name of the new installer
238      * @param installer
239      *            The installer to associate with the given name
240      */
241     private void internalAdd(String name, Installer installer) {
242         Iterator<String> it = installers.keySet().iterator();
243         while (it.hasNext()) {
244             String tname = it.next();
245             Installer tinstaller = installers.get(tname);
246 
247             if (tinstaller.equals(installer)) {
248                 // We have a dupe - remove the old name
249                 log.warn("duplicate installers: {}={}. removing {}", name, tname, tname);
250 
251                 // Can't call removeInstaller while iterating.
252                 it.remove();
253                 fireInstallersChanged(this, tinstaller, false);
254             }
255         }
256 
257         installers.put(name, installer);
258     }
259 
260     /**
261      * Remove an installer from our list
262      * 
263      * @param name
264      *            The name by which this installer is referenced
265      */
266     public void removeInstaller(String name) {
267         if (installers.containsKey(name)) {
268             Installer old = installers.remove(name);
269             fireInstallersChanged(this, old, false);
270         }
271     }
272 
273     /**
274      * Remove a BibleListener from our list of listeners
275      * 
276      * @param li
277      *            The old listener
278      */
279     public void addInstallerListener(InstallerListener li) {
280         listeners.add(li);
281     }
282 
283     /**
284      * Add a BibleListener to our list of listeners
285      * 
286      * @param li
287      *            The new listener
288      */
289     public void removeBooksListener(InstallerListener li) {
290         listeners.remove(li);
291     }
292 
293     /**
294      * Kick of an event sequence
295      * 
296      * @param source
297      *            The event source
298      * @param installer
299      *            The meta-data of the changed Bible
300      * @param added
301      *            Is it added?
302      */
303     protected void fireInstallersChanged(Object source, Installer installer, boolean added) {
304         InstallerEvent ev = new InstallerEvent(source, installer, added);
305         for (InstallerListener listener : listeners) {
306             if (added) {
307                 listener.installerAdded(ev);
308             } else {
309                 listener.installerRemoved(ev);
310             }
311         }
312     }
313 
314     /**
315      * The prefix for the keys in the installer property file.
316      */
317     private static final String PREFIX = "Installer.";
318 
319     /**
320      * The map of installer factories
321      */
322     private Map<String, Class<InstallerFactory>> factories;
323 
324     /**
325      * The list of discovered installers
326      */
327     private Map<String, Installer> installers;
328 
329     /**
330      * The list of listeners
331      */
332     private List<InstallerListener> listeners;
333 
334     /**
335      * The log stream
336      */
337     private static final Logger log = LoggerFactory.getLogger(InstallManager.class);
338 }
339