package jfun.yan.xml;
import java.beans.IntrospectionException;
import java.beans.PropertyDescriptor;
import java.beans.PropertyEditor;
import java.beans.PropertyEditorManager;
import java.io.File;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLClassLoader;
import java.text.DateFormat;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.StringTokenizer;
import java.util.TimeZone;
import jfun.util.DateUtil;
import jfun.util.Misc;
import jfun.util.beans.BeanType;
import jfun.util.os.PathTokenizer;
import jfun.yan.Binder;
import jfun.yan.Component;
import jfun.yan.Components;
import jfun.yan.Creator;
import jfun.yan.Monad;
import jfun.yan.Mutation;
import jfun.yan.util.ReflectionUtil;
import jfun.yan.util.Utils;
import jfun.yan.util.deserializer.Deserializer;
/**
* A common utility class for various Nuts helper functions.
* <p>
* @author Ben Yu
*
*/
public class NutsUtils {
/**
* To split a string into an array of sub-strings.
* @param str the string to split.
* @param sep the separator characters.
* @return an array of sub-strings who are seperated by
* one or many of the characters in the sep string.
*/
public static String[] split(String str, String sep){
final StringTokenizer tok = new StringTokenizer(str, sep);
final ArrayList buf = new ArrayList();
while(tok.hasMoreTokens()){
buf.add(tok.nextToken().trim());
}
final String[] result = new String[buf.size()];
buf.toArray(result);
return result;
}
private static final HashMap booleans = getBooleanMap();
private static HashMap getBooleanMap(){
final HashMap r = new HashMap();
r.put("on", Boolean.valueOf(true));
r.put("yes", Boolean.valueOf(true));
r.put("true", Boolean.valueOf(true));
r.put("off", Boolean.valueOf(false));
r.put("no", Boolean.valueOf(false));
r.put("false", Boolean.valueOf(false));
return r;
}
/**
* To convert a string literal to boolean.
* @param str the string literal
* @return Boolean object. null if the string is not recognized.
*/
public static Boolean toBoolean(String str){
return (Boolean)booleans.get(str);
}
/**
* Convert a string to a File. if the string represents
* a relative path, it is treated as relative to the basedir.
* @param basedir the base directory.
* @param path the string.
* @return the File object.
*/
public static File toFile(File basedir, String path){
final File f = new File(path);
if(f.isAbsolute() || f.getPath().startsWith("/")){
return f;
}
else{
return new File(basedir, path);
}
}
/**
* Convert a string to a URL. if the string represents
* a relative path, it is treated as relative to the basedir.
* @param basedir the base directory.
* @param str the string.
* @return the URL object.
* @throws MalformedURLException if the string represents neither a file nor a URL.
*/
public static URL toUrl(File basedir, String str)
throws MalformedURLException{
try{
return new URL(str);
}
catch(MalformedURLException e){
return toFile(basedir, str).toURL();
}
}
/**
* To convert a string literal to a URI object.
* @param str the string literal.
* @return the URI object.
* @throws URISyntaxException if the uri syntax is wrong.
*/
public static URI toUri(String str)
throws URISyntaxException{
return new URI(str);
}
private static void populate(Map m, Class t, Deserializer des){
m.put(t, des);
m.put(ReflectionUtil.toObjectType(t), des);
}
//private static HashMap deserializers = getDeserializers();
static HashMap getDeserializers(){
final HashMap hmap = new HashMap();
populate(hmap, boolean.class, new Deserializer(){
public Object deserialize(String str){
return toBoolean(str);
}
});
populate(hmap, char.class, new Deserializer(){
public Object deserialize(String str){
if(str.length()!=1){
throw new IllegalArgumentException("cannot convert string to character");
}
return new Character(str.charAt(0));
}
});
populate(hmap, byte.class, new Deserializer(){
public Object deserialize(String str){
return Byte.valueOf(str);
}
});
populate(hmap, short.class, new Deserializer(){
public Object deserialize(String str){
return Short.valueOf(str);
}
});
final Deserializer int_des = new Deserializer(){
public Object deserialize(String str){
return Integer.valueOf(str);
}
};
populate(hmap, int.class, int_des);
populate(hmap, Number.class, int_des);
populate(hmap, long.class, new Deserializer(){
public Object deserialize(String str){
return Long.valueOf(str);
}
});
populate(hmap, float.class, new Deserializer(){
public Object deserialize(String str){
return Float.valueOf(str);
}
});
populate(hmap, double.class, new Deserializer(){
public Object deserialize(String str){
return Double.valueOf(str);
}
});
populate(hmap, BigInteger.class, new Deserializer(){
public Object deserialize(String str){
return new BigInteger(str);
}
});
populate(hmap, BigDecimal.class, new Deserializer(){
public Object deserialize(String str){
return new BigDecimal(str);
}
});
final Deserializer component_des = new Deserializer(){
public Object deserialize(String str){
return asComponent(str);
}
};
populate(hmap, Creator.class, component_des);
populate(hmap, Component.class, component_des);
populate(hmap, URL.class, new Deserializer(){
public Object deserialize(String str)
throws MalformedURLException{
return new URL(str);
}
});
populate(hmap, URI.class, new Deserializer(){
public Object deserialize(String str)
throws URISyntaxException{
return toUri(str);
}
});
populate(hmap, Date.class, new Deserializer(){
public Object deserialize(String str)
throws ParseException{
return DateFormat.getInstance().parseObject(str);
}
});
populate(hmap, Locale.class, new Deserializer(){
public Object deserialize(String str){
return DateUtil.parseLocale(str);
}
});
populate(hmap, TimeZone.class, new Deserializer(){
public Object deserialize(String str){
return TimeZone.getTimeZone(str);
}
});
populate(hmap, Class.class, new Deserializer(){
public Object deserialize(String str)
throws ClassNotFoundException{
return Class.forName(str);
}
});
populate(hmap, File.class, new Deserializer(){
public Object deserialize(String str){
return new File(str);
}
});
return hmap;
}
/**
* Convert a string to an array of URL. The
* string contains a series of sub-string seperated by whitespaces
* or "," or ";"
* @param basedir the base directory to resolve relative path.
* @param str the string.
* @return the array of URL
* @throws MalformedURLException if any sub-string is not convertible to URL.
*/
public static URL[] toUrls(File basedir, String str)
throws MalformedURLException{
final List result = new ArrayList();
PathTokenizer tok = new PathTokenizer(str);
while (tok.hasMoreTokens()) {
final String element = tok.nextToken();
result.add(toUrl(basedir, element));
}
final URL[] ret = new URL[result.size()];
result.toArray(ret);
return ret;
}
private static boolean isIdentifierStart(char c){
return c=='/' || c!='$' && Character.isJavaIdentifierStart(c);
}
private static boolean isIdentifierPart(char c){
return c=='_' || c=='.' || c==' ' || c=='-' || c=='/' ||
Character.isLetter(c)||Character.isDigit(c);
}
/**
* To determine if a string is a valid id in yan xml config file.
* @param str the string.
* @return true if valid.
*/
public static boolean isValidId(String str){
final int len = str.length();
if(str==null || len==0) return false;
if(!isIdentifierStart(str.charAt(0)))
return false;
int i=1;
for(; i<len; i++){
final char c = str.charAt(i);
if(c=='\'')break;
if(!isIdentifierPart(c))
return false;
}
for(++i;i<len;i++){
final char c = str.charAt(i);
if(c!='\''){
return false;
}
}
return true;
}
static String getTypeName(Object v){
if(v==null) return null;
else return Misc.getTypeName(v.getClass());
}
/**
* Call a NutsFunction object with an array of arguments.
* @param nfun the NutsFunction object.
* @param args the arguments.
* @return the result.
*/
public static Object callFunction(NutsFunction nfun, Object[] args) {
final int params = nfun.getParameterCount();
if(params!=args.length){
throw new IllegalArgumentException(""+params+" parameter expected by function "+nfun.getName()
+ ", while "+args.length+" arguments provided.");
}
return nfun.call(args);
}
/**
* Call a NutsFunction object with a map of parameter name to argument value.
* @param nfun the function.
* @param arg_map the map of the argument values.
* @return the result.
*/
public static Object callFunction(NutsFunction nfun,
final Map arg_map){
final String[] params = nfun.getParameterNames();
if(params.length!=arg_map.size()){
throw new IllegalArgumentException(""+params.length
+" parameter expected by function "+nfun.getName()
+ ", while "+arg_map.size()+" arguments provided.");
}
final Object[] args = new Object[params.length];
for(int i=0; i<params.length; i++){
final String name = params[i];
if(!arg_map.containsKey(name)){
throw new IllegalArgumentException("missing parameter "
+ name + " for function "+nfun.getName());
}
args[i] = arg_map.get(name);
}
return nfun.call(args);
}
/**
* Convert an object to Component.
* Conversion is automatically done if necessary.
* @param val the value to be converted to Component.
* @return the Component.
*/
public static Component asComponent(Object val){
if(val==null) return Components.value(null);
else if(val instanceof Creator)
return Components.adapt((Creator)val);
else if(val instanceof NutsFunction){
return Components.fun(new NutsFunction2Function((NutsFunction)val))
.bind(Monad.instantiator());
}
else if(val instanceof Binder){
return Utils.asComponent((Binder)val);
}
else{
return Components.value(val);
}
}
/**
* Canonicalize an attribute name by replacing "-" with "_".
* @param name the attribute name.
* @return the new attribute name, or null if the attribute name is null.
*/
public static String canonicalizeAttributeName(String name){
if(name==null) return name;
return name.replace('-', '_');
}
/**
* To get a ClassLoader object that uses a base ClassLoader object
* as parent and alternatively searches a classpath if the class
* or resource is not found in parent.
* @param baseloader the base class loader.
* @param classpath the alternative classpath.
* @param basedir the base directory used in the classpath.
* @return the ClassLoader object.
* @throws MalformedURLException if the classpath is invalid.
*/
public static ClassLoader getClassLoader(ClassLoader baseloader,
String classpath, File basedir)
throws MalformedURLException{
if(classpath != null){
return new URLClassLoader(
NutsUtils.toUrls(basedir, classpath)
, baseloader);
}
else return baseloader;
}
/**
* Add named state to a Component object.
* @param c the Component object.
* @param key the key of the state.
* @param val the value of the state.
* @return the new Component object with this state.
*/
public static Component setState(Component c, Object key, Object val){
java.util.Map carrier = (java.util.Map)c.getState();
if(carrier==null){
//to save space, we don't leave a lot of empty space in the map.
carrier = new HashMap(4);
c = c.withState(carrier);
}
carrier.put(key, val);
return c;
}
/**
* Get a state value by name.
* @param c the Component object to get state from.
* @param key the state key.
* @return the state value.
*/
public static Object getState(Component c, Object key){
final java.util.Map carrier = (java.util.Map)c.getState();
if(carrier==null) return null;
else return carrier.get(key);
}
/**
* If a property of the component type exists and the property type is
* compatible with the property value, the property is set when
* the component is instantiated.
* @param c the component.
* @param name the property name.
* @param val the property value.
* @return the result Component.
* @throws IntrospectionException
*/
public static Component setPossiblePropertyValue(Component c, String name, Object val)
throws IntrospectionException{
final Method mtd = getPossiblePropertySetter(c, name);
if(mtd==null) return c;
final Class[] param_types = mtd.getParameterTypes();
if(param_types.length!=1){
return c;
}
final Class param_type = param_types[0];
if(ReflectionUtil.isInstance(param_type, val)){
final Object[] args = {val};
return c.mutate(new Mutation(){
public void mutate(Object obj)
throws Exception{
mtd.invoke(obj, args);
}
});
}
return c;
}
public static Component setPossibleProperty(Component c, String name,
final Component valc)
throws IntrospectionException{
final Method mtd = getPossiblePropertySetter(c, name);
if(mtd==null) return c;
final Class[] param_types = mtd.getParameterTypes();
if(param_types.length!=1){
return c;
}
final Class param_type = param_types[0];
final Class argtype = valc.getType();
if(argtype!=null && !ReflectionUtil.isAssignableFrom(param_type, argtype)){
return c;
}
return c.followedBy(new Binder(){
public Creator bind(final Object obj){
return valc.mutate(new Mutation(){
public void mutate(Object arg) throws Exception {
if(ReflectionUtil.isInstance(param_type, arg)){
mtd.invoke(obj, new Object[]{arg});
}
}
});
}
});
}
private static Method getPossiblePropertySetter(Component c, String name)
throws IntrospectionException{
if(name==null||name.length()==0) return null;
final Class type = c.getType();
if(type==null){
//unknown type, do not set property.
return null;
}
final BeanType beantype = BeanType.instance(type);
final PropertyDescriptor prop = beantype.getPropertyDescriptor(name);
if(prop == null) return null;
final Method mtd = prop.getWriteMethod();
return mtd;
}
}