//
// Parago Media GmbH & Co. KG, J�rgen B�urle (jbaurle@parago.de)
//
// 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.CodeDom;
using System.CodeDom.Compiler;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using System.IO;
using System.Text;
using Microsoft.CSharp;
using Microsoft.VisualBasic;
namespace Parago.DALComp.Compiler {
/// <summary>
/// The CodeGen class represents the last phase of the small compiler application
/// and generates source code in C# or VB from an AST (Abstract Syntax Tree) that
/// is built up by a parser instance. Since the compiler is not compiling to any
/// machine code (MSIL), this kind of compiler is often called a source-to-source
/// translator.
/// </summary>
public class CodeGen {
AST dal;
/// <summary>
/// Initializes a new instance of the CodeGen class when given an AST instance.
/// </summary>
public CodeGen(AST dal) {
this.dal=dal;
}
/// <summary>
/// Initializes a new instance of the CodeGen class when given a parser
/// instance that contains the AST (Abstract Syntax Tree).
/// </summary>
public CodeGen(Parser parser) {
dal=parser.DAL;
}
/// <summary>
/// Generates C# source code from the AST (Abstract Syntax Tree) built up by
/// the parser.
/// </summary>
public string GenerateSourceCodeCSharp() {
return GenerateSourceCode(new CSharpCodeProvider());
}
/// <summary>
/// Generates VB source code from the AST (Abstract Syntax Tree) built up by
/// the parser.
/// </summary>
public string GenerateSourceCodeVB() {
return GenerateSourceCode(new VBCodeProvider());
}
/// <summary>
/// Generates source code based on the passed CodeDomProvider, for instance
/// the CSharpCodeProvider for C# code generation.
/// </summary>
public string GenerateSourceCode(CodeDomProvider codeProvider) {
StringWriter writer=new StringWriter();
codeProvider.GenerateCodeFromCompileUnit(Generate(), writer, new CodeGeneratorOptions());
string source=writer.ToString();
writer.Close();
return source;
}
/// <summary>
/// Generates source code by creating a container for a CodeDOM program graph
/// and returns the CodeCompileUnit. The method will use the Abstract Syntax
/// Tree (AST) which is built up by the parser and generate DAL code that
/// targets the Microsoft SQL Server (Config:DatabaseType=MSSQL).
/// </summary>
public CodeCompileUnit Generate() {
// Create container for CodeDOM program graph
CodeCompileUnit compileUnit=new CodeCompileUnit();
try {
// If applicable replace the value '|BaseDirectory|' with the current
// directory of the running assembly (within the connection string)
// to allow database access in DALComp.Test.Console
string connectionString=dal.Settings["CONNECTIONSTRING"]
.Replace("|BaseDirectory|", Directory.GetCurrentDirectory());
// Define new namespace (Config:Namespace)
CodeNamespace namespaceUnit=new CodeNamespace(dal.Settings["NAMESPACE"]);
compileUnit.Namespaces.Add(namespaceUnit);
// Define necessary imports
namespaceUnit.Imports.Add(new CodeNamespaceImport("System"));
namespaceUnit.Imports.Add(new CodeNamespaceImport("System.Collections.Generic"));
namespaceUnit.Imports.Add(new CodeNamespaceImport("System.Data"));
namespaceUnit.Imports.Add(new CodeNamespaceImport("System.Data.SqlClient"));
// Generate private member fields (to save public property values)
// by analyzing the database table which is defined for the DALC
SqlConnection connection=new SqlConnection(connectionString);
connection.Open();
// Generate a new public accessable class for each DALC definition
// with all defined methods
foreach(DALC dalc in dal.DALCs) {
// Generate new DALC class type and add to own namespace
CodeTypeDeclaration typeUnit=new CodeTypeDeclaration(dalc.Name);
namespaceUnit.Types.Add(typeUnit);
// Generate public empty constructor method
CodeConstructor constructor=new CodeConstructor();
constructor.Attributes=MemberAttributes.Public;
typeUnit.Members.Add(constructor);
// Get schema table with column defintions for the current DALC table
DataSet schema=new DataSet();
new SqlDataAdapter(String.Format("SELECT * FROM {0}", dalc.Table), connection)
.FillSchema(schema, SchemaType.Mapped, dalc.Table);
// Generate for each column a private member field and a public
// accessable property to use
foreach(DataColumn column in schema.Tables[0].Columns) {
// Define names by checking DALC mapping definition
string name=column.ColumnName;
string nameMapped=dalc.Mapping.ContainsKey(name.ToUpper())?dalc.Mapping[name.ToUpper()]:name;
// Generate private member field with underscore plus name; define
// member field type by checking if value type and create a
// nullable of that type accordingly
CodeMemberField field=new CodeMemberField();
field.Name=String.Format("_{0}", nameMapped);
field.Type=GenerateFieldTypeReference(column.DataType);
typeUnit.Members.Add(field);
// Generate public accessable property for private member field,
// to use for instance in conjunction with ObjectDataSource
CodeMemberProperty property=new CodeMemberProperty();
property.Name=nameMapped;
property.Type=GenerateFieldTypeReference(column.DataType);
property.Attributes=MemberAttributes.Public;
property.GetStatements.Add(
new CodeMethodReturnStatement(
new CodeFieldReferenceExpression(
new CodeThisReferenceExpression(),
field.Name
)
)
);
property.SetStatements.Add(
new CodeAssignStatement(
new CodeFieldReferenceExpression(
new CodeThisReferenceExpression(),
field.Name
),
new CodePropertySetValueReferenceExpression()
)
);
typeUnit.Members.Add(property);
}
// The following code (language-independent) will be generated
// for SelectData: (CLASSNAME=typeUnit.Name)
// public static List<CLASSNAME> SelectData(string sql) {
// List<CLASSNAME> result;
// result=new List<CLASSNAME>();
// SqlConnection connection;
// SqlCommand command;
// SqlDataReader reader;
// connection=new SqlConnection("CONNECTIONSTRING")
// connection.Open();
// command=new SqlCommand(sql, connection);
// reader=command.ExecuteReader();
// while(reader.Read()) {
// CLASSNAME o;
// o=new CLASSNAME();
// if(Convert.IsDBNull(reader["ID"]))
// o.ID=null;
// else
// o.ID=(int?)reader["ID"];
// ...
// result.Add(o);
// }
// reader.Close();
// connection.Close();
// return result;
// }
CodeMemberMethod selectDataMethod=new CodeMemberMethod();
// public static List<CLASSNAME> SelectData(string sql) { ...
selectDataMethod.Name="SelectData";
selectDataMethod.Attributes=MemberAttributes.Public|MemberAttributes.Static;
selectDataMethod.ReturnType=GenerateGenericListTypeReference(typeUnit.Name);
selectDataMethod.Parameters.Add(new CodeParameterDeclarationExpression(typeof(string), "sql"));
// List<CLASSNAME> result;
selectDataMethod.Statements.Add(
new CodeVariableDeclarationStatement(
GenerateGenericListTypeReference(typeUnit.Name),
"result"
)
);
// result=new List<CLASSNAME>();
selectDataMethod.Statements.Add(
new CodeAssignStatement(
new CodeFieldReferenceExpression(null, "result"),
new CodeObjectCreateExpression(GenerateGenericListTypeReference(typeUnit.Name))
)
);
// SqlConnection connection;
// SqlCommand command;
// SqlDataReader reader;
selectDataMethod.Statements.Add(new CodeVariableDeclarationStatement(typeof(SqlConnection), "connection"));
selectDataMethod.Statements.Add(new CodeVariableDeclarationStatement(typeof(SqlCommand), "command"));
selectDataMethod.Statements.Add(new CodeVariableDeclarationStatement(typeof(SqlDataReader),"reader"));
// connection=new SqlConnection("CONNECTIONSTRING");
selectDataMethod.Statements.Add(
new CodeAssignStatement(
new CodeVariableReferenceExpression("connection"),
new CodeObjectCreateExpression(typeof(SqlConnection), new CodePrimitiveExpression(connectionString))
)
);
// connection.Open();
selectDataMethod.Statements.Add(
new CodeMethodInvokeExpression(
new CodeMethodReferenceExpression(
new CodeVariableReferenceExpression("connection"),
"Open"
)
)
);
// command=new SqlCommand(sql, connection);
selectDataMethod.Statements.Add(
new CodeAssignStatement(
new CodeFieldReferenceExpression(null, "command"),
new CodeObjectCreateExpression(
typeof(SqlCommand),
new CodeExpression[] {
new CodeVariableReferenceExpression("sql"),
new CodeVariableReferenceExpression("connection")
}
)
)
);
// reader=command.ExecuteReader();
selectDataMethod.Statements.Add(
new CodeAssignStatement(
new CodeVariableReferenceExpression("reader"),
new CodeMethodInvokeExpression(
new CodeMethodReferenceExpression(
new CodeVariableReferenceExpression("command"),
"ExecuteReader"
)
)
)
);
CodeStatementCollection whileStatements=new CodeStatementCollection();
// CLASSNAME o;
// o=new CLASSNAME();
whileStatements.Add(new CodeVariableDeclarationStatement(typeUnit.Name, "o"));
whileStatements.Add(new CodeAssignStatement(new CodeVariableReferenceExpression("o"),new CodeObjectCreateExpression(typeUnit.Name)));
// Generate code to populate object instance and add later to
// CodeIterationStatement (while body)
foreach(DataColumn column in schema.Tables[0].Columns) {
// Define names by checking DALC mapping definition
string name=column.ColumnName;
string nameMapped=dalc.Mapping.ContainsKey(name.ToUpper())?dalc.Mapping[name.ToUpper()]:name;
// if(Convert.IsDBNull(reader["ID"]))
// o.ID=null;
// else
// o.ID=(int?)reader["ID"];
whileStatements.Add(
new CodeConditionStatement(
new CodeMethodInvokeExpression(
new CodeMethodReferenceExpression(
new CodeSnippetExpression("Convert"),
"IsDBNull"
),
new CodeArrayIndexerExpression(
new CodeVariableReferenceExpression("reader"),
new CodePrimitiveExpression(name)
)
),
new CodeStatement[] {
new CodeAssignStatement(
new CodeFieldReferenceExpression(
new CodeVariableReferenceExpression("o"),
nameMapped
),
new CodePrimitiveExpression(null)
)
},
new CodeStatement[] {
new CodeAssignStatement(
new CodeFieldReferenceExpression(
new CodeVariableReferenceExpression("o"),
nameMapped
),
new CodeCastExpression(
GenerateFieldTypeReference(column.DataType),
new CodeArrayIndexerExpression(
new CodeVariableReferenceExpression("reader"),
new CodePrimitiveExpression(name)
)
)
)
}
)
);
}
// result.Add(o);
whileStatements.Add(
new CodeMethodInvokeExpression(
new CodeMethodReferenceExpression(
new CodeVariableReferenceExpression("result"),
"Add"
),
new CodeVariableReferenceExpression("o")
)
);
// Need array to add to CodeIterationStatement
CodeStatement[] whileStatementsArray=new CodeStatement[whileStatements.Count];
whileStatements.CopyTo(whileStatementsArray, 0);
// while(reader.Read()) { ...
selectDataMethod.Statements.Add(
new CodeIterationStatement(
new CodeSnippetStatement(),
new CodeMethodInvokeExpression(
new CodeMethodReferenceExpression(
new CodeVariableReferenceExpression("reader"),
"Read"
)
),
new CodeSnippetStatement(),
whileStatementsArray
)
);
// reader.Close();
// connection.Close();
selectDataMethod.Statements.Add(new CodeMethodInvokeExpression(new CodeMethodReferenceExpression(new CodeVariableReferenceExpression("reader"),"Close")));
selectDataMethod.Statements.Add(new CodeMethodInvokeExpression(new CodeMethodReferenceExpression(new CodeVariableReferenceExpression("connection"),"Close")));
// return result;
selectDataMethod.Statements.Add(
new CodeMethodReturnStatement(
new CodeFieldReferenceExpression(null, "result")
)
);
// Add SelectData method to new class
typeUnit.Members.Add(selectDataMethod);
// Generate defined functions as static methods for DALC class
foreach(Function function in dalc.Functions) {
// public static List<CLASSNAME> FUNCTIONNAME( ...
CodeMemberMethod selectMethod=new CodeMemberMethod();
selectMethod.Name=function.Name;
selectMethod.Attributes=MemberAttributes.Public|MemberAttributes.Static;
selectMethod.ReturnType=GenerateGenericListTypeReference(typeUnit.Name);
// string internalSql;
selectMethod.Statements.Add(new CodeVariableDeclarationStatement(typeof(string), "internalSql"));
StringBuilder sb1=new StringBuilder();
StringBuilder sb2=new StringBuilder();
StringBuilder sql=new StringBuilder();
sql.AppendFormat("SELECT * FROM {0}", dalc.Table);
// Add method parameters to DALC defined function and generate code to
// validate and convert input values
if(function.Parameters.Count>0) {
sql.Append(" WHERE ");
int i=1;
// int p1;
// p1=PARAMETERNAME1;
// string p2;
// if((PARAMETERNAME2==null)) {
// p2="-";
// }
// else {
// p2=PARAMETERNAME2.Replace("\'", "\'\'");
// }
// ...
// internalSql=String.Format("SELECT * FROM CLASSNAME WHERE FIELDNAME1=\'{0}\' AND FIELDNAME2=\'{1}\'", PARAMETERNAME1, PARAMETERNAME2);
CodeExpressionCollection formatParameters=new CodeExpressionCollection();
foreach(Parameter parameter in function.Parameters) {
string p=String.Format("p{0}", i);
selectMethod.Parameters.Add(new CodeParameterDeclarationExpression(parameter.Type, parameter.Name));
selectMethod.Statements.Add(new CodeVariableDeclarationStatement(parameter.Type,p));
if(parameter.Type=="System.String")
selectMethod.Statements.Add(
new CodeConditionStatement(
new CodeBinaryOperatorExpression(
new CodeVariableReferenceExpression(parameter.Name),
CodeBinaryOperatorType.ValueEquality,
new CodePrimitiveExpression()
),
new CodeStatement[] {
new CodeAssignStatement(
new CodeVariableReferenceExpression(p),
new CodePrimitiveExpression("-")
)
},
new CodeStatement[] {
new CodeAssignStatement(
new CodeVariableReferenceExpression(p),
new CodeMethodInvokeExpression(
new CodeMethodReferenceExpression(
new CodeVariableReferenceExpression(parameter.Name),
"Replace"
),
new CodeExpression[] {
new CodePrimitiveExpression("'"),
new CodePrimitiveExpression("''")
}
)
)
}
)
);
else
selectMethod.Statements.Add(
new CodeAssignStatement(
new CodeVariableReferenceExpression(p),
new CodeVariableReferenceExpression(parameter.Name)
)
);
sql.AppendFormat("{0}='{{{1}}}' AND ", parameter.Field, i-1);
formatParameters.Add(new CodeVariableReferenceExpression(p));
i++;
}
formatParameters.Insert(0, new CodePrimitiveExpression(sql.ToString().TrimEnd(" AND ".ToCharArray())));
CodeExpression[] formatParametersArray=new CodeExpression[formatParameters.Count];
formatParameters.CopyTo(formatParametersArray, 0);
selectMethod.Statements.Add(
new CodeAssignStatement(
new CodeVariableReferenceExpression("internalSql"),
new CodeMethodInvokeExpression(
new CodeMethodReferenceExpression(
new CodeSnippetExpression("String"),
"Format"
),
formatParametersArray
)
)
);
}
else
// internalSql="SELECT * FROM TABLENAME";
selectMethod.Statements.Add(
new CodeAssignStatement(
new CodeVariableReferenceExpression("internalSql"),
new CodePrimitiveExpression(sql.ToString())
)
);
// return SelectData(internalSql);
selectMethod.Statements.Add(
new CodeMethodReturnStatement(
new CodeMethodInvokeExpression(
new CodeMethodReferenceExpression(
null,
"SelectData"
),
new CodeVariableReferenceExpression("internalSql")
)
)
);
// Add DALC defined function
typeUnit.Members.Add(selectMethod);
}
}
// Close database connection and all associated managed and
// unmanaged resources
connection.Dispose();
}
catch(Exception e) {
throw new Exception(String.Format("DALComp code generation error: {0}", e.Message));
}
return compileUnit;
}
/// <summary>
/// Generates a nullable type if input type is a value type.
/// </summary>
CodeTypeReference GenerateFieldTypeReference(Type columnType) {
if(!columnType.IsValueType)
return new CodeTypeReference(columnType);
Type nullableType=typeof(Nullable<>);
return new CodeTypeReference(nullableType.MakeGenericType(new Type[] { columnType }));
}
/// <summary>
/// Generates a generic type of List with items of type passed in className.
/// </summary>
CodeTypeReference GenerateGenericListTypeReference(string className) {
return new CodeTypeReference("List", new CodeTypeReference[] { new CodeTypeReference(className) });
}
}
}