September 2005 - Posts

Learn to optimize code - Having conversation with Derek in writing efficient C codes

Earlier evening, I had a conversation with Derek, a friend of mine from MIND. I knew he was very good
in C/C++, so I dropped him a message over MSN. I wondered he could share with me some tips on writing
efficient C codes. 
I am looking forward on others for sharing their views as well. 
Below is the comments from the conversation (I did not modified much, to maintain the originality of the
message)
1)
BAD:
for(int i = 0; i < 100; i++)
j += i * 2;
GOOD:
for (int i = 0; i < 200; i += 2)
j += i;
2)
BAD:
Example, in string concatenation, normally what people will do is to code as below;
a = b + c + "Test" + d;
Behind the scene, it will work as below;
i) (b+c) will create a temporary string, let say we named it Z
ii) that become a = Z + "Test" + d;
iii) then (Z + "Test") will have another temporary call Y, which become 
a = Y + d;
iv) (Y + d) will have another X.
v) so in the end is a = X;
vi) so far u have now 3 temporary created
vii) after that "a = X;" statement, all the temporary will be destroyed.
GOOD:
instead of "a = b + c + "Test" + d;"
You can achieve the same result with below:

a += b;
a += c;
a += "Test"; 
a += d;
This concept is similar to StringBuilder in C#, which is a recommended way for string concatenation.
Another reason is to overcome the String immutable attribute. 
Example, if you have this:

String a = "Test";
a += "ing";
The string object the text "Test" will still in memory. but is unreferenced, marked for delete.
meanwhile the 'a' will reference a new string object "Testing"
well if u want speed, often create/delete will not be good
btw, the 'mark for delete'... as far as i know, we can't determine when the VM will starts the 
deletion.
so your memory will remain there until the VM starts the cleanup. which might happen when program 
ends, or when almost out of memory.
 
3)
BAD:
for (int i = 0; i < 100; i++)
{
  for (int j = 0; j < 50; j++)
  {
    somevalue = i + j;
  }
}
GOOD:
the inner-j loop, can use 
N*(1+N)/2
which become:

for (int i = 0; i < 100; ++i)
{
    x = i + (49*(1+49)/2);
}
BETTER:
well... since the (49*(1+49)/2) is constant, u can:

int j = 49;
int J = (j*(1+j)/2);

for (int i = 0; i < 100; ++i)
{

   X = i + J;
}
BEST (NO FOR LOOP):
int i = 100;  // for readability
int j = 50;
int J = ((j-1)*j/2);
int I = ((i-1)*i/2);
X = I + (i * J);
4)
well, just to note that "++i" is as fast or faster than "i++"
5)
You can also see it as when u have 1 million record, u need to travel 1 million iteration for linklist.
I mean, searching in linklist is an O[N] operation.
Highlights:
O[xxxx] is a complexity notation.
O[1] means constant time
O[N] means like single level loop
O[N^2] means like double loop
N -> usually denotes a very large number.
Besides the gain u got from array, which is O[1] random access, but searching is still O[N]. so not good 
Then now is binary tree, binary tree have O[logN] search operation. the log is based-2. or in math is Ln.
so u can see, if for searching, binary tree wins all. but for insertion/deletion, it is entirely 
different case lor,
For binary tree, for every insertion and deletion, the tree need to do compare/search so that 
new data is insert in proper place... or rearrange the tree to fix the deletion so that the tree is 
balanced
Whereas for linklist, u just freely insert an entry either in the head, tail or middle. 
linklist have O[1] insert/delete.
Thank you.
Cheers.
 

Asp.Net - Super Curious on AJAX Approach 1

