Package com.rim.samples.device.httpdemo

Source Code of com.rim.samples.device.httpdemo.HTTPDemo$StatusThread

/**
* HTTPDemo.java
*
* Copyright � 1998-2011 Research In Motion Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Note: For the sake of simplicity, this sample application may not leverage
* resource bundles and resource strings.  However, it is STRONGLY recommended
* that application developers make use of the localization features available
* within the BlackBerry development platform to ensure a seamless application
* experience across a variety of languages and geographies.  For more information
* on localizing your application, please refer to the BlackBerry Java Development
* Environment Development Guide associated with this release.
*/

package com.rim.samples.device.httpdemo;

import java.io.IOException;
import java.io.InputStream;

import javax.microedition.io.Connector;
import javax.microedition.io.HttpConnection;

import net.rim.device.api.command.Command;
import net.rim.device.api.command.CommandHandler;
import net.rim.device.api.command.ReadOnlyCommandMetadata;
import net.rim.device.api.io.IOCancelledException;
import net.rim.device.api.io.IOUtilities;
import net.rim.device.api.system.Characters;
import net.rim.device.api.ui.MenuItem;
import net.rim.device.api.ui.UiApplication;
import net.rim.device.api.ui.component.BasicEditField;
import net.rim.device.api.ui.component.Dialog;
import net.rim.device.api.ui.component.EditField;
import net.rim.device.api.ui.component.Menu;
import net.rim.device.api.ui.component.RichTextField;
import net.rim.device.api.ui.component.SeparatorField;
import net.rim.device.api.ui.container.MainScreen;
import net.rim.device.api.util.StringProvider;

/**
* This sample makes a an http or https connection to a specified URL and
* retrieves and displays html content.
*/
public class HTTPDemo extends UiApplication {
    private static final String SAMPLE_HTTPS_PAGE =
            "https://www.blackberry.com/go/mobile/samplehttps.shtml";
    private static final String[] HTTP_PROTOCOL = { "http://", "http:\\",
            "https://", "https:\\" };
    private static final char HTML_TAG_OPEN = '<';
    private static final char HTML_TAG_CLOSE = '>';
    private static final String HEADER_CONTENTTYPE = "content-type";
    private static final String CONTENTTYPE_TEXTHTML = "text/html";

    private static final int STATE_0 = 0;
    private static final int STATE_1 = 1;
    private static final int STATE_2 = 2;
    private static final int STATE_3 = 3;
    private static final int STATE_4 = 4;
    private static final int STATE_5 = 5;

    private static final char CR = 0x000D;
    private static final char LF = 0x000A;
    private static final char TAB = 0x0009;

    private final HTTPDemoScreen _mainScreen;
    private final EditField _url;
    private final RichTextField _content;
    private boolean _useWapStack;
    private final WapOptionsScreen _wapOptionsScreen;

    private StatusThread _statusThread = new StatusThread();
    private ConnectionThread _connectionThread = new ConnectionThread();

    /**
     * Entry point for application
     *
     * @param args
     *            Command line arguments (not used)
     */
    public static void main(final String[] args) {
        // Create a new instance of the application and make the currently
        // running thread the application's event dispatch thread.
        final HTTPDemo theApp = new HTTPDemo();
        theApp.enterEventDispatcher();
    }

