* Makefile.am:
[monodevelop.git] / main / src / addins / AspNetAddIn / MonoDevelop.AspNet.Parser / DocumentReferenceManager.cs
blobc93f98ca04fa89111cb881a0ea0694946ec99975
1 //
2 // DocumentReferenceManager.cs: Handles web type lookups for ASP.NET documents.
3 //
4 // Authors:
5 // Michael Hutchinson <mhutchinson@novell.com>
6 //
7 // Copyright (C) 2007 Novell, Inc (http://www.novell.com)
8 //
9 //
10 // This source code is licenced under The MIT License:
12 // Permission is hereby granted, free of charge, to any person obtaining
13 // a copy of this software and associated documentation files (the
14 // "Software"), to deal in the Software without restriction, including
15 // without limitation the rights to use, copy, modify, merge, publish,
16 // distribute, sublicense, and/or sell copies of the Software, and to
17 // permit persons to whom the Software is furnished to do so, subject to
18 // the following conditions:
19 //
20 // The above copyright notice and this permission notice shall be
21 // included in all copies or substantial portions of the Software.
22 //
23 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
24 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
25 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
26 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
27 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
28 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
29 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
32 using System;
33 using System.Collections.Generic;
34 using System.Globalization;
36 using MonoDevelop.Core;
37 using MonoDevelop.AspNet.Parser.Dom;
38 using MonoDevelop.Projects.Text;
39 using MonoDevelop.Projects.Dom;
40 using MonoDevelop.Projects.Dom.Output;
41 using MonoDevelop.Projects.Dom.Parser;
42 using MonoDevelop.Projects.Gui.Completion;
43 using MonoDevelop.Ide.Gui;
45 namespace MonoDevelop.AspNet.Parser
49 public class DocumentReferenceManager
51 protected List<RegisterDirective> pageRefsList = new List<RegisterDirective> ();
52 protected Document doc;
54 public DocumentReferenceManager (Document doc)
56 this.doc = doc;
57 updateList ();
60 void updateList ()
62 ReferenceVisitor visitor = new ReferenceVisitor (this);
63 pageRefsList.Clear ();
64 doc.RootNode.AcceptVisit (visitor);
67 public string GetTypeName (string tagPrefix, string tagName)
69 return GetTypeName (tagPrefix, tagName, null);
72 public string GetTypeName (string tagPrefix, string tagName, string htmlTypeAttribute)
74 if (tagPrefix == null || tagPrefix.Length < 1)
75 return WebTypeManager.HtmlControlLookup (tagName, htmlTypeAttribute);
77 if (0 == string.Compare (tagPrefix, "asp", StringComparison.OrdinalIgnoreCase)) {
78 string systemType = WebTypeManager.SystemTypeNameLookup (tagName, doc.Project);
79 if (!string.IsNullOrEmpty (systemType))
80 return systemType;
83 foreach (RegisterDirective directive in pageRefsList) {
84 AssemblyRegisterDirective ard = directive as AssemblyRegisterDirective;
85 if (ard != null && ard.TagPrefix == tagPrefix) {
87 ProjectDom dom = WebTypeManager.ResolveAssembly (doc.Project, ard.Assembly);
88 if (dom == null)
89 continue;
91 string fullName = WebTypeManager.AssemblyTypeNameLookup (dom, ard.Namespace, tagName);
92 if (fullName != null)
93 return fullName;
96 ControlRegisterDirective crd = directive as ControlRegisterDirective;
97 if (crd != null && crd.TagPrefix == tagPrefix) {
98 string fullName = WebTypeManager.GetUserControlTypeName (doc.Project, doc.FilePath, crd.Src);
99 if (fullName != null)
100 return fullName;
104 string globalLookup = WebTypeManager.GetRegisteredTypeName (doc.Project,
105 System.IO.Path.GetDirectoryName (doc.FilePath), tagPrefix, tagName);
107 //returns null if type not found
108 return globalLookup;
111 public IEnumerable<CompletionData> GetControlCompletionData ()
113 return GetControlCompletionData (new DomType ("System.Web.UI.Control"));
116 public IEnumerable<CompletionData> GetControlCompletionData (IType baseType)
118 bool isSWC = baseType.FullName == "System.Web.UI.Control";
120 string aspPrefix = "asp:";
121 foreach (IType cls in WebTypeManager.ListSystemControlClasses (baseType, doc.Project))
122 yield return new AspTagCompletionData (aspPrefix, cls);
124 foreach (RegisterDirective rd in pageRefsList) {
125 if (!rd.IsValid ())
126 continue;
128 AssemblyRegisterDirective ard = rd as AssemblyRegisterDirective;
129 if (ard != null) {
130 ProjectDom dom = WebTypeManager.ResolveAssembly (doc.Project, ard.Assembly);
131 if (dom == null)
132 continue;
134 string prefix = ard.TagPrefix + ":";
135 foreach (IType cls in WebTypeManager.ListControlClasses (baseType, dom, ard.Namespace))
136 yield return new AspTagCompletionData (prefix, cls);
137 continue;
140 if (!isSWC)
141 continue;
143 ControlRegisterDirective cd = rd as ControlRegisterDirective;
144 if (cd != null) {
145 yield return new CompletionData (string.Concat (cd.TagPrefix, ":", cd.TagName),
146 Gtk.Stock.GoForward);
150 //return controls from web.config
151 string webDirectory = System.IO.Path.GetDirectoryName (doc.FilePath);
152 foreach (CompletionData cd in WebTypeManager.GetRegisteredTypeCompletionData (doc.Project, webDirectory, baseType)) {
153 yield return cd;
157 public IType GetControlType (string tagPrefix, string tagName)
159 if (String.IsNullOrEmpty (tagPrefix))
160 return null;
162 IType type = null;
163 if (0 == string.Compare (tagPrefix, "asp", StringComparison.OrdinalIgnoreCase)) {
164 type = WebTypeManager.SystemTypeLookup (tagName, WebTypeManager.GetProjectTargetFramework (doc.Project));
165 if (type != null)
166 return type;
169 foreach (RegisterDirective rd in pageRefsList) {
170 if (string.Compare (rd.TagPrefix, tagPrefix, StringComparison.OrdinalIgnoreCase) != 0)
171 continue;
173 AssemblyRegisterDirective ard = rd as AssemblyRegisterDirective;
174 if (ard != null) {
175 string assembly = ard.Assembly;
176 ProjectDom dom = WebTypeManager.ResolveAssembly (doc.Project, ard.Assembly);
177 if (dom == null)
178 continue;
179 type = WebTypeManager.AssemblyTypeLookup (dom, ard.Namespace, tagName);
180 if (type != null)
181 return type;
182 continue;
185 ControlRegisterDirective crd = rd as ControlRegisterDirective;
186 if (crd != null && string.Compare (crd.TagName, tagName, StringComparison.OrdinalIgnoreCase) != 0) {
187 return WebTypeManager.GetUserControlType (doc.Project, crd.Src, System.IO.Path.GetDirectoryName (doc.FilePath));
191 IType globalLookup = WebTypeManager.GetRegisteredType (doc.Project,
192 System.IO.Path.GetDirectoryName (doc.FilePath), tagPrefix, tagName);
194 //returns null if type not found
195 return globalLookup;
198 public string GetTagPrefix (IType control)
200 if (control.Namespace == "System.Web.UI.WebControls")
201 return "asp";
202 else if (control.Namespace == "System.Web.UI.HtmlControls")
203 return string.Empty;
205 foreach (RegisterDirective rd in pageRefsList) {
206 AssemblyRegisterDirective ard = rd as AssemblyRegisterDirective;
207 if (ard != null && ard.Namespace == control.Namespace)
208 return ard.TagPrefix;
211 string globalPrefix = WebTypeManager.GetControlPrefix (doc.Project, System.IO.Path.GetDirectoryName (doc.FilePath), control);
212 if (globalPrefix != null)
213 return globalPrefix;
215 return null;
218 IEnumerable<RegisterDirective> GetDirectivesForPrefix (string prefix)
220 foreach (RegisterDirective rd in pageRefsList)
221 if (string.Equals (rd.TagPrefix, prefix, StringComparison.OrdinalIgnoreCase))
222 yield return rd;
225 #region "Refactoring" operations -- things that modify the file
227 public string AddAssemblyReferenceToDocument (IType control, string assemblyName)
229 return AddAssemblyReferenceToDocument (control, assemblyName, null);
232 public string AddAssemblyReferenceToDocument (IType control, string assemblyName, string desiredPrefix)
234 string existingPrefix = GetTagPrefix (control);
235 if (existingPrefix != null)
236 return existingPrefix;
238 //TODO: detect control name conflicts
239 string prefix = desiredPrefix;
240 if (desiredPrefix == null)
241 prefix = GetPrefix (control);
243 System.Reflection.AssemblyName an = MonoDevelop.Core.Runtime.SystemAssemblyService.ParseAssemblyName (assemblyName);
245 string directive = string.Format ("{0}<%@ Register TagPrefix=\"{1}\" Namespace=\"{2}\" Assembly=\"{3}\" %>",
246 Environment.NewLine, prefix, control.Namespace, an.Name);
248 //inset a directive into the document
249 InsertDirective (directive);
251 return prefix;
254 public void AddAssemblyReferenceToProject (string assemblyName, string assemblyLocation)
256 //build an reference to the assembly
257 MonoDevelop.Projects.ProjectReference pr;
258 if (string.IsNullOrEmpty (assemblyLocation)) {
259 pr = new MonoDevelop.Projects.ProjectReference
260 (MonoDevelop.Projects.ReferenceType.Gac, assemblyName);
261 } else {
262 pr = new MonoDevelop.Projects.ProjectReference
263 (MonoDevelop.Projects.ReferenceType.Assembly, assemblyLocation);
266 //add the reference if it doesn't match an existing one
267 bool match = false;
268 foreach (MonoDevelop.Projects.ProjectReference p in doc.Project.References)
269 if (p.Equals (pr))
270 match = true;
271 if (!match)
272 doc.Project.References.Add (pr);
275 string GetPrefix (IType control)
277 //FIXME: make this work
279 foreach (IAttributeSection attSec in control.CompilationUnit.Attributes) {
280 foreach (IAttribute att in attSec.Attributes) {
281 if (att.PositionalArguments != null && att.PositionalArguments.Length == 2
282 && ExprToStr (att.PositionalArguments[0]) == control.Namespace) {
283 string prefix = ExprToStr (att.PositionalArguments [1]);
284 if (prefix != null)
285 return prefix;
288 if (att.Name == "System.Web.UI.TagPrefixAttribute") {
289 bool match = false;
290 foreach (NamedAttributeArgument arg in att.NamedArguments) {
291 if (arg.Name == "NamespaceName"
292 && ExprToStr (arg.Expression) == control.Namespace) {
293 match = true;
294 break;
297 foreach (NamedAttributeArgument arg in att.NamedArguments) {
298 if (arg.Name == "TagPrefix") {
299 string prefix = ExprToStr (arg.Expression);
300 if (prefix != null)
301 return prefix;
308 //generate a new prefix base on initials of namespace
309 string[] namespaces = control.Namespace.Split ('.');
310 char[] charr = new char[namespaces.Length];
311 for (int i = 0; i < charr.Length; i++)
312 charr[i] = char.ToLower (namespaces[i][0]);
314 //find a variant that doesn't match an existing prefix
315 string trialPrefix = new string (charr);
316 string trial = trialPrefix;
318 for (int trialSuffix = 1; trialSuffix < int.MaxValue; trialSuffix++) {
319 using (IEnumerator<RegisterDirective> en = GetDirectivesForPrefix (trial).GetEnumerator())
320 if (!en.MoveNext ())
321 return trial;
322 trial = trialPrefix + trialSuffix;
324 throw new InvalidOperationException ("Ran out of integer suffixes for tag prefixes");
327 string ExprToStr (System.CodeDom.CodeExpression expr)
329 System.CodeDom.CodePrimitiveExpression p = expr as System.CodeDom.CodePrimitiveExpression;
330 return p != null? p.Value as string : null;
333 void InsertDirective (string directive)
335 DirectiveNode node = GetPageDirective ();
336 if (node == null)
337 return;
339 IEditableTextFile textFile =
340 MonoDevelop.DesignerSupport.OpenDocumentFileProvider.Instance.GetEditableTextFile (doc.FilePath);
341 if (textFile == null)
342 textFile = new TextFile (doc.FilePath);
344 int pos = textFile.GetPositionFromLineColumn (node.Location.EndLine, node.Location.EndColumn);
345 textFile.InsertText (pos, directive);
348 DirectiveNode GetPageDirective ()
350 PageDirectiveVisitor v = new PageDirectiveVisitor ();
351 doc.RootNode.AcceptVisit (v);
352 return v.DirectiveNode;
355 #endregion
357 #region directive classes
359 protected abstract class RegisterDirective
361 private DirectiveNode node;
363 public RegisterDirective (DirectiveNode node)
365 this.node = node;
368 public DirectiveNode Node {
369 get { return node; }
372 public string TagPrefix {
373 get { return (string) node.Attributes ["TagPrefix"]; }
374 set { node.Attributes ["TagPrefix"] = value; }
377 public virtual bool IsValid ()
379 if (string.IsNullOrEmpty (TagPrefix))
380 return false;
382 foreach (char c in TagPrefix)
383 if (!Char.IsLetterOrDigit (c))
384 return false;
386 return true;
390 protected class AssemblyRegisterDirective : RegisterDirective
392 public AssemblyRegisterDirective (DirectiveNode node)
393 : base (node)
397 public string Namespace {
398 get { return (string) Node.Attributes ["Namespace"]; }
399 set { Node.Attributes ["Namespace"] = value; }
402 public string Assembly {
403 get { return (string) Node.Attributes ["Assembly"]; }
404 set { Node.Attributes ["Assembly"] = value; }
407 public override string ToString ()
409 return String.Format ("<%@ Register {0}=\"{1}\" {2}=\"{3}\" {4}=\"{5}\" %>", "TagPrefix", TagPrefix, "Namespace", Namespace, "Assembly", Assembly);
412 public override bool IsValid ()
414 if (string.IsNullOrEmpty (Assembly) || string.IsNullOrEmpty (Namespace) || !base.IsValid ())
415 return false;
416 return true;
420 protected class ControlRegisterDirective : RegisterDirective
422 public ControlRegisterDirective (DirectiveNode node)
423 : base (node)
427 public string TagName {
428 get { return (string) Node.Attributes ["TagName"]; }
429 set { Node.Attributes ["TagName"] = value; }
432 public string Src {
433 get { return (string) Node.Attributes ["Src"]; }
434 set { Node.Attributes ["Src"] = value; }
437 public override string ToString ()
439 return String.Format ("<%@ Register {0}=\"{1}\" {2}=\"{3}\" {4}=\"{5}\" %>", "TagPrefix", TagPrefix, "TagName", TagName, "Src", Src);
442 public override bool IsValid ()
444 if (string.IsNullOrEmpty (TagName) || string.IsNullOrEmpty (Src) || !base.IsValid ())
445 return false;
446 return true;
450 private class ReferenceVisitor : Visitor
452 DocumentReferenceManager parent;
454 public ReferenceVisitor (DocumentReferenceManager parent)
456 this.parent = parent;
459 public override void Visit (DirectiveNode node)
461 if ((String.Compare (node.Name, "register", true) != 0) || (node.Attributes ["TagPrefix"] == null))
462 return;
464 if ((node.Attributes ["TagName"] != null) && (node.Attributes ["Src"] != null))
465 parent.pageRefsList.Add (new ControlRegisterDirective (node));
466 else if ((node.Attributes ["Namespace"] != null) && (node.Attributes ["Assembly"] != null))
467 parent.pageRefsList.Add (new AssemblyRegisterDirective (node));
471 #endregion classes
474 //lazily loads docs
475 class AspTagCompletionData : CompletionData
477 IType cls;
479 public AspTagCompletionData (string prefix, IType cls)
480 : base (prefix + cls.Name, Gtk.Stock.GoForward)
482 this.cls = cls;
485 public override string Description {
486 get {
487 if (base.Description == null && cls != null)
488 base.Description = DocumentationService.GetCodeCompletionDescription (
489 cls, AmbienceService.DefaultAmbience);
490 return base.Description;
492 set { base.Description = value; }