/* * * * Copyright 1990-2009 Sun Microsystems, Inc. All Rights Reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER * * This program 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. * * This program 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 at /legal/license.txt). * * 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 Sun Microsystems, Inc., 4150 Network Circle, Santa * Clara, CA 95054 or visit www.sun.com if you need additional * information or have any questions. */ package com.sun.j2me.content; /** * The store for pending Invocations. * New Invocations are queued with {@link #put} method and * retrieved with the {@link #get} method. The {@link #cancel} * method is used to unblock calls to blocking {@link #get} methods. * <p> * Synchronization is performed by the native methods; access * is serialized by the VM running in a single native thread and * by NOT preempting native method calls. * The native code uses the SNI ability to block a thread and * unblock it at a later time. The implementation does not poll for * requests but blocks, if requested, until it is unblocked. */ public class InvocationStore { /** The mode for get to retrieve a new request. */ private static final int MODE_REQUEST = 0; /** The mode for get to retrieve a new response. */ private static final int MODE_RESPONSE = 1; /** The mode for get to retrieve a new cleanup. */ private static final int MODE_CLEANUP = 2; /** The mode for listen for new unmarked request. */ private static final int MODE_LREQUEST = 3; /** The mode for listen for a new unmarked response. */ private static final int MODE_LRESPONSE = 4; /** The mode for get to retrieve byte <code>tid</code>. */ private static final int MODE_TID = 6; /** The mode to get the Invocation after <code>tid</code>. */ private static final int MODE_TID_NEXT = 7; /** * Private constructor to prevent instance creation. */ private InvocationStore() { } /** * Put a new Invocation into the store. * It can be modified by {@link #setStatus}. * The TID (transaction ID) is updated with a newly assigned value. * * @param invoc an InvocationImpl instance with the members properly * initialized. * @see #getRequest * @see #getResponse */ static void put(InvocationImpl invoc) { if (AppProxy.LOGGER != null) { AppProxy.LOGGER.println("Store put0: " + invoc); } put0(invoc); } /** * Get a new InvocationImpl request from the store using a MIDlet * suiteId and classname. * * @param suiteId the MIDlet suiteId to search for * @param classname to match, must not be null * @param shouldBlock true if the method should block * waiting for an Invocation * * @return <code>InvocationImpl</code> if any was found with * the same MIDlet suiteId and classname with * its status is set to ACTIVE; * <code>null</code> is returned if there is no matching Invocation */ static InvocationImpl getRequest(ApplicationID appID, boolean shouldBlock, Counter cancelCounter) { if( AppProxy.LOGGER != null ) AppProxy.LOGGER.println( "InvocationStore.getRequest: " + appID ); CLDCAppID.from(appID).className.length(); // null pointer check return get(CLDCAppID.from(appID), MODE_REQUEST, shouldBlock, cancelCounter); } /** * Get a new InvocationImpl response from the store using a * MIDlet suiteId and classname. * The response is removed from the store. * * @param invoc an InvocationImpl to fill with the response * @param suiteId the MIDletSuite ID * @param classname the classname * @param shouldBlock true if the method should block * waiting for an Invocation * * @return <code>InvocationImpl</code> if any was found with * the same MIDlet suiteId and classname if one was requested; * <code>null</code> is returned if there is no matching Invocation */ static InvocationImpl getResponse(ApplicationID appID, boolean shouldBlock, Counter cancelCounter) { if( AppProxy.LOGGER != null ) AppProxy.LOGGER.println( "InvocationStore.getResponse: " + appID ); CLDCAppID.from(appID).className.length(); // null pointer check return get(CLDCAppID.from(appID), MODE_RESPONSE, shouldBlock, cancelCounter); } /** * Performs cleanup for a ContentHandler * by suiteId and classname. * <p> * Any marked {@link #setCleanup} invocations still in the queue * are handled based on status: * <UL> * <li>ACTIVE Invocations are returned from this method * so they can be have the ERROR status set and so the * invoking application relaunched.</li> * <li>INIT Invocations are requeued to the invoking application * with ERROR status. </li> * <li>OK, CANCELLED, ERROR, or INITIATED Invocations are * discarded.</li> * <li>HOLD status Invocations are retained pending * completion of previous Invocation. TBD: Chained HOLDs...</li> * </ul> * * @param suiteId the MIDletSuite ID * @param classname the classname * * @return <code>InvocationImpl</code> if any was found with * the same MIDlet suiteId and classname; * <code>null</code> is returned if there is no matching Invocation */ static InvocationImpl getCleanup(ApplicationID appID) { return get(CLDCAppID.from(appID), MODE_CLEANUP, false, null); } /** * Get an Invocation from the store based on its <code>tid</code>. * The normal state transitions and dispositions are NOT performed. * If TID == 0 then the first tid is used as the reference. * If TID == 0 and relative == 0 then null is returned. * This method never waits. * * @param tid the <code>tid</code> to fetch * @param relative -1, 0, +1 to get previous, equal, or next * @return an InvocationImpl object if a matching tid was found; * otherwise <code>null</code> */ static InvocationImpl getByTid(int tid, boolean next) { InvocationImpl invoc = new InvocationImpl(); int mode = MODE_TID; if (tid != 0 && next) { mode = MODE_TID_NEXT; } invoc.tid = tid; int s = 0; while ((s = getByTid0(invoc, tid, mode)) == -1) { /* * Sizes of arguments and data buffers were insufficient * reallocate and retry. */ invoc.setArgs(new String[invoc.argsLen]); invoc.setData(new byte[invoc.dataLen]); } // Update the return if no invocation if (s == 0) { invoc = null; } if (AppProxy.LOGGER != null) { AppProxy.LOGGER.println("Store getByTid: (" + tid + "), mode: " + mode + ", " + invoc); } return invoc; } /** * Get an InvocationImpl from the store using a MIDlet suiteId * and classname. * The mode controls whether getting an Invocation * from the store removes it from the store. * * @param invoc InvocationImpl to fill in with result * @param mode one of {@link #MODE_REQUEST}, {@link #MODE_RESPONSE}, * or {@link #MODE_CLEANUP}, {@link #MODE_LREQUEST}, * or {@link #MODE_LRESPONSE}, {@link #MODE_TID}. * @param shouldBlock true if the method should block * waiting for an Invocation * * @return <code>InvocationImpl</code> if any was found with * the same MIDlet suiteId and classname if one was requested; * <code>null</code> is returned if there is no matching Invocation */ private static InvocationImpl get(CLDCAppID appID, int mode, boolean shouldBlock, Counter cancelCounter) { InvocationImpl invoc = new InvocationImpl(); int s = 0; int oldCancelCount = 0; if( shouldBlock ) oldCancelCount = cancelCounter.getCounter(); while ((s = get0(invoc, appID.suiteID, appID.className, mode, shouldBlock)) != 1) { if (s == -1) { /* * Sizes of arguments and data buffers were insufficient * reallocate and retry. */ invoc.setArgs(new String[invoc.argsLen]); invoc.setData(new byte[invoc.dataLen]); continue; } // Don't wait unless requested if (!shouldBlock || oldCancelCount != cancelCounter.getCounter()) { break; } } // Update the return if no invocation if (s == 0) { invoc = null; } if (AppProxy.LOGGER != null) { AppProxy.LOGGER.println("Store get: " + appID + ", mode: " + mode + ", " + invoc); } return invoc; } /** * Listen for a matching invocation. * When a matching invocation is present, true is returned. * Each Invocation instance is only returned once. * After it has been returned once; it is ignored subsequently. * * @param suiteId the MIDlet suiteId to search for, * MUST not be <code>null</code> * @param classname to match, must not be null * @param request true to listen for a request; else a response * @param shouldBlock true if the method should block * waiting for an Invocation * * @return true if a matching invocation is present; false otherwise */ static boolean listen(ApplicationID appID, boolean request, boolean shouldBlock, Counter cancelCounter) { final int mode = (request ? MODE_LREQUEST : MODE_LRESPONSE); boolean pending; int oldCancelCount = 0; CLDCAppID app = CLDCAppID.from(appID); if( shouldBlock ) oldCancelCount = cancelCounter.getCounter(); while (!(pending = listen0(app.suiteID, app.className, mode, shouldBlock)) && shouldBlock && oldCancelCount == cancelCounter.getCounter()) { // No pending request; retry unless canceled } if (AppProxy.LOGGER != null) { AppProxy.LOGGER.println("Store listen: " + appID + ", request: " + request + ", pending: " + pending); } return pending; } /** * Reset the flags for requests or responses that are pending. * Once reset, any pending requests or responses will be * returned when listen0 is called. * * @param suiteId the MIDlet suiteId to search for, * MUST not be <code>null</code> * @param classname to match, must not be null * @param request true to reset request notification flags; * else reset response notification flags */ static void setListenNotify(ApplicationID appID, boolean request) { int mode = (request ? MODE_LREQUEST : MODE_LRESPONSE); CLDCAppID app = CLDCAppID.from(appID); setListenNotify0(app.suiteID, app.className, mode); if (AppProxy.LOGGER != null) { AppProxy.LOGGER.println("Store setListenNotify: " + appID + ", request: " + request); } } /** * Cancel a blocked {@link #get} or {@link #listen} * method if it is blocked in the native code. */ static void cancel() { if( AppProxy.LOGGER != null ) AppProxy.LOGGER.println( "InvocationStore.cancel called." ); cancel0(); } /** * Marks any existing invocations for the content handler. * Any marked invocation will be modified by {@link #getCleanup}. * * @param suiteId the suite to mark * @param classname the MIDlet within the suite * @param cleanup <code>true</code> to mark the Invocation for * cleanup at exit */ static void setCleanup(ApplicationID appID, boolean cleanup) { if (AppProxy.LOGGER != null) { AppProxy.LOGGER.println("Store setCleanup: " + appID + ": " + cleanup); } setCleanup0(CLDCAppID.from(appID).suiteID, CLDCAppID.from(appID).className, cleanup); } /** * Return the number of invocations in the native queue. * @return the number of invocations in the native queue */ static int size() { return size0(); } static void update(InvocationImpl invoc) { if( invoc.tid != InvocationImpl.UNDEFINED_TID ){ if( invoc.status != InvocationImpl.DISPOSE ) update0(invoc); else { dispose0(invoc.tid); invoc.tid = InvocationImpl.UNDEFINED_TID; } } } static void resetFlags(int tid) { resetFlags0(tid); } static void dispose(int tid) { dispose0(tid); } /** * Native method to store a new Invocation. * All of the fields of the InvocationImpl are stored. * @param invoc the InvocationImpl to store */ private static native void put0(InvocationImpl invoc); /** * Native method to fill an available InvocationImpl with an * available stored Invocation with the status (if non-zero), * the suiteId, classname in the prototype InvocationImpl. * Any InvocationImpl with a matching status, suite and * class will be returned. * Depending on the mode the stored invocation will be removed * from the store. * @param invoc the Invocation containing the suiteId and * classname to fill in with an available invocation. * @param suiteId the MIDletSuite ID to match * @param classname the classname to match * @param mode one of {@link #MODE_REQUEST}, {@link #MODE_RESPONSE}, * or {@link #MODE_CLEANUP} * @param shouldBlock True if the method should block until an * Invocation is available * @return 1 if a matching invocation was found and returned * in its entirety; zero if there was no matching invocation; * -1 if the sizes of the arguments or parameter array were wrong * @see #get */ private static native int get0(InvocationImpl invoc, int suiteId, String classname, int mode, boolean shouldBlock); private static native int getByTid0(InvocationImpl invoc, int tid, int mode); /** * Native method to listen for pending invocations with * matching suite, classname, and status. Cancel() will * also cause this method to return if blocked. * Each Invocation will only be returned once to prevent * multiple notifications. * * @param suiteId the MIDletSuite ID to match * @param classname the classname to match * @param mode one of {@link #MODE_LREQUEST}, {@link #MODE_LRESPONSE} * @param shouldBlock true if the method should block until an * Invocation is available * @return true if a matching invocation was found; otherwise false. * @see #get0 */ private static native boolean listen0(int suiteId, String classname, int mode, boolean shouldBlock); /** * Native method to reset the listen notified state for pending * invocations with matching suite, classname and status. * Each Invocation will only be returned once to prevent * multiple notifications. * * @param suiteId the MIDletSuite ID to match * @param classname the classname to match * @param mode one of {@link #MODE_LREQUEST}, {@link #MODE_LRESPONSE} * <code>false</code> to reset the notified state for responses * @see #listen0 */ private static native void setListenNotify0(int suiteId, String classname, int mode); /** * Native method to unblock any threads that might be * waiting for an invocation by way of having called * {@link #get0}. * */ private static native void cancel0(); /** * Sets the cleanup flag in matching Invocations. * Any marked invocation will be modified by {@link #getCleanup}. * * @param suiteId the MIDlet suiteId to search for, * MUST not be <code>null</code> * @param classname to match, must not be null * @param cleanup <code>true</code> to mark the Invocation for * cleanup at exit */ private static native void setCleanup0(int suiteId, String classname, boolean cleanup); /** * Return the number of invocations in the native queue. * @return the number of invocations in the native queue */ private static native int size0(); private static native void update0(InvocationImpl invoc); private static native void resetFlags0(int tid); private static native void dispose0(int tid); }