/*
*
*
* 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.Hashtable;
import java.util.Vector;
import java.util.Enumeration;
import javax.microedition.content.ContentHandlerException;
import javax.microedition.content.ActionNameMap;
import com.sun.midp.installer.InvalidJadException;
/**
* Support for parsing attributes and installing from the
* manifest or application descriptors.
*/
final class RegistryInstaller {
private final AppProxy appl;
/** Attribute prefix for ContentHandler attributes. */
private static final String CH_PREFIX = "MicroEdition-Handler-";
/** Attribute suffix for ContentHandler ID attribute. */
private static final String CH_ID_SUFFIX = "-ID";
/** Attribute suffix for ContentHandler visibility attribute. */
private static final String CH_ACCESS_SUFFIX = "-Access";
/** Parsed handlers to be installed. */
private Hashtable/*<classname, ContentHandlerRegData>*/ handlersToInstall;
/** Old handlers to be removed. */
private Hashtable/*<String, ContentHandlerImpl>*/ handlersToRemove;
RegistryInstaller( AppProxy appl ){
this.appl = appl;
}
/**
* Parse the ContentHandler attributes and check for errors.
* <ul>
* <li> Parse attributes into set of ContentHandlers.
* <li> If none, return
* <li> Check for permission to install handlers
* <li> Check each for simple invalid arguments
* <li> Check each for MIDlet is registered
* <li> Check each for conflicts with other application registrations
* <li> Find any current registrations
* <li> Remove current dynamic registrations from set to be removed
* <li> Check and resolve any conflicts between static and current dynamic
* </ul>
* @param appl the AppProxy context with one or more applications
* @return number of handlers prepared for installation.
* @exception IllegalArgumentException if there is no classname field,
* or if there are more than five comma separated fields on the line.
* @exception NullPointerException if missing components
* @exception ContentHandlerException if handlers are ambiguous
* @exception ClassNotFoundException if an application class cannot be found
* @exception SecurityException if not allowed to register
*/
int preInstall() throws ContentHandlerException, ClassNotFoundException
{
int sz;
int suiteId = appl.suiteID;
ContentHandlerImpl[] chs;
if( AppProxy.LOGGER != null )
AppProxy.LOGGER.println( "RegistryInstaller.preInstall: appl = " + appl );
/*
* Check for any CHAPI attributes;
* if so, then the MIDlet suite must have permission.
*/
handlersToRemove = new Hashtable();
handlersToInstall = parseAttributes(appl);
if( AppProxy.LOGGER != null )
AppProxy.LOGGER.println( "RegistryInstaller.preInstall: handlersToInstall.size = " + handlersToInstall.size() );
/*
* Remove all static registrations. Verify dynamically registered.
*/
chs = RegistryStore.forSuite(suiteId);
sz = (chs == null? 0: chs.length);
if( AppProxy.LOGGER != null )
AppProxy.LOGGER.println( "RegistryInstaller.preInstall: suite " + suiteId +
" handlers number = " + sz );
for (int i = 0; i < sz; i++) {
if( AppProxy.LOGGER != null )
AppProxy.LOGGER.println( "RegistryInstaller.preInstall: chs[" + i + "] = " + chs[i] );
if (chs[i] == null) continue;
if( 0 == (chs[i].registrationMethod &
ContentHandlerImpl.REGISTERED_STATIC_FLAG) ) {
// Verify dynamic handler.
class ReplaceDynamicHandlerException extends Exception {};
try {
String handlerClassName =
CLDCAppID.from(chs[i].applicationID).className;
// is the handler a valid application?
appl.verifyApplication(handlerClassName);
// is there new handler to replace this one?
if( handlersToInstall.containsKey(handlerClassName) )
throw new ReplaceDynamicHandlerException();
// The handler remains.
continue;
} catch( ClassNotFoundException x ) {
// verifyApplication hasn't found handler class in the suite being installed
// Pass down to remove handler
} catch(ReplaceDynamicHandlerException t) {
// Pass down to remove handler
}
}
if( AppProxy.LOGGER != null )
AppProxy.LOGGER.println( "RegistryInstaller.preInstall: mark " + i );
// Remove handler -- either [static] or [replaced] or [invalid]
handlersToRemove.put(chs[i].ID, chs[i]);
chs[i] = null;
}
if( AppProxy.LOGGER != null )
AppProxy.LOGGER.println( getClass().getName() +
".preInstall: handlersToRemove " + handlersToRemove.size() );
/* Verify new registrations */
Vector ids = new Vector();;
Enumeration handlerDataEnum = handlersToInstall.elements();
while (handlerDataEnum.hasMoreElements()) {
ContentHandlerRegData handler =
(ContentHandlerRegData)handlerDataEnum.nextElement();
// Verify ID ...
// ... look through Registry
ContentHandlerImpl[] conf =
RegistryStore.findConflicted(handler.ID);
if (conf != null) {
for (int j = 0; j < conf.length; j++) {
if (conf[j] == null) continue;
if (CLDCAppID.from(conf[j].applicationID).suiteID != suiteId ||
!willRemove(conf[j].ID))
throw new ContentHandlerException(
"Content Handler ID: " + handler.ID,
ContentHandlerException.AMBIGUOUS);
}
}
// ... look through newbies
for( int i = 0; i< ids.size(); i++) {
String otherID = (String)ids.elementAt(i);
if (handler.ID.startsWith(otherID) || otherID.startsWith(handler.ID)) {
throw new ContentHandlerException(
"Content Handler ID: "+handler.ID,
ContentHandlerException.AMBIGUOUS);
}
}
ids.addElement(handler.ID);
}
// Check permission to install handlers
if( handlersToInstall.size() > 0 )
appl.checkRegisterPermission("register");
if( AppProxy.LOGGER != null ){
AppProxy.LOGGER.println( getClass().getName() + ".preInstall: handlersToInstall(" +
handlersToInstall.size() + "):");
Enumeration keys = handlersToInstall.keys();
while( keys.hasMoreElements() ){
String classname = (String)keys.nextElement();
AppProxy.LOGGER.println( "\t[" + classname + "] " +
handlersToInstall.get(classname).toString() );
}
}
return handlersToInstall.size();
}
private boolean willRemove(String ID) {
return handlersToRemove.containsKey(ID);
}
/**
* Parse the ContentHandler attributes and check for errors.
*
* @param appl the AppProxy context with one or more applications
*
* @return a Vector of the ContentHandlers parsed from the attributes
* @throws ClassNotFoundException
*
* @exception IllegalArgumentException if there is no classname field,
* or if there are more than five comma separated fields on the line.
* @exception NullPointerException if missing components
* @exception ContentHandlerException if there are conflicts between
* content handlers
* @exception ClassNotFoundException if an application class cannot be found
*/
private static Hashtable/*<classname, ContentHandlersRegData>*/
parseAttributes(AppProxy appl) throws ClassNotFoundException
{
Hashtable handlers = new Hashtable();
for (int index = 1; ; index++) {
String sindex = Integer.toString(index);
String handler_n = CH_PREFIX.concat(sindex);
String value = appl.getProperty(handler_n);
if(AppProxy.LOGGER != null)
AppProxy.LOGGER.println( "RegistryInstaller.parseAttributes: appl.getProperty(" + handler_n + ") = '" +
value + "'" );
if (value == null)
break;
String[] types = null;
String[] suffixes = null;
String[] actions = null;
String[] locales = null;
String classname;
String[] fields = split(value, ',');
switch (fields.length) {
case 5: // Has locales
locales = split(fields[4], ' ');
// Fall through
case 4: // Has actions
actions = split(fields[3], ' ');
// Fall through
case 3: // Has suffixes
suffixes = split(fields[2], ' ');
// Fall through
case 2: // Has types
// Parse out the types (if any)
types = split(fields[1], ' ');
// Fall through
case 1: // Has classname
classname = fields[0];
if (classname != null && classname.length() > 0) {
// Has non-empty classname
break;
}
// No classname, fall through to throw exception
case 0: // no nothing; error
default: // too many fields, error
throw new IllegalArgumentException("Too many or too few fields");
}
// Get the application info for this new class;
// Throws ClassNotFoundException or IllegalArgumentException
AppProxy newAppl = appl.forClass(classname);
if(AppProxy.LOGGER != null)
AppProxy.LOGGER.println( "RegistryInstaller.parseAttributes: newAppl = " +
newAppl );
ActionNameMap[] actionnames =
parseActionNames(actions, locales, handler_n, newAppl);
// Parse the ID if any and the Access attribute
String idAttr = handler_n.concat(CH_ID_SUFFIX);
String id = newAppl.getProperty(idAttr);
String visAttr = handler_n.concat(CH_ACCESS_SUFFIX);
String visValue = newAppl.getProperty(visAttr);
String[] accessRestricted = split(visValue, ' ');
// Default the ID if not supplied
if (id == null) {
// Generate a unique ID based on the MIDlet suite
id = newAppl.getApplicationID();
}
// Now create the handler data
ContentHandlerRegData handlerData =
new ContentHandlerRegData(
ContentHandlerRegData.REGISTERED_STATIC_FLAG,
types, suffixes, actions, actionnames,
id, accessRestricted);
/* replace another handler information with the same classname */
handlers.put(classname, handlerData);
}
return handlers;
}
/**
* Scan the available properties for the locale specific
* attribute names and parse and The actionname maps for
* each.
* @param actions the actions parsed for the handler
* @param locales the list of locales to check for action names
* @param prefix the prefix of the current handler attribute name
* @param appl the AppProxy context with one or more applications
* @return an array of ActionNameMap's
* @exception IllegalArgumentException if locale is missing
*/
private static ActionNameMap[] parseActionNames(String[] actions,
String[] locales,
String prefix,
AppProxy appl)
{
if (locales == null || locales.length == 0) {
return null;
}
prefix = prefix.concat("-");
Vector maps = new Vector();
for (int i = 0; i < locales.length; i++) {
String localeAttr = prefix.concat(locales[i]);
String localeValue = appl.getProperty(localeAttr);
if (localeValue == null) {
throw new IllegalArgumentException("missing locale");
}
String[] actionnames = split(localeValue, ',');
ActionNameMap map =
new ActionNameMap(actions, actionnames, locales[i]);
maps.addElement(map);
}
if (maps.size() > 0) {
ActionNameMap[] result = new ActionNameMap[maps.size()];
maps.copyInto(result);
return result;
} else {
return null;
}
}
/**
* Split the values in a field by delimiter and return a string array.
* @param string the string containing the values
* @param delim the delimiter that separates the values
* @return a String array of the values; must be null
*/
static String[] split(String string, char delim) {
String[] ret = ContentHandlerImpl.ZERO_STRINGS;
if (string != null) {
Vector values = getDelimSeparatedValues(string, delim);
ret = new String[values.size()];
values.copyInto(ret);
}
return ret;
}
/**
* Create a vector of values from a string containing delimiter separated
* values. The values cannot contain the delimiter. The output values will
* be trimmed of whitespace. The vector may contain zero length strings
* where there are 2 delimiters in a row or a comma at the end of the input
* string.
*
* @param input input string of delimiter separated values
* @param delim the delimiter separating values
* @return vector of string values.
*/
private static Vector getDelimSeparatedValues(String input, char delim) {
Vector output = new Vector(5, 5);
int len;
int start;
int end;
input = input.trim();
len = input.length();
if (len == 0) {
return output;
}
for (start = end = 0; end < len; ) {
// Skip leading spaces and control chars
while (start < len && (input.charAt(start) <= ' ')) {
start += 1;
}
// Scan for end delimiter (tab also if delim is space)
for (end = start; end < len; end++) {
char c = input.charAt(end);
if (c == delim || (c == '\t' && delim == ' ')) {
output.addElement(input.substring(start, end).trim());
start = end + 1;
break;
}
}
}
end = len;
output.addElement(input.substring(start, end).trim());
return output;
}
/**
* Performs static installation (registration) the application
* to handle the specified type and to provide a set of actions.
*
* @exception InvalidJadException if there is a content handlers
* IDs conflict
*/
void install() {
// Remove static and conflicted handlers.
if( AppProxy.LOGGER != null && handlersToRemove != null ){
AppProxy.LOGGER.println( getClass().getName() +
".install: handlersToRemove(" + handlersToRemove.size() + "):");
Enumeration htr = handlersToRemove.keys();
while( htr.hasMoreElements() ) AppProxy.LOGGER.println( "\t" + htr.nextElement() );
}
Enumeration htr = handlersToRemove.keys();
while( htr.hasMoreElements() ) {
RegistryStore.unregister( (String)htr.nextElement() );
}
// Install new handlers.
if( handlersToInstall != null ){
Enumeration keys = handlersToInstall.keys();
while( keys.hasMoreElements() ) {
String classname = (String)keys.nextElement();
ContentHandlerRegData handlerData =
(ContentHandlerRegData)handlersToInstall.get(classname);
try {
RegistryStore.register(appl.forClass(classname), handlerData);
if (AppProxy.LOGGER != null) {
AppProxy.LOGGER.println("Register: " + classname + ", id: " + handlerData.getID());
}
} catch (ClassNotFoundException e) {
// assert( false );
// it's impossible because appl.forClass(classname) already checked
// this class
}
}
}
}
/**
* Performs static uninstallation (unregistration) of the application.
*
* @param suiteId suite ID to be unregistered
* @param update flag indicated whether the given suite is about remove or
* update
*/
static void uninstallAll(int suiteId, boolean update) {
ContentHandlerImpl[] chs = RegistryStore.forSuite(suiteId);
for (int i = 0; i < chs.length; i++) {
if (!update || (chs[i].registrationMethod &
ContentHandlerImpl.REGISTERED_STATIC_FLAG) != 0) {
RegistryStore.unregister(chs[i].getID());
}
}
}
}