/// <summary> /// Gets a type from its <see cref="DataContractAttribute.Alias"/> or <see cref="DataAliasAttribute.Name"/>. /// </summary> /// <param name="alias"></param> /// <returns></returns> public static Type GetTypeFromAlias(string alias) { // TODO: At some point we might want to reorganize AssemblyRegistry and DataSerializerFactory // I am not sure the list of assemblies matches between those two (some assemblies are probably not registered in AssemblyRegistry), // so the semantic of GetTypeFromAlias (which include all assemblies) might be different than GetType. return(DataSerializerFactory.GetTypeFromAlias(alias)); }
public override UpdatableMember ResolveIndexer(string indexerName) { // Note: we currently only support component with data contract aliases var dotIndex = indexerName.LastIndexOf('.'); // TODO: Temporary hack to get static field of the requested type/property name // Need to have access to DataContract name<=>type mapping in the runtime (only accessible in SiliconStudio.Core.Design now) var typeName = (dotIndex == -1) ? indexerName : indexerName.Substring(0, dotIndex); var type = DataSerializerFactory.GetTypeFromAlias(typeName); if (type == null) { throw new InvalidOperationException($"Can't find a type with alias {typeName}; did you properly set a DataContractAttribute with this alias?"); } return(new EntityComponentPropertyAccessor(type)); }
/// <summary> /// Compiles a list of update operations into a <see cref="CompiledUpdate"/>, for use with <see cref="Run"/>. /// </summary> /// <param name="rootObjectType">The type of the root object.</param> /// <param name="animationPaths">The different paths and source offsets to use when <see cref="Run"/> is applied.</param> /// <returns>A <see cref="CompiledUpdate"/> object that can be used for <see cref="Run"/>.</returns> public static CompiledUpdate Compile(Type rootObjectType, List <UpdateMemberInfo> animationPaths) { var currentPath = string.Empty; var temporaryObjectsList = new List <object>(); var state = new ComputeUpdateOperationState(); state.UpdateOperations = new List <UpdateOperation>(); state.StackPath = new List <AnimationBuilderStackEntry> { new AnimationBuilderStackEntry(rootObjectType, 0, 0, -1), }; foreach (var animationPath in animationPaths) { var commonPathParts = 1; // Detect how much of the path is unmodified (note: we start from 1 because root always stay there) for (int index = 1; index < state.StackPath.Count; index++) { var pathPart = state.StackPath[index]; // Check if next path part is the same (first check length then content) if (((animationPath.Name.Length == pathPart.EndIndex) || (animationPath.Name.Length > pathPart.EndIndex && (animationPath.Name[pathPart.EndIndex] == PathDelimiter || animationPath.Name[pathPart.EndIndex] == PathIndexerOpen))) && string.Compare(animationPath.Name, pathPart.StartIndex, currentPath, pathPart.StartIndex, pathPart.EndIndex - pathPart.StartIndex, StringComparison.Ordinal) == 0) { commonPathParts++; continue; } break; } PopObjects(ref state, commonPathParts); // Parse the new path parts state.ParseElementStart = state.StackPath.Last().EndIndex; // Compute offset from start of current object state.NewOffset = state.StackPath.Last().ObjectStartOffset; while (state.ParseElementStart < animationPath.Name.Length) { var containerType = state.StackPath.Last().Type; // We have only two cases for now: array or property/field name bool isIndexerAccess = animationPath.Name[state.ParseElementStart] == PathIndexerOpen; if (isIndexerAccess) { // Parse until end of indexer state.ParseElementEnd = animationPath.Name.IndexOf(PathIndexerClose, state.ParseElementStart + 1); if (state.ParseElementEnd == -1) { throw new InvalidOperationException("Property path parse error: could not find indexer end ']'"); } // Include the indexer close state.ParseElementEnd++; // Parse integer // TODO: Avoid substring? var indexerString = animationPath.Name.Substring(state.ParseElementStart + 1, state.ParseElementEnd - state.ParseElementStart - 2); // T[], IList<T>, etc... // Try to resolve using custom resolver UpdatableMember updatableMember = null; var parentType = containerType; while (parentType != null) { UpdateMemberResolver resolver; if (MemberResolvers.TryGetValue(parentType, out resolver)) { updatableMember = resolver.ResolveIndexer(indexerString); if (updatableMember != null) { break; } } parentType = parentType.GetTypeInfo().BaseType; } // Try interfaces if (updatableMember == null) { foreach (var implementedInterface in containerType.GetTypeInfo().ImplementedInterfaces) { UpdateMemberResolver resolver; if (MemberResolvers.TryGetValue(implementedInterface, out resolver)) { updatableMember = resolver.ResolveIndexer(indexerString); if (updatableMember != null) { break; } } } } if (updatableMember == null) { throw new InvalidOperationException(string.Format("Property path parse error: could not find binding info for index {0} in type {1}", indexerString, containerType)); } ProcessMember(ref state, animationPath, updatableMember, temporaryObjectsList); } else { // Note: first character might be a . delimiter, if so, skip it var propertyStart = state.ParseElementStart; if (animationPath.Name[propertyStart] == PathDelimiter) { propertyStart++; } // Check if it started with a parenthesis (to perform a cast operation) if (animationPath.Name[propertyStart] == PathCastOpen) { // Parse until end of cast operation state.ParseElementEnd = animationPath.Name.IndexOf(PathCastClose, ++propertyStart); if (state.ParseElementEnd == -1) { throw new InvalidOperationException("Property path parse error: could not find cast operation ending ')'"); } var typeName = animationPath.Name.Substring(propertyStart, state.ParseElementEnd - propertyStart); // Include the indexer close state.ParseElementEnd++; // Try to resolve using alias first, then full assembly registry using assembly qualified name var type = DataSerializerFactory.GetTypeFromAlias(typeName) ?? AssemblyRegistry.GetType(typeName); if (type == null) { throw new InvalidOperationException($"Could not resolve type {typeName}"); } // Push entry with new type // TODO: Should we actually perform an early castclass and skip if type is incorrect? state.StackPath.Add(new AnimationBuilderStackEntry(type, state.ParseElementStart, state.ParseElementEnd, -1) { ObjectStartOffset = state.NewOffset, }); } else { UpdatableMember updatableMember; // Parse until end next group (or end) state.ParseElementEnd = animationPath.Name.IndexOfAny(PathGroupDelimiters, state.ParseElementStart + 1); if (state.ParseElementEnd == -1) { state.ParseElementEnd = animationPath.Name.Length; } // TODO: Avoid substring? var propertyName = animationPath.Name.Substring(propertyStart, state.ParseElementEnd - propertyStart); if (!UpdateKeys.TryGetValue(new UpdateKey(containerType, propertyName), out updatableMember)) { // Try to resolve using custom resolver var parentType = containerType; while (parentType != null) { UpdateMemberResolver resolver; if (MemberResolvers.TryGetValue(parentType, out resolver)) { updatableMember = resolver.ResolveProperty(propertyName); if (updatableMember != null) { break; } } parentType = parentType.GetTypeInfo().BaseType; } if (updatableMember == null) { throw new InvalidOperationException(string.Format("Property path parse error: could not find binding info for member {0} in type {1}", propertyName, containerType)); } } // Process member ProcessMember(ref state, animationPath, updatableMember, temporaryObjectsList); } } state.ParseElementStart = state.ParseElementEnd; } currentPath = animationPath.Name; } // Totally pop the stack (we might still have stuff to copy back into properties PopObjects(ref state, 0); return(new CompiledUpdate { TemporaryObjects = temporaryObjectsList.ToArray(), UpdateOperations = state.UpdateOperations.ToArray(), }); }