package com.openMap1.mapper.mapping;
import java.util.*;
import java.lang.reflect.*;
import com.openMap1.mapper.core.ClassSet;
import com.openMap1.mapper.core.MapperException;
import com.openMap1.mapper.core.PropertyConversionException;
import com.openMap1.mapper.util.GenUtil;
import com.openMap1.mapper.ClassDetails;
import com.openMap1.mapper.ConversionArgument;
import com.openMap1.mapper.ConversionImplementation;
import com.openMap1.mapper.ConversionSense;
import com.openMap1.mapper.JavaConversionImplementation;
import com.openMap1.mapper.PropertyConversion;
import com.openMap1.mapper.XSLTConversionImplementation;
/**
* class for a two-way conversion of a property value -
* between the form used in the XML instance,
* and the form used in the EMF Ecore model instance.
*
* @author robert
*
*/
public class propertyConversion {
/* sense = 'in' for conversions from XML to the object model;
'out' for conversions from the object model to XML. */
private String sense;
private Vector<String> arguments; // string names of properties or converted properties used as arguments
private String resultProperty; // name of property or converted property returned
private ClassSet cSet; // class and subset this conversion applies to
// supplied implementations of the conversion, in Java or XSLT; key = language name
private Hashtable<String, ConversionImplementation> implementations;
public String resultProperty() {return resultProperty;}
public Vector<String> arguments() {return arguments;}
public boolean in() {return(sense.equals("in"));}
public boolean out() {return(sense.equals("out"));}
public String sense() {return sense;}
public ClassSet cSet() {return cSet;}
// to keep data in this instance between calls to the static Java conversion method
private Hashtable<?,?> lookupTable = new Hashtable<Object,Object>();
// to ensure kept data is created only once
/**
* Constructor for non-local property conversions,
* which may have several arguments
*/
public propertyConversion (PropertyConversion propCon)
{
sense = propCon.getSense().getLiteral();
String className = ((ClassDetails)propCon.eContainer()).getQualifiedClassName();
try {cSet = new ClassSet(className,propCon.getSubset());}
catch (MapperException ex) {GenUtil.surprise(ex,"new propertyConversion");} // nulls unexpected
resultProperty = propCon.getResultSlot();
arguments = new Vector<String>();
for (Iterator<ConversionArgument> it = propCon.getConversionArguments().iterator();it.hasNext();)
{arguments.add(it.next().getPropertyName());}
implementations = new Hashtable<String, ConversionImplementation>();
for (Iterator<ConversionImplementation> it = propCon.getConversionImplementations().iterator();it.hasNext();)
{
ConversionImplementation ci = it.next();
String lang = language(ci);
implementations.put(lang,ci);
}
}
/**
* Constructor for local property conversions, which have only one implementation with one argument
* @param pm the propertyMapping that this conversion belongs to
* @param jci the Java conversion implementation
* @param sense in or out
*/
public propertyConversion(propertyMapping pm, JavaConversionImplementation jci, ConversionSense sense)
{
this.sense = sense.getLiteral();
cSet = pm.cSet();
resultProperty = pm.propertyName();
// the conversion has one argument, with the mapped property name
arguments = new Vector<String>();
arguments.add(resultProperty);
// there is one Java implementation
implementations = new Hashtable<String, ConversionImplementation>();
implementations.put(language(jci),jci);
}
// note upper case 'J' for Java
private String language(ConversionImplementation ci)
{
String lang = null;
if (ci instanceof JavaConversionImplementation) lang = "Java";
if (ci instanceof XSLTConversionImplementation) lang = "XSLT";
return lang;
}
public String methodName(String language)
{
String meth = null;
ConversionImplementation ci = implementations.get(language);
if (ci instanceof JavaConversionImplementation)
{meth = ((JavaConversionImplementation)ci).getMethodName();}
if (ci instanceof XSLTConversionImplementation)
{meth = ((XSLTConversionImplementation)ci).getTemplateName();}
return meth;
}
/**
* For Java, the container name is the fully qualified class name,
* i.e preceded by the full package name
* @param language
* @return
*/
public String containerName(String language)
{
String cont = null;
ConversionImplementation ci = implementations.get(language);
if (ci instanceof JavaConversionImplementation)
{
JavaConversionImplementation jc = (JavaConversionImplementation)ci;
cont = jc.getPackageName() + "." + jc.getClassName();
}
if (ci instanceof XSLTConversionImplementation)
{cont = "";}
return cont;
}
public boolean hasImplementation(String language)
{return(implementations.get(language) != null);}
/**
*
* @return fully qualified method name - preceded by the class, which is preceded
* by the package
*/
public String javaImplementation()
{
String res = null;
JavaConversionImplementation javaImp = (JavaConversionImplementation)implementations.get("Java");
if (javaImp != null)
{res = javaImp.getPackageName()+ "." + javaImp.getClassName() + "." + javaImp.getMethodName();}
return res;
}
/**
* This assumes that class.forName will work with a fully qualified class name
* @param className
* @return
* @throws MapperException
*/
private Class<?> getNamedClass(String className) throws MapperException
{
Class<?> theClass = null;
if (className == null)
{throw new MapperException("Looking for null class");}
try {theClass = Class.forName(className);}
catch (ClassNotFoundException ex)
{throw new PropertyConversionException("Failed to find property conversion class '" + className + "': " + ex.getMessage());}
return theClass;
}
private Method getStaticPublicMethod(Class<?> theClass, String methodName, Class<?>[] argClasses) throws MapperException
{
Method theMethod = null;
if (methodName == null)
{throw new PropertyConversionException("Null method name sought in class '" + theClass.getName() + "'");}
try {theMethod = theClass.getDeclaredMethod(methodName,argClasses);}
catch (NoSuchMethodException ex)
{throw new PropertyConversionException("Failed to find method '" + methodName + "' in class '" + theClass.getName() + "': " + ex.getMessage());}
catch (SecurityException ex)
{throw new PropertyConversionException("Security exception finding method '" + methodName + "' in class '" + theClass.getName() + "': " + ex.getMessage());}
if (!Modifier.isStatic(theMethod.getModifiers()))
{throw new PropertyConversionException("java conversion method '" + methodName + "' in class '" + theClass.getName()
+ "' must be static.");}
if (!Modifier.isPublic(theMethod.getModifiers()))
{throw new PropertyConversionException("java conversion method '" + methodName + "' in class '" + theClass.getName()
+ "' must be public.");}
return theMethod;
}
private Object invokeStaticMethod(Method theMethod, Object[] args, String javaClass, String pName) throws MapperException
{
Object result = null;
String methodName = theMethod.getName();
try
{
result = theMethod.invoke(null,args);
}
catch (IllegalAccessException ex)
{throw new PropertyConversionException("Illegal access to java conversion method '" + methodName + "' in class '" + javaClass
+ "' for property " + pName + " - " + ex.getMessage());}
catch (IllegalArgumentException ex)
{throw new PropertyConversionException("Illegal argument for java conversion method '" + methodName + "' in class '" + javaClass
+ "' for property " + pName + " - " + ex.getMessage());}
catch (InvocationTargetException ex)
{
Throwable ta = ex.getTargetException();
throw new PropertyConversionException("java conversion method '" + methodName + "' in class '" + javaClass
+ "' for property " + pName + " has thrown an exception: " + ta.getMessage());
}
catch (Exception ex)
{throw new PropertyConversionException("Exception converting property " + pName + " - " + ex.getMessage());}
return result;
}
/* true if the system can do the Java implementation of this conversion -
i.e if the necessary class and method are available now.
If it cannot for any reason, throws exceptions or returns false. */
public boolean canDoJavaConvert() throws MapperException
{
int size = arguments.size();
Class<?> convClass;
Class<?>[] argTypes = new Class<?>[size + 1]; // extra for first Hashtable argument
String javaClass = containerName("Java");
String javaMethod = methodName("Java");
// conversion method expects a first Hashtable argument, followed by one or more String arguments
argTypes[0] = getNamedClass("java.util.Hashtable");
for (int i = 0; i < size; i++)
{argTypes[i+1] = getNamedClass("java.lang.String");}
/* find the class containing the static method, then find the method and the initialisation method
These will throw MapperExceptions if anything is wrong. */
convClass = getNamedClass(javaClass);
getStaticPublicMethod(convClass,javaMethod,argTypes);
return true; // if you get this far
}
/* do the Java implementation of this conversion */
public String doJavaConvert(String[] args) throws MapperException
{
Class<?> convClass;
Method jMethod = null;
int size = arguments.size(); // number of String arguments
// conversion method has an initial Hashtable argument, followed by String arguments
Object[] fullArgs = new Object[size + 1];
Class<?>[] argTypes = new Class<?>[size + 1];
String res = null;
String javaClass = containerName("Java");
String javaMethod = methodName("Java");
String pName = resultProperty; // property or converted property
// check correct number of arguments
if (args.length != size)
{throw new PropertyConversionException("Supplied " + args.length + " arguments to conversion method '"
+ javaMethod + "' in Java class '" + javaClass
+ "'; but the conversion expects " + size + " arguments.");}
// method expects a Hashtable argument, followed by one or more String arguments
argTypes[0] = getNamedClass("java.util.Hashtable");
for (int i = 0; i < size; i++)
{argTypes[i+1] = getNamedClass("java.lang.String");}
// find the class containing the static method, then find the method and possibly the initialisation method
convClass = getNamedClass(javaClass);
jMethod = getStaticPublicMethod(convClass, javaMethod, argTypes);
/* lookup table is the first argument of the conversion method. The conversion method may either
* ignore it, or may detect it is empty on the first call, and call some initialisation method
* to set it up (and thereafter, see it is not empty and so not set it up again) */
fullArgs[0] = lookupTable;
for (int i = 0; i < size; i++) {fullArgs[i+1] = args[i];}
// static method, so no object in the class need be specified when invoking it
res = (String)invokeStaticMethod(jMethod,fullArgs,javaClass, pName);
return res;
}
}