Creating an Android application that invokes Data Services

To demonstrate how to create an Android application that invokes Data Services by using the Java API (both the Data Service Java API and the Android Java API), this section creates a client Android trader application. The server-side part of the sample Trader desktop application is available with Data Services.

You use the Java API to create a client Java application that runs on an Android device. Before reading this section, install the Android SDK and become familiar with it. It is recommended that you follow along with the tutorials available for the Android SDK. For information about the Android SDK, see http://developer.android.com/sdk/index.html.

The following illustration shows the sample Android trader application that is running within the Android emulator.

Note: The Android emulator is part of the Android SDK.

This application displays stock information within the Android device by subscribing to an existing Message service destination. The destination is part of the sample Trader desktop application that is available with Data Services. After you complete the Android client application, and are ready to run it, start the feed for the Trader desktop. For information, see the sample application web page that is available with Data Services.

The URL to the sample web page is:

http://<ServerName>:4502/content/dataservices-samples/index.html

where ServerName is the IP address of the server hosting Data Services.

Note: The following sections discuss how to create a Android client application by using the Android SDK and Eclipse.

Data Services Android JAR file

To use the Data Services Java API in an Android application, include the flex-messaging-client-android.jar file in your Java project’s class path. You can locate this JAR file in the following directory:

[ZIP location]\dataservices-sdk-pkg\jcr_root\etc\aep\sdks\riaservices\dataservices\4.5.0\android

This file is located in dataservices-sdk-pkg.zip. For information about extracting Data Services SDK files, see Retrieving SDK files from the Experience Server.

Note: In addition to the flex-messaging-client-android.jar file, you also need the JAR files that are part of the Android SDK.

Summary of steps

To create a client Android application that invokes Data Services, perform the following tasks:

  1. Install the Android SDK.

  2. Create an Android project using Eclipse.

  3. Create the Java client-side classes for the Android trader application.

Note: Deploying an Android application is not discussed in this section. For information about deploying an Android application, see http://developer.android.com/sdk/index.html.

Install the Android SDK

The first step to create an Android client application that can invoke Data Services is to install the Android SDK. For information about installing the Android SDK, see the detailed instructions at http://developer.android.com/sdk/index.html.

Install the ADT plug-in for Eclipse as specified at the Android SDK website. After you have successfully installed the Android SDK, you can create an Android project using Eclipse.

Note: Ensure that your system meets the requirements that are specified at the Android SDK website.

Create an Android project using Eclipse

Create an Android project by using Eclipse. The Android trader application runs within an Android Emulator. Before you create your Android project, create an Android Virtual Device (AVD). For information about creating an AVD and creating an Android project by using Eclipse, see the Hello World tutorial at http://developer.android.com/resources/tutorials/hello-world.html.

For this application, specify the following values in the New Android Project dialog box:

  • Application name: DSAndroid

  • Package name: flex.samples.marketdata.clients.android

  • Create Activity: TraderDesktop (name of the class that implements android.app.Activity)

Note: Ensure that you add the flex-messaging-client-android.jar file to your Android project.

Setting Internet permissions

You set Internet permissions in the AndroidManifest.xml file to ensure that the Android client application can communicate with a Data Services destination. Without this Internet permission, a java.net.SocketException is thrown. To set the Internet permission, specify the following permission in the AndroidManifest.xml file.

<uses-permission android:name="android.permission.INTERNET" />

The following XML code represents the AndroidManifest.xml file that contains this XML element.

<?xml version = 1.0" enc o ding="utf-8"?> 
<manifest xmlns:android="http://schemas.android.com/apk/res/android" 
package="flex.samples.marketdata.clients.android" 
android:versionCode="1" 
android:versionName="1.0"> 
 
<application android:icon="@drawable/icon" android:label="@string/app_name"> 
<activity android:name=".TraderDesktop" android:label="@string/app_name"> 
<intent-filter> 
    <action android:name="android.intent.action.MAIN" /> 
    <category android:name="android.intent.category.LAUNCHER" /> 