I tested AJAX .NET library before this, but I will like to know how you can do AJAX without 
that library.
ASP.NET is an event driven approach and it supports postback. Sometimes I am frustrated with 
ASP.NET (PHP or JSP as well), as I need to refresh the page every time I click the button. 
Luckily Microsoft is working on with Atlas framework and there are few free libraries that 
enable you to do AJAX easier.
AJAX is actually not new, but it started to be very popular when Google uses this. Maybe Google 
is popular among us. [Correct me if I am wrong :)]
In order to implement AJAX within Internet Explorer environment, you can take a look at the MSXML 
parser XMLHTTP class. This class is the key to implement AJAX model within Internet Explorer. But, 
I am not so sure whether this works on other browsers.
Sample Code:
// <HEAD><SCRIPT> 
obj = new ActiveXObject("Msxml2.XMLHTTP");
obj = new ActiveXObject("Microsoft.XMLHTTP");
// </SCRIPT></HEAD>
I am also wondering; can AJAX works at the same time with PostBack? Any idea?
In order to work with AJAX, you need to be strong in JavaScript. :(  Okay, I didn't use JavaScript 
for some time. 
Posted by chuawenching with 1 comment(s)
Filed under:

Asp.Net - Export DataGrid to CSV File Format

I was refreshing my ASP.NET knowledge and saw this article located here 
http://www.dotnetbips.com/386541cb-7e0f-491f-b86a-f9bc8be738b8.aspx?articleid=302
It was quite interesting. While learning it, I made a conversion from vb.net to C#.
I just curious, will CSV format still available in Office 12? I heard there were some new 
formats coming out soon.
Check the screenshots available here:
http://community.sgdotnet.org/photos/chuawenching/category1106/picture21385.aspx
http://community.sgdotnet.org/photos/chuawenching/category1106/picture21386.aspx
http://community.sgdotnet.org/photos/chuawenching/category1106/picture21387.aspx
http://community.sgdotnet.org/photos/chuawenching/category1106/picture21388.aspx
Snippets Code
CSVHelper.cs
/// <summary>
/// Export Helper Function - DataGrid to CSV
/// Converted from this article 
/// http://www.dotnetbips.com/386541cb-7e0f-491f-b86a-f9bc8be738b8.aspx?articleid=302
/// </summary>
/// <param name="ds"></param>
/// <param name="exportColumnHeadings"></param>
/// <returns></returns>
public string Export(DataSet ds, bool exportColumnHeadings)
{
string header = string.Empty;
string body = string.Empty;
string record = string.Empty;
// If you want column to be part of the CSV ...
if (exportColumnHeadings)
{
foreach (DataColumn col in ds.Tables[0].Columns)
{
header = header + (char)34 + col.ColumnName + (char)34 + ",";
}
header = header.Substring(0, header.Length - 1);
}
// Iterate into the rows
foreach (DataRow row in ds.Tables[0].Rows)
{
Object[] arr = row.ItemArray;
for (int i = 0; i < arr.Length - 1; i++)
{
if (arr[i].ToString().IndexOf(",") > 0)
{
record = record + (char)34 + arr[i].ToString() + (char)34 + ",";
}
else
{
record = record + arr[i].ToString() + ",";
}
}
body = body + record.Substring(0, record.Length - 1) + Environment.NewLine;
record = "";
}
if (exportColumnHeadings)
{
return (header + Environment.NewLine + body);
}
else
{
return body;
}
}
Export.aspx.cs
private void Page_Load(object sender, System.EventArgs e)
{
// Put user code to initialize the page here
if (!IsPostBack)
{
BindGrid();
}
}
 
// Retrieve the customer information and bind into the datagrid
void BindGrid()
{
SqlDataAdapter da = new SqlDataAdapter("SELECT * FROM Customers", "data source=wenching;initial catalog=northwind;user id=sa; password=Passw0rd");
DataSet ds = new DataSet();
da.Fill(ds, "customers");
Session["myds"] = ds;
dgResults.DataSource = ds;
dgResults.DataBind();
}
 
private void btnExport_Click(object sender, System.EventArgs e)
{
DataSet ds = (DataSet)Session["myds"];
CSVHelper csv = new CSVHelper();
string strData = csv.Export(ds, chbExport.Checked);
byte[] data = ASCIIEncoding.ASCII.GetBytes(strData);
Response.Clear();
// Set as Excel as the primary format
Response.AddHeader("Content-Type", "application/Excel");
// Save the output as customer.csv
Response.AddHeader("Content-Disposition", "inline;filename=customer.csv");
Response.BinaryWrite(data);
Response.End();
}
Anyway thanks to Bipin Joshi for the nice article :)
Posted by chuawenching with 1 comment(s)
Filed under:

Asp.Net - Trying out with NUnitASP 1