    /**
     * Creates a new HTTPDemo object
     */
    public HTTPDemo() {
        _fetchMenuItem = new MenuItem(new StringProvider("Fetch"), 0x230010, 0);
        _fetchMenuItem.setCommand(new Command(new CommandHandler() {
            /**
             * @see net.rim.device.api.command.CommandHandler#execute(ReadOnlyCommandMetadata,
             *      Object)
             */
            public void execute(final ReadOnlyCommandMetadata metadata,
                    final Object context) {
                // Don't execute on a blank url.
                if (_url.getText().length() > 0) {
                    if (!_connectionThread.isStarted()) {
                        fetchPage(_url.getText());
                    } else {
                        createNewFetch(_url.getText());
                    }
                }
            }
        }));

        _clearContent =
                new MenuItem(new StringProvider("Clear Content"), 0x230020, 3);
        _clearContent.setCommand(new Command(new CommandHandler() {
            /**
             * @see net.rim.device.api.command.CommandHandler#execute(ReadOnlyCommandMetadata,
             *      Object)
             */
            public void execute(final ReadOnlyCommandMetadata metadata,
                    final Object context) {
                _content.setText("<content>");
            }
        }));

        _fetchHTTPSPage =
                new MenuItem(new StringProvider("Fetch Sample HTTPS Page"),
                        0x230030, 2);
        _fetchHTTPSPage.setCommand(new Command(new CommandHandler() {
            /**
             * @see net.rim.device.api.command.CommandHandler#execute(ReadOnlyCommandMetadata,
             *      Object)
             */
            public void execute(final ReadOnlyCommandMetadata metadata,
                    final Object context) {
                if (!_connectionThread.isStarted()) {
                    // Menu items are executed on the event thread, therefore we
                    // can edit the
                    // URL field in place.
                    _url.setText(SAMPLE_HTTPS_PAGE);
                    fetchPage(SAMPLE_HTTPS_PAGE);
                } else {
                    createNewFetch(_url.getText());
                }
            }
        }));

        _wapStackOption =
                new MenuItem(new StringProvider("Use Wap Stack"), 0x230040, 4);
        _wapStackOption.setCommand(new Command(new CommandHandler() {
            /**
             * @see net.rim.device.api.command.CommandHandler#execute(ReadOnlyCommandMetadata,
             *      Object)
             */
            public void execute(final ReadOnlyCommandMetadata metadata,
                    final Object context) {
                _useWapStack = !_useWapStack; // Toggle the wap stack option
            }
        }));

        _wapStackOptionScreen =
                new MenuItem(new StringProvider("Wap Options"), 0x230050, 5);
        _wapStackOptionScreen.setCommand(new Command(new CommandHandler() {
            /**
             * @see net.rim.device.api.command.CommandHandler#execute(ReadOnlyCommandMetadata,
             *      Object)
             */
            public void execute(final ReadOnlyCommandMetadata metadata,
                    final Object context) {
                _wapOptionsScreen.display();
            }
        }));

        _wapOptionsScreen = new WapOptionsScreen(this);
        _mainScreen = new HTTPDemoScreen();
        _mainScreen.setTitle("HTTP Demo");

        _url =
                new EditField("URL: ", "http://", Integer.MAX_VALUE,
                        BasicEditField.FILTER_URL);
        _url.setCursorPosition(7);
        _mainScreen.add(_url);

        _mainScreen.add(new SeparatorField());

        _content = new RichTextField();
        _mainScreen.add(_content);

        // Start the helper threads
        _statusThread.start();
        _connectionThread.start();

        pushScreen(_mainScreen);
    }

    /**
     * Menu item to fetch content from URL specified in URL field
     */
    private final MenuItem _fetchMenuItem;

    /**
     * Clears the content field
     */
    private final MenuItem _clearContent;

    /**
     * Menu item to fetch pre-defined sample HTTPS page
     */
    private final MenuItem _fetchHTTPSPage;

    /**
     * Toggles the wap stack option
     */
    private final MenuItem _wapStackOption;

    /**
     * Menu item to display the wap options screen
     */
    private final MenuItem _wapStackOptionScreen;

    /**
     * Stops current fetch and initiates a new fetch
     *
     * @param url
     *            The url of the content to fetch
     */
    private void createNewFetch(final String url) {
        // Stop the current helper threads
        _statusThread.stop();
        _connectionThread.stop();

        // Reinitialize the helper threads
        _statusThread = new StatusThread();
        _connectionThread = new ConnectionThread();

        // Restart the helper threads
        _statusThread.start();
        _connectionThread.start();

        // Fetch the url
        fetchPage(url);
    }