</intent-filter> 
</activity> 
</application><uses-permission android:name="android.permission.INTERNET" /></manifest>
Note: The AndroidManifest.xml file is part of your Android Java project.

Create the Java client-side classes for the Android client application

Create the following three Java client-side classes in your Android project:

  • TraderDesktop: Represents the main class that implements android.app.Activity. This class contains the onCreate method, which is the entry point for the Android application (the new project wizard creates this class). Most of the application logic in this class setups the user interface and does not contain application logic related to Data Services. This class belongs to the flex.samples.marketdata.clients.android package.

  • TraderDesktopModel: Represents the class that makes a connection to Data Services. This class subscribes to a Message service destination by using a MultiTopicConsumer instance. The destination to which this application subscribes exists as part of the Trader Desktop sample application that is available with Data Services.

  • Stock: Represents a stock that contains data members that store information such as high value, low value, open value, and so on.

The following illustration shows the Package Explorer view of the Android project that contains these classes.

Note: In addition to creating the Java files, be sure to replace main.xml with content located in this section and create the traderdesktop.properties file. The content for the traderdesktop.properties file is specified later in this section.

Creating the TraderDesktop class

The TraderDesktop class represents the main class for the Android application that implements android.app.Activity. This class streams updates from Data Services. In addition, this class defines the user interface that is displayed in the Android screen. In this application, the user interface displays stock data, as shown in the illustration displayed at the beginning of this section.

To set up the user interface, define an android.widget.TableLayout instance that displays data in a row and column format. The main.xml file located in the res/layout folder in your project defines the columns. The following XML represents the main.xml that is part of the Android trader desktop. (Notice that the main.xml file is displayed in the previous illustration.)

<?xml version="1.0" encoding="utf-8"?> 
 
<TableLayout xmlns:android="http://schemas.android.com/apk/res/android" 
    android:id="@+id/mainTable" 
    android:layout_width="fill_parent" 
    android:layout_height="fill_parent" 
    android:stretchColumns="*"> 
 
    <TableRow > 
        <TextView 
            android:layout_column="0" 
            android:text="Symbol" 
            android:textStyle="bold" android:textSize="15sp"/> 
        <TextView 
            android:layout_column="1" 
            android:text="Open" 
            android:textStyle="bold" android:textSize="15sp"/> 
        <TextView 
            android:layout_column="2" 
            android:text="Last" 
            android:textStyle="bold" android:textSize="15sp"/> 
        <TextView 
            android:layout_column="3" 
            android:text="Change" 
            android:textStyle="bold" android:textSize="15sp"/> 
        <TextView 
            android:layout_column="4" 
            android:text="High" 
            android:textStyle="bold" android:textSize="15sp"/> 
        <TextView 
            android:layout_column="5" 
            android:text="Low" 
            android:textStyle="bold" android:textSize="15sp"/> 
    </TableRow> 
 
</TableLayout>

Notice that the identifier value is mainTable as defined by the android:id tag. The following code example represents the onCreate method that is located in the TraderDesktop class.

@Override public void onCreate(Bundle savedInstanceState) 
{ 
    super.onCreate(savedInstanceState); 
    setContentView(R.layout.main); 
 
    // Initialize the model and the layout 
    traderDesktopModel = new TraderDesktopModel(this); 
    tableLayout = (TableLayout)findViewById(R.id.mainTable); 
 
    // Start the stock feed. 
    traderDesktopModel.initializeWatchList(); 
}

The findViewById method returns the android.widget.TableLayout instance that corresponds to the view defined in the main.xml file. Notice that the return value is cast to android.widget.TableLayout. The R.id.mainTable parameter value that is passed to the findViewById method references the main.xml file.

The TraderDesktop class contains a method named addRow that adds rows to the TableLayout instance. Each row displays stock data. An android.widget.TableRow instance that represents a row is created in the addRow method.

The android.widget.TableRow instance is added to the android.widget.TableLayout instance by invoking the android.widget.TableLayout instance’s addView method and passing the android.widget.TableRow instance.

The following code example represents the addRow method located in the TraderDesktop class.

