Add autoconf checks for platforms without IPv6
[mono.git] / mcs / class / monodoc / Monodoc / HelpSource.cs
1 using System;
2 using System.IO;
3 using System.Linq;
4 using System.Xml;
5 using System.Diagnostics;
6 using System.Collections.Generic;
7
8 using Mono.Utilities;
9 using Lucene.Net.Index;
10
11 namespace Monodoc
12 {
13         public enum SortType {
14                 Caption,
15                 Element
16         }
17
18         //
19         // The HelpSource class keeps track of the archived data, and its
20         // tree
21         //
22         public
23 #if LEGACY_MODE
24         partial
25 #endif
26         class HelpSource
27         {
28                 static int id;
29
30                 //
31                 // The unique ID for this HelpSource.
32                 //
33                 int source_id;
34
35                 // The name of the HelpSource, used by all the file (.tree, .zip, ...) used by it
36                 string name;
37                 // The full directory path where the HelpSource files are located
38                 string basePath;
39
40                 // The tree of this help source
41                 Tree tree;
42                 string treeFilePath;
43                 RootTree rootTree;
44
45                 IDocCache cache;
46                 IDocStorage storage;
47
48                 public HelpSource (string base_filename, bool create)
49                 {
50                         this.name = Path.GetFileName (base_filename);
51                         this.basePath = Path.GetDirectoryName (base_filename);
52                         this.treeFilePath = base_filename + ".tree";
53                         this.storage = new Monodoc.Storage.ZipStorage (base_filename + ".zip");
54                         this.cache = DocCacheHelper.GetDefaultCache (Name);
55
56                         tree = create ? new Tree (this, string.Empty, string.Empty) : new Tree (this, treeFilePath);
57
58                         source_id = id++;
59                 }
60         
61                 public HelpSource ()
62                 {
63                         tree = new Tree (this, "Blah", "Blah");
64                         source_id = id++;
65                         this.cache = new Caches.NullCache ();
66                 }
67         
68                 public int SourceID {
69                         get {
70                                 return source_id;
71                         }
72                 }
73         
74                 public string Name {
75                         get {
76                                 return name;
77                         }
78                 }
79
80                 /* This gives the full path of the source/ directory */
81                 public string BaseFilePath {
82                         get {
83                                 return basePath;
84                         }
85                 }
86
87                 public TraceLevel TraceLevel {
88                         get;
89                         set;
90                 }
91
92                 public string BaseDir {
93                         get {
94                                 return basePath;
95                         }
96                 }
97
98                 public Tree Tree {
99                         get {
100                                 return tree;
101                         }
102                 }
103
104                 public RootTree RootTree {
105                         get {
106                                 return rootTree;
107                         }
108                         set {
109                                 rootTree = value;
110                         }
111                 }
112
113                 public IDocCache Cache {
114                         get {
115                                 return cache;
116                         }
117                 }
118
119                 public IDocStorage Storage {
120                         get {
121                                 return storage;
122                         }
123                         protected set {
124                                 storage = value;
125                         }
126                 }
127
128                 // A HelpSource may have a common prefix to its URL, give it here
129                 protected virtual string UriPrefix {
130                         get {
131                                 return "dummy:";
132                         }
133                 }
134
135                 public virtual SortType SortType {
136                         get {
137                                 return SortType.Caption;
138                         }
139                 }
140         
141                 /// <summary>
142                 ///   Returns a stream from the packaged help source archive
143                 /// </summary>
144                 public virtual Stream GetHelpStream (string id)
145                 {
146                         return storage.Retrieve (id);
147                 }
148
149                 public virtual Stream GetCachedHelpStream (string id)
150                 {
151                         if (string.IsNullOrEmpty (id))
152                                 throw new ArgumentNullException ("id");
153                         if (!cache.CanCache (DocEntity.Text))
154                                 return GetHelpStream (id);
155                         if (!cache.IsCached (id))
156                                 cache.CacheText (id, GetHelpStream (id));
157                         return cache.GetCachedStream (id);
158                 }
159
160                 public XmlReader GetHelpXml (string id)
161                 {
162                         var url = "monodoc:///" + SourceID + "@" + Uri.EscapeDataString (id) + "@";
163                         var stream = cache.IsCached (id) ? cache.GetCachedStream (id) : storage.Retrieve (id);
164                         
165                         return stream == null ? null : new XmlTextReader (url, stream);
166                 }
167         
168                 public virtual XmlDocument GetHelpXmlWithChanges (string id)
169                 {
170                         XmlDocument doc = new XmlDocument ();
171                         if (!storage.SupportRevision) {
172                                 doc.Load (GetHelpXml (id));
173                         } else {
174                                 var revManager = storage.RevisionManager;
175                                 doc.Load (revManager.RetrieveLatestRevision (id));
176                         }
177                         return doc;
178                 }
179
180                 public virtual string GetCachedText (string id)
181                 {
182                         if (!cache.CanCache (DocEntity.Text))
183                                 return GetText (id);
184                         if (!cache.IsCached (id))
185                                 cache.CacheText (id, GetText (id));
186                         return cache.GetCachedString (id);
187                 }
188
189                 public virtual string GetText (string id)
190                 {
191                         return new StreamReader (GetHelpStream (id)).ReadToEnd ();
192                 }
193
194                 // Tells if the result for the provided id is generated dynamically
195                 // by the help source
196                 public virtual bool IsGeneratedContent (string id)
197                 {
198                         return false;
199                 }
200
201                 // Tells if the content of the provided id is meant to be returned raw
202                 public virtual bool IsRawContent (string id)
203                 {
204                         return false;
205                 }
206
207                 // Tells if provided id refers to a multi-content-type document if it's case
208                 // tells the ids it's formed of
209                 public virtual bool IsMultiPart (string id, out IEnumerable<string> parts)
210                 {
211                         parts = null;
212                         return false;
213                 }
214
215                 /// <summary>
216                 ///   Saves the tree and the archive
217                 /// </summary>
218                 public void Save ()
219                 {
220                         tree.Save (treeFilePath);
221                         storage.Dispose ();
222                 }
223         
224                 public virtual void RenderPreviewDocs (XmlNode newNode, XmlWriter writer)
225                 {
226                         throw new NotImplementedException ();
227                 }
228
229                 public virtual string GetPublicUrl (Node node)
230                 {
231                         return node.GetInternalUrl ();
232                 }
233
234                 public virtual bool CanHandleUrl (string url)
235                 {
236                         return url.StartsWith (UriPrefix, StringComparison.OrdinalIgnoreCase);
237                 }
238
239                 public virtual string GetInternalIdForUrl (string url, out Node node, out Dictionary<string, string> context)
240                 {
241                         context = null;
242                         node = MatchNode (url);
243                         return node == null ? null : url.Substring (UriPrefix.Length);
244                 }
245                 
246                 public virtual Node MatchNode (string url)
247                 {
248                         Node current = null;
249
250                         var matchCache = LRUCache<string, Node>.Default;
251                         if ((current = matchCache.Get (url)) != null)
252                                 return current;
253
254                         current = Tree.RootNode;
255                         var strippedUrl = url.StartsWith (UriPrefix, StringComparison.OrdinalIgnoreCase) ? url.Substring (UriPrefix.Length) : url;
256                         var searchNode = new Node () { Element = strippedUrl };
257
258                         do {
259                                 int index = current.ChildNodes.BinarySearch (searchNode, NodeElementComparer.Instance);
260                                 if (index >= 0) {
261                                         Node n = current.ChildNodes[index];
262                                         matchCache.Put (url, n);
263                                         return n;
264                                 }
265                                 index = ~index;
266                                 if (index == current.ChildNodes.Count) {
267                                         return SlowMatchNode (Tree.RootNode, matchCache, strippedUrl);
268                                 }
269
270                                 if (index == 0)
271                                         return null;
272
273                                 current = current.ChildNodes [index - 1];
274                         } while (true);
275
276                         return null;
277                 }
278
279                 /* That slow path is mainly here to handle ecmaspec type of url which are composed of hard to sort numbers
280                  * because they don't have the same amount of digit. We could use a regex to harmonise the various number
281                  * parts but then it would be quite specific. Since in the case of ecmaspec the tree is well-formed enough
282                  * the "Slow" match should still be fast enough
283                  */
284                 Node SlowMatchNode (Node current, LRUCache<string, Node> matchCache, string url)
285                 {
286                         //Console.WriteLine ("Entering slow path for {0} starting from {1}", url, current.Element);
287                         while (current != null) {
288                                 bool stop = true;
289                                 foreach (Node n in current.ChildNodes) {
290                                         var element = n.Element.StartsWith (UriPrefix, StringComparison.OrdinalIgnoreCase) ? n.Element.Substring (UriPrefix.Length) : n.Element;
291                                         if (url.Equals (element, StringComparison.Ordinal)) {
292                                                 matchCache.Put (url, n);
293                                                 return n;
294                                         } else if (url.StartsWith (element + ".", StringComparison.OrdinalIgnoreCase) && !n.IsLeaf) {
295                                                 current = n;
296                                                 stop = false;
297                                                 break;
298                                         }
299                                 }
300                                 if (stop)
301                                         current = null;
302                         }
303
304                         return null;
305                 }
306                 
307                 class NodeElementComparer : IComparer<Node>
308                 {
309                         public static NodeElementComparer Instance = new NodeElementComparer ();
310
311                         public int Compare (Node n1, Node n2)
312                         {
313                                 return string.Compare (Cleanup (n1), Cleanup (n2), StringComparison.Ordinal);
314                         }
315
316                         string Cleanup (Node n)
317                         {
318                                 var prefix = n.Tree != null && n.Tree.HelpSource != null ? n.Tree.HelpSource.UriPrefix : string.Empty;
319                                 var element = n.Element.StartsWith (prefix, StringComparison.OrdinalIgnoreCase) ? n.Element.Substring (prefix.Length) : n.Element;
320                                 if (char.IsDigit (element, 0)) {
321                                         var count = element.TakeWhile (char.IsDigit).Count ();
322                                         element = element.PadLeft (Math.Max (0, 3 - count) + element.Length, '0');
323                                 }
324                                 //Console.WriteLine ("Cleaned up {0} to {1}", n.Element, element);
325                                 return element;
326                         }
327                 }
328
329                 public virtual DocumentType GetDocumentTypeForId (string id)
330                 {
331                         return DocumentType.PlainText;
332                 }
333
334                 public virtual Stream GetImage (string url)
335                 {
336                         Stream result = null;
337                         storage.TryRetrieve (url, out result);
338                         return result;
339                 }
340
341                 //
342                 // Populates the index.
343                 //
344                 public virtual void PopulateIndex (IndexMaker index_maker)
345                 {
346                 }
347
348                 //
349                 // Create different Documents for adding to Lucene search index
350                 // The default action is do nothing. Subclasses should add the docs
351                 // 
352                 public virtual void PopulateSearchableIndex (IndexWriter writer)
353                 {
354
355                 }
356         }
357 }