I found out this to be interesting. So I gave it a try.
You can download NUnitASP from here http://nunitasp.sourceforge.net/index.html. 
Basically it extends from NUnit Framework.
In order to use NUnitASP for your asp.net unit testing, you need both NUnitASP and 
NUnit to be available on your machine.
I created a simple website here 
http://community.sgdotnet.org/photos/chuawenching/category1106/picture21368.aspx
The tutorial in the original website is quite helpful, but I fail to get something to 
work. More to research. If anyone know how to use this, please let me know yeah :)
[Test]
public void TestPassingArrayOfUsers()
{
RegisterNow("wenching", "haha", "haha");
// How to get this to work?
string [][] users = new string [][]
{
new string [] { "ultraman", "ultra", "ultra" },
new string [] { "spiderman", "spider", "spider" }
};
AssertEquals("dgResults", users, regResults.TrimmedCells);
}
Error which I obtained from NUnit-GUI:
SimpleRegisterTests.RegisterTest.TestPassingArrayOfUsers : dgResults
expected:
   {
      {"ultraman", "ultra", "ultra"}
      {"spiderman", "spider", "spider"}
   }
 but was:
   {
      {"wenching", "haha", "haha"}
   }
And if i removed this line of code - RegisterNow("wenching", "haha", "haha");
I will get this error:
SimpleRegisterTests.RegisterTest.TestPassingArrayOfUsers : 
NUnit.Extensions.Asp.HtmlTag+ElementNotVisibleException : Couldn't find 'dgResults' on ''
I think this error makes sense since that datagrid is not loaded until there is a data inserted to it.  
Anyway, I had attached my full source code here:
RegisterTest.cs
using System;
using NUnit.Framework;
using NUnit.Extensions.Asp;
using NUnit.Extensions.Asp.AspTester;
namespace SimpleRegisterTests
{
[TestFixture]
public class RegisterTest : WebFormTestCase
{
private TextBoxTester regName;
private TextBoxTester regPassword;
private TextBoxTester regNewPassword;
private ButtonTester regSave;
private ButtonTester regCancel;
private DataGridTester regResults;
public RegisterTest()
{
//
// TODO: Add constructor logic here
//
}
// Notice:
// NUnitASP already uses the [SetUp] attribute, so you are only allowed
// to use this way
// Purpose:
// Load this before running any test - a way of refactoring
protected override void SetUp()
{
regName = new TextBoxTester("txtName", CurrentWebForm);
regPassword = new TextBoxTester("txtPassword", CurrentWebForm);
regNewPassword = new TextBoxTester("txtNewPassword", CurrentWebForm);
regSave = new ButtonTester("btnSave", CurrentWebForm);
regCancel = new ButtonTester("btnCancel", CurrentWebForm);
regResults = new DataGridTester("dgResults", CurrentWebForm);
// Check the URL exists or not
Browser.GetPage("http://localhost/SimpleASPTest/Register.aspx");
}
[Test]
public void TestVisibiltyControls()
{
AssertVisibility(regName, true);
AssertVisibility(regPassword, true);
AssertVisibility(regNewPassword, true);
AssertVisibility(regSave, true);
AssertVisibility(regCancel, true);
// Cannot be true, as you need to enter data before the datagrid
// can display anything
// Error: SimpleRegisterTests.RegisterTest.TestVisibiltyControls : 
// dgResults control should be visible (HTML ID: dgResults; ASP 
// location: DataGridTester 'dgResults' in web form 'Form1')
AssertVisibility(regResults, false);
}
[Test]
public void TestSaveNewUser()
{
RegisterNow("wenching", "haha", "haha");
RegisterNow("nicholas", "necoders", "necoders");
// The Compare Validator is not supported as it is not 
// checked during click event
// RegisterNow("jason", "huh", "heh");
}
[Test]
public void TestPassingArrayOfUsers()
{
//RegisterNow("wenching", "haha", "haha");
string [][] users = new string [][]
{
new string [] { "ultraman", "ultra", "ultra" },
new string [] { "spiderman", "spider", "spider" }
};
AssertEquals("dgResults", users, regResults.TrimmedCells);
}
[Test]
public void TestCancel()
{
// Call the Cancel OnClick event
regCancel.Click();
// Make sure it empties the text boxes
AssertEquals("txtName", "", regName.Text);
AssertEquals("txtPassword", "", regPassword.Text);
AssertEquals("txtNewPasssword", "", regNewPassword.Text);
}
private void RegisterNow(string strName, string strPassword, string strNewPassword)
{
regName.Text = strName;
regPassword.Text = strPassword;
regNewPassword.Text = strNewPassword;
// Manually have to add OnClick event in the html section
regSave.Click();
}
}
}
Register.aspx.cs
using System;
using System.Collections;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Web;
using System.Web.SessionState;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.HtmlControls;
namespace SimpleASPTest
{
/// <summary>
/// Summary description for WebForm1.
/// </summary>
public class Register : System.Web.UI.Page
{
protected System.Web.UI.WebControls.Button btnSave;
protected System.Web.UI.WebControls.Button btnCancel;
protected System.Web.UI.WebControls.TextBox txtNewPassword;
protected System.Web.UI.WebControls.DataGrid dgResults;
protected System.Web.UI.WebControls.TextBox txtName;
protected System.Web.UI.WebControls.Label lblOutput;
protected System.Web.UI.WebControls.CompareValidator cvPassword;
protected System.Web.UI.WebControls.TextBox txtPassword;
 
private void Page_Load(object sender, System.EventArgs e)
{
// Put user code to initialize the page here
}
#region Web Form Designer generated code
override protected void OnInit(EventArgs e)
{
//
// CODEGEN: This call is required by the ASP.NET Web Form Designer.
//
InitializeComponent();
base.OnInit(e);
}
 
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{ 
this.btnSave.Click += new System.EventHandler(this.btnSave_Click);
this.btnCancel.Click += new System.EventHandler(this.btnCancel_Click);
this.Load += new System.EventHandler(this.Page_Load);
}
#endregion
// cannot be private here
protected void btnSave_Click(object sender, System.EventArgs e)
{
DataTable resultsTable = (DataTable) Session["Registration"];
if (resultsTable == null)
{
resultsTable = new DataTable();
resultsTable.Columns.Add(new DataColumn("Name", typeof(string)));
resultsTable.Columns.Add(new DataColumn("Password", typeof(string)));
resultsTable.Columns.Add(new DataColumn("New Password", typeof(string)));
}
DataRow resultsRow = resultsTable.NewRow();
resultsRow["Name"] = txtName.Text;
resultsRow["Password"] = txtPassword.Text;
resultsRow["New Password"] = txtNewPassword.Text;
resultsTable.Rows.Add(resultsRow);
dgResults.DataSource = resultsTable;
dgResults.DataBind();
Session["Registration"] = resultsTable;
txtName.Text = "";
txtPassword.Text = "";
txtNewPassword.Text = "";
}
// cannot be private here
protected void btnCancel_Click(object sender, System.EventArgs e)
{
lblOutput.Text = "Registration Cancelled";
txtName.Text = "";
txtPassword.Text = "";
txtNewPassword.Text = "";
}
}
}
Posted by chuawenching with no comments