private void addRow(Stock stock) 
{ 
        TableRow tableRow = new TableRow(this); 
        tableLayout.addView(tableRow); 
        symbolRows.put(stock.getSymbol(), tableRow); 
 
        TextView textView0 = new TextView(this); 
        textView0.setText(stock.getSymbol()); 
        tableRow.addView(textView0); 
 
        TextView textView1 = new TextView(this); 
        textView1.setText(String.valueOf(stock.getOpen())); 
        tableRow.addView(textView1); 
 
        TextView textView2 = new TextView(this); 
        textView2.setText(String.valueOf(stock.getLast())); 
        tableRow.addView(textView2); 
 
        TextView textView3 = new TextView(this); 
        double change = stock.getChange(); 
        textView3.setText(String.valueOf(stock.getChange())); 
        setColor(textView3, change); 
        tableRow.addView(textView3); 
 
        TextView textView4 = new TextView(this); 
        textView4.setText(String.valueOf(stock.getHigh())); 
        tableRow.addView(textView4); 
 
        TextView textView5 = new TextView(this); 
        textView5.setText(String.valueOf(stock.getLow())); 
        tableRow.addView(textView5); 
    }

An inner class named HandlerRunnable is defined that implements java.lang.Runnable. This class controls when stocks are updated. The following code example represents the entire TraderDesktop.java file, including the HandlerRunnable class. Notice that the TraderDesktop class is located in a package named flex.samples.marketdata.clients.android.

package flex.samples.marketdata.clients.android; 
 
import java.util.List; 
import java.util.Map; 
import java.util.concurrent.ConcurrentHashMap; 
import java.util.concurrent.CopyOnWriteArrayList; 
 
import android.app.Activity; 
import android.app.PendingIntent; 
import android.content.Intent; 
import android.os.Bundle; 
import android.os.Handler; 
import android.widget.TableLayout; 
import android.widget.TableRow; 
import android.widget.TextView; 
import flex.client.ApplicationFramework; 
import flex.client.ApplicationFramework.ApplicationFocusState; 
import flex.samples.marketdata.Stock; 
 
/** 
 * Android version of the trader desktop that uses LCDS Java client library APIs 
 * to get streaming updates from a LCDS messaging destination on multiple subtopics. 
 * See the associated model for LCDS data access related code. 
 */ 
public class TraderDesktop extends Activity 
{ 
    private static final int COLOR_RED = 0xFFFF0000; 
    private static final int COLOR_GREEN = 0xFF00FF00; 
    private static final int COLOR_WHITE = 0xFFFFFFFF; 
 
    // Handler for UI update callbacks to the UI thread. 
    private final Handler handler = new Handler(); 
    private final HandlerRunnable handlerRunnable = new HandlerRunnable(); 
 
    // Map of stock symbols and TableRows used in UI updates. 
    private final Map<String, TableRow> symbolRows = new ConcurrentHashMap<String, TableRow>(); 
 
    private TableLayout tableLayout; 
    private TraderDesktopModel traderDesktopModel; 
 
    @Override public void onCreate(Bundle savedInstanceState) 
    { 
        super.onCreate(savedInstanceState); 
 
        setContentView(R.layout.main); 
 
        // Initialize the model and the layout. 
        traderDesktopModel = new TraderDesktopModel(this); 
        tableLayout = (TableLayout)findViewById(R.id.mainTable); 
 
        // Start the stock feed. 
        traderDesktopModel.initializeWatchList(); 
 
        for (String symbol : traderDesktopModel.watchList) 
        { 
            TableRow tableRow = new TableRow(this); 
            tableLayout.addView(tableRow); 
            symbolRows.put(symbol, tableRow); 
 
            TextView textView0 = new TextView(this); 
            textView0.setText(symbol); 
            tableRow.addView(textView0); 
 
            TextView textView1 = new TextView(this); 
            textView1.setText("0.0"); 
            tableRow.addView(textView1); 
 
            TextView textView2 = new TextView(this); 
            textView2.setText("0.0"); 
            tableRow.addView(textView2); 
 
            TextView textView3 = new TextView(this); 
            double change = 0; 
            textView3.setText("0.0"); 
            setColor(textView3, change); 
            tableRow.addView(textView3); 
 
            TextView textView4 = new TextView(this); 
            textView4.setText("0.0"); 
            tableRow.addView(textView4); 
 
            TextView textView5 = new TextView(this); 
            textView5.setText("0.0"); 
            tableRow.addView(textView5); 
        } 
    } 
 
