View Javadoc
1   package com.github.celldynamics.quimp;
2   
3   import java.lang.reflect.Type;
4   import java.util.ArrayList;
5   import java.util.Collections;
6   import java.util.List;
7   
8   import org.slf4j.Logger;
9   import org.slf4j.LoggerFactory;
10  
11  import com.github.celldynamics.quimp.filesystem.IQuimpSerialize;
12  import com.github.celldynamics.quimp.plugin.IQuimpCorePlugin;
13  import com.github.celldynamics.quimp.plugin.IQuimpPluginSynchro;
14  import com.github.celldynamics.quimp.plugin.ParamList;
15  import com.github.celldynamics.quimp.plugin.QuimpPluginException;
16  import com.github.celldynamics.quimp.plugin.engine.PluginFactory;
17  import com.google.gson.Gson;
18  import com.google.gson.InstanceCreator;
19  
20  /*
21   * //!>
22   * @startuml doc-files/SnakePluginList_1_UML.png
23   * left to right direction
24   * :User: as user
25   * user--(//Initialize//)
26   * user--(setInstance)
27   * user--(Serialize)
28   * user--(delete plugin)
29   * user--(set active)
30   * (Serialize)<|--(Handle\nconfiguration):<<extend>>
31   * @enduml
32   * 
33   * @startuml doc-files/SnakePluginList_2_UML.png
34   * actor User
35   * participant SnakePluginList as slist
36   * participant Plugin as plugin
37   * participant PluginFactory as pfact
38   * participant IQuimpPlugin as iPlugin
39   * note over plugin : Internal representation\nof plugin instance
40   * note over iPlugin : external instance\nof plugin
41   * User->slist : <<create>>\nPluginFactory\nData\nViewUpdater
42   * activate slist
43   * loop all slots
44   * slist->plugin : <<create>>
45   * activate plugin
46   * note left
47   * Create empty instances of plugins
48   * end note
49   * end
50   * @enduml
51   * 
52   * @startuml doc-files/SnakePluginList_3_UML.png
53   * actor User
54   * participant SnakePluginList as slist
55   * participant Plugin as plugin
56   * participant PluginFactory as pfact
57   * participant IQuimpPlugin as iPlugin
58   * note over plugin : Internal representation\nof plugin instance
59   * note over iPlugin : external instance\nof plugin
60   * activate slist
61   * activate plugin
62   * activate pfact
63   * User->slist : ""setInstance""\ni\nName\nActivity
64   * slist-->plugin : <<destroy>>
65   * destroy plugin
66   * slist -> plugin : <<create>>\nName\nActivity\nPluginFactory
67   * activate plugin
68   * plugin -> pfact : ""getInstance(Name)""
69   * pfact -> iPlugin : <<create>>
70   * activate iPlugin
71   * pfact --> plugin : instance
72   * note left: instance is stored in\nPlugin
73   * plugin --> slist
74   * slist->plugin : ""getInstance(i)""
75   * plugin-->slist : instance
76   * slist -> iPlugin : ""attachContext(ViewUpdater)""
77   * @enduml
78   * 
79   * @startuml doc-files/SnakePluginList_4_UML.png
80   * actor User
81   * participant SnakePluginList as slist
82   * participant Plugin as plugin
83   * participant PluginFactory as pfact
84   * participant IQuimpPlugin as iPlugin
85   * note over plugin : Internal representation\nof plugin instance
86   * note over iPlugin : external instance\nof plugin
87   * activate slist
88   * activate plugin
89   * activate pfact
90   * activate iPlugin
91   * == before Serialization ==
92   * User --/ slist : save loop all slots 
93   * slist->plugin : ""downloadPluginConfig()""
94   * plugin -> iPlugin : ""getPluginConfig()""
95   * iPlugin --> plugin: config
96   * plugin -> iPlugin : ""getVersion()""
97   * iPlugin --> plugin : version
98   * == after serialization ==
99   * User --/ slist : load
100  * note left
101  * On load fields of SnakePluginList
102  * and Plugin are restored except plugin
103  * instances
104  * end note
105  * loop all slots
106  * slist->plugin : get version
107  * plugin --> slist: version
108  * slist->plugin : get Name
109  * plugin --> slist: name
110  * note left: Those restored from load
111  * slist->slist : ""setInstance(i,Name,Activity,Config)""
112  * activate slist
113  * destroy plugin
114  * destroy iPlugin
115  * slist->slist : ""setInstance(i,Name,Activity)""
116  * activate slist
117  * activate plugin
118  * activate iPlugin
119  * activate slist
120  * note left: See setInstance UseCase ...
121  * slist->plugin : ""uploadPluginConfig(config)"" 
122  * plugin -> iPlugin : ""setPluginConfig(config)""
123  * end
124  * @enduml
125  * 
126  * @startuml doc-files/SnakePluginList_5_UML.png
127  * actor User
128  * participant SnakePluginList as slist
129  * participant Plugin as plugin
130  * participant PluginFactory as pfact
131  * participant IQuimpPlugin as iPlugin
132  * note over plugin: Internal representation\nof plugin instance
133  * note over iPlugin : external instance\nof plugin
134  * activate slist
135  * activate plugin
136  * activate pfact
137  * activate iPlugin
138  * User --/ slist : Delete plugin
139  * destroy plugin
140  * destroy iPlugin
141  * slist->plugin : <<create>>
142  * activate plugin
143  * @enduml
144  * 
145  * @startuml doc-files/SnakePluginList_6_UML.png
146  * actor User
147  * participant SnakePluginList as slist 
148  * participant Plugin as plugin
149  * participant PluginFactory as pfact
150  * participant IQuimpPlugin as iPlugin
151  * note over plugin : Internal representation\nof plugin instance
152  * note over iPlugin :external instance\nof plugin
153  * activate slist
154  * activate plugin
155  * activate pfact
156  * activate iPlugin
157  * User --/ slist : setActive(i,Activity)
158  * slist->plugin : set isActive
159  * @enduml
160  * 
161  * 
162  * //!<
163  */
164 
165 /**
166  * Ordered list of plugins related to snake processing for one frame.
167  * 
168  * <p>This class keeps configuration for all plugin slots but for one frame. If you look for global
169  * Configuration check {@link BOAState#snakePluginListSnapshots}. Note
170  * also that each plugin is instanced only once by {@link PluginFactory} so their configuration must
171  * be updated for each frame separately.
172  * 
173  * <p>Related to GUI, first plugin is at index 0, etc. Keeps also UI settings activating or
174  * deactivating plugins. Produces plugins from their names using provided
175  * {@link com.github.celldynamics.quimp.plugin.engine.PluginFactory} The <tt>sPluginList</tt> is
176  * serialized
177  * (saved as JSON object). Because serialization does not touch plugins (understood as jars)
178  * directly and moreover they reference the same object among frames, their
179  * configuration and state must be stored locally in Plugin object. This task is
180  * must be
181  * accomplished on any change in plugin configuration, serialisation and deserialisation. Check
182  * {@link #afterSerialize()}, {@link #beforeSerialize()}
183  * {@link BOAState#restore(int)} and {@link BOAState#store(int)}. From plugin side it is enough to
184  * use {@link com.github.celldynamics.quimp.plugin.IQuimpPluginSynchro} interface to store its
185  * current configuration locally.
186  * For the whole stack of images, plugin stack is kept for each frame
187  * ({@link BOAState#snakePluginListSnapshots}) together with updated configuration inside
188  * {@link SnakePluginList.Plugin}. Browsing through frames upload configuration to plugin for each
189  * frame.
190  * 
191  * <p>This class is serializable and it is part of QuimP config.
192  * 
193  * <p>The most important use cases are:<br>
194  * <img src="doc-files/SnakePluginList_1_UML.png"/><br>
195  * 
196  * <p>During initialization basic structures are created. Note that plugins are stored in
197  * intermediate
198  * class {@link SnakePluginList.Plugin} that holds current state of plugin:
199  * <ol>
200  * <li>reference to jar (obtained from PluginFactory)
201  * <li>name of plugin (name uniquely defines the plugin)
202  * <li>version of plugin (read from jar)
203  * <li>status of plugin (active or inactive, related to QuimP UI)
204  * <li>configuration - updated for each frame or on each action in plugin, always actual
205  * </ol>
206  * <br>
207  * <img src="doc-files/SnakePluginList_2_UML.png"/><br>
208  * 
209  * <p>During setInstance the instance of plugin is created and assign to Plugin object <br>
210  * <img src="doc-files/SnakePluginList_3_UML.png"/><br>
211  * 
212  * <p>During Serialize plugins are prepared for serialization what means saving current state of
213  * plugins like:
214  * <ol>
215  * <li>Loaded plugins (those kept in SnakePluginList only, selected by user in UI)
216  * <li>Their configuration
217  * </ol>
218  * <br>
219  * <img src="doc-files/SnakePluginList_4_UML.png"/><br>
220  * 
221  * <p>During Deletion of plugin the new empty plugin is created in place of old one <br>
222  * <img src="doc-files/SnakePluginList_5_UML.png"/><br>
223  * 
224  * <p>During Set Active state, the internal state of plugin is set to active. This is important for
225  * method {@link com.github.celldynamics.quimp.SnakePluginList.Plugin#isExecutable()}. <br>
226  * <img src="doc-files/SnakePluginList_6_UML.png"/><br>
227  * 
228  * @see com.github.celldynamics.quimp.BOA_#run(String)
229  * @author p.baniukiewicz
230  */
231 public class SnakePluginList implements IQuimpSerialize {
232 
233   /**
234    * The Constant LOGGER.
235    */
236   static final Logger LOGGER = LoggerFactory.getLogger(SnakePluginList.class.getName());
237   // all other data that are necessary for plugins
238   private transient PluginFactory pluginFactory;
239   private transient ViewUpdater viewUpdater;
240 
241   /**
242    * Keeps all Plugin related information and produces plugin instance using PluginFactory.
243    * 
244    * <p>Fields like config and ver are used during saving so they are initialized in this time only
245    * and they are not valid during object lifetime.
246    * 
247    * @author p.baniukiewicz
248    *
249    */
250   class Plugin {
251     /**
252      * Reference to plugin instance.
253      */
254     private transient IQuimpCorePlugin ref;
255     /**
256      * Is activate in GUI?.
257      */
258     private boolean isActive;
259     /**
260      * Name of plugin delivered from PluginFactory.
261      */
262     private String name;
263     /**
264      * This is local snapshot of current plugin configuration.
265      * 
266      * <p>Updated on each call
267      * {@link com.github.celldynamics.quimp.ViewUpdater#updateView()} made by plugin (indirectly
268      * {@link BOA_#recalculatePlugins()} and {@link BOAState#store(int)}. Configuration is pushed to
269      * plugin on each change of displayed frame {@link BOAState#restore(int)} because all plugins
270      * with the same name are references of one instance. This field should not be null.
271      * 
272      * @see #uploadPluginConfig(ParamList)
273      * @see #uploadPluginConfig()
274      * @see #downloadPluginConfig()
275      * @see SnakePluginList#downloadPluginsConfig()
276      * @see SnakePluginList#uploadPluginsConfig()
277      */
278     private ParamList config;
279     /**
280      * Version read from plugin on save operation.
281      */
282     private String ver;
283 
284     /**
285      * Initializes empty default plugin.
286      */
287     public Plugin() {
288       ref = null;
289       isActive = true; // Default value
290       name = "";
291       config = new ParamList(); // no config or not supported by plugin
292       ver = ""; // no version or not supported
293     }
294 
295     /**
296      * Main constructor. Creates instance of plugin name if name is known to provided
297      * PluginFactory.
298      * 
299      * <p>If name is not found in registered names of plugins in provided PluginFactory pf, the
300      * reference ref will be null
301      * 
302      * @param name Name of plugin to be instanced
303      * @param isActive is plugin active?
304      * @param pf PluginFactory that provides plugin objects
305      * @throws QuimpPluginException if plugin can not be instanced
306      */
307     public Plugin(final String name, boolean isActive, final PluginFactory pf)
308             throws QuimpPluginException {
309       this.isActive = isActive;
310       ref = pf.getInstance(name); // create instance of plugin
311       if (ref == null) {
312         throw new QuimpPluginException(
313                 "Plugin initialization failed. Plugin " + name + " can not be loaded or instanced");
314       }
315       ver = ref.getVersion();
316       this.name = name;
317     }
318 
319     /**
320      * Instantiates a new plugin.
321      *
322      * @param name the name
323      * @param isActive the is active
324      * @param pf the pf
325      * @param config the config
326      * @throws QuimpPluginException the quimp plugin exception
327      */
328     @Deprecated
329     public Plugin(final String name, boolean isActive, final PluginFactory pf,
330             final ParamList config) throws QuimpPluginException {
331       this(name, isActive, pf);
332       ref.setPluginConfig(config);
333     }
334 
335     /**
336      * Copy method.
337      * 
338      * <p>Returns copy of current object with some limitations. It does not copy loaded plugin
339      * (ref). Should be called after {@link #downloadPluginConfig()} to make sure that config,
340      * ver are filled correctly
341      * 
342      * @return Copy of current object
343      */
344     private Plugin getShallowCopy() {
345       Plugin ret = new Plugin();
346       ret.isActive = this.isActive;
347       ret.name = this.name;
348       ret.config = new ParamList(this.config); // copy config
349       ret.ver = this.ver;
350       return ret;
351     }
352 
353     /**
354      * Copy method.
355      * 
356      * <p>Returns copy of current object. It does copy loaded plugin (ref). Should be called after
357      * {@link #downloadPluginConfig()} to make sure that config, ver are filled correctly
358      * 
359      * @return Copy of current object
360      */
361     private Plugin getDeepCopy() {
362       Plugin ret = getShallowCopy();
363       ret.ref = this.ref;
364       return ret;
365     }
366 
367     /**
368      * Check if all execution conditions are met.
369      * 
370      * <p>These conditions are: 1) Plugin exist 2) Plugin is activated in UI
371      * 
372      * @return true if plugin can be executed
373      */
374     public boolean isExecutable() {
375       if (ref == null) {
376         return false;
377       } else {
378         return isActive;
379       }
380     }
381 
382     /**
383      * Copies plugin configuration to local object.
384      * 
385      * <p>Local copy of configuration is necessary for saving/loading. Should be called before
386      * saving to make sure that latest settings are stored.
387      */
388     public void downloadPluginConfig() {
389       if (ref != null) {
390         ParamList tmpconfig = ref.getPluginConfig();
391         // if plugin returns null, create empty container
392         config = tmpconfig != null ? tmpconfig : new ParamList();
393         String tmpVer = ref.getVersion();
394         ver = tmpVer != null ? tmpVer : "";
395       } else {
396         LOGGER.debug("WARN: Plugin not instanced. Config not downloaded");
397       }
398     }
399 
400     /**
401      * Upload provided configuration to plugin and set it as local.
402      * 
403      * @param config Configuration to upload
404      * @throws QuimpPluginException when config can not be uploaded to plugin
405      */
406     public void uploadPluginConfig(final ParamList config) throws QuimpPluginException {
407       if (ref != null) {
408         ref.setPluginConfig(config);
409       }
410       this.config = config;
411     }
412 
413     /**
414      * Upload local configuration to plugin.
415      * 
416      * @throws QuimpPluginException when config can not be uploaded to plugin
417      */
418     public void uploadPluginConfig() throws QuimpPluginException {
419       if (ref != null) {
420         ref.setPluginConfig(config);
421       }
422     }
423 
424     /**
425      * Return reference to plugin loaded from jar.
426      * 
427      * @return reference to jar
428      */
429     public IQuimpCorePlugin getRef() {
430       return ref;
431     }
432 
433   }
434 
435   /**
436    * Holds list of plugins up to max slots allowed.
437    * 
438    * <p>This list always contains valid Plugin objects but they can point to null reference (
439    * Plugin.ref) when there is no plugin on i-th slot. Keep also plugin configuration for
440    * serialisation purposes. Configuration can be exchanged with jar plugin.
441    * 
442    */
443   private ArrayList<Plugin> sPluginList;
444 
445   /**
446    * Default constructor.
447    * 
448    * <p>Create empty Plugin object that refers to nothing
449    */
450   public SnakePluginList() {
451     sPluginList = new ArrayList<Plugin>();
452     updateRefs(null, null);
453   }
454 
455   /**
456    * Main constructor. Collect all external data necessary to use plugins.
457    * 
458    * @param s Number of supported plugins
459    * @param pf Deliverer of plugins
460    * @param vu ViewUpdater to be connected to plugin
461    */
462   public SnakePluginList(int s, final PluginFactory pf, final ViewUpdater vu) {
463     this(); // initialize structures
464     for (int i = 0; i < s; i++) {
465       sPluginList.add(new Plugin()); // fill list with empty Plugins
466     }
467     // store plugin deliverer and external data that may be important for plugins
468     updateRefs(pf, vu);
469   }
470 
471   /**
472    * Copy method.
473    * 
474    * <p>Returns copy of current object with some limitations. It does not copy loaded plugin (ref).
475    * Should be called after {@link SnakePluginList.Plugin#downloadPluginConfig()} to make sure
476    * that config, ver are filled correctly
477    * 
478    * @return Copy of current object
479    */
480   public SnakePluginList getShallowCopy() {
481     beforeSerialize(); // get plugin config from Plugins (jars->Plugin) to fill Plugin subclass
482     SnakePluginListePluginList.html#SnakePluginList">SnakePluginList ret = new SnakePluginList();
483     ret.updateRefs(pluginFactory, viewUpdater); // assign current external data
484     // make deep copy of the list
485     for (Plugin p : this.sPluginList) {
486       ret.sPluginList.add(p.getShallowCopy());
487     }
488     return ret;
489   }
490 
491   /**
492    * Copy method.
493    * 
494    * <p>Returns copy of current object.It does copy loaded plugin (ref). It requires that plugins
495    * must be correctly instanced. This code copies plugin stack between frames. Note
496    * double afterSerialise():
497    * 
498    * <pre>
499    * <code>
500    * qState.snakePluginList.clear();
501    * // snakePluginListSnapshots keeps only params and name, whereas deepCopy requires real
502    * // instance to download config from it.
503    * SnakePluginList previous = qState.snakePluginListSnapshots.get(frameCopyFrom - 1);
504    * // So create instances on copy first (config will be uploaded here)
505    * previous.afterSerialize();
506    * // and make deep copy downloading config back
507    * qState.snakePluginList = previous.getDeepCopy();
508    * qState.snakePluginList.afterSerialize(); // instance all plugins
509    * qState.store(qState.boap.frame); // copy to current snapshot
510    *
511    * recalculatePlugins(); // update screen
512    * </code>
513    * </pre>
514    * 
515    * <p>All copied plugin will refer to the same jar thus they will share e.g. configuration
516    * parameters. To have independent copies {@link #afterSerialize()} should be called on the list.
517    * Refer to this code making full independent copies of plugins from current frame over all
518    * frames:
519    * 
520    * <pre>
521    * <code>
522    * SnakePluginList tmp = qState.snakePluginList.getDeepCopy();
523    *  for (int i = 0; i < qState.snakePluginListSnapshots.size(); i++) {
524    *    // make a deep copy
525    *    qState.snakePluginListSnapshots.set(i, tmp.getDeepCopy());
526    *    // instance separate copy of jar for this plugin
527    *    qState.snakePluginListSnapshots.get(i).afterSerialize();
528    *  }
529    *  int cf = qState.boap.frame;
530    *  for (boap.frame = 1; boap.frame <= boap.getFrames(); qState.boap.frame++) {
531    *    imageGroup.updateToFrame(boap.frame);
532    *    recalculatePlugins();
533    *  }
534    *  qState.boap.frame = cf;
535    *  imageGroup.updateToFrame(qState.boap.frame);
536    * </code>
537    * </pre>
538    * 
539    * @return Copy of current object
540    */
541   public SnakePluginList getDeepCopy() {
542     // Should be called after downloadPluginConfig()} to make sure that config, ver are filled
543     // correctly
544     downloadPluginsConfig(); // get plugin config from Plugins (jars->Plugin) to fill Plugin
545     SnakePluginListePluginList.html#SnakePluginList">SnakePluginList ret = new SnakePluginList();
546     ret.updateRefs(pluginFactory, viewUpdater); // assign current external data
547     // make deep copy of the list
548     for (Plugin p : this.sPluginList) {
549       ret.sPluginList.add(p.getDeepCopy());
550     }
551     return ret;
552   }
553 
554   /**
555    * Updates references of external object connected in constructor.
556    * 
557    * <p>External references are not copied by {@link #getShallowCopy()} thus they should be
558    * reinitialized after that operation
559    * 
560    * @param pf new PluginFactory
561    * @param vu new ViewUpdater
562    */
563   public void updateRefs(PluginFactory pf, ViewUpdater vu) {
564     this.pluginFactory = pf;
565     this.viewUpdater = vu;
566   }
567 
568   /**
569    * Returns unmodifiable list of plugins.
570    * 
571    * <p>Particular fields in Plugin may not be valid unless beforeSerialize() is called.
572    * 
573    * @return unmodifiable list of plugins
574    */
575   public List<Plugin> getList() {
576     return Collections.unmodifiableList(sPluginList);
577   }
578 
579   /**
580    * Return i-th instance of plugin.
581    * 
582    * @param i Number of plugin to return
583    * @return Instance of plugin
584    */
585   public IQuimpCorePlugin getInstance(int i) {
586     return sPluginList.get(i).ref;
587   }
588 
589   /**
590    * Return i-th plugin name.
591    * 
592    * @param i Number of plugin to return
593    * @return Name of plugin
594    */
595   public String getName(int i) {
596     return sPluginList.get(i).name;
597   }
598 
599   /**
600    * Return i-th plugin version.
601    * 
602    * @param i Number of plugin to return
603    * @return Version of plugin
604    */
605   public String getVer(int i) {
606     return sPluginList.get(i).ver;
607   }
608 
609   /**
610    * Return i-th plugin configuration.
611    * 
612    * @param i Number of plugin to return
613    * @return Copy of configuration of plugin
614    */
615   public ParamList getConfig(int i) {
616     if (sPluginList.get(i).config != null) {
617       return new ParamList(sPluginList.get(i).config); // TODO makes copy? of plugin configuration
618     } else {
619       return new ParamList(); // return empty list
620     }
621   }
622 
623   /**
624    * Check if plugin is active.
625    * 
626    * @param i Number of plugin to check
627    * @return bool if i-th plugin is active or not
628    */
629   public boolean isActive(int i) {
630     return sPluginList.get(i).isActive;
631   }
632 
633   /**
634    * Sets instance of plugin on slot i.
635    * 
636    * <p>If there is other plugin there, it replaces instance keeping its selection state. Connects
637    * also ViewUpdater and data to plugin if necessary.
638    * 
639    * @param i Slot to be set
640    * @param name Name of plugin - must be registered in PluginFactory or ref will be null
641    * @param act true for active plugin, false for inactive
642    * @throws QuimpPluginException When instance can not be created
643    */
644   public void setInstance(int i, final String name, boolean act) throws QuimpPluginException {
645 
646     if (name.isEmpty()) {
647       sPluginList.set(i, new Plugin()); // just create new empty plugin with no instance
648       return;
649     }
650     sPluginList.set(i, new Plugin(name, act, pluginFactory)); // create new Plugin
651 
652     IQuimpCorePlugin ref = getInstance(i);
653     // connects all goods to created plugin
654     if (ref != null) {
655       if (ref instanceof IQuimpPluginSynchro) { // if it supports backward synchronization
656         ((IQuimpPluginSynchro) ref).attachContext(viewUpdater); // attach BOA context
657       }
658     }
659   }
660 
661   /**
662    * Sets instance of plugin on slot i.
663    * 
664    * <p>If there is other plugin there, it replaces instance keeping its selection. Connects also
665    * ViewUpdater and data to plugin if necessary.
666    * 
667    * @param i Slot to be set
668    * @param name Name of plugin - must be registered in PluginFactory or ref will be null
669    * @param act true for active plugin, false for inactive
670    * @param config Configuration to connect to plugin
671    * @throws QuimpPluginException When config is not compatible or instance can not be created
672    */
673   private void setInstance(int i, final String name, boolean act, final ParamList config)
674           throws QuimpPluginException {
675     setInstance(i, name, act);
676     try {
677       sPluginList.get(i).uploadPluginConfig(config); // and set given config as local as well
678     } catch (QuimpPluginException e) { // catch here if e.g. lack of backward comp.
679       LOGGER.warn("Plugin " + sPluginList.get(i).name + " refused provided configuration");
680     }
681 
682   }
683 
684   /**
685    * Activate or deactivate plugin.
686    * 
687    * @param i Slot to be set
688    * @param act true for active plugin, false for inactive
689    */
690   public void setActive(int i, boolean act) {
691     sPluginList.get(i).isActive = act;
692   }
693 
694   /**
695    * Deletes plugin from memory.
696    * 
697    * @param i Number of slot to delete
698    */
699   public void deletePlugin(int i) {
700     sPluginList.set(i, new Plugin());
701   }
702 
703   /**
704    * Deletes all plugins from list and closes theirs windows.
705    */
706   public void clear() {
707     closeAllWindows();
708     for (int i = 0; i < sPluginList.size(); i++) {
709       sPluginList.set(i, new Plugin());
710     }
711   }
712 
713   /**
714    * Check if list of references contains all null elements.
715    * 
716    * @return true if list does not contain any valid plugin, false otherwise
717    */
718   public boolean isRefListEmpty() {
719     for (Plugin i : sPluginList) {
720       if (i.ref != null) {
721         return false;
722       }
723     }
724     return true;
725   }
726 
727   /**
728    * Download configuration from all opened plugins into internal structure. Performs transfer
729    * jar->local object.
730    * 
731    * <p>Configuration is kept locally for serialisation purposes.
732    * 
733    * @see #beforeSerialize()
734    */
735   public void downloadPluginsConfig() {
736     for (Plugin i : sPluginList) {
737       i.downloadPluginConfig();
738     }
739   }
740 
741   /**
742    * Transfer locally stored config to plugins instance for current list of plugin.
743    * 
744    * @see #downloadPluginsConfig()
745    */
746   public void uploadPluginsConfig() {
747     for (Plugin i : sPluginList) {
748       try {
749         i.uploadPluginConfig();
750       } catch (QuimpPluginException e) {
751         LOGGER.warn("Plugin " + i.name
752                 + " refused provided configuration. Default values will be used on next run");
753       }
754     }
755   }
756 
757   /**
758    * Fills fields in Plugin class related to configuration and version. These fields are
759    * serialized then.
760    * 
761    * <p>This method should be called directly before saving to have most recent options.
762    */
763   @Override
764   public void beforeSerialize() {
765     downloadPluginsConfig();
766   }
767 
768   /*
769    * Restore plugins instances after deserialization.
770    * 
771    * <p>On load all fields of {@link Plugin} object are restored from JSON automaticly except
772    * plugin instance. In this step the instance is created using fields populated from JSON.
773    * 
774    * <p>This method masks all QuimpPluginException exceptions that allows to skip defective plugin
775    * and load all next plugins.
776    */
777   @Override
778   public void afterSerialize() {
779     // go through list and create new Plugin using old values that were restored after loading
780     for (int i = 0; i < sPluginList.size(); i++) {
781       String savedVer = sPluginList.get(i).ver;
782       String name = getName(i); // only for exception handling to know name before setInstance
783       // sets new instance of plugin using old configuration loaded
784       // skip plugin that cannot be loaded or with wrong configuration
785       try {
786         // try load jar according to data saved in JSON
787         setInstance(i, getName(i), isActive(i), sPluginList.get(i).config);
788       } catch (QuimpPluginException e) {
789         deletePlugin(i); // delete plugin on any error
790         LOGGER.warn("Plugin name: " + name + " " + e.getMessage());
791       }
792       // check version compatibility - only inform user
793       if (getInstance(i) != null) {
794         if (!savedVer.equals(sPluginList.get(i).ver)) {
795           LOGGER.warn("Loaded plugin (" + sPluginList.get(i).name
796                   + ") is in different version than saved (" + sPluginList.get(i).ver + " vs. "
797                   + savedVer + ")");
798         }
799       }
800     }
801   }
802 
803   /**
804    * Close all opened plugins windows.
805    */
806   public void closeAllWindows() {
807     for (int i = 0; i < sPluginList.size(); i++) {
808       if (getInstance(i) != null) {
809         getInstance(i).showUi(false);
810       }
811     }
812   }
813 
814   /**
815    * Return names of plugins of given type registered in PluginFactory associated with this
816    * object.
817    * 
818    * @param type requested plugin type {@link PluginFactory}
819    * @return list of plugin names
820    * 
821    * @see com.github.celldynamics.quimp.plugin.engine.PluginFactory#getPluginNames(int)
822    */
823   public ArrayList<String> getPluginNames(int type) {
824     return pluginFactory.getPluginNames(type);
825   }
826 }
827 
828 /**
829  * Object builder for GSon and SnakePluginList class.
830  * 
831  * <p>This class is used on load JSon representation of SnakePluginList class.
832  * 
833  * @author p.baniukiewicz
834  * @see Gson documentation
835  */
836 class SnakePluginListInstanceCreator implements InstanceCreator<SnakePluginList> {
837 
838   private int size;
839   private PluginFactory pf;
840   private ViewUpdater vu;
841 
842   public SnakePluginListInstanceCreator(int size, final PluginFactory pf, final ViewUpdater vu) {
843     this.size = size;
844     this.pf = pf;
845     this.vu = vu;
846   }
847 
848   @Override
849   public SnakePluginList createInstance(Type arg0) {
850     return new SnakePluginList(size, pf, vu);
851   }
852 
853 }