Wednesday, October 28, 2009
Do not give your email id and password to any web site unless you know for sure what you are doing
When you receive the email, delete it immediately and do not follow through it. This is a trap!
Generally, for any email/web site asking for login/password for another email account, don't follow through that and delete that email unless you know you are giving information to a well trusted email service such as google. Otherwise, you are in danger of exposing your complete email contact list to an unknown user/web site.
For example the mentioned "Desktop Dating" email, this is what the hacker site was doing:
1. in the email it ask you to input your email id (for example yahoo) and password,
2. the "desktop dating" site could use your yahoo email id and password and login to your email account and (don't be fooled by the encryption of the password that shows on the page)
3. read your emails and
4. fetch all your email contact and send the chain email to all your contacts.
5. The cycle continues back to step 1 for all your contacts and their contacts again and again unless you stop it from step 1.
If you already input your email id /password to site like that, change your password now, inform your friends do not open that email from you etc.
Sunday, October 25, 2009
Use GSON to access json response in grails action
1. download gson package and extract the jar into lib directory
http://code.google.com/p/google-gson/
2. grails action
def fetch={
def data
def reader
try {
URL url = new URL("http://localhost:8080/controller/action");
reader = new BufferedReader(new InputStreamReader(url.openStream()));
JsonParser jp = new JsonParser()
JsonElement je = jp.parse(reader)
data = je.getAt("data");
reader.close()
} catch (MalformedURLException e) {
// ...
} catch (IOException e) {
// ...
} finally {
reader.close()
}
render data;
}
Saturday, October 24, 2009
Create google wave robot from grails
2. create a grail project gswordwave
grails create-app gswordwave
3. uninstall hiberante
grails uninstall-plugin hibernate
4. install app engine plugin, selected jdo ( default)
grails install-plugin app-engine
5. run the app with this command:
grails app-engine
6. kill it
7. do initial deployment and make sure it works without wave stuff
- Register your application on appengine site. gswordwave
- grails set-version 1
- grails app-engine package
- %APP-ENGINE-HOME%/bin/appcfg.cmd update ./target/war
8. download the google wave client jars from
- http://code.google.com/p/wave-robot-java-client/downloads/list and copy all the wave client jar to the lib directory in the grails root dir, also download servlet.api.2.5.jar into lib
- mkdir src/java/parroty/server
- copy http://code.google.com/apis/wave/extensions/robots/java-tutorial.html src/parroty/ParrotyServlet.java into the above directory src/java/parroty/server and name it ParrotyServlet.java and update the package to parroty.server.
- Make a web-app/_wave dir. Create in it a capabilties.xml file and copy paste the code
<?xml version="1.0" encoding="utf-8"?>
<w:robot w="http://wave.google.com/extensions/robots/1.0">
<w:capabilities>
<w:capability name="WAVELET_PARTICIPANTS_CHANGED" content="true">
</w:capability>
<w:version>1</w:version>
</w:capabilities>
</w:robot></pre>
9. In your appengine-web.xml (mine is in
c:\users\yiguang\.grails\1.1.1\projects\gswordbible\stage)file
you will find your application id is there already(mine, "gswordwave").
<appengine-web-app xmlns="http://appengine.google.com/ns/1.0">
<application>gswordwave</application>
<version>0.1</version>
<sessions-enabled>true</sessions-enabled>
<ssl-enabled>true</ssl-enabled>
<system-properties>
</system-properties>
</appengine-web-app>
10. add your ParrotyServlet config into src\templates\war\web.xml :
<servlet>
<servlet-name>Parroty</servlet-name>
<servlet-class>parroty.server.ParrotyServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>Parroty</servlet-name>
<url-pattern>/_wave/robot/jsonrpc</url-pattern>
</servlet-mapping>
11. do redeployment
- grails app-engine package
- %APP-ENGINE-HOME%/bin/appcfg.cmd update ./target/war
12. check it out
http://gswordwave.appspot.com/_wave/capabilities.xml
13. goto your wave account and add gswordwave@appsport.com onto
your contact list and start to communicate with it
Wednesday, October 21, 2009
Pintout Unicode of a String
This is how to do it:
def thatString="sdsdsd sd fsd fsd fsd fsd fsd s ->s sd fsdf sdf "
that.toCharArray().each{ch->
printf("%04X%n", (int)ch);
}
This will printout all the unicode of the string
Monday, September 14, 2009
GWT on Google Appengine
The first part list the steps to create the first google app engine GWT applicaiton(Mostly copied from Google Instruction). The second part is more detail explanation.
1. Download Eclipse and install google app engine plugin
Eclipse 3.5 (Galileo)
http://dl.google.com/eclipse/plugin/3.5
2. Create applicaiton (areyouup)
File > New > Web Application Project (areyouup, com.asianwondersnet.areyouup)
(select workbench if not show up)
3. Running your Web Application locally
Right-click on your web application project and select Debug As > Web Application from the popup menu.
This action creates an Eclipse Web Application launch configuration for you and launches it. The web application launch configuration will start a server and the GWT hosted browser.
At this point, you can set breakpoints, inspect variables and modify code as you would normally expect from a Java Eclipse debugging session.
4. Deploying your Web Application
To deploy your web application, you will need to create an application from the App Engine Administration Console, at the following URL: https://appengine.google.com/. If you already have one, then you can skip this step.
Once you have an application ID, just right-click on your project, and select Google > App Engine Settings... from the context menu. Enter your application ID into the Application ID text box. Click OK.
Right-click on your project and select Google > Deploy to App Engine. In the resulting Deploy Project to Google App Engine dialog, enter your Google Account email and password.
Note: Don't worry - the plugin doesn't store your password anywhere.
Click Deploy.
Go to http://application-id.appspot.com/ to see your application.
The following is a little bit more detail. Here are the steps to add a new service. Using TestService as example.
1. Define TestService.java
package com.asianwondersnet.areyouup.client;
import com.google.gwt.user.client.rpc.RemoteService;
import com.google.gwt.user.client.rpc.RemoteServiceRelativePath;
@RemoteServiceRelativePath("test")
public interface TestService extends RemoteService{
String testServer(String name);
}
Please notice the relative path annotation above. We will use that information in web.xml
2. Define the TestServiceAsyn.java
package com.asianwondersnet.areyouup.client;
import com.google.gwt.user.client.rpc.AsyncCallback;
public interface TestServiceAsync {
void testServer(String input, AsyncCallback
}
Notice the above two service interface both has testServer. But there is a little bit difference. The second one has a AsyncCallback argument which we will show it in entrypoint code.
3. Define the TestServiceImpl.java which implement the TestService interface.
package com.asianwondersnet.areyouup.server;
import com.asianwondersnet.areyouup.client.TestService;
import com.google.gwt.user.server.rpc.RemoteServiceServlet;
@SuppressWarnings("serial")
public class TestServiceImpl extends RemoteServiceServlet implements TestService{
public String testServer(String input) {
StringBuffer sb=new StringBuffer();
try {
URL url = new URL(input);
BufferedReader reader = new BufferedReader(new InputStreamReader(url.openStream()));
String line;
while ((line = reader.readLine()) != null) {
sb.append(line+"\\n");
}
reader.close();
}finally{
if (sb.length()==0){
sb.append("Please input the full URL for fetching data!");
}
}
return sb.toString();
}
}
4. Add the servlet configuration in web.xml
The name: testServlet
The class: com.asianwondersnet.areyouup.server.TestServiceImpl
Add the servlet mapping to web.xml
map /areyouup/test to testServlet
The relative path test is annotated in the TestService.java interface.
Also define the hosting page as welcome-file-list: Areyouup.htmlThe hosting page was generated automatically but you can change it from the web.xml file.
5. Now the entrypoing code: Areyouup.java (This was generated automatically when the application is setup on Eclipse).
package com.asianwondersnet.areyouup.client;
import com.google.gwt.core.client.EntryPoint;
public class Areyouup implements EntryPoint{
private final TestServiceAsync testService = GWT.create(TestService.class);
//The entrypoint
public void onModuleLoad() {
//Define the buttons/textfields specify style as necessary
final Button sendButton = new Button("Send");
final TextBox nameField = new TextBox();
nameField.setText("Input URL");
// We can add style names to widgets
sendButton.addStyleName("sendButton");
// Add the nameField and sendButton to the RootPanel
// Use RootPanel.get() to get the entire body element
RootPanel.get("nameFieldContainer").add(nameField);
RootPanel.get("sendButtonContainer").add(sendButton);
// Focus the cursor on the name field when the app loads
nameField.setFocus(true);
nameField.selectAll();
// Create the popup dialog box
final DialogBox dialogBox = new DialogBox();
dialogBox.setText("Remote Procedure Call");
dialogBox.setAnimationEnabled(true);
final Button closeButton = new Button("Close");
// We can set the id of a widget by accessing its Element
closeButton.getElement().setId("closeButton");
final Label textToServerLabel = new Label();
final HTML serverResponseLabel = new HTML();
VerticalPanel dialogVPanel = new VerticalPanel();
dialogVPanel.addStyleName("dialogVPanel");
dialogVPanel.add(new HTML("Sending name to the server:"));
dialogVPanel.add(textToServerLabel);
dialogVPanel.add(new HTML("Server replies:"));
dialogVPanel.add(serverResponseLabel);
dialogVPanel.setHorizontalAlignment(VerticalPanel.ALIGN_RIGHT);
dialogVPanel.add(closeButton);
dialogBox.setWidget(dialogVPanel);
// Add a handler to close the DialogBox
closeButton.addClickHandler(new ClickHandler() {
public void onClick(ClickEvent event) {
dialogBox.hide();
sendButton.setEnabled(true);
sendButton.setFocus(true);
}
});
// Create a handler for the sendButton and nameField
class MyHandler implements ClickHandler, KeyUpHandler {
/**
* Fired when the user clicks on the sendButton.
*/
public void onClick(ClickEvent event) {
sendNameToServer();
}
/**
* Fired when the user types in the nameField.
*/
public void onKeyUp(KeyUpEvent event) {
if (event.getNativeKeyCode() == KeyCodes.KEY_ENTER) {
sendNameToServer();
}
}
/**
* Send the name from the nameField to the server and wait for a response.
*/
private void sendNameToServer() {
sendButton.setEnabled(false);
String textToServer = nameField.getText();
textToServerLabel.setText(textToServer);
serverResponseLabel.setText("");
testService.testServer(textToServer,
new AsyncCallback
public void onFailure(Throwable caught) {
// Show the RPC error message to the user
dialogBox
.setText("Remote Procedure Call - Failure");
serverResponseLabel
.addStyleName("serverResponseLabelError");
serverResponseLabel.setHTML(SERVER_ERROR);
dialogBox.center();
closeButton.setFocus(true);
}
public void onSuccess(String result) {
dialogBox.setText("Remote Procedure Call");
serverResponseLabel
.removeStyleName("serverResponseLabelError");
serverResponseLabel.setHTML(result);
dialogBox.center();
closeButton.setFocus(true);
}
});
}
}
// Add a handler to send the name to the server
MyHandler handler = new MyHandler();
sendButton.addClickHandler(handler);
nameField.addKeyUpHandler(handler);
}
}
The GWT has been simplified a bit from older versions. No more Service casting and the annotation also helps simplify coding.
6. Look at the Hosting page:
In the html header, link style "Areyouup.css" and java script src="areyouup/areyouup.nocache.js"
In the body define the field "nameFieldContainer" and "sendButtonContainer" refered in the code
Ok. Here is the play ground. It has the above function plus some more experimental image animation stuff. The URL:http://areyouup.appspot.com/
Thursday, September 10, 2009
Application development process for Google Appengine on Grails
This is the note I took while watching grocher 's Screen cast from the Grails 1.1.1 announcement
So all honor goes to grocher and the grails/groovy team.
1. grails create-app grails-music-store
2. Goto appengine.google.com, add new application grails-music-store . Note the app name must match
3. cd grails-music-store
4. grails uninstall-plugin hibernate
5. grails install-plugin app-engine
6. export APPENGINE_HOME=/Developer/appengine-java-sdk-1.2.0
7. grails app-engine
then goto 8080 and see the skeleton app
8. grails create-domain-class Album
JDO by default
9. grails generate-all com.music.Album
10. grails app-engine
On Windows, After kill the application, the java process is still running. I have to kill the java process manually. (currports is a nice tool for this process)
11. grails app-engine package
12. /Developer/appengine-java-sdk-1.2.0/bin/appcfg.sh update ./target/war
provide password //first time
It will fail. Fail because of version.
13. grails set-version 1 (This is not the app engine version)
14. do step 11-12 to build and deploy again
/Developer/appengine-java-sdk-1.2.0/bin/appcfg.sh update ./target/war
15. Check it out
http://grails-music-store.appspot.com/album/create (or list...)
Of course you need to install the app engine sdk first.
Saturday, August 15, 2009
Bible on Twitter
Each Bible verse has a story better than those novels. So I am starting to publish Bible verses on Twitter. This is my twitter with Bible verses
If you are familiar with the Bible verse, this may serve as a good reminder and a starting point of inspiration so that you can reflect on the verse.
If you are not familiar with the particular Bible verse, this may serve as a challenge for you to figure out the context of the verse by going back to the Bible and read around the verse and get familiar with it. GSword is a comprehensive Bible study tool you can use for this purpose.
Any way, this is yet another way to read Bible. I hope you find it fun and challenging.
Twitter through Grails Application
Here is a barebone twitter service code. Simply put it into your grails-app/services directory
import twitter4j.Twitter
class TwitterService {
boolean transactional = false
def update(String message) {
Twitter twitter = new Twitter("username", "password");
twitter.updateStatus(message);
}
}
Notice the twitter4j package is used here
In your controller or job, define the service
def twitterService
whereever you want to update your twitter, call the service as following:
twitterService.update("I am fine so far")
Friday, August 7, 2009
Google Spell Checker
def spellchecker(String word){
def path = "/tbproxy/spell?lang=en&hl=en";
def urlString = "https://www.google.com"+path
def url = new URL(urlString)
def connection = url.openConnection()
connection.setRequestMethod("POST")
connection.doOutput = true
def post ='<spellrequest textalreadyclipped="0" ignoredups="1" ignoredigits="1" ignoreallcaps="0"><text>'+word+'</text></spellrequest>'
def writer = new OutputStreamWriter(connection.outputStream)
writer.write(post)
writer.flush()
writer.close()
connection.connect()
println "The query:"+word
println "The response:\n"+connection.content.text
}
def x="""
The suggestions are tab-delineated. The .o. attribute is an offset from the start of your query to the misspelled word. .l. is the length of the misspelled w
ord. .s. is the confidence of Google.s suggestion (presumably higher is better, but I.ve only gotten 0 or 1).
"""
println x
spellchecker("love is patient")
spellchecker("weee nottt sxdsasd")
spellchecker("xxx")
spellchecker("toatlygrbg")
The following is the printout:
The query:love is patient
The response:
The query:weee nottt sxdsasd
The response:
The query:xxx
The response:
The query:toatlygrbg
The response:
Sunday, June 14, 2009
Java Internationalization and Localization(I18N, L10N)
This document summarizes some important Java classes and techniques for I18N development. I have made most examples to show the point directly. Many Java based web frameworks(JSF, STRUTS, GRAILS etc) have made I18N/L10N very simple, but the fundamentals behind the frameworks are still the same. So hope the blog is still relevant.
Many of the samples are adopted from the resources listed in the reference area, especially the book "Java Internationalization
Internationalization Tips
Don't assume all letters of the alphabet fall between A and Z. Use Character.
Don't hardcode strings. Use resource bundle.
Don’t concatenate strings. Use MessageFormat/ChoiceFormat.
Use InputStreamReader/OutputStreamWriter for reading/writing.
Sort using Collator/RuleBasedCollator.
Use ComponentOrientation for GUI layout.
Don't hardcode fonts.
Use NumberFormat/DecimalFormat to format numbers.
Use DateFormat/SimpleDateFormat to display dates.
Culturally Dependent Data
In developing internationalized applications, you need to identify culturally dependent data. The following lists some examples of culturally dependent data:
Messages
Labels on GUI components
Online help
Sounds
Colors
Graphics
Icons
Dates
Times
Numbers
Currencies
Measurements
Phone numbers
Honorifics and personal titles
Postal addresses
Page layouts
This document introduces some of the key java classes for developing international applications.
Locale
A Locale object represents a specific geographical, political, or cultural region. An operation that requires a Locale to perform its task is called locale-sensitive and uses the Locale to tailor information for the user.
Here are the Locale constructors:
Locale(String language, String country)
Locale(String language, String country, String variant)
For Example:
Locale ja = new Locale("ja", "JP");
ResourceBundle
ResourceBundle objects contain locale-specific objects. When you need a locale-specific object, you fetch it from a ResourceBundle.
For example, you have the following resource bundle,
Image
Image_ja_JP
Image_fr_FR
Image_zh
Each of these resource bundles is a set of related subclasses that share the same base name Image. To select the appropriate ResourceBundle, invoke the ResourceBundle.getBundle method. For example:
Locale currentLocale = new Locale("fr", "FR");
ResourceBundle myimages =
ResourceBundle.getBundle("Image", currentLocale);
To get specific object in the bundle, use get method of ResourceBundle with the key. I.e,
String menubar = myimages.getString(“MenuBar”);
ResourceBundle may be back by property files or ListResourceBunle.
Property Files
Property file contains properties defined in the format of key=values. You should always create a default properties file. The name of this file begins with the base name of your ResourceBundle and ends with .properties suffix. For example:
Image.properties
Image_ja_JP.properties
Image_fr_FR.properties
Image_zh.properties
This example show how resources bundle is created:
ResourceBundle labels =
ResourceBundle.getBundle("Image", currentLocale);
The getBundle method first looks for a class file that matches the base name and the Locale. If it can't find a class file, it then checks for properties files.
ListResourceBundle
When you have non-string object in resource bundle, you need to use ListResourceBundle. Here is a list resource bundle class example:
public class StatsBundle_ja_JP extends ListResourceBundle {
public Object[][] getContents() {
return contents;
}
private Object[][] contents = {
{ "GDP", new Integer(21300) },
{ "Population", new Integer(125449703) },
{ "Literacy", new Double(0.99) },
};
}
To create the ListResourceBundle, invoke the getBundle method.
NumberFormat
NumberFormat is the abstract base class for all number formats.
Here are some examples:
Format number:
myString = NumberFormat.getNumberInstance(yourlocale).format(myNumber);
Format Currency:
currencyFormatter = NumberFormat.getCurrencyInstance(currentLocale).format(currency);
Format Percentage:
percentFormatter = NumberFormat.getPercentInstance(currentLocale).format(percent);
Examples for NumberFormat:
-
Locale
Number
Number
Currency
Percentage
fr_FR
123 456
345 987,246
9 876 543,21 F
75%
de_DE
123.456
345.987,246
9.876.543,21 DM
75%
en_US
123,456
345,987.246
$9,876,543.21
75%
ja_JP
123,456
345,987.246
¥9,876,543
75%
zh_TW
123,456
345,987.246
NT$9,876,543.21
75%
DecimalFormat
You can use the DecimalFormat class to format decimal numbers into locale-specific strings with patterns.
You can design your own format patterns for numbers by following the rules specified by the following BNF diagram:
pattern := subpattern{;subpattern}
subpattern := {prefix}integer{.fraction}{suffix}
prefix := '\\u0000'..'\\uFFFD' - specialCharacters
suffix := '\\u0000'..'\\uFFFD' - specialCharacters
integer := '#'* '0'* '0'
fraction := '0'* '#'*
For example:
String pattern=”###.##”;
DecimalFormat myFormatter = new DecimalFormat(pattern);
String output = myFormatter.format(value);
Locale sensitive format with pattern:
NumberFormat nf = NumberFormat.getNumberInstance(currentlocale);
DecimalFormat df = (DecimalFormat)nf;
df.applyPattern(pattern);
String output = df.format(value);
Examples DecimalFormat:
-
Pattern
Output of 123456.789
Locale
###,###.###
123,456.789
en_US
###,###.###
123.456,789
de_DE
###,###.###
123 456,789
fr_FR
###,###.###
123,456.789
ja_JP
###,###.###
123,456.789
zh_TW
DecimalFormatSymbols
DecimalFormatSymbols unusualSymbols =
new DecimalFormatSymbols(currentLocale);
unusualSymbols.setDecimalSeparator('|');
unusualSymbols.setGroupingSeparator('^');
String strange = "#,##0.###";
DecimalFormat weirdFormatter = new DecimalFormat(strange, unusualSymbols);
weirdFormatter.setGroupingSize(4);
String bizarre = weirdFormatter.format(12345.678);
System.out.println(bizarre);
output: 1^2345|678
DateFormat
Format date:
Date today = new Date();
String dateOut;
DateFormat dateFormatter;
dateFormatter = DateFormat.getDateInstance(DateFormat.DEFAULT, currentLocale);
dateOut = dateFormatter.format(today);
DateFormat timeFormatter =
Format time: DateFormat.getTimeInstance(DateFormat.DEFAULT,
currentLocale);
Format both date and time:
DateFormat formatter =
DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.LONG, currentLocale);
Example: Date styles
-
Locale
Default
SHORT
MEDIUM
LONG
FULL
En_US
Jan 9, 2003
1/9/03
Jan 9, 2003
January 9, 2003
Thursday, January 9, 2003
fr_FR
9 janv. 03
09/01/03
9 janv. 03
9 janvier 2003
jeudi 9 janvier 2003
de_DE
09.01.2003
09.01.03
09.01.2003
9. Januar 2003
Donnerstag, 9. Januar 2003
ja_JP
2003/01/09
03/01/09
2003/01/09
2003/01/09
2003年1月9日
zh_TW
2003/1/9
2003/1/9
2003/1/9
2003年1月9日
2003年1月9日
Example: Time Style
-
Locale
Default
SHORT
MEDIUM
LONG
FULL
en_US
3:07:28 PM
3:07 PM
3:07:28 PM
3:07:28 PM GMT-05:00
3:07:28 PM GMT-05:00
fr_FR
15:07:28
15:07
15:07:28
15:07:28 GMT-05:00
15 h 07 GMT-05:00
de_DE
15:07:28
15:07
15:07:28
15:07:28 GMT-05:00
15.07 Uhr GMT-05:00
ja_JP
15:07:28
15:07
15:07:28
15:07:28:GMT-05:00
15時07分28秒GMT-05:00
zh_TW
下午 03:07:28
下午 3:07
下午 03:07:28
下午03時07分28秒
下午03時07分28秒 GMT-05:00
Example: Both Date and Time Style
-
Locale
Default
SHORT
MEDIUM
LONG
FULL
en_US
Dec 12, 2002 3:07:28 PM
12/12/02 3:07 PM
Dec 12, 2002 3:07:28 PM
December 12, 2002 3:07:28 PM GMT-05:00
Thursday, December 12, 2002 3:07:28 PM GMT-05:00
fr_FR
12 déc. 02 15:07:28
12/12/02 15:07
12 déc. 02 15:07:28
12 décembre 2002 15:07:28 GMT-05:00
jeudi 12 décembre 2002 15 h 07 GMT-05:00
de_DE
12.12.2002 15:07:28
12.12.02 15:07
12.12.2002 15:07:28
12. Dezember 2002 15:07:28 GMT-05:00
Donnerstag, 12. Dezember 2002 15.07 Uhr GMT-05:00
ja_JP
2002/12/12 15:07:28
02/12/12 15:07
2002/12/12 15:07:28
2002/12/12 15:07:28:GMT-05:00
2002年12月12日 15時07分28秒GMT-05:00
zh_TW
2002/12/12 下午 03:07:28
2002/12/12 下午 3:07
2002/12/12 下午 03:07:28
2002年12月12日 下午03時07分28秒
2002年12月12日 下午03時07分28秒 GMT-05:00
SimpleDateFormat
If the DateFormat does not satisfy your need, you can define your own pattern to format date use SimpleDateFormat.
Date today= new Date();
String output;
SimpleDateFormat formatter;
formatter = new SimpleDateFormat(pattern, currentLocale);
output = formatter.format(today);
System.out.println(pattern + " " + output);
formatter = new SimpleDateFormat("EEE d MMM yy", currentLocale);
output = formatter.format(today);
Examples SimpleDateFormat:
-
Locale
Pattern
Output
en_US
yyyy.MM.dd G 'at' hh:mm:ss z
2002.12.12 AD at 03:29:36 GMT-05:00
Fr_FR
yyyy.MM.dd G 'at' hh:mm:ss z
2002.12.12 ap. J.-C. at 03:29:36 GMT-05:00
de_DE
yyyy.MM.dd G 'at' hh:mm:ss z
2002.12.12 n. Chr. at 03:29:36 GMT-05:00
ja_JP
yyyy.MM.dd G 'at' hh:mm:ss z
2002.12.12 西暦 at 03:29:36 GMT-05:00
zh_TW
yyyy.MM.dd G 'at' hh:mm:ss z
2002.12.12 民國 at 03:29:36 GMT-05:00
Zh_CN
yyyy.MM.dd G 'at' hh:mm:ss z
2002.12.12 公元 at 03:40:52 GMT-05:00
MessageFormat
A compound message may contain several kinds of variables: dates, times, strings, numbers, currencies, and percentages. To format a compound message in a locale-independent manner, you construct a pattern that you apply to a MessageFormat object, and store this pattern in a ResourceBundle.
1. Identify the Variables in the Message
Suppose that you want to internationalize the following message:
Notice that we've underlined the variable data and have identified what kind of objects will represent this data.
2. Isolate the Message Pattern in a ResourceBundle
Store the message in a ResourceBundle named MessageBundle, as follows:
ResourceBundle messages =
ResourceBundle.getBundle("MessageBundle", currentLocale);
This ResourceBundle is backed by a properties file for each Locale. The properties file for U.S. English is named MessageBundle_en_US.properties. The contents of this file is as follows:
template = At {2,time,short} on {2,date,long}, we detected \
{1,number,integer} spaceships on the planet {0}.
planet = Mars
The first line of the properties file contains the message pattern. If you compare this pattern with the message text shown in step 1, you'll see that an argument enclosed in braces replaces each variable in the message text. Each argument starts with a digit called the argument number, which matches the index of an element in an Object array that holds the argument values. Note that in the pattern the argument numbers are not in any particular order. You can place the arguments anywhere in the pattern. The only requirement is that the argument number has a matching element in the array of argument values.
The next step discusses the argument value array, but first let's look at each of the arguments in the pattern. The following table provides some details about the arguments:
-
Arguments for template in MessageBundle_en_US.properties
Argument
Description
{2,time, short}
The time portion of a Date object. The short style specifies the DateFormat.SHORT formatting style.
{2,date, long}
The date portion of a Date object. The same Date object is used for both the date and time variables. In the Object array of arguments the index of the element holding the Date object is 2. (This is described in the next step.)
{1,number, integer}
A Number object, further qualified with the integer number style.
{0}
The String in the ResourceBundle that corresponds to the planet key.
3. Set the Message Arguments
The following lines of code assign values to each argument in the pattern. The indexes of the elements in the messageArguments array match the argument numbers in the pattern. For example, the Integer element at index 1 corresponds to the {1,number,integer} argument in the pattern. Because it must be translated, the String object at element 0 will be fetched from the ResourceBundle with the getString method. Here is the code that defines the array of message arguments:
Object[] messageArguments = {
messages.getString("planet"),
new Integer(7),
new Date()
};
4. Create the Formatter
Next, create a MessageFormat object. You set the Locale because the message contains Date and Number objects, which should be formatted in a locale-sensitive manner.
MessageFormat formatter = new MessageFormat("");
formatter.setLocale(currentLocale);
5. Format the Message Using the Pattern and the Arguments
This step shows how the pattern, message arguments, and formatter all work together. First, fetch the pattern String from the ResourceBundle with the getString method. The key to the pattern is template. Pass the pattern String to the formatter with the applyPattern method. Then format the message using the array of message arguments, by invoking the format method. The String returned by the format method is ready to be displayed. All of this is accomplished with just two lines of code:
formatter.applyPattern(messages.getString("template"));
String output = formatter.format(messageArguments);
Example MessageFormat:
-
Locale
Pattern
Output
en_US
template = At {2,time,short} on {2,date,long}, we detected \
{1,number,integer} spaceships on the planet {0}.
planet = Mars
At 4:16 PM on December 12, 2002, we detected 7 spaceships on the planet Mars.
de_DE
emplate = Um {2,time,short} Uhr am {2,date,long}, haben wir \
{1,number,integer} Raumschiffe auf dem planeten {0} entdeckt.
planet = Mars
Um 16:16 Uhr am 12. Dezember 2002, haben wir 7 Raumschiffe auf dem planeten Mars entdeckt.
zh
template =我们在{2,date,long}{2,time,short} 在 {0}上探测到{1,number,integer}个太空船。
planet = 火星
我们在2002年12月12日下午4:16 在 火星上探测到7个太空船。
ChoiceFormat
The words in a message may vary if both plural and singular word forms are possible. With the ChoiceFormat class, you can map a number to a word or a phrase, allowing you to construct grammatically correct messages.
1. Define the Message Pattern
First, identify the variables in the message:
Next, replace the variables in the message with arguments, creating a pattern that can be applied to a MessageFormat object:
There {0} on {1}.
The argument for the disk name, which is represented by{1}, is easy enough to deal with. You just treat it like any other String variable in a MessageFormat pattern. This argument matches the element at index 1 in the array of argument values.
Dealing with argument{0} is more complex, for a couple of reasons:
The phrase that this argument replaces varies with the number of files. To construct this phrase at run time, you need to map the number of files to a particular String. For example, the number 1 will map to the String containing the phrase is one file. The ChoiceFormat class allows you to perform the necessary mapping.
If the disk contains multiple files, the phrase includes an integer. The MessageFormat class lets you insert a number into a phrase.
2. Create a ResourceBundle
Because the message text must be translated, isolate it in a ResourceBundle:
ResourceBundle bundle =
ResourceBundle.getBundle("ChoiceBundle", currentLocale);
The sample program backs the ResourceBundle with properties files. The ChoiceBundle_en_US.properties file contains the following lines:
pattern = There {0} on {1}.
noFiles = are no files
oneFile = is one file
multipleFiles = are {2} files
The contents of this properties file show how the message will be constructed and formatted. The first line contains the pattern for MessageFormat. The other lines contain phrases that will replace argument {0} in the pattern. The phrase for the multipleFiles key contains the argument {2}, which will be replaced by a number.
Check out the following table for more examples.
3. Create a Message Formatter
In this step you instantiate MessageFormat and set its Locale:
MessageFormat messageForm = new MessageFormat("");
messageForm.setLocale(currentLocale);
4. Create a Choice Formatter
The ChoiceFormat object allows you to choose, based on a double number, a particular String. The range of double numbers, and the String objects to which they map, are specified in arrays:
double[] fileLimits = {0,1,2};
String [] fileStrings = {
bundle.getString("noFiles"),
bundle.getString("oneFile"),
bundle.getString("multipleFiles")
};
ChoiceFormat maps each element in the double array to the element in the String array that has the same index. In the sample code the 0 maps to the String returned by calling bundle.getString("noFiles"). By coincidence the index is the same as the value in the fileLimits array. If the code had set fileLimits[0] to seven, ChoiceFormat would map the number 7 to fileStrings[0].
You specify the double and String arrays when instantiating ChoiceFormat:
ChoiceFormat choiceForm = new ChoiceFormat(fileLimits, fileStrings);
5. Apply the Pattern
Remember the pattern you constructed in step 1? It's time to retrieve the pattern from the ResourceBundle and apply it to the MessageFormat object:
String pattern = bundle.getString("pattern");
messageForm.applyPattern(pattern);
6. Assign the Formats
In this step you assign to the MessageFormat object the ChoiceFormat object created in step 4:
Format[] formats = {choiceForm, null, NumberFormat.getInstance()};
messageForm.setFormatsByArgumentIndex(formats); //(After Java1.4)
or
messageForm.setFormats(formats);//This method has trouble dealing with complicated grammar.
The setFormatsByArgumentIndex (formats); method assigns Format objects to the arguments in the message pattern. You must invoke the applyPattern method before you call the setFormats method. The following table shows how the elements of the Format array correspond to the arguments in the message pattern:
-
The Format Array of the ChoiceFormatDemo Program
Array Element
Pattern Argument
choiceForm
{0}
null
{1}
NumberFormat.getInstance()
{2}
7. Set the Arguments and Format the Message
At run time the program assigns the variables to the array of arguments it passes to the MessageFormat object. The elements in the array correspond to the arguments in the pattern. For example, messageArgument[1] maps to pattern argument {1}, which is a String containing the name of the disk. In the previous step the program assigned a ChoiceFormat object to argument {0} of the pattern. Therefore the number assigned to messageArgument[0] determines which String the ChoiceFormat object selects. If messageArgument[0] is greater than or equal to 2, the String containing the phrase are {2} files replaces argument {0} in the pattern. The number assigned to messageArgument[2] will be substituted in place of pattern argument {2}. Here's the code that tries this out:
Object[] messageArguments = {null, "XDisk", null};
for (int numFiles = 0; numFiles <>
messageArguments[0] = new Integer(numFiles);
messageArguments[2] = new Integer(numFiles);
String result = messageForm.format(messageArguments);
System.out.println(result);
}
Examples ChoiceFormat:
-
Locale
Pattern
output
en_US
noFiles = are no files
oneFile = is one file
multipleFiles = are {2} files
pattern = There {0} on {1}.
disk=XDISK
There are no files on XDISK.
There is one file on XDISK.
There are 2 files on XDISK.
There are 3 files on XDISK.
fr_FR
noFiles = n' y a pas des fichiers
oneFile = y a un fichier
multipleFiles = y a {2} fichiers
pattern = Il {0} sur {1}.
disk=frdisk
Il n' y a pas des fichiers sur frdisk.
Il y a un fichier sur frdisk.
Il y a 2 fichiers sur frdisk.
Il y a 3 fichiers sur frdisk.
zh
noFiles =没有文件
oneFile = 有一个文件
multipleFiles = 有 {2} 个文件
pattern = {0}在{1}.
disk=某磁碟上
没有文件在某磁碟上.
有一个文件在某磁碟上.
有 2 个文件在某磁碟上.
有 3 个文件在某磁碟上.
Character
To check characters, use Character object method, for example:
isDigit
isLetter
isLetterOrDigit
isLowerCase
isUpperCase
isSpaceChar
isDefined
BreakIterator
The BreakIterator class implements methods for finding the location of boundaries in text. Instances of BreakIterator maintain a current position and scan over text returning the index of characters where boundaries occur.
You can analyze four kinds of boundaries with the BreakIterator class: character, word, sentence, and potential line break. When instantiating a BreakIterator, you invoke the appropriate factory method:
getCharacterInstance
getWordInstance
getSentenceInstance
getLineInstance
Each instance of BreakIterator can detect just one type of boundary.
Locale currentLocale = new Locale ("en","US");
BreakIterator wordIterator =
BreakIterator.getWordInstance(currentLocale);
String someText = "She stopped. " +
"She said, \"Hello there,\" and then went on.";
markBoundaries(someText, wordIterator);
static void markBoundaries(String target, BreakIterator iterator) {
StringBuffer markers = new StringBuffer();
markers.setLength(target.length() + 1);
for (int k = 0; k <>
markers.setCharAt(k,' ');
}
iterator.setText(target);
int boundary = iterator.first();
while (boundary != BreakIterator.DONE) {
markers.setCharAt(boundary,'^');
boundary = iterator.next();
}
System.out.println(target);
System.out.println(markers);
}
String
When you convert between String and byte array, be reminded that you are dealing with locales and provide proper encoding information for conversion.
String original = new String("A" + "\u00ea" + "\u00f1" + "\u00fc" + "C");
byte[] utf8Bytes = original.getBytes("UTF8");
String roundTrip = new String(utf8Bytes, "UTF8");
Collator
The Collator class performs locale-sensitive String comparison. You use this class to build searching and sorting routines for natural language text.
Collator fr_FRCollator = Collator.getInstance(new Locale("fr","FR"));
public static void sortStrings(Collator collator,
String[] words) {
String tmp;
for (int i = 0; i <>
for (int j = i + 1; j <>
if (collator.compare(words[i], words[j]) > 0) {
tmp = words[i];
words[i] = words[j];
words[j] = tmp;
}
}
}
}
-
Locale
Original
Sorted
fr_FR
Péché pêche peach sin
Peach pêche péché sin
en_US
Peach pêche péché sin
Peach péché pêche sin
zh
胡益光你好
光好胡你益
RuleBasedCollator
String smallnTilde = new String("\u00F1"); // ñ
String capitalNTilde = new String("\u00D1"); // Ñ
String traditionalSpanishRules =
("<>
"<>
"<>
"<>
"<>
"<>
"< " + smallnTilde + "," + capitalNTilde + " " +
"<>
"<>
"<>
RuleBasedCollator spCollator =
new RuleBasedCollator(traditionalSpanishRules);
sortStrings(spCollator, words);
public static void sortStrings(Collator collator, String[] words) {
String tmp;
for (int i = 0; i <>
for (int j = i + 1; j <>
if (collator.compare(words[i], words[j]) > 0) {
tmp = words[i];
words[i] = words[j];
words[j] = tmp;
}
}
}
}
InputStreamReader and OutputStreamWriter
InputStreamReader and OutputStreamWriter bridges byte streams and character streams. The InputStreamReader allows you to read different bytes from byte stream and convert the bytes into particular encoding character. OutputStreamWriter takes character stream data and output it to a byte stream. The characters written are converted to proper bytes according to the encoding. Failing to use proper encoding may corrupt your data.
Non-unicode byte-> InputStreamReader->Unicode character
Unicode character-> OutputStreamWriter->non-unicode byte
static String readInput() {
StringBuffer buffer = new StringBuffer();
try {
FileInputStream fis = new FileInputStream("test.txt");
InputStreamReader isr = new InputStreamReader(fis,
"UTF8");
Reader in = new BufferedReader(isr);
int ch;
while ((ch = in.read()) > -1) {
buffer.append((char)ch);
}
in.close();
return buffer.toString();
} catch (IOException e) {
e.printStackTrace();
return null;
}
}
static void writeOutput(String str) {
try {
FileOutputStream fos = new FileOutputStream("test.txt");
Writer out = new OutputStreamWriter(fos, "UTF8");
out.write(str);
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
Socket communication
To pass non-UNICODE data correctly through socket, you need to wrap up the stream of socket with InputStreamReader and OutputStreamWriter and write and read data the same way as above.
BufferedWriter out = new BufferedWriter( new OutputStreamWriter(
clientSocket.getOutputStream(), "UTF8"));
BufferedReader in = new BufferedReader(new InputStreamReader(
clientSocket.getInputStream(),"UTF8"));
ComponentOrientation
The ComponentOrientation class encapsulates the language-sensitive orientation that is to be used to order the elements of a component or of text.
For different locale, you need to setup ComponentOrientation and apply it to each component such as menu, items etc.
ComponentOrientation Co;
If (Chinese) {
Locale.setDefault(Locale.CHINA);
Co=ComponentOrientation.LEFT_TO_RIGHT;
}
If (Hebrew){
Locale.setDefault(new Locale(“iw”,”IL”);
Co=ComponentOrientation.RIGHT_TO_LEFT;
}
applyComponentOrientation(Frame frm, Co);
applyComponentOrientation(Component c, ComponentOrientation co){
c.setComponentOrientation(co);
applyComponentOrientation(EachComponent of c,co);
}
Internationalized web applications
For a internationalized web application, the same server need to response to request from different locales properly. This section describes how the client and server are communicating the locale information.
1. The client sends the preferred locale information in the Accept-Language header.
2. ServletRequest provide two methods to parse the Accept-Language header: getLocale() and getLocales().
3. ServletResponse has two methods, getLocale() and setLocale(Locale loc) to get and set the locale of the response to client.
4. There are two ways to set the character set in servlet response. Browser uses the charset information to render the page with proper font.
4.1 One way is to call response.setContentType(“text/html”, charset=ISO-8859-1”);
4.2 The second way is to use response.setLocale(Locale loc).
The second method takes a Locale object and set the appropriate headers in the response to the client. The second method is preferred since you may not know the exact charset name of a given locale. Even if you know the exact charset name, you would still need to maintain that list and manage the mapping between locale and charset name if you use the first method.
The character set must be set using either setContentType or setLocale before constructing PrintWriter object by calling ServletResponse.getWriter(); the call to getWriter() uses the charset tag to construct PrintWriter object.
JSP Custom Tags
JSP custom tags are a very powerful mechanism for performing complex processing that removes java code from the JSP pages while encapsulating nontrivial programming logic in a reusable component, which can be packaged in tag library. There are many open source tag libraries on inter-net including an I18N tag library that we can take advantage. To use tag lib, you need to configure the tag lib and then refer to it in JSP pages similar to normal html tags. You can also develop your own tags and package them into tag lib.
FAQ
Why I saw square blocks on my screen where I supposed to see MBCS characters?
This may be because you are using the wrong font. You need to install the necessary font on your client machine. If you see square blocks on applet, you need to use the right font.properties file for the java VM.
Why I saw ‘?’ marks on my screen or my output file where I supposed to see MBCS characters?
When you convert characters from one encoding EC1 to another EC2 while the EC2 may not have a character or has more than one character that maps to the character in EC1, the result is unknown character-the ‘?’ mark. The following is a small list of possible scenario:
When you read the file, you didn’t use InputStreamReader with proper encoding.
When you write the file, you didn’t use OutputStreamWriter with proper encoding.
When you convert a String to byte array, you didn’t use the proper encoding. Or you may not specify encoding at all and the default encoding doesn’t match your String.
When you convert byte array to String, you didn’t use the proper encoding. Or you may not specify encoding at all and the default encoding doesn’t match your original String encoding.
When you convert characters from one encoding EC1 to another EC2 while the EC2 may have more than one character that maps to the character in EC1. For example, one simplified Chinese character may map to more than one Traditional Chinese Character.
Reference
Java Internationalization by Andrew Deitsch & David Czarnecki from O’Reilly