    /** 
     * Used by the model to add or update stocks. This method uses the handler 
     * to post updates to the UI thread. 
     * 
     * @param stock The stock. 
     */ 
    public void addOrUpdateStock(Stock stock) 
    { 
        handlerRunnable.addUpdate(stock); 
        handler.post(handlerRunnable); 
    } 
 
    private void addOrUpdateRow(Stock stock) 
    { 
        TableRow tableRow = symbolRows.get(stock.getSymbol()); 
        if (tableRow != null) 
            updateRow(tableRow, stock); 
        else 
            addRow(stock); 
    } 
 
    private void addRow(Stock stock) 
    { 
        TableRow tableRow = new TableRow(this); 
        tableLayout.addView(tableRow); 
        symbolRows.put(stock.getSymbol(), tableRow); 
 
        TextView textView0 = new TextView(this); 
        textView0.setText(stock.getSymbol()); 
        tableRow.addView(textView0); 
 
        TextView textView1 = new TextView(this); 
        textView1.setText(String.valueOf(stock.getOpen())); 
        tableRow.addView(textView1); 
 
        TextView textView2 = new TextView(this); 
        textView2.setText(String.valueOf(stock.getLast())); 
        tableRow.addView(textView2); 
 
        TextView textView3 = new TextView(this); 
        double change = stock.getChange(); 
        textView3.setText(String.valueOf(stock.getChange())); 
        setColor(textView3, change); 
        tableRow.addView(textView3); 
 
        TextView textView4 = new TextView(this); 
        textView4.setText(String.valueOf(stock.getHigh())); 
        tableRow.addView(textView4); 
 
        TextView textView5 = new TextView(this); 
        textView5.setText(String.valueOf(stock.getLow())); 
        tableRow.addView(textView5); 
    } 
 
    private void setColor(TextView textView, double change) 
    { 
        if (change < 0) 
            textView.setTextColor(COLOR_RED); 
        else if (change > 0) 
            textView.setTextColor(COLOR_GREEN); 
        else 
            textView.setTextColor(COLOR_WHITE); 
    } 
 
    private void updateRow(TableRow tableRow, Stock stock) 
    { 
        TextView textView0 = (TextView)tableRow.getChildAt(0); 
        textView0.setText(stock.getSymbol()); 
 
        TextView textView1 = (TextView)tableRow.getChildAt(1); 
        textView1.setText(String.valueOf(stock.getOpen())); 
 
        TextView textView2 = (TextView)tableRow.getChildAt(2); 
        textView2.setText(String.valueOf(stock.getLast())); 
 
        TextView textView3 = (TextView)tableRow.getChildAt(3); 
        double change = stock.getChange(); 
        textView3.setText(String.valueOf(change)); 
        setColor(textView3, change); 
 
        TextView textView4 = (TextView)tableRow.getChildAt(4); 
        textView4.setText(String.valueOf(stock.getHigh())); 
 
        TextView textView5 = (TextView)tableRow.getChildAt(5); 
        textView5.setText(String.valueOf(stock.getLow())); 
    } 
 
    /** 
     * Inner class used to keep track of stock updates that haven't been applied 
     * to the UI yet. 
     */ 
    class HandlerRunnable implements Runnable 
    { 
        private List<Stock> updates = new CopyOnWriteArrayList<Stock>(); 
 
        @Override public void run() 
        { 
            if (updates.isEmpty()) 
                return; 
 
            for (Stock stock : updates) 
                addOrUpdateRow(stock); 
 
            updates.clear(); 
        } 
 
