OLD | NEW |
| (Empty) |
1 /// Copyright (c) Microsoft Corporation. All rights reserved. | |
2 | |
3 using System; | |
4 using System.Collections.Generic; | |
5 using System.Diagnostics; | |
6 using System.Globalization; | |
7 using System.IO; | |
8 using System.Runtime.InteropServices; | |
9 using Microsoft.VisualStudio; | |
10 using MSBuild = Microsoft.Build.BuildEngine; | |
11 | |
12 namespace Microsoft.VisualStudio.Project | |
13 { | |
14 | |
15 /// <summary> | |
16 /// This class represent a project item (usualy a file) and allow gettin
g and | |
17 /// setting attribute on it. | |
18 /// This class allow us to keep the internal details of our items hidden
from | |
19 /// our derived classes. | |
20 /// While the class itself is public so it can be manipulated by derived
classes, | |
21 /// its internal constructors make sure it can only be created from with
in the assembly. | |
22 /// </summary> | |
23 public sealed class ProjectElement | |
24 { | |
25 #region fields | |
26 private MSBuild.BuildItem item; | |
27 private ProjectNode itemProject; | |
28 private bool deleted; | |
29 private bool isVirtual; | |
30 private Dictionary<string, string> virtualProperties; | |
31 #endregion | |
32 | |
33 #region properties | |
34 public string ItemName | |
35 { | |
36 get | |
37 { | |
38 if(this.HasItemBeenDeleted()) | |
39 { | |
40 return String.Empty; | |
41 } | |
42 else | |
43 { | |
44 return this.item.Name; | |
45 } | |
46 } | |
47 set | |
48 { | |
49 if(!this.HasItemBeenDeleted()) | |
50 { | |
51 // Check out the project file. | |
52 if(!this.itemProject.QueryEditProjectFil
e(false)) | |
53 { | |
54 throw Marshal.GetExceptionForHR(
VSConstants.OLE_E_PROMPTSAVECANCELLED); | |
55 } | |
56 | |
57 this.item.Name = value; | |
58 } | |
59 } | |
60 } | |
61 | |
62 internal MSBuild.BuildItem Item | |
63 { | |
64 get | |
65 { | |
66 return this.item; | |
67 } | |
68 } | |
69 | |
70 internal bool IsVirtual | |
71 { | |
72 get | |
73 { | |
74 return this.isVirtual; | |
75 } | |
76 } | |
77 #endregion | |
78 | |
79 #region ctors | |
80 /// <summary> | |
81 /// Constructor to create a new MSBuild.BuildItem and add it to
the project | |
82 /// Only have internal constructors as the only one who should b
e creating | |
83 /// such object is the project itself (see Project.CreateFileNod
e()). | |
84 /// </summary> | |
85 internal ProjectElement(ProjectNode project, string itemPath, st
ring itemType) | |
86 { | |
87 if(project == null) | |
88 { | |
89 throw new ArgumentNullException("project"); | |
90 } | |
91 | |
92 if(String.IsNullOrEmpty(itemPath)) | |
93 { | |
94 throw new ArgumentException(SR.GetString(SR.Para
meterCannotBeNullOrEmpty, CultureInfo.CurrentUICulture), "itemPath"); | |
95 } | |
96 | |
97 | |
98 if(String.IsNullOrEmpty(itemType)) | |
99 { | |
100 throw new ArgumentException(SR.GetString(SR.Para
meterCannotBeNullOrEmpty, CultureInfo.CurrentUICulture), "itemType"); | |
101 } | |
102 | |
103 this.itemProject = project; | |
104 | |
105 // create and add the item to the project | |
106 | |
107 this.item = project.BuildProject.AddNewItem(itemType, Mi
crosoft.Build.BuildEngine.Utilities.Escape(itemPath)); | |
108 this.itemProject.SetProjectFileDirty(true); | |
109 this.RefreshProperties(); | |
110 } | |
111 | |
112 /// <summary> | |
113 /// Constructor to Wrap an existing MSBuild.BuildItem | |
114 /// Only have internal constructors as the only one who should b
e creating | |
115 /// such object is the project itself (see Project.CreateFileNod
e()). | |
116 /// </summary> | |
117 /// <param name="project">Project that owns this item</param> | |
118 /// <param name="existingItem">an MSBuild.BuildItem; can be null
if virtualFolder is true</param> | |
119 /// <param name="virtualFolder">Is this item virtual (such as re
ference folder)</param> | |
120 internal ProjectElement(ProjectNode project, MSBuild.BuildItem e
xistingItem, bool virtualFolder) | |
121 { | |
122 if(project == null) | |
123 throw new ArgumentNullException("project"); | |
124 if(!virtualFolder && existingItem == null) | |
125 throw new ArgumentNullException("existingItem"); | |
126 | |
127 // Keep a reference to project and item | |
128 this.itemProject = project; | |
129 this.item = existingItem; | |
130 this.isVirtual = virtualFolder; | |
131 | |
132 if(this.isVirtual) | |
133 this.virtualProperties = new Dictionary<string,
string>(); | |
134 } | |
135 #endregion | |
136 | |
137 #region public methods | |
138 /// <summary> | |
139 /// Calling this method remove this item from the project file. | |
140 /// Once the item is delete, you should not longer be using it. | |
141 /// Note that the item should be removed from the hierarchy prio
r to this call. | |
142 /// </summary> | |
143 public void RemoveFromProjectFile() | |
144 { | |
145 if(!deleted && item != null) | |
146 { | |
147 deleted = true; | |
148 itemProject.BuildProject.RemoveItem(item); | |
149 } | |
150 itemProject = null; | |
151 item = null; | |
152 } | |
153 | |
154 /// <summary> | |
155 /// Set an attribute on the project element | |
156 /// </summary> | |
157 /// <param name="attributeName">Name of the attribute to set</pa
ram> | |
158 /// <param name="attributeValue">Value to give to the attribute<
/param> | |
159 public void SetMetadata(string attributeName, string attributeVa
lue) | |
160 { | |
161 Debug.Assert(String.Compare(attributeName, ProjectFileCo
nstants.Include, StringComparison.OrdinalIgnoreCase) != 0, "Use rename as this w
on't work"); | |
162 | |
163 if(this.IsVirtual) | |
164 { | |
165 // For virtual node, use our virtual property co
llection | |
166 if(virtualProperties.ContainsKey(attributeName)) | |
167 virtualProperties.Remove(attributeName); | |
168 virtualProperties.Add(attributeName, attributeVa
lue); | |
169 return; | |
170 } | |
171 | |
172 // Build Action is the type, not a property, so intercep
t | |
173 if(String.Compare(attributeName, ProjectFileConstants.Bu
ildAction, StringComparison.OrdinalIgnoreCase) == 0) | |
174 { | |
175 item.Name = attributeValue; | |
176 return; | |
177 } | |
178 | |
179 // Check out the project file. | |
180 if(!this.itemProject.QueryEditProjectFile(false)) | |
181 { | |
182 throw Marshal.GetExceptionForHR(VSConstants.OLE_
E_PROMPTSAVECANCELLED); | |
183 } | |
184 | |
185 if(attributeValue == null) | |
186 item.RemoveMetadata(attributeName); | |
187 else | |
188 item.SetMetadata(attributeName, attributeValue); | |
189 itemProject.SetProjectFileDirty(true); | |
190 } | |
191 | |
192 public string GetEvaluatedMetadata(string attributeName) | |
193 { | |
194 if(this.IsVirtual) | |
195 { | |
196 // For virtual items, use our virtual property c
ollection | |
197 if(!virtualProperties.ContainsKey(attributeName)
) | |
198 { | |
199 return String.Empty; | |
200 } | |
201 return virtualProperties[attributeName]; | |
202 } | |
203 | |
204 // cannot ask MSBuild for Include, so intercept it and r
eturn the corresponding property | |
205 if(String.Compare(attributeName, ProjectFileConstants.In
clude, StringComparison.OrdinalIgnoreCase) == 0) | |
206 { | |
207 return item.FinalItemSpec; | |
208 } | |
209 | |
210 // Build Action is the type, not a property, so intercep
t this one as well | |
211 if(String.Compare(attributeName, ProjectFileConstants.Bu
ildAction, StringComparison.OrdinalIgnoreCase) == 0) | |
212 { | |
213 return item.Name; | |
214 } | |
215 | |
216 return item.GetEvaluatedMetadata(attributeName); | |
217 } | |
218 | |
219 /// <summary> | |
220 /// Get the value of an attribute on a project element | |
221 /// </summary> | |
222 /// <param name="attributeName">Name of the attribute to get the
value for</param> | |
223 /// <returns>Value of the attribute</returns> | |
224 public string GetMetadata(string attributeName) | |
225 { | |
226 if(this.IsVirtual) | |
227 { | |
228 // For virtual items, use our virtual property c
ollection | |
229 if(!virtualProperties.ContainsKey(attributeName)
) | |
230 return String.Empty; | |
231 return virtualProperties[attributeName]; | |
232 } | |
233 | |
234 // cannot ask MSBuild for Include, so intercept it and r
eturn the corresponding property | |
235 if(String.Compare(attributeName, ProjectFileConstants.In
clude, StringComparison.OrdinalIgnoreCase) == 0) | |
236 return item.FinalItemSpec; | |
237 | |
238 // Build Action is the type, not a property, so intercep
t this one as well | |
239 if(String.Compare(attributeName, ProjectFileConstants.Bu
ildAction, StringComparison.OrdinalIgnoreCase) == 0) | |
240 return item.Name; | |
241 | |
242 return item.GetMetadata(attributeName); | |
243 } | |
244 | |
245 /// <summary> | |
246 /// Gets the attribute and throws the handed exception if the ex
ception if the attribute is empty or null. | |
247 /// </summary> | |
248 /// <param name="attributeName">The name of the attribute to get
.</param> | |
249 /// <param name="exception">The exception to be thrown if not fo
und or empty.</param> | |
250 /// <returns>The attribute if found</returns> | |
251 /// <remarks>The method will throw an Exception and neglect the
passed in exception if the attribute is deleted</remarks> | |
252 public string GetMetadataAndThrow(string attributeName, Exceptio
n exception) | |
253 { | |
254 Debug.Assert(!String.IsNullOrEmpty(attributeName), "Cann
ot retrieve an attribute for a null or empty attribute name"); | |
255 string attribute = GetMetadata(attributeName); | |
256 | |
257 if(String.IsNullOrEmpty(attributeName) && exception != n
ull) | |
258 { | |
259 if(String.IsNullOrEmpty(exception.Message)) | |
260 { | |
261 Debug.Assert(!String.IsNullOrEmpty(this.
itemProject.BaseURI.AbsoluteUrl), "Cannot retrieve an attribute for a project th
at does not have a name"); | |
262 string message = String.Format(CultureIn
fo.CurrentCulture, SR.GetString(SR.AttributeLoad, CultureInfo.CurrentUICulture),
attributeName, this.itemProject.BaseURI.AbsoluteUrl); | |
263 throw new Exception(message, exception); | |
264 } | |
265 throw exception; | |
266 } | |
267 | |
268 return attribute; | |
269 } | |
270 | |
271 | |
272 public void Rename(string newPath) | |
273 { | |
274 string escapedPath = Microsoft.Build.BuildEngine.Utiliti
es.Escape(newPath); | |
275 if(this.IsVirtual) | |
276 { | |
277 virtualProperties[ProjectFileConstants.Include]
= escapedPath; | |
278 return; | |
279 } | |
280 | |
281 item.Include = escapedPath; | |
282 this.RefreshProperties(); | |
283 } | |
284 | |
285 | |
286 /// <summary> | |
287 /// Reevaluate all properties for the current item | |
288 /// This should be call if you believe the property for this ite
m | |
289 /// may have changed since it was created/refreshed, or global p
roperties | |
290 /// this items depends on have changed. | |
291 /// Be aware that there is a perf cost in calling this function. | |
292 /// </summary> | |
293 public void RefreshProperties() | |
294 { | |
295 if(this.IsVirtual) | |
296 return; | |
297 | |
298 MSBuild.BuildItemGroup items = itemProject.BuildProject.
EvaluatedItems; | |
299 foreach(MSBuild.BuildItem projectItem in items) | |
300 { | |
301 if(projectItem.Include == item.Include) | |
302 { | |
303 item = projectItem; | |
304 return; | |
305 } | |
306 } | |
307 } | |
308 | |
309 /// <summary> | |
310 /// Return an absolute path for the passed in element. | |
311 /// If the element is already an absolute path, it is returned. | |
312 /// Otherwise, it is unrelativized using the project directory | |
313 /// as the base. | |
314 /// Note that any ".." in the paths will be resolved. | |
315 /// | |
316 /// For non-file system based project, it may make sense to over
ride. | |
317 /// </summary> | |
318 /// <returns>FullPath</returns> | |
319 public string GetFullPathForElement() | |
320 { | |
321 string path = this.GetMetadata(ProjectFileConstants.Incl
ude); | |
322 if(!Path.IsPathRooted(path)) | |
323 path = Path.Combine(this.itemProject.ProjectFold
er, path); | |
324 | |
325 // If any part of the path used relative paths, resolve
this now | |
326 path = Path.GetFullPath(path); | |
327 return path; | |
328 } | |
329 | |
330 #endregion | |
331 | |
332 #region helper methods | |
333 /// <summary> | |
334 /// Has the item been deleted | |
335 /// </summary> | |
336 private bool HasItemBeenDeleted() | |
337 { | |
338 return (this.deleted || this.item == null); | |
339 } | |
340 #endregion | |
341 | |
342 #region overridden from System.Object | |
343 public static bool operator ==(ProjectElement element1, ProjectE
lement element2) | |
344 { | |
345 // Do they reference the same element? | |
346 if(Object.ReferenceEquals(element1, element2)) | |
347 return true; | |
348 | |
349 // Verify that they are not null (cast to object first t
o avoid stack overflow) | |
350 if(element1 as object == null || element2 as object == n
ull) | |
351 { | |
352 return false; | |
353 } | |
354 | |
355 Debug.Assert(!element1.IsVirtual || !element2.IsVirtual,
"Cannot compare virtual nodes"); | |
356 | |
357 // Cannot compare vitual items. | |
358 if(element1.IsVirtual || element2.IsVirtual) | |
359 { | |
360 return false; | |
361 } | |
362 | |
363 // Do they reference the same project? | |
364 if(!element1.itemProject.Equals(element2.itemProject)) | |
365 return false; | |
366 | |
367 // Do they have the same include? | |
368 string include1 = element1.GetMetadata(ProjectFileConsta
nts.Include); | |
369 string include2 = element2.GetMetadata(ProjectFileConsta
nts.Include); | |
370 | |
371 // Unfortunately the checking for nulls have to be done
again, since neither String.Equals nor String.Compare can handle nulls. | |
372 // Virtual folders should not be handled here. | |
373 if(include1 == null || include2 == null) | |
374 { | |
375 return false; | |
376 } | |
377 | |
378 return String.Equals(include1, include2, StringCompariso
n.CurrentCultureIgnoreCase); | |
379 } | |
380 | |
381 | |
382 public static bool operator !=(ProjectElement element1, ProjectE
lement element2) | |
383 { | |
384 return !(element1 == element2); | |
385 } | |
386 | |
387 | |
388 public override bool Equals(object obj) | |
389 { | |
390 ProjectElement element2 = obj as ProjectElement; | |
391 if(element2 == null) | |
392 return false; | |
393 | |
394 return this == element2; | |
395 } | |
396 | |
397 | |
398 public override int GetHashCode() | |
399 { | |
400 return base.GetHashCode(); | |
401 } | |
402 #endregion | |
403 | |
404 | |
405 | |
406 } | |
407 } | |
OLD | NEW |