Files
RobustToolbox/Robust.Shared/ViewVariables/ViewVariablesManager.PathResolving.cs
2023-04-29 19:28:47 +10:00

205 lines
6.5 KiB
C#

using System;
using System.Linq;
using System.Reflection;
using System.Text.RegularExpressions;
using Robust.Shared.Reflection;
namespace Robust.Shared.ViewVariables;
internal abstract partial class ViewVariablesManager
{
private static readonly Regex IndexerRegex = new (@"\[[^\[]+\]", RegexOptions.Compiled);
private static readonly Regex TypeSpecifierRegex = new (@"\{[^\{]+\}", RegexOptions.Compiled);
public ViewVariablesPath? ResolvePath(string path)
{
if (string.IsNullOrEmpty(path))
return null;
if (path.StartsWith('/'))
path = path[1..];
if (path.EndsWith('/'))
path = path[..^1];
var segments = path.Split('/');
// Technically, this should never happen... But hey, better be safe than sorry?
if (segments.Length == 0)
return null;
var domain = segments[0];
if (!_registeredDomains.TryGetValue(domain, out var domainData))
return null;
var (newPath, relativePath) = domainData.ResolveObject(string.Join('/', segments[1..]));
return ResolveRelativePath(newPath, relativePath);
}
private ViewVariablesPath? ResolveRelativePath(ViewVariablesPath? path, string[] segments)
{
ViewVariablesComponentPath? oldComp;
// Who needs recursion, am I right?
while (true)
{
if (path is ViewVariablesComponentPath compPath)
path.ParentComponent = compPath;
oldComp = path?.ParentComponent;
// Empty path, return current path. This can happen as we slowly take away segments from the array.
if (segments.Length == 0)
{
return path;
}
if (path?.Get() is not {} obj)
return null;
var nextSegment = segments[0];
if (string.IsNullOrEmpty(nextSegment))
{
// Let's ignore that...
segments = segments[1..];
continue;
}
var specifiers = TypeSpecifierRegex.Matches(nextSegment);
var indexers = IndexerRegex.Matches(nextSegment);
var nextSegmentClean = TypeSpecifierRegex.Replace(
IndexerRegex.Replace(nextSegment, string.Empty), string.Empty);
// Yeah, that's not valid bud.
if (specifiers.Count > 1)
return null;
VVAccess? access = null;
if (specifiers.Count == 1 || ResolveTypeHandlers(path, nextSegmentClean) is not {} customPath)
{
Type? declaringType = null;
if (specifiers.Count == 1 && _reflectionMan.GetType(specifiers[0].Value[1..^1]) is {} t)
{
declaringType = t;
}
var memberInfo = obj.GetType().GetSingleMember(nextSegmentClean, declaringType);
if (memberInfo == null || !ViewVariablesUtility.TryGetViewVariablesAccess(memberInfo, out access))
return null;
path = memberInfo switch
{
FieldInfo or PropertyInfo => new ViewVariablesFieldOrPropertyPath(obj, memberInfo, _entMan),
MethodInfo methodInfo => new ViewVariablesMethodPath(obj, methodInfo),
_ => throw new InvalidOperationException("Invalid member! Must be a property, field or method.")
};
}
else
{
path = customPath;
access = VVAccess.ReadWrite;
}
UpdateParentComp(path, ref oldComp);
// After this, obj is essentially the parent.
foreach (Match match in indexers)
{
path = ResolveIndexing(path, ParseArguments(match.Value[1..^1]), access.Value);
UpdateParentComp(path, ref oldComp);
}
segments = segments[1..];
}
}
private void UpdateParentComp(ViewVariablesPath? newPath, ref ViewVariablesComponentPath? oldPath)
{
if (newPath == null)
return;
if (newPath is ViewVariablesComponentPath newCompPath)
newPath.ParentComponent = newCompPath;
else if (newPath != null)
newPath.ParentComponent ??= oldPath;
oldPath = newPath?.ParentComponent;
}
private ViewVariablesPath? ResolveIndexing(ViewVariablesPath? path, string[] arguments, VVAccess access)
{
if (path?.Get() is not {} obj || arguments.Length == 0)
return null;
var type = obj.GetType();
// Multidimensional arrays... More like, painful arrays.
if (type.IsArray && type.GetArrayRank() > 1)
{
var getter = type.GetSingleMember("Get") as MethodInfo;
var setter = type.GetSingleMember("Set") as MethodInfo;
if (getter == null && setter == null)
return null;
var p = DeserializeArguments(
getter?.GetParameters().Select(p => p.ParameterType).ToArray()
?? setter!.GetParameters()[1..].Select(p => p.ParameterType).ToArray(),
0, arguments);
object? Get()
{
return getter?.Invoke(obj, p);
}
void Set(object? value)
{
if(p != null && access == VVAccess.ReadWrite)
setter?.Invoke(obj, new[] {value}.Concat(p).ToArray());
}
return new ViewVariablesFakePath(Get, Set, null, getter?.ReturnType ?? setter!.GetParameters()[0].ParameterType);
}
// No indexer.
if (type.GetIndexer() is not {} indexer)
return null;
var parametersInfo = indexer.GetIndexParameters();
var parameters = DeserializeArguments(
parametersInfo.Select(p => p.ParameterType).ToArray(),
parametersInfo.Count(p => p.IsOptional),
arguments);
if (parameters == null)
return null;
return new ViewVariablesIndexedPath(obj, indexer, parameters, access);
}
private ViewVariablesPath? ResolveTypeHandlers(ViewVariablesPath path, string relativePath)
{
if (path.Get() is not {} obj
|| string.IsNullOrEmpty(relativePath)
|| relativePath.Contains('/'))
return null;
foreach (var handler in GetAllTypeHandlers(obj.GetType()))
{
if (handler.HandlePath(path, relativePath) is {} newPath)
return newPath;
}
// Not handled by a custom type handler!
return null;
}
}