20 September 2005 - unlucky day

Yesterday 20 September 2005, was a day to be remembered.
1. My yahoo email was hacked. Maybe people were jealous about me. I faced this before 
in MIND forum. 
2. I stucked in the lift for 1 hour after I went back home from work.
Geez, see the pictures below. I just beginning to do asp.net in the lift. LOL.
http://community.sgdotnet.org/photos/chuawenching/category1149/picture21317.aspx
http://community.sgdotnet.org/photos/chuawenching/category1149/picture21318.aspx
Posted by chuawenching with no comments
Filed under:

Notice: Someone hijack my yahoo email.

Dear all,
   Last Friday, my email account was hacked by an annoymous. I found out only today by a friend.
  An annoymous was able to read my emails (chuawenching@yahoo.com) and replied back with 
(mavitz@yahoo.com) with my name "eric chua wen ching".
  I am really sorry about this. 
  If possible, you can reach me on another email at wenching.chua@hotmail.com as well or call 
me at +60126787879.
Thank you so much and sorry for the inconveniences.
Regards,
Chua Wen Ching
Posted by chuawenching with no comments
Filed under:

Security - need to know all :(

I received a critic today from my friend in APIIT. I was in APIIT to surf internet. LOL. 
He told me, that I should know all about Security and then focused in 1 particular area. 
Right now I am on the opposite side, I only focus entirely on cryptography and I do not know 
about other securities issues. It is kind of embarrassing.
What do you all think? Hmm…
Posted by chuawenching with 2 comment(s)
Filed under:

Test a C# exe without the source code

I has writen a simple illustration as below, in order to test a simple C# program 
that accepts input and perform factorial calculation to it. I am hoping to hear feedback from you.
I will try to be specific and explain in steps by steps. 
 
General Assumption 1: this is a C# console application called Fac.exe
General Assumption 2: only on .NET Framework 1.1 and Visual Studio .NET 2003
 
There are 2 possible ways which I can think of at the moment.
 
1) 
 
