<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>JavaWorld's Daily Brew</title>
  <subtitle>Starting conversations in the Java developer community</subtitle>
  <link rel="alternate" type="text/html" href="http://www.javaworld.com/community"/>
  <link rel="self" type="application/atom+xml" href="http://www.javaworld.com/community/atom/feed"/>
  <id>http://www.javaworld.com/community/atom/feed</id>
  <updated>2010-02-03T09:52:36-05:00</updated>
  <entry>
    <title>The Rise of Google Chrome</title>
    <link rel="alternate" type="text/html" href="http://www.javaworld.com/community/node/4024" />
    <id>http://www.javaworld.com/community/node/4024</id>
    <published>2010-02-07T18:39:00-05:00</published>
    <updated>2010-02-08T04:51:42-05:00</updated>
    <author>
      <name>dmarx</name>
    </author>
    <category term="Dustin" />
    <category term="Web Development" />
    <summary type="html"><![CDATA[<!--paging_filter--><p>The <a href="http://www.google.com/chrome/">Google Chrome web browser</a> has continued to gain in popularity since most of us <a href="http://marxsoftware.blogspot.com/2008/09/google-chrome-web-browser.html">first heard about it</a>.</p>
    ]]></summary>
    <content type="html"><![CDATA[<!--paging_filter--><p>The <a href="http://www.google.com/chrome/">Google Chrome web browser</a> has continued to gain in popularity since most of us <a href="http://marxsoftware.blogspot.com/2008/09/google-chrome-web-browser.html">first heard about it</a>.  <a href="http://www.thechromesource.com/author/daniel-cawrey/">Daniel Cawrey</a>'s blog post <a href="http://www.thechromesource.com/w3schools-chrome-has-surpassed-ten-percent-share/">w3schools: Chrome Has Surpassed Ten Percent Share</a> points out that <a href="http://www.w3schools.com/">W3Schools</a>'s <a href="http://www.w3schools.com/browsers/browsers_stats.asp">Browser Statistics Month by Month</a> shows the Chrome browser being used for 10.8% of visits to that web site in January 2010.  In that same month, <a href="http://www.microsoft.com/windows/internet-explorer/default.aspx">Microsoft Internet Explorer</a> accounted for 36.2% of visits (14.3% MSIE 8, 11.7% <a href="http://www.microsoft.com/windows/internet-explorer/ie7/">MSIE 7</a>, and 10.2% <a href="http://www.microsoft.com/windows/ie/ie6/default.mspx">MSIE 6</a>) and <a href="http://www.mozilla.com/en-US/firefox/firefox.html">Mozilla Firefox</a> accounted for 46.3% of visits.</p>
<p>The numbers reported by W3Schools (especially the totals of 46.3% for Firefox, 36.2% for MSIE, and 10.8% for Chrome) are not exactly what one would expect to see in the general web browsing population and this difference reflects the specialized web development focus of the W3Schools audience.  Knowing that the audience of people who would read my blog or come to it from a search engine would be similarly slanted in terms of which browsers were used, I was curious to see exactly which browsers are most popular for visitors to my blog.  I used <a href="http://www.google.com/analytics/">Google Analytics</a> to determine the following numbers for viewers of my blog in January 2010: Firefox 57%, Chrome 20%, Internet Explorer 10%, Safari 5.6%, and several others picking up the remaining percentage.  These numbers show that Firefox is by far the most dominant browser for people who access my blog and that Chrome makes a very impressive second-place appearance with 20% of the visits.</p>
<p>Neither the W3Schools nor my blog usage are indicative of the general web browsing population.  <a href="http://www.netmarketshare.com/">NetMarketShare</a> provides an estimate of more general browser usage and its <a href="http://www.netmarketshare.com/report.aspx?qprid=0&amp;qptimeframe=M&amp;qpsp=132">January 2010 results</a> show Internet Explorer still being the dominant browser by far with Firefox firmly in control of second place followed by Chrome and then Safari.  According to NetMarketShare's numbers more than 4 out of every 5 browsers being used is Internet Explorer or Firefox.</p>
<p>The long-term implications of all this remain to be seen, but there are some short-term observations that can be made.  Not surprisingly, Chrome and Firefox appear to be far more popular in the software development world (including Java development and web development) than in the general population.  For web developers in particular, it has long been <a href="http://saucelabs.com/blog/index.php/2010/02/death-to-internet-explorer-long-live-internet-explorer/">frustrating</a> to deal with MSIE's being stubbornly non-standard in many different areas.  However, because MSIE has dominated the general web user community, it has been important to write significant non-standard code to support that large set of users.  Furthermore, Chrome continues to gain users in both the general public and in the software development communities.</p>
<p>Security concerns related to Microsoft Internet Explorer are not really new, but have <a href="http://blogs.msdn.com/the_hardman/archive/2010/01/20/the-internet-explorer-threat.aspx">recently received renewed focus</a>, including the <a href="http://www.sophos.com/blogs/gc/g/2010/01/15/danger-internet-explorer-zeroday-vulnerability-patch/">now frequently cited</a> <a href="http://mashable.com/2010/01/18/france-against-internet-explorer/">French</a> and <a href="http://mashable.com/2010/01/15/german-government-stop-using-internet-explorer/">German</a> governments' <a href="http://news.bbc.co.uk/2/hi/technology/8465038.stm">recommendations</a> to abstain (temporarily) from using <a href="http://mashable.com/2010/01/26/security-flaws-internet-explorer/">MSIE</a>.  In a blog post earlier this week called <a href="http://www.taranfx.com/ie-flaw-public-files">IE Flaw Makes Local Files Public</a>, <a href="http://www.taranfx.com/author/admin">taranfx</a> states, "The end of Internet Explorer is finally here" and supports this conclusion with a brief account of problems related to use of MSIE that are "forcing users to move to alternatives."  This post also provides a link to the <a href="http://www.blackhat.com/">BlackHat</a> <a href="http://www.blackhat.com/html/bh-dc-10/bh-dc-10-home.html">DC 2010</a> white paper <a href="http://www.blackhat.com/presentations/bh-dc-10/Medina_Jorge/BlackHat-DC-2010-Medina-Abusing-insecure-features-of-Internet-Explorer-wp.pdf">Abusing Insecure Features of Internet Explorer</a>.</p>
<p>Chrome is not only slowing chipping away at MSIE's usage, but is also taking market share from Firefox.  It still remains to be seen how popular Chrome can become.  My preference would be for Firefox and Chrome to remain strong alternatives in the browser market.  I don't see a lot of benefit from Chrome simply replacing Firefox as an alternative.  The best scenario for web developers and web users would be for the browser wars to drive greater standardization at a faster pace.</p>
    ]]></content>
  </entry>
  <entry>
    <title>Oracle Enterprise Pack for Eclipse</title>
    <link rel="alternate" type="text/html" href="http://www.javaworld.com/community/node/4021" />
    <id>http://www.javaworld.com/community/node/4021</id>
    <published>2010-02-06T21:59:00-05:00</published>
    <updated>2010-02-08T04:51:42-05:00</updated>
    <author>
      <name>dmarx</name>
    </author>
    <category term="Dustin" />
    <category term="Eclipse" />
    <category term="Oracle" />
    <summary type="html"><![CDATA[<!--paging_filter--><p>The <a href="http://www.oracle.com/technology/products/enterprise-pack-for-eclipse/index.html">Oracle Enterprise Pack for Eclipse</a> (<a href="http://www.oracle.com/tools/enterprise-eclipse-pack.html">OEPE</a>) is described as a "certified set of Eclipse plug-ins is designed to help develop, deploy and debug applications for Oracle WebLogic Server."  Although this is true, it offers much more than that with support for development with the <a href="http://www.oracle.com/technology/pub/articles/marx_spring.html">Spring Framework</a>, the <a></p>
    ]]></summary>
    <content type="html"><![CDATA[<!--paging_filter--><p>The <a href="http://www.oracle.com/technology/products/enterprise-pack-for-eclipse/index.html">Oracle Enterprise Pack for Eclipse</a> (<a href="http://www.oracle.com/tools/enterprise-eclipse-pack.html">OEPE</a>) is described as a "certified set of Eclipse plug-ins is designed to help develop, deploy and debug applications for Oracle WebLogic Server."  Although this is true, it offers much more than that with support for development with the <a href="http://www.oracle.com/technology/pub/articles/marx_spring.html">Spring Framework</a>, the <a href="http://www.oracle.com/technology/pub/articles/marx-jpa.html">Java Persistence API</a> (<a href="http://java.sun.com/developer/technicalArticles/J2EE/jpa/">JPA</a>) and/or other <a href="http://en.wikipedia.org/wiki/Object-relational_mapping">object-relational mapping</a> technologies, and other enterprise development in conjunction with its support for <a href="http://www.oracle.com/appserver/weblogic/weblogic-suite.html">WebLogic</a> development and deployment.  <a href="http://www.oracle.com/technology/pub/articles/marx-oepe-spring.html">My article</a> on using OEPE to improve development with Spring, JPA, and WebLogic was published this week on <a href="http://www.oracle.com/technology/index.html">Oracle Technology Network</a>.  The article, called <a href="http://www.oracle.com/technology/pub/articles/marx-oepe-spring.html">Build a Java Application with Eclipse, Spring, and Oracle WebLogic Server</a>, covers the convenient features of OEPE for Spring and JPA developers, especially (but not necessarily) those who deploy to WebLogic.</p>
<p>There are several links on <a href="http://www.dzone.com/links/index.html">DZone</a> related to the recent release of a new version of <a href="http://www.oracle.com/technology/products/enterprise-pack-for-eclipse/index.html">Oracle Enterprise Pack for Eclipse</a>.  These include <a href="http://www.dzone.com/links/oracle_enterprise_pack_for_eclipse_11gr1_11114_re.html">Oracle Enterprise Pack for Eclipse 11gR1 (11.1.1.4) Release Webinar</a> (<a href="http://live.eclipse.org/node/876">linked article</a>) and <a href="http://www.dzone.com/links/oracle_upgrades_eclipse_free_plugin_package.html">Oracle Upgrades Eclipse Free Plug-in Package</a> (<a href="http://www.infoworld.com/d/developer-world/oracle-upgrades-eclipse-free-plug-in-package-287">linked article</a>).  The <a href="http://eclipse.dzone.com/">EclipseZone</a> <a href="http://eclipse.dzone.com/dose/dzone-daily-dose-26">portion</a> of DZone has several additional articles devoted to OEPE such as the article <a href="http://eclipse.dzone.com/articles/oracle-enterprise-pack-eclipse">Oracle Enterprise Pack for Eclipse</a> (an interview with Ashish Mohindroo)</p>
    ]]></content>
  </entry>
  <entry>
    <title>Tracking session expiration in browser</title>
    <link rel="alternate" type="text/html" href="http://www.javaworld.com/community/node/4012" />
    <id>http://www.javaworld.com/community/node/4012</id>
    <published>2010-02-04T21:12:58-05:00</published>
    <updated>2010-02-05T06:14:08-05:00</updated>
    <author>
      <name>Oleg Mikheev</name>
    </author>
    <category term="browser" />
    <category term="cookie" />
    <category term="servlet" />
    <category term="session expiration" />
    <summary type="html"><![CDATA[<!--paging_filter--><p>So, there is that complex heterogeneous web application, with AJAX parts done both manually and by frameworks, multiple pop-up windows, etc.<br />
A big respectable client approaches you with a requirement to invalidate, close, or do some other activity on all web application windows once HTTP session times out. Hopefully you know how to control the HTTP session time-out interval, for a J2EE-compliant web-application it is done from a web.xml file (however in lots of app servers it's done not in a standard way). For a 10 minutes time-out it is:<br />
<code></p>
    ]]></summary>
    <content type="html"><![CDATA[<!--paging_filter--><p>So, there is that complex heterogeneous web application, with AJAX parts done both manually and by frameworks, multiple pop-up windows, etc.<br />
A big respectable client approaches you with a requirement to invalidate, close, or do some other activity on all web application windows once HTTP session times out. Hopefully you know how to control the HTTP session time-out interval, for a J2EE-compliant web-application it is done from a web.xml file (however in lots of app servers it's done not in a standard way). For a 10 minutes time-out it is:<br />
<div class="codeblock"><code>	&lt;session-config&gt;<br />		&lt;session-timeout&gt;10&lt;/session-timeout&gt;<br />	&lt;/session-config&gt;</code></div></p>
<p>The client requirement is not absurd at all and makes perfect sense from the end-user perspective, but it can become a terrible pain for a developer because:</p>
<p>1. You can't just start a countdown timer in the browser window each time page loads to close the window upon time-out. This approach worked in a non-AJAX world when each browser-server interaction resulted in browser window reloaded.</p>
<p>2. You can't query the server to check whether HTTP session has timed-out or not, as each such query will be treated as a browser-server interaction prolonging the session. This will lead to a never expiring session.</p>
<p>3. You could create a separate web-application being aware of the primary web-app's HTTP session and intersecting with it. But that's an overkill, and the chances of such solution getting accepted are extremely low due to integration issues likely to arise.</p>
<p>4. You could try to intercept all AJAX browser-server interactions with some advanced hack-like code, and it will help you to deal with your current window. But this doesn't work for the multiple open windows case - you just can't communicate between browser windows. The only way to talk to some open window from a primary one is to use other window's JavaScript reference, and once primary window is reloaded or directed to a different location it loses all JavaScript references to other windows.</p>
<p>5. The most realistic approach is to make periodical JavaScript XMLHTTP requests (from each open window) to the server each {session max inactive interval}+10 seconds. This will eventually close all windows, but may result in windows closed minutes (or even hours depending on web-app session time-out setting) after the HTTP session gets destroyed, e.g. once user logs out from within the primary window.</p>
<p>No more options left, you are frustrated and you think that it's just the right time to take your daddy's gun and shoot your classmates at the school tomorrow. No, not yet kid - there is still a way out!</p>
<p>The way out is not very straightforward, but is very elegant. Cookies will help us.</p>
<p>One could think that cookies expiration time would do the trick. Unfortunately, as described in <a href="http://www.javaworld.com/community/node/3673">this</a> article, you can't rely on cookie expiration time since it is measured by a client browser, and noone can guarantee that the client system clock is not one year behind.</p>
<p>So, here is the System and Method for Tracking HTTP Session Time-Outs in Heterogeneous Web applications.</p>
<p>On each request made from a browser to a server two cookies are set by a servlet filter. One holds the server current time, and another holds session expiration time. Server current time is only needed to calculate an offset between client and server. Session expiration time is then gets periodically checked against the _calculated_ current server time (remember the offset). Each time _any_ request is made to the server the expiration time cookie gets updated, and the whole thing just works.</p>
<p>In practice this method is realized in just three steps:</p>
<p>1. Create a servlet filter that would filter each and every request to your web-application. Configure it in web.xml like this:<br />
<div class="codeblock"><code>	&lt;filter&gt;<br />		&lt;filter-name&gt;SessionTimeoutCookieFilter&lt;/filter-name&gt;<br />		&lt;filter-class&gt;some.package.SessionTimeoutCookieFilter&lt;/filter-class&gt;<br />	&lt;/filter&gt;<br />	&lt;filter-mapping&gt;<br />		&lt;filter-name&gt;SessionTimeoutCookieFilter&lt;/filter-name&gt;<br />		&lt;url-pattern&gt;/*&lt;/url-pattern&gt;<br />	&lt;/filter-mapping&gt;</code></div><br />
Don't worry about you web-app performance - this filter is VERY primitive, all it does is adding two cookies to the response:<br />
<div class="codeblock"><code>	public void doFilter(ServletRequest req, ServletResponse resp, FilterChain filterChain) throws IOException, ServletException {<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; HttpServletResponse httpResp = (HttpServletResponse) resp;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; HttpServletRequest httpReq = (HttpServletRequest) req;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; long currTime = System.currentTimeMillis();<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; long expiryTime = currTime + session.getMaxInactiveInterval() * 1000;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Cookie cookie = new Cookie(&quot;serverTime&quot;, &quot;&quot; + currTime);<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cookie.setPath(&quot;/&quot;);<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; httpResp.addCookie(cookie);<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (httpReq.getRemoteUser() != null) {<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cookie = new Cookie(&quot;sessionExpiry&quot;, &quot;&quot; + expiryTime);<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } else {<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cookie = new Cookie(&quot;sessionExpiry&quot;, &quot;&quot; + currTime);<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cookie.setPath(&quot;/&quot;);<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; httpResponse.addCookie(cookie);<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; filterChain.doFilter(req, resp);<br />	}</code></div><br />
Setting path (to "/" in our case) is very important. If you omit path setting the browser will automatically calculate it from the URL which will result in chaos inside your browser cookies storage.</p>
<p>2. We need a small JavaScript on each window to calculate offset between server and client time. It needs to be run just once, but it wouldn't hurt to run it on each page load:<br />
<div class="codeblock"><code>function calcOffset() {<br />&nbsp;&nbsp;&nbsp; var serverTime = getCookie(&#039;serverTime&#039;);<br />&nbsp;&nbsp;&nbsp; serverTime = serverTime==null ? null : Math.abs(serverTime);<br />&nbsp;&nbsp;&nbsp; var clientTimeOffset = (new Date()).getTime() - serverTime;<br />&nbsp;&nbsp;&nbsp; setCookie(&#039;clientTimeOffset&#039;, clientTimeOffset);<br />}<br /><br />window.onLoad = function() { calcOffset(); };</code></div></p>
<p>3. And finally we need a function that would actually check whether session has timed-out. It needs to be executed periodically, in our case each 10 seconds (or 10000 milliseconds):<br />
<div class="codeblock"><code>function checkSession() {<br />&nbsp;&nbsp;&nbsp; var sessionExpiry = Math.abs(getCookie(&#039;sessionExpiry&#039;));<br />&nbsp;&nbsp;&nbsp; var timeOffset = Math.abs(getCookie(&#039;clientTimeOffset&#039;));<br />&nbsp;&nbsp;&nbsp; var localTime = (new Date()).getTime();<br />&nbsp;&nbsp;&nbsp; if (localTime - timeOffset &gt; (sessionExpiry+15000)) { // 15 extra seconds to make sure<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; window.close();<br />&nbsp;&nbsp;&nbsp; } else {<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; setTimeout(&#039;checkSession()&#039;, 10000);<br />&nbsp;&nbsp;&nbsp; }<br />}</code></div></p>
<p>Indeed, closing browser windows upon session expiry is pure brutality, and it can and should be accompanied by a warning message popping up like 1 minute before session times out.</p>
<p>I'm really interested in receiving your <strong>critical feedback</strong> on my method.</p>
    ]]></content>
  </entry>
  <entry>
    <title>Hope for Oracle, as the axe starts to gently fall</title>
    <link rel="alternate" type="text/html" href="http://www.javaworld.com/community/node/4013" />
    <id>http://www.javaworld.com/community/node/4013</id>
    <published>2010-02-04T17:59:18-05:00</published>
    <updated>2010-02-04T22:03:16-05:00</updated>
    <author>
      <name>Josh Fruhlinger</name>
    </author>
    <category term="Darkstar" />
    <category term="JCP" />
    <category term="Oracle" />
    <summary type="html"><![CDATA[<!--paging_filter--><p>Well, with Oracle having been in control of Java for a week or so, now, and Sun's former CEO <a href="http://twitter.com/OpenJonathan/status/8620937722" target="_blank">quitting via Tweet haiku</a>, we can start to take stock a little of the future of the Java platform.  This <a href="http://java.dzone.com/articles/oracle-and-jcp" target="_blank">JavaLobby roundup for JCP member reactions</a> is intriguing just for its sheer hopefulness about the future of that crucial aspect of the Java community.    ]]></summary>
    <content type="html"><![CDATA[<!--paging_filter--><p>Well, with Oracle having been in control of Java for a week or so, now, and Sun's former CEO <a href="http://twitter.com/OpenJonathan/status/8620937722" target="_blank">quitting via Tweet haiku</a>, we can start to take stock a little of the future of the Java platform.  This <a href="http://java.dzone.com/articles/oracle-and-jcp" target="_blank">JavaLobby roundup for JCP member reactions</a> is intriguing just for its sheer hopefulness about the future of that crucial aspect of the Java community.  Even IBM's Mark Thomas is saying nice things about looking forward to working with Oracle, though they have feeling of being growled through gritted teeth.  Of course, much of that hope centers on a <a href="http://jcp.org/aboutJava/communityprocess/summaries/2007/December07-summary.html" target="_blank">JCP resolution Oracle proposed more than two years ago</a> to open the process up further.  Even the  We'll see how that works out now that Oracle is in control; by their nature, corporations are even less likely to act outside their immediate self-interest than individuals are, even if it makes them obviously hypocritical.</p>

<p>Meanwhile, the rumors of <a href="http://www.javaworld.com/community/node/3935" target="_blank">insanely widespread layoffs</a> seem not to have come true just yet, although my unfiltered feed of blogs.sun.com have featured plenty of "My last day at Sun/Oracle" posts.  One whole project that has been put out to pasture is Darkstar, a Java-language server aimed at MMORPGs and social networking sites.  The Register derided it as a <a href="http://www.theregister.co.uk/2010/02/04/oracle_lights_out_for_project_darkstar/" target="_blank">vanity project</a>, and it seems that these sorts of initiatives will be the ones to go in the first wave.  The question is how the core Java team will be cut, or reorganized.</p>    ]]></content>
  </entry>
  <entry>
    <title>Keeping LOC and Tests in Balance</title>
    <link rel="alternate" type="text/html" href="http://www.javaworld.com/community/node/4010" />
    <id>http://www.javaworld.com/community/node/4010</id>
    <published>2010-02-04T04:25:47-05:00</published>
    <updated>2010-02-04T09:52:21-05:00</updated>
    <author>
      <name>Andrew Binstock</name>
    </author>
    <summary type="html"><![CDATA[<!--paging_filter--><p><a href="http://3.bp.blogspot.com/_tFo9UMOnn4Y/S2qO0Kjy8VI/AAAAAAAAAIc/Ce6WFXlcF1Q/s1600-h/LinesToTestsRatio.gif"></a></p>
    ]]></summary>
    <content type="html"><![CDATA[<!--paging_filter--><p><a href="http://3.bp.blogspot.com/_tFo9UMOnn4Y/S2qO0Kjy8VI/AAAAAAAAAIc/Ce6WFXlcF1Q/s1600-h/LinesToTestsRatio.gif"></a><br />The proliferation of metrics in software development threatens to take important quantitative measures and bury them beneath an avalanche of noisy numbers. Consequently, it's important to look for certain ratios and trends among the numbers to inform you whether a project is healthy. One tell-tale relation links LOCs and number of tests. These two values should grow in direct proportion to each other.</p>
<p>The included diagram presents the ratio of these two values for <a href="http://platypus.pz.org">Platypus</a>, the OSS project I work on.</p>
<p>As you can see, except for a few dips here and there, these numbers have stayed in lock step for the last 18 months. And, as you might expect, code coverage from these tests has similarly remained in fairly narrow range--right around 60%. </p>
<p>The most typical violation of this ratio is, as you would guess, a jump in LOCs without a corresponding rise in tests. This is something managers should watch out for. With a good dashboard, they can tell early on when these trend lines diverge. This is frequently, but not always, always indicative of a problem. (For example, it could be that a lot of code without tests was imported to the project.) Whatever the cause is, managers need to find out and respond accordingly.</p>
<p>(For the record, the tests counted in this diagram include unit tests and functional tests.)</p>
    ]]></content>
  </entry>
  <entry>
    <title>&quot;forking&quot; Glassfish</title>
    <link rel="alternate" type="text/html" href="http://www.javaworld.com/community/node/4003" />
    <id>http://www.javaworld.com/community/node/4003</id>
    <published>2010-02-03T16:46:06-05:00</published>
    <updated>2010-02-03T16:47:56-05:00</updated>
    <author>
      <name>Douglas Dooley</name>
    </author>
    <summary type="html"><![CDATA[<!--paging_filter--><p>i dont buy it, i just refuse to capitulate, and i dont have to as i am not an employee of the world's largest business software provider, and say that Glassfish is a 'departmental' application server, to be upsold to expensive WebLogic licenses, that frankly offers an inferior product to what has been built in the OSS community through the long-gone independent java.net....someone needs to explain to me the art of forking because the only company or organization that i have heard do it have been failures in the form of Apache's attempts with openJDK, and Oracle's attempt to copy Red hat's Ente</p>
    ]]></summary>
    <content type="html"><![CDATA[<!--paging_filter--><p>i dont buy it, i just refuse to capitulate, and i dont have to as i am not an employee of the world's largest business software provider, and say that Glassfish is a 'departmental' application server, to be upsold to expensive WebLogic licenses, that frankly offers an inferior product to what has been built in the OSS community through the long-gone independent java.net....someone needs to explain to me the art of forking because the only company or organization that i have heard do it have been failures in the form of Apache's attempts with openJDK, and Oracle's attempt to copy Red hat's Enterprise Linux....but to me, it is time to explore the option of forking glassfish and continuing to invest in it as a platform for cloud-based, high availability, enterprise-wide and web-wide deployments, that scale beyond a developer's studio....</p>
<p>so, i am calling on those in the industry with the means to look at code, namely the Java developer ranks to get glassfish on to google's Code site, and begin the process of forking the OSS content that has been built over the past four years, since Glassfish's announcement and inception...there is no logical play for Glassfish within Oracle's stated product strategy with Fusion, and therefore there is a significant market opportunity to take the assets under GPL license and form a new entity that will support enterprise-wide deployments, and even build within Google's cloud offerings to make the Reference Implementation the premier app server in the marketplace....makes sense, doesnt it?....</p>
<p>I would say that the most natural person to do the rallying for this is Marc Fleury, himself, though i know it would be a tough sell, as this is not his code base baby, but JBoss has morphed in to something else beyond what he originally started, and perhaps his non-compete is coming up to expiration....just think of the developers he would attract worldwide, if he were to sign-on to a fork of Glassfish, and just think of the resources that could be potentially available from Google, if we were to make an app server cloud oriented, and on their Code site, its like a built-in acquisition guarantee, from the company with the deepest pockets....there is even a scenario where Google will need Glassfish to do business, as they move in to the enterprise, so with some luck and lots of execution, it could be a high, high premium paid for the work of a fork job....</p>
<p>I have also proposed portable applications across JEE servers, perhaps even components for vertical implementations for HIPAA, SWIFT, FMEA, and other industry standards, so the project could grow beyond just an app server platform fork, and include applications that would run on JBoss, and perhaps even WebLogic and WebSphere with some work....but the real test is in finding Glassfish a home, and getting the write-once-run-anywhere promise going with some apps that run in the cloud that can be deployed on compliant platforms from any vendor or company....forking Glassfish means taking on Oracle's stated product strategy, and it is about time someone does it, as Fusion toils along, with no word whatsoever on its delivery schedule....all they do is buy more companies to divert attention from their timelines for release, and now they have kicked the most valuable Sun asset in to the ranks of 'departmental': Fail.....</p>
<p>i ask you all in the industry to consider a world where JBoss is the only JEE 6 app server left for deployments, as WebLogic and WebSphere have basically not even supported JEE 5 yet, within their product-lines, so now we have a uni-tier app server market on standards, with Spring Source being the natural beneficiary of this splintering....it is time for the Java developer ranks to get paid for OSS work and not let companies like Oracle take all the work in-house and mis-market it, with a term that make me cringe: 'departmental'....</p>
<p>take a look around, what do you see in the marketplace other than a lot of invested companies looking for answers on what is next for JEE, and the answer had been coming from the Glassfish org., and now that org. is essentially dead, so forks dont usually work, they are seldom ever attempted, but this one is worth it, this one could introduce Google in to the enterprise and could make the developers on the project a lot of money that Oracle is leaving on the table with their positioning that basically says we will not sell Glassfish, and we will only sell WebLogic....i say to all those not satisfied with the decision, to rise up, and begin to build the ranks of those willing to fork glassfish for the industry to stay afloat of portability....it only takes some momentum to really build something unique, and only you are one of many who could make it possible, so take a look at my site, and tell me i am not on to something....</p>
<p>it is time to fork Glassfish:<br />
<a href="http://code.google.com/p/astrocloud/wiki/PageName" title="http://code.google.com/p/astrocloud/wiki/PageName">http://code.google.com/p/astrocloud/wiki/PageName</a></p>
    ]]></content>
  </entry>
  <entry>
    <title>Introduction to easyb video</title>
    <link rel="alternate" type="text/html" href="http://www.javaworld.com/community/node/3998" />
    <id>http://www.javaworld.com/community/node/3998</id>
    <published>2010-02-02T17:48:14-05:00</published>
    <updated>2010-02-05T06:52:39-05:00</updated>
    <author>
      <name>Andrew Glover</name>
    </author>
    <category term="agile" />
    <category term="Andy" />
    <category term="bdd" />
    <category term="Developer Testing" />
    <category term="development 2.0" />
    <category term="easyb" />
    <category term="Eclipse" />
    <category term="groovy" />
    <category term="java" />
    <category term="JUnit" />
    <category term="open source" />
    <category term="programming" />
    <category term="TDD" />
    <category term="testing" />
    <category term="unit testing" />
    <category term="xunit" />
    <summary type="html"><![CDATA[<!--paging_filter--><p>The <a href="http://easyb.org/">easyb</a> team is pleased to announce the posting of a hip introductory video that demonstrates both <a href="http://easyb.org/howtobb.html">specifications</a> and <a href="http://easyb.org/howtos.html">stories</a> in action. In this 8 and 1/2 minute video, you&#8217;ll learn that easyb enables you to express human readable expectations that verify any Java application (or to be more precise, anything running on the JVM) and that you express those expectations in Groovy.</p>
    ]]></summary>
    <content type="html"><![CDATA[<!--paging_filter--><p>The <a href="http://easyb.org/">easyb</a> team is pleased to announce the posting of a hip introductory video that demonstrates both <a href="http://easyb.org/howtobb.html">specifications</a> and <a href="http://easyb.org/howtos.html">stories</a> in action. In this 8 and 1/2 minute video, you&#8217;ll learn that easyb enables you to express human readable expectations that verify any Java application (or to be more precise, anything running on the JVM) and that you express those expectations in Groovy.</p>
<p>
</p>
<p>If you think easyb concisely expresses expectations (i.e. features or requirements) in a simple, yet readable manner, then what are you waiting for, man? <a href="http://code.google.com/p/easyb/downloads/list">Download it today</a> and see for yourself how easy behavior driven development in Java can get with <a href="http://thediscoblog.com/index.php?s=easyb&amp;submit=Search">easyb</a>! </p>
<p>Looking to spin up Continuous Integration <em>quickly</em>? Check out <a href="http://www.ciinabox.com">www.ciinabox.com</a>.</p>
    ]]></content>
  </entry>
  <entry>
    <title>The iPad and what it means for Java</title>
    <link rel="alternate" type="text/html" href="http://www.javaworld.com/community/node/3996" />
    <id>http://www.javaworld.com/community/node/3996</id>
    <published>2010-02-02T16:43:35-05:00</published>
    <updated>2010-02-02T16:42:58-05:00</updated>
    <author>
      <name>Josh Fruhlinger</name>
    </author>
    <category term="Apple" />
    <category term="iPad" />
    <category term="Java ME" />
    <summary type="html"><![CDATA[<!--paging_filter--><p>In my <a href="http://www.javaworld.com/community/node/3980" target="_blank">last post</a>, I made a little joke about the closing of the Sun-Oracle deal being overshadowed by Apple's big iPad announcement.  The folks at Oracle would almost certainly have preferred that their deal closed perhaps a few days earlier or later, so they wouldn't have to compete with the Apple marketing juggernaut.  The two presentations were aimed at wildly different audiences; but still, there were important (and possibly troubling) hints at Java's future hidden in Apple's messaging.</p>    ]]></summary>
    <content type="html"><![CDATA[<!--paging_filter--><p>In my <a href="http://www.javaworld.com/community/node/3980" target="_blank">last post</a>, I made a little joke about the closing of the Sun-Oracle deal being overshadowed by Apple's big iPad announcement.  The folks at Oracle would almost certainly have preferred that their deal closed perhaps a few days earlier or later, so they wouldn't have to compete with the Apple marketing juggernaut.  The two presentations were aimed at wildly different audiences; but still, there were important (and possibly troubling) hints at Java's future hidden in Apple's messaging.</p>

<p>First, let's consider the biggest knock against the iPad: that's it's just a "big iPod Touch."  One of the big knocks on the iPad and iPhone is that they don't support Flash, but of course, they don't support Java either, and for the same reason -- there will be no runtimes on iPhone OS devices, period. This means that the only Java apps making their way to the iPhone will be those whose code has been converted via <a href="http://www.xmlvm.org/overview/" target="_blank">XMLVM</a> or the like.  The Java community seems to have gotten past its <a href="http://www.javaworld.com/community/node/2085" target="_blank">initial desperate hopes</a> that the iPhone OS would support Java someday and have settled into sullen acquiescence; presumably, they will join the rising time of tech commentary deriding the iPad's hopes.</p>

<p>Believe it or not, I'm on the record as <a href="http://www.itworld.com/mobile-amp-wireless/94680/the-reality-distortion-field-fades-ipad" target="_blank">also deriding those hopes</a>, though I don't think the absence of Java (or Flash) is going to turn out to be a big motivating factor.  Developers have an exaggerated view of the importance of their favorite tools to the success and failure of various platforms, particularly those aimed at consumers.  But the success or failure of the iPad <em>as a device</em> is in some ways besides the point.  As folks like <a href="http://stevenf.tumblr.com/post/359224392/i-need-to-talk-to-you-about-computers-ive-been" target="_blank">Steven Frank</a> and <a href="http://daringfireball.net/2010/01/various_ipad_thoughts" target="_blank">John Gruber</a> have opined, the iPad shows that Apple, at least, sees the future of general-interest computing in platforms that abstract nearly all of what we think of as the typical complexity of using a computer away from the user.</p>

<p>Part of abstracting that complexity away involves ditching things like Flash runtimes and Java VMs.  Google is a major mover behind this movement as well, with their Chrome OS project whose UI will consist solely of a browser window. While Google tend to be more agnostic about plugins, they're moving in parallel with Apple on a push to use JavaScript and HTML 5 for in-browser media and functionality that once would be the province of something like Java.</p>

<p>So Java ME (not counting the nonstandard KVM that underlies Android) has essentially been locked out of the emerging smartphone market, being restricted to the second-tier "<a href="http://www.javaworld.com/community/?q=node/3425" target="_blank">feature phone</a>" market that doesn't have a long-term future.  And if this new model of computing takes hold, Java may be forever banished from the desktop (or the future's desktop equivalents) as well.  Which leaves the server, which is probably never going to be made a slick, locked down appliance -- or if it is, it will be one built by Oracle with Java at its core.  It's not a terrible future for the platform, but it is one that's a lot more limited than the initial vision for it.</p>    ]]></content>
  </entry>
  <entry>
    <title>Basic Java hashCode and equals Demonstrations</title>
    <link rel="alternate" type="text/html" href="http://www.javaworld.com/community/node/3994" />
    <id>http://www.javaworld.com/community/node/3994</id>
    <published>2010-02-01T11:18:00-05:00</published>
    <updated>2010-02-08T04:51:42-05:00</updated>
    <author>
      <name>dmarx</name>
    </author>
    <category term="Dustin" />
    <category term="Java (General)" />
    <summary type="html"><![CDATA[<!--paging_filter--><p>I often like to use this blog to revisit hard-earned lessons in the <a href="http://java.sun.com/docs/books/tutorial/">basics of Java</a>.  This blog post is one such example and focuses on illustration of the dangerous power behind the <a href="http://java.sun.com/javase/6/docs/api/java/lang/Object.html#equals%28java.lang.Object%29">equals(Object)</a> and <a href="http://java.sun.com/javase/6/docs/api/java/lang/Object.html#hashCode%28%29">hashCode()</a> methods.</p>
    ]]></summary>
    <content type="html"><![CDATA[<!--paging_filter--><p>I often like to use this blog to revisit hard-earned lessons in the <a href="http://java.sun.com/docs/books/tutorial/">basics of Java</a>.  This blog post is one such example and focuses on illustration of the dangerous power behind the <a href="http://java.sun.com/javase/6/docs/api/java/lang/Object.html#equals%28java.lang.Object%29">equals(Object)</a> and <a href="http://java.sun.com/javase/6/docs/api/java/lang/Object.html#hashCode%28%29">hashCode()</a> methods.  I won't cover every nuance of these two highly significant methods that all Java objects have whether explicitly declared or implicitly inherited from a parent (possibly directly from <a href="http://java.sun.com/javase/6/docs/api/java/lang/Object.html">Object</a> itself), but I will cover some of the common issues that do arise when these are not implemented or are not implemented correctly.  I also attempt to show by these demonstrations why it is important for careful code reviews, thorough unit testing, and/or tool-based analysis to verify the correctness of these methods' implementations.</p>
<p>Because all Java objects ultimately inherit implementations for <code>equals(Object)</code> and <code>hashCode()</code>, the Java compiler and indeed the Java runtime launcher will report no problem when invoking these "default implementations" of these methods.  Unfortunately, when these methods are needed, the default implementations of these methods (like their cousin the <a href="http://marxsoftware.blogspot.com/2008/11/apache-commons-tostringbuilder.html">toString</a> method) are rarely what are desired.  The <a href="http://java.sun.com/j2se/javadoc/">Javadoc</a>-based API <a href="http://java.sun.com/javase/6/docs/api/java/lang/Object.html">documentation for the Object class</a> discusses the "contract" expected of any implementation of the <code>equals(Object)</code> and <code>hashCode()</code> methods and also discusses the likely default implementation of each if not overridden by child classes.</p>
<p>For the examples in this post, I'll be using the HashAndEquals class whose code listing is shown next to process object instantiations of various Person classes with differing levels of support for <code>hashCode</code> and <code>equals</code> methods.</p>
<p><strong>HashAndEquals.java</strong></p>
<p>package dustin.examples;</p>
<p>import java.util.HashSet;<br />import java.util.Set;<br />import static java.lang.System.out;</p>
<p>public class HashAndEquals<br />{<br />   private static final String HEADER_SEPARATOR =<br />      "======================================================================";</p>
<p>   private static final int HEADER_SEPARATOR_LENGTH = HEADER_SEPARATOR.length();</p>
<p>   private static final String NEW_LINE = System.getProperty("line.separator");</p>
<p>   private final Person person1 = new Person("Flintstone", "Fred");<br />   private final Person person2 = new Person("Rubble", "Barney");<br />   private final Person person3 = new Person("Flintstone", "Fred");<br />   private final Person person4 = new Person("Rubble", "Barney");</p>
<p>   public void displayContents()<br />   {<br />      printHeader("THE CONTENTS OF THE OBJECTS");<br />      out.println("Person 1: " + person1);<br />      out.println("Person 2: " + person2);<br />      out.println("Person 3: " + person3);<br />      out.println("Person 4: " + person4);<br />   }</p>
<p>   public void compareEquality()<br />   {<br />      printHeader("EQUALITY COMPARISONS");<br />      out.println("Person1.equals(Person2): " + person1.equals(person2));<br />      out.println("Person1.equals(Person3): " + person1.equals(person3));<br />      out.println("Person2.equals(Person4): " + person2.equals(person4));<br />   }</p>
<p>   public void compareHashCodes()<br />   {<br />      printHeader("COMPARE HASH CODES");<br />      out.println("Person1.hashCode(): " + person1.hashCode());<br />      out.println("Person2.hashCode(): " + person2.hashCode());<br />      out.println("Person3.hashCode(): " + person3.hashCode());<br />      out.println("Person4.hashCode(): " + person4.hashCode());<br />   }</p>
<p>   public Set addToHashSet()<br />   {<br />      printHeader("ADD ELEMENTS TO SET - ARE THEY ADDED OR THE SAME?");<br />      final Set set = new HashSet();<br />      out.println("Set.add(Person1): " + set.add(person1));<br />      out.println("Set.add(Person2): " + set.add(person2));<br />      out.println("Set.add(Person3): " + set.add(person3));<br />      out.println("Set.add(Person4): " + set.add(person4));<br />      return set;<br />   }</p>
<p>   public void removeFromHashSet(final Set sourceSet)<br />   {<br />      printHeader("REMOVE ELEMENTS FROM SET - CAN THEY BE FOUND TO BE REMOVED?");<br />      out.println("Set.remove(Person1): " + sourceSet.remove(person1));<br />      out.println("Set.remove(Person2): " + sourceSet.remove(person2));<br />      out.println("Set.remove(Person3): " + sourceSet.remove(person3));<br />      out.println("Set.remove(Person4): " + sourceSet.remove(person4));<br />   }</p>
<p>   public static void printHeader(final String headerText)<br />   {<br />      out.println(NEW_LINE);<br />      out.println(HEADER_SEPARATOR);<br />      out.println("= " + headerText);<br />      out.println(HEADER_SEPARATOR);<br />   }</p>
<p>   public static void main(final String[] arguments)<br />   {<br />      final HashAndEquals instance = new HashAndEquals();<br />      instance.displayContents();<br />      instance.compareEquality();<br />      instance.compareHashCodes();<br />      final Set set = instance.addToHashSet();<br />      out.println("Set Before Removals: " + set);<br />      //instance.person1.setFirstName("Bam Bam");<br />      instance.removeFromHashSet(set);<br />      out.println("Set After Removals: " + set);<br />   }<br />}</p>
<p>The class above will be used as-is repeatedly with only one minor change later in the post.  However, the <code>Person</code> class will be changed to reflect the importance of <code>equals</code> and <code>hashCode</code> and to demonstrate how easily it can be to mess these up while at the same time being difficult to track down the problem when there is a mistake.</p>
<p><strong>No Explicit <code>equals</code> or <code>hashCode</code> Methods</strong></p>
<p>The first version of the <code>Person</code> class does not provide an explicit overridden version of either the <code>equals</code> method or the <code>hashCode</code> method.  This will demonstrate the "default implementation" of each of these methods inherited from <code>Object</code>.  Here is the source code for <code>Person</code> without <code>hashCode</code> or <code>equals</code> explicitly overridden.</p>
<p><strong>Person.java (no explicit hashCode or equals method)</strong></p>
<p>package dustin.examples;</p>
<p>public class Person<br />{<br />   private final String lastName;<br />   private final String firstName;</p>
<p>   public Person(final String newLastName, final String newFirstName)<br />   {<br />      this.lastName = newLastName;<br />      this.firstName = newFirstName;<br />   }</p>
<p>   @Override<br />   public String toString()<br />   {<br />      return this.firstName + " " + this.lastName;<br />   }<br />}</p>
<p>This first version of <code>Person</code> does not provide get/set methods and does not provide <code>equals</code> or <code>hashCode</code> implementations.  When the main demonstration class <code>HashAndEquals</code> is executed with instances of this <code>equals</code>-less and <code>hashCode</code>-less <code>Person</code> class, the results appear as shown in the next screen snapshot.</p>
<p><a href="http://3.bp.blogspot.com/_sDOe5HxTdMk/S2ZlOnkkSJI/AAAAAAAABf4/KeIeXjM5s6Q/s1600-h/hashCodeEqualsOutput-01-defaultImplementations.png"></a></p>
<p>Several observations can be made from the output shown above.  First, without explicit implementation of an <code>equals(Object)</code> method, none of the instances of <code>Person</code> are considered equal, even when all attributes of the instances (the two Strings) are identical.  This is because, as is explained in the <a href="http://java.sun.com/javase/6/docs/api/java/lang/Object.html#equals%28java.lang.Object%29">documentation for Object.equals(Object)</a>, the default <code>equals</code> implementation is based on an exact reference match:</p>
<p>The equals method for class Object implements the most discriminating possible equivalence relation on objects; that is, for any non-null reference values x and y, this method returns true if and only if x and y refer to the same object (x == y has the value true). </p>
<p>A second observation from this first example is that the hash code is different for each instance of the <code>Person</code> object even when two instances share the same values for all of their attributes.  The <a href="http://java.sun.com/javase/6/docs/api/java/util/HashSet.html">HashSet</a> returns <code>true</code> when a "unique" object is added (<a href="http://java.sun.com/javase/6/docs/api/java/util/HashSet.html#add%28E%29">HashSet.add</a>) to the set or <code>false</code> if the added object is not considered unique and so is not added.  Similarly, the <code>HashSet</code>'s <a href="http://java.sun.com/javase/6/docs/api/java/util/HashSet.html#remove%28java.lang.Object%29">remove</a> method returns <code>true</code> if the provided object is considered found and removed or <code>false</code> if the specified object is considered to not be part of the <code>HashSet</code> and so cannot be removed.  Because the <code>equals</code> and <code>hashCode</code> inherited default methods treat these instances as completely different, it is no surprise that all are added to the set and all are successfully removed from the set.</p>
<p><strong>Explicit <code>equals</code> Method Only</strong></p>
<p>The second version of the <code>Person</code> class includes an explicitly overridden <code>equals</code> method as shown in the next code listing.</p>
<p><strong>Person.java (explicit equals method provided)</strong></p>
<p>package dustin.examples;</p>
<p>public class Person<br />{<br />   private final String lastName;<br />   private final String firstName;</p>
<p>   public Person(final String newLastName, final String newFirstName)<br />   {<br />      this.lastName = newLastName;<br />      this.firstName = newFirstName;<br />   }</p>
<p>   @Override<br />   public boolean equals(Object obj)<br />   {<br />      if (obj == null)<br />      {<br />         return false;<br />      }<br />      if (this == obj)<br />      {<br />         return true;<br />      }<br />      if (this.getClass() != obj.getClass())<br />      {<br />         return false;<br />      }<br />      final Person other = (Person) obj; <br />      if (this.lastName == null ? other.lastName != null : !this.lastName.equals(other.lastName))<br />      {<br />         return false;<br />      }<br />      if (this.firstName == null ? other.firstName != null : !this.firstName.equals(other.firstName))<br />      {<br />         return false;<br />      }<br />      return true;<br />   }</p>
<p>   @Override<br />   public String toString()<br />   {<br />      return this.firstName + " " + this.lastName;<br />   }<br />}</p>
<p>When instances of this <code>Person</code> with <code>equals(Object)</code> explicitly defined are used, the output is as shown in the next screen snapshot.</p>
<p><a href="http://4.bp.blogspot.com/_sDOe5HxTdMk/S2ZqZi5liyI/AAAAAAAABgA/TGzTiW3GkpI/s1600-h/hashCodeEqualsOutput-02-overriddenEqualsDefaultHashCode.png"></a></p>
<p>The first observation is that now the <code>equals</code> calls on the <code>Person</code> instances do indeed return <code>true</code> when the object is equal in terms of all attributes being the same rather than checking for a strict reference equality.  This demonstrates that the custom <code>equals</code> implementation on <code>Person</code> has done its job.  The second observation is that implementation of the <code>equals</code> method has had no effect on the ability to add and remove the seemingly same object to the <code>HashSet</code>.</p>
<p><strong>Explicit <code>equals</code> and <code>hashCode</code> Methods</strong></p>
<p>It is now time to add an explicit <code>hashCode()</code> method to the <code>Person</code> class.  Indeed, this really should have been done when the <code>equals</code> method was implemented.  The reason for this is stated in the documentation for the <code>Object.equals(Object)</code> method:</p>
<p>Note that it is generally necessary to override the hashCode  method whenever this method is overridden, so as to maintain the general contract for the hashCode method, which states that equal objects must have equal hash codes. </p>
<p>Here is <code>Person</code> with an explicitly implemented <code>hashCode</code> method based on the same attributes of <code>Person</code> as the <code>equals</code> method.</p>
<p><strong>Person.java (explicit equals and hashCode implementations)</strong></p>
<p>package dustin.examples;</p>
<p>public class Person<br />{<br />   private final String lastName;<br />   private final String firstName;</p>
<p>   public Person(final String newLastName, final String newFirstName)<br />   {<br />      this.lastName = newLastName;<br />      this.firstName = newFirstName;<br />   }</p>
<p>   @Override<br />   public int hashCode()<br />   {<br />      return lastName.hashCode() + firstName.hashCode();<br />   }</p>
<p>   @Override<br />   public boolean equals(Object obj)<br />   {<br />      if (obj == null)<br />      {<br />         return false;<br />      }<br />      if (this == obj)<br />      {<br />         return true;<br />      }<br />      if (this.getClass() != obj.getClass())<br />      {<br />         return false;<br />      }<br />      final Person other = (Person) obj; <br />      if (this.lastName == null ? other.lastName != null : !this.lastName.equals(other.lastName))<br />      {<br />         return false;<br />      }<br />      if (this.firstName == null ? other.firstName != null : !this.firstName.equals(other.firstName))<br />      {<br />         return false;<br />      }<br />      return true;<br />   }</p>
<p>   @Override<br />   public String toString()<br />   {<br />      return this.firstName + " " + this.lastName;<br />   }<br />}</p>
<p>The output from running with the new <code>Person</code> class with <code>hashCode</code> and <code>equals</code> methods is shown next.</p>
<p><a href="http://1.bp.blogspot.com/_sDOe5HxTdMk/S2ZsYFZYZ1I/AAAAAAAABgI/NTgwJ6yGafg/s1600-h/hashCodeEqualsOutput-03-overriddenEqualsAndHashCode.png"></a></p>
<p>It is not surprising that the hash codes returned for objects with the same attributes' values are now the same, but the more interesting observation is that we can only add two of the four instances to the <code>HashSet</code> now.  This is because the third and fourth add attempts are considered to be attempting to add an object that was already added to the set.  Because there were only two added, only two can be found and removed.</p>
<p><strong>The Trouble with Mutable hashCode Attributes</strong></p>
<p>For the fourth and final example in this post, I look at what happens when the <code>hashCode</code> implementation is based on an attribute that changes.  For this example, a <code>setFirstName</code> method is added to <code>Person</code> and the <code>final</code> modifier is removed from its <code>firstName</code> attribute.  In addition, the main HashAndEquals class needs to have the comment removed from the line that invokes this new set method.  The new version of <code>Person</code> is shown next.</p>
<p>package dustin.examples;</p>
<p>public class Person<br />{<br />   private final String lastName;<br />   private String firstName;</p>
<p>   public Person(final String newLastName, final String newFirstName)<br />   {<br />      this.lastName = newLastName;<br />      this.firstName = newFirstName;<br />   }</p>
<p>   @Override<br />   public int hashCode()<br />   {<br />      return lastName.hashCode() + firstName.hashCode();<br />   }</p>
<p>   public void setFirstName(final String newFirstName)<br />   {<br />      this.firstName = newFirstName;<br />   }</p>
<p>   @Override<br />   public boolean equals(Object obj)<br />   {<br />      if (obj == null)<br />      {<br />         return false;<br />      }<br />      if (this == obj)<br />      {<br />         return true;<br />      }<br />      if (this.getClass() != obj.getClass())<br />      {<br />         return false;<br />      }<br />      final Person other = (Person) obj; <br />      if (this.lastName == null ? other.lastName != null : !this.lastName.equals(other.lastName))<br />      {<br />         return false;<br />      }<br />      if (this.firstName == null ? other.firstName != null : !this.firstName.equals(other.firstName))<br />      {<br />         return false;<br />      }<br />      return true;<br />   }</p>
<p>   @Override<br />   public String toString()<br />   {<br />      return this.firstName + " " + this.lastName;<br />   }<br />}</p>
<p>Output generated from running this example is shown next.</p>
<p><a href="http://1.bp.blogspot.com/_sDOe5HxTdMk/S2ZvITH7P8I/AAAAAAAABgQ/d4qFPlNWfm4/s1600-h/hashCodeEqualsOutput-04-changingHashCodeProblem.png"></a></p>
<p>The most interesting observation in this example is that although two instances get added to the set, only one gets removed.  This is because one of the attributes upon which the hash code is based, first name, changes in between adding the object to the <code>HashSet</code> and attempting to remove the same object (albeit with a changed first name attribute) from the same <code>HashSet</code>.  This illustrates the importance of implementing <code>hashcode</code> (and by extension, <code>equals</code>) on immutable values.  More details regarding this can be found in blog posts <a href="http://boriskirzner.wordpress.com/2006/10/01/hashsetcontains-does-your-busket-contain-something/">HashSet.contains(): does your busket contain something?</a> and <a href="http://boriskirzner.wordpress.com/2007/05/29/back-to-hashcode-mutability/">Back to hashCode Mutability</a>.</p>
<p><strong>Detecting Problems Related to hashCode and equals Implementations</strong></p>
<p>As the examples in this post demonstrate, different behaviors occur depending on how these methods are implemented, but none of them involve an obvious error or warning.  This can make it difficult to track down seemingly inconsistent or strange behavior.  The best way to address this is with careful implementation of these methods, careful reviews of these important methods, and thorough testing.  Another useful tactic is to avoid <a href="http://www.javaworld.com/javaworld/jw-09-2003/jw-0905-toolbox.html">mindless creation</a> of unnecessary "set" methods for all of a class's data attributes.  If a "set" method is truly appropriate, that attribute should not be used in the implementation of <code>equals</code> or <code>hashCode</code>.</p>
<p><strong>Conclusion</strong></p>
<p>It is almost never appropriate to rely on an object's default implementation of <code>hashCode</code> and <code>equals</code> as inherited from <code>Object</code>.  Furthermore, the <code>equals</code> and <code>hashCode</code> methods should be implemented to the contract advertised in the Javadoc documentation (only a small part of which was covered here) and should not be based on any attributes that will be changed (mutable) during the lifecycle of the instance.</p>
    ]]></content>
  </entry>
  <entry>
    <title>Nav4All shuts down</title>
    <link rel="alternate" type="text/html" href="http://www.javaworld.com/community/node/3990" />
    <id>http://www.javaworld.com/community/node/3990</id>
    <published>2010-01-31T14:38:03-05:00</published>
    <updated>2010-01-31T19:07:50-05:00</updated>
    <author>
      <name>Oleg Mikheev</name>
    </author>
    <category term="google maps" />
    <category term="maps" />
    <summary type="html"><![CDATA[<!--paging_filter--><p>Just received a sad newsletter from Nav4All -- they are shutting down the business due to map data licensing issues with Navteq.</p>
<p>Nav4All was a navigation software for mobile devices including Android.</p>
<p>This is probably a good example of how an acquisition can influence the industry. Navteq was acquired by Nokia some time ago.</p>
<p>Also this news can probably explain why Google ditched its map data provider Tele Atlas recently, even despite the fact that it signed a five years contract the year before. Tele Atlas was purchased by TomTom.</p>
<p>Here is the letter itself:</p>
<p>Dear Customers,</p>
    ]]></summary>
    <content type="html"><![CDATA[<!--paging_filter--><p>Just received a sad newsletter from Nav4All -- they are shutting down the business due to map data licensing issues with Navteq.</p>
<p>Nav4All was a navigation software for mobile devices including Android.</p>
<p>This is probably a good example of how an acquisition can influence the industry. Navteq was acquired by Nokia some time ago.</p>
<p>Also this news can probably explain why Google ditched its map data provider Tele Atlas recently, even despite the fact that it signed a five years contract the year before. Tele Atlas was purchased by TomTom.</p>
<p>Here is the letter itself:</p>
<p>Dear Customers,</p>
<p>It is with the deepest regret that we hereby notify you that the<br />
global navigation of Nav4All and the Tracking &amp; Tracing will go<br />
offline in 3 days. The reason for the same is that the data licence<br />
agreement with Navteq (a 100% Nokia subsidiary) was not extended, in<br />
a totally unexpected manner. It is not possible to implement data<br />
from another supplier in our Nav4All systems within the short term.<br />
The Nav4All navigation system was developed for Navteq data. Nav4All<br />
has therefore been constrained to stop.</p>
<p>We greatly regret the fact that we have to suspend the operation of<br />
our service. With your help, we have developed Nav4All into a global<br />
product with 27.5 million users in 56 languages, in 5 years. This has<br />
made Nav4All the largest navigation supplier. This large number of<br />
users also has to do with the fact that Nav4All works on hundreds of<br />
different mobile telephones of many makes such as Blackberry, Sony<br />
Ericsson, Samsung, Motorola, Android, HTC, Nokia, LG, Iphone, Ipod<br />
etc.</p>
<p>After 5 years of testing and market development, we witnessed rapid -<br />
in fact, exponential - growth during the last two years. That growth<br />
was reported in the licence reports to Navteq. In mid-December 2009,<br />
the global coverage was extended to include the Philippines, Morocco<br />
and Kenya.</p>
<p>Please contact the Nav4All support desk in case you have any<br />
questions: <a href="http://www.nav4all.com/support" title="www.nav4all.com/support">www.nav4all.com/support</a>. If there is any further<br />
information from Nav4All concerning the subject of this letter, the<br />
same will be published on our website: <a href="http://www.nav4all.com" title="www.nav4all.com">www.nav4all.com</a>. For reasons<br />
of privacy, Nav4All does not have the email addresses of all its<br />
customers, and we therefore request you to forward this email to the<br />
maximum extent possible, in order to ensure that everyone is<br />
informed.</p>
<p>Kind Regards,</p>
<p>Hennie J.M. Groot Koerkamp (CEO)</p>
<p>Nav4All BV </p>
<p>Keizersgracht 62-64 </p>
<p>1015 CS Amsterdam NL</p>
    ]]></content>
  </entry>
  <entry>
    <title>Groovy and SQL*Plus</title>
    <link rel="alternate" type="text/html" href="http://www.javaworld.com/community/node/3989" />
    <id>http://www.javaworld.com/community/node/3989</id>
    <published>2010-01-31T00:11:00-05:00</published>
    <updated>2010-02-08T04:51:45-05:00</updated>
    <author>
      <name>dmarx</name>
    </author>
    <category term="Dustin" />
    <category term="groovy" />
    <category term="Oracle" />
    <summary type="html"><![CDATA[<!--paging_filter--><p><a href="http://www.oracle.com/index.html">Oracle</a>'s <a href="http://www.oracle.com/technology/tech/sql_plus/index.html">SQL*Plus</a> is not as user-friendly as <a href="http://www.oracle.com/technology/products/database/sql_developer/index.html">SQL Developer</a> or <a href="http://www.oracle.com/technology/products/jdev/collateral/papers/11/newfeatures/index.html">JDeveloper</a> for manipulating data in an <a href="http://www.oracle.com/us/products/database/index.htm">Oracle database</a>, but it is still <a></p>
    ]]></summary>
    <content type="html"><![CDATA[<!--paging_filter--><p><a href="http://www.oracle.com/index.html">Oracle</a>'s <a href="http://www.oracle.com/technology/tech/sql_plus/index.html">SQL*Plus</a> is not as user-friendly as <a href="http://www.oracle.com/technology/products/database/sql_developer/index.html">SQL Developer</a> or <a href="http://www.oracle.com/technology/products/jdev/collateral/papers/11/newfeatures/index.html">JDeveloper</a> for manipulating data in an <a href="http://www.oracle.com/us/products/database/index.htm">Oracle database</a>, but it is still <a href="http://marxsoftware.blogspot.com/2009/08/faithful-old-development-tools.html">commonly used</a>.  In fact, <a href="http://cisnet.baruch.cuny.edu/holowczak/oracle/sqlplus/">SQL*Plus</a> is often preferred to the tools with fancier user interfaces for quick and dirty manipulation, for running SQL scripts, for being run as part of shell scripts, and for other non-interactive uses.  Furthermore, Oracle's <a href="http://www.oracle.com/technology/tech/sql_plus/index.html">SQL*Plus page</a> calls <a href="http://en.wikipedia.org/wiki/SQL*Plus">SQL*Plus</a> "the primary interface to the Oracle Database server."  In this blog post, I look at using <a href="http://ss64.com/ora/syntax-sqlplus.html">SQL*Plus</a> in conjunction with <a href="http://groovy.codehaus.org/">Groovy</a> scripts.</p>
<p>Groovy's <a href="http://groovy.codehaus.org/Process+Management">Process Management</a> plays the most significant role in the process of using Groovy with SQL*Plus.  This functionality is easily applied thanks to the <a href="http://groovy.codehaus.org/groovy-jdk/">GDK</a>'s <a href="http://marxsoftware.blogspot.com/2009/09/groovy-jdk-gdk-string-support.html">String extension</a> that includes an <a href="http://groovy.codehaus.org/groovy-jdk/java/lang/String.html#execute()">execute()</a> method which returns a <a href="http://java.sun.com/javase/6/docs/api/java/lang/Process.html">Process</a> running the command contained within that GDK-extended String.  This mechanism will be used repeatedly in the examples in this blog post.</p>
<p><strong>Notes Related Specifically to Windows/Vista</strong></p>
<p>Many books, blogs, and articles on using Groovy with Windows rightly point out that some of the commonly used commands in Windows are not actually executables, but are instead built-in commands.  These commands must be prepended with <a href="http://stackoverflow.com/questions/515309/what-does-cmd-c-mean">cmd /C</a> to be executed appropriately.  However, this step is unnecessary when invoking SQL*Plus from Groovy on Windows because SQL*Plus is a separate executable.</p>
<p>When using Vista, it is important to run SQL*Plus with the appropriate privileges.  Because the Groovy scripts in this post run SQL*Plus, the scripts should be executed in a console window that is being run under administrative privileges.  An error message including the phrase "The requested operation requires elevation" is seen when the Groovy scripts using SQL*Plus are executed without the appropriate privileges.  This is demonstrated in the next screen snapshot.</p>
<p><a href="http://2.bp.blogspot.com/_sDOe5HxTdMk/S2To0-JwZlI/AAAAAAAABew/Vx_QZ_oIM40/s1600-h/groovyProcessExecuteSqlPlus-requestedOperationRequiresElevation.png"></a></p>
<p><strong>Example 1: Basic Query Statement</strong></p>
<p>One of the easiest ways of using SQL*Plus is to write a file containing commands to be run within SQL*Plus.  These files often end with the <code>.sql</code> suffix and are typically executed in SQL*Plus by prefacing the name of script file (with path if the file is not in the current working directory) with the <code>@</code> symbol.</p>
<p>In this first example, that SQL*Plus script file is called <code>01-employeeIds.sql</code> and is a single-line script:</p>
<p><strong>01-employeeIds.sql</strong></p>
<p>select employee_id from employees;</p>
<p>The above script is expected to be executed against the <a href="http://marxsoftware.blogspot.com/2008/01/oracle-database-sample-schemas.html">HR sample schema</a> that is supplied with most modern versions of the Oracle database.  The Groovy to run this SQL*Plus script file is contained in the next code listing for <code>sql01.groovy</code>:</p>
<p><strong>sql01.groovy</strong></p>
<p>#!/usr/bin/env groovy<br />def sqlplusCmd = "sqlplus hr/hr @01-employeeIds.sql".execute()<br />println "${sqlplusCmd.class}"<br />sqlplusCmd.in.eachLine { line -&gt; println line }</p>
<p>The above Groovy script specifies an explicit call to SQL*Plus with the 'hr' username and associated 'hr' password.  It then runs the script showed above by prefacing its name with the "@" symbol.  All of this is included in a GDK-extended String upon which the <code>execute()</code> method is invoked.  The object returned from that call is a GDK-extended Process instance (as illustrated by the println call below its instantiation) from which each line is printed.</p>
<p>The initial results of running the above script are shown in the next screen snapshot:</p>
<p><a href="http://2.bp.blogspot.com/_sDOe5HxTdMk/S2TuHTyGcOI/AAAAAAAABe4/v9-9iUMxg7k/s1600-h/sql01Output-bannerProcessImplEtc.png"></a></p>
<p>In the above output, the first of many rows returned from the database are shown after the SQL*Plus banner is displayed and the line showing that the GDK <code>String.execute()</code> method returns a <code>java.lang.ProcessImpl</code> is displayed.</p>
<p>When run as shown above, this script never ends because the SQL*Plus session is never exited.  This is easily addressed by adding the word "exit" to the end of the SQL script, which will be done in the next example.  The next screen snapshot demonstrates how the script appears to "hang" when the SQL*Plus session has not been explicitly exited.</p>
<p><a href="http://4.bp.blogspot.com/_sDOe5HxTdMk/S2TvPsOnvyI/AAAAAAAABfA/h-9o3Hc7bsw/s1600-h/sql01Output-bannerProcessImpNotEndedBottom.png"></a></p>
<p><strong>Example 2: Cleaning Up SQL*Plus's Output</strong></p>
<p>We often don't want the SQL*Plus banner to be displayed when running SQL*Plus via script.  SQL*Plus provides the <code>-S</code> option to suppress this banner.  The next version of the Groovy script will take advantage of that as shown in <code>sql02.groovy</code>:</p>
<p><strong>sql02.groovy</strong></p>
<p>#!/usr/bin/env groovy<br />def sqlplusCmd = "sqlplus -S hr/hr @02-employeeIds.sql".execute()<br />sqlplusCmd.in.eachLine { line -&gt; println line }</p>
<p>The only changes to this script from the Groovy script in the first example is the addition of <code>-S</code> to suppress SQL*Plus's banner and the calling of a different SQL script file (<code>02-employeeIds.sql</code>), which will be shown next.</p>
<p>When running SQL*Plus with scripts, it is often convenient to suppress other portions of feedback in addition to suppressing the SQL*Plus banner.  Many of these types of output are turned off or suppressed within SQL*Plus by using the <code>SET</code> <a href="http://ss64.com/ora/syntax-sqlplus-set.html">command</a> to set the relevant property to a desired value.  The code listing for <code>02-employeeIds.sql</code> demonstrates turning off query results feedback, header information, and page separation in the SQL*Plus results and also explicitly exits from SQL*Plus.</p>
<p><strong>02-employeeIds.sql</strong></p>
<p>-- HEADING off turns off column headings in output of query results<br />set HEADING off<br />-- FEEDBACK turns off message saying number of rows returned<br />set FEEDBACK off<br />-- PAGESIZE to 0 removes spaces between "pages" of results<br />set PAGESIZE 0</p>
<p>select employee_id from employees;</p>
<p>-- Return SQL*Plus settings to defaults<br />set HEADING on<br />set FEEDBACK 6<br />set PAGESIZE 14<br />exit</p>
<p>The next two screen snapshots demonstrate the leaner/cleaner output with the SQL*Plus banner suppressed along with no headers, no page separation, and no feedback.  The second of the images proves that <code>exit</code> successfully exits SQL*Plus (and the invoking Groovy script).</p>
<p><a href="http://2.bp.blogspot.com/_sDOe5HxTdMk/S2T2SwUg_jI/AAAAAAAABfI/PbvXHOCG5As/s1600-h/sql02OutputTopCleanSqlPlusOutput.png"></a></p>
<p><a href="http://1.bp.blogspot.com/_sDOe5HxTdMk/S2T2g77jkMI/AAAAAAAABfQ/yznxFajsrDg/s1600-h/sql02OutputBottomCleanSqlPlusEndsOutput.png"></a></p>
<p><strong>Example 3: Passing in Parameter</strong></p>
<p>It is often the case that we want to run SQL*Plus scripts depending on parameters set dynamically when the script is executed.  SQL*Plus accepts command-line parameters and this feature is leveraged in the next SQL script listing.</p>
<p><strong>03-employeeFind.sql</strong></p>
<p>-- HEADING off turns off column headings in output of query results<br />set HEADING off<br />-- FEEDBACK turns off message saying number of rows returned<br />set FEEDBACK off<br />-- PAGESIZE to 0 removes spaces between "pages" of results<br />set PAGESIZE 0</p>
<p>select first_name || ' ' || last_name "NAME" from employees where employee_id = &amp;1;</p>
<p>-- Return SQL*Plus settings to defaults<br />set HEADING on<br />set FEEDBACK 6<br />set PAGESIZE 14<br />exit</p>
<p>In the above script, an employee's full name is returned based on a provided parameter (<code>&amp;amp;1</code>) representing the employee's ID.  To support this expected parameter, the Groovy script must supply that ID to the SQL*Plus script.  This is shown in the Groovy script <code>sql03.sql</code>:</p>
<p><strong>sql03.grooy</strong></p>
<p>#!/usr/bin/env groovy<br />if (!args)<br />{<br />   println "You must supply the ID of the employee of interest."<br />   System.exit(-1)<br />}<br />def sqlplusCmd = "sqlplus -S hr/hr @03-employeeFind.sql ${args[0]}".execute()<br />sqlplusCmd.in.eachLine { line -&gt; println line }</p>
<p>This Groovy script exits promptly if no parameter is provided and informs the user that an ID is required.  If the ID is provided, it is passed to the SQL*Plus script by appending it to the end of the SQL*Plus invocation.  The output from this is shown next.</p>
<p><a href="http://1.bp.blogspot.com/_sDOe5HxTdMk/S2T9bTcdvKI/AAAAAAAABfY/Su1y8iecLOE/s1600-h/sql03OutputSubstitutionExplicitlyShown.png"></a></p>
<p>The screen snapshot just shown demonstrates that the parameter passing from command-line to Groovy script to SQL*Plus worked fine.  The only downside is that the SQL*Plus variable substitution is explicitly shown and this may not always be desirable.  I can turn this off by specifying <code>set VERIFY off</code> in the SQL*Plus script.  When I do that, the output is as shown next.</p>
<p><a href="http://3.bp.blogspot.com/_sDOe5HxTdMk/S2T-wMUMYJI/AAAAAAAABfg/hwUO5jmLMVk/s1600-h/sql03OutputSubstitutionNotShownVerifyOff.png"></a></p>
<p><strong>Example 4: Handling Return SQL*Plus Return Code</strong></p>
<p>Because Groovy's process management capability uses a GDK-extended <a href="http://groovy.codehaus.org/groovy-jdk/java/lang/Process.html">Process</a>, the <a href="http://java.sun.com/javase/6/docs/api/java/lang/Process.html#exitValue()">Process.exitValue()</a> method can be used to access the codes returned from invoked commands.  This allows the Groovy code to analyze the codes returned from the invoked  script and act appropriately.</p>
<p>To illustrate this, the SQL*Plus script used above is modified to return a -4.  The modified version is shown next.</p>
<p><strong>04-employeeFind.sql</strong></p>
<p>-- HEADING off turns off column headings in output of query results<br />set HEADING off<br />-- FEEDBACK turns off message saying number of rows returned<br />set FEEDBACK off<br />-- PAGESIZE to 0 removes spaces between "pages" of results<br />set PAGESIZE 0<br />-- VERIFY off turns off the prompts showing variable substitution<br />set VERIFY off</p>
<p>select first_name || ' ' || last_name "NAME" from employees where employee_id = &amp;1;</p>
<p>-- Return SQL*Plus settings to defaults<br />set HEADING on<br />set FEEDBACK 6<br />set PAGESIZE 14<br />set VERIFY on</p>
<p>-- Pretend there is some type of error or other condition in which the returning<br />-- of -4 is appropriate<br />exit -4</p>
<p>The modified Groovy script that takes advantage of <code>Process.exitValue()</code> to process this returned code is shown next.  This Groovy script simply prints the value, but it could implement alternative logic based on the code returned.</p>
<p><strong>sql04.groovy</strong></p>
<p>#!/usr/bin/env groovy<br />if (!args)<br />{<br />   println "You must supply the ID of the employee of interest."<br />   System.exit(-1)<br />}<br />def sqlplusCmd = "sqlplus -S hr/hr @04-employeeFind.sql ${args[0]}".execute()<br />sqlplusCmd.in.eachLine { line -&gt; println line }<br />println "Return value: ${sqlplusCmd.exitValue()}"</p>
<p>When this script is executed, its output looks like that shown in the next screen snapshot.</p>
<p><a href="http://3.bp.blogspot.com/_sDOe5HxTdMk/S2UR4poxziI/AAAAAAAABfo/TBa1-Z4psIc/s1600-h/sql04OutputErrorReturnCode.png"></a></p>
<p><strong>Example 5: Processing Significant SQL*Plus Output</strong></p>
<p>There are times when the Groovy script needs to process more than a return code.  One way of doing this is to have the SQL*Plus script write out data to an external file that the Groovy script can process.  It helps, of course, that Groovy provides some nice <a href="http://marxsoftware.blogspot.com/2009/09/groovy-jdk-gdk-more-file-fun.html">GDK File extensions</a>.</p>
<p>The SQL*Plus SPOOL command can be used to spool output to an operating system file.</p>
<p>An example of a SQL*Plus script that writes employee last names and first names, separated by a comma, on individual lines of a file named "name.txt" is shown next.</p>
<p><strong>05-employeeList.sql</strong></p>
<p>-- HEADING off turns off column headings in output of query results<br />set HEADING off<br />-- FEEDBACK turns off message saying number of rows returned<br />set FEEDBACK off<br />-- PAGESIZE to 0 removes spaces between "pages" of results<br />set PAGESIZE 0<br />-- VERIFY off turns off the prompts showing variable substitution<br />set VERIFY off<br />-- COLSEP sets character to be placed between returned columns<br />set COLSEP ,<br />-- TRIMSPOOL affects spooling output only; not SQL*Plus<br />set TRIMSPOOL on</p>
<p>spool name.txt<br />select last_name, first_name from employees;<br />spool off</p>
<p>-- Return SQL*Plus settings to defaults<br />set HEADING on<br />set FEEDBACK 6<br />set PAGESIZE 14<br />set VERIFY on<br />set COLSEP " "<br />set TRIMSPOOL off<br />exit</p>
<p>When a Groovy script runs this file, the expected file named <code>name.txt</code> is generated.  A portion of it (top and bottom) is shown next.</p>
<p>Abel                     ,Ellen<br />Ande                     ,Sundar<br />Atkinson                 ,Mozhe<br />Austin                   ,David<br />Baer                     ,Hermann<br />Baida                    ,Shelli<br />Banda                    ,Amit<br />Bates                    ,Elizabeth<br />Bell                     ,Sarah<br />Bernstein                ,David<br />Bissot                   ,Laura<br />Bloom                    ,Harrison<br />Bull                     ,Alexis</p>
<p>. . .</p>
<p>Vollman                  ,Shanta<br />Walsh                    ,Alana<br />Weiss                    ,Matthew<br />Whalen                   ,Jennifer<br />Zlotkey                  ,Eleni</p>
<p>This output file can be processed in the same Groovy script that caused it to be generated.  Groovy provides rich features for file handling and String manipulation, making it highly effective in this situation.</p>
<p><strong>sql05.groovy</strong></p>
<p>#!/usr/bin/env groovy<br />def sqlplusCmd = "sqlplus -S hr/hr @05-employeeList.sql".execute()<br />def file = new File("name.txt")<br />file.eachLine { println "Name (Last, First): ${it}" }</p>
<p>Much more sophisticated logic could be performed on the contents of the <code>name.txt</code> file, but this demonstrates how easy it is to fix the spooled output with the Groovy script.  The bottom portion of the script's execution is shown in the next screen snapshot.</p>
<p><a href="http://2.bp.blogspot.com/_sDOe5HxTdMk/S2UaYfJZGCI/AAAAAAAABfw/QSLtHOXSprQ/s1600-h/sql05OutputTop.png"></a></p>
<p><strong>Conclusion and Final Remarks</strong></p>
<p>Groovy and SQL*Plus can be used effectively together to script data-related functionality.  Groovy offers many of the advantages of traditional scripting languages when used in conjunction with SQL*Plus, but offers the added advantage of enjoying full access to the JVM and the plethora of Java libraries.  Groovy can be used with existing SQL*Plus scripts or can access the database directly with its powerful and convenient <a href="http://java.sun.com/docs/books/tutorial/jdbc/">JDBC</a> abstraction <a href="http://marxsoftware.blogspot.com/2009/05/groovysql-groovy-jdbc.html">GroovySql</a>.</p>
    ]]></content>
  </entry>
  <entry>
    <title>My Personal Blog Policies</title>
    <link rel="alternate" type="text/html" href="http://www.javaworld.com/community/node/3987" />
    <id>http://www.javaworld.com/community/node/3987</id>
    <published>2010-01-30T12:10:00-05:00</published>
    <updated>2010-02-08T04:51:45-05:00</updated>
    <author>
      <name>dmarx</name>
    </author>
    <category term="blogging" />
    <category term="Dustin" />
    <category term="General Development" />
    <summary type="html"><![CDATA[<!--paging_filter--><p>The recent posting of the <a href="http://blogs.oracle.com/otn/2010/01/the_oracle_social_media_partic.html#comments">The Oracle Social Media Participation Policy</a> on <a href="http://blogs.oracle.com/otn/">Justin Kestelyn</a>'s (<a href="http://www.oracle.com/technology/index.html">Oracle Technology Network</a> Editor in Chief) <a href="http://blogs.oracle.com/otn/">blog</a> has caused me to reevaluate my own personal and previously unwritten policies that affect how and what I write in this blog.</p>
    ]]></summary>
    <content type="html"><![CDATA[<!--paging_filter--><p>The recent posting of the <a href="http://blogs.oracle.com/otn/2010/01/the_oracle_social_media_partic.html#comments">The Oracle Social Media Participation Policy</a> on <a href="http://blogs.oracle.com/otn/">Justin Kestelyn</a>'s (<a href="http://www.oracle.com/technology/index.html">Oracle Technology Network</a> Editor in Chief) <a href="http://blogs.oracle.com/otn/">blog</a> has caused me to reevaluate my own personal and previously unwritten policies that affect how and what I write in this blog.  In this post, I briefly look at the informal and unwritten policies that I have attempted to follow in writing posts for this blog and why these internally-motivated policies are important.  Other developers who blog (and <a href="http://marxsoftware.blogspot.com/2009/09/more-software-developers-should-write.html">I think more should</a>) may wish to similarly <a href="http://www.physorg.com/news149350705.html">take an inventory</a> of their own blogging practices.</p>
<p><strong>Mixing of Personal and Technical</strong></p>
<p>One of the main differences Justin calls out between Oracle's policy and Sun's policy is the mixing of personal with work-related content.  In an update to the post, Justin points out that not all personal is discouraged.  In many ways, my own post (though hosted on <a href="http://www.google.com/intl/en/about.html">Google</a>'s <a href="http://www.blogger.com/">Blogger</a> rather than on any employer's infrastructure) follows the same advice.  I do weave small personal details into my posts, but the focus of each post is always technically related.</p>
<p>The reason for this is that I have found that I prefer that approach as a reader of other peoples' blogs.  I present in a way I most enjoy as an attendee at a presentation (fast-talking, content-rich presentations) and I write my blog in the way I prefer others' blogs (strongly focused on the technical subjects with just enough personal details to make the topic less dry, somewhat personable, and to provide informed opinion).</p>
<p>I tend to prefer it when other bloggers separate purely personal non-technical content from their technical content.  The reason for this is that I often don't know these people personally and while their technical expertise and insight is valuable to me, I really don't care about their purely personal interests (especially in terms of politics).  I don't want to wade through meaningless and uninteresting non-technical details to find the technical gems.  I do "maintain" a personal blog with non-technical subjects, but it doesn't get the same attention from me as my technical blog.</p>
<p><strong>Long-Term Blog Availability</strong></p>
<p>One of the respondents to Justin's post asked about the future of his Sun blog.  This is a reminder that one's blog can get removed at anytime.  Hosting one's own blog obviously reduces the chances of anyone else removing it, but insufficient backups can mean problems even then.  Hackers, hardware problems, and even user error could wipe out a blog whether it is hosted externally or on one's own.  Very few things in life are guaranteed and online resource availability is certainly not (see the <a href="http://marxsoftware.blogspot.com/2009/10/impact-of-demise-of-geocities-on-online.html">demise of GeoCities</a> as an example).  It is important to back up one's blogs.  It helps if a blog has been reproduced on other hosts (which is sometimes the case), but even this can be difficult to piece back together.</p>
<p>I was very happy when Blogger <a href="http://bloggerindraft.blogspot.com/2008/06/new-feature-import-and-export.html">announced the ability</a> to <a href="http://www.google.com/support/blogger/bin/answer.py?hl=en&amp;answer=97416">export a blog and import a blog</a>.  That certainly contributes to the ability to move one's blog to another host if necessary or desired.  The ability to do that is one of the reasons that I've stayed with Blogger.</p>
<p>In addition, I have my blog posts forwarded to multiple e-mail accounts as additional backup.  The export/import is certainly easier if I ever need to actually do this in bulk, but the e-mail messages are automatic and are easily stored.  In other words, the export/import can be more difficult to remember to do in a timely manner, but are easier to do if the time comes to export/import the blog in bulk.</p>
<p>In the worst case, there are other options for resurrecting a "lost blog" such as using <a href="http://www.googleguide.com/cached_pages.html">Google Cache</a> or the <a href="http://marxsoftware.blogspot.com/2009/10/internet-archive-wayback-machine.html">Internet Archive WayBack Machine</a>.  Reconstructing a blog from these two sources would be tedious and depends on the blog being archived/cached in these resources.</p>
<p>In summary on the topic of long-term blog availability, I continue to use Blogger and back it up occasionally using the export feature.  I also have my posts forwarded to multiple e-mail accounts as soon as they are submitted.  If Google ever stops supporting Blogger, I will hopefully have the ability to easily move my blog posts to another provider or, in the less optimal case, reconstruct them from my e-mail messages.</p>
<p><strong>Watching What I Say</strong></p>
<p>In my blog, I do endeavor to keep my criticisms as constructive as possible and I try not to allow emotion to override technical merit.  Although I feel I do pretty well at this generally, I do better at it sometimes more than others.  One of the blog posts that seemed to lead to Justin's post, <a href="http://danesecooper.blogs.com/divablog/2010/01/assimilation-beginsoracle-censors-blogssuncom.html">Assimilation Begins...Oracle Censors Blogs.Sun.com</a>, the blog author does seem to drop into an emotionally-charged state with significant use of hyperbole (though I love the use of the image of one of my favorite fictional villains [<a href="http://en.wikipedia.org/wiki/Borg_(Star_Trek)">The Borg</a>]).  </p>
<p>The author uses words like "<a href="http://www.urbandictionary.com/define.php?term=Mucky+Muck">muck-mucks</a>", "<a href="http://www.thefreedictionary.com/subjugate">subjugate</a> Sun culture" (cultural change seems to inevitably happen in mergers and acquisitions between very large companies), "<a href="http://news.cnet.com/8301-10784_3-6021865-7.html">Snoracle</a>," "<a href="http://dictionary.reference.com/browse/Draconian">draconian</a> rules," and "<a href="http://en.wikipedia.org/wiki/Cultural_imperialism">cultural imperialism</a>."  Normally, use of exaggeration and hyperbole undermines the credibility of a blog topic in my mind, but in this case, I attribute it more to the blog author's referenced personal connection to the creation of the blogs.sun.com site.  It is easier to understand this emotion in that light, even if it seems a little overly dramatic from an outsider's perspective.</p>
<p>I acknowledge that I have said things and written things that are emotionally-driven and often overly critical, especially in e-mail and newsgroups.  I try a little harder with blogs to not go that far and be more controlled in what I write.</p>
<p>I prefer blogs with opinions and facts as long as the opinions are informed opinions and are presented as opinion rather than fact.  I also understand the importance of not revealing trade secrets regarding employers, clients, etc. and in not committing libel, slander, and the like.  When one writes a blog post, the onus is on that person to think carefully about what he or she writes.</p>
<p>In general, I sometimes feel the "online frontier" is far too wild in terms of lack of civility and respect.  I do try to keep my blog more positively focused and to talk about things I like or appreciate more often than my occasional posts about things I don't like or wish would be better.  There is a place for constructive criticism and constant improvement, but it doesn't need to be rude and condescending.</p>
<p><strong>Conclusion</strong></p>
<p>I think it is important to have personal guidelines that shape what we say, do, and write.  Having internal guidelines that I attempt to adhere to in writing this blog is an extension of that.  It is my opinion that these guidelines benefit me and those who read one or more of my posts.  It's time to back up my blog while I'm thinking about it.</p>
    ]]></content>
  </entry>
  <entry>
    <title>Oracle-Sun closes; mists clear (a little)</title>
    <link rel="alternate" type="text/html" href="http://www.javaworld.com/community/node/3980" />
    <id>http://www.javaworld.com/community/node/3980</id>
    <published>2010-01-29T00:40:52-05:00</published>
    <updated>2010-01-29T00:41:58-05:00</updated>
    <author>
      <name>Josh Fruhlinger</name>
    </author>
    <category term="glassfish" />
    <category term="javafx" />
    <category term="JDeveloper" />
    <category term="merger" />
    <category term="netbeans" />
    <summary type="html"><![CDATA[<!--paging_filter--><p>Well, the day we've been waiting for has finally arrived: Apple released its long-awaited tablet computer!  Oh, wait, wrong blog.  No, Sun has finally ceased to exist as a separate entity (at least in the US and Europe) and Java is firmly in Oracle's hands.    ]]></summary>
    <content type="html"><![CDATA[<!--paging_filter--><p>Well, the day we've been waiting for has finally arrived: Apple released its long-awaited tablet computer!  Oh, wait, wrong blog.  No, Sun has finally ceased to exist as a separate entity (at least in the US and Europe) and Java is firmly in Oracle's hands.  Reactions across the Internet to Oracle's media blitz are legion; I've tried to distill some succint information from a <a href="http://www.infoworld.com/d/developer-world/oracle-hails-java-kills-sun-cloud-960" target="_blank">couple</a> <a href="http://www.infoworld.com/d/applications/oracles-ambitious-plans-integrating-suns-technology-891" target="_blank">InfoWorld</a> articles, from <a href="http://news.cnet.com/8301-13556_3-10443069-61.html" target="_blank">CNet</a>, <a href="http://www.eweek.com/c/a/IT-Management/Oracle-Java-Will-Be-Business-As-Usual-317902/?kc=rss" target="_blank">eWeek</a>, and <a href="http://java.dzone.com/articles/sun-has-not-set-oracle-makes" target="_blank">JavaLobby</a>, along with <a href="http://saviorodrigues.wordpress.com/2010/01/27/oracle-explains-glassfish-mysql-openoffice-solaris-plans/" target="_blank">Savio Rodrigues</a>.</p>

<ul>
<li><p>Oracle wants to emphasize that everything with Java will be "business as usual."</p></li>
<li><p>Except that it will be profitable.  Larry Ellison said, "There's a lot of money to be made in the Java middleware space. Our next-generation Fusion applications are based entirely on Java. What you charge and what you give away isn't as important. We already know how to make money from Java."</p></li>
<li><p>Oracle wants to pour resources into JavaFX -- it has RIA plans for its own products that JavaFX will be the cornerstone of.  It also wants to tie JavaFX, Java, and JavaScript together more tightly (which may mean changes to the still-fluid JavaFX platform).</p></li>
<li><p>Oracle wants to merge, or at least tie more closely together, Java SE and ME, to achieve real a right once, run anywhere platform, at least on the low end.  Ah, the eternal dream!</p></li>
<li><p>HotSpot and JRocket will eventually be merged into a single JVM.</p></li>
<li><p>JavaOne will live on as a theoretically separate institution, but will be moved to September and take place at the same time and same place as Oracle OpenWorld, and will focus on the development community rather than on vendors.</p></li>
<li><p>Glassfish will survive as a "departmental" (read: low end) app server, and Oracle will continue to sell support for it.  Presumably its main importance will be that it's the Java EE reference implementation.  WebLogic continues to be Oracle's main commercial app server, and ideas will be shared between the two projects.</p></li>
<li><p>NetBeans will still be offered as Oracle's "open source" IDE, though it will not receive the love that JDeveloper will.  It may be spared the axe primarily becase it's the primary JavaFX IDE, and once JDeveloper and/or Eclipse gain JavaFX extensions, NetBeans will be quietly retired.</p></li>
<li><p>Oracle wants to make the JCP "more participatory."  The first test of that will come when Oracle doesn't get its way on something.</p></li>
</ul>

<p>It all sounds very promising, but as noted in that last bullet point, the proof of behavior comes when people start actually behaving, and everyone will be watching pretty carefully in the coming months to understand the flavor of how Oracle is going to run things.  As JBoss CTO Mark Little put charmingly if confusingly put it, "<a href="http://www.theregister.co.uk/2010/01/26/red_hat_bi_java_oracle/" target="_blank">We don't know what we don't know, which is what I've been saying for weeks</a>."
    ]]></content>
  </entry>
  <entry>
    <title>Sun-Oracle strategy update event: my perception</title>
    <link rel="alternate" type="text/html" href="http://www.javaworld.com/community/node/3978" />
    <id>http://www.javaworld.com/community/node/3978</id>
    <published>2010-01-28T07:34:41-05:00</published>
    <updated>2010-01-28T17:52:33-05:00</updated>
    <author>
      <name>Oleg Mikheev</name>
    </author>
    <category term="Sun-Oracle update event" />
    <summary type="html"><![CDATA[<!--paging_filter--><p>"IBM can't scale, they can't do clusters, can't do clouds"... "Oracle is taking the same strategy as IBM"...</p>
<p>Of course the first statement was made on IBM's DB2 (not the mainframe one) and the second one was referencing IBM's strategy that it took back in 1960s, but still the combination of these two sounds as a joke. And Larry loves joking. He couldn't go to iPad presentation because of his obligations to give a speech at the Oracle + Sun Product Strategy event. He didn't miss much though.</p>
    ]]></summary>
    <content type="html"><![CDATA[<!--paging_filter--><p>"IBM can't scale, they can't do clusters, can't do clouds"... "Oracle is taking the same strategy as IBM"...</p>
<p>Of course the first statement was made on IBM's DB2 (not the mainframe one) and the second one was referencing IBM's strategy that it took back in 1960s, but still the combination of these two sounds as a joke. And Larry loves joking. He couldn't go to iPad presentation because of his obligations to give a speech at the Oracle + Sun Product Strategy event. He didn't miss much though.</p>
<p>While neighboring bloggers are supposedly preparing an in-depth analysis of the "industry-changing event" that happened yesterday I will just point out the main items that seemed interesting to me as a Java/OpenSource guy.</p>
<p>In general presenters went through all of the Oracle/Sun technologies and underlined the main idea: <strong>INTEGRATION</strong>. Oracle will definitely spend a lot of effort integrating stuff. It will probably take one year to get somewhere.</p>
<p>First of all JRockit is going to be integrated with HotSpot. When JRockit first appeared it had lots of innovative ideas. For one - it had a built-in support for AOP (AspectWerkz). Lets hope that JRockit can still provide some added value for the good old HotSpot.</p>
<p>Future of JavaEE is modularity based on Open Standards. Does it sound like OSGi? Not sure.</p>
<p>Unifying JavaME and JavaSE. This sounds weird to me. JavaME is something that is being used in millions (or billions?) of devices, but I really doubt that it can evolve into something special. I think that this 'unification' will mostly be adaptation of useful parts ME provides into SE to let ME rest in piece in favor of FX. It was also strange to see how JavaFX was positioned for the current mobile market, as to my knowledge there are no commercial devices running JavaFX to date.</p>
<p>NetBeans will be positioned as a lightweight IDE for the open source community, JDeveloper - IDE for enterprise customers. At the same time Oracle plans to continue investing in Eclipse. Clearly something is wrong with that -- you can't support three competing products at the same time.</p>
<p>Linux was referenced a lot. Larry is a Linux fan, and so are his colleagues -- there's no doubt. ZFS was referenced a lot as one of the most powerful filesystems (it can do snapshots), I really hope that Linux fans will be able to truly enjoy ZFS on Linux kernel level because as of now it works so slowly via Fuse (Filesystem in Userspace).</p>
<p>Oracle is going to produce a Web-based Office suite based on OpenOffice.org. This reminds me of Google and the line of its online office tools that it managed to sell to CA government. If Larry's protege will become the next CA governor who knows whose office tools CA will use then.</p>
<p>OracleVM is so much better than VMWare. I personally never tried it so I can't tell, but will do a test run sometime soon. The thing that sounded really awesome to me was the plan to make VirtualBox images deployable to OracleVM. I've been using VirtualBox for quite some time for my testing purposes and I really missed this functionality -- being able to put VirtualBox image to production.</p>
<p>EDIT: OracleVM appears to be Xen which I tried a couple of years ago and got frustrated with.</p>
<p>While waiting for Larry's speech I was surprised by Sun logo continuously put above Oracle's in all video clips during the break -- will it stay like that?</p>
<p>Then he came. Thanks God he was not on a skateboard or roller skates. He also didn't have a tie and didn't talk about ROI, TCO etc. He seemed to be a golden mean between a Google-typed and an IBM-typed guy.</p>
<p>Larry clearly didn't like the term 'cloud'.</p>
<p>He claims that Oracle is taking the strategy that IBM took in 1960s. This statement really scared me because the same strategy will lead to the same dead-end and a $%&amp;*&amp;^ software.</p>
<p>The most important thing is that he claims that rumors about layoffs in Sun is 'garbage' and they will hire another 2000 in the coming months. We'll see sometime soon how they are going to do that. The scenario that everyone is afraid of is dismissal of Sun's local development centers and hiring of thousands in their single dev center in India. This scenario fits well into what Larry stated regarding the new hirings.</p>
<p>Larry really doubts that SAP will chose Oracle products as a middleware for their installations. Even despite the fact that SAP uses Oracle as a database for their most critical installations.</p>
<p>That's it for now. The event was very positive in general, but still some questions left unanswered.</p>
    ]]></content>
  </entry>
  <entry>
    <title>Five Reasons to Wait on the iPad</title>
    <link rel="alternate" type="text/html" href="http://www.javaworld.com/community/node/3976" />
    <id>http://www.javaworld.com/community/node/3976</id>
    <published>2010-01-28T04:05:19-05:00</published>
    <updated>2010-02-03T09:52:36-05:00</updated>
    <author>
      <name>Charles Nutter</name>
    </author>
    <summary type="html"><![CDATA[<!--paging_filter--><p>Like most geeks, I watched with great anticipation as the iPad was unveiled yesterday. In my case, it was from liveblogs monitored on spotty 3G and WiFi access at the Jfokus after-conference event. I'm reasonably impressed with the device, especially the $500 price point on the low end. But after giving it some thought, I have a few good reasons to wait on purchasing one. Here's five I came up with:</p>
    ]]></summary>
    <content type="html"><![CDATA[<!--paging_filter--><p>Like most geeks, I watched with great anticipation as the iPad was unveiled yesterday. In my case, it was from liveblogs monitored on spotty 3G and WiFi access at the Jfokus after-conference event. I'm reasonably impressed with the device, especially the $500 price point on the low end. But after giving it some thought, I have a few good reasons to wait on purchasing one. Here's five I came up with:
<ol>
<li>Wait for the next generation. Never, ever buy the first generation of a device this new and this complex. Doubly so for anything the Apple hype machine is pimping. First-gen devices are always at least a little bit tweaky, and Apple has proven repeatedly that their first-gen devices are overpriced, underpowered, and replaced by something better in 4-6 months.</li>
<li>Wait for competitors to answer. Sure, there have been other tablets and "slates" announced over the past year, but the iPad has moved the bar. Given the number of competing vendors, the availability of viable tablet options like Windows 7 and Android (or Chrome OS), and the ever-present iControl and iLock-in associated with all things Apple, you can bet there's going to be a bunch of competitive options during 2010.</li>
<li>Wait for everyone else to buy it. Yeah, this one is painful, but think about all the suckers that bought the iPhone right when it came out. You'll spend a couple months as a temporary luddite, ridiculed by your peers. And then you'll get a better device for cheaper and have the last laugh. I mean, isn't half the joy of the iPad in having the bigger iPenis? You can hold off for a while.</li>
<li>Wait for crackers to bust it wide open. Nobody's happy that the iPhone is a closed platform, nor are they happy that the App Store is so sketchy at approving applications. So why not wait and see what the busy iPhone hackers can do with an iPad before diving it? Chances are it will be a far better laptop replacement once they get ahold of it.</li>
<li>Wait because you don't actually need it. It can't replace your phone. It can't replace your laptop. It can't replace your 50" LCD TV. Seriously now...what do you need it for?</li>
</ol>
<p>Me, I'm on the fence. I can afford it, but then I probably wouldn't be able to afford something else. And I'm a programmer...I want to be able to put my own apps on the device (or give apps to friends) without dealing with the App Store gatekeeper. In my ideal world, it would be Apple's hardware and design sensibility combined with Android's open platform and familiar runtime. Anything even close to that would outshine the iPad for me.<br />Update: One last bit of anecdotal evidence. Before the iPhone, I had held off buying anything other than the crappiest, cheapest phones, the lowest-end music devices (yep, I had the "pack of gum" Shuffle), the most basic digital cameras, and no PDA. I was waiting for something that would allow me to get rid of all devices at once. iPhone obviously did that, as a music/media player, internet device, PDA, phone, and camera all in one. iPad takes two of those features away (phone and camera) and only adds a larger screen with the potential for large-form apps.</p>
    ]]></content>
  </entry>
</feed>
