/**
** Copyright (C) SAS Institute, All rights reserved.
** General Public License: http://www.opensource.org/licenses/gpl-license.php
**/
package com.jayway.android.robotium.remotecontrol.client.processor;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.List;
import java.util.Properties;
import java.util.UUID;
import java.util.Vector;
import org.safs.android.messenger.client.MessageResult;
import com.jayway.android.robotium.remotecontrol.client.AbstractTestRunner;
import com.jayway.android.robotium.remotecontrol.client.SoloMessage;
/**
*
* @author Lei Wang, SAS Institute, Inc
* @since
* <br>May 17, 2013 (SBJLWA) Move some static methods to com.jayway.android.robotium.remotecontrol.solo.Message
*/
public abstract class AbstractProcessor implements ProcessorInterface {
public static String TAG = AbstractProcessor.class.getName();
/**
* The command from the 'reomote solo'
*/
protected String remoteCommand = null;
/**
* A TestRunner by definition is already a DebugListener.
* This field can serve as both, if desired.
*/
protected AbstractTestRunner testRunner = null;
public AbstractProcessor(AbstractTestRunner testRunner){
this.testRunner = testRunner;
}
/**
* Forward debug messages to our testRunner which may or may not have debug enabled
* for performance reasons.
* <p>
* This method will NOT add any prefix or suffix to the message. It is the responsibility
* of the caller to provide the complete message to be logged. This is to prevent cases where
* debugPrefix is NOT used or changed by the caller and incorrect debug output occurs.
*/
protected void debug(String message){
testRunner.debug(message);
}
/**
* Create a String from a Throwable suitable for debug output that provides
* comparable information to x.printStackTrace();
* @param x
* @return String ready for output to debug(String) or other sink.
*/
protected String getStackTrace(Throwable x){
String rc = x.getClass().getName()+", "+ x.getMessage()+"\n";
StackTraceElement[] se = x.getStackTrace();
for(StackTraceElement s:se){ rc += s.toString()+"\n"; }
return rc;
}
/**
* Before calling this method, you may need to call setRemoteCommand()
*
* @see ProcessorInterface#processProperties(Properties)
*/
public void processProperties(Properties props) {
}
/**
*
* @see ProcessorInterface#processMessage(String)
*/
public MessageResult processMessage(String message) {
return null;
}
/**
*
* @see ProcessorInterface#setRemoteCommand(String)
*/
public void setRemoteCommand(String remoteCommand) {
this.remoteCommand = remoteCommand;
}
/**
* Call to set a "<command> : successful" result into the properties object.
* <p>
* set the isremoteresult to "true"<br>
* set the remoteresultcode to a constant code {@link SoloMessage#STATUS_REMOTERESULT_OK_STRING}<br>
*
* <b>Note:</b> This method will concatenate {@link #remoteCommand} with <br>
* {@link SoloMessage#RESULT_INFO_GENERAL_SUCCESS}<br> and set this string to remoteresultinfo<br>
*
* @param props The Properties object containing the in and out parameters
* @param resultInfo The result information to be modified and set to remoteresultinfo of props.
*/
protected void setGeneralSuccess(Properties props){
setGeneralSuccess(props, "");
}
/**
* Call to set a "<command> : <resultInfo> successful." result into the properties object.
* <p>
* set the isremoteresult to "true"<br>
* set the remoteresultcode to a constant code {@link SoloMessage#STATUS_REMOTERESULT_OK_STRING}<br>
*
* <b>Note:</b> This method will concatenate {@link #remoteCommand} with 'resultInfo', then with <br>
* {@link SoloMessage#RESULT_INFO_GENERAL_SUCCESS}<br> and set this string to remoteresultinfo<br>
*
* @param props The Properties object containing the in and out parameters
* @param resultInfo The result information to be modified and set to remoteresultinfo of props.
*/
protected void setGeneralSuccess(Properties props, String resultInfo){
resultInfo = resultInfo != null ? resultInfo: SoloMessage.NULL_VALUE;
if(remoteCommand!=null) resultInfo += " : '"+ remoteCommand + "' ";
resultInfo += SoloMessage.RESULT_INFO_GENERAL_SUCCESS;
setGeneralSuccessWithSpecialInfo(props, resultInfo);
}
/**
* Call to set a "<resultInfo>" remoteresultinfo into the properties object unmodified.
* set the isremoteresult to "true"<br>
* set the remoteresultcode to a constant code {@link SoloMessage#STATUS_REMOTERESULT_OK_STRING}<br>
* set the remoteresultinfo to the string given by parameter resultInfo<br>
*
* <b>Note:</b> If remoteresultinfo should contain a result of a predefined or unmodified format
* you MUST call this method.<br>
*
* @param props The Properties object containing the in and out parameters
* @param resultInfo The result information to be set unmodified to remoteresultinfo of props.
*/
protected void setGeneralSuccessWithSpecialInfo(Properties props, String resultInfo){
try{
resultInfo = resultInfo != null ? resultInfo: SoloMessage.NULL_VALUE;
props.setProperty(SoloMessage.KEY_ISREMOTERESULT, String.valueOf(true));
props.setProperty(SoloMessage.KEY_REMOTERESULTCODE, SoloMessage.STATUS_REMOTERESULT_OK_STRING);
props.setProperty(SoloMessage.KEY_REMOTERESULTINFO, resultInfo);
}catch(Exception e){
//Properties.setProperty() may throw NullPointerException
String debugmsg = TAG+".setGeneralSuccessWithSpecialInfo() ";
debug(debugmsg+" Met Exception="+e.getMessage());
}
}
/**
* Call to set a "<resultInfo>" remoteresultinfo into the properties object unmodified.
* set the isremoteresult to "true"<br>
* set the remoteresultcode to a constant code {@link SoloMessage#STATUS_REMOTERESULT_WARN_STRING}<br>
* set the remoteresultinfo to the string given by parameter resultInfo<br>
*
* <b>Note:</b> If remoteresultinfo should contain a result of a predefined or unmodified format
* you MUST call this method.<br>
*
* @param props The Properties object containing the in and out parameters
* @param resultInfo The result information to be set unmodified to remoteresultinfo of props.
*/
protected void setGeneralWarningWithSpecialInfo(Properties props, String resultInfo){
try{
resultInfo = resultInfo != null ? resultInfo: SoloMessage.NULL_VALUE;
props.setProperty(SoloMessage.KEY_ISREMOTERESULT, String.valueOf(true));
props.setProperty(SoloMessage.KEY_REMOTERESULTCODE, SoloMessage.STATUS_REMOTERESULT_WARN_STRING);
props.setProperty(SoloMessage.KEY_REMOTERESULTINFO, resultInfo);
}catch(Exception e){
//Properties.setProperty() may throw NullPointerException
String debugmsg = TAG+".setGeneralWarningWithSpecialInfo() ";
debug(debugmsg+" Met Exception="+e.getMessage());
}
}
/**
* set the isremoteresult to "true"<br>
* set the remoteresultcode to a constant code {@link SoloMessage#STATUS_REMOTERESULT_FAIL_STRING}<br>
*
* <b>Note:</b> This method will concatenate {@link #remoteCommand} with 'resultInfo', then with <br>
* {@link SoloMessage#RESULT_INFO_GENERAL_FAIL}<br> and set this string to remoteresultinfo<br>
*
* @param props The Properties object containing the in and out parameters
* @param resultInfo The result information to be modified and set to remoteresultinfo of props.
*/
protected void setGeneralError(Properties props, String resultInfo){
setGeneralError(props, resultInfo, null);
}
/**
* set the isremoteresult to "true"<br>
* set the remoteresultcode to a constant code {@link SoloMessage#STATUS_REMOTERESULT_FAIL_STRING}<br>
* set the errormsg to the string given by parameter errorMessage<br>
*
* <b>Note:</b> This method will concatenate {@link #remoteCommand} with 'resultInfo', then with <br>
* {@link SoloMessage#RESULT_INFO_GENERAL_FAIL}<br> and set this string to remoteresultinfo<br>
*
* @param props The Properties object containing the in and out parameters
* @param resultInfo The result information to be set to resultinfo of props.
* @param errorMessage The error message to be set to errormessage of props. Null if no
* error message is to be sent.
*/
protected void setGeneralError(Properties props, String resultInfo, String errorMessage){
resultInfo = resultInfo != null ? resultInfo: SoloMessage.NULL_VALUE;
if(remoteCommand!=null) resultInfo += " : '"+remoteCommand+"'";
resultInfo += SoloMessage.RESULT_INFO_GENERAL_FAIL;
setGeneralErrorWithSpecialInfo(props, resultInfo, errorMessage);
}
/**
* set the isremoteresult to "true"<br>
* set the remoteresultcode to a constant code {@link SoloMessage#STATUS_REMOTERESULT_FAIL_STRING}<br>
* set the remoteresultinfo to the string given by parameter resultInfo<br>
*
* <b>Note:</b> If remoteresultinfo should contain result of a predefined or unmodified format
* you MUST call this method.<br>
*
* @param props The Properties object containing the in and out parameters
* @param resultInfo The result information to be set unmodified to remoteresultinfo of props.
*/
protected void setGeneralErrorWithSpecialInfo(Properties props, String resultInfo){
setGeneralErrorWithSpecialInfo(props, resultInfo, null);
}
/**
* set the isremoteresult to "true"<br>
* set the remoteresultcode to a constant code {@link SoloMessage#STATUS_REMOTERESULT_FAIL_STRING}<br>
* set the errormsg to the string given by parameter errorMessage<br>
*
* <b>Note:</b> If remoteresultinfo should contain result of 'predefined format', you MUST call this method.<br>
*
* @param props The Properties object containing the in and out parameters
* @param resultInfo The result information to be set unmodified to resultinfo of props.
* @param errorMessage The error message to be set to errormessage of props. Null if no error message
* is to be sent.
*/
protected void setGeneralErrorWithSpecialInfo(Properties props, String resultInfo, String errorMessage){
try{
resultInfo = resultInfo != null ? resultInfo: SoloMessage.NULL_VALUE;
props.setProperty(SoloMessage.KEY_ISREMOTERESULT, String.valueOf(true));
props.setProperty(SoloMessage.KEY_REMOTERESULTCODE, SoloMessage.STATUS_REMOTERESULT_FAIL_STRING);
props.setProperty(SoloMessage.KEY_REMOTERESULTINFO, resultInfo);
if(errorMessage!=null){
props.setProperty(SoloMessage.PARAM_ERRORMSG, errorMessage);
}
}catch(Exception e){
//Properties.setProperty() may throw NullPointerException
String debugmsg = TAG+".setGeneralErrorWithSpecialInfo() ";
debug(debugmsg+" Met Exception="+e.getMessage());
}
}
protected int INITIAL_CACHE_SIZE = 50;
/**
* Clear and\or reset the internal component cache used in non-typical modes
* of operation like MODE_EXTERNAL_PROCESSING (Process Container).
* @param cache Hashtable, MUST be an initialized object
* @see #makeUniqueCacheKey(Object)
* @see #putCachedItem(Hashtable, Object, Object)
* @see #getCachedItem(Hashtable, Object)
* @see #removeCachedItem(Hashtable, Object)
*/
public void resetExternalModeCache(Hashtable cache){
if (cache != null) cache.clear();
}
/**
* Attempts to retrieve an item from cache using the provided key.
*
* If not found will attempt to use key as-is.
* If not found will return key as-is.
* @param cache Hashtable, MUST be an initialized object
* @param key Object to use as lookup reference into cache
* @return Object stored in cache or null.
* @see #makeUniqueCacheKey(Object)
* @see #putCachedItem(Hashtable, Object, Object)
* @see #removeCachedItem(Hashtable, Object)
*/
protected Object getCachedItem(Hashtable cache, Object key){
Object item = null;
if (cache==null) return key;
if(key==null) return null;
if (key instanceof String) item = cache.get(key);
if (item==null) item = cache.get(key);
// if (item==null) item = key;
return item;
}
/**
* Remove an item from cache.
* Will attempt to use key as-is.
*
* @param cache Hashtable, MUST be an initialized object
* @param key Object to use as lookup reference into cache
* @return the Object removed or null if not found.
* @see #makeUniqueCacheKey(Object)
* @see #getCachedItem(Hashtable, Object)
* @see #putCachedItem(Hashtable, Object, Object)
*/
protected Object removeCachedItem(Hashtable cache, Object key){
Object item = null;
if ((cache==null)||(key==null)) return null;
item = cache.remove(key);
if ((item==null)&&(key instanceof String)) item = cache.remove(key);
return item;
}
/**
* Attempts to put an item in cache using the provided key.
*
* @param cache Hashtable, MUST be an initialized object
* @param key Object to use as lookup reference into cache.
* @param item Item to store in the cache.
* @throws IllegalArgumentException if either cache or key or item is null.
* @see #makeUniqueCacheKey(Object)
* @see #getCachedItem(Hashtable, Object)
* @see #removeCachedItem(Hashtable, Object)
*/
protected void putCachedItem(Hashtable cache, Object key, Object item){
if (cache==null){
throw new IllegalArgumentException("The cache CAN NOT be null!");
}
try{
cache.put(key, item);
}catch(NullPointerException np){
throw new IllegalArgumentException("Neither cache key nor item can be null.");
}
}
/**
* Test if the value is contained in the local cache.<br>
* @param cache Hashtable, MUST be an initialized object
* @param value Object to be checked if it is in the cache.
*/
boolean cacheContainValue(Hashtable cache, Object value){
if(cache == null) return false;
return cache.containsValue(value);
}
/**
* To check if the local cache contains the expectedValue.<br>
* If found, return the corresponding key, otherwise return null.<br>
*
* @param cache Hashtable, MUST be an initialized object
* @param expectedValue Object, the value to be checked
* @return Object, the key for the value found in cache.<br>
* null, if the value can't be found in cache.<br>
*/
Object getCacheKeyForValue(Hashtable cache, Object expectedValue){
Object key = null;
Object value = null;
if(cacheContainValue(cache, expectedValue)){
Enumeration<Object> enumerator = cache.keys();
while(enumerator.hasMoreElements()){
key = enumerator.nextElement();
value = cache.get(key);
//As cache is a Hashtable, which doesn't permit null as key or value
//so all the values got from it will NOT be null.
if(value.equals(expectedValue)) break;
}
}
return key;
}
/**
* Convert a list of engine-specific objects to an array of unique keys
* in the cache. The items will be stored in the cache using the unique keys.
*
* @param cache Hashtable, MUST be an initialized object
* @param itemsList List of objects to store in the cache.
* @return an array of String keys used to retrieve the items from the cache.
* @see #convertToKeys(Hashtable, Object[])
*
*/
protected String[] convertToKeys(Hashtable cache, List itemsList){
Object[] items = null;
if(itemsList != null){
items = itemsList.toArray();
}
return convertToKeys(cache, items);
}
/**
* Convert an array of engine-specific objects to an array of unique keys
* in the cache. The items will be stored in the cache using the unique keys.
*
* @param cache Hashtable, MUST be an initialized object
* @param items Array of objects to store in the cache.
* @return an array of String keys used to retrieve the items from the cache.
*
* @see #convertToKey(Hashtable, Object)
* @see #makeUniqueCacheKey(Object)
* @see #getCachedItem(Hashtable, Object)
* @see #putCachedItem(Hashtable, Object, Object)
*
* @author CANAGL APR 23,2010 handle case of null items in items array.
* @author sbjlwa MAR 01,2012 Return an array of String instead of Object,
* as makeUniqueCacheKey() return only String.
*/
protected String[] convertToKeys(Hashtable cache, Object[] items){
String[] keyarray = new String[0];
if (items == null) return keyarray;
Vector<String> keys = new Vector<String>();
String key = null;
for(int it=0; it<items.length;it++){
key = convertToKey(cache, items[it]);
if(key != null) keys.add(key);
}
if(keys.size()==0) return keyarray;
return keys.toArray(keyarray);
}
/**
* Convert a item to a unique key.<br>
* If the cache contain the item, return the cached key directly.<br>
* Else, generate a new unique key and put the item into cache with that key<br>
* then return the key.<br>
*
* @param cache Hashtable, MUST be an initialized object
* @param items Array of objects to store in the cache.
* @return String key used to retrieve the item from the cache.
*
* @see #makeUniqueCacheKey(Object)
* @see #getCachedItem(Hashtable, Object)
* @see #putCachedItem(Hashtable, Object, Object)
*
*/
protected String convertToKey(Hashtable cache, Object item){
String debugPrefix = TAG+".convertToKey(): ";
String key = null;
if (item != null) {
// check if item is in local cache
try {
key = (String) getCacheKeyForValue(cache, item);
} catch (ClassCastException e) {
// item exist in cache, but the key is not a String!!!
key = null;
}
if (key == null) {
key = makeUniqueCacheKey(item);
try{
//putCachedItem can throw IllegalArgumentException, if one of its parameter is null
//although we are sure that key and item will not be null, but the cache may be null
//we still need to catch this Exception
putCachedItem(cache, key, item);
}catch(Exception e){
key = null;
debug(debugPrefix+" Can NOT put object to cache. Exception="+e.getMessage());
}
}
}else{
debug(debugPrefix+" Can NOT generate a key for null object.");
}
if(key==null){
//This should hardly happen
debug(debugPrefix+" Can't get key for item!!!");
}
return key;
}
private static String __last_unique_key = "";
/**
* Routine is used to create a unique ID String key that can be used by external
* processes like Process Container to identify an engine-specific item in the
* cache.<br>
* This method is thread-safe: it guarantees that multiple threads can get unique ID.<br>
*
* @param item to be stored in cache.
* @return unique String suitable to be the key for the item.
* @see #putCachedItem(Hashtable, Object, Object)
* @see #getCachedItem(Hashtable, Object)
* @see #removeCachedItem(Hashtable, Object)
*/
protected String makeUniqueCacheKey(Object item){
String timestamp = "";
synchronized(__last_unique_key){
do{ timestamp = UUID.randomUUID().toString();
}while(timestamp.equals(__last_unique_key));
__last_unique_key = timestamp;
}
return timestamp;
}
/**
* Test if the second string match with the first string.
*
* @param string1, String, the first string
* @param string2, String, the second string
* @param partial boolean, if the comparison is partial match.
* @param caseSensitive boolean, if the comparison is case sensitive.
*
*/
public static boolean stringIsMatched(String string1, String string2, boolean partial, boolean caseSensitive){
boolean matched = false;
if(string1==null || string2==null) return string1==string2;
if(caseSensitive){
if(partial){
matched = string1.contains(string2);
}else{
matched = string1.equals(string2);
}
}else{
if(partial){
matched= string1.toLowerCase().contains(string2.toLowerCase());
}else{
matched = string1.equalsIgnoreCase(string2);
}
}
return matched;
}
}