Friday, November 07, 2008
Games: Little Big Planet
I won't bore you with the details about "Sackboy" since the main character and his possible customizations are covered very well in depth by the many gaming rags on the market. I will say he is cute and clever, as is the control scheme for him. I can't help myself but to have him disco dance ala Saturday Night Fever after every level.
The stock levels showcase the games physics and themes really well. It has a very animatronic theme going, with characters being voiced by little speakers, and movements being done by something right out of a Rube Goldberg invention. They are challenging, with a very silly backstory behind many of the different levels.
Once the gremlins with the network, be in on my end or Sonys end finally cleared up and I was able to update, I got to see where LBP really shines. User created content always ends up enhancing a game, and showcasing possibilities that the developers couldn't imagine. In this case there is no exception. I played around with user levels, some of which were mere mechanisms to earn trophies, but others were clever, challenging platformers that brought me back to the yesteryears of the NES. I had one level where I was doing a mock prison break, another where I was going through the American Gladiators obstacle course, and one where I played basketball while wearing a rocketpack.
So, those are the facts, so whats my opinion. I can openly say this, and all naysayers be damned. This is the first game where I have actually smiled and enjoyed playing. I haven't even gotten into level creation, just playing, and this is easily one of the most enjoyable gaming experiences I have had in a long time. Finally, something that breaks away from the tired first person shooters, dystopian futures, survival horrors, and the getting very thin rhythm games (although I did pick up Gears of War 2, Guitar Hero World Tour, and Rock Band 2, so apparently I still have some demand for these games). Easily the best game for the PS3, and I look forward to seeing what else the community can cook up.
Thursday, November 06, 2008
GWT: Calling GWT Classes and Methods from Regular Javascript
My current project involves us working with a group who uses a custom built web framework that works on top of Dojo. Since I prefer to use GWT, even for strictly client side Javascript due to the debugger, I needed something that would allow me to create a somewhat functional Javascript based library. Out of the box, doing so in GWT is a bit of a pain, involving the need to create native JSNI mappings to the GWT methods. This is ugly, and one of the reasons I like GWT is because the code can be much prettier in development, even if it is not in deployment. I would prefer a much more efficient way, where the creation of the publically exposed methods are hidden for the most part during the development. This would normally take some function of the compiler to do this, but the clever folks behind the GWT Chronoscope project figured out a way to do this using Generators. They were even nice enough to release the fruits of that work free of charge.
Now, I will give you fair warning, download the sample code from the SVN repository. The documentation for this is non-existent, and the only way you can figure anything out is by looking at the samples. Here I will try to give you a good idea how to implement this in your own code by using a real simple proof of concept.
In the following example, I am going to create a new GWT module that will expose a simple Hello World return to a DOM object whose ID will be passed in. I will be doing so in a GWT project in Eclipse, and my runtime stuff will be done using the Instantiations GWT Designer, although you can do this in any GWT IDE you like. This will be compiled against GWT 1.5.2.
The first thing you need to do is add the library for GWT-Exporter into your projects classpath. In my case, I am using the Alpha release for version 2.
Once added, I create a simple module with a blank onModuleLoad. Normally in the onModuleLoad, we want to load and prepare out Ajax based stuff, but in this case, I don’t want anything yet, I will add a little code to jumpstart the export process in a bit. First, I want to create a class that looks like so:
package com.digiassn.blogspot.client;
import org.timepedia.exporter.client.Export;
import org.timepedia.exporter.client.ExportPackage;
import org.timepedia.exporter.client.Exportable;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Window;
@Export
@ExportPackage("helloworld")
public class UpdateWithHelloWorld implements Exportable {
@Export
public void UpdateMyDomObject(String id)
{
DOM.getElementById(id).setInnerHTML("Hello World");
}
}
Also note the annotations for package and Export. This tells the generator that this class is to be exported, and that it is accessible by that package name.
With that class there is a slight problem. The Exportable class is not set up properly in the GWT Modules XML file. So I need to modify my ExporterExample.gwt.xml file to contain the Exportable class. Be sure to add in the option to export, otherwise it won't export your classes.
<module>
<inherits name="com.google.gwt.user.User"/>
<inherits name='com.google.gwt.user.theme.standard.Standard'/>
<entry-point class="com.digiassn.blogspot.client.ExporterExample"/>
<inherits name="org.timepedia.exporter.Exporter"/>
<set-property name="export" value="yes"/>
</module>
package com.digiassn.blogspot.client;
import org.timepedia.exporter.client.Exporter;
import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.core.client.GWT;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.ui.Button;
/**
* Entry point classes define <code>onModuleLoad()</code>.
*/
public class ExporterExample implements EntryPoint {
public void onModuleLoad() {
try {
Exporter export = (Exporter)GWT.create(UpdateWithHelloWorld.class);
export.export();
} catch (Exception e) {
Window.alert("Exception " + e.toString());
}
}
}
For now, I want to open my public HTML page, and create two things, the DIV tag that will be updated, and a small Javascript method that will call my GWT class. I use the following as a small test.
<html>
<head>
<title>Wrapper HTML for ExporterExample</title>
<link type="text/css" rel='stylesheet' href='ExporterExample.css'/>
<script language="javascript" src="com.digiassn.blogspot.ExporterExample.nocache.js"></script>
<script language="javascript">
function updateText()
{
var hello = new helloworld.UpdateWithHelloWorld();
hello.UpdateMyDomObject("updateMe");
}
</script>
</head>
<body>
<div id="updateMe">I show nothing right now</div>
<input type="button" onclick="updateText();">
</body>
</html>
And whala, when I click on my button, it will update my DOM code using the GWT. This demonstrates that a GWT based class and method are called from and outside Javascript.
You got to hand it to the guys over at Timepedia, this is a pretty cool little library. Now it is possible for me to do any Javascript based development, debugging, and re-use of components with my development in GWT for reliability and cross-browser support, and use in non-GWT based calls.
Thursday, October 30, 2008
General: Using VI to Insert String Before a Line and Append Strings After a Line
:1,$s/ ^ /drop table /
:1,$s/$ / ; /
Strange, I’ve been using VI for years, and never needed to do that before. I am so used to my small set of commands, I never bothered to expand out and learn everything else that VI could do.
Here is a list of sites that have useful VI commands, shortcuts, and tutorials:
http://andrewfraser.wordpress.com/2007/01/12/useful-simple-vi-commands-for-dbas/
http://www.viemu.com/vi-vim-cheat-sheet.gif
http://tnerual.eriogerg.free.fr/vimqrc.pdf
http://www.eng.hawaii.edu/Tutor/vi.html#modes
Sunday, October 26, 2008
ASM: Revised Chekerboard program, now with flashing squares
I revised by checkerboard ASM program to have the white colored squares fade in and out. I wanted to do this for a few reasons. It shows a palette shift, which I have demonstrated previously, but it also shows how to do a time delay in ASM. The time delay turned out to be a little trickier than I thought. I tried using the DOS Get Time interrupt, but if you don’t test all the registers it returns and try to get away with just the DX register, you end up with unacceptably long delays when minutes change or hours change. I ended up using the system BIOS call to get the system clock ticks. The same problem will arise after midnight, but that can easily be overcome by testing the appropriate flag which gets set when midnight occurs. Again, I have included comments, and I also included the addresses that DOS Debug displays. If I do anymore ASM programs, I thing I will use an assembler going forward. Its easier than having to specify jumps by address, which change when I add something new to a program.
--initialize video mode to 320x200x256
13F2:0100 B81300 MOV AX,0013
13F2:0103 CD10 INT 10
point data segment and extra segment to video memory
13F2:0105 B800A0 MOV AX,A000
13F2:0108 8ED8 MOV DS,AX
13F2:010A 8EC0 MOV ES,AX
13F2:010C 31FF XOR DI,DI
--initialize registers with values of palette to use
--use word instead of bytes for the stosw instruction
13F2:010E B80F0F MOV AX,0F0F
13F2:0111 BB0000 MOV BX,0000
--initialize to the number of veritical squares
13F2:0114 B90800 MOV CX,0008
13F2:0117 51 PUSH CX
--start execution, logic is explained in previous post
13F2:0118 B9C800 MOV CX,00C8
13F2:011B 51 PUSH CX
13F2:011C B91400 MOV CX,0014
13F2:011F F3 REPZ
13F2:0120 AB STOSW
13F2:0121 50 PUSH AX
13F2:0122 89D8 MOV AX,BX
13F2:0124 5B POP BX
13F2:0125 59 POP CX
13F2:0126 E2F3 LOOP 011B
13F2:0128 50 PUSH AX
13F2:0129 89D8 MOV AX,BX
13F2:012B 5B POP BX
13F2:012C 59 POP CX
13F2:012D E2E8 LOOP 0117
--get ready to ramp down the white palette entry
--do this 3fh times
13F2:012F B93F00 MOV CX,003F
13F2:0132 B3FF MOV BL,FF
13F2:0134 FECB DEC BL
--initialize with the port to tell video card we are changing
--a pallete entry. This should be changed at some point to
--actually retrieve the pallete entry to change
13F2:0136 BAC803 MOV DX,03C8
13F2:0139 B00F MOV AL,0F
13F2:013B EE OUT DX,AL
--increment dx by 1, to point to the port to actually
--set a pallette entry
13F2:013C 42 INC DX
13F2:013D 88D8 MOV AL,BL
--output the new pallette entries
13F2:013F EE OUT DX,AL
13F2:0140 EE OUT DX,AL
13F2:0141 EE OUT DX,AL
--we need a slight pause so this is effect is visible, otherwise it
--just looks like ugly staticy squares, so save our registers
13F2:0142 50 PUSH AX
13F2:0143 53 PUSH BX
13F2:0144 51 PUSH CX
13F2:0145 52 PUSH DX
--we want to test this by a single clock tick, 1/18 of a second, which
--we will use DI to compare against
13F2:0146 BF0100 MOV DI,0001
--call int 1ah, function 00, which will get a clock tick, to initialize
--out testing value and set in BX
13F2:0149 B400 MOV AH,00
13F2:014B CD1A INT 1A
13F2:014D 89D3 MOV BX,DX
--cal again to get current value
13F2:014F CD1A INT 1A
--subtract stored value from current value. If it is greater, than we have
--surpassed our delay time, and we need to keep moving, otherwise, test again
13F2:0151 29DA SUB DX,BX
13F2:0153 39D7 CMP DI,DX
13F2:0155 77F8 JA 014F
--restore registers
13F2:0157 5A POP DX
13F2:0158 59 POP CX
13F2:0159 5B POP BX
13F2:015A 58 POP AX
--loop back to palette shift beginning
13F2:015B E2D7 LOOP 0134
--do the same thing as the palette shift above, but in reverse order, ramping
--the values up
13F2:015D B93F00 MOV CX,003F
13F2:0160 FEC3 INC BL
13F2:0162 BAC803 MOV DX,03C8
13F2:0165 B00F MOV AL,0F
13F2:0167 EE OUT DX,AL
13F2:0168 42 INC DX
13F2:0169 88D8 MOV AL,BL
13F2:016B EE OUT DX,AL
13F2:016C EE OUT DX,AL
13F2:016D EE OUT DX,AL
13F2:016E 50 PUSH AX
13F2:016F 53 PUSH BX
13F2:0170 51 PUSH CX
13F2:0171 52 PUSH DX
13F2:0172 BF0100 MOV DI,0001
13F2:0175 B400 MOV AH,00
13F2:0177 CD1A INT 1A
13F2:0179 89D3 MOV BX,DX
13F2:017B CD1A INT 1A
13F2:017D 29DA SUB DX,BX
13F2:017F 39D7 CMP DI,DX
13F2:0181 77F8 JA 017B
13F2:0183 5A POP DX
13F2:0184 59 POP CX
13F2:0185 5B POP BX
13F2:0186 58 POP AX
13F2:0187 E2D7 LOOP 0160
--check if there is a key press. If not, keep going, otherwise, stop
--running the program.
13F2:0189 B406 MOV AH,06
13F2:018B B2FF MOV DL,FF
13F2:018D CD21 INT 21
13F2:018F 749E JZ 012F
--reset video mode
13F2:0191 B80300 MOV AX,0003
13F2:0194 CD10 INT 10
--return to DOS
13F2:0196 B8004C MOV AX,4C00
13F2:0199 CD21 INT 21
Friday, October 24, 2008
Java: Multi-Part Form Request using Apache Commons HttpClient
I previously wrote about how to do a simple HTTPUrlConnection request to a web page. This has served me well for quite some time. However, I ran into an issue on a recent project where I needed to do POST request and do a Multi-Part form to post large XML feeds to a service an a web appliance. This is where using HTTPUrlConnection broke down for my needs. While it is possible to do a Multi-Part POST using these classes, it turned out to be more trouble than it was worth.
This is where the Apache Commons HttpClient libraries came into play. Using the more robust Apache Commons libraries, I was easily able to throw together a Multi-Part form POST. There is one slight caveat when using HttpClient, you will need to include the Apache Commons Logging and Apache Commons Codec libraries in your classpath. Even if you do not use them in your implementation, HttpClient apparently uses them internally.
Below is a simplified example of using the HttpClient libraries to do a Multi-Part form POST. I commented out the sections relating to Cookies and posting Files since I was using XML strings.
package com.digiassn.blogspot;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpException;
import org.apache.commons.httpclient.HttpStatus;
import org.apache.commons.httpclient.methods.PostMethod;
import org.apache.commons.httpclient.methods.multipart.ByteArrayPartSource;
import org.apache.commons.httpclient.methods.multipart.FilePart;
import org.apache.commons.httpclient.methods.multipart.MultipartRequestEntity;
import org.apache.commons.httpclient.methods.multipart.Part;
import org.apache.commons.httpclient.methods.multipart.StringPart;
import org.apache.commons.httpclient.params.HttpMethodParams;
public class ApacheHttpClientRequest {
private URL serverAddress;
private String multiPart;
private String result;
private String originalURL;
/**
* Main constructor
*
* @param urlString
* @param queryString
* @param mainReferenceIn
*/
public ApacheHttpClientRequest(String urlString)
{
try {
//This is only used to test the URL as a valid URL, serverAddress is not actually used anywhere
serverAddress = new URL(urlString);
originalURL = urlString;
} catch (MalformedURLException e) {
e.printStackTrace();
}
}
/**
* This method will connect to server, and return the results of the push
* You must set Query first, which is the contents of your XML file
*/
private void executeQuery()
{
PostMethod filePost = null;
HttpClient client = null;
try {
//create new post method, and set parameters
filePost = new PostMethod(originalURL);
filePost.getParams().setBooleanParameter(HttpMethodParams.USE_EXPECT_CONTINUE,
true);
if ((multiPart.equals("")) || (multiPart == null))
{
throw new Exception("Query is null");
}
//create bytearray multi-part source from the query
ByteArrayPartSource targetFile = new ByteArrayPartSource("data", multiPart.getBytes());
//instead of a ByteArrayPartSource, the following could just as easily be used
//to include a file
//File f = new File("");
//FilePartSource fileSource = new FilePartSource(f);
Part[] parts = {
new StringPart("firstParameter", "value"),
new StringPart("secondParameter", "value"),
new FilePart("data", targetFile)
};
//Create the multi-part request
filePost.setRequestEntity(
new MultipartRequestEntity(parts, filePost.getParams())
);
//connect to server
client = new HttpClient();
client.getHttpConnectionManager().getParams().setConnectionTimeout(5000);
//the Cookie Handler, if your using manual cookies
//filePost.getParams().setCookiePolicy(CookiePolicy.IGNORE_COOKIES);
//filePost.setRequestHeader("Cookie", "name=value");
//execute and return status
int status = client.executeMethod(filePost);
if (status == HttpStatus.SC_OK) {
System.out.println("Upload success");
} else {
System.out.println("Upload failed: " + HttpStatus.getStatusText(status));
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (HttpException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
catch (Exception e)
{
e.printStackTrace();
}
finally
{
//set objects to null and garbage collect, not normally necessary but
//some issues popped up in the multi-threaded environment with memory consumption if this
//was not included
filePost = null;
client = null;
//force garbage collection. Expect to take a hit here in performance, may not be necessary
Runtime r = Runtime.getRuntime();
r.gc();
}
}
//below here are the gets and sets
public void setMultiPartString(String query) {
this.multiPart = query;
}
public String getResult() {
return result;
}
public URL getServerAddress() {
return serverAddress;
}
public void setServerAddress(URL serverAddress) {
this.serverAddress = serverAddress;
}
}
Thursday, October 23, 2008
ASM: Drawing a Checkerboard
The comments don't belong, I just added them so it is easier to follow. The following was done using DOS Debug.
--Initialize to 320*200*256 color mode
MOV AX,0013
INT 10
--Point the data and extra segments to the video memory directly and clear the DI register
MOV AX,A000
MOV DS,AX
MOV ES,AX
XOR DI,DI
--Set AX to the first pallete entry (using 2 byte for the STOSW instructions instead of one)
--Set BX to second one. We will swap these after our checkerboard rows are done
MOV AX,0F0F
MOV BX,0000
--initialize CX, our outmost loop, to the number of rows to draw
MOV CX,0008
PUSH CX
--set CX, the middle loop, set to the number of times we need to draw a line pattern for the square
--The way it works is like this, there are 8 pattern breaks in a line (black white alternations).
--we need to repeat that alternation 25 times in order to make a single increment of that alteration
--on the veritcal plane, so 25 * 8 = 200
MOV CX,00C8
PUSH CX
--set Cx to the number of horizontal pixels in a square, which is 20 (320 / 8) = 160, which
--is the 320 / 2 since we are using a word instruction, not a byte instruction
MOV CX,0014
--copy what is in AX to video memory 20 times
REPZ
STOSW
--swap the pallete bytes
PUSH AX
MOV AX,BX
POP BX
--get the value of our middle loop
POP CX
LOOP 011B
--we are at the end of a single verital iteration, swap the pallete entries and go again, until
--the outtermost loop is at 0
PUSH AX
MOV AX,BX
POP BX
POP CX
LOOP 0117
--wait for keypress
MOV AH,10
INT 16
--reset video mode
MOV AX,0003
INT 10
--return to dos
MOV AX,4C00
INT 21
Java: Simple HTTPUrlConnection example
package com.digiassn.blogspot;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.ProtocolException;
import java.net.URL;
public class SimpleHTTPRequest {
/**
* @param args
*/
public static void main(String[] args) {
HttpURLConnection connection = null;
OutputStreamWriter wr = null;
BufferedReader rd = null;
StringBuilder sb = null;
String line = null;
URL serverAddress = null;
try {
serverAddress = new URL("http://localhost");
//set up out communications stuff
connection = null;
//Set up the initial connection
connection = (HttpURLConnection)serverAddress.openConnection();
connection.setRequestMethod("GET");
connection.setDoOutput(true);
connection.setReadTimeout(10000);
connection.connect();
//get the output stream writer and write the output to the server
//not needed in this example
//wr = new OutputStreamWriter(connection.getOutputStream());
//wr.write("");
//wr.flush();
//read the result from the server
rd = new BufferedReader(new InputStreamReader(connection.getInputStream()));
sb = new StringBuilder();
while ((line = rd.readLine()) != null)
{
sb.append(line + '\n');
}
System.out.println(sb.toString());
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (ProtocolException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
finally
{
//close the connection, set all objects to null
connection.disconnect();
rd = null;
sb = null;
wr = null;
connection = null;
}
}
}
Next post will show how to use the Apache HTTPClient to establish a Multi-Part form POST request.
Monday, September 29, 2008
OSGi: JAXB with OSGi
But I ran into some issues with the project I was working on. All of these issues had to do with Classloaders in the OSGI framework not being compatible with whatever packages I was working with. One such case was with JAXB.
Fortunatly, I came across the DynamicJava website. These guys seem dedicated to creating OSGi Launchers and JSR API's that are compatible with OSGi. Neat trick, and they work too. However, to get these to work within Eclipse is another story all together.
First, download the following packages from their site:
- STAX
- JPA
- JAXB
- org.dynamicjava.jsr.jaxb_api
- org.dynamicjava.jsr.jpa_api
- org.dynamicjava.jsr.stax_api
For the STAX package, the only modification that needed to be made was in the build.properties file. It should read like so:
source.. = src/
output.. = bin/
bin.includes = META-INF/,\
.
Be sure to include the dot on the line for bin.includes. Eclipse did a really strange thing where it kept losing it for me.
I did end up needing to make a few changes for the JPA_API plugin. The MANIFEST.MF read as follows when I was done:
Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-SymbolicName: org.dynamicjava.jsr.jpa_api
Bundle-Version: 1.0.0
Bundle-Activator: javax.persistence.internal.Activator
Bundle-Name: DynamicJava.org: JSR: JPA-API
Export-Package: javax.persistence;version="1.0.0",
javax.persistence.internal,
javax.persistence.internal.classloading_utils,
javax.persistence.internal.support,
javax.persistence.spi;version="1.0.0"
Import-Package: org.osgi.framework
Require-Bundle: org.eclipse.osgi
The Build.properties was exactly the same as the STAX_API.
If both projects are building correctly in Eclipse, then the final part will be to get JAXB working. This one really proved to be a big PITA. First, download the geronimo-activation_1.1_spec-1.0.2.jar. This jar is available inside of the test program that the Dynamic Java guys provide. The JPA plugin should contain this, however I had issues putting this into the JPA plugin, so I ended up putting it into the JAXB plugin (which I may change at some point). Download the jar and put it into the /lib folder in the JAXB plugin. You will also need the jaxb-impl.jar file, available in the same archive.
From there, the Manifest.MF for the JAXB project should read like so:
Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-SymbolicName: org.dynamicjava.jsr.jaxb_api
Bundle-Version: 1.0.0
Bundle-Activator: javax.xml.bind.internal.Activator
Bundle-Name: DynamicJava.org: JSR: JAXB-API
Export-Package: com.sun.xml.bind,
com.sun.xml.bind.v2,
javax.activation,
javax.xml.bind;version="2.1.0",
javax.xml.bind.annotation;version="2.1.0",
javax.xml.bind.annotation.adapters;version="2.1.0",
javax.xml.bind.attachment;version="2.1.0",
javax.xml.bind.helpers;version="2.1.0",
javax.xml.bind.util;version="2.1.0"
Import-Package: javax.xml.namespace,
javax.xml.stream;version="1.0.0",
javax.xml.transform
Bundle-License: http://www.opensource.org/licenses/cddl1.php
Require-Bundle: org.dynamicjava.jsr.stax_api,
org.dynamicjava.jsr.jpa_api,
org.eclipse.osgi
Bundle-ClassPath: lib/jaxb-impl.jar,
.,
lib/geronimo-activation_1.1_spec-1.0.2.jar
The Build.properties files needs to be modified to read like so:
source.. = src/
output.. = bin/
bin.includes = META-INF/,\
.,\
lib/jaxb-impl.jar,\
lib/geronimo-activation_1.1_spec-1.0.2.jar
Now, you can use JAXB from within OSGi.
Monday, August 11, 2008
BIRT: Launch a BIRT RCP Application through Java Web Start
I followed, for the most part, the instructions from the following sites:
http://www.onjava.com/pub/a/onjava/2006/07/26/deploying-birt.html?page=4
I did have to modify this slightly since both of these articles were a little out of date.
First step is to create the RCP application with BIRT capable of running my report.
- Create a new Eclipse Plug-In project.
- Name the Eclipse project BIRTRCPViewer, and keep the default option.
- In the next screen, set that this project will make contributions to the UI, and that it is an RCP Application.
- Specify that this will be a RCP application with a view. You can click Finish at this point.
- In the Plugin Editor, go over to the Dependencies tab. Add in the org.eclipse.birt.core and org.eclipse.birt.report.engine packages to dependencies, and in the Imported Packages, add in the oda.jdbc, oda.sampledb, and emitter.html packages. If you plan to use other formats besides HTML for your final report output, add them here.
- Open up the View.java file and modify it to have a simple Browser element embedded in a fill layout. (Code will be displayed at the end of the next step)
- Now, we need to start the BIRT engine, and render a report. I have created a simple report called test.rptdesign, and stored it on my local Apache web server, located at localhost:8080/birt/test.rptdesign. I put all the BIRT startup and render code in the createPartControl method, and set it so that the results will be displayed in the Browser control. The finished code for the View.java is as follows:
package rcptest;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.net.URL;
import java.util.HashMap;
import org.eclipse.birt.core.framework.Platform;
import org.eclipse.birt.report.engine.api.EngineConfig;
import org.eclipse.birt.report.engine.api.EngineConstants;
import org.eclipse.birt.report.engine.api.EngineException;
import org.eclipse.birt.report.engine.api.HTMLRenderContext;
import org.eclipse.birt.report.engine.api.HTMLRenderOption;
import org.eclipse.birt.report.engine.api.IRenderOption;
import org.eclipse.birt.report.engine.api.IReportEngine;
import org.eclipse.birt.report.engine.api.IReportEngineFactory;
import org.eclipse.birt.report.engine.api.IReportRunnable;
import org.eclipse.birt.report.engine.api.IRunAndRenderTask;
import org.eclipse.swt.SWT;
import org.eclipse.swt.browser.Browser;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.ui.part.ViewPart;
public class View extends ViewPart {
public static final String ID = "RCPTest.view";
private Browser browser;
/**
* This is a callback that will allow us to create the viewer and initialize
* it.
*/
public void createPartControl(Composite parent) {
browser = new Browser(parent, SWT.NONE);
try {
this.previewReport();
} catch (EngineException e) {
e.printStackTrace();
}
}
private void previewReport()
throws EngineException {
EngineConfig config = new EngineConfig();
// Create the report engine
IReportEngineFactory factory =
(IReportEngineFactory) Platform
.createFactoryObject(
IReportEngineFactory.
EXTENSION_REPORT_ENGINE_FACTORY );
IReportEngine engine = factory.
createReportEngine( config );
IReportRunnable design = null;
try {
// Open a report design -
// use design to
// modify design, retrieve
// embedded images etc.
String report = "http://localhost:8080/birt/test.rptdesign";
URL url = new URL(report);
InputStream fs = url.openStream();
design = engine.openReportDesign(fs);
IRunAndRenderTask task = engine.
createRunAndRenderTask(design);
// Set Render context to handle url
// and image locations
HTMLRenderContext renderContext =
new HTMLRenderContext();
renderContext.setImageDirectory(
"c:/test/image");
HashMap< String, HTMLRenderContext >
contextMap = new
HashMap< String, HTMLRenderContext >();
contextMap.put( EngineConstants.
APPCONTEXT_HTML_RENDER_CONTEXT,
renderContext );
task.setAppContext(contextMap);
// Set rendering options -
// such as file or stream output,
// output format, whether it is
// embeddable, etc
// Render report to
// Byte Array
IRenderOption options;
options = new HTMLRenderOption( );
ByteArrayOutputStream bos =
new ByteArrayOutputStream();
options.setOutputStream(bos);
options.setOutputFormat("html");
task.setRenderOption(options);
// run the report and destroy the engine
task.run();
task.close();
//set Browser text accordingly
browser.setText(bos.toString());
engine.destroy();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* Passing the focus request to the viewer's control.
*/
public void setFocus() {
//do nothing
}
} - From the Plug-In editor, under the Overview tab, select Launch Eclipse application to confirm that the application works. As you can see from the screenshot, we now have a simple application with a brower with our BIRT results.
- Right-mouse click on the BIRTRCPViewer project, go to Other, and under the Plug-In projects, select Product Configuration.
- Name your product configuration BIRTRCPViewer.product, or whatever you named your project, and base it off of the launch configuration that we just created. In my screenshot below, I based it off of RCPTest.product launch configuration.
- In the Product Configuration Dialog, make sure you have the Product Name and Product ID filled in. Otherwise, when you go to launch the application in Web Start, OSGi will complain about not being able to find a Product ID.
Now, feel free to test launch with the Product Configuration to make sure your application works. Once completed, we will now need to make a “wrapper” for the Java Web Start launch. For the most part, I followed the instructions in the above link. But to be absolute, here are the steps I followed to create the Web Start application.
- Create a new Feature Project
- Name your Feature Project, and be sure to put in a Feature Provider.
- On the next screen, set to initialize from Launch Configuration.
- Create a folder under the new Feature product called Startup.
- Copy C:\eclipse\plugins\org.eclipse.equinox.launcher_
.jar to your projects Startup folder, and rename it startup.jar. This took me a few steps to figure out since in newer version of Eclipse, they did away with the startup.jar in favor of the Equinox launcher. - Under the Overview tab of the Feature product, click on the Export Wizard link.
- Specify your output folder.
- Under the Options tab, check Package as individual JAR archives (required for JNLP and update sites.
- Under the Jar signing tab, enter the relevant information. For better instructions than I can provide on how to set up the Java Keystore, check here: http://www.exampledepot.com/egs/java.security.cert/CreateCert.html
- In the Java Web Start tab, put in the Site URL where you will store your application and the JRE version.
- Now, export your wrapper. If all went according to plan, you should have a structure similar to below
- We are missing 1 thing in this file structure, the JNLP file that needs to launch the whole thing. Below is my final JNLP based off of the example in the web start link at the top of the article.
<?xml version="1.0" encoding="UTF-8"?>
<jnlp
spec="1.0+"
codebase="http://localhost:8080/birt"
href="/birt/startup.jnlp"> <!-- URL to the site containing the jnlp application. It should match the value used on export. Href, the name of this file -->
<information>
<!-- user readable name of the application -->
<title> BIRT RCP Viewer </title>
<!-- vendor name -->
<vendor>digiassn.blogspot.com</vendor>
<!-- vendor homepage -->
<homepage href="My company website" />
<!-- product description -->
<description>This is a RCP BIRT Viewer</description>
<icon kind="splash" href="splash.gif"/>
<offline-allowed/>
</information>
<!--request all permissions from the application. This does not change-->
<security>
<all-permissions/>
</security>
<!-- The name of the main class to execute. This does not change-->
<application-desc main-class="org.eclipse.equinox.launcher.WebStartMain">
<argument>-nosplash</argument>
</application-desc>
<resources>
<!-- Reference to the startup.jar. This does not change -->
<jar href="startup.jar"/>
<!-- Reference to all the plugins and features constituting the application -->
<!-- Here we are referring to the wrapper feature since it transitively refers to all the other plug-ins necessary -->
<extension
name="Wrapper feature"
href="/birt/features/MyRCP_1.0.0.jnlp"/>
<!-- Information usually specified in the config.ini -->
<property
name="osgi.instance.area"
value="@user.home/Application Data/birt"/>
<property
name="osgi.configuration.area"
value="@user.home/Application Data/birt"/>
<!-- The id of the product to run, like found in the overview page of the product editor -->
<property
name="eclipse.product"
value="RCPTest.product"/>
</resources>
<!-- Indicate on a platform basis which JRE to use -->
<resources os="Mac">
<j2se version="1.5+" java-vm-args="-XstartOnFirstThread"/>
</resources>
<resources os="Windows">
<j2se version="1.4+"/>
</resources>
<resources os="Linux">
<j2se version="1.4+"/>
</resources>
</jnlp> - Copy everything to the /birt folder on Apache Tomcat.
And that’s it, now, I can go to my web server at http://localhost:8080/birt/startup.jnlp and launch my new RCP application via Java Web Start.
Saturday, July 12, 2008
BIRT: Reading Chart From Library and Adding to Report Design
One of the ideas we came up with was to read chart prototypes from a library and to add them using the Report Engine API to their final report design. There are several advantages to this. First on, of course, is that this gives them the ability to graphically build the visual attributes of their charts using the BIRT Report Designer, and the ability to rapidly change elements and have them propagate to any report consuming them. This will keep chart look and feel consistent throughout their site. But with any approach, pros and cons need to be taken into consideration. In this case, there were a few hurdles to overcome. First being that since their data is build dynamically through their report wizard, data binding to the chart cannot be done in the report library and would need to be done at run-time, as would any chart series formatting. This differs from my previous example in that it actually adds to a report, not just rendering a stand-alone chart.
The following example of how to read a chart from a Library design, and add the data binding to it. There are a few things to notate about this example. First, if you look at my previous Chart Engine API example, you will notice that data is bound differently. When you work with the Chart Engine API in a standalone fashion, you have to manually create what is called a Data Set (not to be confused with a BIRT Report Data Set). A Data Set in the Chart Engine API is basically an array of values bound to an axis, there are no report binding elements involved. When adding to a report, you have to create a dataset binding to the ExtendedReportItem, not the chart itself.
package com.digiassn.blogspot.birt.chartBuilder;
import java.io.IOException;
import java.util.HashMap;
import java.util.Locale;
import org.eclipse.birt.chart.model.ChartWithoutAxes;
import org.eclipse.birt.chart.model.component.Series;
import org.eclipse.birt.chart.model.component.impl.SeriesImpl;
import org.eclipse.birt.chart.model.data.Query;
import org.eclipse.birt.chart.model.data.SeriesDefinition;
import org.eclipse.birt.chart.model.data.impl.QueryImpl;
import org.eclipse.birt.chart.model.data.impl.SeriesDefinitionImpl;
import org.eclipse.birt.chart.model.type.PieSeries;
import org.eclipse.birt.chart.model.type.impl.PieSeriesImpl;
import org.eclipse.birt.core.exception.BirtException;
import org.eclipse.birt.core.framework.Platform;
import org.eclipse.birt.core.framework.PlatformConfig;
import org.eclipse.birt.report.engine.api.EngineConfig;
import org.eclipse.birt.report.engine.api.EngineConstants;
import org.eclipse.birt.report.engine.api.EngineException;
import org.eclipse.birt.report.engine.api.HTMLRenderContext;
import org.eclipse.birt.report.engine.api.HTMLRenderOption;
import org.eclipse.birt.report.engine.api.IRenderTask;
import org.eclipse.birt.report.engine.api.IReportDocument;
import org.eclipse.birt.report.engine.api.IReportEngine;
import org.eclipse.birt.report.engine.api.IReportEngineFactory;
import org.eclipse.birt.report.engine.api.IReportRunnable;
import org.eclipse.birt.report.engine.api.IRunTask;
import org.eclipse.birt.report.model.api.DataSetHandle;
import org.eclipse.birt.report.model.api.DataSourceHandle;
import org.eclipse.birt.report.model.api.DesignConfig;
import org.eclipse.birt.report.model.api.DesignElementHandle;
import org.eclipse.birt.report.model.api.DesignFileException;
import org.eclipse.birt.report.model.api.ElementFactory;
import org.eclipse.birt.report.model.api.ExtendedItemHandle;
import org.eclipse.birt.report.model.api.IDesignEngine;
import org.eclipse.birt.report.model.api.IDesignEngineFactory;
import org.eclipse.birt.report.model.api.LabelHandle;
import org.eclipse.birt.report.model.api.LibraryHandle;
import org.eclipse.birt.report.model.api.PropertyHandle;
import org.eclipse.birt.report.model.api.ReportDesignHandle;
import org.eclipse.birt.report.model.api.SessionHandle;
import org.eclipse.birt.report.model.api.activity.SemanticException;
import org.eclipse.birt.report.model.api.command.ContentException;
import org.eclipse.birt.report.model.api.command.NameException;
import org.eclipse.birt.report.model.api.elements.structures.ComputedColumn;
import com.ibm.icu.util.ULocale;
public class ChartBuilder7 {
private static String BIRT_HOME = "C:/birt-runtime-2_2_1_1/ReportEngine";
public static void main(String[] args) {
try {
final ChartBuilder7 cb = new ChartBuilder7();
cb.buildReport();
cb.runReport();
cb.renderReport();
//cb.renderReportlet();
cb.shutdown();
} catch (final ContentException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (final NameException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (final SemanticException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (final DesignFileException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (final BirtException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (final IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
//factories and engines used in program
IDesignEngine dengine;
IDesignEngineFactory designFactory;
IReportEngine rengine; // stores the single instance of the report engine
SessionHandle session;
public ChartBuilder7() throws BirtException {
final PlatformConfig platformConfig = new PlatformConfig();
platformConfig.setBIRTHome(BIRT_HOME);
Platform.startup(platformConfig);
// create a new report engine factory
final IReportEngineFactory reportFactory = (IReportEngineFactory) Platform
.createFactoryObject(IReportEngineFactory.EXTENSION_REPORT_ENGINE_FACTORY);
// create a new report engine
final EngineConfig engineConfig = new EngineConfig();
engineConfig.setBIRTHome(BIRT_HOME); // will replace with
// configuration file
rengine = reportFactory.createReportEngine(engineConfig);
final DesignConfig dconfig = new DesignConfig();
dconfig.setBIRTHome(BIRT_HOME);
// try to start up the eclipse platform
designFactory = (IDesignEngineFactory) Platform
.createFactoryObject(IDesignEngineFactory.EXTENSION_DESIGN_ENGINE_FACTORY);
dengine = designFactory.createDesignEngine(dconfig);
// create a new session, open the library, and retrieve the first data
// source since it is uniform in our library
session = dengine.newSessionHandle(ULocale.ENGLISH);
}
/**
* Adds data binding information to chart retrieved from library
*
* @param eih
*/
public void addPieChartFromLibrary(ExtendedItemHandle eih) {
try {
// create chart and set standard options
ChartWithoutAxes cwaPie = (ChartWithoutAxes)eih.getReportItem().getProperty("chart.instance");
//clear any existing definitions from the chart
cwaPie.getSeriesDefinitions().clear();
//create actual series data. This will not be in the library chart
Series seCategory = SeriesImpl.create( );
//This is where things differ from standalone charts. A Query is an EList
//that contains an expression for data. Keep in mind we need to set out
//data set below, otherwise setting these expressions are pointless
Query query = QueryImpl.create( "row[\"OrganizationName\"]");//$NON-NLS-1$
// When creating a series, getDataDefinition() is expecting the EList,
// and the Query is an instance of that
seCategory.getDataDefinition( ).add( query );
SeriesDefinition series = SeriesDefinitionImpl.create( );
series.getSeries( ).add( seCategory );
cwaPie.getSeriesDefinitions( ).add( series );
series.getSeriesPalette().shift(1); // to change the color of the next slice
// Orthogonal Series
PieSeries sePie = (PieSeries) PieSeriesImpl.create( );
Query query2 = QueryImpl.create( "row[\"CandidatesImage\"]");//$NON-NLS-1$
sePie.getDataDefinition().add(query2);
sePie.setExplosion( 5 );
SeriesDefinition sdEmpCount = SeriesDefinitionImpl.create( );
sdEmpCount.getSeriesPalette().shift(2);
series.getSeriesDefinitions( ).add( sdEmpCount );
sdEmpCount.getSeries( ).add( sePie );
//add to report
// eih.loadExtendedElement(); // this may or may not need to
//be called - it's typically used for reading (if already in the rpt design file)
// eih.getReportItem().setProperty("chart.instance", cwaPie);
eih.setProperty("outputFormat", "PNG");
eih.setProperty("dataSet", "GridXMLDataSet"); // tell the chart which dataset to use to bind the chart to the report
//do the bindings to the extended element handle
PropertyHandle computedSet = eih.getColumnBindings( );
ComputedColumn cs1 = new ComputedColumn();
cs1.setExpression( "dataSetRow[\"OrganizationName\"]");//$NON-NLS-1$
cs1.setName( "OrgName" );
cs1.setDataType( "string" );
computedSet.addItem( cs1 );
ComputedColumn cs2 = new ComputedColumn();
cs2.setExpression( "dataSetRow[\"CandidatesImage\"]");//$NON-NLS-1$
cs2.setName( "Candidates" );
cs2.setDataType( "float" );
computedSet.addItem( cs2 );
}
catch ( Exception E ) {
E.printStackTrace();
}
}
/**
* Build report
* @throws ContentException
* @throws NameException
* @throws SemanticException
* @throws DesignFileException
* @throws IOException
*/
public void buildReport() throws ContentException, NameException,
SemanticException, DesignFileException, IOException {
// create a new report
final ReportDesignHandle reportDesign = session.createDesign();
final ElementFactory ef = reportDesign.getElementFactory();
// add new master page element
final DesignElementHandle element = ef
.newSimpleMasterPage("Page Master");
reportDesign.getMasterPages().add(element);
// get the session, open the library
final SessionHandle session = dengine.newSessionHandle(ULocale.ENGLISH);
final LibraryHandle library = session
.openLibrary("C:\\contracts\\CompanyA\\ChartBuild\\chart_library.rptlibrary");
// get the data source and date set from library
final DataSourceHandle dataSourceHandle = (DataSourceHandle) library
.getDataSources().get(0);
final DataSetHandle dataSetHandle = (DataSetHandle) library
.getDataSets().get(0);
// add the data sources and data sets to report
reportDesign.getDataSources().add(dataSourceHandle);
reportDesign.getDataSets().add(dataSetHandle);
// create a new extended item handle and add. This is read in the library
//so we do not need to manually create visual elements. Then add to the body of our
//new report design
final ExtendedItemHandle eih = (ExtendedItemHandle)library.findElement("PieChart");
reportDesign.getBody().add(eih);
//modify the chart with data bindings so that it is bound to our new report design
this.addPieChartFromLibrary(eih);
// add some sample labels
final LabelHandle lh = ef.newLabel("New Label");
lh.setText("Can you see me?");
reportDesign.getBody().add(lh);
// set my initial properties for the new report
final String reportName = "Example Chart";
reportDesign.setDisplayName(reportName);
reportDesign.setDescription(reportName);
reportDesign.setIconFile("/templates/blank_report.gif");
reportDesign.setFileName("C:/Temp/" + reportName + ".rptdesign");
reportDesign.setDefaultUnits("in");
reportDesign.setProperty("comments", reportName);
reportDesign.setProperty(IReportRunnable.TITLE, reportName);
// save report design
reportDesign.saveAs("C:/Temp/" + reportName + ".rptdesign");
}
/**
* Render full report
*/
public void renderReport() {
try {
final IReportDocument rptdoc = rengine
.openReportDocument("C:/temp/tempDoc.rptDocument");
// Create Render Task
final IRenderTask rtask = rengine.createRenderTask(rptdoc);
// Set Render context to handle url and image locataions
final HTMLRenderContext renderContext = new HTMLRenderContext();
// Set the Base URL for all actions
renderContext.setBaseURL("http://localhost/");
// Tell the Engine to prepend all images with this URL - Note this
// requires using the HTMLServerImageHandler
renderContext.setBaseImageURL("http://localhost/myimages");
// Tell the Engine where to write the images to
renderContext.setImageDirectory("C:/xampplite/htdocs/myimages");
// Tell the Engine what image formats are supported. Note you must
// have SVG in the string
// to render charts in SVG.
renderContext.setSupportedImageFormats("JPG;PNG;BMP;SVG");
final HashMap<String, HTMLRenderContext> contextMap = new HashMap<String, HTMLRenderContext>();
contextMap.put(EngineConstants.APPCONTEXT_HTML_RENDER_CONTEXT,
renderContext);
rtask.setAppContext(contextMap);
// Set rendering options - such as file or stream output,
// output format, whether it is embeddable, etc
final HTMLRenderOption options = new HTMLRenderOption();
// Remove HTML and Body tags
// options.setEmbeddable(true);
// Set ouptut location
options.setOutputFileName("C:/temp/outputReport.html");
// Set output format
options.setOutputFormat("html");
rtask.setRenderOption(options);
rtask.render();
// render the report and destroy the engine
// Note - If the program stays resident do not shutdown the Platform
// or the Engine
rtask.close();
} catch (final EngineException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
/**
* render just a reportlet
*/
public void renderReportlet() {
try {
final IReportDocument rptdoc = rengine
.openReportDocument("C:/temp/tempDoc.rptDocument");
// Create Render Task
final IRenderTask rtask = rengine.createRenderTask(rptdoc);
// Set Render context to handle url and image locataions
final HTMLRenderContext renderContext = new HTMLRenderContext();
// Set the Base URL for all actions
renderContext.setBaseURL("http://localhost/");
// Tell the Engine to prepend all images with this URL - Note this
// requires using the HTMLServerImageHandler
renderContext.setBaseImageURL("http://localhost/myimages");
// Tell the Engine where to write the images to
renderContext.setImageDirectory("C:/xampplite/htdocs/myimages");
// Tell the Engine what image formats are supported. Note you must
// have SVG in the string
// to render charts in SVG.
renderContext.setSupportedImageFormats("JPG;PNG;BMP;SVG");
final HashMap<String, HTMLRenderContext> contextMap = new HashMap<String, HTMLRenderContext>();
contextMap.put(EngineConstants.APPCONTEXT_HTML_RENDER_CONTEXT,
renderContext);
rtask.setAppContext(contextMap);
// Set rendering options - such as file or stream output,
// output format, whether it is embeddable, etc
final HTMLRenderOption options = new HTMLRenderOption();
// Remove HTML and Body tags
// options.setEmbeddable(true);
// Set ouptut location
options.setOutputFileName("C:/temp/outputReportlet.html");
// Set output format
options.setOutputFormat("html");
rtask.setRenderOption(options);
rtask.setReportlet("ChartToRender");
rtask.render();
// render the report and destroy the engine
// Note - If the program stays resident do not shutdown the Platform
// or the Engine
rtask.close();
} catch (final EngineException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
/**
* Run report
*/
public void runReport() {
try {
final IReportRunnable design = rengine.openReportDesign("C:/Temp/"
+ "Example Chart" + ".rptdesign");
final IRunTask runTask = rengine.createRunTask(design);
// use the default locale
runTask.setLocale(Locale.getDefault());
runTask.run("C:/temp/tempDoc.rptDocument");
runTask.close();
} catch (final EngineException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public void shutdown() {
Platform.shutdown();
}
}
Sunday, June 29, 2008
BIRT: BIRT 2.3 is out
Thursday, June 26, 2008
Phone: Verizon XV6900
So, I had an unfortunate situation with my old Motorola Razr, where the speaker stopped working and I couldn’t hear anything, so it was time for a new phone. My requirements were simple, I needed to be able to tether, sync with Outlook to retire my existing PDA, and something that I could play Chess on.
What I ended up with was the HTC XV8900 from Verizon. I refused to leave Verizon, although I am still tempted to get an iPhone, I just can’t stomach AT&T’s coverage. So far it is a decision I have been very happy with. I won’t review it since there are plenty off reviews about this phone out there, so I’ll just share my thoughts on it. First off, it uses a touch interface for everything, so no slideout QWERTY keyboard or anything of that nature for quick texting, which is fine because I seem to be of a generation before texting became popular. Besides, that’s one of the things that makes the iPhone so stylish, and I agree with the “no buttons” philosophy to an extent. I have a portable Bluetooth foldout keyboard, so I’d rather have a fullsize keyboard for any serious typing than the crappy keyboards they have on phones. I did have to hack the keyboard a little since it was a HP Foldout Bluetooth keyboard and used some sort of proprietary driver, but thanks to a nice fellow from Africa, I have a workable solution.
After a few modifications, I have a really nice “Slide to Unlock” option that mimics the iPhones, with the Caller ID mimicking it as well. Outside of that, the other features, such as web browsing, media, camera, are pretty much there, so anything an iPhone can do, I can do on this thing since Windows Mobile devices have been doing these things since before I got my WM 2003 device.
Outlook syncing hasn’t changes since my WM2003 device, except now I have it on my phone, and I can check my email when I am out and about. The only issue is Activesync considers my “Outlook Email” separate from my two email accounts, so when I sync up, it wont sync mail that is checked already on the phone. A little annoying, so I have it configured not to delete messages on the phone when checked. If I ran an Exchange server, I could use the Push option, which I’m sure Blackberry users are all too familiar with, but I’m lazy and don’t want to configure an Exchange server just for this purpose.
While stylish, and features that do just about everything I need, there are a few gripes about this device. 1st, since it is a Windows Mobile device, the ever present X does not close applications annoyance is there. While HTC includes a quick link in the top right corner to kill all applications, its still annoying. Also annoying is the fact that TouchFlo is always present. I wanted to give Spb mobile shell a try, but I don’t want to run that AND touchflo at the same time. Even after I kill all the TouchFlo apps, it still manages to start itself if I do a finger gesture or pen swipe from the bottom to the top of the screen. Also, the paltry 512 flashram is laughable compared to the 16 Gb you can get on other phones. I had to purchase a separate MicroSD card to compensate. Also annoying is the fact that the memory usage is not configurable. On my older WM2003 device, I could allocate memory for storage or applications running, on this device that doesn’t seem to e an option. With the MicroSD card, why would I want to use the precious flashram to store apps when I don’t have to? Also, I had a hard time finding a case to fit the device. Verizon only offered a silicone slip cover for the phone, so I had to put on a screen protector, and find a 3rd part case. So the case I have is slightly larger than the phone itself, and it’s a little gangly. I’ll have to keep my eye out for a more easily accessible case for this phone.
Overall, I am happy with the phone though. It does everything my old hacked Razr did, plus more. No more crappy, hard to read, mobile web, I have the same games, tethering capability, plus a suite of mobile office applications to use with my Bluetooth keyboard, a media device, movies, and games.
Tuesday, April 22, 2008
BIRT: Another BIRT Book
Friday, March 28, 2008
Games: Ninja Gaiden Dragon Sword
Now that I've gotten a hands on with it, I can say those fears are completely unfounded. NG:DS is a completely engrossing handheld experience right from the get go. Gone are the days and stories of Ryu chasing Jaquio around looking for statues and saving Irine from the clutches of certain death. NG:DS takes up from the original Xbox story line, some 6 months after the "Dark Dragon Blade" incident. While I haven't gotten enough into the story, I did run around quite a bit to make sure the control scheme wasn't nearly as wonky as I had feared. Holding the DS at 90 degrees, book style, is a bit awkward, but only because I've spent the past 20 years of my life holding controllers ALA the original NES control scheme. Moving the character around is done via the stylus, as is the attacks. How is this done? Why, by pointing where you want to move to, writing/slashing the stylus over the opponent to attack, tapping the opponent to throw ninja stars, and slashing up to jump. So the attacks are actually pretty intuitive, despite my original fears.
There are a few hiccups however. There re times where to want to jump, and the chacter runs right into the coming onslaught of enemies instead. Thats kind of annoying, but I can chalk that up to my own ineptness with the control scheme. Blocking is a bit of a pain. Any button will cause you to block. But since I am a righty, I hold the stylus with my right-hand, the DS is in a right-hand orientation, which puts the buttons on the right-screen. So, as you can imagine, having to use my left hand to push a button in a right-handed orientation leaves me a little short-circuited due to my lack of ambidextrous abilities. Fortunately, what I lack in coordination I more than make up for in hand size, so reaching over to push buttons to block isn't as bad as I make it seem.
While I haven't done enough story line to really pass judgment on it, I did find the game bordering on "not able to put down" fun from running around and slashing. While this game isn't quite suitable for my game time during meals while traveling due to it's more interactive nature, it is definitely suitable for the evenings at the hotel while traveling and on long plane rides so that I can be the uncoordinated geeky kid poking peoples eyes out.
Sguil: Sguil 0.7 is out
Saturday, March 22, 2008
BIRT: Building Chart with Chart Engine API based off Report Document
I have been sitting on this example for a few weeks now, and have been waiting to put it out until after EclipseCon so I can absorb any ideas from the Charting API presentation that Jason Weathersby over at BirtWorld did. Now that is done and gone, I have decided to put this one out into the wild.
The original purpose of this was to be able to extract a chart design out of a BIRT report, and render it using the BIRT Chart Engine API’s. What I found out the hard way is that data set and data are not linked to Charts in BIRT outside of the BIRT render engine. The Chart Engine API, as it turns out, is completely decoupled from the BIRT Report Engine API, so axis to data mappings are not accessible in the Chart Engine API and the actual data binding is done through some Java-Fu wizardry at runtime. So, I salvaged what I could of the idea.
The following example will demonstrate a couple of different things. First, it will open a BIRT Report, and pull the Chart definition out of the report. It will then read the XML Representation of the chart to determine what columns in the report are mapped to which Axis. It then runs the report, and uses a Data Extraction task to pull the data and create an appropriate Chart Engine API Series definition. It then renders a chart to a PNG file.
There are a few things to keep in mind in this example. First, it assumes that there is a 2 column data set defined in the report, with column 1 containing what is the X axis definitions, and column 2 containing the values. It then calculates the aggregates based on those. Second, it assumes a chart definition is defined in the report. In a real world scenario, you would NEVER render a chart like this, but I am a glutton for punishment and needed a good way to learn how the Chart Engine API works. You will notice that I commented out a good chunk of code that defines the look and feel of the chart. I kept these commented out as a reference, but they aren’t necessary in this example since the visual attributes for the chart were defined in the source report design.
I did learn one really cool thing about Chart Engine API. There is a whole slew of example Chart Engine API code available in a BIRT install by default, by going to Window/Show View/Other/Report and Chart Design/Chart Examples. This will open a new view, just select any the examples listed and click on the Open button on the right hand side to see example code on how to build those charts.
Anyway, here is my example:
package com.test;
import java.io.File;
import java.io.StringBufferInputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Locale;
import java.util.Map;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;
import org.eclipse.birt.chart.api.ChartEngine;
import org.eclipse.birt.chart.device.IDeviceRenderer;
import org.eclipse.birt.chart.device.image.PngRendererImpl;
import org.eclipse.birt.chart.exception.ChartException;
import org.eclipse.birt.chart.factory.GeneratedChartState;
import org.eclipse.birt.chart.factory.Generator;
import org.eclipse.birt.chart.factory.RunTimeContext;
import org.eclipse.birt.chart.model.Chart;
import org.eclipse.birt.chart.model.ChartWithoutAxes;
import org.eclipse.birt.chart.model.attribute.Bounds;
import org.eclipse.birt.chart.model.attribute.impl.BoundsImpl;
import org.eclipse.birt.chart.model.component.Series;
import org.eclipse.birt.chart.model.component.impl.SeriesImpl;
import org.eclipse.birt.chart.model.data.NumberDataSet;
import org.eclipse.birt.chart.model.data.SeriesDefinition;
import org.eclipse.birt.chart.model.data.TextDataSet;
import org.eclipse.birt.chart.model.data.impl.NumberDataSetImpl;
import org.eclipse.birt.chart.model.data.impl.SeriesDefinitionImpl;
import org.eclipse.birt.chart.model.data.impl.TextDataSetImpl;
import org.eclipse.birt.chart.model.type.PieSeries;
import org.eclipse.birt.chart.model.type.impl.PieSeriesImpl;
import org.eclipse.birt.core.exception.BirtException;
import org.eclipse.birt.core.framework.Platform;
import org.eclipse.birt.core.framework.PlatformConfig;
import org.eclipse.birt.data.engine.core.DataException;
import org.eclipse.birt.report.engine.api.EngineConfig;
import org.eclipse.birt.report.engine.api.EngineException;
import org.eclipse.birt.report.engine.api.IDataExtractionTask;
import org.eclipse.birt.report.engine.api.IDataIterator;
import org.eclipse.birt.report.engine.api.IExtractionResults;
import org.eclipse.birt.report.engine.api.IReportDocument;
import org.eclipse.birt.report.engine.api.IReportEngine;
import org.eclipse.birt.report.engine.api.IReportEngineFactory;
import org.eclipse.birt.report.engine.api.IReportRunnable;
import org.eclipse.birt.report.engine.api.IResultSetItem;
import org.eclipse.birt.report.engine.api.IRunTask;
import org.eclipse.birt.report.model.api.ExtendedItemHandle;
import org.eclipse.birt.report.model.api.ReportDesignHandle;
import org.eclipse.birt.report.model.api.extension.ExtendedElementException;
import org.w3c.dom.DOMException;
import org.xml.sax.InputSource;
import com.ibm.icu.util.ULocale;
import com.sun.org.apache.xml.internal.dtm.ref.DTMNodeList;
public class RenderChart {
private static String BIRT_HOME = "C:/birt-runtime-2_2_1_1/ReportEngine";
/**
* Creates the X series for the Chart
* @param uniqueMap
* @return
*/
public static TextDataSet createCategoriesDataSet(Map uniqueMap)
{
String[] categories = new String[uniqueMap.keySet().size()];
int x = 0;
for (Iterator keyIt = uniqueMap.keySet().iterator(); keyIt.hasNext();)
{
categories[x] = (String)keyIt.next();
x++;
}
return TextDataSetImpl.create(categories);
}
/**
* Creates the Y Series for a chart
* @param uniqueMap
* @return
*/
public static NumberDataSet createValueDataSet(Map uniqueMap)
{
double[] values = new double[uniqueMap.values().size()];
int x = 0;
for (Iterator valIt = uniqueMap.values().iterator(); valIt.hasNext();)
{
values[x] = Math.round(((Double)valIt.next()).doubleValue());
x++;
}
return NumberDataSetImpl.create(values);
}
/**
* Will open a report design and return the Document instance
* @param reportName
* @return
*/
public static IReportDocument executeReport(String reportName) throws EngineException {
// create a new report engine factory
IReportEngineFactory factory = (IReportEngineFactory) Platform
.createFactoryObject(IReportEngineFactory.EXTENSION_REPORT_ENGINE_FACTORY);
// create a new report engine
EngineConfig engineConfig = new EngineConfig();
engineConfig.setBIRTHome(BIRT_HOME); // will replace with
// configuration file
IReportEngine engine = factory.createReportEngine(engineConfig);
// create the report task and open the report design file
IReportRunnable design = engine.openReportDesign(reportName);
IRunTask runTask = engine.createRunTask(design);
// use the default locale
runTask.setLocale(Locale.getDefault());
// run and close the report run task
File newTempFile = new File("C:/TEMP/birtRenderTemp",
"test.rptDocument");
String tempFileLocation = newTempFile.getAbsolutePath();
// delete the temp file, we just needed the name and path
newTempFile.delete();
runTask.run(tempFileLocation);
runTask.close();
IReportDocument ird = engine
.openReportDocument("C:/TEMP/birtRenderTemp/test.rptDocument");
return ird;
}
/**
* Creates an input source from the XMLProperties string
* @param xmlProperties
* @return
*/
public static InputSource getInputSourceFromString(String xmlProperties)
{
StringBufferInputStream is = new StringBufferInputStream(
xmlProperties);
return new InputSource(is);
}
/**
* Using a data extraction task, this will build a simple Map of columns and rows
* This method assumes a 1-1 relation between what will be the x value and y value
* @param det
* @param ySeries
* @param xSeries
* @return
* @throws BirtException
*/
public static Map getValueMap(IDataExtractionTask det, String ySeries, String xSeries) throws BirtException
{
//extract the results from the task
IExtractionResults iExtractResults = det.extract();
IDataIterator iData = null;
//hashmap to return
Map uniqueMap = new HashMap();
//if we have results, process them, otherwise, don't
if (iExtractResults != null) {
iData = iExtractResults.nextResultIterator();
// iterate through the results
if (iData != null) {
//loop while there is still data in out iterator
while (iData.next()) {
Object objColumn1;
Object objColumn2;
try {
objColumn1 = iData.getValue(ySeries);
} catch (DataException e) {
objColumn1 = new String("");
}
try {
objColumn2 = iData.getValue(xSeries);
} catch (DataException e) {
objColumn2 = new String("");
}
Double newNumber = (Double) objColumn1;
if (uniqueMap.keySet().contains(objColumn2)) {
newNumber += (Double) uniqueMap
.get(objColumn2);
}
uniqueMap.put(objColumn2, newNumber);
}
iData.close();
}
}
//close the data extraction task and return our value map
det.close();
return uniqueMap;
}
/**
* Extract the XML resources from the extended item handle, used to get the XML
* representation of the chart from a report
*
* @param eih
* @return
*/
public static String getXMLResources(ExtendedItemHandle eih)
{
return (String) eih.getProperty("xmlRepresentation");
}
/**
* Retrieves the name of the X Series column from a chart XMLRepresentation
*
* @param xmlProperties
* @return
* @throws XPathExpressionException
* @throws DOMException
*/
public static String getXSeries(String xmlProperties) throws XPathExpressionException, DOMException
{
InputSource inputSource = getInputSourceFromString(xmlProperties);
XPath xpath = XPathFactory.newInstance().newXPath();
DTMNodeList nodeList = (DTMNodeList) xpath
.evaluate(
"//SeriesDefinitions/Series/DataDefinition/Definition",
inputSource, XPathConstants.NODESET);
String xSeries = nodeList.item(1).getTextContent();
//we found the series definitions, now remove the expression and just return
//the key
return xSeries.replace("row[\"", "").replace("\"]", "");
}
/**
* Retrieves the name of the Y Series column from a chart XMLRepresentation
* @param xmlProperties
* @return
* @throws XPathExpressionException
* @throws DOMException
*/
public static String getYSeries(String xmlProperties) throws XPathExpressionException, DOMException
{
InputSource inputSource = getInputSourceFromString(xmlProperties);
XPath xpath = XPathFactory.newInstance().newXPath();
DTMNodeList nodeList = (DTMNodeList) xpath
.evaluate(
"//SeriesDefinitions/Series/DataDefinition/Definition",
inputSource, XPathConstants.NODESET);
String ySeries = nodeList.item(0).getTextContent();
return ySeries.replace("row[\"", "").replace("\"]", "");
}
/**
* Will render a Pie chart with given categories and values. Chart needs to be created beforehand, so pull from document first.
* @param cwa
* @param categories
* @param values
* @throws ChartException
*/
public static void renderPieChart(ChartWithoutAxes cwa, TextDataSet categories, NumberDataSet values) throws ChartException
{
// Create the png renderer
IDeviceRenderer idr = new PngRendererImpl();
//create new run time context
RunTimeContext rtc = new RunTimeContext();
rtc.setULocale(ULocale.getDefault());
//create a new generator
final Generator gr = Generator.instance();
GeneratedChartState gcs = null;
//clear any existing series definitions since we created out own
cwa.getSeriesDefinitions().clear();
// Plot the chart...
/*
* Note: I commented this stuff out since I already designed my chart
* look and feel in the BIRT report designer, and all of that will
* already be set in my chart opened from a BIRT Report
cwa.setSeriesThickness(25);
cwa.getBlock().setBackground(ColorDefinitionImpl.WHITE());
Plot p = cwa.getPlot();
cwa.setDimension(ChartDimension.TWO_DIMENSIONAL_LITERAL);
p.getClientArea().setBackground(null);
p.getClientArea().getOutline().setVisible(true);
p.getOutline().setVisible(true);
Legend lg = cwa.getLegend();
lg.getText().getFont().setSize(16);
lg.setBackground(null);
lg.getOutline().setVisible(true);
// Title
//cwa.getTitle( ).getLabel( ).getCaption( ).setValue( "Pie Chart" );//$NON-NLS-1$
cwa.getTitle( ).getOutline( ).setVisible( true );
*/
//define base series
Series seCategory = SeriesImpl.create();
seCategory.setDataSet(categories);
SeriesDefinition sd = SeriesDefinitionImpl.create();
sd.getSeriesPalette().shift(0);
sd.getSeries().add(seCategory);
cwa.getSeriesDefinitions().add(sd);
//define pie seies
PieSeries categorySeries = (PieSeries) PieSeriesImpl
.create();
categorySeries.setDataSet(values);
categorySeries.setSeriesIdentifier("Territories");
SeriesDefinition sdValues = SeriesDefinitionImpl.create();
sdValues.getQuery().setDefinition("Census.Territories");//$NON-NLS-1$
sdValues.getSeries().add(categorySeries);
sdValues.getSeries().add(categorySeries);
sd.getSeriesDefinitions().add(sdValues);
// Set the chart size
Bounds bo = BoundsImpl.create(0, 0, 350, 275);
//Now build the chart
gcs = gr.build(idr.getDisplayServer(), (Chart)cwa, bo, null, rtc,
null);
// Specify the file to write to.
idr.setProperty(IDeviceRenderer.FILE_IDENTIFIER,
"test.png"); //$NON-NLS-1$
// generate the chart
gr.render(idr, gcs);
}
/**
* Main: The delegator of out program. Starts the BIRT Platform, gets chart, extracts values, and renders chart
* @param args
*/
public static void main(String[] args) {
try {
//start up the platform
//note: needed to add STANDALONE = true, otherwise the chart engine
//would not work.
PlatformConfig platformConfig = new PlatformConfig();
platformConfig.setBIRTHome(BIRT_HOME);
//standalone platform
platformConfig.setProperty("STANDALONE", "true");
ChartEngine.instance(platformConfig);
Platform.startup(platformConfig);
// create a new report engine factory
IReportEngineFactory factory = (IReportEngineFactory) Platform
.createFactoryObject(IReportEngineFactory.EXTENSION_REPORT_ENGINE_FACTORY);
// create a new report engine
EngineConfig engineConfig = new EngineConfig();
engineConfig.setBIRTHome(BIRT_HOME);
IReportEngine engine = factory.createReportEngine(engineConfig);
//open the given report and create a new Data Extraction task to get data from run report
IReportDocument ird = executeReport("C:/contracts/GWTBirt/BIRTGwt/src/reports/Charts/TerritorySalesPieChart.rptdesign");
IDataExtractionTask det = engine.createDataExtractionTask(ird);
//get the report runnable from the document so we can grab the chart information from the report
IReportRunnable r = ird.getReportRunnable();
ReportDesignHandle rh = (ReportDesignHandle) r.getDesignHandle();
//for each element in the report (assuming only charts), go through and grab info, then render to PNG
for (Iterator i = rh.getBody().getContents().iterator(); i
.hasNext();) {
Object o = i.next();
//make sure this is an extended item handle
if (o instanceof ExtendedItemHandle) {
ExtendedItemHandle eih = (ExtendedItemHandle) o;
// read in the XML Representation for getting the data
// definitions from the chart, get the values of the X and Y
// axis
String xSeries = getXSeries(getXMLResources(eih));
String ySeries = getYSeries(getXMLResources(eih));
//Look into using serializer to grab from a report design.
//alternative of doing this, although a little uglier
//ChartEngine.instance().getSerializer().fromXml(arg0, arg1)
ChartWithoutAxes cwa = (ChartWithoutAxes) eih
.getReportItem().getProperty("chart.instance");
// Get list of result sets
ArrayList resultSetList = (ArrayList) det
.getResultSetList();
//we know out data is in the first result set
IResultSetItem resultItem = (IResultSetItem) resultSetList
.get(0);
String dispName = resultItem.getResultSetName();
//tell the data extraction task to use the first result set
det.selectResultSet(dispName);
// retrieves the dataset with column/values as unique to map
// to Pie Chart
Map uniqueMap = getValueMap(det, ySeries, xSeries);
//crete the category, or X series, and values, or y series
TextDataSet categoryValues = createCategoriesDataSet(uniqueMap);
NumberDataSet seriesOneValues = createValueDataSet(uniqueMap);
//now that we have data and our chart, render to image
renderPieChart(cwa, categoryValues, seriesOneValues);
}
}
} catch (ExtendedElementException e) {
e.printStackTrace();
} catch (XPathExpressionException e) {
e.printStackTrace();
} catch (DOMException e) {
e.printStackTrace();
} catch (EngineException e) {
e.printStackTrace();
} catch (ChartException e) {
e.printStackTrace();
} catch (BirtException e) {
e.printStackTrace();
}
//shutdown the platform, we are done
Platform.shutdown();
}
}