        void addUpdate(Stock stock) 
        { 
            updates.add(stock); 
        } 
    } 
 
    @Override protected void onPause() 
    { 
        ApplicationFramework.notifyApplicationFocusChange(ApplicationFocusState.BACKGROUND); 
        super.onPause(); 
    } 
 
    @Override protected void onResume() 
    { 
        super.onResume(); 
        ApplicationFramework.notifyApplicationFocusChange(ApplicationFocusState.FOREGROUND); 
        if (C2DMHandler.deviceId == null) 
        { 
            Intent intent = new Intent("com.google.android.c2dm.intent.REGISTER"); 
            intent.putExtra("app", PendingIntent.getBroadcast(this, 0, new Intent(), 0)); 
            intent.putExtra("sender", C2DMHandler.SENDER_ID); 
            startService(intent); 
        } 
    } 
}

Creating the TraderDesktopModel class

The TraderDesktopModel class makes a connection to Data Services. Because the Android client application tracks multiple stocks, a MultiTopicConsumer instance is used. Using a MultiTopicConsumer instance, the client application receives messages from different subtopics that are located in the destination. As information is obtained about a stock, it is updated in the client application.

The TraderDesktopModel class contains two methods:

  • initializeWatchList: Subscribes to a Message Service destination by using a MultiTopicConsumer instance. The name of the destination is defined in the traderdesktop.properties file.

  • loadProperties: Loads properties from the traderdesktop.properties file. Property values are stored in data members that belong to the TraderDesktopModel class.

The traderdesktop.properties file contains properties that the client application requires to subscribe to the destination. This file defines a value for the destination identifier value, the channel identifier value, and the URL of the server hosting Data Services. The traderdesktop.properties file also contains a property named WATCH_LIST that contains a comma delimited list of stock symbols. The client application tracks the stocks that are specified in this list.

The following code represents the traderdesktop.properties file.

DESTINATION_ID=market-data-feed 
CHANNEL_ID=my-polling-amf 
CHANNEL_URL=http://<ServerName>:<Port>/dataservices/messagebroker/amfpolling 
WATCH_LIST=ADBE, BA, COKE, GE, IBM, JBLU, MOT, MCD, XOM, SAP
Note: Place the traderdesktop.properties file in the flex.samples.marketdata.clients.android package. Also, replace ServerName and Port with the IP address of the server hosting the Experience Server. Also note that LocalHost cannot be used with an Android application.

The initializeWatchList method contains application logic that subscribes to the destination specified in the traderdesktop.properties file. In this example application, the destination is named market-data-feed. Within this method, a MultiTopicConsumer instance named consumer is created by using its constructor and passing a string value that specifies the destination identifier value.

MultiTopicConsumer consumer = new MultiTopicConsumer(destinationId); 

Next, a Channel instance is created by using its constructor and passing the channel identifier value, channel URL value. Both of these values are retrieved from the traderdesktop.properties file. In addition, the third and forth paramater values are HttpMode.POLLING and Encoding.AMF.

A ChannelSet instance is created by using the ChannelSet constructor and passing the Channel instance. The MultiTopicConsumer object’s setChannelSet method is invoked and the ChannelSet instance is passed. This method defines the channel that the MultiTopicConsumer object uses to subscribe to the destination.

The MultiTopicConsumer object’s addMessageListener method is invoked to define the message listening events. For example, an event handler method is defined that is invoked when a message is received from the destination. In this example, the method is named MessageRecieved. The following code example shows the MultiTopicConsumer object’s addMessageListener method defining the event handler methods.

consumer.addMessageListener(new MessageListener() 
{ 
            public void messageAcknowledged(MessageAckEvent ackEvent) 
            { 
                System.out.println(ackEvent); 
            } 
 
            public void messageFaulted(MessageFaultEvent faultEvent) 
            { 
                ErrorMessage errorMessage = faultEvent.getError(); 
                System.err.println(errorMessage.faultString); 
            } 
 
            public void messageReceived(MessageEvent messageEvent) 
            { 
                Message message = messageEvent.getMessage(); 
                Stock changedStock = (Stock)message.getBody(); 
                traderDesktop.addOrUpdateStock(changedStock); 
            } 
 
        });

