/*
*
*
* 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;
import java.util.Vector;
import javax.microedition.content.ActionNameMap;
import javax.microedition.content.ContentHandler;
import javax.microedition.content.Invocation;
import javax.microedition.content.RequestListener;
/**
* The internal structure of a registered content handler.
*/
public class ContentHandlerImpl extends ContentHandlerRegData
implements ContentHandler {
public static interface Handle {
public static interface Receiver {
void push( Handle handle );
}
/**
* Content Handler fields indexes.
* <BR>Used with functions: @link findHandler(), @link getValues() and
* @link getArrayField().
* <BR> They should match according enums in jsr211_registry.h
*/
static final int FIELD_ID = 0; /** Handler ID */
static final int FIELD_TYPES = 1; /** Types supported by a handler */
static final int FIELD_SUFFIXES = 2; /** Suffixes supported */
/** by a handler */
static final int FIELD_ACTIONS = 3; /** Actions supported */
/** by a handler */
static final int FIELD_LOCALES = 4; /** Locales supported */
/** by a handler */
static final int FIELD_ACTION_MAP = 5; /** Handler action map */
static final int FIELD_ACCESSES = 6; /** Access list */
static final int FIELD_COUNT = 7; /** Total number of fields */
String getID();
//int getSuiteId();
/**
* Returns array field
* @param fieldId index of field. Allowed:
* @link FIELD_TYPES, @link FIELD_SUFFIXES, @link FIELD_ACTIONS
* @link FIELD_LOCALES, @link FIELD_ACTION_MAP, @link FIELD_ACCESSES
* values.
* @return array of values
*/
String[] getArrayField(int fieldId);
ContentHandlerImpl get();
}
/**
* handle of registered content handler.
*/
final protected Handle handle;
final protected ApplicationID applicationID;
// /**
// * The MIDlet suite storagename that contains the MIDlet.
// */
// protected int storageId;
//
// /**
// * The application class name that implements this content
// * handler. Note: Only the application that registered the class
// * will see the classname; for other applications the value will be
// * <code>null</code>.
// */
// protected String classname;
/** Count of requests retrieved via {@link #getRequest}. */
protected int requestCalls;
/**
* The RequestListenerImpl; if a listener is set.
*/
RequestListenerImpl listenerImpl;
/** Property name for the current locale. */
private final static String LOCALE_PROP = "microedition.locale";
/**
* The Name from parsing the Property for the MIDlet
* with this classname.
*/
String appname;
/**
* The Version parsed from MIDlet-Version attribute.
*/
String version;
/**
* The authority that authenticated this ContentHandler.
*/
String authority;
/**
* Initialize a new instance with the same information.
* @param handler another ContentHandlerImpl
* @see com.sun.j2me.content.ContentHandlerServerImpl
*/
protected ContentHandlerImpl(ContentHandlerImpl handler) {
super( handler );
handle = handler.handle;
applicationID = handler.applicationID;
listenerImpl = handler.listenerImpl;
version = handler.version;
requestCalls = handler.requestCalls;
authority = handler.authority;
appname = handler.appname;
}
protected ContentHandlerImpl( ApplicationID appID, Handle handle ){
this.applicationID = appID;
this.handle = handle;
}
/**
* Get the nth type supported by the content handler.
* @param index the index into the types
* @return the nth type
* @exception IndexOutOfBounds if index is less than zero or
* greater than or equal to the value of the
* {@link #getTypeCount getTypeCount} method.
*/
public String getType(int index) {
return get(index, getTypes());
}
/**
* Get the number of types supported by the content handler.
*
* @return the number of types
*/
public int getTypeCount() {
return getTypes().length;
}
/**
* Get types supported by the content handler.
*
* @return array of types supported
*/
public String[] getTypes() {
if (types == null) {
types = handle.getArrayField(Handle.FIELD_TYPES);
}
return types;
}
/**
* Determine if a type is supported by the content handler.
*
* @param type the type to check for
* @return <code>true</code> if the type is supported;
* <code>false</code> otherwise
* @exception NullPointerException if <code>type</code>
* is <code>null</code>
*/
public boolean hasType(String type) {
return has(type, getTypes(), true);
}
/**
* Get the nth suffix supported by the content handler.
* @param index the index into the suffixes
* @return the nth suffix
* @exception IndexOutOfBounds if index is less than zero or
* greater than or equal to the value of the
* {@link #getSuffixCount getSuffixCount} method.
*/
public String getSuffix(int index) {
return get(index, getSuffixes());
}
/**
* Get the number of suffixes supported by the content handler.
*
* @return the number of suffixes
*/
public int getSuffixCount() {
return getSuffixes().length;
}
/**
* Determine if a suffix is supported by the content handler.
*
* @param suffix the suffix to check for
* @return <code>true</code> if the suffix is supported;
* <code>false</code> otherwise
* @exception NullPointerException if <code>suffix</code>
* is <code>null</code>
*/
public boolean hasSuffix(String suffix) {
return has(suffix, getSuffixes(), true);
}
/**
* Get suffixes supported by the content handler.
*
* @return array of suffixes supported
*/
public String[] getSuffixes() {
if (suffixes == null) {
suffixes = handle.getArrayField(Handle.FIELD_SUFFIXES);
}
return suffixes;
}
/**
* Get the nth action supported by the content handler.
* @param index the index into the actions
* @return the nth action
* @exception IndexOutOfBounds if index is less than zero or
* greater than or equal to the value of the
* {@link #getActionCount getActionCount} method.
*/
public String getAction(int index) {
return get(index, getActions());
}
/**
* Get the number of actions supported by the content handler.
*
* @return the number of actions
*/
public int getActionCount() {
return getActions().length;
}
/**
* Determine if a action is supported by the content handler.
*
* @param action the action to check for
* @return <code>true</code> if the action is supported;
* <code>false</code> otherwise
* @exception NullPointerException if <code>action</code>
* is <code>null</code>
*/
public boolean hasAction(String action) {
return has(action, getActions(), false);
}
/**
* Get actions supported by the content handler.
*
* @return array of actions supported
*/
public String[] getActions() {
if (actions == null) {
actions = handle.getArrayField(Handle.FIELD_ACTIONS);
}
return actions;
}
/**
* Gets the value at index in the string array.
* @param index of the value
* @param strings array of strings to get from
* @return string at index.
* @exception IndexOutOfBounds if index is less than zero or
* greater than or equal length of the array.
*/
static private String get(int index, String[] strings) {
if (index < 0 || index >= strings.length) {
throw new IndexOutOfBoundsException();
}
return strings[index];
}
/**
* Determines if the string is in the array.
* @param string to locate
* @param strings array of strings to get from
* @param ignoreCase true to ignore case in matching
* @return <code>true</code> if the value is found
* @exception NullPointerException if <code>string</code>
* is <code>null</code>
*/
static private boolean has(String string, String[] strings, boolean ignoreCase) {
int len = string.length(); // Throw NPE if null
for (int i = 0; i < strings.length; i++) {
if (strings[i].length() == len &&
string.regionMatches(ignoreCase, 0, strings[i], 0, len)) {
return true;
}
}
return false;
}
/**
* Get the mapping of actions to action names for the current
* locale supported by this content handler. The behavior is
* the same as invoking {@link #getActionNameMap} with the current
* locale.
*
* @return an ActionNameMap; if there is no map available for the
* current locale, then it MUST be <code>null</code>
*/
public ActionNameMap getActionNameMap() {
String locale = System.getProperty(LOCALE_PROP);
return (locale == null) ? null : getActionNameMap(locale);
}
/**
* Get the mapping of actions to action names for the requested
* locale supported by this content handler.
* The locale is matched against the available locales.
* If a match is found it is used. If an exact match is
* not found, then the locale string is shortened and retried
* if either of the "_" or "-" delimiters is present.
* The locale is shortened by retaining only the characters up to
* but not including the last occurrence of the delimiter
* (either "_" or "-").
* The shortening and matching is repeated as long as the string
* contains one of the delimiters.
* Effectively, this will try the full locale and then try
* without the variant or country code, if they were present.
*
* @param locale for which to find an ActionNameMap;
* MUST NOT be <code>null</code>
* @return an ActionNameMap; if there is no map available for the
* locale, then it MUST be <code>null</code>
* @exception NullPointerException if the locale is <code>null</code>
*/
public ActionNameMap getActionNameMap(String locale) {
while (locale.length() > 0) {
for (int i = 0; i < getActionNames().length; i++) {
if (locale.equals(getActionNames()[i].getLocale())) {
return getActionNames()[i];
}
}
int lastdash = locale.lastIndexOf('-');
if (lastdash < 0) {
break;
}
locale = locale.substring(0, lastdash);
}
return null;
}
/**
* Gets the number of action name maps supported by the content handler.
*
* @return the number of action name maps
*/
public int getActionNameMapCount() {
return getActionNames().length;
}
/**
* Gets the n<sup>th</sup> ActionNameMap supported by the
* content handler.
* @param index the index of the locale
* @return the n<sup>th</sup> ActionNameMap
*
* @exception IndexOutOfBoundsException if index is less than zero or
* greater than or equal to the value of the
* {@link #getActionNameMapCount getActionNameMapCount} method.
*/
public ActionNameMap getActionNameMap(int index) {
if (index < 0 || index >= getActionNames().length) {
throw new IndexOutOfBoundsException();
}
return getActionNames()[index];
}
/**
* Get actions names for the content handler.
*
* @return array of actions names
*/
private ActionNameMap[] getActionNames() {
if (actionnames == null) {
String[] locales = handle.getArrayField(Handle.FIELD_LOCALES);
String[] names = handle.getArrayField(Handle.FIELD_ACTION_MAP);
actionnames = new ActionNameMap[locales.length];
for (int index = 0; index < locales.length; index++) {
String[] temp = new String[getActions().length];
System.arraycopy(names,
index * getActions().length,
temp,
0,
getActions().length);
actionnames[index] = new ActionNameMap(getActions(),
temp,
locales[index]);
}
}
return actionnames;
}
/**
* Returns the name used to present this content handler to a user.
* The value is extracted from the normal installation information
* about the content handler application.
*
* @return the user-friendly name of the application;
* it MUST NOT be <code>null</code>
*/
public String getAppName() {
loadAppData();
return appname;
}
/**
* Gets the version number of this content handler.
* The value is extracted from the normal installation information
* about the content handler application.
* @return the version number of the application;
* MAY be <code>null</code>
*/
public String getVersion() {
loadAppData();
return version;
}
/**
* Gets the name of the authority that authorized this application.
* This value MUST be <code>null</code> unless the device has been
* able to authenticate this application.
* If <code>non-null</code>, it is the string identifying the
* authority. For example,
* if the application was a signed MIDlet, then this is the
* "subject" of the certificate used to sign the application.
* <p>The format of the authority for X.509 certificates is defined
* by the MIDP Printable Representation of X.509 Distinguished
* Names as defined in class
* <code>javax.microedition.pki.Certificate</code>. </p>
*
* @return the authority; may be <code>null</code>
*/
public String getAuthority() {
loadAppData();
return authority;
}
/**
* Initializes fields retrieved from AppProxy 'by-demand'.
*/
private void loadAppData() {
if (appname == null) {
try {
AppProxy app = AppProxy.getCurrent().forApp(applicationID);
appname = app.getApplicationName();
version = app.getVersion();
authority = app.getAuthority();
} catch (Throwable t) {
}
if (appname == null) {
appname = "";
}
}
}
/**
* Gets the n<sup>th</sup> ID of an application or content handler
* allowed access to this content handler.
* The ID returned for each index must be the equal to the ID
* at the same index in the <tt>accessAllowed</tt> array passed to
* {@link javax.microedition.content.Registry#register Registry.register}.
*
* @param index the index of the ID
* @return the n<sup>th</sup> ID
* @exception IndexOutOfBoundsException if index is less than zero or
* greater than or equal to the value of the
* {@link #accessAllowedCount accessAllowedCount} method.
*/
public String getAccessAllowed(int index) {
return get(index, getAccessRestricted());
}
/**
* Gets the number of IDs allowed access by the content handler.
* The number of IDs must be equal to the length of the array
* of accessRestricted passed to
* {@link javax.microedition.content.Registry#register Registry.register}.
* If the number of IDs is zero then all applications and
* content handlers are allowed access.
*
* @return the number of accessRestricteds
*/
public int accessAllowedCount() {
return getAccessRestricted().length;
}
/**
* Determines if an ID MUST be allowed access by the content handler.
* Access MUST be allowed if the ID has a prefix that exactly matches
* any of the IDs returned by {@link #getAccessAllowed}.
* The prefix comparison is equivalent to
* <code>java.lang.String.startsWith</code>.
*
* @param ID the ID for which to check access
* @return <code>true</code> if access MUST be allowed by the
* content handler;
* <code>false</code> otherwise
* @exception NullPointerException if <code>accessRestricted</code>
* is <code>null</code>
*/
public boolean isAccessAllowed(String ID) {
ID.length(); // check for null
if (getAccessRestricted().length == 0) {
return true;
}
for (int i = 0; i < getAccessRestricted().length; i++) {
if (ID.startsWith(getAccessRestricted()[i])) {
return true;
}
}
return false;
}
/**
* Get accesses for the content handler.
*
* @return array of allowed class names
*/
public String[] getAccessRestricted() {
if (accessRestricted == null) {
accessRestricted = handle.getArrayField(Handle.FIELD_ACCESSES);
}
return accessRestricted;
}
/**
* Finish this Invocation and set the status for the response.
* The <code>finish</code> method may only be called when this
* Invocation
* has a status of <code>ACTIVE</code> or <code>HOLD</code>.
* <p>
* The content handler may modify the URL, type, action, or
* arguments before invoking <code>finish</code>.
* If the method {@link Invocation#getResponseRequired} returns
* <code>true</code> then the modified
* values MUST be returned to the invoking application.
*
* @param invoc the Invocation to finish
* @param status the new status of the Invocation. This MUST be either
* <code>OK</code> or <code>CANCELLED</code>.
*
* @return <code>true</code> if the MIDlet suite MUST
* voluntarily exit before the response can be returned to the
* invoking application
*
* @exception IllegalArgumentException if the new
* <code>status</code> of the Invocation
* is not <code>OK</code> or <code>CANCELLED</code>
* @exception IllegalStateException if the current
* <code>status</code> of the
* Invocation is not <code>ACTIVE</code> or <code>HOLD</code>
* @exception NullPointerException if the invocation is <code>null</code>
*/
protected boolean finish(InvocationImpl invoc, int status) {
int currst = invoc.getStatus();
if (currst != Invocation.ACTIVE && currst != Invocation.HOLD) {
throw new IllegalStateException("Status already set");
}
// If ACTIVE or HOLD it must be an InvocationImpl
return invoc.finish(status);
}
/**
* Set the listener to be notified when a new request is
* available for this content handler. The request MUST
* be retrieved using {@link #getRequest}.
*
* @param listener the listener to register;
* <code>null</code> to remove the listener.
*/
public void setListener(RequestListener listener) {
synchronized (this) {
if (listener != null || listenerImpl != null) {
// Create or update the active listener thread
if (listenerImpl == null) {
listenerImpl = new RequestListenerImpl(this, listener);
} else {
listenerImpl.setListener(listener);
}
// If the listener thread no longer needed; clear it
if (listener == null) {
listenerImpl = null;
}
}
}
}
/**
* Notify the request listener of a pending request.
* Overridden by subclass.
*/
protected void requestNotify() {
}
// /**
// * Compare two ContentHandlerImpl's for equality.
// * Classname, storageID, and seqno must match.
// * @param other another ContentHandlerImpl
// * @return true if the other handler is for the same class,
// * storageID, and seqno.
// */
// boolean equals(ContentHandlerImpl other) {
// return storageId == other.storageId && classname.equals(other.classname);
// }
/**
* Debug routine to print the values.
* @return a string with the details
*/
public String toString() {
if (AppProxy.LOGGER != null) {
StringBuffer sb = new StringBuffer(80);
sb.append("CH:");
sb.append(", appID: ");
sb.append(applicationID);
sb.append(", removed: ");
sb.append(", flag: ");
sb.append(registrationMethod);
sb.append(", types: ");
toString(sb, types);
sb.append(", ID: ");
sb.append(ID);
sb.append(", suffixes: ");
toString(sb, suffixes);
sb.append(", actions: ");
toString(sb, actions);
sb.append(", access: ");
toString(sb, accessRestricted);
sb.append(", authority: ");
sb.append(authority);
sb.append(", appname: ");
sb.append(appname);
return sb.toString();
} else {
return super.toString();
}
}
/**
* Append all of the strings in the array to the string buffer.
* @param sb a StringBuffer to append to
* @param strings an array of strings.
*/
private void toString(StringBuffer sb, String[] strings) {
if (strings == null) {
sb.append("null");
return;
}
for (int i = 0; i < strings.length; i++) {
if (i > 0) {
sb.append(':');
}
sb.append(strings[i]);
}
}
}
//-----------------------------------------------------
class HandlerNameFinder implements ContentHandlerImpl.Handle.Receiver {
static class FoundException extends RuntimeException {
public ContentHandlerImpl.Handle handle;
FoundException(ContentHandlerImpl.Handle handle) {
this.handle = handle;
}
}
private String handlerID;
private boolean exact;
HandlerNameFinder(String handlerID, boolean exact) {
this.handlerID = handlerID;
this.exact = exact;
}
public void push(ContentHandlerImpl.Handle handle) {
if( exact ){
if( handle.getID().equals(handlerID) )
throw new FoundException( handle );
} else if( handle.getID().startsWith(handlerID) )
throw new FoundException( handle );
}
}
abstract class HandlerFilter implements ContentHandlerImpl.Handle.Receiver {
protected ContentHandlerImpl.Handle.Receiver output;
protected HandlerFilter( ContentHandlerImpl.Handle.Receiver output ){
this.output = output;
}
}
class HandlerNameFilter extends HandlerFilter {
private String testID;
protected HandlerNameFilter(String testID, ContentHandlerImpl.Handle.Receiver r) {
super( r );
this.testID = testID;
}
public void push(ContentHandlerImpl.Handle handle) {
if( handle.getID().startsWith(testID) || testID.startsWith(handle.getID()) )
output.push(handle);
}
}
//class HandlerSuiteIDFilter extends HandlerFilter {
// private int suiteId;
//
// HandlerSuiteIDFilter( int suiteId, ContentHandlerImpl.Handle.Receiver r ){
// super( r );
// this.suiteId = suiteId;
// }
//
// public void push(ContentHandlerImpl.Handle handle) {
// if( handle.getSuiteId() == suiteId )
// output.push(handle);
// }
//}
class HandlerTypeFilter extends HandlerFilter {
private String type;
protected HandlerTypeFilter(String type, ContentHandlerImpl.Handle.Receiver r) {
super( r );
this.type = type;
}
public void push(ContentHandlerImpl.Handle handle) {
if( handle.get().hasType(type) )
output.push(handle);
}
}
class HandlerActionFilter extends HandlerFilter {
private String action;
protected HandlerActionFilter(String action, ContentHandlerImpl.Handle.Receiver r) {
super( r );
this.action = action;
}
public void push(ContentHandlerImpl.Handle handle) {
if( handle.get().hasAction(action) )
output.push(handle);
}
}
class HandlerSuffixFilter extends HandlerFilter {
private String suffix;
protected HandlerSuffixFilter(String suffix, ContentHandlerImpl.Handle.Receiver r) {
super( r );
this.suffix = suffix;
}
public void push(ContentHandlerImpl.Handle handle) {
if( handle.get().hasSuffix(suffix) )
output.push(handle);
}
}
class HandlerAccessFilter extends HandlerFilter {
private String callerId;
public HandlerAccessFilter(String callerId, ContentHandlerImpl.Handle.Receiver r) {
super( r );
this.callerId = callerId;
}
public void push(ContentHandlerImpl.Handle handle) {
if( handle.get().isAccessAllowed(callerId) )
output.push(handle);
}
}
class HandlersCollection implements ContentHandlerImpl.Handle.Receiver {
Vector/*<ContentHandlerImpl>*/ vector = new Vector();
public void push(ContentHandlerImpl.Handle handle) {
vector.addElement( handle.get() );
}
public ContentHandlerImpl[] getArray() {
ContentHandlerImpl[] result = new ContentHandlerImpl[ vector.size() ];
vector.copyInto(result);
return result;
}
}