// Copyright (c) 2010-2013 AlphaSierraPapa for the SharpDevelop Team
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
// to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
using System;
using System.Linq;
using System.Collections.Generic;
using System.Text;
using ICSharpCode.NRefactory.TypeSystem;
using ICSharpCode.NRefactory.TypeSystem.Implementation;
namespace ICSharpCode.NRefactory.Documentation
{
///
/// Provides ID strings for entities. (C# 4.0 spec, §A.3.1)
/// ID strings are used to identify members in XML documentation files.
///
public static class IdStringProvider
{
#region GetIdString
///
/// Gets the ID string (C# 4.0 spec, §A.3.1) for the specified entity.
///
public static string GetIdString(this IEntity entity)
{
StringBuilder b = new StringBuilder();
switch (entity.SymbolKind) {
case SymbolKind.TypeDefinition:
b.Append("T:");
AppendTypeName(b, (ITypeDefinition)entity, false);
return b.ToString();
case SymbolKind.Field:
b.Append("F:");
break;
case SymbolKind.Property:
case SymbolKind.Indexer:
b.Append("P:");
break;
case SymbolKind.Event:
b.Append("E:");
break;
default:
b.Append("M:");
break;
}
IMember member = (IMember)entity;
AppendTypeName(b, member.DeclaringType, false);
b.Append('.');
if (member.IsExplicitInterfaceImplementation && member.Name.IndexOf('.') < 0 && member.ImplementedInterfaceMembers.Count == 1) {
AppendTypeName(b, member.ImplementedInterfaceMembers[0].DeclaringType, true);
b.Append('#');
}
b.Append(member.Name.Replace('.', '#'));
IMethod method = member as IMethod;
if (method != null && method.TypeParameters.Count > 0) {
b.Append("``");
b.Append(method.TypeParameters.Count);
}
IParameterizedMember parameterizedMember = member as IParameterizedMember;
if (parameterizedMember != null && parameterizedMember.Parameters.Count > 0) {
b.Append('(');
var parameters = parameterizedMember.Parameters;
for (int i = 0; i < parameters.Count; i++) {
if (i > 0) b.Append(',');
AppendTypeName(b, parameters[i].Type, false);
}
b.Append(')');
}
if (member.SymbolKind == SymbolKind.Operator && (member.Name == "op_Implicit" || member.Name == "op_Explicit")) {
b.Append('~');
AppendTypeName(b, member.ReturnType, false);
}
return b.ToString();
}
#endregion
#region GetTypeName
public static string GetTypeName(IType type)
{
if (type == null)
throw new ArgumentNullException("type");
StringBuilder b = new StringBuilder();
AppendTypeName(b, type, false);
return b.ToString();
}
static void AppendTypeName(StringBuilder b, IType type, bool explicitInterfaceImpl)
{
switch (type.Kind) {
case TypeKind.Dynamic:
b.Append(explicitInterfaceImpl ? "System#Object" : "System.Object");
break;
case TypeKind.TypeParameter:
ITypeParameter tp = (ITypeParameter)type;
if (explicitInterfaceImpl) {
b.Append(tp.Name);
} else {
b.Append('`');
if (tp.OwnerType == SymbolKind.Method)
b.Append('`');
b.Append(tp.Index);
}
break;
case TypeKind.Array:
ArrayType array = (ArrayType)type;
AppendTypeName(b, array.ElementType, explicitInterfaceImpl);
b.Append('[');
if (array.Dimensions > 1) {
for (int i = 0; i < array.Dimensions; i++) {
if (i > 0)
b.Append(explicitInterfaceImpl ? '@' : ',');
if (!explicitInterfaceImpl)
b.Append("0:");
}
}
b.Append(']');
break;
case TypeKind.Pointer:
AppendTypeName(b, ((PointerType)type).ElementType, explicitInterfaceImpl);
b.Append('*');
break;
case TypeKind.ByReference:
AppendTypeName(b, ((ByReferenceType)type).ElementType, explicitInterfaceImpl);
b.Append('@');
break;
default:
IType declType = type.DeclaringType;
if (declType != null) {
AppendTypeName(b, declType, explicitInterfaceImpl);
b.Append(explicitInterfaceImpl ? '#' : '.');
b.Append(type.Name);
AppendTypeParameters(b, type, declType.TypeParameterCount, explicitInterfaceImpl);
} else {
if (explicitInterfaceImpl)
b.Append(type.FullName.Replace('.', '#'));
else
b.Append(type.FullName);
AppendTypeParameters(b, type, 0, explicitInterfaceImpl);
}
break;
}
}
static void AppendTypeParameters(StringBuilder b, IType type, int outerTypeParameterCount, bool explicitInterfaceImpl)
{
int tpc = type.TypeParameterCount - outerTypeParameterCount;
if (tpc > 0) {
ParameterizedType pt = type as ParameterizedType;
if (pt != null) {
b.Append('{');
var ta = pt.TypeArguments;
for (int i = outerTypeParameterCount; i < ta.Count; i++) {
if (i > outerTypeParameterCount)
b.Append(explicitInterfaceImpl ? '@' : ',');
AppendTypeName(b, ta[i], explicitInterfaceImpl);
}
b.Append('}');
} else {
b.Append('`');
b.Append(tpc);
}
}
}
#endregion
#region ParseMemberName
///
/// Parse the ID string into a member reference.
///
/// The ID string representing the member (with "M:", "F:", "P:" or "E:" prefix).
/// A member reference that represents the ID string.
/// The syntax of the ID string is invalid
///
/// The member reference will look in first,
/// and if the member is not found there,
/// it will look in all other assemblies of the compilation.
///
public static IMemberReference ParseMemberIdString(string memberIdString)
{
if (memberIdString == null)
throw new ArgumentNullException("memberIdString");
if (memberIdString.Length < 2 || memberIdString[1] != ':')
throw new ReflectionNameParseException(0, "Missing type tag");
char typeChar = memberIdString[0];
int parenPos = memberIdString.IndexOf('(');
if (parenPos < 0)
parenPos = memberIdString.LastIndexOf('~');
if (parenPos < 0)
parenPos = memberIdString.Length;
int dotPos = memberIdString.LastIndexOf('.', parenPos - 1);
if (dotPos < 0)
throw new ReflectionNameParseException(0, "Could not find '.' separating type name from member name");
string typeName = memberIdString.Substring(0, dotPos);
int pos = 2;
ITypeReference typeReference = ParseTypeName(typeName, ref pos);
if (pos != typeName.Length)
throw new ReflectionNameParseException(pos, "Expected end of type name");
// string memberName = memberIDString.Substring(dotPos + 1, parenPos - (dotPos + 1));
// pos = memberName.LastIndexOf("``");
// if (pos > 0)
// memberName = memberName.Substring(0, pos);
// memberName = memberName.Replace('#', '.');
return new IdStringMemberReference(typeReference, typeChar, memberIdString);
}
#endregion
#region ParseTypeName
///
/// Parse the ID string type name into a type reference.
///
/// The ID string representing the type (the "T:" prefix is optional).
/// A type reference that represents the ID string.
/// The syntax of the ID string is invalid
///
///
/// The type reference will look in first,
/// and if the type is not found there,
/// it will look in all other assemblies of the compilation.
///
///
/// If the type is open (contains type parameters '`0' or '``0'),
/// an with the appropriate CurrentTypeDefinition/CurrentMember is required
/// to resolve the reference to the ITypeParameter.
///
///
public static ITypeReference ParseTypeName(string typeName)
{
if (typeName == null)
throw new ArgumentNullException("typeName");
int pos = 0;
if (typeName.StartsWith("T:", StringComparison.Ordinal))
pos = 2;
ITypeReference r = ParseTypeName(typeName, ref pos);
if (pos < typeName.Length)
throw new ReflectionNameParseException(pos, "Expected end of type name");
return r;
}
static bool IsIDStringSpecialCharacter(char c)
{
switch (c) {
case ':':
case '{':
case '}':
case '[':
case ']':
case '(':
case ')':
case '`':
case '*':
case '@':
case ',':
return true;
default:
return false;
}
}
static ITypeReference ParseTypeName(string typeName, ref int pos)
{
string reflectionTypeName = typeName;
if (pos == typeName.Length)
throw new ReflectionNameParseException(pos, "Unexpected end");
ITypeReference result;
if (reflectionTypeName[pos] == '`') {
// type parameter reference
pos++;
if (pos == reflectionTypeName.Length)
throw new ReflectionNameParseException(pos, "Unexpected end");
if (reflectionTypeName[pos] == '`') {
// method type parameter reference
pos++;
int index = ReflectionHelper.ReadTypeParameterCount(reflectionTypeName, ref pos);
result = TypeParameterReference.Create(SymbolKind.Method, index);
} else {
// class type parameter reference
int index = ReflectionHelper.ReadTypeParameterCount(reflectionTypeName, ref pos);
result = TypeParameterReference.Create(SymbolKind.TypeDefinition, index);
}
} else {
// not a type parameter reference: read the actual type name
List typeArguments = new List();
int typeParameterCount;
string typeNameWithoutSuffix = ReadTypeName(typeName, ref pos, true, out typeParameterCount, typeArguments);
result = new GetPotentiallyNestedClassTypeReference(typeNameWithoutSuffix, typeParameterCount);
while (pos < typeName.Length && typeName[pos] == '.') {
pos++;
string nestedTypeName = ReadTypeName(typeName, ref pos, false, out typeParameterCount, typeArguments);
result = new NestedTypeReference(result, nestedTypeName, typeParameterCount);
}
if (typeArguments.Count > 0) {
result = new ParameterizedTypeReference(result, typeArguments);
}
}
while (pos < typeName.Length) {
switch (typeName[pos]) {
case '[':
int dimensions = 1;
do {
pos++;
if (pos == typeName.Length)
throw new ReflectionNameParseException(pos, "Unexpected end");
if (typeName[pos] == ',')
dimensions++;
} while (typeName[pos] != ']');
result = new ArrayTypeReference(result, dimensions);
break;
case '*':
result = new PointerTypeReference(result);
break;
case '@':
result = new ByReferenceTypeReference(result);
break;
default:
return result;
}
pos++;
}
return result;
}
static string ReadTypeName(string typeName, ref int pos, bool allowDottedName, out int typeParameterCount, List typeArguments)
{
int startPos = pos;
// skip the simple name portion:
while (pos < typeName.Length && !IsIDStringSpecialCharacter(typeName[pos]) && (allowDottedName || typeName[pos] != '.'))
pos++;
if (pos == startPos)
throw new ReflectionNameParseException(pos, "Expected type name");
string shortTypeName = typeName.Substring(startPos, pos - startPos);
// read type arguments:
typeParameterCount = 0;
if (pos < typeName.Length && typeName[pos] == '`') {
// unbound generic type
pos++;
typeParameterCount = ReflectionHelper.ReadTypeParameterCount(typeName, ref pos);
} else if (pos < typeName.Length && typeName[pos] == '{') {
// bound generic type
typeArguments = new List();
do {
pos++;
typeArguments.Add(ParseTypeName(typeName, ref pos));
typeParameterCount++;
if (pos == typeName.Length)
throw new ReflectionNameParseException(pos, "Unexpected end");
} while (typeName[pos] == ',');
if (typeName[pos] != '}')
throw new ReflectionNameParseException(pos, "Expected '}'");
pos++;
}
return shortTypeName;
}
#endregion
#region FindEntity
///
/// Finds the entity in the given type resolve context.
///
/// ID string of the entity.
/// Type resolve context
/// Returns the entity, or null if it is not found.
/// The syntax of the ID string is invalid
public static IEntity FindEntity(string idString, ITypeResolveContext context)
{
if (idString == null)
throw new ArgumentNullException("idString");
if (context == null)
throw new ArgumentNullException("context");
if (idString.StartsWith("T:", StringComparison.Ordinal)) {
return ParseTypeName(idString.Substring(2)).Resolve(context).GetDefinition();
} else {
return ParseMemberIdString(idString).Resolve(context);
}
}
#endregion
}
}