    /**
     * Fetches the content on the speicifed url
     *
     * @param url
     *            The url of the content to fetch
     */
    private void fetchPage(String url) {
        // Normalize the url
        final String lcase = url.toLowerCase();

        boolean validHeader = false;
        int i = 0;

        for (i = HTTP_PROTOCOL.length - 1; i >= 0; --i) {
            if (-1 != lcase.indexOf(HTTP_PROTOCOL[i])) {
                validHeader = true;
                break;
            }
        }

        if (_useWapStack) {
            url = url + _wapOptionsScreen.getWapParameters();
        }

        if (!validHeader) {
            url = HTTP_PROTOCOL[0] + url; // Prepend the protocol specifier
        }

        // It is illegal to open a connection on the event thread. We need to
        // spawn a new thread for connection operations.
        _connectionThread.fetch(url);

        // Create a thread to display the status of the current operation
        _statusThread.go();
    }

    /**
     * Method to update the content field
     *
     * @param text
     *            The text to display
     */
    private void updateContent(final String text) {
        // This will create significant garbage, but avoids threading issues
        // (compared with creating a static Runnable and setting the text).
        UiApplication.getUiApplication().invokeLater(new Runnable() {
            public void run() {
                _content.setText(text);
            }
        });
    }

    /**
     * Performs operations on the html data. Removes tags, comments, whitespace
     * and inserts new lines for the
     * <p>
     * tag.
     *
     * @param text
     *            The text to be prepared for display
     * @return The processed text
     */
    private String prepareData(final String text) {
        final int text_length = text.length();
        final StringBuffer data = new StringBuffer(text_length);
        int state = STATE_0;
        int count = 0;
        int writeIndex = -1;
        char c = (char) 0;

        for (int i = 0; i < text_length; ++i) {
            c = text.charAt(i);
            switch (state) {
            case STATE_0:
                if (c == HTML_TAG_OPEN) {
                    ++count;
                    state = STATE_1;
                } else if (c == ' ') {
                    data.insert(++writeIndex, c);
                    state = STATE_5;
                } else if (!specialChar(c)) {
                    data.insert(++writeIndex, c);
                }
                break;

            case STATE_1:
                if (c == '!' && text.charAt(i + 1) == '-'
                        && text.charAt(i + 2) == '-') {
                    System.out.println("Entering Comment state");
                    i += 2;
                    state = STATE_3;
                } else if (Character.toLowerCase(c) == 'p') {
                    state = STATE_4;
                } else if (c == HTML_TAG_CLOSE) {
                    --count;
                    state = STATE_0;
                } else {
                    state = STATE_2;
                }
                break;

            case STATE_2:
                if (c == HTML_TAG_OPEN) {
                    ++count;
                } else if (c == HTML_TAG_CLOSE) {
                    if (--count == 0) {
                        state = STATE_0;
                    }
                }
                break;

            case STATE_3:
                if (c == '-' && text.charAt(i + 1) == '-'
                        && text.charAt(i + 2) == HTML_TAG_CLOSE) {
                    --count;
                    i += 2;
                    state = STATE_0;
                    System.out.println("Exiting comment state");
                }
                break;

            case STATE_4:
                if (c == HTML_TAG_CLOSE) {
                    --count;
                    data.insert(++writeIndex, '\n');
                    state = STATE_0;
                } else {
                    state = STATE_1;
                }
                break;

            case STATE_5:
                if (c == HTML_TAG_OPEN) {
                    ++count;
                    state = STATE_1;
                } else if (c != ' ') {
                    state = STATE_0;
                    if (!specialChar(c)) {
                        data.insert(++writeIndex, c);
                    }
                }
                break;
            }
        }

        return data.toString().substring(0, writeIndex + 1);
    }

    /**
     * Checks whether a char is a carriage return, line feed or tab character
     *
     * @param c
     *            The char to check
     * @return True if char is a carriage return or a line feed, otherwise false
     */
    private boolean specialChar(final char c) {
        return c == LF || c == CR || c == TAB;
    }

