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