The following Java code represents the TraderDesktopModel class.

package flex.samples.marketdata.clients.andr oid; 
 
import java.io.InputStream; 
import java.net.MalformedURLException; 
import java.util.Properties; 
 
import flex.client.channels.Channel; 
import flex.client.channels.Channel.Encoding;
import flex.client.channels.Channel.HttpMode;
import flex.client.channels.ChannelSet; 
import flex.client.messaging.MultiTopicConsumer; 
import flex.client.messaging.events.MessageAckEvent; 
import flex.client.messaging.events.MessageEvent; 
import flex.client.messaging.events.MessageFaultEvent; 
import flex.client.messaging.events.MessageListener; 
import flex.messaging.messages.ErrorMessage; 
import flex.messaging.messages.Message; 
import flex.samples.marketdata.Stock; 
 
/** 
 * Table model for the trader desktop that reads configuration values from the properties 
 * file and handles the data connectivity with the Data Services destination in initializeWatchList. 
 */ 
public class TraderDesktopModel 
{ 
    private final TraderDesktop traderDesktop; 
 
    private String destinationId; 
    private String channelId; 
    private String channelUrl; 
    private String[] watchList; 
 
    /** 
     * Construct the model with the supplied desktop 
     * @param traderDesktop The associated trader desktop 
     */ 
    TraderDesktopModel(TraderDesktop traderDesktop) 
    { 
        this.traderDesktop = traderDesktop; 
        loadProperties(); 
    } 
 
    /** 
     * Main method that handles data connectivity using a MultiTopicConsumer instance 
     */ 
    void initializeWatchList() 
    { 
        MultiTopicConsumer consumer = new MultiTopicConsumer(destinationId); 
        Channel channel = null; 
        try 
        { 
             channel = new Channel(channelId, channelUrl, HttpMode.POLLING, Encoding.AMF); 
        } 
        catch (MalformedURLException e1) 
        { 
            e1.printStackTrace(); 
        } 
        ChannelSet channelSet = new ChannelSet(channel); 
        consumer.setChannelSet(channelSet); 
        consumer.addMessageListener(new MessageListener() 
        { 
            public void messageAcknowledged(MessageAckEvent ackEvent) 
            { 
                System.out.println(ackEvent); 
            } 
 
            public void messageFaulted(MessageFaultEvent faultEvent) 
            { 
                ErrorMessage errorMessage = faultEvent.getError(); 
                System.err.println(errorMessage.faultString); 
            } 
 
            public void messageReceived(MessageEvent messageEvent) 
            { 
                Message message = messageEvent.getMessage(); 
                Stock changedStock = (Stock)message.getBody(); 
                traderDesktop.addOrUpdateStock(changedStock); 
            } 
 
        }); 
 
 
        for (String symbol : watchList) 
            consumer.addSubscription(symbol, null, 0); 
 
        consumer.subscribe(); 
        
        boolean ans = consumer.isSubscribed(); 
        System.out.println(ans); 
    } 
 
    //Loads properties from the traderdesktop.properties file 
    private void loadProperties() 
    { 
        Properties properties = new Properties(); 
        try 
        { 
            InputStream inStream = getClass().getClassLoader().getResourceAsStream("flex/samples/marketdata/clients/android/traderdesktop.properties"); 
            properties.load(inStream); 
            destinationId = properties.getProperty("DESTINATION_ID"); 
            channelId = properties.getProperty("CHANNEL_ID"); 
            channelUrl = properties.getProperty("CHANNEL_URL"); 
            String watchList = properties.getProperty("WATCH_LIST"); 
            if (watchList != null) 
            { 
                this.watchList = watchList.split(","); 
                for (int i = 0; i < this.watchList.length; i++) 
                    this.watchList[i] = this.watchList[i].trim(); 
            } 
        } 
        catch (Exception e) 
        { 
            e.printStackTrace(); 
        } 
    } 
}