    /**
     * The ConnectionThread class manages the HTTP connection. If a fetch call
     * is made and another request is made while the first is still active, the
     * first fetch will be terminated and the second one will start processing.
     */
    private class ConnectionThread extends Thread {
        private static final int TIMEOUT = 500; // ms

        private String _theUrl;

        private volatile boolean _fetchStarted = false;
        private volatile boolean _stop = false;

        /**
         * Retrieves the url this thread is trying to connect to
         *
         * @return The url that this thread is trying to connect to
         */
        private String getUrl() {
            return _theUrl;
        }

        /**
         * Indicates whether the thread has started fetching yet
         *
         * @return True if the fetching has started, false otherwise
         */
        private boolean isStarted() {
            return _fetchStarted;
        }

        /**
         * Fetches a page
         *
         * @param url
         *            The url of the page to fetch
         */
        private void fetch(final String url) {
            _fetchStarted = true;
            _theUrl = url;
        }

        /**
         * Stop the thread
         */
        private void stop() {
            _stop = true;
        }

        /**
         * This method is where the thread retrieves the content from the page
         * whose url is associated with this thread.
         *
         * @see java.lang.Runnable#run()
         */
        public void run() {
            for (;;) {
                // Thread control
                while (!_fetchStarted && !_stop) {
                    // Sleep for a bit so we don't spin
                    try {
                        sleep(TIMEOUT);
                    } catch (final InterruptedException e) {
                        errorDialog("Thread#sleep(long) threw " + e.toString());
                    }
                }

                // Exit condition
                if (_stop) {
                    return;
                }

                String content = "";

                // Open the connection and extract the data
                try {
                    final HttpConnection httpConn =
                            (HttpConnection) Connector.open(getUrl());

                    final int status = httpConn.getResponseCode();

                    if (status == HttpConnection.HTTP_OK) {
                        // Is this html?
                        final String contentType =
                                httpConn.getHeaderField(HEADER_CONTENTTYPE);
                        final boolean htmlContent =
                                contentType != null
                                        && contentType
                                                .startsWith(CONTENTTYPE_TEXTHTML);

                        final InputStream input = httpConn.openInputStream();

                        final byte[] bytes = IOUtilities.streamToBytes(input);

                        final StringBuffer raw =
                                new StringBuffer(new String(bytes));
                        raw.insert(0, "bytes received]\n");
                        raw.insert(0, bytes.length);
                        raw.insert(0, '[');

                        content = raw.toString();

                        if (htmlContent) {
                            content = prepareData(raw.toString());
                        }
                        input.close();
                    } else {
                        content = "response code = " + status;
                    }
                    httpConn.close();
                } catch (final IOCancelledException e) {
                    System.out.println(e.toString());
                    return;
                } catch (final IOException e) {
                    errorDialog(e.toString());
                    return;
                }

                // Make sure status thread doesn't overwrite the content
                stopStatusThread();
                updateContent(content);

                // We're finished with the operation so reset
                // the start state.
                _fetchStarted = false;
            }
        }

        /**
         * Stops the status thread
         */
        private void stopStatusThread() {
            _statusThread.pause();
            try {
                synchronized (_statusThread) {
                    // Check the paused condition, in case the notify
                    // fires prior to our wait, in which case we may
                    // never see that notify.
                    while (!_statusThread.isPaused()) {
                        ;
                    }
                    {
                        _statusThread.wait();
                    }
                }
            } catch (final InterruptedException e) {
                errorDialog("StatusThread#wait() threw " + e.toString());
            }
        }
    }

    /**
     * The StatusThread class manages display of the status message while
     * lengthy HTTP/HTML operations are taking place.
     */
    private class StatusThread extends Thread {
        private static final int TIMEOUT = 500; // ms
        private static final int THREAD_TIMEOUT = 500;

        private volatile boolean _stop = false;
        private volatile boolean _running = false;
        private volatile boolean _isPaused = false;

