~ubuntu-branches/ubuntu/trusty/monodevelop/trusty-proposed

« back to all changes in this revision

Viewing changes to external/mono-addins/Mono.Addins/Mono.Addins.Database/AddinScanner.cs

  • Committer: Package Import Robot
  • Author(s): Jo Shields
  • Date: 2013-05-12 09:46:03 UTC
  • mto: This revision was merged to the branch mainline in revision 29.
  • Revision ID: package-import@ubuntu.com-20130512094603-mad323bzcxvmcam0
Tags: upstream-4.0.5+dfsg
ImportĀ upstreamĀ versionĀ 4.0.5+dfsg

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
//
 
2
// AddinScanner.cs
 
3
//
 
4
// Author:
 
5
//   Lluis Sanchez Gual
 
6
//
 
7
// Copyright (C) 2007 Novell, Inc (http://www.novell.com)
 
8
//
 
9
// Permission is hereby granted, free of charge, to any person obtaining
 
10
// a copy of this software and associated documentation files (the
 
11
// "Software"), to deal in the Software without restriction, including
 
12
// without limitation the rights to use, copy, modify, merge, publish,
 
13
// distribute, sublicense, and/or sell copies of the Software, and to
 
14
// permit persons to whom the Software is furnished to do so, subject to
 
15
// the following conditions:
 
16
// 
 
17
// The above copyright notice and this permission notice shall be
 
18
// included in all copies or substantial portions of the Software.
 
19
// 
 
20
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 
21
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 
22
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 
23
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
 
24
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
 
25
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 
26
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 
27
//
 
28
 
 
29
 
 
30
using System;
 
31
using System.Collections;
 
32
using System.Collections.Generic;
 
33
using System.IO;
 
34
using System.Text;
 
35
using System.Reflection;
 
36
using System.Collections.Specialized;
 
37
using System.Xml;
 
38
using System.ComponentModel;
 
39
 
 
40
using Mono.Addins.Description;
 
41
 
 
42
namespace Mono.Addins.Database
 
