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