using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Reflection;
using System.Text;
using Pfz.Extensions.AttributeExtensions;
using Pfz.Extensions.DatabaseNameExtensions;
namespace Pfz.Databasing.Internal
{
internal sealed class CSharpGenerator
{
private StringBuilder fStringBuilder = new StringBuilder();
private int fLevel = 0;
private bool fLineHasRecords;
private void p_Write(string line)
{
if (!fLineHasRecords)
{
for(int i=0; i<fLevel; i++)
fStringBuilder.Append("\t");
fLineHasRecords = true;
}
fStringBuilder.Append(line);
}
private void p_WriteLine(string line)
{
p_Write(line);
fStringBuilder.Append("\r\n");
fLineHasRecords = false;
}
private void p_WriteLine(params object[] values)
{
p_Write(values);
fStringBuilder.Append("\r\n");
fLineHasRecords = false;
}
private void p_Write(params object[] values)
{
foreach (object value in values)
{
Type type = value as Type;
if (type == null)
p_Write(value.ToString());
else
{
p_Write("global::");
p_Write(p_GetName(type));
}
}
}
private static List<PropertyInfo> p_GetProperties(Type type, bool primaryKeyOnly)
{
List<PropertyInfo> result = new List<PropertyInfo>();
p_AddProperties(result, type, primaryKeyOnly);
return result;
}
private static void p_AddProperties(List<PropertyInfo> list, Type type, bool primaryKeyOnly)
{
foreach(var interfaceType in type.GetInterfaces())
foreach(var propertyInfo in interfaceType.GetProperties())
p_AddProperty(list, propertyInfo, primaryKeyOnly);
foreach(var propertyInfo in type.GetProperties())
p_AddProperty(list, propertyInfo, primaryKeyOnly);
}
private static void p_AddProperty(List<PropertyInfo> list, PropertyInfo propertyInfo, bool primaryKeyOnly)
{
if (primaryKeyOnly && !propertyInfo.ContainsCustomAttribute<PrimaryKeyAttribute>())
return;
if (!propertyInfo.CanRead)
return;
if (!propertyInfo.CanWrite)
return;
if (propertyInfo.ContainsCustomAttribute<DatabaseIgnoredAttribute>())
return;
list.Add(propertyInfo);
}
public void Implement(Type type)
{
PropertyInfoPaths propertyPaths = new PropertyInfoPaths(type, false);
PropertyInfoPaths primaryKeyPaths = new PropertyInfoPaths(type, true);
var dictionaryOfPropertyPaths = propertyPaths.GetDictionaryByPrimaryProperty();
var propertyInfos = p_GetProperties(type, false);
bool isPersisted = type.ContainsCustomAttribute<DatabasePersistedAttribute>();
string fieldsSql = null;
string whereSql = null;
string whereSqlMT = null;
if (isPersisted)
{
fieldsSql = p_GetFieldsSql(propertyPaths);
whereSql = p_GetWhereSql(type, primaryKeyPaths, null);
whereSqlMT = p_GetWhereSql(type, primaryKeyPaths, "MT.");
}
p_Write("namespace Pfz.Databasing.ImplementedRecords.");
p_Write(type.Namespace);
using (p_CreateBlock())
{
string databaseName = type.GetDatabaseName();
if (isPersisted)
{
p_WriteLine("[global::System.Serializable]");
p_WriteLine("[", typeof(DatabaseNameAttribute), "(\"", databaseName, "\")]");
p_Write("public class ");
}
else
p_Write("public static class ");
p_Write(type.Name);
if (isPersisted)
{
p_WriteLine(":");
p_WriteLine(" ", typeof(RecordBase), ",");
p_WriteLine(" ", type);
}
else
p_WriteLine("");
using (p_CreateBlock())
{
p_Write("public const string FieldsSql = \"");
p_Write(fieldsSql);
p_WriteLine("\";");
p_Write("public const string PrimaryKeyWhereSql = \"");
p_Write(whereSql);
p_WriteLine("\";");
foreach(var pair in dictionaryOfPropertyPaths)
{
var firstProperty = pair.Key;
if (firstProperty.DeclaringType != type)
continue;
var paths = pair.Value;
if (paths[0].Length == 1)
{
p_Write("public static readonly global::System.Reflection.PropertyInfo PropertyOf_");
p_Write(firstProperty.Name);
p_Write(" = typeof(global::");
p_Write(p_GetName(type));
p_Write(").GetProperty(\"");
p_Write(firstProperty.Name);
p_WriteLine("\");");
}
else
{
int pathIndex = -1;
foreach(var path in paths)
{
pathIndex++;
p_Write("public static readonly ", typeof(PropertyInfoPath), " PropertyPathOf_");
p_Write(firstProperty.Name);
p_Write("_", pathIndex);
p_Write(" = new ", typeof(PropertyInfoPath), "(new global::System.Reflection.PropertyInfo[]{");
foreach(var propertyInfo in path)
p_Write("typeof(", propertyInfo.DeclaringType, ").GetProperty(\"", propertyInfo.Name, "\"), ");
fStringBuilder.Length -= 2;
p_WriteLine("});");
}
}
}
p_WritePropertiesCollection(type, primaryKeyPaths, "PrimaryKeyProperties");
p_WritePropertiesCollection(type, propertyPaths, "DatabaseProperties");
p_Write("public static readonly ", typeof(ReadOnlyCollection<PropertyInfo>), " NonPersistedReferenceProperties = new ", typeof(ReadOnlyCollection<PropertyInfo>), "(new ", typeof(PropertyInfo[]), "{");
bool hasAny = false;
foreach(var property in propertyInfos)
hasAny |= p_WriteNonPersistedReferenceProperty(property);
if (hasAny)
fStringBuilder.Length -= 2;
p_WriteLine("});");
if (!isPersisted)
return;
p_Write("public const string SimpleSelectAllSql = \"SELECT ");
p_Write(fieldsSql);
p_Write(" FROM ");
p_Write(databaseName);
p_WriteLine(" MT\";");
p_Write("public const string SelectByPrimaryKeySql = \"SELECT ");
p_Write(fieldsSql);
p_Write(" FROM ");
p_Write(databaseName);
p_Write(" MT WHERE ");
p_Write(whereSqlMT);
p_WriteLine("\";");
p_WriteInsertSql(databaseName, propertyPaths.Count, fieldsSql);
p_WriteLine("public const string DeleteSql = \"DELETE FROM ", databaseName, " WHERE ", whereSql, "\";");
p_CreateBasicConstructor(type);
p_CreateFromDatabaseConstructor(type, propertyPaths, dictionaryOfPropertyPaths);
p_CreateCloneConstructor(type, dictionaryOfPropertyPaths);
p_WriteLine("public override object Clone()");
using(p_CreateBlock())
{
p_WriteLine(type.Name, " result = new ", type.Name, "(fRecordMode, this);");
p_WriteLine("result.fOldRecord = fOldRecord;");
p_WriteLine("result.fMustDeleteOnApply = fMustDeleteOnApply;");
p_WriteLine("return result;");
}
p_WriteLine("public override ", typeof(IRecord), " UntypedCreateUpdateRecord()");
using(p_CreateBlock())
{
p_WriteLine("if (fRecordMode != ", typeof(RecordMode), ".ReadOnly) throw new ", typeof(DatabaseException), "(\"Can't create an update record from records that are not read-only.\");");
p_WriteLine(type.Name, " result = new ", type.Name, "(", typeof(RecordMode), ".Update, this);");
p_WriteLine("result.fOldRecord = this;");
p_WriteLine("return result;");
}
p_WriteGetHashCode(primaryKeyPaths);
p_WriteEquals(type, propertyPaths);
foreach(var pair in dictionaryOfPropertyPaths)
{
var propertyInfo = pair.Key;
Type propertyType = propertyInfo.PropertyType;
p_Write("private ");
p_Write(propertyType);
p_Write(" _");
p_Write(propertyInfo.Name);
p_WriteLine(";");
if (typeof(IRecord).IsAssignableFrom(propertyType))
{
p_Write("private object[] __keyOf_");
p_Write(propertyInfo.Name);
p_WriteLine(";");
}
p_Write("public ");
p_Write(propertyType);
p_Write(" ");
p_WriteLine(propertyInfo.Name);
using(p_CreateBlock())
{
p_WriteLine("get");
using (p_CreateBlock())
{
if (typeof(IRecord).IsAssignableFrom(propertyType))
{
p_Write("if (_");
p_Write(propertyInfo.Name);
p_WriteLine(" == null)");
using(p_CreateBlock())
{
p_Write("if (__keyOf_");
p_Write(propertyInfo.Name);
p_WriteLine(" == null) return null;");
p_Write("_");
p_Write(propertyInfo.Name);
p_Write(" = global::");
p_Write(typeof(Record).FullName);
p_Write(".LoadByPrimaryKey<", propertyInfo.PropertyType, ">(__keyOf_");
p_Write(propertyInfo.Name);
p_WriteLine(");");
p_WriteLine("__keyOf_", propertyInfo.Name, " = null;");
}
}
p_Write("return _");
p_Write(propertyInfo.Name);
p_WriteLine(";");
}
p_WriteLine("set");
using (p_CreateBlock())
{
p_WriteLine("switch(fRecordMode)");
using(p_CreateBlock())
{
p_Write("case ");
p_Write(typeof(RecordMode));
p_Write(".");
p_Write(RecordMode.Insert.ToString());
p_WriteLine(":");
p_Write("case ");
p_Write(typeof(RecordMode));
p_Write(".");
p_Write(RecordMode.Update.ToString());
p_WriteLine(":");
if (typeof(IRecord).IsAssignableFrom(propertyType))
{
p_WriteLine("if (value != null)");
using(p_CreateBlock())
{
p_WriteLine("if (value.GetRecordMode() != ", typeof(RecordMode),".ReadOnly)");
p_WriteLine("\tthrow new ", typeof(DatabaseException), "(\"You can only point to already persisted (read-only) records.\");");
}
}
p_Write(" _");
p_Write(propertyInfo.Name);
p_WriteLine(" = value;");
if (typeof(IRecord).IsAssignableFrom(propertyType))
p_WriteLine(" __keyOf_", propertyInfo.Name, " = null;");
p_WriteLine(" break;");
p_WriteLine("default: throw new global::System.Data.ReadOnlyException();");
}
}
}
}
}
}
}
private bool p_WriteNonPersistedReferenceProperty(PropertyInfo property)
{
var propertyType = property.PropertyType;
if (!typeof(IRecord).IsAssignableFrom(propertyType))
return false;
if (propertyType.ContainsCustomAttribute<DatabasePersistedAttribute>())
return false;
p_Write("typeof(", property.DeclaringType, ").GetProperty(\"", property.Name, "\"), ");
return true;
}
private void p_WriteGetHashCode(PropertyInfoPaths primaryKeyPaths)
{
p_WriteLine("public override int GetHashCode()");
using(p_CreateBlock())
{
p_WriteLine("unchecked");
using(p_CreateBlock())
{
p_WriteLine("int __result = 0;");
PropertyInfo lastProperty = null;
foreach(var primaryKeyPath in primaryKeyPaths)
{
PropertyInfo property = primaryKeyPath[0];
if (property == lastProperty)
continue;
lastProperty = property;
p_WriteLine("if (_", property.Name, " != null) __result ^= _", property.Name, ".GetHashCode();");
}
p_WriteLine("return __result;");
}
}
}
private void p_WriteEquals(Type type, PropertyInfoPaths propertyPaths)
{
p_WriteLine("public override bool Equals(object __obj)");
using(p_CreateBlock())
{
p_WriteLine(type.Name, " __other = __obj as ", type.Name, ";");
p_WriteLine("if (__other == null) return false;");
PropertyInfo lastProperty = null;
foreach(var propertyPath in propertyPaths)
{
PropertyInfo property = propertyPath[0];
if (property == lastProperty)
continue;
lastProperty = property;
Type propertyType = property.PropertyType;
if (propertyType.IsPrimitive || propertyType.IsEnum || propertyType == typeof(string))
p_WriteLine("if(_", property.Name, " != __other._", property.Name, ") return false;");
else
if (propertyType.IsValueType)
p_WriteLine("if(!_", property.Name, ".Equals(__other._", property.Name, ")) return false;");
else
p_WriteLine("if (!object.Equals(_", property.Name, ", __other._", property.Name, ")) return false;");
}
p_WriteLine("return true;");
}
}
private void p_WriteInsertSql(string databaseName, int count, string fieldsSql)
{
p_Write("public const string InsertSql = \"INSERT INTO ", databaseName, "(", fieldsSql, ") VALUES (");
for (int i=0; i<count; i++)
p_Write("@P", i, ", ");
fStringBuilder.Length -= 2;
p_WriteLine(")\";");
}
private void p_CreateCloneConstructor(Type type, Dictionary<PropertyInfo, List<PropertyInfoPath>> properties)
{
p_Write("public ");
p_Write(type.Name);
p_Write("(global::");
p_Write(typeof(RecordMode).FullName);
p_Write(" recordMode, ");
p_Write(type.Name);
p_WriteLine(" source)");
using (p_CreateBlock())
{
p_WriteLine("if (source == null) throw new global::System.ArgumentNullException(\"source\");");
p_WriteLine("fRecordMode = recordMode;");
p_WriteLine("CopyValuesFrom(source);");
}
p_WriteLine("public void CopyValuesFrom(", type.Name, " source)");
using (p_CreateBlock())
{
foreach (var property in properties.Keys)
{
if (typeof(ICloneable).IsAssignableFrom(property.PropertyType))
p_WriteLine("if (source._", property.Name, " != null) _", property.Name, " = (", property.PropertyType, ")(((", typeof(ICloneable), ")source._", property.Name, ").Clone()); else _", property.Name, " = default(", property.PropertyType, ");");
else
{
p_Write("_");
p_Write(property.Name);
p_Write(" = source._");
p_Write(property.Name);
p_WriteLine(";");
}
if (typeof(IRecord).IsAssignableFrom(property.PropertyType))
p_WriteLine("__keyOf_", property.Name, " = source.__keyOf_", property.Name, ";");
}
}
}
private void p_CreateFromDatabaseConstructor(Type type, PropertyInfoPaths properties, Dictionary<PropertyInfo, List<PropertyInfoPath>> dictionary)
{
p_WriteLine("public ", type.Name, "(", typeof(IDatabaseConnection), " connection, object[] itemArray)");
using (p_CreateBlock())
{
p_WriteLine(typeof(LocalDatabaseManager), " local__databaseManager = ", typeof(LocalDatabaseManager), ".Value;");
p_WriteLine("fRecordMode = ", typeof(RecordMode), ".ReadOnly;");
int itemIndex = -1;
foreach(var pair in dictionary)
{
var firstProperty = pair.Key;
var paths = pair.Value;
if (paths[0].Length > 1)
{
if (firstProperty.ContainsCustomAttribute<DatabaseNullableAttribute>())
{
p_Write("if(");
int count = paths.Count;
for (int i = 1; i <= count; i++)
{
if (i > 1)
p_Write(" || ");
p_Write("itemArray[");
p_Write((i + itemIndex).ToString());
p_Write("] != global::System.DBNull.Value");
}
p_Write(") ");
}
p_Write("__keyOf_");
p_Write(firstProperty.Name);
p_Write(" = new object[]{");
int pathIndex = -1;
foreach(var path in paths)
{
pathIndex++;
itemIndex++;
var lastProperty = path[path.Length-1];
p_Write
(
"local__databaseManager.ChangeTypeFromDatabase<",
lastProperty.PropertyType,
">(connection, global::Pfz.Databasing.ImplementedRecords.",
firstProperty.DeclaringType.FullName,
".PropertyPathOf_",
firstProperty.Name,
"_",
pathIndex,
", itemArray[",
itemIndex,
"]), "
);
}
fStringBuilder.Length -= 2;
p_WriteLine("};");
}
else
{
itemIndex++;
p_WriteLine
(
"_",
firstProperty.Name,
" = local__databaseManager.ChangeTypeFromDatabase<",
firstProperty.PropertyType,
">(connection, global::Pfz.Databasing.ImplementedRecords.",
firstProperty.DeclaringType.FullName,
".PropertyOf_",
firstProperty.Name,
", itemArray[",
itemIndex,
"]);"
);
}
}
}
}
private int p_CreateFromDatabaseConstructor3(int itemIndex, PropertyInfo propertyInfo, Type propertyType, PropertyInfoPaths keyProperties)
{
foreach (var keyPropertyPath in keyProperties)
{
var keyProperty = keyPropertyPath[keyPropertyPath.Length-1];
if (typeof(IRecord).IsAssignableFrom(keyProperty.PropertyType))
{
p_Write("new object[]{");
var otherKeys = new PropertyInfoPaths(keyProperty.PropertyType, true);
itemIndex = p_CreateFromDatabaseConstructor3(itemIndex, propertyInfo, propertyType, otherKeys);
p_Write("}, ");
}
else
{
itemIndex++;
p_Write
(
"local__databaseManager.ChangeTypeFromDatabase<",
keyProperty.PropertyType,
">(global::Pfz.Databasing.ImplementedRecords.",
p_GetName(propertyInfo.DeclaringType),
".PropertyOf_",
propertyInfo.Name,
", global::Pfz.Databasing.ImplementedRecords.",
p_GetName(keyProperty.DeclaringType),
".PropertyOf_",
keyProperty.Name,
", itemArray[",
itemIndex,
"]), "
);
}
}
fStringBuilder.Length -= 2;
return itemIndex;
}
private void p_CreateBasicConstructor(Type type)
{
p_Write("public ");
p_Write(type.Name);
p_WriteLine("(){}");
}
private void p_WritePropertiesCollection(Type type, List<PropertyInfoPath> properties, string fieldName)
{
p_Write("public static readonly global::System.Collections.ObjectModel.ReadOnlyCollection<", typeof(PropertyInfoPath), "> ");
p_Write(fieldName);
p_Write(" = new global::System.Collections.ObjectModel.ReadOnlyCollection<", typeof(PropertyInfoPath), ">(new ", typeof(PropertyInfoPath), "[]{");
foreach (var propertyPath in properties)
{
p_Write("new ", typeof(PropertyInfoPath), "(new global::System.Reflection.PropertyInfo[]{");
foreach(var property in propertyPath)
{
p_Write("typeof(global::");
p_Write(p_GetName(property.DeclaringType));
p_Write(").GetProperty(\"");
p_Write(property.Name);
p_Write("\"), ");
}
fStringBuilder.Length -= 2;
p_Write("}), ");
}
fStringBuilder.Length -= 2;
p_WriteLine("});");
}
private string p_GetWhereSql(Type type, PropertyInfoPaths primaryKeyProperties, string prefix)
{
int itemIndex = -1;
StringBuilder sql = new StringBuilder();
foreach (var propertyPath in primaryKeyProperties)
{
itemIndex++;
string databaseName = propertyPath.GetDatabaseName();
sql.Append(prefix);
sql.Append(databaseName);
sql.Append("=@PW");
sql.Append(itemIndex);
sql.Append(" AND ");
}
if (itemIndex == -1)
throw new ArgumentException("Persisted type " + type.FullName + " does not have a primary key.");
sql.Length -= 5;
return sql.ToString();
}
private string p_GetFieldsSql(PropertyInfoPaths properties)
{
StringBuilder fieldsSql = new StringBuilder();
foreach (var propertyPath in properties)
{
string databaseName = propertyPath.GetDatabaseName();
fieldsSql.Append("MT.");
fieldsSql.Append(databaseName);
fieldsSql.Append(", ");
}
fieldsSql.Length -= 2;
return fieldsSql.ToString();
}
private void p_WriteReturnType(Type type)
{
if (type == typeof(void))
p_Write("void");
else
{
p_Write("global::");
p_Write(p_GetName(type));
}
}
private p_CreateBlockStruct p_CreateBlock()
{
return new p_CreateBlockStruct(this);
}
private struct p_CreateBlockStruct:
IDisposable
{
private CSharpGenerator fGenerator;
internal p_CreateBlockStruct(CSharpGenerator generator)
{
fGenerator = generator;
generator.p_WriteLine("{");
generator.fLevel ++;
}
public void Dispose()
{
fGenerator.fLevel --;
fGenerator.p_WriteLine("}");
}
}
public string Result
{
get
{
return fStringBuilder.ToString();
}
}
private string p_GetName(Type type)
{
StringBuilder result = new StringBuilder();
p_GetName2(type, result);
return result.ToString();
}
private static void p_GetName2(Type type, StringBuilder result)
{
result.Append(type.Namespace);
result.Append('.');
if (!type.IsGenericType)
result.Append(type.Name);
else
{
result.Append(type.Name.Split('`')[0]);
result.Append('<');
foreach (Type genericType in type.GetGenericArguments())
{
p_GetName2(genericType, result);
result.Append(", ");
}
result.Length -= 2;
result.Append('>');
}
}
}
}