//
//
//
//
// $Revision$
//
using System;
using System.CodeDom;
using System.IO;
using System.Linq;
using ICSharpCode.Core;
using ICSharpCode.SharpDevelop;
using ICSharpCode.SharpDevelop.Dom;
using ICSharpCode.TextEditor;
using ICSharpCode.TextEditor.Document;
namespace ICSharpCode.FormsDesigner.Services
{
///
/// Supports project-level resources in the Windows.Forms designer.
///
public sealed class ProjectResourceService
{
public const string ProjectResourceKey = "SDProjectResource_";
IProjectContent projectContent;
string stringLiteralDelimiter;
bool designerSupportsProjectResources = true;
public ProjectResourceService(IProjectContent projectContent)
{
if (projectContent == null)
throw new ArgumentNullException("projectContent");
this.projectContent = projectContent;
}
public IProjectContent ProjectContent {
get { return projectContent; }
set {
if (value == null)
throw new ArgumentNullException("value");
if (this.projectContent != value) {
this.projectContent = value;
this.stringLiteralDelimiter = null;
}
}
}
public bool DesignerSupportsProjectResources {
get { return designerSupportsProjectResources; }
set { designerSupportsProjectResources = value; }
}
///
/// Gets the string literal delimiter for the current language
/// by generating code for a known literal.
///
string StringLiteralDelimiter {
get {
if (stringLiteralDelimiter == null) {
const string TestString = "A";
string testCode = this.projectContent.Language.CodeGenerator.GenerateCode(new NRefactory.Ast.PrimitiveExpression(TestString, TestString), String.Empty);
stringLiteralDelimiter = testCode.Substring(0, testCode.IndexOf(TestString, StringComparison.Ordinal));
}
return stringLiteralDelimiter;
}
}
///
/// Gets the project resource from the specified expression.
///
public ProjectResourceInfo GetProjectResource(CodePropertyReferenceExpression propRef)
{
CodeTypeReferenceExpression typeRef = propRef.TargetObject as CodeTypeReferenceExpression;
if (typeRef == null) {
LoggingService.Info("Target of possible project resources property reference is not a type reference, but " + propRef.TargetObject.ToString() + ".");
return null;
}
// Get the (generated) class where the resource is defined.
IClass resourceClass = this.projectContent.GetClass(typeRef.Type.BaseType, 0);
if (resourceClass == null) {
throw new InvalidOperationException("Could not find class for project resources: '" + typeRef.Type.BaseType + "'.");
}
if (resourceClass.CompilationUnit == null || resourceClass.CompilationUnit.FileName == null) {
return null;
}
// Make sure the class we have found is a generated resource class.
if (!IsGeneratedResourceClass(resourceClass)) {
return null;
}
// Get the name of the resource file based on the file that contains the generated class.
string resourceFileName = Path.GetFileNameWithoutExtension(resourceClass.CompilationUnit.FileName);
if (resourceFileName.EndsWith("Designer", StringComparison.OrdinalIgnoreCase)) {
resourceFileName = Path.GetFileNameWithoutExtension(resourceFileName);
}
resourceFileName = Path.Combine(Path.GetDirectoryName(resourceClass.CompilationUnit.FileName), resourceFileName);
if (File.Exists(resourceFileName + ".resources")) {
resourceFileName = resourceFileName + ".resources";
} else if (File.Exists(resourceFileName + ".resx")) {
resourceFileName = resourceFileName + ".resx";
} else {
throw new FileNotFoundException("Could not find the resource file for type '" + resourceClass.FullyQualifiedName + "'. Tried these file names: '" + resourceFileName + ".(resources|resx)'.");
}
// Get the property for the resource.
IProperty prop = resourceClass.Properties.SingleOrDefault(p => p.Name == propRef.PropertyName);
if (prop == null) {
throw new InvalidOperationException("Property '" + propRef.PropertyName + "' not found in type '" + resourceClass.FullyQualifiedName + "'.");
}
if (!prop.CanGet) {
throw new InvalidOperationException("Property '" + propRef.PropertyName + "' in type '" + resourceClass.FullyQualifiedName + "' does not have a getter.");
}
// Get the code of the resource class and
// extract the resource key from the property.
// This is necessary because the key may differ from the property name
// if special characters are used.
// It would be better if we could use a real code parser for this, but
// that is not possible without getting dependent on the programming language.
IDocument doc = new DocumentFactory().CreateDocument();
doc.TextContent = ParserService.GetParseableFileContent(resourceClass.CompilationUnit.FileName);
int startOffset = doc.PositionToOffset(new TextLocation(prop.GetterRegion.BeginColumn - 1, prop.GetterRegion.BeginLine - 1));
int endOffset = doc.PositionToOffset(new TextLocation(prop.GetterRegion.EndColumn - 1, prop.GetterRegion.EndLine - 1));
string code = doc.GetText(startOffset, endOffset - startOffset + 1);
int index = code.IndexOf("ResourceManager", StringComparison.Ordinal);
if (index == -1) {
throw new InvalidOperationException("No reference to ResourceManager found in property getter of '" + prop.FullyQualifiedName + "'. Code: '" + code + "'");
}
index = code.IndexOf("Get", index, StringComparison.Ordinal);
if (index == -1) {
throw new InvalidOperationException("No call to Get... found in property getter of '" + prop.FullyQualifiedName + "'. Code: '" + code + "'");
}
index = code.IndexOf(this.StringLiteralDelimiter, index + 1, StringComparison.Ordinal);
if (index == -1) {
throw new InvalidOperationException("No string delimiter ('" + this.StringLiteralDelimiter + "') found in property getter of '" + prop.FullyQualifiedName + "'. Code: '" + code + "'");
}
index += this.StringLiteralDelimiter.Length;
int endIndex = code.LastIndexOf(this.StringLiteralDelimiter, StringComparison.Ordinal);
if (endIndex == -1) {
throw new InvalidOperationException("No string terminator ('" + this.StringLiteralDelimiter + "') found in property getter of '" + prop.FullyQualifiedName + "'. Code: '" + code + "'");
}
string resourceKey = code.Substring(index, endIndex - index);
LoggingService.Debug("-> Decoded resource: In: " + resourceFileName + ". Key: " + resourceKey);
return new ProjectResourceInfo(resourceFileName, resourceKey);
}
///
/// Determines whether the specified class is a generated resource
/// class, based on the attached attributes.
///
public static bool IsGeneratedResourceClass(IClass @class)
{
IClass generatedCodeAttributeClass = @class.ProjectContent.GetClass("System.CodeDom.Compiler.GeneratedCodeAttribute", 0);
if (generatedCodeAttributeClass == null) {
LoggingService.Info("Could not find the class for 'System.CodeDom.Compiler.GeneratedCodeAttribute'.");
return false;
}
IReturnType generatedCodeAttribute = generatedCodeAttributeClass.DefaultReturnType;
return @class.Attributes.Any(
att =>
att.AttributeType.Equals(generatedCodeAttribute) &&
att.PositionalArguments.Count == 2 &&
String.Equals("System.Resources.Tools.StronglyTypedResourceBuilder", att.PositionalArguments[0] as string, StringComparison.Ordinal)
);
}
}
}