Assumption 1: There is a public method in this application. You can check it through .NET Reflector by Lutz Roeder.
 
  1. Rename Fac.exe to Fac.dll
  2. Create a C# console application and add references to nunit.framework and Fac.dll
  3. Optional: You can use object browser in Visual Studio .NET 2003 to look into Fac.dll for available information.
  4. Place [TestFixture] attribute at the main public class.
  5. Create a new void method called TestFacMethod. Place a [Test] attribute above this method.
  6. Place your assertion within this method to check the factorial function.
  7. Build this project, and run the nunit-gui program.
  8. You can test it.
 
A simple snippet of the test elaborates above:
 
            [Test]
            public void TestThis()
            {
// Assuming Calc is the class for Fac.exe, and Fac(long input) is the public static method
                  long result = Calc.Fac(5);
                  Assert.AreNotEqual(0, result);
            }
 
2)
 
Assumption 2: What if there is no public method, which will be difficult to use NUnit?
 
  1. Basically you can write a simple C# Console application that utilizes the System.Diagnostics.Process to create a new process to run Fac.exe.
  2. You can pass the argument you want to Fac.exe
  3. You can redirect the standard output and error into a string (or a text file) to be displayed later at the console.  
 
A simple snippet to elaborate as above:
 
                  Process mProcess = new Process();
                  mProcess.StartInfo.FileName = "Fac.exe";
                  mProcess.StartInfo.UseShellExecute = false;
                  mProcess.StartInfo.CreateNoWindow = true;
                  mProcess.StartInfo.RedirectStandardInput = true;
                  mProcess.StartInfo.RedirectStandardOutput = true;
                  mProcess.StartInfo.RedirectStandardError = true;
                  mProcess.Start();
 
                  StreamWriter input = mProcess.StandardInput;
                  StreamReader output = mProcess.StandardOutput;
                  StreamReader error = mProcess.StandardError;
 
                  input.Write("5" + System.Environment.NewLine);
 
                  input.Write("exit" + System.Environment.NewLine);
 
                  string sOutput = output.ReadToEnd();
                  string sError = error.ReadToEnd();
 
                  if (!mProcess.HasExited)
                        mProcess.Kill();
 
                  Console.WriteLine("Exited Code: " + mProcess.ExitCode);
 
                  input.Close();
                  output.Close();
                  error.Close();
                  mProcess.Close();
 
                  // Display at the console
                  Console.WriteLine("[Start] " + sOutput + " [End]" + System.Environment.NewLine);
                  Console.WriteLine("[Start] " + sError + " [End]" + System.Environment.NewLine);
Cheers.
Posted by chuawenching with 4 comment(s)
Filed under:

Wow - I have my own Wen Ching google logo

Check this out

http://google.topmasala.com/ggl.asp?lo=Wen+Ching 

Thanks to Poh Sze. She created for me and let me aware of such thing.

Create your new logo here ...

http://google.topmasala.com/

Cheers.

Posted by chuawenching with no comments
Filed under:

BlogSearch Beta by Google

Another interesting tool by Google
http://blogsearch.google.com/
Cool :) 
Posted by chuawenching with no comments
Filed under:

Received certificate for Imagine Cup 2005 Malaysia

Yeah, finally I had received my Imagine Cup 2005 finalist certificate.
The certificate looked quite nice, if compare to any competitions I ever joined before.
Cool!
Check the certificate as below:
http://community.sgdotnet.org/photos/chuawenching/category1105/picture20824.aspx
http://community.sgdotnet.org/photos/chuawenching/category1105/picture20825.aspx
Posted by chuawenching with 2 comment(s)
Filed under:

End of ACM 2005 - Fatty Crab

