|
1 /* |
|
2 * Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies). |
|
3 * All rights reserved. |
|
4 * This component and the accompanying materials are made available |
|
5 * under the terms of "Eclipse Public License v1.0" |
|
6 * which accompanies this distribution, and is available |
|
7 * at the URL "http://www.eclipse.org/legal/epl-v10.html". |
|
8 * |
|
9 * Initial Contributors: |
|
10 * Nokia Corporation - initial contribution. |
|
11 * |
|
12 * Contributors: |
|
13 * |
|
14 * Description: |
|
15 * |
|
16 */ |
|
17 using System; |
|
18 using System.Collections.Generic; |
|
19 using System.Text; |
|
20 using System.IO; |
|
21 using System.Reflection; |
|
22 |
|
23 namespace SymbianUtils.PluginManager |
|
24 { |
|
25 public class PluginManager<T> : DisposableObject, IEnumerable<T> |
|
26 { |
|
27 #region Events |
|
28 public delegate void PluginLoadedHandler( T aPlugin ); |
|
29 public event PluginLoadedHandler PluginLoaded; |
|
30 #endregion |
|
31 |
|
32 #region Constructors |
|
33 public PluginManager() |
|
34 { |
|
35 } |
|
36 |
|
37 public PluginManager( bool aDiagnostics ) |
|
38 { |
|
39 iDiagnostics = aDiagnostics; |
|
40 } |
|
41 |
|
42 public PluginManager( int aMinimumNumberOfExpectedPlugins ) |
|
43 { |
|
44 iMinimumNumberOfExpectedPlugins = aMinimumNumberOfExpectedPlugins; |
|
45 } |
|
46 |
|
47 public PluginManager( bool aDiagnostics, int aMinimumNumberOfExpectedPlugins ) |
|
48 : this( aDiagnostics ) |
|
49 { |
|
50 iMinimumNumberOfExpectedPlugins = aMinimumNumberOfExpectedPlugins; |
|
51 } |
|
52 #endregion |
|
53 |
|
54 #region API - loading |
|
55 public void Load( object[] aPluginConstructorParameters ) |
|
56 { |
|
57 Trace( "Load() - invoked by: " + System.Environment.StackTrace ); |
|
58 Assembly assembly = Assembly.GetCallingAssembly(); |
|
59 string path = Path.GetDirectoryName( assembly.Location ); |
|
60 FindPluginsWithinPath( path, KDefaultSearchSpecification, aPluginConstructorParameters, assembly ); |
|
61 } |
|
62 |
|
63 public void LoadFromCallingAssembly() |
|
64 { |
|
65 Trace( "LoadFromCallingAssembly(1) - invoked by: " + System.Environment.StackTrace ); |
|
66 Assembly assembly = Assembly.GetCallingAssembly(); |
|
67 FindPluginsWithinAssembly( assembly, true, new object[] {} ); |
|
68 Rationalise(); |
|
69 } |
|
70 |
|
71 public void LoadFromCallingAssembly( object[] aPluginConstructorParameters ) |
|
72 { |
|
73 Trace( "LoadFromCallingAssembly(2) - invoked by: " + System.Environment.StackTrace ); |
|
74 Assembly assembly = Assembly.GetCallingAssembly(); |
|
75 FindPluginsWithinAssembly( assembly, true, aPluginConstructorParameters ); |
|
76 Rationalise(); |
|
77 } |
|
78 |
|
79 public void LoadFromPath( string aPath, object[] aPluginConstructorParameters ) |
|
80 { |
|
81 FindPluginsWithinPath( aPath, KDefaultSearchSpecification, aPluginConstructorParameters, null ); |
|
82 } |
|
83 |
|
84 public void LoadFromPath( string aPath, string aMatchSpec, object[] aPluginConstructorParameters ) |
|
85 { |
|
86 FindPluginsWithinPath( aPath, aMatchSpec, aPluginConstructorParameters, null ); |
|
87 } |
|
88 #endregion |
|
89 |
|
90 #region API - misc |
|
91 public void Unload() |
|
92 { |
|
93 foreach ( T plugin in iPlugins ) |
|
94 { |
|
95 IDisposable disposable = plugin as IDisposable; |
|
96 if ( disposable != null ) |
|
97 { |
|
98 disposable.Dispose(); |
|
99 } |
|
100 } |
|
101 // |
|
102 iPlugins.Clear(); |
|
103 } |
|
104 |
|
105 public void Sort( IComparer<T> aComparer ) |
|
106 { |
|
107 iPlugins.Sort( aComparer ); |
|
108 } |
|
109 |
|
110 public void Sort( Comparison<T> aComparer ) |
|
111 { |
|
112 iPlugins.Sort( aComparer ); |
|
113 } |
|
114 |
|
115 public bool Predicate( Predicate<T> aPredicate ) |
|
116 { |
|
117 bool ret = iPlugins.Exists( aPredicate ); |
|
118 return ret; |
|
119 } |
|
120 |
|
121 public void Rationalise() |
|
122 { |
|
123 List<T> plugins = iPlugins; |
|
124 iPlugins = new List<T>(); |
|
125 // |
|
126 int count = plugins.Count; |
|
127 for ( int i = count - 1; i >= 0; i-- ) |
|
128 { |
|
129 T pluginToCheck = plugins[ i ]; |
|
130 plugins.RemoveAt( i ); |
|
131 Trace( "Rationalise() - checking hierarchy for derivates of: " + pluginToCheck.GetType().ToString() ); |
|
132 // |
|
133 bool isDerviedFromByOtherPlugin = IsClassDerivedFrom( plugins, pluginToCheck ); |
|
134 Trace( "Rationalise() - is base class for other plugin: " + isDerviedFromByOtherPlugin ); |
|
135 // |
|
136 if ( !isDerviedFromByOtherPlugin ) |
|
137 { |
|
138 iPlugins.Add( pluginToCheck ); |
|
139 Trace( "Rationalise() - found good plugin: " + pluginToCheck.GetType().ToString() ); |
|
140 OnPluginLoaded( pluginToCheck ); |
|
141 } |
|
142 } |
|
143 |
|
144 // Check that we loaded the expected minimum amount |
|
145 count = iPlugins.Count; |
|
146 SymbianUtils.SymDebug.SymDebugger.Assert( iMinimumNumberOfExpectedPlugins < 0 || count >= iMinimumNumberOfExpectedPlugins ); |
|
147 } |
|
148 #endregion |
|
149 |
|
150 #region Properties |
|
151 public int Count |
|
152 { |
|
153 get { return iPlugins.Count; } |
|
154 } |
|
155 |
|
156 public T this[ int aIndex ] |
|
157 { |
|
158 get { return iPlugins[ aIndex ]; } |
|
159 } |
|
160 |
|
161 public Type PluginType |
|
162 { |
|
163 get { return typeof( T ); } |
|
164 } |
|
165 #endregion |
|
166 |
|
167 #region Internal constants |
|
168 private const string KDefaultSearchSpecification = "*.plugin.dll"; |
|
169 #endregion |
|
170 |
|
171 #region Event propgation |
|
172 private void OnPluginLoaded( T aPlugin ) |
|
173 { |
|
174 if ( PluginLoaded != null ) |
|
175 { |
|
176 PluginLoaded( aPlugin ); |
|
177 } |
|
178 } |
|
179 #endregion |
|
180 |
|
181 #region Internal methods |
|
182 private void Trace( string aMessage ) |
|
183 { |
|
184 System.Diagnostics.Debug.WriteLineIf( iDiagnostics, "PluginLoader<" + typeof(T).Name + "> " + aMessage ); |
|
185 } |
|
186 |
|
187 private void Trace( Exception aException, string aFunction ) |
|
188 { |
|
189 Trace( string.Format( "PluginLoader.{0}() - exception: {1}", aFunction, aException.Message ) ); |
|
190 Trace( string.Format( "PluginLoader.{0}() - stack: {1}", aFunction, aException.StackTrace ) ); |
|
191 // |
|
192 if ( aException is ReflectionTypeLoadException ) |
|
193 { |
|
194 ReflectionTypeLoadException refEx = (ReflectionTypeLoadException) aException; |
|
195 foreach ( Exception l in refEx.LoaderExceptions ) |
|
196 { |
|
197 Trace( string.Format( " loader exception: {0}", l.Message ) ); |
|
198 Trace( string.Format( " loader stack: {0}", l.StackTrace ) ); |
|
199 } |
|
200 } |
|
201 else if ( aException is FileNotFoundException ) |
|
202 { |
|
203 FileNotFoundException fnf = (FileNotFoundException) aException; |
|
204 Trace( string.Format( " file name: {0}", fnf.FileName ) ); |
|
205 Trace( string.Format( " fusion log: {0}", fnf.FusionLog ) ); |
|
206 } |
|
207 } |
|
208 |
|
209 private static bool IsClassDerivedFrom( List<T> aPluginList, T aPlugin ) |
|
210 { |
|
211 bool ret = false; |
|
212 Type checkType = aPlugin.GetType(); |
|
213 // |
|
214 foreach ( T plugin in aPluginList ) |
|
215 { |
|
216 Type type = plugin.GetType(); |
|
217 if ( type.IsSubclassOf( checkType ) ) |
|
218 { |
|
219 ret = true; |
|
220 break; |
|
221 } |
|
222 } |
|
223 // |
|
224 return ret; |
|
225 } |
|
226 |
|
227 private void CreateFromTypes( Type[] aTypes, object[] aPluginConstructorParameters ) |
|
228 { |
|
229 Type pluginType = PluginType; |
|
230 Trace( "CreateFromTypes() - got " + aTypes.Length + " types. Searching for instances of: " + pluginType.Name ); |
|
231 // |
|
232 foreach ( Type type in aTypes ) |
|
233 { |
|
234 if ( pluginType.IsAssignableFrom( type ) ) |
|
235 { |
|
236 Trace( "CreateFromTypes() - found type: " + type.Name ); |
|
237 if ( !type.IsAbstract ) |
|
238 { |
|
239 Trace( "CreateFromTypes() - type \'" + type.Name + "\' is concrete implementation" ); |
|
240 CreateFromType( type, aPluginConstructorParameters ); |
|
241 } |
|
242 } |
|
243 } |
|
244 } |
|
245 |
|
246 private void CreateFromType( Type aType, object[] aPluginConstructorParameters ) |
|
247 { |
|
248 try |
|
249 { |
|
250 Trace( "CreateFromType() - calling constructor: " + aType.Name ); |
|
251 // |
|
252 object ret = CallConstructor( aType, aPluginConstructorParameters ); |
|
253 if ( ret != null ) |
|
254 { |
|
255 T plugin = (T) ret; |
|
256 Trace( "CreateFromType() - saving instance: " + plugin.GetType().ToString() ); |
|
257 iPlugins.Add( plugin ); |
|
258 } |
|
259 } |
|
260 catch ( FileNotFoundException ) |
|
261 { |
|
262 } |
|
263 catch ( Exception e ) |
|
264 { |
|
265 Trace( e, "CreateFromType" ); |
|
266 } |
|
267 } |
|
268 |
|
269 private object CallConstructor( Type aType, object[] aParameters ) |
|
270 { |
|
271 object ret = null; |
|
272 // |
|
273 try |
|
274 { |
|
275 ret = Activator.CreateInstance( aType, aParameters ); |
|
276 } |
|
277 catch ( Exception e ) |
|
278 { |
|
279 Trace( e, "CallConstructor" ); |
|
280 } |
|
281 // |
|
282 return ret; |
|
283 } |
|
284 |
|
285 private void LoadAssemblyAndCreatePlugins( string aFileName, object[] aPluginConstructorParameters, bool aExplicit ) |
|
286 { |
|
287 try |
|
288 { |
|
289 Trace( "LoadAssemblyAndCreatePlugins() - Trying to load plugins from dll: " + aFileName ); |
|
290 if ( SymbianUtils.Assemblies.AssemblyHelper.IsCLRAssembly( aFileName ) ) |
|
291 { |
|
292 Assembly pluginAssembly = Assembly.LoadFrom( aFileName ); |
|
293 if ( pluginAssembly != null ) |
|
294 { |
|
295 FindPluginsWithinAssembly( pluginAssembly, aExplicit, aPluginConstructorParameters ); |
|
296 } |
|
297 } |
|
298 } |
|
299 catch ( BadImageFormatException ) |
|
300 { |
|
301 // Not a managed dll - ignore error |
|
302 } |
|
303 catch ( Exception assemblyLoadException ) |
|
304 { |
|
305 Trace( assemblyLoadException, "LoadAssemblyAndCreatePlugins" ); |
|
306 } |
|
307 } |
|
308 |
|
309 private void FindPluginsWithinAssembly( Assembly aAssembly, bool aExplit, object[] aPluginConstructorParameters ) |
|
310 { |
|
311 bool okayToGetTypes = aExplit; |
|
312 |
|
313 #if BETTER_PERFORMANCE |
|
314 // If explit load requested, then don't check for plugin attribute. Otherwise, to avoid |
|
315 // enumerating all types we can check for our special "plugin" attribute |
|
316 object[] attributes = aAssembly.GetCustomAttributes( typeof( PluginAssemblyAttribute ), false) ; |
|
317 if ( attributes != null && attributes.Length > 0 ) |
|
318 { |
|
319 okayToGetTypes = true; |
|
320 } |
|
321 #else |
|
322 okayToGetTypes = true; |
|
323 #endif |
|
324 |
|
325 // Now get types |
|
326 if ( okayToGetTypes ) |
|
327 { |
|
328 Trace( "DoLoad() - getting types from assembly: " + aAssembly.Location ); |
|
329 Type[] types = aAssembly.GetTypes(); |
|
330 CreateFromTypes( types, aPluginConstructorParameters ); |
|
331 } |
|
332 } |
|
333 |
|
334 private void FindPluginsWithinPath( string aPath, string aMatchSpec, object[] aPluginConstructorParameters, Assembly aAdditionalSearchAssembly ) |
|
335 { |
|
336 Trace( string.Format( "FindPluginsWithinPath() - path: {0}, matchSpec: {1}, invoked by: {2}", aPath, aMatchSpec, System.Environment.StackTrace ) ); |
|
337 Unload(); |
|
338 |
|
339 // Find from path |
|
340 string[] dllNames = Directory.GetFiles( aPath, aMatchSpec ); |
|
341 foreach ( string dll in dllNames ) |
|
342 { |
|
343 string justFileName = Path.GetFileName( dll ); |
|
344 LoadAssemblyAndCreatePlugins( dll, aPluginConstructorParameters, false ); |
|
345 } |
|
346 |
|
347 // Check additional assembly |
|
348 if ( aAdditionalSearchAssembly != null ) |
|
349 { |
|
350 FindPluginsWithinAssembly( aAdditionalSearchAssembly, true, aPluginConstructorParameters ); |
|
351 } |
|
352 |
|
353 // Check hierarchy for derived plugins |
|
354 Rationalise(); |
|
355 } |
|
356 #endregion |
|
357 |
|
358 #region From IEnumerable<T> |
|
359 public IEnumerator<T> GetEnumerator() |
|
360 { |
|
361 foreach ( T p in iPlugins ) |
|
362 { |
|
363 yield return p; |
|
364 } |
|
365 } |
|
366 |
|
367 System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() |
|
368 { |
|
369 foreach ( T p in iPlugins ) |
|
370 { |
|
371 yield return p; |
|
372 } |
|
373 } |
|
374 #endregion |
|
375 |
|
376 #region From System.Object |
|
377 public override string ToString() |
|
378 { |
|
379 StringBuilder ret = new StringBuilder(); |
|
380 ret.AppendFormat( "Found: {0} of type: {1}", iPlugins.Count, typeof(T).Name ); |
|
381 if ( iMinimumNumberOfExpectedPlugins > 0 ) |
|
382 { |
|
383 ret.AppendFormat( ", expected: {2}", iMinimumNumberOfExpectedPlugins ); |
|
384 } |
|
385 return ret.ToString(); |
|
386 } |
|
387 #endregion |
|
388 |
|
389 #region From DisposableObject |
|
390 protected override void CleanupManagedResources() |
|
391 { |
|
392 try |
|
393 { |
|
394 base.CleanupManagedResources(); |
|
395 } |
|
396 finally |
|
397 { |
|
398 foreach ( T obj in iPlugins ) |
|
399 { |
|
400 IDisposable disp = obj as IDisposable; |
|
401 if ( disp != null ) |
|
402 { |
|
403 disp.Dispose(); |
|
404 } |
|
405 } |
|
406 // |
|
407 iPlugins.Clear(); |
|
408 iPlugins = null; |
|
409 } |
|
410 } |
|
411 #endregion |
|
412 |
|
413 #region Data members |
|
414 private bool iDiagnostics = false; |
|
415 private int iMinimumNumberOfExpectedPlugins = -1; |
|
416 private List<T> iPlugins = new List<T>(); |
|
417 #endregion |
|
418 } |
|
419 } |