/*
* Copyright (c) 2008, 2010, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores
* CA 94065 USA or visit www.oracle.com if you need additional information or
* have any questions.
*/
package com.sun.lwuit.io;
import com.sun.lwuit.Display;
import com.sun.lwuit.events.ActionEvent;
import com.sun.lwuit.events.ActionListener;
import com.sun.lwuit.io.impl.IOImplementation;
import com.sun.lwuit.util.EventDispatcher;
import java.io.IOException;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Vector;
/**
* Main entry point for managing the connection requests, this is essentially a
* threaded queue that makes sure to route all connections via the network thread
* while sending the callbacks through the LWUIT EDT.
*
* @author Shai Almog
*/
public class NetworkManager {
/**
* Indicates an unknown access point type
*/
public static final int ACCESS_POINT_TYPE_UNKNOWN = 1;
/**
* Indicates a wlan (802.11b/c/g/n) access point type
*/
public static final int ACCESS_POINT_TYPE_WLAN = 2;
/**
* Indicates an access point based on a cable
*/
public static final int ACCESS_POINT_TYPE_CABLE = 3;
/**
* Indicates a 3g network access point type
*/
public static final int ACCESS_POINT_TYPE_NETWORK3G = 4;
/**
* Indicates a 2g network access point type
*/
public static final int ACCESS_POINT_TYPE_NETWORK2G = 5;
/**
* Indicates a corporate routing server access point type (e.g. BIS etc.)
*/
public static final int ACCESS_POINT_TYPE_CORPORATE = 6;
private static final Object LOCK = new Object();
private static final NetworkManager INSTANCE = new NetworkManager();
private Vector pending = new Vector();
private boolean running;
private int threadCount = 1;
private NetworkThread[] networkThreads;
private EventDispatcher errorListeners;
private EventDispatcher progressListeners;
private int timeout = 300000;
private Hashtable threadAssignements = new Hashtable();
private Hashtable userHeaders;
private NetworkManager() {
}
private boolean handleException(ConnectionRequest r, Exception o) {
if(errorListeners != null) {
ActionEvent ev = new NetworkEvent(r, o);
errorListeners.fireActionEvent(ev);
return ev.isConsumed();
}
return false;
}
/**
* The number of threads
*
* @return the threadCount
*/
public int getThreadCount() {
return threadCount;
}
/**
* Thread count should never be changed when the network is running since it will have no effect.
* Increasing the thread count can bring many race conditions and problems to the surface,
* furthermore MIDP doesn't require support for more than one network thread hence increasing
* the thread count might fail.
*
* @param threadCount the threadCount to set
*/
public void setThreadCount(int threadCount) {
this.threadCount = threadCount;
}
class NetworkThread implements Runnable {
private ConnectionRequest currentRequest;
private Thread threadInstance;
public NetworkThread() {
}
public ConnectionRequest getCurrentRequest() {
return currentRequest;
}
public void start() {
IOImplementation.getInstance().startThread("Network Thread", this);
}
public void interrupt() {
if(threadInstance != null) {
threadInstance.interrupt();
}
}
public Thread getThreadInstance() {
return threadInstance;
}
public void run() {
threadInstance = Thread.currentThread();
while(running) {
if(pending.size() > 0) {
// the synchronization here isn't essential, only for good measure
synchronized(LOCK) {
//double lock to prevent a potential exception
if(pending.size() == 0){
continue;
}
currentRequest = (ConnectionRequest)pending.elementAt(0);
pending.removeElementAt(0);
currentRequest.prepare();
if(currentRequest.isKilled()){
continue;
}
}
if(userHeaders != null) {
Enumeration e = userHeaders.keys();
while(e.hasMoreElements()) {
String key = (String)e.nextElement();
String value = (String)userHeaders.get(key);
currentRequest.addRequestHeaderDontRepleace(key, value);
}
}
if(threadAssignements.size() > 0) {
String n = currentRequest.getClass().getName();
Integer threadOffset = (Integer)threadAssignements.get(n);
if(threadOffset != null && networkThreads[threadOffset.intValue()] != this) {
synchronized(LOCK) {
if(pending.size() > 0) {
pending.insertElementAt(currentRequest, 1);
continue;
}
pending.addElement(currentRequest);
LOCK.notify();
try {
LOCK.wait(30);
} catch (InterruptedException ex) {
ex.printStackTrace();
}
}
}
}
int frameRate = -1;
try {
// for higher priority tasks increase the thread priority, for lower
// prioirty tasks decrease it. In critical priority reduce the LWUIT
// rendering thread speed for even faster download
switch(currentRequest.getPriority()) {
case ConnectionRequest.PRIORITY_CRITICAL:
frameRate = Display.getInstance().getFrameRate();
Display.getInstance().setFramerate(4);
Thread.currentThread().setPriority(Thread.MAX_PRIORITY - 1);
break;
case ConnectionRequest.PRIORITY_HIGH:
Thread.currentThread().setPriority(Thread.NORM_PRIORITY + 2);
break;
case ConnectionRequest.PRIORITY_NORMAL:
break;
case ConnectionRequest.PRIORITY_LOW:
Thread.currentThread().setPriority(Thread.MIN_PRIORITY + 2);
break;
case ConnectionRequest.PRIORITY_REDUNDANT:
Thread.currentThread().setPriority(Thread.MIN_PRIORITY);
break;
}
if(progressListeners != null) {
progressListeners.fireActionEvent(new NetworkEvent(currentRequest, NetworkEvent.PROGRESS_TYPE_INITIALIZING));
}
if(currentRequest.getShowOnInit() != null) {
currentRequest.getShowOnInit().showModeless();
}
currentRequest.performOperation();
} catch(IOException e) {
if(!handleException(currentRequest, e)) {
currentRequest.handleIOException(e);
}
} catch(RuntimeException er) {
if(!handleException(currentRequest, er)) {
currentRequest.handleRuntimeException(er);
}
} finally {
Thread.currentThread().setPriority(Thread.NORM_PRIORITY);
if(frameRate > -1) {
Display.getInstance().setFramerate(frameRate);
}
if(progressListeners != null) {
progressListeners.fireActionEvent(new NetworkEvent(currentRequest, NetworkEvent.PROGRESS_TYPE_COMPLETED));
}
if(currentRequest.getDisposeOnCompletion() != null) {
// there may be a race condition where the dialog hasn't yet appeared but the
// network request completed
while(Display.getInstance().getCurrent() != currentRequest.getDisposeOnCompletion()) {
try {
Thread.sleep(10);
} catch (InterruptedException ex) {
ex.printStackTrace();
}
}
currentRequest.getDisposeOnCompletion().dispose();
}
}
currentRequest = null;
// wakeup threads waiting for the completion of this network operation
synchronized(LOCK) {
LOCK.notifyAll();
}
} else {
synchronized(LOCK) {
try {
// prevent waiting when there is still a pending request
// this can occur with a race condition since the synchronize
// scope is limited to prevent blocking on add...
if(pending.size() == 0) {
LOCK.wait();
}
} catch (InterruptedException ex) {
ex.printStackTrace();
}
}
}
}
}
}
boolean hasProgressListeners() {
return progressListeners != null;
}
void fireProgressEvent(ConnectionRequest c, int type, int length, int sentReceived) {
// progressListeners might be made null by a separate thread
EventDispatcher d = progressListeners;
if(d != null) {
NetworkEvent n = new NetworkEvent(c, type);
n.setLength(length);
n.setSentReceived(sentReceived);
d.fireActionEvent(n);
}
}
private NetworkThread createNetworkThread() {
return new NetworkThread();
}
/**
* Invoked to initialize the network thread and start executing elements on the queue
*/
public void start() {
if(networkThreads != null) {
//throw new IllegalStateException("Network manager already initialized");
return;
}
running = true;
networkThreads = new NetworkThread[getThreadCount()];
for(int iter = 0 ; iter < getThreadCount() ; iter++) {
networkThreads[iter] = createNetworkThread();
networkThreads[iter].start();
}
// we need to implement a timeout thread of our own for this case...
if(!IOImplementation.getInstance().isTimeoutSupported()) {
IOImplementation.getInstance().startThread("Timeout Thread", new Runnable() {
public void run() {
// detect timeout violations by polling
while(running) {
try {
Thread.sleep(timeout / 10);
} catch (InterruptedException ex) {
ex.printStackTrace();
}
// check for timeout violations on the currently executing threads
for(int iter = 0 ; iter < networkThreads.length ; iter++) {
ConnectionRequest c = networkThreads[iter].getCurrentRequest();
if(c != null) {
int cTimeout = Math.min(timeout, c.getTimeout());
if(c.getTimeSinceLastActivity() > cTimeout) {
// we have a timeout problem on our hands! We need to try and kill!
c.kill();
networkThreads[iter].interrupt();
try {
Thread.sleep(500);
} catch (InterruptedException ex) {
ex.printStackTrace();
}
// did the attempt work?
if(networkThreads[iter].getCurrentRequest() == c) {
if(c.getTimeSinceLastActivity() > cTimeout) {
// we need to create a whole new network thread and abandon this one!
if(running) {
networkThreads[iter] = createNetworkThread();
networkThreads[iter].start();
}
}
}
}
}
}
}
}
});
}
}
/**
* Shuts down the network thread
*/
public void shutdown() {
running = false;
}
/**
* Returns the singleton instance of this class
*
* @return instance of this class
*/
public static NetworkManager getInstance() {
return INSTANCE;
}
private void addSortedToQueue(ConnectionRequest request, int priority) {
for(int iter = 0 ; iter < pending.size() ; iter++) {
ConnectionRequest r = (ConnectionRequest)pending.elementAt(iter);
if(r.getPriority() < priority) {
pending.insertElementAt(request, iter);
return;
}
}
pending.addElement(request);
}
/**
* Adds a header to the global default headers, this header will be implicitly added
* to all requests going out from this point onwards. The main use case for this is
* for authentication information communication via the header.
*
* @param key the key of the header
* @param value the value of the header
*/
public void addDefaultHeader(String key, String value) {
if(userHeaders == null) {
userHeaders = new Hashtable();
}
userHeaders.put(key, value);
}
/**
* Identical to add to queue but waits until the request is processed in the queue,
* this is useful for completely synchronous operations.
*
* @param request the request object to add
*/
public void addToQueueAndWait(final ConnectionRequest request) {
class WaitingClass implements Runnable, ActionListener {
private boolean finishedWaiting;
public void run() {
while(!finishedWaiting) {
try {
Thread.sleep(30);
} catch (InterruptedException ex) {
ex.printStackTrace();
}
}
}
public void actionPerformed(ActionEvent evt) {
NetworkEvent e = (NetworkEvent)evt;
if(e.getConnectionRequest() == request) {
if(e.getProgressType() == NetworkEvent.PROGRESS_TYPE_COMPLETED) {
finishedWaiting = true;
removeProgressListener(this);
return;
}
}
}
}
WaitingClass w = new WaitingClass();
addProgressListener(w);
addToQueue(request);
if(Display.getInstance().isEdt()) {
Display.getInstance().invokeAndBlock(w);
} else {
w.run();
}
}
/**
* Adds the given network connection to the queue of execution
*
* @param request network request for execution
*/
public void addToQueue(ConnectionRequest request) {
addToQueue(request, false);
}
/**
* Kills the given request and waits until the request is killed if it is
* being processed by one of the threads. This method must not be invoked from
* a network thread or a LWUIT thread!
* @param request
*/
public void killAndWait(ConnectionRequest request) {
request.kill();
for(int iter = 0 ; iter < threadCount ; iter++) {
if(networkThreads[iter].currentRequest == request) {
synchronized(LOCK) {
while(networkThreads[iter].currentRequest == request) {
try {
LOCK.wait(20);
} catch (InterruptedException ex) {
ex.printStackTrace();
}
}
}
}
}
}
/**
* Adds the given network connection to the queue of execution
*
* @param request network request for execution
*/
void addToQueue(ConnectionRequest request, boolean retry) {
if(!running) {
System.out.println("Warning: Network queue wasn't started!");
}
request.validateImpl();
synchronized(LOCK) {
int i = request.getPriority();
if(!retry) {
if(!request.isDuplicateSupported()) {
if(pending.contains(request)) {
System.out.println("Duplicate entry in the queue: " + request.getClass().getName() + ": " + request);
return;
}
ConnectionRequest currentRequest = networkThreads[0].getCurrentRequest();
if(currentRequest != null && currentRequest.equals(request)) {
System.out.println("Duplicate entry detected");
return;
}
}
} else {
i = ConnectionRequest.PRIORITY_HIGH;
}
switch(i) {
case ConnectionRequest.PRIORITY_CRITICAL:
pending.insertElementAt(request, 0);
ConnectionRequest currentRequest = networkThreads[0].getCurrentRequest();
if(currentRequest != null && currentRequest.getPriority() < ConnectionRequest.PRIORITY_CRITICAL) {
if(currentRequest.isPausable()) {
currentRequest.pause();
pending.insertElementAt(currentRequest, 1);
} else {
currentRequest.kill();
}
}
break;
case ConnectionRequest.PRIORITY_HIGH:
case ConnectionRequest.PRIORITY_NORMAL:
case ConnectionRequest.PRIORITY_LOW:
case ConnectionRequest.PRIORITY_REDUNDANT:
addSortedToQueue(request, i);
break;
}
LOCK.notify();
}
}
/**
* Sets the timeout in milliseconds for network connections, a timeout may be "faked"
* for platforms that don't support the notion of a timeout such as MIDP
*
* @param t the timeout duration
*/
public void setTimeout(int t) {
if(IOImplementation.getInstance().isTimeoutSupported()) {
IOImplementation.getInstance().setTimeout(t);
} else {
timeout = t;
}
}
/**
* Returns the timeout duration
*
* @return timeout in milliseconds
*/
public int getTimeout() {
return timeout;
}
/**
* Adds a generic listener to a network error that is invoked before the exception is propogated.
* Notice that this doesn't apply to server error codes!
* Consume the event in order to prevent it from propogating further.
*
* @param e callback will be invoked with the Exception as the source object
*/
public void addErrorListener(ActionListener e) {
if(errorListeners == null) {
errorListeners = new EventDispatcher();
errorListeners.setBlocking(true);
}
errorListeners.addListener(e);
}
/**
* Removes the given error listener
*
* @param e callback to remove
*/
public void removeErrorListener(ActionListener e) {
if(errorListeners == null) {
return;
}
errorListeners.removeListener(e);
}
/**
* Adds a listener to be notified when progress updates
*
* @param al action listener
*/
public void addProgressListener(ActionListener al) {
if(progressListeners == null) {
progressListeners = new EventDispatcher();
progressListeners.setBlocking(false);
}
progressListeners.addListener(al);
}
/**
* Adds a listener to be notified when progress updates
*
* @param al action listener
*/
public void removeProgressListener(ActionListener al) {
if(progressListeners == null) {
return;
}
progressListeners.removeListener(al);
Vector v = progressListeners.getListenerVector();
if(v == null || v.size() == 0) {
progressListeners = null;
}
}
/**
* Makes sure the given class (subclass of ConnectionRequest) is always assigned
* to the given thread number. This is useful for a case of an application that wants
* all background downloads to occur on one thread so it doesn't tie up the main
* network thread (but doesn't stop like a low priority request would).
*
* @param requestType the class of the specific connection request
* @param offset the offset of the thread starting from 0 and smaller than thread count
*/
public void assignToThread(Class requestType, int offset) {
threadAssignements.put(requestType.getName(), new Integer(offset));
}
/**
* This method returns all pending ConnectioRequest connections.
* @return the queue elements
*/
public Enumeration enumurateQueue(){
Vector elements = new Vector();
synchronized(LOCK) {
Enumeration e = pending.elements();
while(e.hasMoreElements()){
elements.addElement(e.nextElement());
}
}
return elements.elements();
}
}