Creating the Stock class

The Stock class contains data members that store stock information such as high value, low value, open value, and so on. This class contains a method named simulateChange that modifies the value of the stock price by using random values. Notice that this class belongs to a package named flex.samples.marketdata.

Note: Modifying stock data by using random values is used only in the context of a sample application. A real world application typically uses a third-party financial content provider to retrieve stock data. For information about using Data Services with a third party financial content provider, see Creating live stock quote sample applications using the Message service.

The following Java code represents the Stock class.

package flex.samples.marketdata; 
 
import java.text.DecimalFormat; 
import java.util.Date; 
import java.util.Random; 
 
public class Stock implements Comparable<Stock> 
{ 
    private String symbol; 
    private String name; 
    private double open; 
    private double last; 
    private double change; 
    private double low; 
    private double high; 
    private Date date; 
 
    private final Random random = new Random(); 
    private final DecimalFormat formatter = new DecimalFormat("####.##"); 
 
    public Stock() 
    { 
        // No-op. 
    } 
 
    public Stock(String symbol, String name, double open, double last, 
            double change, double low, double high) 
    { 
        this.symbol = symbol; 
        this.name = name; 
        this.open = open; 
        this.last = last; 
        this.change = change; 
        this.low = low; 
        this.high = high; 
        date = new Date(); 
    } 
 
    public String getSymbol() 
    { 
        return symbol; 
    } 
 
    public void setSymbol(String symbol) 
    { 
        this.symbol = symbol; 
    } 
 
    public String getName() 
    { 
        return name; 
    } 
 
    public void setName(String name) 
    { 
        this.name = name; 
    } 
 
    public double getOpen() 
    { 
        return Double.valueOf(formatter.format(open)); 
    } 
 
    public void setOpen(double open) 
    { 
        this.open = open; 
    } 
 
    public double getLast() 
    { 
        return Double.valueOf(formatter.format(last)); 
    } 
 
    public void setLast(double last) 
    { 
        this.last = last; 
    } 
 
    public double getChange() 
    { 
        return Double.valueOf(formatter.format(change)); 
    } 
 
    public void setChange(double change) 
    { 
        this.change = change; 
    } 
 
    public double getLow() 
    { 
        return Double.valueOf(formatter.format(low)); 
    } 
 
    public void setLow(double low) 
    { 
        this.low = low; 
    } 
 
    public double getHigh() 
    { 
        return Double.valueOf(formatter.format(high)); 
    } 
 
    public void setHigh(double high) 
    { 
        this.high = high; 
    } 
 
    public Date getDate() 
    { 
        return date; 
    } 
 
    public void setDate(Date date) 
    { 
        this.date = date; 
    } 
 
    @Override public int hashCode() 
    { 
        final int prime = 31; 
        int result = 1; 
        result = prime * result + ((symbol == null) ? 0 : symbol.hashCode()); 
        return result; 
    } 
 
    @Override public boolean equals(Object obj) 
    { 
        if (this == obj) 
            return true; 
 
        if (obj == null) 
            return false; 
 
        if (getClass() != obj.getClass()) 
            return false; 
 
        Stock other = (Stock)obj; 
        if (symbol == null) 
        { 
            if (other.symbol != null) 
                return false; 
        } 
        else if (!symbol.equals(other.symbol)) 
            return false; 
 
        return true; 
    } 
 
    void simulateChange() 
    { 
        double maxPerChange = 0.1; // Limit changes to less than 10%. 
        double maxChange = last * (random.nextDouble() * maxPerChange); 
        double currentChange = random.nextDouble() * maxChange; 
        boolean increasing = random.nextBoolean(); 
        change = increasing? currentChange : currentChange * -1; 
        last = increasing? last + currentChange : last - currentChange; 
 
        if (last > high) 
            high = last; 
        else if (last < low) 
            low = last; 
 
        date = new Date(); 
    } 
 
    public int compareTo(Stock other) 
    { 
        return symbol.compareTo(other.symbol); 
    } 
}