43
{
 
44
        class AddinScanner: MarshalByRefObject
 
45
        {
 
46
                AddinDatabase database;
 
47
                AddinFileSystemExtension fs;
 
48
                Dictionary<IAssemblyReflector,object> coreAssemblies = new Dictionary<IAssemblyReflector, object> ();
 
49
                
 
50
                public AddinScanner (AddinDatabase database, AddinScanResult scanResult, IProgressStatus monitor)
 
51
                {
 
52
                        this.database = database;
 
53
                        fs = database.FileSystem;
 
54
                }
 
55
                
 
56
                public void ScanFolder (IProgressStatus monitor, string path, string domain, AddinScanResult scanResult)
 
57
                {
 
58
                        path = Path.GetFullPath (path);
 
59
                        
 
60
                        // Avoid folders including each other
 
61
                        if (!scanResult.VisitFolder (path))
 
62
                                return;
 
63
                        
 
64
                        AddinScanFolderInfo folderInfo;
 
65
                        if (!database.GetFolderInfoForPath (monitor, path, out folderInfo)) {
 
66
                                // folderInfo file was corrupt.
 
67
                                // Just in case, we are going to regenerate all relation data.
 
68
                                if (!fs.DirectoryExists (path))
 
69
                                        scanResult.RegenerateRelationData = true;
 
70
                        } else {
 
71
                                // Directory is included but it doesn't exist. Ignore it.
 
72
                                if (folderInfo == null && !fs.DirectoryExists (path))
 
73
                                        return;
 
74
                        }
 
75
                        
 
76
                        // if domain is null it means that a new domain has to be created.
 
77
                        
 
78
                        bool sharedFolder = domain == AddinDatabase.GlobalDomain;
 
79
                        bool isNewFolder = folderInfo == null;
 
80
                        
 
81
                        if (isNewFolder) {
 
82
                                // No folder info. It is the first time this folder is scanned.
 
83
                                // There is no need to store this object if the folder does not
 
84
                                // contain add-ins.
 
85
                                folderInfo = new AddinScanFolderInfo (path);
 
86
                        }
 
87
                        
 
88
                        if (!sharedFolder && (folderInfo.SharedFolder || folderInfo.Domain != domain)) {
 
89
                                // If the folder already has a domain, reuse it
 
90
                                if (domain == null && folderInfo.RootsDomain != null && folderInfo.RootsDomain != AddinDatabase.GlobalDomain)
 
91
                                        domain = folderInfo.RootsDomain;
 
92
                                else if (domain == null) {
 
93
                                        folderInfo.Domain = domain = database.GetUniqueDomainId ();
 
94
                                        scanResult.RegenerateRelationData = true;
 
95
                                }
 
96
                                else {
 
97
                                        folderInfo.Domain = domain;
 
98
                                        if (!isNewFolder) {
 
99
                                                // Domain has changed. Update the folder info and regenerate everything.
 
100
                                                scanResult.RegenerateRelationData = true;
 
101
                                                scanResult.RegisterModifiedFolderInfo (folderInfo);
 
102
                                        }
 
103
                                }
 
104
                        }
 
105
                        else if (!folderInfo.SharedFolder && sharedFolder) {
 
106
                                scanResult.RegenerateRelationData = true;
 
107
                        }
 
108
                        
 
109
                        folderInfo.SharedFolder = sharedFolder;
 
110
                        
 
111
                        // If there is no domain assigned to the host, get one now
 
112
                        if (scanResult.Domain == AddinDatabase.UnknownDomain)
 
113
                                scanResult.Domain = domain;
 
114
                        
 
115
                        // Discard folders not belonging to the required domain
 
116
                        if (scanResult.Domain != null && domain != scanResult.Domain && domain != AddinDatabase.GlobalDomain) {
 
117
                                return;
 
118
                        }
 
119
                        
 
120
                        if (monitor.LogLevel > 1 && !scanResult.LocateAssembliesOnly)
 
121
                                monitor.Log ("Checking: " + path);
 
122
                        
 
123
                        if (fs.DirectoryExists (path))
 
124
                        {
 
125
                                IEnumerable<string> files = fs.GetFiles (path);
 
126
                                
 
127
                                // First of all, look for .addin files. Addin files must be processed before
 
128
                                // assemblies, because they may add files to the ignore list (i.e., assemblies
 
129
                                // included in .addin files won't be scanned twice).
 
130
                                foreach (string file in files) {
 
131
                                        if (file.EndsWith (".addin.xml") || file.EndsWith (".addin")) {
 
132
                                                RegisterFileToScan (monitor, file, scanResult, folderInfo);
 
133
                                        }
 
134
                                }
 
135
                                
 
136
                                // Now scan assemblies. They can also add files to the ignore list.
 
137
                                
 
138
                                foreach (string file in files) {
 
139
                                        string ext = Path.GetExtension (file).ToLower ();
 
140
                                        if (ext == ".dll" || ext == ".exe") {
 
141
                                                RegisterFileToScan (monitor, file, scanResult, folderInfo);
 
142
                                                scanResult.AddAssemblyLocation (file);
 
143
                                        }
 
144
                                }
 
145
                                
 
146
                                // Finally scan .addins files
 
147
                                
 
148
                                foreach (string file in files) {
 
149
                                        if (Path.GetExtension (file).EndsWith (".addins")) {
 
150
                                                ScanAddinsFile (monitor, file, domain, scanResult);
 
151
                                        }
 
152
                                }
 
153
                        }
 
154
                        else if (!scanResult.LocateAssembliesOnly) {
 
155
                                // The folder has been deleted. All add-ins defined in that folder should also be deleted.
 
156
                                scanResult.RegenerateRelationData = true;
 
157
                                scanResult.ChangesFound = true;
 
158
                                if (scanResult.CheckOnly)
 
159
                                        return;
 
160
                                database.DeleteFolderInfo (monitor, folderInfo);
 
161
                        }
 
162
                        
 
163
                        if (scanResult.LocateAssembliesOnly)
 
164
                                return;
 
165
                        
 
166
                        // Look for deleted add-ins.
 
167
                        
 
168
                        UpdateDeletedAddins (monitor, folderInfo, scanResult);
 
169
                }
 
170
                
 
171
                public void UpdateDeletedAddins (IProgressStatus monitor, AddinScanFolderInfo folderInfo, AddinScanResult scanResult)
 
172
                {
 
173
                        ArrayList missing = folderInfo.GetMissingAddins (fs);
 
174
                        if (missing.Count > 0) {
 
175
                                if (fs.DirectoryExists (folderInfo.Folder))
 
176
                                        scanResult.RegisterModifiedFolderInfo (folderInfo);
 
177
                                scanResult.ChangesFound = true;
 
178
                                if (scanResult.CheckOnly)
 
179
                                        return;
 
180
                                        
 
181
                                foreach (AddinFileInfo info in missing) {
 
182
                                        database.UninstallAddin (monitor, info.Domain, info.AddinId, info.File, scanResult);
 
183
                                }
 
184
                        }
 
185
                }
 
186
                
 
187
                void RegisterFileToScan (IProgressStatus monitor, string file, AddinScanResult scanResult, AddinScanFolderInfo folderInfo)
 
188
                {
 
189
                        if (scanResult.LocateAssembliesOnly)
 
190
                                return;
 
191
 
 
192
                        AddinFileInfo finfo = folderInfo.GetAddinFileInfo (file);
 
193
                        bool added = false;
 
194
                           
 
195
                        if (finfo != null && (!finfo.IsAddin || finfo.Domain == folderInfo.GetDomain (finfo.IsRoot)) && fs.GetLastWriteTime (file) == finfo.LastScan && !scanResult.RegenerateAllData) {
 
196
                                if (finfo.ScanError) {
 
197
                                        // Always schedule the file for scan if there was an error in a previous scan.
 
198
                                        // However, don't set ChangesFound=true, in this way if there isn't any other
 
199
                                        // change in the registry, the file won't be scanned again.
 
200
                                        scanResult.AddFileToScan (file, folderInfo);
 
201
                                        added = true;
 
202
                                }
 
203
                        
 
204
                                if (!finfo.IsAddin)
 
205
                                        return;
 
206
                                
 
207
                                if (database.AddinDescriptionExists (finfo.Domain, finfo.AddinId)) {
 
208
                                        // It is an add-in and it has not changed. Paths in the ignore list
 
209
                                        // are still valid, so they can be used.
 
210
                                        if (finfo.IgnorePaths != null)
 
211
                                                scanResult.AddPathsToIgnore (finfo.IgnorePaths);
 
212
                                        return;
 
213
                                }
 
214
                        }
 
215
                        
 
216
                        scanResult.ChangesFound = true;
 
217
                        
 
218
                        if (!scanResult.CheckOnly && !added)
 
219
                                scanResult.AddFileToScan (file, folderInfo);
 
220
                }
 
221
                
 
222
                public void ScanFile (IProgressStatus monitor, string file, AddinScanFolderInfo folderInfo, AddinScanResult scanResult)
 
223
                {
 
224
                        if (scanResult.IgnorePath (file)) {
 
225
                                // The file must be ignored. Maybe it caused a crash in a previous scan, or it
 
226
                                // might be included by a .addin file (in which case it will be scanned when processing
 
227
                                // the .addin file).
 
228
                                folderInfo.SetLastScanTime (file, null, false, fs.GetLastWriteTime (file), true);
 
229
                                return;
 
230
                        }
 
231
                        
 
232
                        if (monitor.LogLevel > 1)
 
233
                                monitor.Log ("Scanning file: " + file);
 
234
                        
 
235
                        // Log the file to be scanned, so in case of a process crash the main process
 
236
                        // will know what crashed
 
237
                        monitor.Log ("plog:scan:" + file);
 
238
                        
 
239
                        string scannedAddinId = null;
 
240
                        bool scannedIsRoot = false;
 
241
                        bool scanSuccessful = false;
 
242
                        AddinDescription config = null;
 
243
                        
 
244
                        try {
 
245
                                string ext = Path.GetExtension (file).ToLower ();
 
246
                                
 
247
                                if (ext == ".dll" || ext == ".exe")
 
248
                                        scanSuccessful = ScanAssembly (monitor, file, scanResult, out config);
 
249
                                else
 
250
                                        scanSuccessful = ScanConfigAssemblies (monitor, file, scanResult, out config);
 
251
 
 
252
                                if (config != null) {
 
253
                                        
 
254
                                        AddinFileInfo fi = folderInfo.GetAddinFileInfo (file);
 
255
                                        
 
256
                                        // If version is not specified, make up one
 
257
                                        if (config.Version.Length == 0) {
 
258
                                                config.Version = "0.0.0.0";
 
259
                                        }
 
260
                                        
 
261
                                        if (config.LocalId.Length == 0) {
 
262
                                                // Generate an internal id for this add-in
 
263
                                                config.LocalId = database.GetUniqueAddinId (file, (fi != null ? fi.AddinId : null), config.Namespace, config.Version);
 
264
                                                config.HasUserId = false;
 
265
                                        }
 
266
                                        
 
267
                                        // Check errors in the description
 
268
                                        StringCollection errors = config.Verify (fs);
 
269
                                        
 
270
                                        if (database.IsGlobalRegistry && config.AddinId.IndexOf ('.') == -1) {
 
271
                                                errors.Add ("Add-ins registered in the global registry must have a namespace.");
 
272
                                        }
 
273
                                            
 
274
                                        if (errors.Count > 0) {
 
275
                                                scanSuccessful = false;
 
276
                                                monitor.ReportError ("Errors found in add-in '" + file + ":", null);
 
277
                                                foreach (string err in errors)
 
278
                                                        monitor.ReportError (err, null);
 
279
                                        }
 
280
                                
 
281
                                        // Make sure all extensions sets are initialized with the correct add-in id
 
282
                                        
 
283
                                        config.SetExtensionsAddinId (config.AddinId);
 
284
                                        
 
285
                                        scanResult.ChangesFound = true;
 
286
                                        
 
287
                                        // If the add-in already existed, try to reuse the relation data it had.
 
288
                                        // Also, the dependencies of the old add-in need to be re-analized
 
289
                                        
 
290
                                        AddinDescription existingDescription = null;
 
291
                                        bool res = database.GetAddinDescription (monitor, folderInfo.Domain, config.AddinId, config.AddinFile, out existingDescription);
 
292
                                        
 
293
                                        // If we can't get information about the old assembly, just regenerate all relation data
 
294
                                        if (!res)
 
295
                                                scanResult.RegenerateRelationData = true;
 
296
                                        
 
297
                                        string replaceFileName = null;
 
298
                                        
 
299
                                        if (existingDescription != null) {
 
300
                                                // Reuse old relation data
 
301
                                                config.MergeExternalData (existingDescription);
 
302
                                                Util.AddDependencies (existingDescription, scanResult);
 
303
                                                replaceFileName = existingDescription.FileName;
 
304
                                        }
 
305
                                        
 
306
                                        // If the scanned file results in an add-in version different from the one obtained from
 
307
                                        // previous scans, the old add-in needs to be uninstalled.
 
308
                                        if (fi != null && fi.IsAddin && fi.AddinId != config.AddinId) {
 
309
                                                database.UninstallAddin (monitor, folderInfo.Domain, fi.AddinId, fi.File, scanResult);
 
310
                                                
 
311
                                                // If the add-in version has changed, regenerate everything again since old data can't be reused
 
312
                                                if (Addin.GetIdName (fi.AddinId) == Addin.GetIdName (config.AddinId))
 
313
                                                        scanResult.RegenerateRelationData = true;
 
314
                                        }
 
315
                                        
 
316
                                        // If a description could be generated, save it now (if the scan was successful)
 
317
                                        if (scanSuccessful) {
 
318
                                                
 
319
                                                // Assign the domain
 
320
                                                if (config.IsRoot) {
 
321
                                                        if (folderInfo.RootsDomain == null) {
 
322
                                                                if (scanResult.Domain != null && scanResult.Domain != AddinDatabase.UnknownDomain && scanResult.Domain != AddinDatabase.GlobalDomain)
 
323
                                                                        folderInfo.RootsDomain = scanResult.Domain;
 
324
                                                                else
 
325
                                                                        folderInfo.RootsDomain = database.GetUniqueDomainId ();
 
326
                                                        }
 
327
                                                        config.Domain = folderInfo.RootsDomain;
 
328
                                                } else
 
329
                                                        config.Domain = folderInfo.Domain;
 
330
                                                
 
331
                                                if (config.IsRoot && scanResult.HostIndex != null) {
 
332
                                                        // If the add-in is a root, register its assemblies
 
333
                                                        foreach (string f in config.MainModule.Assemblies) {
 
334
                                                                string asmFile = Path.Combine (config.BasePath, f);
 
335
                                                                scanResult.HostIndex.RegisterAssembly (asmFile, config.AddinId, config.AddinFile, config.Domain);
 
336
                                                        }
 
337
                                                }
 
338
                                                
 
339
                                                // Finally save
 
340
                                                
 
341
                                                if (database.SaveDescription (monitor, config, replaceFileName)) {
 
342
                                                        // The new dependencies also have to be updated
 
343
                                                        Util.AddDependencies (config, scanResult);
 
344
                                                        scanResult.AddAddinToUpdate (config.AddinId);
 
345
                                                        scannedAddinId = config.AddinId;
 
346
                                                        scannedIsRoot = config.IsRoot;
 
347
                                                        return;
 
348
                                                }
 
349
                                        }
 
350
                                }
 
351
                        }
 
352
                        catch (Exception ex) {
 
353
                                monitor.ReportError ("Unexpected error while scanning file: " + file, ex);
 
354
                        }
 
355
                        finally {
 
356
                                AddinFileInfo ainfo = folderInfo.SetLastScanTime (file, scannedAddinId, scannedIsRoot, fs.GetLastWriteTime (file), !scanSuccessful);
 
357
                                
 
358
                                if (scanSuccessful && config != null) {
 
359
                                        // Update the ignore list in the folder info object. To be used in the next scan
 
360
                                        foreach (string df in config.AllIgnorePaths) {
 
361
                                                string path = Path.Combine (config.BasePath, df);
 
362
                                                ainfo.AddPathToIgnore (Path.GetFullPath (path));
 
363
                                        }
 
364
                                }
 
365
                                
 
366
                                monitor.Log ("plog:endscan");
 
367
                        }
 
368
                }
 
369
                
 
370
                public AddinDescription ScanSingleFile (IProgressStatus monitor, string file, AddinScanResult scanResult)
 
371
                {
 
372
                        AddinDescription config = null;
 
373
                        
 
374
                        if (monitor.LogLevel > 1)
 
375
                                monitor.Log ("Scanning file: " + file);
 
376
                                
 
377
                        monitor.Log ("plog:scan:" + file);
 
378
                        
 
379
                        try {
 
380
                                string ext = Path.GetExtension (file).ToLower ();
 
381
                                bool scanSuccessful;
 
382
                                
 
383
                                if (ext == ".dll" || ext == ".exe")
 
384
                                        scanSuccessful = ScanAssembly (monitor, file, scanResult, out config);
 
385
                                else
 
386
                                        scanSuccessful = ScanConfigAssemblies (monitor, file, scanResult, out config);
 
387
 
 
388
                                if (scanSuccessful && config != null) {
 
389
                                        
 
390
                                        config.Domain = "global";
 
391
                                        if (config.Version.Length == 0)
 
392
                                                config.Version = "0.0.0.0";
 
393
                                        
 
394
                                        if (config.LocalId.Length == 0) {
 
395
                                                // Generate an internal id for this add-in
 
396
                                                config.LocalId = database.GetUniqueAddinId (file, "", config.Namespace, config.Version);
 
397
                                        }
 
398
                                }
 
399
                        }
 
400
                        catch (Exception ex) {
 
401
                                monitor.ReportError ("Unexpected error while scanning file: " + file, ex);
 
402
                        } finally {
 
403
                                monitor.Log ("plog:endscan");
 
404
                        }
 
405
                        return config;
 
406
                }
 
407
                
 
408
                public void ScanAddinsFile (IProgressStatus monitor, string file, string domain, AddinScanResult scanResult)
 
409
                {
 
410
                        XmlTextReader r = null;
 
411
                        ArrayList directories = new ArrayList ();
 
412
                        ArrayList directoriesWithSubdirs = new ArrayList ();
 
413
                        string basePath = Path.GetDirectoryName (file);
 
414
                        
 
415
                        try {
 
416
                                r = new XmlTextReader (fs.OpenTextFile (file));
 
417
                                r.MoveToContent ();
 
418
                                if (r.IsEmptyElement)
 
419
                                        return;
 
420
                                r.ReadStartElement ();
 
421
                                r.MoveToContent ();
 
422
                                while (r.NodeType != XmlNodeType.EndElement) {
 
423
                                        if (r.NodeType == XmlNodeType.Element && r.LocalName == "Directory") {
 
424
                                                string subs = r.GetAttribute ("include-subdirs");
 
425
                                                string sdom;
 
426
                                                string share = r.GetAttribute ("shared");
 
427
                                                if (share == "true")
 
428
                                                        sdom = AddinDatabase.GlobalDomain;
 
429
                                                else if (share == "false")
 
430
                                                        sdom = null;
 
431
                                                else
 
432
                                                        sdom = domain; // Inherit the domain
 
433
                                                
 
434
                                                string path = r.ReadElementString ().Trim ();
 
435
                                                if (path.Length > 0) {
 
436
                                                        path = Util.NormalizePath (path);
 
437
                                                        if (subs == "true")
 
438
                                                                directoriesWithSubdirs.Add (new string[] {path, sdom});
 
439
                                                        else
 
440
                                                                directories.Add (new string[] {path, sdom});
 
441
                                                }
 
442
                                        }
 
443
                                        else if (r.NodeType == XmlNodeType.Element && r.LocalName == "GacAssembly") {
 
444
                                                string aname = r.ReadElementString ().Trim ();
 
445
                                                if (aname.Length > 0) {
 
446
                                                        aname = Util.NormalizePath (aname);
 
447
                                                        aname = Util.GetGacPath (aname);
 
448
                                                        if (aname != null) {
 
449
                                                                // Gac assemblies always use the global domain
 
450
                                                                directories.Add (new string[] {aname, AddinDatabase.GlobalDomain});
 
451
                                                        }
 
452
                                                }
 
453
                                        }
 
454
                                        else if (r.NodeType == XmlNodeType.Element && r.LocalName == "Exclude") {
 
455
                                                string path = r.ReadElementString ().Trim ();
 
456
                                                if (path.Length > 0) {
 
457
                                                        path = Util.NormalizePath (path);
 
458
                                                        if (!Path.IsPathRooted (path))
 
459
                                                                path = Path.Combine (basePath, path);
 
460
                                                        scanResult.AddPathToIgnore (Path.GetFullPath (path));
 
461
                                                }
 
462
                                        }
 
463
                                        else
 
464
                                                r.Skip ();
 
465
                                        r.MoveToContent ();
 
466
                                }
 
467
                        } catch (Exception ex) {
 
468
                                monitor.ReportError ("Could not process addins file: " + file, ex);
 
469
                                return;
 
470
                        } finally {
 
471
                                if (r != null)
 
472
                                        r.Close ();
 
473
                        }
 
474
                        
 
475
                        foreach (string[] d in directories) {
 
476
                                string dir = d[0];
 
477
                                if (!Path.IsPathRooted (dir))
 
478
                                        dir = Path.Combine (basePath, dir);
 
479
                                ScanFolder (monitor, dir, d[1], scanResult);
 
480
                        }
 
481
                        foreach (string[] d in directoriesWithSubdirs) {
 
482
                                string dir = d[0];
 
483
                                if (!Path.IsPathRooted (dir))
 
484
                                        dir = Path.Combine (basePath, dir);
 
485
                                ScanFolderRec (monitor, dir, d[1], scanResult);
 
486
                        }
 
487
                }
 
488
                
 
489
                public void ScanFolderRec (IProgressStatus monitor, string dir, string domain, AddinScanResult scanResult)
 
490
                {
 
491
                        ScanFolder (monitor, dir, domain, scanResult);
 
492
                        
 
493
                        if (!fs.DirectoryExists (dir))
 
494
                                return;
 
495
                                
 
496
                        foreach (string sd in fs.GetDirectories (dir))
 
497
                                ScanFolderRec (monitor, sd, domain, scanResult);
 
498
                }
 
499
                
 
500
                bool ScanConfigAssemblies (IProgressStatus monitor, string filePath, AddinScanResult scanResult, out AddinDescription config)
 
501
                {
 
502
                        config = null;
 
503
                        
 
504
                        try {
 
505
                                IAssemblyReflector reflector = GetReflector (monitor, scanResult, filePath);
 
506
                                
 
507
                                string basePath = Path.GetDirectoryName (filePath);
 
508
                                
 
509
                                using (var s = fs.OpenFile (filePath)) {
 
510
                                        config = AddinDescription.Read (s, basePath);
 
511
                                }
 
512
                                config.FileName = filePath;
 
513
                                config.SetBasePath (basePath);
 
514
                                config.AddinFile = filePath;
 
515
                                
 
516
                                return ScanDescription (monitor, reflector, config, null, scanResult);
 
517
                        }
 
518
                        catch (Exception ex) {
 
519
                                // Something went wrong while scanning the assembly. We'll ignore it for now.
 
520
                                monitor.ReportError ("There was an error while scanning add-in: " + filePath, ex);
 
521
                                return false;
 
522
                        }
 
523
                }
 
524
                
 
525
                IAssemblyReflector GetReflector (IProgressStatus monitor, AddinScanResult scanResult, string filePath)
 
526
                {
 
527
                        IAssemblyReflector reflector = fs.GetReflectorForFile (scanResult, filePath);
 
528
                        object coreAssembly;
 
529
                        if (!coreAssemblies.TryGetValue (reflector, out coreAssembly)) {
 
530
                                if (monitor.LogLevel > 1)
 
531
                                        monitor.Log ("Using assembly reflector: " + reflector.GetType ());
 
532
                                coreAssemblies [reflector] = coreAssembly = reflector.LoadAssembly (GetType().Assembly.Location);
 
533
                        }
 
534
                        return reflector;
 
535
                }
 
536
                
 
537
                bool ScanAssembly (IProgressStatus monitor, string filePath, AddinScanResult scanResult, out AddinDescription config)
 
538
                {
 
539
                        config = null;
 
540
                                
 
541
                        try {
 
542
                                IAssemblyReflector reflector = GetReflector (monitor, scanResult, filePath);
 
543
                                object asm = reflector.LoadAssembly (filePath);
 
544
                                if (asm == null)
 
545
                                        throw new Exception ("Could not load assembly: " + filePath);
 
546
                                
 
547
                                // Get the config file from the resources, if there is one
 
548
                                
 
549
                                foreach (string res in reflector.GetResourceNames (asm)) {
 
550
                                        if (res.EndsWith (".addin") || res.EndsWith (".addin.xml")) {
 
551
                                                using (Stream s = reflector.GetResourceStream (asm, res)) {
 
552
                                                        AddinDescription ad = AddinDescription.Read (s, Path.GetDirectoryName (filePath));
 
553
                                                        if (config != null) {
 
554
                                                                if (!config.IsExtensionModel && !ad.IsExtensionModel) {
 
555
                                                                        // There is more than one add-in definition
 
556
                                                                        monitor.ReportError ("Duplicate add-in definition found in assembly: " + filePath, null);
 
557
                                                                        return false;
 
558
                                                                }
 
559
                                                                config = AddinDescription.Merge (config, ad);
 
560
                                                        } else
 
561
                                                                config = ad;
 
562
                                                }
 
563
                                        }
 
564
                                }
 
565
                                
 
566
                                if (config == null) {
 
567
                                        // In this case, only scan the assembly if it has the Addin attribute.
 
568
                                        AddinAttribute att = (AddinAttribute) reflector.GetCustomAttribute (asm, typeof(AddinAttribute), false);
 
569
                                        if (att == null)
 
570
                                                return true;
 
571
 
 
572
                                        config = new AddinDescription ();
 
573
                                }
 
574
                                
 
575
                                config.SetBasePath (Path.GetDirectoryName (filePath));
 
576
                                config.AddinFile = filePath;
 
577
                                
 
578
                                string rasmFile = Path.GetFileName (filePath);
 
579
                                if (!config.MainModule.Assemblies.Contains (rasmFile))
 
580
                                        config.MainModule.Assemblies.Add (rasmFile);
 
581
                                
 
582
                                return ScanDescription (monitor, reflector, config, asm, scanResult);
 
583
                        }
 
584
                        catch (Exception ex) {
 
585
                                // Something went wrong while scanning the assembly. We'll ignore it for now.
 
586
                                monitor.ReportError ("There was an error while scanning assembly: " + filePath, ex);
 
587
                                return false;
 
588
                        }
 
589
                }
 
590
 
 
591
                bool ScanDescription (IProgressStatus monitor, IAssemblyReflector reflector, AddinDescription config, object rootAssembly, AddinScanResult scanResult)
 
592
                {
 
593
                        // First of all scan the main module
 
594
                        
 
595
                        ArrayList assemblies = new ArrayList ();
 
596
                        
 
597
                        try {
 
598
                                string rootAsmFile = null;
 
599
                                
 
600
                                if (rootAssembly != null) {
 
601
                                        ScanAssemblyAddinHeaders (reflector, config, rootAssembly, scanResult);
 
602
                                        ScanAssemblyImports (reflector, config.MainModule, rootAssembly);
 
603
                                        assemblies.Add (rootAssembly);
 
604
                                        rootAsmFile = Path.GetFileName (config.AddinFile);
 
605
                                }
 
606
                                
 
607
                                // The assembly list may be modified while scanning the headears, so
 
608
                                // we use a for loop instead of a foreach
 
609
                                for (int n=0; n<config.MainModule.Assemblies.Count; n++) {
 
610
                                        string s = config.MainModule.Assemblies [n];
 
611
                                        string asmFile = Path.GetFullPath (Path.Combine (config.BasePath, s));
 
612
                                        scanResult.AddPathToIgnore (asmFile);
 
613
                                        if (s == rootAsmFile || config.MainModule.IgnorePaths.Contains (s))
 
614
                                                continue;
 
615
                                        object asm = reflector.LoadAssembly (asmFile);
 
616
                                        assemblies.Add (asm);
 
617
                                        ScanAssemblyAddinHeaders (reflector, config, asm, scanResult);
 
618
                                        ScanAssemblyImports (reflector, config.MainModule, asm);
 
619
                                }
 
620
                                
 
621
                                // Add all data files to the ignore file list. It avoids scanning assemblies
 
622
                                // which are included as 'data' in an add-in.
 
623
                                foreach (string df in config.MainModule.DataFiles) {
 
624
                                        string file = Path.Combine (config.BasePath, df);
 
625
                                        scanResult.AddPathToIgnore (Path.GetFullPath (file));
 
626
                                }
 
627
                                foreach (string df in config.MainModule.IgnorePaths) {
 
628
                                        string path = Path.Combine (config.BasePath, df);
 
629
                                        scanResult.AddPathToIgnore (Path.GetFullPath (path));
 
630
                                }
 
631
                                
 
632
                                // The add-in id and version must be already assigned at this point
 
633
                                
 
634
                                // Clean host data from the index. New data will be added.
 
635
                                if (scanResult.HostIndex != null)
 
636
                                        scanResult.HostIndex.RemoveHostData (config.AddinId, config.AddinFile);
 
637
 
 
638
                                foreach (object asm in assemblies)
 
639
                                        ScanAssemblyContents (reflector, config, config.MainModule, asm, scanResult);
 
640
                                
 
641
                        } catch (Exception ex) {
 
642
                                ReportReflectionException (monitor, ex, config, scanResult);
 
643
                                return false;
 
644
                        }
 
645
                        
 
646
                        // Extension node types may have child nodes declared as attributes. Find them.
 
647
                        
 
648
                        Hashtable internalNodeSets = new Hashtable ();
 
649
                        
 
650
                        ArrayList setsCopy = new ArrayList ();
 
651
                        setsCopy.AddRange (config.ExtensionNodeSets);
 
652
                        foreach (ExtensionNodeSet eset in setsCopy)
 
653
                                ScanNodeSet (reflector, config, eset, assemblies, internalNodeSets);
 
654
                        
 
655
                        foreach (ExtensionPoint ep in config.ExtensionPoints) {
 
656
                                ScanNodeSet (reflector, config, ep.NodeSet, assemblies, internalNodeSets);
 
657
                        }
 
658
                
 
659
                        // Now scan all modules
 
660
                        
 
661
                        if (!config.IsRoot) {
 
662
                                foreach (ModuleDescription mod in config.OptionalModules) {
 
663
                                        try {
 
664
                                                assemblies.Clear ();
 
665
                                                for (int n=0; n<mod.Assemblies.Count; n++) {
 
666
                                                        string s = mod.Assemblies [n];
 
667
                                                        if (mod.IgnorePaths.Contains (s))
 
668
                                                                continue;
 
669
                                                        string asmFile = Path.Combine (config.BasePath, s);
 
670
                                                        object asm = reflector.LoadAssembly (asmFile);
 
671
                                                        assemblies.Add (asm);
 
672
                                                        scanResult.AddPathToIgnore (Path.GetFullPath (asmFile));
 
673
                                                        ScanAssemblyImports (reflector, mod, asm);
 
674
                                                }
 
675
                                                // Add all data files to the ignore file list. It avoids scanning assemblies
 
676
                                                // which are included as 'data' in an add-in.
 
677
                                                foreach (string df in mod.DataFiles) {
 
678
                                                        string file = Path.Combine (config.BasePath, df);
 
679
                                                        scanResult.AddPathToIgnore (Path.GetFullPath (file));
 
680
                                                }
 
681
                                                foreach (string df in mod.IgnorePaths) {
 
682
                                                        string path = Path.Combine (config.BasePath, df);
 
683
                                                        scanResult.AddPathToIgnore (Path.GetFullPath (path));
 
684
                                                }
 
685
                                                
 
686
                                                foreach (object asm in assemblies)
 
687
                                                        ScanAssemblyContents (reflector, config, mod, asm, scanResult);
 
688
                                                
 
689
                                        } catch (Exception ex) {
 
690
                                                ReportReflectionException (monitor, ex, config, scanResult);
 
691
                                        }
 
692
                                }
 
693
                        }
 
694
                        
 
695
                        config.StoreFileInfo ();
 
696
                        return true;
 
697
                }
 
698
 
 
699
                void ReportReflectionException (IProgressStatus monitor, Exception ex, AddinDescription config, AddinScanResult scanResult)
 
700
                {
 
701
                        scanResult.AddFileToWithFailure (config.AddinFile);
 
702
                        monitor.ReportWarning ("[" + config.AddinId + "] Could not load some add-in assemblies: " + ex.Message);
 
703
                        if (monitor.LogLevel <= 1)
 
704
                            return;
 
705
                        
 
706
                        ReflectionTypeLoadException rex = ex as ReflectionTypeLoadException;
 
707
                        if (rex != null) {
 
708
                                foreach (Exception e in rex.LoaderExceptions)
 
709
                                        monitor.Log ("Load exception: " + e);
 
710
                        }
 
711
                }
 
712
                
 
713
                void ScanAssemblyAddinHeaders (IAssemblyReflector reflector, AddinDescription config, object asm, AddinScanResult scanResult)
 
714
                {
 
715
                        // Get basic add-in information
 
716
                        AddinAttribute att = (AddinAttribute) reflector.GetCustomAttribute (asm, typeof(AddinAttribute), false);
 
717
                        if (att != null) {
 
718
                                if (att.Id.Length > 0)
 
719
                                        config.LocalId = att.Id;
 
720
                                if (att.Version.Length > 0)
 
721
                                        config.Version = att.Version;
 
722
                                if (att.Namespace.Length > 0)
 
723
                                        config.Namespace = att.Namespace;
 
724
                                if (att.Category.Length > 0)
 
725
                                        config.Category = att.Category;
 
726
                                if (att.CompatVersion.Length > 0)
 
727
                                        config.CompatVersion = att.CompatVersion;
 
728
                                if (att.Url.Length > 0)
 
729
                                        config.Url = att.Url;
 
730
                                config.IsRoot = att is AddinRootAttribute;
 
731
                                config.EnabledByDefault = att.EnabledByDefault;
 
732
                                config.Flags = att.Flags;
 
733
                        }
 
734
                        
 
735
                        // Author attributes
 
736
                        
 
737
                        object[] atts = reflector.GetCustomAttributes (asm, typeof(AddinAuthorAttribute), false);
 
738
                        foreach (AddinAuthorAttribute author in atts) {
 
739
                                if (config.Author.Length == 0)
 
740
                                        config.Author = author.Name;
 
741
                                else
 
742
                                        config.Author += ", " + author.Name;
 
743
                        }
 
744
                        
 
745
                        // Name
 
746
                        
 
747
                        atts = reflector.GetCustomAttributes (asm, typeof(AddinNameAttribute), false);
 
748
                        foreach (AddinNameAttribute at in atts) {
 
749
                                if (string.IsNullOrEmpty (at.Locale))
 
750
                                        config.Name = at.Name;
 
751
                                else
 
752
                                        config.Properties.SetPropertyValue ("Name", at.Name, at.Locale);
 
753
                        }
 
754
                        
 
755
                        // Description
 
756
                        
 
757
                        object catt = reflector.GetCustomAttribute (asm, typeof(AssemblyDescriptionAttribute), false);
 
758
                        if (catt != null && config.Description.Length == 0)
 
759
                                config.Description = ((AssemblyDescriptionAttribute)catt).Description;
 
760
                        
 
761
                        atts = reflector.GetCustomAttributes (asm, typeof(AddinDescriptionAttribute), false);
 
762
                        foreach (AddinDescriptionAttribute at in atts) {
 
763
                                if (string.IsNullOrEmpty (at.Locale))
 
764
                                        config.Description = at.Description;
 
765
                                else
 
766
                                        config.Properties.SetPropertyValue ("Description", at.Description, at.Locale);
 
767
                        }
 
768
                        
 
769
                        // Copyright
 
770
                        
 
771
                        catt = reflector.GetCustomAttribute (asm, typeof(AssemblyCopyrightAttribute), false);
 
772
                        if (catt != null && config.Copyright.Length == 0)
 
773
                                config.Copyright = ((AssemblyCopyrightAttribute)catt).Copyright;
 
774
                        
 
775
                        // Localizer
 
776
                        
 
777
                        AddinLocalizerGettextAttribute locat = (AddinLocalizerGettextAttribute) reflector.GetCustomAttribute (asm, typeof(AddinLocalizerGettextAttribute), false);
 
778
                        if (locat != null) {
 
779
                                ExtensionNodeDescription node = new ExtensionNodeDescription ();
 
780
                                if (!string.IsNullOrEmpty (locat.Catalog))
 
781
                                        node.SetAttribute ("catalog", locat.Catalog);
 
782
                                if (!string.IsNullOrEmpty (locat.Location))
 
783
                                        node.SetAttribute ("location", locat.Catalog);
 
784
                                config.Localizer = node;
 
785
                        }
 
786
                        
 
787
                        // Optional modules
 
788
                        
 
789
                        atts = reflector.GetCustomAttributes (asm, typeof(AddinModuleAttribute), false);
 
790
                        foreach (AddinModuleAttribute mod in atts) {
 
791
                                if (mod.AssemblyFile.Length > 0) {
 
792
                                        ModuleDescription module = new ModuleDescription ();
 
793
                                        module.Assemblies.Add (mod.AssemblyFile);
 
794
                                        config.OptionalModules.Add (module);
 
795
                                }
 
796
                        }
 
797
                }
 
798
                
 
799
                void ScanAssemblyImports (IAssemblyReflector reflector, ModuleDescription module, object asm)
 
800
                {
 
801
                        object[] atts = reflector.GetCustomAttributes (asm, typeof(ImportAddinAssemblyAttribute), false);
 
802
                        foreach (ImportAddinAssemblyAttribute import in atts) {
 
803
                                if (!string.IsNullOrEmpty (import.FilePath)) {
 
804
                                        module.Assemblies.Add (import.FilePath);
 
805
                                        if (!import.Scan)
 
806
                                                module.IgnorePaths.Add (import.FilePath);
 
807
                                }
 
808
                        }
 
809
                        atts = reflector.GetCustomAttributes (asm, typeof(ImportAddinFileAttribute), false);
 
810
                        foreach (ImportAddinFileAttribute import in atts) {
 
811
                                if (!string.IsNullOrEmpty (import.FilePath))
 
812
                                        module.DataFiles.Add (import.FilePath);
 
813
                        }
 
814
                }
 
815
                
 
816
                void ScanAssemblyContents (IAssemblyReflector reflector, AddinDescription config, ModuleDescription module, object asm, AddinScanResult scanResult)
 
817
                {
 
818
                        bool isMainModule = module == config.MainModule;
 
819
                        
 
820
                        // Get dependencies
 
821
                        
 
822
                        object[] deps = reflector.GetCustomAttributes (asm, typeof(AddinDependencyAttribute), false);
 
823
                        foreach (AddinDependencyAttribute dep in deps) {
 
824
                                AddinDependency adep = new AddinDependency ();
 
825
                                adep.AddinId = dep.Id;
 
826
                                adep.Version = dep.Version;
 
827
                                module.Dependencies.Add (adep);
 
828
                        }
 
829
                        
 
830
                        if (isMainModule) {
 
831
                                
 
832
                                // Get properties
 
833
                                
 
834
                                object[] props = reflector.GetCustomAttributes (asm, typeof(AddinPropertyAttribute), false);
 
835
                                foreach (AddinPropertyAttribute prop in props)
 
836
                                        config.Properties.SetPropertyValue (prop.Name, prop.Value, prop.Locale);
 
837
                        
 
838
                                // Get extension points
 
839
                                
 
840
                                object[] extPoints = reflector.GetCustomAttributes (asm, typeof(ExtensionPointAttribute), false);
 
841
                                foreach (ExtensionPointAttribute ext in extPoints) {
 
842
                                        ExtensionPoint ep = config.AddExtensionPoint (ext.Path);
 
843
                                        ep.Description = ext.Description;
 
844
                                        ep.Name = ext.Name;
 
845
                                        ExtensionNodeType nt = ep.AddExtensionNode (ext.NodeName, ext.NodeTypeName);
 
846
                                        nt.ExtensionAttributeTypeName = ext.ExtensionAttributeTypeName;
 
847
                                }
 
848
                        }
 
849
                        
 
850
                        // Look for extension nodes declared using assembly attributes
 
851
                        
 
852
                        foreach (CustomAttribute att in reflector.GetRawCustomAttributes (asm, typeof(CustomExtensionAttribute), true))
 
853
                                AddCustomAttributeExtension (module, att, "Type");
 
854
                        
 
855
                        // Get extensions or extension points applied to types
 
856
                        
 
857
                        foreach (object t in reflector.GetAssemblyTypes (asm)) {
 
858
                                
 
859
                                string typeFullName = reflector.GetTypeFullName (t);
 
860
                                
 
861
                                // Look for extensions
 
862
                                
 
863
                                object[] extensionAtts = reflector.GetCustomAttributes (t, typeof(ExtensionAttribute), false);
 
864
                                if (extensionAtts.Length > 0) {
 
865
                                        Dictionary<string,ExtensionNodeDescription> nodes = new Dictionary<string, ExtensionNodeDescription> ();
 
866
                                        ExtensionNodeDescription uniqueNode = null;
 
867
                                        foreach (ExtensionAttribute eatt in extensionAtts) {
 
868
                                                string path;
 
869
                                                string nodeName = eatt.NodeName;
 
870
                                                
 
871
                                                if (eatt.TypeName.Length > 0) {
 
872
                                                        path = "$" + eatt.TypeName;
 
873
                                                }
 
874
                                                else if (eatt.Path.Length == 0) {
 
875
                                                        path = GetBaseTypeNameList (reflector, t);
 
876
                                                        if (path == "$") {
 
877
                                                                // The type does not implement any interface and has no superclass.
 
878
                                                                // Will be reported later as an error.
 
879
                                                                path = "$" + typeFullName;
 
880
                                                        }
 
881
                                                } else {
 
882
                                                        path = eatt.Path;
 
883
                                                }
 
884
 
 
885
                                                ExtensionNodeDescription elem = module.AddExtensionNode (path, nodeName);
 
886
                                                nodes [path] = elem;
 
887
                                                uniqueNode = elem;
 
888
                                                
 
889
                                                if (eatt.Id.Length > 0) {
 
890
                                                        elem.SetAttribute ("id", eatt.Id);
 
891
                                                        elem.SetAttribute ("type", typeFullName);
 
892
                                                } else {
 
893
                                                        elem.SetAttribute ("id", typeFullName);
 
894
                                                }
 
895
                                                if (eatt.InsertAfter.Length > 0)
 
896
                                                        elem.SetAttribute ("insertafter", eatt.InsertAfter);
 
897
                                                if (eatt.InsertBefore.Length > 0)
 
898
                                                        elem.SetAttribute ("insertbefore", eatt.InsertBefore);
 
899
                                        }
 
900
                                        
 
901
                                        // Get the node attributes
 
902
                                        
 
903
                                        foreach (ExtensionAttributeAttribute eat in reflector.GetCustomAttributes (t, typeof(ExtensionAttributeAttribute), false)) {
 
904
                                                ExtensionNodeDescription node;
 
905
                                                if (!string.IsNullOrEmpty (eat.Path))
 
906
                                                        nodes.TryGetValue (eat.Path, out node);
 
907
                                                else if (eat.TypeName.Length > 0)
 
908
                                                        nodes.TryGetValue ("$" + eat.TypeName, out node);
 
909
                                                else {
 
910
                                                        if (nodes.Count > 1)
 
911
                                                                throw new Exception ("Missing type or extension path value in ExtensionAttribute for type '" + typeFullName + "'.");
 
912
                                                        node = uniqueNode;
 
913
                                                }
 
914
                                                if (node == null)
 
915
                                                        throw new Exception ("Invalid type or path value in ExtensionAttribute for type '" + typeFullName + "'.");
 
916
                                                        
 
917
                                                node.SetAttribute (eat.Name ?? string.Empty, eat.Value ?? string.Empty);
 
918
                                        }
 
919
                                }
 
920
                                else {
 
921
                                        // Look for extension points
 
922
                                        
 
923
                                        extensionAtts = reflector.GetCustomAttributes (t, typeof(TypeExtensionPointAttribute), false);
 
924
                                        if (extensionAtts.Length > 0 && isMainModule) {
 
925
                                                foreach (TypeExtensionPointAttribute epa in extensionAtts) {
 
926
                                                        ExtensionPoint ep;
 
927
                                                        
 
928
                                                        ExtensionNodeType nt = new ExtensionNodeType ();
 
929
                                                        
 
930
                                                        if (epa.Path.Length > 0) {
 
931
                                                                ep = config.AddExtensionPoint (epa.Path);
 
932
                                                        }
 
933
                                                        else {
 
934
                                                                ep = config.AddExtensionPoint (GetDefaultTypeExtensionPath (config, typeFullName));
 
935
                                                                nt.ObjectTypeName = typeFullName;
 
936
                                                        }
 
937
                                                        nt.Id = epa.NodeName;
 
938
                                                        nt.TypeName = epa.NodeTypeName;
 
939
                                                        nt.ExtensionAttributeTypeName = epa.ExtensionAttributeTypeName;
 
940
                                                        ep.NodeSet.NodeTypes.Add (nt);
 
941
                                                        ep.Description = epa.Description;
 
942
                                                        ep.Name = epa.Name;
 
943
                                                        ep.RootAddin = config.AddinId;
 
944
                                                        ep.SetExtensionsAddinId (config.AddinId);
 
945
                                                }
 
946
                                        }
 
947
                                        else {
 
948
                                                // Look for custom extension attribtues
 
949
                                                foreach (CustomAttribute att in reflector.GetRawCustomAttributes (t, typeof(CustomExtensionAttribute), false)) {
 
950
                                                        ExtensionNodeDescription elem = AddCustomAttributeExtension (module, att, "Type");
 
951
                                                        elem.SetAttribute ("type", typeFullName);
 
952
                                                        if (string.IsNullOrEmpty (elem.GetAttribute ("id")))
 
953
                                                                elem.SetAttribute ("id", typeFullName);
 
954
                                                }
 
955
                                        }
 
956
                                }
 
957
                        }
 
958
                }
 
959
                
 
960
                ExtensionNodeDescription AddCustomAttributeExtension (ModuleDescription module, CustomAttribute att, string nameName)
 
961
                {
 
962
                        string path;
 
963
                        if (!att.TryGetValue (CustomExtensionAttribute.PathFieldKey, out path))
 
964
                                path = "%" + att.TypeName;
 
965
                        ExtensionNodeDescription elem = module.AddExtensionNode (path, nameName);
 
966
                        foreach (KeyValuePair<string,string> prop in att) {
 
967
                                if (prop.Key != CustomExtensionAttribute.PathFieldKey)
 
968
                                        elem.SetAttribute (prop.Key, prop.Value);
 
969
                        }
 
970
                        return elem;
 
971
                }
 
972
                
 
973
                void ScanNodeSet (IAssemblyReflector reflector, AddinDescription config, ExtensionNodeSet nset, ArrayList assemblies, Hashtable internalNodeSets)
 
974
                {
 
975
                        foreach (ExtensionNodeType nt in nset.NodeTypes)
 
976
                                ScanNodeType (reflector, config, nt, assemblies, internalNodeSets);
 
977
                }
 
978
                
 
979
                void ScanNodeType (IAssemblyReflector reflector, AddinDescription config, ExtensionNodeType nt, ArrayList assemblies, Hashtable internalNodeSets)
 
980
                {
 
981
                        if (nt.TypeName.Length == 0)
 
982
                                nt.TypeName = "Mono.Addins.TypeExtensionNode";
 
983
                        
 
984
                        object ntype = FindAddinType (reflector, nt.TypeName, assemblies);
 
985
                        if (ntype == null)
 
986
                                return;
 
987
                        
 
988
                        // Add type information declared with attributes in the code
 
989
                        ExtensionNodeAttribute nodeAtt = (ExtensionNodeAttribute) reflector.GetCustomAttribute (ntype, typeof(ExtensionNodeAttribute), true);
 
990
                        if (nodeAtt != null) {
 
991
                                if (nt.Id.Length == 0 && nodeAtt.NodeName.Length > 0)
 
992
                                        nt.Id = nodeAtt.NodeName;
 
993
                                if (nt.Description.Length == 0 && nodeAtt.Description.Length > 0)
 
994
                                        nt.Description = nodeAtt.Description;
 
995
                                if (nt.ExtensionAttributeTypeName.Length == 0 && nodeAtt.ExtensionAttributeTypeName.Length > 0)
 
996
                                        nt.ExtensionAttributeTypeName = nodeAtt.ExtensionAttributeTypeName;
 
997
                        } else {
 
998
                                // Use the node type name as default name
 
999
                                if (nt.Id.Length == 0)
 
1000
                                        nt.Id = reflector.GetTypeName (ntype);
 
1001
                        }
 
1002
                        
 
1003
                        // Add information about attributes
 
1004
                        object[] fieldAtts = reflector.GetCustomAttributes (ntype, typeof(NodeAttributeAttribute), true);
 
1005
                        foreach (NodeAttributeAttribute fatt in fieldAtts) {
 
1006
                                NodeTypeAttribute natt = new NodeTypeAttribute ();
 
1007
                                natt.Name = fatt.Name;
 
1008
                                natt.Required = fatt.Required;
 
1009
                                if (fatt.TypeName != null)
 
1010
                                        natt.Type = fatt.TypeName;
 
1011
                                if (fatt.Description.Length > 0)
 
1012
                                        natt.Description = fatt.Description;
 
1013
                                nt.Attributes.Add (natt);
 
1014
                        }
 
1015
                        
 
1016
                        // Check if the type has NodeAttribute attributes applied to fields.
 
1017
                        foreach (object field in reflector.GetFields (ntype)) {
 
1018
                                NodeAttributeAttribute fatt = (NodeAttributeAttribute) reflector.GetCustomAttribute (field, typeof(NodeAttributeAttribute), false);
 
1019
                                if (fatt != null) {
 
1020
                                        NodeTypeAttribute natt = new NodeTypeAttribute ();
 
1021
                                        if (fatt.Name.Length > 0)
 
1022
                                                natt.Name = fatt.Name;
 
1023
                                        else
 
1024
                                                natt.Name = reflector.GetFieldName (field);
 
1025
                                        if (fatt.Description.Length > 0)
 
1026
                                                natt.Description = fatt.Description;
 
1027
                                        natt.Type = reflector.GetFieldTypeFullName (field);
 
1028
                                        natt.Required = fatt.Required;
 
1029
                                        nt.Attributes.Add (natt);
 
1030
                                }
 
1031
                        }
 
1032
                        
 
1033
                        // Check if the extension type allows children by looking for [ExtensionNodeChild] attributes.
 
1034
                        // First of all, look in the internalNodeSets hashtable, which is being used as cache
 
1035
                        
 
1036
                        string childSet = (string) internalNodeSets [nt.TypeName];
 
1037
                        
 
1038
                        if (childSet == null) {
 
1039
                                object[] ats = reflector.GetCustomAttributes (ntype, typeof(ExtensionNodeChildAttribute), true);
 
1040
                                if (ats.Length > 0) {
 
1041
                                        // Create a new node set for this type. It is necessary to create a new node set
 
1042
                                        // instead of just adding child ExtensionNodeType objects to the this node type
 
1043
                                        // because child types references can be recursive.
 
1044
                                        ExtensionNodeSet internalSet = new ExtensionNodeSet ();
 
1045
                                        internalSet.Id = reflector.GetTypeName (ntype) + "_" + Guid.NewGuid().ToString ();
 
1046
                                        foreach (ExtensionNodeChildAttribute at in ats) {
 
1047
                                                ExtensionNodeType internalType = new ExtensionNodeType ();
 
1048
                                                internalType.Id = at.NodeName;
 
1049
                                                internalType.TypeName = at.ExtensionNodeTypeName;
 
1050
                                                internalSet.NodeTypes.Add (internalType);
 
1051
                                        }
 
1052
                                        config.ExtensionNodeSets.Add (internalSet);
 
1053
                                        nt.NodeSets.Add (internalSet.Id);
 
1054
                                        
 
1055
                                        // Register the new set in a hashtable, to allow recursive references to the
 
1056
                                        // same internal set.
 
1057
                                        internalNodeSets [nt.TypeName] = internalSet.Id;
 
1058
                                        internalNodeSets [reflector.GetTypeAssemblyQualifiedName (ntype)] = internalSet.Id;
 
1059
                                        ScanNodeSet (reflector, config, internalSet, assemblies, internalNodeSets);
 
1060
                                }
 
1061
                        }
 
1062
                        else {
 
1063
                                if (childSet.Length == 0) {
 
1064
                                        // The extension type does not declare children.
 
1065
                                        return;
 
1066
                                }
 
1067
                                // The extension type can have children. The allowed children are
 
1068
                                // defined in this extension set.
 
1069
                                nt.NodeSets.Add (childSet);
 
1070
                                return;
 
1071
                        }
 
1072
                        
 
1073
                        ScanNodeSet (reflector, config, nt, assemblies, internalNodeSets);
 
1074
                }
 
1075
                
 
1076
                string GetBaseTypeNameList (IAssemblyReflector reflector, object type)
 
1077
                {
 
1078
                        StringBuilder sb = new StringBuilder ("$");
 
1079
                        foreach (string tn in reflector.GetBaseTypeFullNameList (type))
 
1080
                                sb.Append (tn).Append (',');
 
1081
                        if (sb.Length > 0)
 
1082
                                sb.Remove (sb.Length - 1, 1);
 
1083
                        return sb.ToString ();
 
1084
                }
 
1085
                
 
1086
                object FindAddinType (IAssemblyReflector reflector, string typeName, ArrayList assemblies)
 
1087
                {
 
1088
                        // Look in the current assembly
 
1089
                        object etype = reflector.GetType (coreAssemblies [reflector], typeName);
 
1090
                        if (etype != null)
 
1091
                                return etype;
 
1092
                        
 
1093
                        // Look in referenced assemblies
 
1094
                        foreach (object asm in assemblies) {
 
1095
                                etype = reflector.GetType (asm, typeName);
 
1096
                                if (etype != null)
 
1097
                                        return etype;
 
1098
                        }
 
1099
                        
 
1100
                        Hashtable visited = new Hashtable ();
 
1101
                        
 
1102
                        // Look in indirectly referenced assemblies
 
1103
                        foreach (object asm in assemblies) {
 
1104
                                foreach (object aref in reflector.GetAssemblyReferences (asm)) {
 
1105
                                        if (visited.Contains (aref))
 
1106
                                                continue;
 
1107
                                        visited.Add (aref, aref);
 
1108
                                        object rasm = reflector.LoadAssemblyFromReference (aref);
 
1109
                                        if (rasm != null) {
 
1110
                                                etype = reflector.GetType (rasm, typeName);
 
1111
                                                if (etype != null)
 
1112
                                                        return etype;
 
1113
                                        }
 
1114
                                }
 
1115
                        }
 
1116
                        return null;
 
1117
                }
 
1118
 
 
1119
                void RegisterTypeNode (AddinDescription config, ExtensionAttribute eatt, string path, string nodeName, string typeFullName)
 
1120
                {
 
1121
                        ExtensionNodeDescription elem = config.MainModule.AddExtensionNode (path, nodeName);
 
1122
                        if (eatt.Id.Length > 0) {
 
1123
                                elem.SetAttribute ("id", eatt.Id);
 
1124
                                elem.SetAttribute ("type", typeFullName);
 
1125
                        } else {
 
1126
                                elem.SetAttribute ("id", typeFullName);
 
1127
                        }
 
1128
                        if (eatt.InsertAfter.Length > 0)
 
1129
                                elem.SetAttribute ("insertafter", eatt.InsertAfter);
 
1130
                        if (eatt.InsertBefore.Length > 0)
 
1131
                                elem.SetAttribute ("insertbefore", eatt.InsertBefore);
 
1132
                }
 
1133
                
 
1134
                internal string GetDefaultTypeExtensionPath (AddinDescription desc, string typeFullName)
 
1135
                {
 
1136
                        return "/" + Addin.GetIdName (desc.AddinId) + "/TypeExtensions/" + typeFullName;
 
1137
                }
 
1138
        }
 
1139
}