<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>gruchalski.com &#187; Adobe Flex/AIR</title>
	<atom:link href="http://gruchalski.com/category/adobe-flexair/feed/" rel="self" type="application/rss+xml" />
	<link>http://gruchalski.com</link>
	<description>confessions of a ria developer</description>
	<lastBuildDate>Tue, 15 Dec 2009 23:41:16 +0000</lastBuildDate>
	<generator>http://wordpress.org/?v=2.9.2</generator>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
			<item>
		<title>Flex Builder 3 Linux Alpha 5 available on labs.adobe.com</title>
		<link>http://gruchalski.com/2009/11/25/flex-builder-3-linux-alpha-5-available-on-labs-adobe-com/</link>
		<comments>http://gruchalski.com/2009/11/25/flex-builder-3-linux-alpha-5-available-on-labs-adobe-com/#comments</comments>
		<pubDate>Wed, 25 Nov 2009 09:57:34 +0000</pubDate>
		<dc:creator>radekg</dc:creator>
				<category><![CDATA[Adobe Flex/AIR]]></category>

		<guid isPermaLink="false">http://gruchalski.com/?p=412</guid>
		<description><![CDATA[I&#8217;m sure everyone who&#8217;s interested in Flex Builder for Linux already knows that by following Twitter and Adobe JIRA. So what&#8217;s exactly happened? Today, just 5 days before Flex Builder alpha 4 was to expire Adobe released an alpha 5 update on labs.adobe.com which is going to expire in 401 days.
I have the gut feeling [...]]]></description>
			<content:encoded><![CDATA[<p>I&#8217;m sure everyone who&#8217;s interested in Flex Builder for Linux already knows that by following Twitter and <a href="http://bugs.adobe.com/jira/browse/FB-19053">Adobe JIRA</a>. So what&#8217;s exactly happened? Today, just 5 days before Flex Builder alpha 4 was to expire Adobe released an alpha 5 update on <a href="http://labs.adobe.com/technologies/flex/flexbuilder_linux/">labs.adobe.com</a> which is going to expire in 401 days.</p>
<p>I have the gut feeling that Adobe is working on Flex Builder 4 for Linux but is not keen on sharing the info yet. Specially that there is an unofficial build available <a href="http://code.google.com/p/fb4linux/">from here</a>.</p>
<p>Good decision Adobe.</p>
<p>More information on current release <a href="http://blogs.adobe.com/flex/archives/2009/11/flex_builder_linux_alpha_5_rel.html">here</a> and <a href="http://labs.adobe.com/technologies/flex/flexbuilder_linux/releasenotes.html">here (release notes)</a>.</p>
]]></content:encoded>
			<wfw:commentRss>http://gruchalski.com/2009/11/25/flex-builder-3-linux-alpha-5-available-on-labs-adobe-com/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>ColdFusion to Flex serializer needs better error messages</title>
		<link>http://gruchalski.com/2009/09/02/coldfusion-to-flex-serializer-needs-better-error-messages/</link>
		<comments>http://gruchalski.com/2009/09/02/coldfusion-to-flex-serializer-needs-better-error-messages/#comments</comments>
		<pubDate>Wed, 02 Sep 2009 09:44:03 +0000</pubDate>
		<dc:creator>radekg</dc:creator>
				<category><![CDATA[Adobe Flex/AIR]]></category>
		<category><![CDATA[ColdFusion]]></category>
		<category><![CDATA[Moan]]></category>

		<guid isPermaLink="false">http://gruchalski.com/?p=353</guid>
		<description><![CDATA[I&#8217;m using Flex with ColdFusion for a long time now but sometimes I really can&#8217;t stand these two products together. I&#8217;m sending nested CFCs through the remote object back to Flex so ColdFusion has to serialize them to the form that may be understood by Flex. But I&#8217;ve got an error, somewhere I have an [...]]]></description>
			<content:encoded><![CDATA[<p>I&#8217;m using Flex with ColdFusion for a long time now but sometimes I really can&#8217;t stand these two products together. I&#8217;m sending nested CFCs through the remote object back to Flex so ColdFusion has to serialize them to the form that may be understood by Flex. But I&#8217;ve got an error, somewhere I have an empty string where number is expected. You would expect that CF is smart enough to tell you what the problem is? Nope, it says:</p>
<p><code>Unable to invoke CFC - The value '' cannot be converted to a number.</code></p>
<p>And here is the stack trace:</p>
<pre>
<pre class="brush: cf;">
        [0] &quot;coldfusion.runtime.Cast._double(Cast.java:652)&quot;
        [1] &quot;coldfusion.runtime.Cast._double(Cast.java:632)&quot;
        [2] &quot;coldfusion.runtime.Cast._double(Cast.java:786)&quot;
        [3] &quot;coldfusion.flash.messaging.io.amf.Translator.CFASSerializer.translate(CFASSerializer.java:545)&quot;
        [4] &quot;coldfusion.flash.messaging.io.amf.Translator.CFASSerializer.translate(CFASSerializer.java:494)&quot;
        [5] &quot;coldfusion.flash.messaging.io.amf.Translator.CFASSerializer.translate(CFASSerializer.java:387)&quot;
        [6] &quot;coldfusion.flash.messaging.io.amf.Translator.CFASSerializer.translate(CFASSerializer.java:81)&quot;
        [7] &quot;coldfusion.flash.messaging.io.amf.Translator.CFASSerializer.translate(CFASSerializer.java:512)&quot;
        [8] &quot;coldfusion.flash.messaging.io.amf.Translator.CFASSerializer.translate(CFASSerializer.java:494)&quot;
        [9] &quot;coldfusion.flash.filter.CFCInvokeFilter.invoke(CFCInvokeFilter.java:160)&quot;
        [10] &quot;coldfusion.filter.ApplicationFilter.invoke(ApplicationFilter.java:279)&quot;
        [11] &quot;coldfusion.filter.MonitoringFilter.invoke(MonitoringFilter.java:40)&quot;
        [12] &quot;coldfusion.flash.filter.CFCInvokeDebugFilter.invoke(CFCInvokeDebugFilter.java:54)&quot;
        [13] &quot;coldfusion.flash.filter.CFCInvokePathFilter.invoke(CFCInvokePathFilter.java:70)&quot;
        [14] &quot;coldfusion.filter.ClientScopePersistenceFilter.invoke(ClientScopePersistenceFilter.java:28)&quot;
        [15] &quot;coldfusion.filter.GlobalsFilter.invoke(GlobalsFilter.java:38)&quot;
        [16] &quot;coldfusion.filter.DatasourceFilter.invoke(DatasourceFilter.java:22)&quot;
        [17] &quot;coldfusion.flash.messaging.ColdFusionAdapter.invoke(ColdFusionAdapter.java:223)&quot;
        [18] &quot;flex.messaging.services.RemotingService.serviceMessage(RemotingService.java:173)&quot;
        [19] &quot;flex.messaging.MessageBroker.routeMessageToService(MessageBroker.java:1165)&quot;
        [20] &quot;flex.messaging.endpoints.AbstractEndpoint.serviceMessage(AbstractEndpoint.java:757)&quot;
        [21] &quot;flex.messaging.endpoints.amf.MessageBrokerFilter.invoke(MessageBrokerFilter.java:117)&quot;
        [22] &quot;flex.messaging.endpoints.amf.LegacyFilter.invoke(LegacyFilter.java:158)&quot;
        [23] &quot;flex.messaging.endpoints.amf.SessionFilter.invoke(SessionFilter.java:48)&quot;
        [24] &quot;flex.messaging.endpoints.amf.BatchProcessFilter.invoke(BatchProcessFilter.java:67)&quot;
        [25] &quot;flex.messaging.endpoints.amf.SerializationFilter.invoke(SerializationFilter.java:145)&quot;
        [26] &quot;flex.messaging.endpoints.AMFEndpoint.service(AMFEndpoint.java:122)&quot;
        [27] &quot;flex.messaging.MessageBrokerServlet.service(MessageBrokerServlet.java:438)&quot;
        [28] &quot;coldfusion.flex.ColdFusionMessageBrokerServlet.service(ColdFusionMessageBrokerServlet.java:50)&quot;
        [29] &quot;javax.servlet.http.HttpServlet.service(HttpServlet.java:853)&quot;
        [30] &quot;coldfusion.bootstrap.BootstrapServlet.service(BootstrapServlet.java:89)&quot;
        [31] &quot;jrun.servlet.FilterChain.doFilter(FilterChain.java:86)&quot;
        [32] &quot;coldfusion.filter.FlashRequestControlFilter.doFilter(FlashRequestControlFilter.java:71)&quot;
        [33] &quot;coldfusion.bootstrap.BootstrapFilter.doFilter(BootstrapFilter.java:46)&quot;
        [34] &quot;jrun.servlet.FilterChain.doFilter(FilterChain.java:94)&quot;
        [35] &quot;jrun.servlet.FilterChain.service(FilterChain.java:101)&quot;
        [36] &quot;jrun.servlet.ServletInvoker.invoke(ServletInvoker.java:106)&quot;
        [37] &quot;jrun.servlet.JRunInvokerChain.invokeNext(JRunInvokerChain.java:42)&quot;
        [38] &quot;jrun.servlet.JRunRequestDispatcher.invoke(JRunRequestDispatcher.java:286)&quot;
        [39] &quot;jrun.servlet.ServletEngineService.dispatch(ServletEngineService.java:543)&quot;
        [40] &quot;jrun.servlet.jrpp.JRunProxyService.invokeRunnable(JRunProxyService.java:203)&quot;
        [41] &quot;jrunx.scheduler.ThreadPool$DownstreamMetrics.invokeRunnable(ThreadPool.java:320)&quot;
        [42] &quot;jrunx.scheduler.ThreadPool$ThreadThrottle.invokeRunnable(ThreadPool.java:428)&quot;
        [43] &quot;jrunx.scheduler.ThreadPool$UpstreamMetrics.invokeRunnable(ThreadPool.java:266)&quot;
        [44] &quot;jrunx.scheduler.WorkerThread.run(WorkerThread.java:66)&quot;
</pre>
</pre>
<p>Helpful, heh?</p>
<p>Why, WHY! it does not say what property is causing the problem? Surely it knows, it just keeps it for itself. Adobe &#8211; please fix!</p>
]]></content:encoded>
			<wfw:commentRss>http://gruchalski.com/2009/09/02/coldfusion-to-flex-serializer-needs-better-error-messages/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Flex DateFormatter bug, missing January 1970 month name</title>
		<link>http://gruchalski.com/2009/06/18/flex-dateformatter-bug-missing-january-1970-month-name/</link>
		<comments>http://gruchalski.com/2009/06/18/flex-dateformatter-bug-missing-january-1970-month-name/#comments</comments>
		<pubDate>Thu, 18 Jun 2009 20:23:56 +0000</pubDate>
		<dc:creator>radekg</dc:creator>
				<category><![CDATA[Adobe Flex/AIR]]></category>

		<guid isPermaLink="false">http://gruchalski.com/?p=330</guid>
		<description><![CDATA[Today, while working on a small piece of code I discovered something that appears to be a DateFormatter bug. I was using following DateFormatter:


private var formatter:DateFormatter = new DateFormatter();
...
 formatter.formatString = &#34;MMMM&#34;;


to display just the month names. I didn&#8217;t really care about the year when constructing new Date so I thought I would pick 1970. [...]]]></description>
			<content:encoded><![CDATA[<p>Today, while working on a small piece of code I discovered something that appears to be a <em>DateFormatter</em> bug. I was using following DateFormatter:</p>
<pre>
<pre class="brush: as3;">
private var formatter:DateFormatter = new DateFormatter();
...
 formatter.formatString = &quot;MMMM&quot;;
</pre>
</pre>
<p>to display just the month names. I didn&#8217;t really care about the year when constructing new <em>Date</em> so I thought I would pick 1970. It appears that for January 1970 the <em>DateFormatter</em> returns an empty string while for 1969, 1971 and any other dates it works fine. Here is the demo.</p>
<p>
<object width="450" height="300">
<param name="movie" value="/wp-content/uploads/2009/06/DateFormatterBug.swf"></param>
<param name="quality" value="high"></param>
<param name="wmode" value="window"></param>
<param name="menu" value="false"></param>
<param name="bgcolor" value="#FFFFFF"></param>
<embed type="application/x-shockwave-flash" width="450" height="300" src="/wp-content/uploads/2009/06/DateFormatterBug.swf" quality="high" bgcolor="#FFFFFF" wmode="window" menu="false" ></embed>
</object>
</p>
<p>And the source code below.</p>
<pre>
<pre class="brush: as3;">
&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;
&lt;mx:Application xmlns:mx=&quot;http://www.adobe.com/2006/mxml&quot;&gt;

 &lt;mx:Script&gt;
  &lt;![CDATA[
   import mx.formatters.DateFormatter;

   [Bindable] private var names:String = &quot;&quot;;
   private var formatter:DateFormatter = new DateFormatter();

   private function listMonths(year:Number):void {
    // get just the month name:
    formatter.formatString = &quot;MMMM&quot;;
    // create dates and get formatted values:
    var values:Array = [];
    for ( var i:Number=0; i&lt;12; i++ ) {
     values.push( formatter.format( new Date(year, i, 1) ) );
    }
    names = values.join(String.fromCharCode(13));
   }

  ]]&gt;
 &lt;/mx:Script&gt;

 &lt;mx:HBox&gt;
  &lt;mx:Button label=&quot;1969&quot; click=&quot;listMonths(1969);&quot; /&gt;
  &lt;mx:Button label=&quot;1970&quot; click=&quot;listMonths(1970);&quot; /&gt;
  &lt;mx:Button label=&quot;1971&quot; click=&quot;listMonths(1971);&quot; /&gt;
 &lt;/mx:HBox&gt;

 &lt;mx:TextArea width=&quot;300&quot; height=&quot;200&quot; text=&quot;{names}&quot; /&gt;

&lt;/mx:Application&gt;
</pre>
</pre>
<p>It appears that the bug was logged quite a few times, just one of the tickets I&#8217;ve found: <a href="https://bugs.adobe.com/jira/browse/SDK-14528">https://bugs.adobe.com/jira/browse/SDK-14528</a>. It&nbsp;also appears that it won&#8217;t be fixed. According to the comments under the linked story <em>DateFormatter</em> class is not an Adobe code. Interesting&#8230;</p>
]]></content:encoded>
			<wfw:commentRss>http://gruchalski.com/2009/06/18/flex-dateformatter-bug-missing-january-1970-month-name/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>Flex DataGridColumn width set to the longest value of that column</title>
		<link>http://gruchalski.com/2009/06/16/flex-datagridcolumn-width-set-to-the-longest-value-in-that-column/</link>
		<comments>http://gruchalski.com/2009/06/16/flex-datagridcolumn-width-set-to-the-longest-value-in-that-column/#comments</comments>
		<pubDate>Tue, 16 Jun 2009 23:17:23 +0000</pubDate>
		<dc:creator>radekg</dc:creator>
				<category><![CDATA[Adobe Flex/AIR]]></category>
		<category><![CDATA[stackoverflow]]></category>

		<guid isPermaLink="false">http://gruchalski.com/?p=312</guid>
		<description><![CDATA[There was an interesting question on stackoverflow.com today. Some unnamed user asked:
Can we change the width of the datagrid column dynamically by clicking on the border of the column in order to display the complete string which is too long to be displayed and needs to be scrolled ? If so, How ?Also, how can [...]]]></description>
			<content:encoded><![CDATA[<p>There was an interesting question on <em>stackoverflow.com</em> today. Some unnamed user asked:</p>
<blockquote><p>Can we change the width of the datagrid column dynamically by clicking on the border of the column in order to display the complete string which is too long to be displayed and needs to be scrolled ? If so, How ?<br/><br/>Also, how can we ensure that the column width changes dynamically based on the number of characters / length of string; since many a times the data is too long to be displayed. Can we set the column width to take the length of data into consideration before displaying onto the datagrid ?</p></blockquote>
<p>It looked like a cool practice so I thought I would give it a go. This is what I came up, it doesn&#8217;t resize columns on double click but rather automatically when data provider is set/updated.</p>
<pre>
<pre class="brush: as3;">
 &lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;
 &lt;mx:WindowedApplication xmlns:mx=&quot;http://www.adobe.com/2006/mxml&quot;
  creationComplete=&quot;onComplete();&quot;&gt;

  &lt;mx:Script&gt;
   &lt;![CDATA[
    // imports:
    import mx.events.FlexEvent;
    import mx.core.UIComponent;
    import mx.controls.dataGridClasses.DataGridColumn;
    import mx.controls.Text;
    import mx.utils.ObjectUtil;
    import mx.controls.Label;
    import mx.collections.ArrayCollection;
    // data provider:
    [Bindable] private var dp:ArrayCollection = new ArrayCollection();

    private function onComplete():void {
     // populate data provider here
     // to avoid calcMaxLengths execution when the app is created:
     dp = new ArrayCollection(
      [
       { col1: &quot;Short&quot;, col2: &quot;Other column 1&quot; },
       { col1: &quot;Some long string&quot;, col2: &quot;Other column 2&quot; },
       { col1: &quot;Short&quot;, col2: &quot;Other column 3&quot; },
       { col1: &quot;Short&quot;, col2: &quot;Other column 4&quot; },
       { col1: &quot;The longest value in this column&quot;, col2: &quot;Other column 5&quot; },
       { col1: &quot;Short&quot;, col2: &quot;Other column 6&quot; },
       { col1: &quot;Short&quot;, col2: &quot;Other column 7&quot; }
      ]
     );
    }

    // this is going to be executed whenever the data provider changes:
    [Bindable(&quot;dataChange&quot;)]
    private function calcMaxLengths(input:ArrayCollection):ArrayCollection {
     // if there are items in the DP:
     if ( input.length &gt; 0 ) {
      // and no SPECIAL child exists:
      if ( getChildByName(&quot;$someTempUICToRemoveAfterFinished&quot;) == null ) {
       // create new SPECIAL child
       // this is required to call measureText
       // if you use custom data grid item renderer
       // then create instance of it instead of UIComponent:
       var uic:UIComponent = new UIComponent();
       // do not show and do not mess with the sizes:
       uic.includeInLayout = false;
       uic.visible = false;
       // name it to leverage get getChildByName method:
       uic.name = &quot;$someTempUICToRemoveAfterFinished&quot;;
       // add event listener:
       uic.addEventListener(FlexEvent.CREATION_COMPLETE, onTempUICCreated);
       // add to parent:
       addChild(uic);
      }
     }
     // return an input:
     return input;
    }

    // called when SPECIAL child is created:
    private function onTempUICCreated(event:FlexEvent):void {
     // keep the ref to the SPECIAL child:
     var renderer:UIComponent = UIComponent(event.target);
     // output - this will contain max size for each column:
     var maxLengths:Object = {};
     // temp variables:
     var key:String = &quot;&quot;;
     var i:int=0;
     // for each item in the DP:
     for ( i=0; i&lt;dp.length; i++ ) {
      var o:Object = dp.getItemAt(i);
      // for each key in the DP row:
      for ( key in o ) {
       // if the output doesn't have current key yet create it and set to 0:
       if ( !maxLengths.hasOwnProperty(key) ) {
        maxLengths[key] = 0;
       }
       // check if it's simple object (may cause unexpected issues for Boolean):
       if ( ObjectUtil.isSimple(o[key]) ) {
        // measure the text:
        var cellMetrics:TextLineMetrics = renderer.measureText(o[key]+&quot;&quot;);
        // and if the width is greater than longest found up to now:
        if ( cellMetrics.width &gt; maxLengths[key] ) {
         // set it as the longest one:
         maxLengths[key] = cellMetrics.width;
        }
       }
      }
     }

     // apply column sizes:
     for ( key in maxLengths ) {
      for ( i=0; i&lt;dg.columnCount; i++ ) {
       // if the column actually exists:
       if ( DataGridColumn(dg.columns[i]).dataField == key ) {
        // set size + some constant margin
        DataGridColumn(dg.columns[i]).width = Number(maxLengths[key]) + 12;
       }
      }
     }
     // cleanup:
     removeChild(getChildByName(&quot;$someTempUICToRemoveAfterFinished&quot;));
    }

   ]]&gt;
  &lt;/mx:Script&gt;

  &lt;mx:DataGrid id=&quot;dg&quot; horizontalScrollPolicy=&quot;on&quot; dataProvider=&quot;{calcMaxLengths(dp)}&quot; width=&quot;400&quot;&gt;
   &lt;mx:columns&gt;
    &lt;mx:DataGridColumn dataField=&quot;col1&quot; width=&quot;40&quot; /&gt;
    &lt;mx:DataGridColumn dataField=&quot;col2&quot; width=&quot;100&quot; /&gt;
   &lt;/mx:columns&gt;
  &lt;/mx:DataGrid&gt;

 &lt;/mx:WindowedApplication&gt;
</pre>
</pre>
<p>Sample:</p>
<p>
<object width="450" height="240">
<param name="movie" value="/wp-content/uploads/2009/06/TextTest.swf"></param>
<param name="quality" value="high"></param>
<param name="wmode" value="window"></param>
<param name="menu" value="false"></param>
<param name="bgcolor" value="#FFFFFF"></param>
<embed type="application/x-shockwave-flash" width="450" height="240" src="/wp-content/uploads/2009/06/TextTest.swf" quality="high" bgcolor="#FFFFFF" wmode="window" menu="false" ></embed>
</object>
</p>
<p>This code works just fine however it may be not efficient enough when applied to large data providers.</p>
<p>Source code is also available on <a href="http://stackoverflow.com/questions/1002518/dynamically-change-the-width-of-datagrid-column-in-flex/1004305#1004305">stackoverflow.com</a> under the original entry.</p>
]]></content:encoded>
			<wfw:commentRss>http://gruchalski.com/2009/06/16/flex-datagridcolumn-width-set-to-the-longest-value-in-that-column/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>XMLSocket.send / Socket.writeUTFBytes doesn&#8217;t work?</title>
		<link>http://gruchalski.com/2009/06/11/xmlsocket-send-socket-writeutfbytes-doesnt-work/</link>
		<comments>http://gruchalski.com/2009/06/11/xmlsocket-send-socket-writeutfbytes-doesnt-work/#comments</comments>
		<pubDate>Thu, 11 Jun 2009 13:37:09 +0000</pubDate>
		<dc:creator>radekg</dc:creator>
				<category><![CDATA[Adobe Flex/AIR]]></category>
		<category><![CDATA[Java]]></category>

		<guid isPermaLink="false">http://gruchalski.com/?p=298</guid>
		<description><![CDATA[Disclamer
In first words &#8211; no, of course it is not broken, it is working fine. But there is a specific issue with these two methods when the socket server is implemented incorrectly. I noticed I&#8217;ve been looking for the solution using these phrases and that&#8217;s why the post it titled like that.
I just finished writing [...]]]></description>
			<content:encoded><![CDATA[<p><em>Disclamer</em><br />
In first words &#8211; no, of course it is not broken, it is working fine. But there is a specific issue with these two methods when the socket server is implemented incorrectly. I noticed I&#8217;ve been looking for the solution using these phrases and that&#8217;s why the post it titled like that.</p>
<p>I just finished writing an ActionScript 3 SWC library which is going to be used with Flex and Flash applications. The library uses Socket connection to provide some statistical information to the Java socket server. As part of the project I had to create simple socket server which simply writes what it gets to the standard output. </p>
<p>The code was working fine when running in Flex Builder debugger. However, as soon as I started the test application in the Flash IDE I found following problem:</p>
<ul>
<li>SWF was requesting policy file</li>
<li>my Java socket server was serving policy file</li>
<li>SWF was showing that it was connected</li>
<li>any other messages sent to the socket were not coming through</li>
</ul>
<p>Exactly the same problem appeared when I deployed Flex version on the external server. It worked while running in the debugger but not from the browser. So it was clearly something wrong with the socket server. Once the policy file was served any other communication wasn&#8217;t working. My socket server looked like this:</p>
<pre>
<pre class="brush: java;">
package uk.co.test;

import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;

public class SocketTest {
 public static void main(String[] args) throws Exception {
  System.out.println(&quot;Starting...&quot;);
  start();
 }
 public static void start() throws Exception {
  // create socket server
  ServerSocket ss = new ServerSocket(1234);
  for (;;) {

   System.out.println(&quot;Waiting for the client.&quot;);
   Socket cs = ss.accept();
   System.out.println(&quot;Connection...&quot;);

   InputStream in = cs.getInputStream();
   OutputStream out = cs.getOutputStream();
   boolean isConnected = true;
   StringBuffer soFar = new StringBuffer();
   byte b;

   while (isConnected) {
    // let it rest a bit:
    Thread.sleep(10);
    // read everything what's coming in:
    while ( (b = (byte)in.read()) &gt; -1 ) {
     if ( b == 0 ) {
      // zero byte, process:
      String value = soFar.toString();
      // get the value
      if ( value.equals(&quot;&lt;policy-file-request/&gt;&quot;) ) {
       // policy file requested, sent the policy back:
       System.out.println(&quot;Policy file request.&quot;);
       String crossdomain = &quot;&lt;?xml version=\&quot;1.0\&quot;?&gt;&quot;;
       crossdomain += &quot;&lt;cross-domain-policy&gt;&quot;;
       crossdomain += &quot;&lt;allow-access-from domain=\&quot;*\&quot; to-ports=\&quot;*\&quot; secure=\&quot;false\&quot; /&gt;&quot;;
       crossdomain += &quot;&lt;/cross-domain-policy&gt;&quot;;
       out.write(crossdomain.getBytes());
       out.write((byte)0);
       out.flush();
       System.out.println(&quot;Policy file sent.&quot;);

      } else {

       if ( value.equals(&quot;exit&quot;) ) {
        // if exit command received, finish:
        isConnected = false;
       } else {
        // just output the message:
        System.out.println(value);
       }

      }
      soFar.setLength(0);
     } else {
      // append the character to the buffer:
      byte[] buf = new byte[1];
      buf[0] = b;
      soFar.append( new String(buf) );
     }
    }
   }
  }
 }
}
</pre>
</pre>
<p>While looking for the solution I found the following article: <em><a href="http://www.adobe.com/devnet/flashplayer/articles/socket_policy_files.html">Setting up a socket policy file server</a></em>. <em>Peleus Uhley</em> from Adobe describes how to use policy files effectively. In <em>What data is sent in the request and response?</em> section of the article there is a solution..</p>
<p><em>Once Flash Player receives the socket policy file, it closes the connection and opens a new connection if the policy file approves the request.</em></p>
<p>Looking at the above socket server code I could now clearly see what&#8217;s wrong. Once the connection is accepted no other connections are coming in until the first client sends <em>exit</em> message. So I modified my socket server, here it is:</p>
<p><em>SocketTest.java</em></p>
<pre>
<pre class="brush: java;">
package uk.co.test;

import java.net.ServerSocket;
import java.net.Socket;

public class SocketTest {

 public static void main(String[] args) throws Exception {
  System.out.println(&quot;Starting...&quot;);
  start();
 }

 public static void start() throws Exception {
  // create socket:
  ServerSocket ss = new ServerSocket(1234);
  for (;;) {
   System.out.println(&quot;Waiting for the client.&quot;);
   Socket cs = ss.accept();
   System.out.println(&quot;Connection...&quot;);
   // create socket connection handler and run it in separate thread:
   new SocketHandler(cs);
  }
 }
}
</pre>
</pre>
<p><em>SocketHandler.java</em></p>
<pre>
<pre class="brush: java;">
package uk.co.test;

import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;

public class SocketHandler
implements Runnable {

 private Socket _client;

 public SocketHandler(Socket s) {
  _client = s;
  // create new thread from this instance and start it:
  Thread t = new Thread(this);
  t.start();
 }

 public void run() {
  try {
   // get client in/out:
   InputStream in = _client.getInputStream();
   OutputStream out = _client.getOutputStream();
   boolean isConnected = true;
   StringBuffer soFar = new StringBuffer();
   byte b;

   while (isConnected) {
    // let it rest a bit:
    Thread.sleep(10);
    // read everything coming in:
    while ( (b = (byte)in.read()) &gt; -1 ) {
     if ( b == 0 ) {
      // zero byte, process what already came in:
      String value = soFar.toString();
      if ( value.equals(&quot;&lt;policy-file-request/&gt;&quot;) ) {
       // policy file requested, send it to the client:
       System.out.println(&quot;Policy file request.&quot;);
       String crossdomain = &quot;&lt;?xml version=\&quot;1.0\&quot;?&gt;&quot;;
       crossdomain += &quot;&lt;cross-domain-policy&gt;&quot;;
       crossdomain += &quot;&lt;allow-access-from domain=\&quot;*\&quot; to-ports=\&quot;*\&quot; secure=\&quot;false\&quot; /&gt;&quot;;
       crossdomain += &quot;&lt;/cross-domain-policy&gt;&quot;;
       out.write(crossdomain.getBytes());
       out.write((byte)0);
       out.flush();
       System.out.println(&quot;Policy file sent.&quot;);

      } else {

       if ( value.equals(&quot;exit&quot;) ) {
        // exit command received, exit then...
        isConnected = false;
       } else {
        // just print the message to the stdout:
        System.out.println(value);
       }

      }
      soFar.setLength(0);
     } else {
      // append the char to the buffer:
      byte[] buf = new byte[1];
      buf[0] = b;
      soFar.append( new String(buf) );
     }
    }
   }
  } catch (Exception e) {
   // ignore
  }
 }
}
</pre>
</pre>
<p>The second socket server fixed the problem. It is working for connections with and without policy requests, from Flash IDE, Flex Builder and the browser.</p>
<p>It took me 5 hours to figure out the solution (process client connections in separate threads) so if you&#8217;re in the same situation as I was I hope this post helps you.</p>
]]></content:encoded>
			<wfw:commentRss>http://gruchalski.com/2009/06/11/xmlsocket-send-socket-writeutfbytes-doesnt-work/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>Why crossdomain.xml is even more than a good thing</title>
		<link>http://gruchalski.com/2009/05/22/why-crossdomainxml-is-even-more-than-a-good-thing/</link>
		<comments>http://gruchalski.com/2009/05/22/why-crossdomainxml-is-even-more-than-a-good-thing/#comments</comments>
		<pubDate>Fri, 22 May 2009 01:19:16 +0000</pubDate>
		<dc:creator>radekg</dc:creator>
				<category><![CDATA[Adobe Flex/AIR]]></category>
		<category><![CDATA[Silverlight/WPF]]></category>

		<guid isPermaLink="false">http://gruchalski.com/?p=271</guid>
		<description><![CDATA[For a long time I couldn&#8217;t really understand what crossdomain.xml is&#160;for. Today, after finishing one the Flex projects I finally figured it out. At&#160;least one of two reasons. About 4 years ago Martijn de Visser described one of them &#8211; defending your internal network from the attacks. But there is another way reason why crossdomain.xml [...]]]></description>
			<content:encoded><![CDATA[<p>For a long time I couldn&#8217;t really understand what <em>crossdomain.xml</em> is&nbsp;for. Today, after finishing one the Flex projects I finally figured it out. At&nbsp;least one of two reasons. About 4 years ago <a href="http://www.martijndevisser.com/blog/about/">Martijn de Visser</a> <a href="http://www.martijndevisser.com/blog/2005/why-crossdomainxml-is-a-good-thing/">described one of them</a> &#8211; defending your internal network from the attacks. But there is another way reason why crossdomain.xml is good.</p>
<p>Let&#8217;s say I&#8217;m developing some smart module and I let people download and load it from their domains but there are some specific sites that I&nbsp;want to prohibit. I&#8217;m going to use this very simple module to demonstrate how this can be achieved.</p>
<pre>
<pre class="brush: xml;">
&lt;?xml version=&quot;1.0&quot;?&gt;
&lt;mx:Module xmlns:mx=&quot;http://www.adobe.com/2006/mxml&quot; width=&quot;100%&quot; height=&quot;100%&quot;&gt;
 &lt;mx:Label text=&quot;I'm the protected module&quot; /&gt;
&lt;/mx:Module&gt;
</pre>
</pre>
<p>Suppose that I let <em>http://www.friend1.com</em> and <em>http://www.friend2.com</em> use it but <em>http://www.pron.com</em> shouldn&#8217;t be allowed. When I modify the code a bit I will use Flash Player sandboxing to do so.</p>
<pre>
<pre class="brush: as3;">
&lt;?xml version=&quot;1.0&quot;?&gt;
&lt;mx:Module xmlns:mx=&quot;http://www.adobe.com/2006/mxml&quot; width=&quot;100%&quot; height=&quot;100%&quot;
 creationComplete=&quot;onCreated();&quot;&gt;
 &lt;mx:Script&gt;
  &lt;![CDATA[

   import flash.net.URLRequest;
   import flash.net.URLLoader;
   import flash.events.SecurityErrorEvent;
   import flash.events.Event;
   import flash.events.IOErrorEvent;
   import flash.external.ExternalInterface;

   private function onCreated():void {
    var url:URLRequest = new URLRequest();
    url.url = &quot;http://www.mymodulesfactory.com/check.cfm&quot;
    var loader:URLLoader = new URLLoader();
    loader.addEventListener(Event.COMPLETE, onComplete);
    loader.addEventListener(SecurityErrorEvent.SECURITY_ERROR, onSecurityError);
    // this one really doesn't matter
    // I'm catching it so the error is not visible
    // in the debug player if the user uses it and check.cfm doesn't exist
    // check.cfm does not have to exist
    loader.addEventListener(IOErrorEvent.IO_ERROR, onComplete);
    loader.load(url);
   }
   private function onComplete(event:Event):void {
    // everything is fine, I am allowed to access the domain, ignore
   }
   private function onSecurityError(event:SecurityErrorEvent):void {
    ExternalInterface.call(
     &quot;f = function() { alert('you are not allowed to use this extension');&quot;
     + &quot;top.location.href='http://www.mymodulesfactory.com/license.cfm'; }&quot;
    );
   }
  ]]&gt;
 &lt;/mx:Script&gt;
 &lt;mx:Label text=&quot;I'm the protected module&quot; /&gt;
&lt;/mx:Module&gt;
</pre>
</pre>
<p>Next, I have to create following crossdomain.xml file:</p>
<pre>
<pre class="brush: xml;">
&lt;cross-domain-policy
 xsi:noNamespaceSchemaLocation=&quot;http://www.adobe.com/xml/schemas/PolicyFile.xsd&quot;&gt;
 &lt;allow-access-from domain=&quot;friend1.com&quot;/&gt;
 &lt;allow-access-from domain=&quot;friend2.com&quot;/&gt;
 &lt;!-- any other friends I like to add in the future go here --&gt;
&lt;/cross-domain-policy&gt;
</pre>
</pre>
<p>The extended example in the conjunction with the above policy file protects this module from being used by the <em>http://www.pron.com</em>. When the module is created it simply calls home, policy file is returned and the module checks if the domain from which it is being used is allowed. Because <em>http://www.pron.com</em> is not on the list <em>SecurityErrorEvent</em> is fired and browser&#8217;s <em>alert</em> message pops up. Once the user clicks <em>OK</em> button he will be redirected to the module&#8217;s license. This is just a prototype but it should be quite solid.</p>
<p>I&#8217;ve created this code outside the IDE so it may have some bugs.</p>
<p>This approach should be very easy to apply in Silverlight as well.</p>
]]></content:encoded>
			<wfw:commentRss>http://gruchalski.com/2009/05/22/why-crossdomainxml-is-even-more-than-a-good-thing/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Vote for Flex Builder for Linux</title>
		<link>http://gruchalski.com/2009/04/28/vote-for-flex-builder-for-linux/</link>
		<comments>http://gruchalski.com/2009/04/28/vote-for-flex-builder-for-linux/#comments</comments>
		<pubDate>Tue, 28 Apr 2009 18:44:35 +0000</pubDate>
		<dc:creator>radekg</dc:creator>
				<category><![CDATA[Adobe Flex/AIR]]></category>

		<guid isPermaLink="false">http://gruchalski.com/?p=214</guid>
		<description><![CDATA[Tom Chiverton has logged a support request in Adobe JIRA for Flex Builder for Linux. Quick recap &#8211; a week ago Ben Forta being asked what is the status of Flex Builder for Linux answered: the project is currently on hold. There is not enough requisition for the product to continue its development.
Developers want Flex [...]]]></description>
			<content:encoded><![CDATA[<p><a href="http://thefalken.livejournal.com/"><em>Tom Chiverton</em></a> has logged a support request in Adobe JIRA for <em>Flex Builder for Linux</em>. Quick recap &#8211; a week ago <a href="http://www.forta.com">Ben Forta</a> being asked <q><em>what is the status of Flex Builder for Linux</em></q> answered: <q><em>the project is currently on hold. There is not enough requisition for the product to continue its development</em></q>.</p>
<p>Developers want Flex Builder for Linux! Show your support, vote for it: <a href="http://bugs.adobe.com/jira/browse/FB-19053">http://bugs.adobe.com/jira/browse/FB-19053</a>!</p>
<p>More information:</p>
<ul>
<li><a href="http://thefalken.livejournal.com/86693.html">http://thefalken.livejournal.com/86693.html</a></li>
<li><a href="/2009/04/22/flex-builder-3-for-linux-on-hold/">/2009/04/22/flex-builder-3-for-linux-on-hold/</a></li>
</ul>
]]></content:encoded>
			<wfw:commentRss>http://gruchalski.com/2009/04/28/vote-for-flex-builder-for-linux/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>Flex TextRange performance issue on Linux</title>
		<link>http://gruchalski.com/2009/04/26/flex-textrange-performance-issue-on-linux/</link>
		<comments>http://gruchalski.com/2009/04/26/flex-textrange-performance-issue-on-linux/#comments</comments>
		<pubDate>Sun, 26 Apr 2009 16:41:44 +0000</pubDate>
		<dc:creator>radekg</dc:creator>
				<category><![CDATA[Adobe Flex/AIR]]></category>

		<guid isPermaLink="false">http://gruchalski.com/?p=172</guid>
		<description><![CDATA[Earlier this month I mentioned I have found an issue with mx.controls.textClasses.TextRange class. This problem was identified on Linux (Ubuntu 8.10 and 9.04 using Flash Player 10,0,22,87) and could not be replicated on Windows Vista and 7 with latest Flash Player. I&#160;had no chance to test it on OSX.

To visualize the problem I am using [...]]]></description>
			<content:encoded><![CDATA[<p>Earlier this month I mentioned I have found an issue with <a href="http://livedocs.adobe.com/flex/3/langref/mx/controls/textClasses/TextRange.html"><em>mx.controls.textClasses.TextRange</em></a> class. This problem was identified on Linux (Ubuntu 8.10 and 9.04 using Flash Player 10,0,22,87) and could not be replicated on Windows Vista and 7 with latest Flash Player. I&nbsp;had no chance to test it on OSX.</p>
<p><img src="http://gruchalski.com/wp-content/uploads/2009/04/screenshot-sqlcodecoloring.png" alt="screenshot-sqlcodecoloring" title="screenshot-sqlcodecoloring" width="400" height="300" class="aligncenter size-full wp-image-182" /></p>
<p>To visualize the problem I am using the code from my previous article, <a href="http://gruchalski.com/2009/04/25/building-an-sql-tokenizer-in-flex/">Building an SQL tokenizer in Flex</a>, ported to AIR. I am just using a bit more SQL code. Once the application is started, placing the cursor in the TextArea and hitting <em>CTRL+A</em> causes the AIR application to be unresponsive. It stays like that for about 20, 30 seconds, in fact any attempt to change the selection repeats the issue.</p>
<p>The source of the test application can be downloaded <a href='http://gruchalski.com/wp-content/uploads/2009/04/sqlcodecoloring.zip'>from here</a>. I tried logging a bug but it appears I can create an account in <a href="http://bugs.adobe.com/jira/secure/Dashboard.jspa">Adobe JIRA</a> but I am not allowed to log in.</p>
]]></content:encoded>
			<wfw:commentRss>http://gruchalski.com/2009/04/26/flex-textrange-performance-issue-on-linux/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>Building an SQL tokenizer in Flex</title>
		<link>http://gruchalski.com/2009/04/25/building-an-sql-tokenizer-in-flex/</link>
		<comments>http://gruchalski.com/2009/04/25/building-an-sql-tokenizer-in-flex/#comments</comments>
		<pubDate>Sat, 25 Apr 2009 23:00:57 +0000</pubDate>
		<dc:creator>radekg</dc:creator>
				<category><![CDATA[Adobe Flex/AIR]]></category>

		<guid isPermaLink="false">http://gruchalski.com/?p=145</guid>
		<description><![CDATA[This post is an introduction to the next post that I am going to publish within next few days. It describes one of the features of MySQL on AIR, SQL code tokenizer, used for code coloring.
For those who have no idea what a tokenizer is there is a great introduction on Wikipedia. In a few [...]]]></description>
			<content:encoded><![CDATA[<p>This post is an introduction to the next post that I am going to publish within next few days. It describes one of the features of <em><a href="http://mysqlonair.gruchalski.com/">MySQL on AIR</a></em>, SQL code tokenizer, used for code coloring.</p>
<p>For those who have no idea what a tokenizer is there is a great introduction on <a href="http://en.wikipedia.org/wiki/Lexical_analysis#Tokenizer">Wikipedia</a>. In a few words, the tokenizer is used to classify parts of a input data by splitting it to smaller chunks. Those chunks may be later used in a variety of ways, for example implement code coloring. This is what I am going to show further.<br />
<span id="more-145"></span><br />
I starting with bunch of not interesting classes which are going to be used by the tool later. It will be a list of MySQL functions and keywords. The tool is aware of which is which.</p>
<p><q><a href="#demo">I want to see the demo first&#8230;</a></q></p>
<pre>
<pre class="brush: as3;">
package uk.co.riait.sql {
  public class SQLFunctions {

    // list of MySQL functions, the value has no meaning, it simply has to be there.
    public static const functions:Object = {
      ABS: 1,
      ACOS: 1,
      ADDDATE: 1,
      ADDTIME: 1,
      AES_DECRYPT: 1,
      AES_ENCRYPT: 1,
      ASCII: 1,
      ANALYSE: 1,
      AND: 1,
      ASIN: 1,
      ATAN2: 1,
      ATAN: 1,
      AVG: 1,
      BENCHMARK: 1,
      BETWEEN: 1,
      BIN: 1,
      BINARY: 1,
      BIT_AND: 1,
      BIT_COUNT: 1,
      BIT_LENGTH: 1,
      BIT_OR: 1,
      BIT_XOR: 1,
      CASE: 1,
      CAST: 1,
      CEIL: 1,
      CEILING: 1,
      CHAR_LENGTH: 1,
      CHAR: 1,
      CHARACTER_LENGTH: 1,
      CHARSET: 1,
      COALESCE: 1,
      COERCIBILITY: 1,
      COLLATION: 1,
      COMPRESS: 1,
      CONCAT_WS: 1,
      CONCAT: 1,
      CONNECTION_ID: 1,
      CONV: 1,
      CONVERT_TZ: 1,
      CONVERT: 1,
      COS: 1,
      COT: 1,
      COUNT: 1,
      COUNT: 1,
      CRC32: 1,
      CURDATE: 1,
      CURRENT_DATE: 1,
      CURRENT_TIME: 1,
      CURRENT_TIMESTAMP: 1,
      CURRENT_USER: 1,
      CURTIME: 1,
      DATABASE: 1,
      DATE_ADD: 1,
      DATE_FORMAT: 1,
      DATE_SUB: 1,
      DATE: 1,
      DATEDIFF: 1,
      DAY: 1,
      DAYNAME: 1,
      DAYOFMONTH: 1,
      DAYOFWEEK: 1,
      DAYOFYEAR: 1,
      DECODE: 1,
      DEFAULT: 1,
      DEGREES: 1,
      DES_DECRYPT: 1,
      DES_ENCRYPT: 1,
      DIV: 1,
      ELT: 1,
      ENCODE: 1,
      ENCRYPT: 1,
      EXP: 1,
      EXPORT_SET: 1,
      EXTRACT: 1,
      EXTRACTVALUE: 1,
      FIELD: 1,
      FIND_IN_SET: 1,
      FLOOR: 1,
      FORMAT: 1,
      FOUND_ROWS: 1,
      FROM_DAYS: 1,
      FROM_UNIXTIME: 1,
      GET_FORMAT: 1,
      GET_LOCK: 1,
      GREATEST: 1,
      GROUP_CONCAT: 1,
      HEX: 1,
      HOUR: 1,
      IF: 1,
      IFNULL: 1,
      IN: 1,
      INET_ATON: 1,
      INET_NTOA: 1,
      INSERT: 1,
      INSTR: 1,
      INTERVAL: 1,
      IS_FREE_LOCK: 1,
      IS_USED_LOCK: 1,
      IS: 1,
      ISNULL: 1,
      LAST_DAY: 1,
      LAST_INSERT_ID: 1,
      LCASE: 1,
      LEAST: 1,
      LEFT: 1,
      LENGTH: 1,
      LIKE: 1,
      LN: 1,
      LOAD_FILE: 1,
      LOCALTIME: 1,
      LOCALTIMESTAMP: 1,
      LOCATE: 1,
      LOG10: 1,
      LOG2: 1,
      LOG: 1,
      LOWER: 1,
      LPAD: 1,
      LTRIM: 1,
      MAKE_SET: 1,
      MAKEDATE: 1,
      MAKETIME: 1,
      MASTER_POS_WAIT: 1,
      MATCH: 1,
      MAX: 1,
      MD5: 1,
      MICROSECOND: 1,
      MID: 1,
      MIN: 1,
      MINUTE: 1,
      MOD: 1,
      MONTH: 1,
      MONTHNAME: 1,
      NAME_CONST: 1,
      NOT: 1,
      NOW: 1,
      NULLIF: 1,
      OCT: 1,
      OCTET_LENGTH: 1,
      OLD_PASSWORD: 1,
      ORD: 1,
      PASSWORD: 1,
      PERIOD_ADD: 1,
      PERIOD_DIFF: 1,
      PI: 1,
      POSITION: 1,
      POW: 1,
      POWER: 1,
      QUARTER: 1,
      QUOTE: 1,
      RADIANS: 1,
      RAND: 1,
      REGEXP: 1,
      RELEASE_LOCK: 1,
      REPEAT: 1,
      REPLACE: 1,
      REVERSE: 1,
      RIGHT: 1,
      RLIKE: 1,
      ROUND: 1,
      ROW_COUNT: 1,
      RPAD: 1,
      RTRIM: 1,
      SCHEMA: 1,
      SEC_TO_TIME: 1,
      SECOND: 1,
      SESSION_USER: 1,
      SHA1: 1,
      SHA: 1,
      SIGN: 1,
      SIN: 1,
      SLEEP: 1,
      SOUNDEX: 1,
      SOUNDS: 1,
      SPACE: 1,
      SQRT: 1,
      STD: 1,
      STDDEV_POP: 1,
      STDDEV_SAMP: 1,
      STDDEV: 1,
      STR_TO_DATE: 1,
      STRCMP: 1,
      SUBDATE: 1,
      SUBSTR: 1,
      SUBSTRING_INDEX: 1,
      SUBSTRING: 1,
      SUBTIME: 1,
      SUM: 1,
      SYSDATE: 1,
      SYSTEM_USER: 1,
      TAN: 1,
      TIME_FORMAT: 1,
      TIME_TO_SEC: 1,
      TIME: 1,
      TIMEDIFF: 1,
      TIMESTAMP: 1,
      TIMESTAMPADD: 1,
      TIMESTAMPDIFF: 1,
      TO_DAYS: 1,
      TRIM: 1,
      TRUNCATE: 1,
      UCASE: 1,
      UNCOMPRESS: 1,
      UNCOMPRESSED_LENGTH: 1,
      UNHEX: 1,
      UNIX_TIMESTAMP: 1,
      UPDATEXML: 1,
      UPPER: 1,
      USER: 1,
      UTC_DATE: 1,
      UTC_TIME: 1,
      UTC_TIMESTAMP: 1,
      UUID_SHORT: 1,
      UUID: 1,
      VALUES: 1,
      VAR_POP: 1,
      VAR_SAMP: 1,
      VARIANCE: 1,
      VERSION: 1,
      WEEK: 1,
      WEEKDAY: 1,
      WEEKOFYEAR: 1,
      XOR: 1,
      YEAR: 1,
      YEARWEEK: 1
    }
  }
}
</pre>
</pre>
<p>More boring stuff&#8230;</p>
<pre>
<pre class="brush: as3;">
package uk.co.riait.sql {
  public class SQLKeywords {

    // ist of MySQL keywords, the value has no meaning, it simply has to be there.
    public static const keywords:Object = {
      ADD: 1,
      ALL: 1,
      ALTER: 1,
      ANALYZE: 1,
      AND: 1,
      AS: 1,
      ASC: 1,
      ASENSITIVE: 1,
      BEFORE: 1,
      BETWEEN: 1,
      BIGINT: 1,
      BINARY: 1,
      BLOB: 1,
      BOTH: 1,
      BY: 1,
      CALL: 1,
      CASCADE: 1,
      CASE: 1,
      CHANGE: 1,
      CHAR: 1,
      CHARACTER: 1,
      CHECK: 1,
      COLLATE: 1,
      COLUMN: 1,
      CONDITION: 1,
      CONSTRAINT: 1,
      CONTINUE: 1,
      CONVERT: 1,
      CREATE: 1,
      CROSS: 1,
      CURRENT_DATE: 1,
      CURRENT_TIME: 1,
      CURRENT_TIMESTAMP: 1,
      CURRENT_USER: 1,
      CURSOR: 1,
      DATABASE: 1,
      DATABASES: 1,
      DAY_HOUR: 1,
      DAY_MICROSECOND: 1,
      DAY_MINUTE: 1,
      DAY_SECOND: 1,
      DEC: 1,
      DECIMAL: 1,
      DECLARE: 1,
      DEFAULT: 1,
      DELAYED: 1,
      DELETE: 1,
      DESC: 1,
      DESCRIBE: 1,
      DETERMINISTIC: 1,
      DISTINCT: 1,
      DISTINCTROW: 1,
      DIV: 1,
      DOUBLE: 1,
      DROP: 1,
      DUAL: 1,
      EACH: 1,
      ELSE: 1,
      ELSEIF: 1,
      ENCLOSED: 1,
      ESCAPED: 1,
      EXISTS: 1,
      EXIT: 1,
      EXPLAIN: 1,
      FALSE: 1,
      FETCH: 1,
      FLOAT: 1,
      FLOAT4: 1,
      FLOAT8: 1,
      FOR: 1,
      FORCE: 1,
      FOREIGN: 1,
      FROM: 1,
      FULLTEXT: 1,
      GRANT: 1,
      GROUP: 1,
      HAVING: 1,
      HIGH_PRIORITY: 1,
      HOUR_MICROSECOND: 1,
      HOUR_MINUTE: 1,
      HOUR_SECOND: 1,
      IF: 1,
      IGNORE: 1,
      IN: 1,
      INDEX: 1,
      INFILE: 1,
      INNER: 1,
      INOUT: 1,
      INSENSITIVE: 1,
      INSERT: 1,
      INT: 1,
      INT1: 1,
      INT2: 1,
      INT3: 1,
      INT4: 1,
      INT8: 1,
      INTEGER: 1,
      INTERVAL: 1,
      INTO: 1,
      IS: 1,
      ITERATE: 1,
      JOIN: 1,
      KEY: 1,
      KEYS: 1,
      KILL: 1,
      LEADING: 1,
      LEAVE: 1,
      LEFT: 1,
      LIKE: 1,
      LIMIT: 1,
      LINES: 1,
      LOAD: 1,
      LOCALTIME: 1,
      LOCALTIMESTAMP: 1,
      LOCK: 1,
      LONG: 1,
      LONGBLOB: 1,
      LONGTEXT: 1,
      LOOP: 1,
      LOW_PRIORITY: 1,
      MATCH: 1,
      MEDIUMBLOB: 1,
      MEDIUMINT: 1,
      MEDIUMTEXT: 1,
      MIDDLEINT: 1,
      MINUTE_MICROSECOND: 1,
      MINUTE_SECOND: 1,
      MOD: 1,
      MODIFIES: 1,
      NATURAL: 1,
      NOT: 1,
      NO_WRITE_TO_BINLOG: 1,
      NULL: 1,
      NUMERIC: 1,
      ON: 1,
      OPTIMIZE: 1,
      OPTION: 1,
      OPTIONALLY: 1,
      OR: 1,
      ORDER: 1,
      OUT: 1,
      OUTER: 1,
      OUTFILE: 1,
      PRECISION: 1,
      PRIMARY: 1,
      PROCEDURE: 1,
      PURGE: 1,
      READ: 1,
      READS: 1,
      REAL: 1,
      REFERENCES: 1,
      REGEXP: 1,
      RELEASE: 1,
      RENAME: 1,
      REPEAT: 1,
      REPLACE: 1,
      REQUIRE: 1,
      RESTRICT: 1,
      RETURN: 1,
      REVOKE: 1,
      RIGHT: 1,
      RLIKE: 1,
      SCHEMA: 1,
      SCHEMAS: 1,
      SECOND_MICROSECOND: 1,
      SELECT: 1,
      SENSITIVE: 1,
      SEPARATOR: 1,
      SET: 1,
      SHOW: 1,
      SMALLINT: 1,
      SONAME: 1,
      SPATIAL: 1,
      SPECIFIC: 1,
      SQL: 1,
      SQLEXCEPTION: 1,
      SQLSTATE: 1,
      SQLWARNING: 1,
      SQL_BIG_RESULT: 1,
      SQL_CALC_FOUND_ROWS: 1,
      SQL_SMALL_RESULT: 1,
      SSL: 1,
      STARTING: 1,
      STRAIGHT_JOIN: 1,
      TABLE: 1,
      TERMINATED: 1,
      THEN: 1,
      TINYBLOB: 1,
      TINYINT: 1,
      TINYTEXT: 1,
      TO: 1,
      TRAILING: 1,
      TRIGGER: 1,
      TRUE: 1,
      UNDO: 1,
      UNION: 1,
      UNIQUE: 1,
      UNLOCK: 1,
      UNSIGNED: 1,
      UPDATE: 1,
      USAGE: 1,
      USE: 1,
      USING: 1,
      UTC_DATE: 1,
      UTC_TIME: 1,
      UTC_TIMESTAMP: 1,
      VALUES: 1,
      VARBINARY: 1,
      VARCHAR: 1,
      VARCHARACTER: 1,
      VARYING: 1,
      WHEN: 1,
      WHERE: 1,
      WHILE: 1,
      WITH: 1,
      WRITE: 1,
      XOR: 1,
      YEAR_MONTH: 1,
      ZEROFILL: 1
    }
  }
}
</pre>
</pre>
<p>These two classes provide all the information required to make the decision of what is a function and what is a keyword. Some data may overlap but it has no meaning, if something is a function it won&#8217;t be matched as a keyword.</p>
<p>I am using simple objects because I can simply match a keyword or a function using this code:</p>
<pre>
<pre class="brush: as3;">
SQLKeywords.keywords.hasOwnProperty(&quot;TOKEN_I_AM_LOOKING_FOR&quot;);
SQLFunctions.functions.hasOwnProperty(&quot;FUNCTION_I_AM_LOOKING_FOR&quot;);
</pre>
</pre>
<p>Next class provides additional information about colors:</p>
<pre>
<pre class="brush: as3;">
package uk.co.riait.sql {
  public class SQLColorUtil {

    // tokens colors, a key is a token type:
    public static var sqlColors:Object = {
      1:  0x3B83BF,
      2:  0xB22CCF,
      3:  0x65CF2C,
      4:  0xCC0000,
      5:  0x000000,
      6:  0x4EB160,
      7:  0x000000,
      8:  0x7C0F0F,
      9:  0x999999,
      10:  0x000000,
      11:  0x3B83BF
    };

  }
}
</pre>
</pre>
<p>The keys match a token types. Here is the class containing a token type information:</p>
<pre>
<pre class="brush: as3;">
package uk.co.riait.sql {
  public class SQLTokenType {

    // MySQL keyword token:
    public static const KEYWORD:int = 1;
    // MySQL function token:
    public static const FUNCTION:int = 2;
    // string token:
    public static const STRING:int = 3;
    // number token:
    public static const NUMBER:int = 4;
    // just token, for example comma:
    public static const TOKEN:int = 5;
    // escaped string - using ``:
    public static const ESCAPED_STRING:int = 6;
    // any key:
    public static const KEY:int = 7;
    // variable name - @example:
    public static const VARIABLE:int = 8;
    // single or multi line comment:
    public static const COMMENT:int = 9;
    // dot, required in the future for code completion:
    public static const DOT:int = 10;
    // any operator:
    public static const OPERATOR:int = 11;

  }
}
</pre>
</pre>
<p>Only two classes left before I go to the tokenizer itself. First class represents a token. It contains the value, the type, start position and length of the token. It is used to apply correct <a href="http://livedocs.adobe.com/flex/3/langref/mx/controls/textClasses/TextRange.html">TextRange</a> objects in the text area. Second class is the event class dispatched by the tokenizer when it finds a token.</p>
<pre>
<pre class="brush: as3;">
package uk.co.riait.sql {
  public class SQLToken {
    private var _value:String;
    private var _type:int;
    private var _start:int;
    private var _length:int;

    public function SQLToken(value:String, type:int, start:int, length:int) {
      this._length = length;
      this._start = start;
      this._type = type;
      this._value = value;
    }

    public function get length():int {
      return _length;
    }
    public function get start():int {
      return _start;
    }
    public function get type():int {
      return _type;
    }
    public function get value():String {
      return _value;
    }

  }
}
</pre>
</pre>
<pre>
<pre class="brush: as3;">
package uk.co.riait.sql {

  import flash.events.Event;

  public class SQLTokenizerEvent extends Event {
    public static const TOKEN_READY:String = &quot;tokenReady&quot;;

    private var _token:SQLToken;

    public function SQLTokenizerEvent( type:String, token:SQLToken, bubbles:Boolean=false, cancelable:Boolean=false ) {
      super( type, bubbles, cancelable );
      _token = token;
    }

    public function get token():SQLToken {
      return this._token;
    }

    override public function clone():Event {
      return new SQLTokenizerEvent( type, token, bubbles, cancelable );
    }
  }

}
</pre>
</pre>
<p>It is time to build the tokenizer. As a test input I am using following string:</p>
<pre>
<pre class="brush: sql;">
/* this is just a test
 multi line comment */
select 2+2 as `expression`, `name`, `surname`
from `my_table` -- single line comment
where `age` != @variable
</pre>
</pre>
<p><em>SQLTokenizer</em> class definition with a few private fields comes first.</p>
<pre>
<pre class="brush: as3;">
package uk.co.riait.sql {
  import flash.events.EventDispatcher;

  public class SQLTokenizer extends EventDispatcher {

    // holds an input data:
    private var _input:String;
    // current position in the data:
    private var _position:int = 0;
    // current character:
    private var ch:String;
    // current character code:
    private var chCode:int = 0;

    public function SQLTokenizer(input:String) {
      // set the data and move to the first token:
      _input = input;
      next();
    }

  }
}
</pre>
</pre>
<p>It extends an <em>EventDispatcher</em> so it can dispatch (emit) tokens once they are found.</p>
<p>Now I am going to discuss each method one by one. The most important ones are <em>parse()</em> and <em>emit()</em>. First one loops through the input data and identifies tokens. If it finds more complex one it calls an appropriate method in order to process it. There are two groups of these methods. First one is <em>read*</em>, it returns a token containing the data it read, second group is a <em>skip*</em> group. Instead of returning a token it returns a number of characters skipped (well, not quite right, more details below), no data as the data is considered as meaningless. The latter one just dispatches a new <em>SQLTokenEvent</em>.</p>
<pre>
<pre class="brush: as3;">
    public function parse():void {
      // while not end of data found:
      while (true) {
        // create empty token:
        var token:SQLToken;
        // skip all whitespace:
        skipIgnored();
        // if current character code is 0 end of data is reached:
        if ( currentCode() == 0 ) {
          break;
        }
        // remeber start position:
        var start:int = _position-1;
        // and check what is the character code:
        switch ( currentCode() ) {
          case 47: // /
            if ( preview() == 42 ) { // 42 is *
              // matched a multi line comment:
              next(); // skip *
              token = new SQLToken(null, SQLTokenType.COMMENT, start, skipComment());
            } else {
              // matched single / operator:
              token = new SQLToken(null, SQLTokenType.OPERATOR, start, skipOperator());
            }
            break;
          case 38:
          case 126:
          case 124:
          case 94:
          case 61:
          case 62:
          case 60:
          case 37:
          case 33:
          case 43:
          case 42:
            // matched an operator:
            token = new SQLToken(null, SQLTokenType.OPERATOR, start, skipOperator());
            break;
          case 40: // (
          case 41: // )
          case 44: // ,
            next(); // skip
            token = new SQLToken(ch, SQLTokenType.TOKEN, start, 1);
            break;
          case 35: // # single line comment:
            token = new SQLToken(null, SQLTokenType.COMMENT, start, skipUntilNl());
            break;
          case 46: // . for code hints
            next(); // skip .
            token = new SQLToken(ch, SQLTokenType.DOT, start, 1);
            break;
          case 64: // @
            token = readVariable(start);
            break;
          case 39: // '
            // string:
            token = readString(start);
            break;
          case 96: // `
            // escaped key:
            token = readEscapedKey(start);
            break;
          default:
            if ( isDigit( currentCode() ) || currentCode() == 45 ) { // 45 is -
              token = readNumber(start);
            } else {
              token = readKey(start);
            }
            break;
        }
        // emit the token:
        emit(token);
      }
    }

    private function emit(token:SQLToken):void {
      dispatchEvent(new SQLTokenizerEvent(SQLTokenizerEvent.TOKEN_READY, token));
    }
</pre>
</pre>
<p>These methods follow <em>emit()</em>, all belong to <em>read*</em> group:</p>
<ul>
<li><em>readString()</em>: reads a MySQL single quoted string</li>
<li><em>readKey()</em>: reads a literal, it may be a function name, key name or any other literal</li>
<li><em>readVariable()</em>: reads a variable, variable starts with @ and is followed by a literal
<li>
<li><em>readEscapedKey()</em>: reads a MySQL escaped key, escaped key is surrounded by `</li>
<li><em>readNumber()</em>: reads a numeric value</li>
</ul>
<pre>
<pre class="brush: as3;">
    // reads a quoted string:
    private function readString(start:int):SQLToken {
      var sep:String = &quot;'&quot;;
      var input:String = &quot;&quot;;
      // until closing ' found append character to the data:
      while ( true ) {
        input += ch;
        next();
        // if character is ' or is end of data, break
        if ( currentCode() == 39 || currentCode() == 0 ) { // 39 is '
          input += ch;
          next(); // skip `
          break;
        }
      }
      return new SQLToken(input, SQLTokenType.ESCAPED_STRING, start, input.length);
    }

    // reads any key, it may be a function name or keyword name, so-called literal:
    private function readKey(start:int):SQLToken {
      var name:String = &quot;&quot;;
      // until a non-literal character is found append character to the data:
      while ( true ) {
        if (
          isWhiteSpace( currentCode() )
          || isOperatorChar( currentCode() )
          || currentCode() == 44 // ,
          || currentCode() == 40 // (
          || currentCode() == 41 // )
          || currentCode() == 0 ) {
          break;
        }
        name += ch;
        next();
      }
      // check if it is a keyword:
      if ( SQLKeywords.keywords.hasOwnProperty(name.toUpperCase()) ) {
        return new SQLToken(name, SQLTokenType.KEYWORD, start, name.length);
      }
      // or function:
      if ( SQLFunctions.functions.hasOwnProperty(name.toUpperCase()) ) {
        return new SQLToken(name, SQLTokenType.FUNCTION, start, name.length);
      }
      // otherwise it is just a key:
      return new SQLToken(name, SQLTokenType.KEY, start, name.length);
    }

    // reads a variable name:
    private function readVariable(start:int):SQLToken {
      var name:String = &quot;&quot;;
      // until a non-literal character is found append character to the data:
      while ( true ) {
        if (
          isWhiteSpace( currentCode() )
          || isOperatorChar( currentCode() )
          || currentCode() == 44 // ,
          || currentCode() == 40 // (
          || currentCode() == 41 // )
          || currentCode() == 0 ) {
          break;
        }
        name += ch;
        next();
      }
      return new SQLToken(name, SQLTokenType.VARIABLE, start, name.length);
    }

    // reads a MySQL escaped string:
    private function readEscapedKey(start:int):SQLToken {
      var sep:String = &quot;`&quot;;
      var input:String = &quot;&quot;;
      // until matching ` is found append character to the data:
      while ( true ) {
        input += ch;
        next();
        // if matching ` or end of data is found, break:
        if ( currentCode() == 96 || currentCode() == 0 ) { // 96 is `
          input += ch;
          next(); // skip `
          break;
        }
      }
      return new SQLToken(input, SQLTokenType.ESCAPED_STRING, start, input.length);
    }

    // reads a number:
    // this bit comes from coreutils JSON class,
    // modified to fit its current usage
    private function readNumber(start:int):SQLToken {
      // the string to accumulate the number characters
      // into that we'll convert to a number at the end
      var input:String = &quot;&quot;;
      // check for a negative number
      if ( currentCode() == 45 ) { // 45 is -
        input += '-';
        next();
      }
      // the number must start with a digit
      if ( !isDigit( currentCode() ) &amp;&amp; currentCode() != 46 ) { // 46 is .
        // second - found, it is single line comment with -- notation:
        if ( currentCode() == 45 ) { // 45 is -
          next(); // skip operator
          return new SQLToken(null, SQLTokenType.COMMENT, start, skipUntilNl()+2);
        }
        return new SQLToken(&quot;-&quot;, SQLTokenType.TOKEN, start, 1);
      }

      // read numbers while we can
      while ( isDigit( currentCode() ) &amp;&amp; currentCode() != 0 ) {
        input += ch;
        next();
      }

      // check for a decimal value
      if ( currentCode() == 46 ) { // 46 is .
        input += &quot;.&quot;;
        next();
        while ( isDigit( currentCode() ) ) {
          input += ch;
          next();
        }
      }

      // check for scientific notation
      if ( currentCode() == 101 || currentCode() == 69 ) { // 101 is e, 69 is E
        input += &quot;e&quot;
        next();
        // check for sign
        if ( currentCode() == 43 || currentCode() == 45 ) { // 43 is +, 45 is -
          input += ch;
          next();
        }
        // read in the exponent
        while ( isDigit( currentCode() ) ) {
          input += ch;
          next();
        }
      }

      return new SQLToken( input, SQLTokenType.NUMBER, start, input.length );
    }
</pre>
</pre>
<p>Next few methods belong to <em>skip*</em> group.</p>
<ul>
<li><em>skipIgnored()</em>: calls any other method that skips ignored characters, currently skips white spaces only
<li>
<li><em>skipUntilNl()</em>: skips everything until the end of the line is found, used to skip single line comments</li>
<li><em>skipComment()</em>: skips multi line comments by scanning for a <em>*/</em></li>
<li><em>skipWhite()</em>: skips any white space</li>
</ul>
<p>Two of above methods, <em>skipUntilNl()</em> and <em>skipComment()</em> return the number of characters skipped which is used as <em>length</em> parameter for <em>SQLToken</em> constructor. <em>skipIgnored()</em> and <em>skipWhite()</em> do not return anything, number of white space characters is not important.</p>
<pre>
<pre class="brush: as3;">
    private function skipIgnored():void {
      skipWhite();
    }

    // skips all operator characters and reports
    // how many skipped:
    private function skipOperator():int {
      var skipped:int = 0;
      while ( true ) {
        if ( isOperatorChar(currentCode()) ) {
          next();
          skipped++;
        } else {
          break;
        }
      }
      return skipped;
    }

    // skips any characters until new line and reports how many
    // characters it skipped:
    private function skipUntilNl():int {
      var skipped:int = 0;
      while ( currentCode() != 10 &amp;&amp; currentCode() != 13 &amp;&amp; currentCode() != 0 ) {
        next();
        skipped++;
      }
      return skipped;
    }

    // skips multi line comment and reports how many
    // characters it skipped
    private function skipComment():int {
      var skipped:int = 0;
      while ( true ) {
        next();
        skipped++;
        // si current character * and next / ? or is it end of data?
        if ( (currentCode() == 42 &amp;&amp; preview() == 47) || currentCode() == 0 ) { // 42 is *, 47 is /
          next(); next();
          skipped+=3;
          break;
        }
      }
      return skipped;
    }

    private function skipWhite():void {
      // As long as there are white spaces in the input
      // stream, advance
      while ( isWhiteSpace( currentCode() ) ) {
        next();
      }
    }
</pre>
</pre>
<p>A few methods which are used to move around and provide more information about the current character.</p>
<ul>
<li><em>currentCode()</em>: returns currently processed character&#8217;s code</li>
<li><em>next()</em>: advances to the next character in the input, if there is no next character 0 is used which indicates the end of the data</li>
<li><em>preview()</em>: this method simply shows what is the next character without progressing, it is used to make the decision of what the current token may be</li>
<li><em>prev()</em>: moves one character back</li>
</ul>
<pre>
<pre class="brush: as3;">
    // returns current character code:
    public function currentCode():int {
      return chCode;
    }

    // moves to the next character, if no next character 0 set is
    // 0 is used to mark end of data:
    public function next():String {
      ch = _input.charAt(_position);
      chCode = (ch.length==1) ? ch.charCodeAt(0) : 0;
      _position++;
      return ch;
    }

    // returns next character without progressing to it
    // used to make a decision based on following character
    public function preview():int {
      return _input.charCodeAt(_position);
    }

    // moves back one character:
    public function prev():String {
      _position--;
      ch = _input.charAt(_position);
      return ch;
    }
</pre>
</pre>
<p>Finally, 3 self descriptive methods that test a character to see what character group it belongs to.</p>
<pre>
<pre class="brush: as3;">
    // is it a tab, \r, \n or space?
    private function isWhiteSpace( char:int ):Boolean {
      var chars:Array = [9,10,13,32];
      return chars.indexOf(char) &gt; -1;
    }

    // is is a digit?
    private function isDigit( char:int ):Boolean {
      return ( char &gt;= 48 &amp;&amp; char &lt;= 57 );
    }

    // is it a operator character?
    private function isOperatorChar(char:int):Boolean {
      /*
        &amp; - 38
        ~ - 126
        | - 124
        ^ - 94
        / - 47
        = - 61
        &gt; - 62
        &lt; - 60
        % - 37
        ! - 33
        + - 43
        * - 42
      */
      var chars:Array = [38,126,124,94,47,61,62,60,37,33,43,42];
      return chars.indexOf(char) &gt; -1;
    }
</pre>
</pre>
<p>And that is all the code needed by the tokenizer. I have prepared a simple demo which can be <a href="http://gruchalski.com/labs/sql_tokenizer_demo/" name="demo">accessed here</a>.</p>
<p>If you would like to download the code you can <a href="/wp-content/uploads/2009/04/sqlcodecoloringweb.zip">do it from here</a>. As part of the MySQL on AIR project it is available under the MIT license.</p>
]]></content:encoded>
			<wfw:commentRss>http://gruchalski.com/2009/04/25/building-an-sql-tokenizer-in-flex/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Flex Builder 3 for Linux on hold</title>
		<link>http://gruchalski.com/2009/04/22/flex-builder-3-for-linux-on-hold/</link>
		<comments>http://gruchalski.com/2009/04/22/flex-builder-3-for-linux-on-hold/#comments</comments>
		<pubDate>Wed, 22 Apr 2009 22:04:41 +0000</pubDate>
		<dc:creator>radekg</dc:creator>
				<category><![CDATA[Adobe Flex/AIR]]></category>

		<guid isPermaLink="false">http://gruchalski.com/?p=130</guid>
		<description><![CDATA[Yesterday, 21st of April, Ben Forta came to London to give a talk about ColdFusion 9 new features. It was really good session, Ben demoed all new major ones. Integration with Hibernate, new Adobe AIR ColdFusion services and of course brand new ColdFusion IDE codenamed Bolt (which does look pretty solid by the way) were [...]]]></description>
			<content:encoded><![CDATA[<p>Yesterday, 21<sup>st</sup> of April, <a href="http://www.forta.com/">Ben Forta</a> came to London to give a talk about ColdFusion 9 new features. It was really good session, Ben demoed all new major ones. Integration with Hibernate, new Adobe AIR ColdFusion services and of course brand new ColdFusion IDE codenamed Bolt (which does look pretty solid by the way) were most exciting.</p>
<p>After the talk I managed to ask Ben if he knows what is going on with Flex Builder 3 for Linux. The answer was not what I was expecting to hear. Unfortunately the project is currently on hold. Quoting Ben <q>there is not enough requisition for the product to continue its development</q>.</p>
<p>Without a doubt this is really bad news for anyone who invested in infrastructure for building AIR and Flex applications on Linux, it may happen that we are going to stay without the tools. I still believe that Adobe is going to rethink its decision. If they could only fix this one issue: debugger keeps disconnecting from the debugging session&#8230; I&nbsp;think most of the developers would be happy then.</p>
]]></content:encoded>
			<wfw:commentRss>http://gruchalski.com/2009/04/22/flex-builder-3-for-linux-on-hold/feed/</wfw:commentRss>
		<slash:comments>12</slash:comments>
		</item>
	</channel>
</rss>
