Steve Linabery
2008-Oct-09 10:16 UTC
[Ovirt-devel] [PATCH server] Add a rudimentary flash chart written in flex framework to summary pages.
First stab at showing real rrd data in flash chart (in this case, memory peak use for last 40 samples of rrd data). Note that to test this, you'll need to get the open flex sdk and compile src/flexchart/flexchart.mxml yourself. Enjoy, Steve -------------- next part -------------->From ce87700f1570ce6146f9cdc87062a079f0192b3c Mon Sep 17 00:00:00 2001From: Steve Linabery <slinabery at redhat.com> Date: Thu, 9 Oct 2008 05:10:02 -0500 Subject: [PATCH server] Add a rudimentary flash chart written in flex framework to summary pages. --- src/app/controllers/graph_controller.rb | 30 +- src/app/views/graph/flexchart_data.rhtml | 1 + src/app/views/graph/history_graphs.rhtml | 87 +--- src/config/routes.rb | 1 + src/flexchart/README.txt | 8 + src/flexchart/com/adobe/serialization/json/JSON.as | 85 +++ .../com/adobe/serialization/json/JSONDecoder.as | 221 ++++++++ .../com/adobe/serialization/json/JSONEncoder.as | 299 +++++++++++ .../com/adobe/serialization/json/JSONParseError.as | 87 +++ .../com/adobe/serialization/json/JSONToken.as | 104 ++++ .../com/adobe/serialization/json/JSONTokenType.as | 67 +++ .../com/adobe/serialization/json/JSONTokenizer.as | 547 ++++++++++++++++++++ src/flexchart/flexchart.mxml | 20 + src/flexchart/org/ovirt/ChartLoader.as | 44 ++ src/flexchart/org/ovirt/DataSeries.as | 22 + src/flexchart/org/ovirt/DataSource.as | 37 ++ src/public/javascripts/jquery.flash.js | 288 ++++++++++ 17 files changed, 1870 insertions(+), 78 deletions(-) create mode 100644 src/app/views/graph/flexchart_data.rhtml create mode 100644 src/flexchart/README.txt create mode 100644 src/flexchart/com/adobe/serialization/json/JSON.as create mode 100644 src/flexchart/com/adobe/serialization/json/JSONDecoder.as create mode 100644 src/flexchart/com/adobe/serialization/json/JSONEncoder.as create mode 100644 src/flexchart/com/adobe/serialization/json/JSONParseError.as create mode 100644 src/flexchart/com/adobe/serialization/json/JSONToken.as create mode 100644 src/flexchart/com/adobe/serialization/json/JSONTokenType.as create mode 100644 src/flexchart/com/adobe/serialization/json/JSONTokenizer.as create mode 100644 src/flexchart/flexchart.mxml create mode 100644 src/flexchart/org/ovirt/ChartLoader.as create mode 100644 src/flexchart/org/ovirt/DataSeries.as create mode 100644 src/flexchart/org/ovirt/DataSource.as create mode 100644 src/public/javascripts/jquery.flash.js diff --git a/src/app/controllers/graph_controller.rb b/src/app/controllers/graph_controller.rb index dbe2afc..6450935 100644 --- a/src/app/controllers/graph_controller.rb +++ b/src/app/controllers/graph_controller.rb @@ -3,7 +3,24 @@ require 'util/stats/Stats' class GraphController < ApplicationController layout nil - # generate layout for avaialability bar graphs + def flexchart_data + + #FIXME: use the stats package aggregation (when it's available) + #instead of the old method + graph_obj = history_graph_data_object + + #FIXME: for this release, the flexchart shows only peak values, + # and only shows a default of the last 40 data points in rrd. + graph_data = { :labels => graph_obj[:timepoints].last(40), + :values => graph_obj[:dataset][2][:values].last(40) } + my_data = graph_data[:labels].zip(graph_data[:values]) + @graph = { :vectors => my_data, + :max_value => graph_obj[:total_peak] + } + end + + + # generate layout for availability bar graphs def availability_graph @id = params[:id] @target = params[:target] @@ -67,6 +84,10 @@ class GraphController < ApplicationController # retrieves data for history graphs def history_graph_data + render :json => history_graph_data_object + end + + def history_graph_data_object history_graphs myDays = params[:days] target = params[:target] @@ -212,9 +233,10 @@ class GraphController < ApplicationController :stroke => @avg_history[:color], :strokeWidth => 1 } - ] + ], + :total_peak => total_peak } - render :json => graph_object + end @@ -261,7 +283,7 @@ class GraphController < ApplicationController } ] } - render :json => graph_object + end diff --git a/src/app/views/graph/flexchart_data.rhtml b/src/app/views/graph/flexchart_data.rhtml new file mode 100644 index 0000000..a79ce06 --- /dev/null +++ b/src/app/views/graph/flexchart_data.rhtml @@ -0,0 +1 @@ +<%= @graph.to_json %> diff --git a/src/app/views/graph/history_graphs.rhtml b/src/app/views/graph/history_graphs.rhtml index 2b6874f..f372e4b 100644 --- a/src/app/views/graph/history_graphs.rhtml +++ b/src/app/views/graph/history_graphs.rhtml @@ -1,76 +1,15 @@ +<%= javascript_include_tag "jquery.flash.js" %> +<div id="the-div-name"></div> <script type="text/javascript"> - -var graph = "load_history"; -var days = "7"; - -function swap_history_graph(newgraph, newdays){ - if(newgraph == null) newgraph = graph - if(newdays == null) newdays = days - $('.history_graph').hide(); - $('#' + newgraph + "_" + newdays).parent().show(); - eval("draw_" + newgraph + "_" + newdays + "_graph_get_data()"); -} -function swap_history_graph_target(title, newgraph){ - swap_history_graph(newgraph, null); - $('#history_graph_selection').html(title + ' <%= image_tag 'icon_menu_arrow.gif' %>'); - graph = newgraph -} -function swap_history_graph_time(title, newdays){ - swap_history_graph(null, newdays); - $('#history_graph_time_selection').html(title + ' <%= image_tag 'icon_menu_arrow.gif' %>'); - days = newdays -} - -</script> - -<%= render :partial => '/layouts/graph', :locals => { :drawMe => false, :includeDiv => false, :methodName=> 'draw_cpu_history_1_graph', :div_id => 'cpu_history_1', :chartType => 'line', :yGridLines => 'lightgrey', :xGridLines => 'lightgrey', :ticksX => 20, :scaleX => 173, :ticksY => 10, :scaleY => 110, :url => (url_for :escape => false, :controller => 'graph', :action => 'history_graph_data', :id => @id, :params => { :target => 'cpu', :poolType => @poolType, :days => 1 } ) } %> -<%= render :partial => '/layouts/graph', :locals => { :drawMe => false, :includeDiv => false, :methodName=> 'draw_cpu_history_7_graph', :div_id => 'cpu_history_7', :chartType => 'line', :yGridLines => 'lightgrey', :xGridLines => 'lightgrey', :ticksX => 39, :scaleX => 272, :ticksY => 10, :scaleY => 110, :url => (url_for :escape => false, :controller => 'graph', :action => 'history_graph_data', :id => @id, :params => { :target => 'cpu', :poolType => @poolType, :days => 7 } ) } %> -<%= render :partial => '/layouts/graph', :locals => { :drawMe => false, :includeDiv => false, :methodName=> 'draw_cpu_history_30_graph', :div_id => 'cpu_history_30', :chartType => 'line', :yGridLines => 'lightgrey', :xGridLines => 'lightgrey', :ticksX => 120, :scaleX => 1200, :ticksY => 10, :scaleY => 110, :url => (url_for :escape => false, :controller => 'graph', :action => 'history_graph_data', :id => @id, :params => { :target => 'cpu', :poolType => @poolType, :days => 30 } ) } %> -<%= render :partial => '/layouts/graph', :locals => { :drawMe => false, :includeDiv => false, :methodName=> 'draw_memory_history_1_graph', :div_id => 'memory_history_1', :chartType => 'line', :yGridLines => 'lightgrey', :xGridLines => 'lightgrey', :ticksX => 20, :scaleX => 173, :ticksY => 50, :scaleY => 756, :url => (url_for :escape => false, :controller => 'graph', :action => 'history_graph_data', :id => @id, :params => { :target => 'memory', :poolType => @poolType, :days => 1 } ) } %> -<%= render :partial => '/layouts/graph', :locals => { :drawMe => false, :includeDiv => false, :methodName=> 'draw_memory_history_7_graph', :div_id => 'memory_history_7', :chartType => 'line', :yGridLines => 'lightgrey', :xGridLines => 'lightgrey', :ticksX => 39, :scaleX => 272, :ticksY => 50, :scaleY => 756, :url => (url_for :escape => false, :controller => 'graph', :action => 'history_graph_data', :id => @id, :params => { :target => 'memory', :poolType => @poolType, :days => 7 } ) } %> -<%= render :partial => '/layouts/graph', :locals => { :drawMe => false, :includeDiv => false, :methodName=> 'draw_memory_history_30_graph', :div_id => 'memory_history_30', :chartType => 'line', :yGridLines => 'lightgrey', :xGridLines => 'lightgrey', :ticksX => 120, :scaleX => 1162, :ticksY => 50, :scaleY => 756, :url => (url_for :escape => false, :controller => 'graph', :action => 'history_graph_data', :id => @id, :params => { :target => 'memory', :poolType => @poolType, :days => 30 } ) } %> -<%= render :partial => '/layouts/graph', :locals => { :drawMe => false, :includeDiv => false, :methodName=> 'draw_load_history_1_graph', :div_id => 'load_history_1', :chartType => 'line', :yGridLines => 'lightgrey', :xGridLines => 'lightgrey', :ticksX => 20, :scaleX => 173, :ticksY => 2, :scaleY => 23, :url => (url_for :escape => false, :controller => 'graph', :action => 'history_graph_data', :id => @id, :params => { :target => 'load', :poolType => @poolType, :days => 1 } ) } %> -<%= render :partial => '/layouts/graph', :locals => { :drawMe => false, :includeDiv => false, :methodName=> 'draw_load_history_7_graph', :div_id => 'load_history_7', :chartType => 'line', :yGridLines => 'lightgrey', :xGridLines => 'lightgrey', :ticksX => 39, :scaleX => 272, :ticksY => 2, :scaleY => 23, :url => (url_for :escape => false, :controller => 'graph', :action => 'history_graph_data', :id => @id, :params => { :target => 'load', :poolType => @poolType, :days => 7 } ) } %> -<%= render :partial => '/layouts/graph', :locals => { :drawMe => false, :includeDiv => false, :methodName=> 'draw_load_history_30_graph', :div_id => 'load_history_30', :chartType => 'line', :yGridLines => 'lightgrey', :xGridLines => 'lightgrey', :ticksX => 120, :scaleX => 1200, :ticksY => 2, :scaleY => 23, :url => (url_for :escape => false, :controller => 'graph', :action => 'history_graph_data', :id => @id, :params => { :target => 'load', :poolType => @poolType, :days => 30 } ) } %> - -<div id="history_graphs"> - <div id="history_graphs_control"> - <div class="history_graphs_menu"> - <ul> - <li><div id="history_graph_selection" class="history_graph_menu_header">Overall Load <%= image_tag 'icon_menu_arrow.gif' %></div></li> - <li class="history_graph_menu_item history_graph_menu_fitem"><a href="#" onclick="swap_history_graph_target('Overall Load', 'load_history')" >Overall Load</a></li> - <li class="history_graph_menu_item"><a href="#" onclick="swap_history_graph_target('CPU History', 'cpu_history');" >CPU History</a></li> - <li class="history_graph_menu_item history_graph_menu_litem"><a href="#" onclick="swap_history_graph_target('Memory History', 'memory_history');">Memory History</a></li> - </ul> - </div> - <div class="history_graphs_menu"> - <ul> - <li><div id="history_graph_time_selection" class="history_graph_menu_header">Last 7 Days <%= image_tag 'icon_menu_arrow.gif' %></div></li> - <li class="history_graph_menu_item history_graph_menu_fitem"<a href="#" onclick="swap_history_graph_time('Last 24 Hours', '1')" >Last 24 Hours</a></li> - <li class="history_graph_menu_item"><a href="#" onclick="swap_history_graph_time('Last 7 days', '7')" >Last 7 Days</a></li> - <li class="history_graph_menu_item history_graph_menu_litem"><a href="#" onclick="swap_history_graph_time('Last 30 Days', '30')" >Last 30 Days</a></li> - </ul> - </div> - <div class="history_graphs_legend"> - <font color="<%= @peak_history[:color] %>">Peak </font> - <font color="<%= @avg_history[:color] %>">Average </font> - <font color="<%= @roll_peak_history[:color] %>">Rolling Peak </font> - <font color="<%= @roll_avg_history[:color] %>">Rolling Average </font> - </div> - </div> - <div id="history_graphs_graphs"> - <div class="history_graph"><div id="cpu_history_1"> </div></div> - <div class="history_graph"><div id="cpu_history_7"> </div></div> - <div class="history_graph"><div id="cpu_history_30"> </div></div> - <div class="history_graph"><div id="memory_history_1"> </div></div> - <div class="history_graph"><div id="memory_history_7"> </div></div> - <div class="history_graph"><div id="memory_history_30"> </div></div> - <div class="history_graph"><div id="load_history_1"> </div></div> - <div class="history_graph"><div id="load_history_7"> </div></div> - <div class="history_graph"><div id="load_history_30"> </div></div> - </div> -</div> - -<script type="text/javascript"> - swap_history_graph(null, null); // display 1st graph +$(document).ready(function(){ +$('#the-div-name').flash( + { + src: '/ovirt/flexchart.swf', + width: 720, + height: 300, + flashvars: { flexchart_data: '/ovirt/graph/flexchart_data/<%= @id %>/memory/1' } + }, + { version: 9 } + ); +}); </script> diff --git a/src/config/routes.rb b/src/config/routes.rb index 6f8e481..8d538cb 100644 --- a/src/config/routes.rb +++ b/src/config/routes.rb @@ -41,6 +41,7 @@ ActionController::Routing::Routes.draw do |map| map.connect ':controller/service.wsdl', :action => 'wsdl' # Install the default route as the lowest priority. + map.connect 'graph/flexchart_data/:id/:target/:days', :controller => 'graph', :action => 'flexchart_data' map.connect ':controller/:action/:id.:format' map.connect ':controller/:action/:id' diff --git a/src/flexchart/README.txt b/src/flexchart/README.txt new file mode 100644 index 0000000..7402112 --- /dev/null +++ b/src/flexchart/README.txt @@ -0,0 +1,8 @@ +Until mxmlc gets packaged and this becomes part of autobuild, +you must obtain the open flex SDK to build the swf movie. + +Once you have mxmlc on your system, run: + +mxmlc flexchart.mxml + +in this directory, and copy the resulting file flexchart.swf to /usr/share/ovirt-server/public on your appliance. diff --git a/src/flexchart/com/adobe/serialization/json/JSON.as b/src/flexchart/com/adobe/serialization/json/JSON.as new file mode 100644 index 0000000..bfee6d9 --- /dev/null +++ b/src/flexchart/com/adobe/serialization/json/JSON.as @@ -0,0 +1,85 @@ +/* + Copyright (c) 2008, Adobe Systems Incorporated + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the name of Adobe Systems Incorporated nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +package com.adobe.serialization.json { + + /** + * This class provides encoding and decoding of the JSON format. + * + * Example usage: + * <code> + * // create a JSON string from an internal object + * JSON.encode( myObject ); + * + * // read a JSON string into an internal object + * var myObject:Object = JSON.decode( jsonString ); + * </code> + */ + public class JSON { + + + /** + * Encodes a object into a JSON string. + * + * @param o The object to create a JSON string for + * @return the JSON string representing o + * @langversion ActionScript 3.0 + * @playerversion Flash 9.0 + * @tiptext + */ + public static function encode( o:Object ):String { + + var encoder:JSONEncoder = new JSONEncoder( o ); + return encoder.getString(); + + } + + /** + * Decodes a JSON string into a native object. + * + * @param s The JSON string representing the object + * @return A native object as specified by s + * @throw JSONParseError + * @langversion ActionScript 3.0 + * @playerversion Flash 9.0 + * @tiptext + */ + public static function decode( s:String ):* { + + var decoder:JSONDecoder = new JSONDecoder( s ) + return decoder.getValue(); + + } + + } + +} \ No newline at end of file diff --git a/src/flexchart/com/adobe/serialization/json/JSONDecoder.as b/src/flexchart/com/adobe/serialization/json/JSONDecoder.as new file mode 100644 index 0000000..82ade19 --- /dev/null +++ b/src/flexchart/com/adobe/serialization/json/JSONDecoder.as @@ -0,0 +1,221 @@ +/* + Copyright (c) 2008, Adobe Systems Incorporated + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the name of Adobe Systems Incorporated nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +package com.adobe.serialization.json { + + public class JSONDecoder { + + /** The value that will get parsed from the JSON string */ + private var value:*; + + /** The tokenizer designated to read the JSON string */ + private var tokenizer:JSONTokenizer; + + /** The current token from the tokenizer */ + private var token:JSONToken; + + /** + * Constructs a new JSONDecoder to parse a JSON string + * into a native object. + * + * @param s The JSON string to be converted + * into a native object + * @langversion ActionScript 3.0 + * @playerversion Flash 9.0 + * @tiptext + */ + public function JSONDecoder( s:String ) { + + tokenizer = new JSONTokenizer( s ); + + nextToken(); + value = parseValue(); + } + + /** + * Gets the internal object that was created by parsing + * the JSON string passed to the constructor. + * + * @return The internal object representation of the JSON + * string that was passed to the constructor + * @langversion ActionScript 3.0 + * @playerversion Flash 9.0 + * @tiptext + */ + public function getValue():* { + return value; + } + + /** + * Returns the next token from the tokenzier reading + * the JSON string + */ + private function nextToken():JSONToken { + return token = tokenizer.getNextToken(); + } + + /** + * Attempt to parse an array + */ + private function parseArray():Array { + // create an array internally that we're going to attempt + // to parse from the tokenizer + var a:Array = new Array(); + + // grab the next token from the tokenizer to move + // past the opening [ + nextToken(); + + // check to see if we have an empty array + if ( token.type == JSONTokenType.RIGHT_BRACKET ) { + // we're done reading the array, so return it + return a; + } + + // deal with elements of the array, and use an "infinite" + // loop because we could have any amount of elements + while ( true ) { + // read in the value and add it to the array + a.push ( parseValue() ); + + // after the value there should be a ] or a , + nextToken(); + + if ( token.type == JSONTokenType.RIGHT_BRACKET ) { + // we're done reading the array, so return it + return a; + } else if ( token.type == JSONTokenType.COMMA ) { + // move past the comma and read another value + nextToken(); + } else { + tokenizer.parseError( "Expecting ] or , but found " + token.value ); + } + } + return null; + } + + /** + * Attempt to parse an object + */ + private function parseObject():Object { + // create the object internally that we're going to + // attempt to parse from the tokenizer + var o:Object = new Object(); + + // store the string part of an object member so + // that we can assign it a value in the object + var key:String + + // grab the next token from the tokenizer + nextToken(); + + // check to see if we have an empty object + if ( token.type == JSONTokenType.RIGHT_BRACE ) { + // we're done reading the object, so return it + return o; + } + + // deal with members of the object, and use an "infinite" + // loop because we could have any amount of members + while ( true ) { + + if ( token.type == JSONTokenType.STRING ) { + // the string value we read is the key for the object + key = String( token.value ); + + // move past the string to see what's next + nextToken(); + + // after the string there should be a : + if ( token.type == JSONTokenType.COLON ) { + + // move past the : and read/assign a value for the key + nextToken(); + o[key] = parseValue(); + + // move past the value to see what's next + nextToken(); + + // after the value there's either a } or a , + if ( token.type == JSONTokenType.RIGHT_BRACE ) { + // // we're done reading the object, so return it + return o; + + } else if ( token.type == JSONTokenType.COMMA ) { + // skip past the comma and read another member + nextToken(); + } else { + tokenizer.parseError( "Expecting } or , but found " + token.value ); + } + } else { + tokenizer.parseError( "Expecting : but found " + token.value ); + } + } else { + tokenizer.parseError( "Expecting string but found " + token.value ); + } + } + return null; + } + + /** + * Attempt to parse a value + */ + private function parseValue():Object + { + // Catch errors when the input stream ends abruptly + if ( token == null ) + { + tokenizer.parseError( "Unexpected end of input" ); + } + + switch ( token.type ) { + case JSONTokenType.LEFT_BRACE: + return parseObject(); + + case JSONTokenType.LEFT_BRACKET: + return parseArray(); + + case JSONTokenType.STRING: + case JSONTokenType.NUMBER: + case JSONTokenType.TRUE: + case JSONTokenType.FALSE: + case JSONTokenType.NULL: + return token.value; + + default: + tokenizer.parseError( "Unexpected " + token.value ); + + } + return null; + } + } +} diff --git a/src/flexchart/com/adobe/serialization/json/JSONEncoder.as b/src/flexchart/com/adobe/serialization/json/JSONEncoder.as new file mode 100644 index 0000000..44469d0 --- /dev/null +++ b/src/flexchart/com/adobe/serialization/json/JSONEncoder.as @@ -0,0 +1,299 @@ +/* + Copyright (c) 2008, Adobe Systems Incorporated + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the name of Adobe Systems Incorporated nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +package com.adobe.serialization.json +{ + + import flash.utils.describeType; + + public class JSONEncoder { + + /** The string that is going to represent the object we're encoding */ + private var jsonString:String; + + /** + * Creates a new JSONEncoder. + * + * @param o The object to encode as a JSON string + * @langversion ActionScript 3.0 + * @playerversion Flash 9.0 + * @tiptext + */ + public function JSONEncoder( value:* ) { + jsonString = convertToString( value ); + + } + + /** + * Gets the JSON string from the encoder. + * + * @return The JSON string representation of the object + * that was passed to the constructor + * @langversion ActionScript 3.0 + * @playerversion Flash 9.0 + * @tiptext + */ + public function getString():String { + return jsonString; + } + + /** + * Converts a value to it's JSON string equivalent. + * + * @param value The value to convert. Could be any + * type (object, number, array, etc) + */ + private function convertToString( value:* ):String { + + // determine what value is and convert it based on it's type + if ( value is String ) { + + // escape the string so it's formatted correctly + return escapeString( value as String ); + + } else if ( value is Number ) { + + // only encode numbers that finate + return isFinite( value as Number) ? value.toString() : "null"; + + } else if ( value is Boolean ) { + + // convert boolean to string easily + return value ? "true" : "false"; + + } else if ( value is Array ) { + + // call the helper method to convert an array + return arrayToString( value as Array ); + + } else if ( value is Object && value != null ) { + + // call the helper method to convert an object + return objectToString( value ); + } + return "null"; + } + + /** + * Escapes a string accoding to the JSON specification. + * + * @param str The string to be escaped + * @return The string with escaped special characters + * according to the JSON specification + */ + private function escapeString( str:String ):String { + // create a string to store the string's jsonstring value + var s:String = ""; + // current character in the string we're processing + var ch:String; + // store the length in a local variable to reduce lookups + var len:Number = str.length; + + // loop over all of the characters in the string + for ( var i:int = 0; i < len; i++ ) { + + // examine the character to determine if we have to escape it + ch = str.charAt( i ); + switch ( ch ) { + + case '"': // quotation mark + s += "\\\""; + break; + + //case '/': // solidus + // s += "\\/"; + // break; + + case '\\': // reverse solidus + s += "\\\\"; + break; + + case '\b': // bell + s += "\\b"; + break; + + case '\f': // form feed + s += "\\f"; + break; + + case '\n': // newline + s += "\\n"; + break; + + case '\r': // carriage return + s += "\\r"; + break; + + case '\t': // horizontal tab + s += "\\t"; + break; + + default: // everything else + + // check for a control character and escape as unicode + if ( ch < ' ' ) { + // get the hex digit(s) of the character (either 1 or 2 digits) + var hexCode:String = ch.charCodeAt( 0 ).toString( 16 ); + + // ensure that there are 4 digits by adjusting + // the # of zeros accordingly. + var zeroPad:String = hexCode.length == 2 ? "00" : "000"; + + // create the unicode escape sequence with 4 hex digits + s += "\\u" + zeroPad + hexCode; + } else { + + // no need to do any special encoding, just pass-through + s += ch; + + } + } // end switch + + } // end for loop + + return "\"" + s + "\""; + } + + /** + * Converts an array to it's JSON string equivalent + * + * @param a The array to convert + * @return The JSON string representation of <code>a</code> + */ + private function arrayToString( a:Array ):String { + // create a string to store the array's jsonstring value + var s:String = ""; + + // loop over the elements in the array and add their converted + // values to the string + for ( var i:int = 0; i < a.length; i++ ) { + // when the length is 0 we're adding the first element so + // no comma is necessary + if ( s.length > 0 ) { + // we've already added an element, so add the comma separator + s += "," + } + + // convert the value to a string + s += convertToString( a[i] ); + } + + // KNOWN ISSUE: In ActionScript, Arrays can also be associative + // objects and you can put anything in them, ie: + // myArray["foo"] = "bar"; + // + // These properties aren't picked up in the for loop above because + // the properties don't correspond to indexes. However, we're + // sort of out luck because the JSON specification doesn't allow + // these types of array properties. + // + // So, if the array was also used as an associative object, there + // may be some values in the array that don't get properly encoded. + // + // A possible solution is to instead encode the Array as an Object + // but then it won't get decoded correctly (and won't be an + // Array instance) + + // close the array and return it's string value + return "[" + s + "]"; + } + + /** + * Converts an object to it's JSON string equivalent + * + * @param o The object to convert + * @return The JSON string representation of <code>o</code> + */ + private function objectToString( o:Object ):String + { + // create a string to store the object's jsonstring value + var s:String = ""; + + // determine if o is a class instance or a plain object + var classInfo:XML = describeType( o ); + if ( classInfo. at name.toString() == "Object" ) + { + // the value of o[key] in the loop below - store this + // as a variable so we don't have to keep looking up o[key] + // when testing for valid values to convert + var value:Object; + + // loop over the keys in the object and add their converted + // values to the string + for ( var key:String in o ) + { + // assign value to a variable for quick lookup + value = o[key]; + + // don't add function's to the JSON string + if ( value is Function ) + { + // skip this key and try another + continue; + } + + // when the length is 0 we're adding the first item so + // no comma is necessary + if ( s.length > 0 ) { + // we've already added an item, so add the comma separator + s += "," + } + + s += escapeString( key ) + ":" + convertToString( value ); + } + } + else // o is a class instance + { + // Loop over all of the variables and accessors in the class and + // serialize them along with their values. + for each ( var v:XML in classInfo..*.( name() == "variable" || name() == "accessor" ) ) + { + // When the length is 0 we're adding the first item so + // no comma is necessary + if ( s.length > 0 ) { + // We've already added an item, so add the comma separator + s += "," + } + + s += escapeString( v. at name.toString() ) + ":" + + convertToString( o[ v. at name ] ); + } + + } + + return "{" + s + "}"; + } + + + } + +} diff --git a/src/flexchart/com/adobe/serialization/json/JSONParseError.as b/src/flexchart/com/adobe/serialization/json/JSONParseError.as new file mode 100644 index 0000000..5aec1e3 --- /dev/null +++ b/src/flexchart/com/adobe/serialization/json/JSONParseError.as @@ -0,0 +1,87 @@ +/* + Copyright (c) 2008, Adobe Systems Incorporated + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the name of Adobe Systems Incorporated nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +package com.adobe.serialization.json { + + /** + * + * + */ + public class JSONParseError extends Error { + + /** The location in the string where the error occurred */ + private var _location:int; + + /** The string in which the parse error occurred */ + private var _text:String; + + /** + * Constructs a new JSONParseError. + * + * @param message The error message that occured during parsing + * @langversion ActionScript 3.0 + * @playerversion Flash 9.0 + * @tiptext + */ + public function JSONParseError( message:String = "", location:int = 0, text:String = "") { + super( message ); + name = "JSONParseError"; + _location = location; + _text = text; + } + + /** + * Provides read-only access to the location variable. + * + * @return The location in the string where the error occurred + * @langversion ActionScript 3.0 + * @playerversion Flash 9.0 + * @tiptext + */ + public function get location():int { + return _location; + } + + /** + * Provides read-only access to the text variable. + * + * @return The string in which the error occurred + * @langversion ActionScript 3.0 + * @playerversion Flash 9.0 + * @tiptext + */ + public function get text():String { + return _text; + } + } + +} \ No newline at end of file diff --git a/src/flexchart/com/adobe/serialization/json/JSONToken.as b/src/flexchart/com/adobe/serialization/json/JSONToken.as new file mode 100644 index 0000000..258d63c --- /dev/null +++ b/src/flexchart/com/adobe/serialization/json/JSONToken.as @@ -0,0 +1,104 @@ +/* + Copyright (c) 2008, Adobe Systems Incorporated + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the name of Adobe Systems Incorporated nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +package com.adobe.serialization.json { + + public class JSONToken { + + private var _type:int; + private var _value:Object; + + /** + * Creates a new JSONToken with a specific token type and value. + * + * @param type The JSONTokenType of the token + * @param value The value of the token + * @langversion ActionScript 3.0 + * @playerversion Flash 9.0 + * @tiptext + */ + public function JSONToken( type:int = -1 /* JSONTokenType.UNKNOWN */, value:Object = null ) { + _type = type; + _value = value; + } + + /** + * Returns the type of the token. + * + * @see com.adobe.serialization.json.JSONTokenType + * @langversion ActionScript 3.0 + * @playerversion Flash 9.0 + * @tiptext + */ + public function get type():int { + return _type; + } + + /** + * Sets the type of the token. + * + * @see com.adobe.serialization.json.JSONTokenType + * @langversion ActionScript 3.0 + * @playerversion Flash 9.0 + * @tiptext + */ + public function set type( value:int ):void { + _type = value; + } + + /** + * Gets the value of the token + * + * @see com.adobe.serialization.json.JSONTokenType + * @langversion ActionScript 3.0 + * @playerversion Flash 9.0 + * @tiptext + */ + public function get value():Object { + return _value; + } + + /** + * Sets the value of the token + * + * @see com.adobe.serialization.json.JSONTokenType + * @langversion ActionScript 3.0 + * @playerversion Flash 9.0 + * @tiptext + */ + public function set value ( v:Object ):void { + _value = v; + } + + } + +} \ No newline at end of file diff --git a/src/flexchart/com/adobe/serialization/json/JSONTokenType.as b/src/flexchart/com/adobe/serialization/json/JSONTokenType.as new file mode 100644 index 0000000..fceb3f0 --- /dev/null +++ b/src/flexchart/com/adobe/serialization/json/JSONTokenType.as @@ -0,0 +1,67 @@ +/* + Copyright (c) 2008, Adobe Systems Incorporated + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the name of Adobe Systems Incorporated nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +package com.adobe.serialization.json { + + /** + * Class containing constant values for the different types + * of tokens in a JSON encoded string. + */ + public class JSONTokenType { + + public static const UNKNOWN:int = -1; + + public static const COMMA:int = 0; + + public static const LEFT_BRACE:int = 1; + + public static const RIGHT_BRACE:int = 2; + + public static const LEFT_BRACKET:int = 3; + + public static const RIGHT_BRACKET:int = 4; + + public static const COLON:int = 6; + + public static const TRUE:int = 7; + + public static const FALSE:int = 8; + + public static const NULL:int = 9; + + public static const STRING:int = 10; + + public static const NUMBER:int = 11; + + } + +} \ No newline at end of file diff --git a/src/flexchart/com/adobe/serialization/json/JSONTokenizer.as b/src/flexchart/com/adobe/serialization/json/JSONTokenizer.as new file mode 100644 index 0000000..f5d406a --- /dev/null +++ b/src/flexchart/com/adobe/serialization/json/JSONTokenizer.as @@ -0,0 +1,547 @@ +/* + Copyright (c) 2008, Adobe Systems Incorporated + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the name of Adobe Systems Incorporated nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +package com.adobe.serialization.json { + + public class JSONTokenizer { + + /** The object that will get parsed from the JSON string */ + private var obj:Object; + + /** The JSON string to be parsed */ + private var jsonString:String; + + /** The current parsing location in the JSON string */ + private var loc:int; + + /** The current character in the JSON string during parsing */ + private var ch:String; + + /** + * Constructs a new JSONDecoder to parse a JSON string + * into a native object. + * + * @param s The JSON string to be converted + * into a native object + */ + public function JSONTokenizer( s:String ) { + jsonString = s; + loc = 0; + + // prime the pump by getting the first character + nextChar(); + } + + /** + * Gets the next token in the input sting and advances + * the character to the next character after the token + */ + public function getNextToken():JSONToken { + var token:JSONToken = new JSONToken(); + + // skip any whitespace / comments since the last + // token was read + skipIgnored(); + + // examine the new character and see what we have... + switch ( ch ) { + + case '{': + token.type = JSONTokenType.LEFT_BRACE; + token.value = '{'; + nextChar(); + break + + case '}': + token.type = JSONTokenType.RIGHT_BRACE; + token.value = '}'; + nextChar(); + break + + case '[': + token.type = JSONTokenType.LEFT_BRACKET; + token.value = '['; + nextChar(); + break + + case ']': + token.type = JSONTokenType.RIGHT_BRACKET; + token.value = ']'; + nextChar(); + break + + case ',': + token.type = JSONTokenType.COMMA; + token.value = ','; + nextChar(); + break + + case ':': + token.type = JSONTokenType.COLON; + token.value = ':'; + nextChar(); + break; + + case 't': // attempt to read true + var possibleTrue:String = "t" + nextChar() + nextChar() + nextChar(); + + if ( possibleTrue == "true" ) { + token.type = JSONTokenType.TRUE; + token.value = true; + nextChar(); + } else { + parseError( "Expecting 'true' but found " + possibleTrue ); + } + + break; + + case 'f': // attempt to read false + var possibleFalse:String = "f" + nextChar() + nextChar() + nextChar() + nextChar(); + + if ( possibleFalse == "false" ) { + token.type = JSONTokenType.FALSE; + token.value = false; + nextChar(); + } else { + parseError( "Expecting 'false' but found " + possibleFalse ); + } + + break; + + case 'n': // attempt to read null + + var possibleNull:String = "n" + nextChar() + nextChar() + nextChar(); + + if ( possibleNull == "null" ) { + token.type = JSONTokenType.NULL; + token.value = null; + nextChar(); + } else { + parseError( "Expecting 'null' but found " + possibleNull ); + } + + break; + + case '"': // the start of a string + token = readString(); + break; + + default: + // see if we can read a number + if ( isDigit( ch ) || ch == '-' ) { + token = readNumber(); + } else if ( ch == '' ) { + // check for reading past the end of the string + return null; + } else { + // not sure what was in the input string - it's not + // anything we expected + parseError( "Unexpected " + ch + " encountered" ); + } + } + + return token; + } + + /** + * Attempts to read a string from the input string. Places + * the character location at the first character after the + * string. It is assumed that ch is " before this method is called. + * + * @return the JSONToken with the string value if a string could + * be read. Throws an error otherwise. + */ + private function readString():JSONToken { + // the token for the string we'll try to read + var token:JSONToken = new JSONToken(); + token.type = JSONTokenType.STRING; + + // the string to store the string we'll try to read + var string:String = ""; + + // advance past the first " + nextChar(); + + while ( ch != '"' && ch != '' ) { + + // unescape the escape sequences in the string + if ( ch == '\\' ) { + + // get the next character so we know what + // to unescape + nextChar(); + + switch ( ch ) { + + case '"': // quotation mark + string += '"'; + break; + + case '/': // solidus + string += "/"; + break; + + case '\\': // reverse solidus + string += '\\'; + break; + + case 'b': // bell + string += '\b'; + break; + + case 'f': // form feed + string += '\f'; + break; + + case 'n': // newline + string += '\n'; + break; + + case 'r': // carriage return + string += '\r'; + break; + + case 't': // horizontal tab + string += '\t' + break; + + case 'u': + // convert a unicode escape sequence + // to it's character value - expecting + // 4 hex digits + + // save the characters as a string we'll convert to an int + var hexValue:String = ""; + + // try to find 4 hex characters + for ( var i:int = 0; i < 4; i++ ) { + // get the next character and determine + // if it's a valid hex digit or not + if ( !isHexDigit( nextChar() ) ) { + parseError( " Excepted a hex digit, but found: " + ch ); + } + // valid, add it to the value + hexValue += ch; + } + + // convert hexValue to an integer, and use that + // integrer value to create a character to add + // to our string. + string += String.fromCharCode( parseInt( hexValue, 16 ) ); + + break; + + default: + // couldn't unescape the sequence, so just + // pass it through + string += '\\' + ch; + + } + + } else { + // didn't have to unescape, so add the character to the string + string += ch; + + } + + // move to the next character + nextChar(); + + } + + // we read past the end of the string without closing it, which + // is a parse error + if ( ch == '' ) { + parseError( "Unterminated string literal" ); + } + + // move past the closing " in the input string + nextChar(); + + // attach to the string to the token so we can return it + token.value = string; + + return token; + } + + /** + * Attempts to read a number from the input string. Places + * the character location at the first character after the + * number. + * + * @return The JSONToken with the number value if a number could + * be read. Throws an error otherwise. + */ + private function readNumber():JSONToken { + // the token for the number we'll try to read + var token:JSONToken = new JSONToken(); + token.type = JSONTokenType.NUMBER; + + // the string to accumulate the number characters + // into that we'll convert to a number at the end + var input:String = ""; + + // check for a negative number + if ( ch == '-' ) { + input += '-'; + nextChar(); + } + + // the number must start with a digit + if ( !isDigit( ch ) ) + { + parseError( "Expecting a digit" ); + } + + // 0 can only be the first digit if it + // is followed by a decimal point + if ( ch == '0' ) + { + input += ch; + nextChar(); + + // make sure no other digits come after 0 + if ( isDigit( ch ) ) + { + parseError( "A digit cannot immediately follow 0" ); + } + } + else + { + // read numbers while we can + while ( isDigit( ch ) ) { + input += ch; + nextChar(); + } + } + + // check for a decimal value + if ( ch == '.' ) { + input += '.'; + nextChar(); + + // after the decimal there has to be a digit + if ( !isDigit( ch ) ) + { + parseError( "Expecting a digit" ); + } + + // read more numbers to get the decimal value + while ( isDigit( ch ) ) { + input += ch; + nextChar(); + } + } + + // check for scientific notation + if ( ch == 'e' || ch == 'E' ) + { + input += "e" + nextChar(); + // check for sign + if ( ch == '+' || ch == '-' ) + { + input += ch; + nextChar(); + } + + // require at least one number for the exponent + // in this case + if ( !isDigit( ch ) ) + { + parseError( "Scientific notation number needs exponent value" ); + } + + // read in the exponent + while ( isDigit( ch ) ) + { + input += ch; + nextChar(); + } + } + + // convert the string to a number value + var num:Number = Number( input ); + + if ( isFinite( num ) && !isNaN( num ) ) { + token.value = num; + return token; + } else { + parseError( "Number " + num + " is not valid!" ); + } + return null; + } + + /** + * Reads the next character in the input + * string and advances the character location. + * + * @return The next character in the input string, or + * null if we've read past the end. + */ + private function nextChar():String { + return ch = jsonString.charAt( loc++ ); + } + + /** + * Advances the character location past any + * sort of white space and comments + */ + private function skipIgnored():void { + skipWhite(); + skipComments(); + skipWhite(); + } + + /** + * Skips comments in the input string, either + * single-line or multi-line. Advances the character + * to the first position after the end of the comment. + */ + private function skipComments():void { + if ( ch == '/' ) { + // Advance past the first / to find out what type of comment + nextChar(); + switch ( ch ) { + case '/': // single-line comment, read through end of line + + // Loop over the characters until we find + // a newline or until there's no more characters left + do { + nextChar(); + } while ( ch != '\n' && ch != '' ) + + // move past the \n + nextChar(); + + break; + + case '*': // multi-line comment, read until closing */ + + // move past the opening * + nextChar(); + + // try to find a trailing */ + while ( true ) { + if ( ch == '*' ) { + // check to see if we have a closing / + nextChar(); + if ( ch == '/') { + // move past the end of the closing */ + nextChar(); + break; + } + } else { + // move along, looking if the next character is a * + nextChar(); + } + + // when we're here we've read past the end of + // the string without finding a closing */, so error + if ( ch == '' ) { + parseError( "Multi-line comment not closed" ); + } + } + + break; + + // Can't match a comment after a /, so it's a parsing error + default: + parseError( "Unexpected " + ch + " encountered (expecting '/' or '*' )" ); + } + } + + } + + + /** + * Skip any whitespace in the input string and advances + * the character to the first character after any possible + * whitespace. + */ + private function skipWhite():void { + + // As long as there are spaces in the input + // stream, advance the current location pointer + // past them + while ( isWhiteSpace( ch ) ) { + nextChar(); + } + + } + + /** + * Determines if a character is whitespace or not. + * + * @return True if the character passed in is a whitespace + * character + */ + private function isWhiteSpace( ch:String ):Boolean { + return ( ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r' ); + } + + /** + * Determines if a character is a digit [0-9]. + * + * @return True if the character passed in is a digit + */ + private function isDigit( ch:String ):Boolean { + return ( ch >= '0' && ch <= '9' ); + } + + /** + * Determines if a character is a digit [0-9]. + * + * @return True if the character passed in is a digit + */ + private function isHexDigit( ch:String ):Boolean { + // get the uppercase value of ch so we only have + // to compare the value between 'A' and 'F' + var uc:String = ch.toUpperCase(); + + // a hex digit is a digit of A-F, inclusive ( using + // our uppercase constraint ) + return ( isDigit( ch ) || ( uc >= 'A' && uc <= 'F' ) ); + } + + /** + * Raises a parsing error with a specified message, tacking + * on the error location and the original string. + * + * @param message The message indicating why the error occurred + */ + public function parseError( message:String ):void { + throw new JSONParseError( message, loc, jsonString ); + } + } + +} diff --git a/src/flexchart/flexchart.mxml b/src/flexchart/flexchart.mxml new file mode 100644 index 0000000..796329d --- /dev/null +++ b/src/flexchart/flexchart.mxml @@ -0,0 +1,20 @@ +<?xml version="1.0"?> +<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" applicationComplete="populate(flexChart)"> + <mx:Script> + <![CDATA[ + + import mx.containers.Box; + import org.ovirt.*; + + private function populate(chart:Box):void { + var chartLoader:ChartLoader = new ChartLoader(chart, parameters['flexchart_data']); + chartLoader.load(); + } + + ]]> + </mx:Script> + <mx:Panel height="100%" width="100%" visible="true"> + <mx:HBox id="flexChart" height="100%" width="100%" visible="true" verticalAlign="bottom" opaqueBackground="0xFFFFFF" borderThickness="0"> + </mx:HBox> + </mx:Panel> +</mx:Application> diff --git a/src/flexchart/org/ovirt/ChartLoader.as b/src/flexchart/org/ovirt/ChartLoader.as new file mode 100644 index 0000000..d76bf28 --- /dev/null +++ b/src/flexchart/org/ovirt/ChartLoader.as @@ -0,0 +1,44 @@ +package org.ovirt { + + import mx.containers.Box; + import mx.containers.HBox; + import mx.controls.Text; + + public class ChartLoader { + + private var element:Box; + private var datasourceUrl:String; + + public function ChartLoader(element:Box, datasourceUrl:String) { + this.element = element; + this.datasourceUrl = datasourceUrl; + } + + public function addData(dataSeries:DataSeries):void { + var points:Array = dataSeries.getPoints(); + var maxValue:Number = dataSeries.getMaxValue(); + var scale:Number = maxValue; + if (scale == 0) { scale = 1; } + var size:int = points.length; + element.removeAllChildren(); + element.setStyle("horizontalGap","2"); + for (var i:int = 0; i < size; i++) { + var value:Number = (points[i] as Array)[1]; + var bar:HBox = new HBox(); + bar.percentHeight = ((value / scale) * 90); + bar.percentWidth = (100 / size); + bar.setStyle("backgroundColor","0x0000FF"); + bar.setStyle("left","1"); + bar.setStyle("right","1"); + bar.visible = true; + bar.setVisible(true); + element.addChild(bar); + } + } + + public function load():void { + var dataSource:DataSource = new DataSource(this); + dataSource.retrieveData(datasourceUrl); + } + } +} \ No newline at end of file diff --git a/src/flexchart/org/ovirt/DataSeries.as b/src/flexchart/org/ovirt/DataSeries.as new file mode 100644 index 0000000..c47da97 --- /dev/null +++ b/src/flexchart/org/ovirt/DataSeries.as @@ -0,0 +1,22 @@ +//class to encapsulate the json object representation of a data +//series returned from stats package + +package org.ovirt { + + public class DataSeries { + + private var object:Object; + + public function DataSeries (object:Object) { + this.object = object; + } + + public function getPoints():Array { + return object["vectors"] as Array; + } + + public function getMaxValue():Number { + return object["max_value"] as Number; + } + } +} \ No newline at end of file diff --git a/src/flexchart/org/ovirt/DataSource.as b/src/flexchart/org/ovirt/DataSource.as new file mode 100644 index 0000000..e10cf93 --- /dev/null +++ b/src/flexchart/org/ovirt/DataSource.as @@ -0,0 +1,37 @@ +package org.ovirt { + + import flash.net.URLLoader; + import flash.net.URLRequest; + import com.adobe.serialization.json.JSON; + import flash.events.Event; + import flash.events.IOErrorEvent; + + public class DataSource { + + private var chartLoader:ChartLoader; + + public function DataSource(chartLoader:ChartLoader) { + this.chartLoader = chartLoader; + } + + public function retrieveData(url:String):void { + var loader:URLLoader = new URLLoader(); + loader.addEventListener( IOErrorEvent.IO_ERROR, this.ioError ); + loader.addEventListener( Event.COMPLETE, dataLoaded ); + var request:URLRequest = new URLRequest(url); + loader.load(request); + } + + private function dataLoaded(event:Event):void { + var loader:URLLoader = URLLoader(event.target); + var object:Object = JSON.decode(loader.data); + var series:DataSeries = new DataSeries(object); + chartLoader.addData(series); + } + + private function ioError( e:IOErrorEvent ):void { + //FIXME: + //do something useful with this error + } + } +} diff --git a/src/public/javascripts/jquery.flash.js b/src/public/javascripts/jquery.flash.js new file mode 100644 index 0000000..e47cc2e --- /dev/null +++ b/src/public/javascripts/jquery.flash.js @@ -0,0 +1,288 @@ +/** + * Flash (http://jquery.lukelutman.com/plugins/flash) + * A jQuery plugin for embedding Flash movies. + * + * Version 1.0 + * November 9th, 2006 + * + * Copyright (c) 2006 Luke Lutman (http://www.lukelutman.com) + * Dual licensed under the MIT and GPL licenses. + * http://www.opensource.org/licenses/mit-license.php + * http://www.opensource.org/licenses/gpl-license.php + * + * Inspired by: + * SWFObject (http://blog.deconcept.com/swfobject/) + * UFO (http://www.bobbyvandersluis.com/ufo/) + * sIFR (http://www.mikeindustries.com/sifr/) + * + * IMPORTANT: + * The packed version of jQuery breaks ActiveX control + * activation in Internet Explorer. Use JSMin to minifiy + * jQuery (see: http://jquery.lukelutman.com/plugins/flash#activex). + * + **/ +;(function(){ + +var $$; + +/** + * + * @desc Replace matching elements with a flash movie. + * @author Luke Lutman + * @version 1.0.1 + * + * @name flash + * @param Hash htmlOptions Options for the embed/object tag. + * @param Hash pluginOptions Options for detecting/updating the Flash plugin (optional). + * @param Function replace Custom block called for each matched element if flash is installed (optional). + * @param Function update Custom block called for each matched if flash isn't installed (optional). + * @type jQuery + * + * @cat plugins/flash + * + * @example $('#hello').flash({ src: 'hello.swf' }); + * @desc Embed a Flash movie. + * + * @example $('#hello').flash({ src: 'hello.swf' }, { version: 8 }); + * @desc Embed a Flash 8 movie. + * + * @example $('#hello').flash({ src: 'hello.swf' }, { expressInstall: true }); + * @desc Embed a Flash movie using Express Install if flash isn't installed. + * + * @example $('#hello').flash({ src: 'hello.swf' }, { update: false }); + * @desc Embed a Flash movie, don't show an update message if Flash isn't installed. + * +**/ +$$ = jQuery.fn.flash = function(htmlOptions, pluginOptions, replace, update) { + + // Set the default block. + var block = replace || $$.replace; + + // Merge the default and passed plugin options. + pluginOptions = $$.copy($$.pluginOptions, pluginOptions); + + // Detect Flash. + if(!$$.hasFlash(pluginOptions.version)) { + // Use Express Install (if specified and Flash plugin 6,0,65 or higher is installed). + if(pluginOptions.expressInstall && $$.hasFlash(6,0,65)) { + // Add the necessary flashvars (merged later). + var expressInstallOptions = { + flashvars: { + MMredirectURL: location, + MMplayerType: 'PlugIn', + MMdoctitle: jQuery('title').text() + } + }; + // Ask the user to update (if specified). + } else if (pluginOptions.update) { + // Change the block to insert the update message instead of the flash movie. + block = update || $$.update; + // Fail + } else { + // The required version of flash isn't installed. + // Express Install is turned off, or flash 6,0,65 isn't installed. + // Update is turned off. + // Return without doing anything. + return this; + } + } + + // Merge the default, express install and passed html options. + htmlOptions = $$.copy($$.htmlOptions, expressInstallOptions, htmlOptions); + + // Invoke $block (with a copy of the merged html options) for each element. + return this.each(function(){ + block.call(this, $$.copy(htmlOptions)); + }); + +}; +/** + * + * @name flash.copy + * @desc Copy an arbitrary number of objects into a new object. + * @type Object + * + * @example $$.copy({ foo: 1 }, { bar: 2 }); + * @result { foo: 1, bar: 2 }; + * +**/ +$$.copy = function() { + var options = {}, flashvars = {}; + for(var i = 0; i < arguments.length; i++) { + var arg = arguments[i]; + if(arg == undefined) continue; + jQuery.extend(options, arg); + // don't clobber one flash vars object with another + // merge them instead + if(arg.flashvars == undefined) continue; + jQuery.extend(flashvars, arg.flashvars); + } + options.flashvars = flashvars; + return options; +}; +/* + * @name flash.hasFlash + * @desc Check if a specific version of the Flash plugin is installed + * @type Boolean + * +**/ +$$.hasFlash = function() { + // look for a flag in the query string to bypass flash detection + if(/hasFlash\=true/.test(location)) return true; + if(/hasFlash\=false/.test(location)) return false; + var pv = $$.hasFlash.playerVersion().match(/\d+/g); + var rv = String([arguments[0], arguments[1], arguments[2]]).match(/\d+/g) || String($$.pluginOptions.version).match(/\d+/g); + for(var i = 0; i < 3; i++) { + pv[i] = parseInt(pv[i] || 0); + rv[i] = parseInt(rv[i] || 0); + // player is less than required + if(pv[i] < rv[i]) return false; + // player is greater than required + if(pv[i] > rv[i]) return true; + } + // major version, minor version and revision match exactly + return true; +}; +/** + * + * @name flash.hasFlash.playerVersion + * @desc Get the version of the installed Flash plugin. + * @type String + * +**/ +$$.hasFlash.playerVersion = function() { + // ie + try { + try { + // avoid fp6 minor version lookup issues + // see: http://blog.deconcept.com/2006/01/11/getvariable-setvariable-crash-internet-explorer-flash-6/ + var axo = new ActiveXObject('ShockwaveFlash.ShockwaveFlash.6'); + try { axo.AllowScriptAccess = 'always'; } + catch(e) { return '6,0,0'; } + } catch(e) {} + return new ActiveXObject('ShockwaveFlash.ShockwaveFlash').GetVariable('$version').replace(/\D+/g, ',').match(/^,?(.+),?$/)[1]; + // other browsers + } catch(e) { + try { + if(navigator.mimeTypes["application/x-shockwave-flash"].enabledPlugin){ + return (navigator.plugins["Shockwave Flash 2.0"] || navigator.plugins["Shockwave Flash"]).description.replace(/\D+/g, ",").match(/^,?(.+),?$/)[1]; + } + } catch(e) {} + } + return '0,0,0'; +}; +/** + * + * @name flash.htmlOptions + * @desc The default set of options for the object or embed tag. + * +**/ +$$.htmlOptions = { + height: 240, + flashvars: {}, + pluginspage: 'http://www.adobe.com/go/getflashplayer', + src: '#', + type: 'application/x-shockwave-flash', + width: 320 +}; +/** + * + * @name flash.pluginOptions + * @desc The default set of options for checking/updating the flash Plugin. + * +**/ +$$.pluginOptions = { + expressInstall: false, + update: true, + version: '6.0.65' +}; +/** + * + * @name flash.replace + * @desc The default method for replacing an element with a Flash movie. + * +**/ +$$.replace = function(htmlOptions) { + this.innerHTML = '<div class="alt">'+this.innerHTML+'</div>'; + jQuery(this) + .addClass('flash-replaced') + .prepend($$.transform(htmlOptions)); +}; +/** + * + * @name flash.update + * @desc The default method for replacing an element with an update message. + * +**/ +$$.update = function(htmlOptions) { + var url = String(location).split('?'); + url.splice(1,0,'?hasFlash=true&'); + url = url.join(''); + var msg = '<p>This content requires the Flash Player. <a href="http://www.adobe.com/go/getflashplayer">Download Flash Player</a>. Already have Flash Player? <a href="'+url+'">Click here.</a></p>'; + this.innerHTML = '<span class="alt">'+this.innerHTML+'</span>'; + jQuery(this) + .addClass('flash-update') + .prepend(msg); +}; +/** + * + * @desc Convert a hash of html options to a string of attributes, using Function.apply(). + * @example toAttributeString.apply(htmlOptions) + * @result foo="bar" foo="bar" + * +**/ +function toAttributeString() { + var s = ''; + for(var key in this) + if(typeof this[key] != 'function') + s += key+'="'+this[key]+'" '; + return s; +}; +/** + * + * @desc Convert a hash of flashvars to a url-encoded string, using Function.apply(). + * @example toFlashvarsString.apply(flashvarsObject) + * @result foo=bar&foo=bar + * +**/ +function toFlashvarsString() { + var s = ''; + for(var key in this) + if(typeof this[key] != 'function') + s += key+'='+encodeURIComponent(this[key])+'&'; + return s.replace(/&$/, ''); +}; +/** + * + * @name flash.transform + * @desc Transform a set of html options into an embed tag. + * @type String + * + * @example $$.transform(htmlOptions) + * @result <embed src="foo.swf" ... /> + * + * Note: The embed tag is NOT standards-compliant, but it + * works in all current browsers. flash.transform can be + * overwritten with a custom function to generate more + * standards-compliant markup. + * +**/ +$$.transform = function(htmlOptions) { + htmlOptions.toString = toAttributeString; + if(htmlOptions.flashvars) htmlOptions.flashvars.toString = toFlashvarsString; + return '<embed ' + String(htmlOptions) + '/>'; +}; + +/** + * + * Flash Player 9 Fix (http://blog.deconcept.com/2006/07/28/swfobject-143-released/) + * +**/ +if (window.attachEvent) { + window.attachEvent("onbeforeunload", function(){ + __flash_unloadHandler = function() {}; + __flash_savedUnloadHandler = function() {}; + }); +} + +})(); \ No newline at end of file -- 1.5.5.1
Steve Linabery
2008-Oct-09 10:19 UTC
[Ovirt-devel] [PATCH server] Add a rudimentary flash chart written in flex framework to summary pages.
On Thu, Oct 09, 2008 at 05:16:15AM -0500, Steve Linabery wrote:> First stab at showing real rrd data in flash chart (in this case, memory peak use for last 40 samples of rrd data). Note that to test this, you'll need to get the open flex sdk and compile src/flexchart/flexchart.mxml yourself.Gah, I just realized I didn't add our "preamble" & authorship info to the actionscript files I created for this. But I'll add that stuff if necessary. Thanks, Steve