Recently, I was busy with ACM 2005. I need to be there to monitor a group of 
promoters in demonstrating Sapura M2 (pronounce as M Square), a new mobile 
payment system that utilizes voice for data transmission. It was pretty cool 
technology from Europe. Anyway I will be away for 1 week on holiday. :) 
I took a picture with a promoter.
http://community.sgdotnet.org/photos/chuawenching/category1149/picture20815.aspx - 
I didn't notice she stands so close to me :) 
I will post another blog on ACM 2005. Earlier today 6.00 p.m, William treated 
all of us (Sapura, RHB and the 2 promoters) in Fatty Crab.
William did ordered a lot of dishes. I really surrendered at the first 15 minutes. 
Angie and Siew Tee are great eater. Angie finished her law studies and Siew Tee will 
finish her Finance studies very soon.
Check the pictures out as below:
http://community.sgdotnet.org/photos/chuawenching/category1149/picture20816.aspx
http://community.sgdotnet.org/photos/chuawenching/category1149/picture20817.aspx
http://community.sgdotnet.org/photos/chuawenching/category1149/picture20818.aspx
http://community.sgdotnet.org/photos/chuawenching/category1149/picture20819.aspx
http://community.sgdotnet.org/photos/chuawenching/category1149/picture20820.aspx
http://community.sgdotnet.org/photos/chuawenching/category1149/picture20821.aspx
Posted by chuawenching with 2 comment(s)

My birthday - Thanks for all the wishes :)

My girlfriend treated me Irish Lamb Stew for dinner in Secret Recipe. I forgot to 
take a snap shot of that dish. Oops! I was not sure why the food tasted so different? Maybe I was 
sick for 4 days and lost my appetite.
Later, I had a small party with my girlfriend at her place on Sept 4 12.00 a.m. sharp. 
I made a birthday wish ...secret :) 
After my birthday, I felt myself older. Now I am 23 years old. I must learn to think 
like an adult. No way :( 
Then I (Sept 4) combined with Nicholas (Sept 5) (my other good friend) for our simple 
birthday session yesterday. 
We celebrated at Genki Sushi and then had supper in SS2 Sri Murni Mamak till 12.10 a.m 
just to wish Nicholas, Happy Birthday.
I had attached some pictures here. Check them out.  
http://community.sgdotnet.org/photos/chuawenching/category1149/picture20605.aspx
http://community.sgdotnet.org/photos/chuawenching/category1149/picture20606.aspx
http://community.sgdotnet.org/photos/chuawenching/category1149/picture20607.aspx
http://community.sgdotnet.org/photos/chuawenching/category1149/picture20608.aspx
http://community.sgdotnet.org/photos/chuawenching/category1149/picture20609.aspx
http://community.sgdotnet.org/photos/chuawenching/category1149/picture20610.aspx
Posted by chuawenching with no comments

My E-Book's progress - slow but focus

I hope you all will not get me wrong by not blogging anything technically at the moment. 
Actually I am progressing slowly on my e-book. 
I did not post any update for time being as I want to study deeply on a particular 
cryptography before writing anything. 
I had a conversation with Wehyong couple of days ago, and he had suggested me to look 
into "Q" cryptography. It was indeed interesting. I had added it into my E-Book's cryptography 
wish list. 
I am spending a lot of time researching obfuscation, Reflection and IL parsing. 
Thank you.
Posted by chuawenching with no comments
Filed under:

Lunch at Regent Hotel

Wow. I had lunch with my colleagues earlier today in Regent Hotel, Malaysia.
The meals were so expensive. Just imagine nasi goreng costed around RM 26.00.
See this (quite blur):
http://community.sgdotnet.org/photos/chuawenching/picture20493.aspx
I ordered spaghetti. I actually dislike tomato sauce spaghetti but all the meals 
were so expensive.
http://community.sgdotnet.org/photos/chuawenching/picture20494.aspx
Mike was enjoying his food - tempura (not too sure the exact name). I believed his 
meal costed around RM 42.00. Wow!
http://community.sgdotnet.org/photos/chuawenching/picture20495.aspx
Just look careful at the tempura, you can see the soft shell crab. I miss it this time.
http://community.sgdotnet.org/photos/chuawenching/picture20496.aspx 
Posted by chuawenching with no comments