        /**
         * Resumes this thread
         *
         * @see #pause()
         */
        private void go() {
            _running = true;
            _isPaused = false;
        }

        /**
         * Pauses this thread
         *
         * @see #go()
         */
        private void pause() {
            _running = false;
        }

        /**
         * Queries the paused status of this thread
         *
         * @return True if the thread is paused, false otherwise
         */
        private boolean isPaused() {
            return _isPaused;
        }

        /**
         * Stops the thread
         */
        private void stop() {
            _stop = true;
        }

        /**
         * This method is where the thread updates the status message while
         * HTTP/HTML operations are taking place.
         *
         * @see java.lang.Runnable#run()
         */
        public void run() {
            int i = 0;

            // Set up the status messages
            final String[] statusMsg = new String[6];
            final StringBuffer status = new StringBuffer("Working");
            statusMsg[0] = status.toString();

            for (int j = 1; j < 6; ++j) {
                statusMsg[j] = status.append(" .").toString();
            }

            for (;;) {
                while (!_stop && !_running) {
                    // Sleep a bit so we don't spin
                    try {
                        sleep(THREAD_TIMEOUT);
                    } catch (final InterruptedException e) {
                        errorDialog("Thread#sleep(long) threw " + e.toString());
                    }
                }

                if (_stop) {
                    return;
                }

                i = 0;

                // Clear the status buffer
                status.delete(0, status.length());

                for (;;) {
                    // We're not synchronizing on the boolean flag,
                    // therefore, value is declared volatile.
                    if (_stop) {
                        return;
                    }

                    if (!_running) {
                        _isPaused = true;

                        synchronized (this) {
                            this.notify();
                        }

                        break;
                    }

                    updateContent(statusMsg[++i % 6]);

                    try {
                        Thread.sleep(TIMEOUT); // Wait for a bit.
                    } catch (final InterruptedException e) {
                        errorDialog("Thread.sleep(long) threw " + e.toString());
                    }
                }
            }
        }
    }

    /**
     * This is the main screen that displays the content fetched by the
     * ConnectionThread.
     */
    private class HTTPDemoScreen extends MainScreen {
        /**
         * @see net.rim.device.api.ui.container.MainScreen#makeMenu(Menu,int)
         */
        protected void makeMenu(final Menu menu, final int instance) {
            menu.add(_fetchMenuItem);
            menu.add(_clearContent);
            menu.add(_fetchHTTPSPage);
            menu.add(_wapStackOptionScreen);

            final StringBuffer sb = new StringBuffer();

            if (_useWapStack) {
                sb.append(Characters.CHECK_MARK);
            }

            sb.append("Use Wap Stack");
            _wapStackOption.setText(new StringProvider(sb.toString()));
            menu.add(_wapStackOption);

            menu.addSeparator();

            super.makeMenu(menu, instance);
        }

        /**
         * @see net.rim.device.api.ui.container.MainScreen#onSavePrompt()
         */
        public boolean onSavePrompt() {
            // Prevent the save dialog from being displayed
            return true;
        }

        /**
         * @see net.rim.device.api.ui.Screen#close()
         */
        public void close() {
            _statusThread.stop();
            _connectionThread.stop();

            super.close();
        }

        /**
         * @see net.rim.device.api.ui.Screen#keyChar(char,int,int)
         */
        protected boolean keyChar(final char key, final int status,
                final int time) {
            if (getLeafFieldWithFocus() == _url && key == Characters.ENTER) {
                _fetchMenuItem.run();
                return true; // Consume the key event
            } else {
                return super.keyChar(key, status, time);
            }
        }
    }

    /**
     * Presents a dialog to the user with a given message
     *
     * @param message
     *            The text to display
     */
    public static void errorDialog(final String message) {
        UiApplication.getUiApplication().invokeLater(new Runnable() {
            public void run() {
                Dialog.alert(message);
            }
        });
    }
}
TOP

Related Classes of com.rim.samples.device.httpdemo.HTTPDemo$StatusThread

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.