
org.n52.matlab.control.link.InvocationInfo Maven / Gradle / Ivy
The newest version!
package org.n52.matlab.control.link;
/*
* Copyright (c) 2013, Joshua Kaplan
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
* following conditions are met:
* - Redistributions of source code must retain the above copyright notice, this list of conditions and the following
* disclaimer.
* - Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the
* following disclaimer in the documentation and/or other materials provided with the distribution.
* - Neither the name of matlabcontrol nor the names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.GenericArrayType;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.lang.reflect.WildcardType;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
/**
* A holder of information about the Java method and the associated MATLAB function.
*
* @since 4.2.0
* @author Joshua Kaplan
*/
class InvocationInfo
{
/**
* The name of the function.
*/
final String name;
/**
* The directory containing the function. This is an absolute path to an m-file on the system, never inside of
* a compressed file such as a jar. {@code null} if the function is (supposed to be) on MATLAB's path.
*/
final String containingDirectory;
/**
* The types of each returned argument. The length of this array is the nargout to call the function with.
*/
final Class>[] returnTypes;
/**
* Array of generic type parameters of the return types. For instance for return type at index i in
* {@code returnTypes} the generic parameters are in the array returned by {@code returnTypeGenericParameters}
* at index i.
*/
final Class>[][] returnTypeParameters;
static InvocationInfo getInvocationInfo(Method method, MatlabFunction annotation)
{
FunctionInfo funcInfo = getFunctionInfo(method, annotation);
ReturnTypeInfo returnInfo = getReturnTypes(method);
InvocationInfo invocationInfo = new InvocationInfo(funcInfo.name, funcInfo.containingDirectory,
returnInfo.returnTypes, returnInfo.returnTypeParameters);
return invocationInfo;
}
InvocationInfo(String name, String containingDirectory, Class>[] returnTypes, Class>[][] returnTypeParameters)
{
this.name = name;
this.containingDirectory = containingDirectory;
this.returnTypes = returnTypes;
this.returnTypeParameters = returnTypeParameters;
}
@Override
public String toString()
{
String genericParameters = "[";
for(int i = 0; i < returnTypeParameters.length; i++)
{
genericParameters += classArrayToString(returnTypeParameters[i]);
if(i != returnTypeParameters.length - 1)
{
genericParameters += " ";
}
}
genericParameters += "]";
return "[" + this.getClass().getSimpleName() +
" name=" + name + "," +
" containingDirectory=" + containingDirectory + "," +
" returnTypes=" + classArrayToString(returnTypes) + "," +
" returnTypesGenericParameters=" + genericParameters + "]";
}
private static String classArrayToString(Class>[] array)
{
String str = "[";
for(int i = 0; i < array.length; i++)
{
str += array[i].getCanonicalName();
if(i != array.length - 1)
{
str += " ";
}
}
str += "]";
return str;
}
private static class FunctionInfo
{
final String name;
final String containingDirectory;
FunctionInfo(String name, String containingDirectory)
{
this.name = name;
this.containingDirectory = containingDirectory;
}
}
private static FunctionInfo getFunctionInfo(Method method, MatlabFunction annotation)
{
//Determine the function's name and if applicable, the directory the function is located in
String functionName;
String containingDirectory;
//If a function name
if(isFunctionName(annotation.value()))
{
functionName = annotation.value();
containingDirectory = null;
}
//If a path
else
{
String path = annotation.value();
//Retrieve location of function file
File functionFile;
if(new File(path).isAbsolute())
{
functionFile = new File(path);
}
else
{
functionFile = resolveRelativePath(method, path);
}
//Resolve canonical path
try
{
functionFile = functionFile.getCanonicalFile();
}
catch(IOException e)
{
throw new LinkingException("Unable to resolve canonical path of specified function\n" +
"method: " + method.getName() + "\n" +
"path:" + path + "\n" +
"non-canonical path: " + functionFile.getAbsolutePath(), e);
}
//Validate file location
if(!functionFile.exists())
{
throw new LinkingException("Specified file does not exist\n" +
"method: " + method.getName() + "\n" +
"path: " + path + "\n" +
"resolved as: " + functionFile.getAbsolutePath());
}
if(!functionFile.isFile())
{
throw new LinkingException("Specified file is not a file\n" +
"method: " + method.getName() + "\n" +
"path: " + path + "\n" +
"resolved as: " + functionFile.getAbsolutePath());
}
if(!(functionFile.getName().endsWith(".m") || functionFile.getName().endsWith(".p")))
{
throw new LinkingException("Specified file does not end in .m or .p\n" +
"method: " + method.getName() + "\n" +
"path: " + path + "\n" +
"resolved as: " + functionFile.getAbsolutePath());
}
//Parse out the name of the function and the directory containing it
containingDirectory = functionFile.getParent();
functionName = functionFile.getName().substring(0, functionFile.getName().length() - 2);
//Validate the function name
if(!isFunctionName(functionName))
{
throw new LinkingException("Specified file's name is not a MATLAB function name\n" +
"Function Name: " + functionName + "\n" +
"File: " + functionFile.getAbsolutePath());
}
}
return new FunctionInfo(functionName, containingDirectory);
}
private static boolean isFunctionName(String functionName)
{
boolean isFunctionName = true;
char[] nameChars = functionName.toCharArray();
if(!Character.isLetter(nameChars[0]))
{
isFunctionName = false;
}
else
{
for(char element : nameChars)
{
if(!(Character.isLetter(element) || Character.isDigit(element) || element == '_'))
{
isFunctionName = false;
break;
}
}
}
return isFunctionName;
}
/**
* Cache used by {@link #resolveRelativePath(Method, String)}
*/
private static final ConcurrentHashMap, Map> UNZIPPED_ENTRIES =
new ConcurrentHashMap, Map>();
/**
* Resolves the location of {@code relativePath} relative to the interface which declared {@code method}. If the
* interface is inside of a zip file (jar/war/ear etc. file) then the contents of the zip file may first need to be
* unzipped.
*
* @param method
* @param relativePath
* @return the absolute location of the file
*/
private static File resolveRelativePath(Method method, String relativePath)
{
Class> theInterface = method.getDeclaringClass();
File interfaceLocation = getClassLocation(theInterface);
File functionFile;
//Code source is in a file, which means it should be jar/ear/war/zip etc.
if(interfaceLocation.isFile())
{
if(!UNZIPPED_ENTRIES.containsKey(theInterface))
{
UnzipResult unzipResult = unzip(interfaceLocation);
Map mapping = unzipResult.unzippedMapping;
List filesToDelete = new ArrayList(mapping.values());
filesToDelete.add(unzipResult.rootDirectory);
//If there was a previous mapping, delete all of the files just unzipped
if(UNZIPPED_ENTRIES.putIfAbsent(theInterface, mapping) != null)
{
deleteFiles(filesToDelete, true);
}
//No previous mapping, delete unzipped files on JVM exit
else
{
deleteFiles(filesToDelete, false);
}
}
functionFile = UNZIPPED_ENTRIES.get(theInterface).get(relativePath);
if(functionFile == null)
{
throw new LinkingException("Unable to find file inside of zip\n" +
"Method: " + method.getName() + "\n" +
"Relative Path: " + relativePath + "\n" +
"Zip File: " + interfaceLocation.getAbsolutePath());
}
}
//Code source is a directory, it code should not be inside a jar/ear/war/zip etc.
else
{
functionFile = new File(interfaceLocation, relativePath);
}
return functionFile;
}
private static void deleteFiles(Collection files, boolean deleteNow)
{
ArrayList sortedFiles = new ArrayList(files);
Collections.sort(sortedFiles);
//Delete files in the opposite order so that files are deleted before their containing directories
if(deleteNow)
{
for(int i = sortedFiles.size() - 1; i >= 0; i--)
{
sortedFiles.get(i).delete();
}
}
//Delete files in the existing order because the files will be deleted on exit in the opposite order
else
{
for(File file : sortedFiles)
{
file.deleteOnExit();
}
}
}
/**
* Cache used by {@link #getClassLocation(java.lang.Class)}}.
*/
private static final ConcurrentHashMap, File> CLASS_LOCATIONS = new ConcurrentHashMap, File>();
private static File getClassLocation(Class> clazz)
{
if(!CLASS_LOCATIONS.containsKey(clazz))
{
try
{
URL url = clazz.getProtectionDomain().getCodeSource().getLocation();
File file = new File(url.toURI().getPath()).getCanonicalFile();
if(!file.exists())
{
throw new LinkingException("Incorrectly resolved location of class\n" +
"class: " + clazz.getCanonicalName() + "\n" +
"location: " + file.getAbsolutePath());
}
CLASS_LOCATIONS.put(clazz, file);
}
catch(IOException e)
{
throw new LinkingException("Unable to determine location of " + clazz.getCanonicalName(), e);
}
catch(URISyntaxException e)
{
throw new LinkingException("Unable to determine location of " + clazz.getCanonicalName(), e);
}
}
return CLASS_LOCATIONS.get(clazz);
}
private static class UnzipResult
{
final Map unzippedMapping;
File rootDirectory;
private UnzipResult(Map mapping, File root)
{
this.unzippedMapping = mapping;
this.rootDirectory = root;
}
}
/**
* Unzips the file located at {@code zipLocation}.
*
* @param zipLocation the location of the file zip
* @return resulting files from unzipping
* @throws LinkingException if unable to unzip the zip file for any reason
*/
private static UnzipResult unzip(File zipLocation)
{
ZipFile zip;
try
{
zip = new ZipFile(zipLocation);
}
catch(IOException e)
{
throw new LinkingException("Unable to open zip file\n" +
"zip location: " + zipLocation.getAbsolutePath(), e);
}
try
{
//Mapping from entry names to the unarchived location on disk
Map entryMap = new HashMap();
//Destination
File unzipDir = new File(System.getProperty("java.io.tmpdir"), "linked_" + UUID.randomUUID().toString());
for(Enumeration extends ZipEntry> entries = zip.entries(); entries.hasMoreElements(); )
{
ZipEntry entry = entries.nextElement();
//Directory
if(entry.isDirectory())
{
File destDir = new File(unzipDir, entry.getName());
destDir.mkdirs();
entryMap.put(entry.getName(), destDir);
}
//File
else
{
//File should not exist, but confirm it
File destFile = new File(unzipDir, entry.getName());
if(destFile.exists())
{
throw new LinkingException("Cannot unzip file, randomly generated path already exists\n" +
"generated path: " + destFile.getAbsolutePath() + "\n" +
"zip file: " + zipLocation.getAbsolutePath());
}
destFile.getParentFile().mkdirs();
//Unarchive
try
{
final int BUFFER_SIZE = 2048;
OutputStream dest = new BufferedOutputStream(new FileOutputStream(destFile), BUFFER_SIZE);
try
{
InputStream entryStream = zip.getInputStream(entry);
try
{
byte[] buffer = new byte[BUFFER_SIZE];
int count;
while((count = entryStream.read(buffer, 0, BUFFER_SIZE)) != -1)
{
dest.write(buffer, 0, count);
}
dest.flush();
}
finally
{
entryStream.close();
}
}
finally
{
dest.close();
}
}
catch(IOException e)
{
throw new LinkingException("Unable to unzip file entry\n" +
"entry: " + entry.getName() + "\n" +
"zip location: " + zipLocation.getAbsolutePath() + "\n" +
"destination file: " + destFile.getAbsolutePath(), e);
}
entryMap.put(entry.getName(), destFile);
}
}
return new UnzipResult(Collections.unmodifiableMap(entryMap), unzipDir);
}
finally
{
try
{
zip.close();
}
catch(IOException ex)
{
throw new LinkingException("Unable to close zip file: " + zipLocation.getAbsolutePath(), ex);
}
}
}
private static class ReturnTypeInfo
{
Class>[] returnTypes;
Class>[][] returnTypeParameters;
private ReturnTypeInfo(Class>[] returnTypes, Class>[][] returnTypeParameters)
{
this.returnTypes = returnTypes;
this.returnTypeParameters = returnTypeParameters;
}
}
private static ReturnTypeInfo getReturnTypes(Method method)
{
//The type-erasured return type of the method
Class> methodReturn = method.getReturnType();
//The return type of the method with type information
Type genericReturn = method.getGenericReturnType();
//The return types to be determined
Class>[] returnTypes;
Class>[][] returnTypeGenericParameters;
//0 return arguments
if(methodReturn.equals(void.class))
{
returnTypes = new Class>[0];
returnTypeGenericParameters = new Class>[0][0];
}
//1 return argument
else if(!MatlabReturns.ReturnN.class.isAssignableFrom(methodReturn))
{
//MatlabNumberArray subclasses are allowed to be parameterized
if(MatlabNumberArray.class.isAssignableFrom(methodReturn))
{
if(genericReturn instanceof ParameterizedType)
{
Type[] parameterizedTypes = ((ParameterizedType) genericReturn).getActualTypeArguments();
Class> parameter = getMatlabNumberArrayParameter(parameterizedTypes[0], methodReturn, method);
returnTypeGenericParameters = new Class>[][] { { parameter } };
}
else
{
throw new LinkingException(method + " must parameterize " + methodReturn.getCanonicalName());
}
}
else if(!methodReturn.equals(genericReturn))
{
throw new LinkingException(method + " may not have a return type that uses generics");
}
else
{
returnTypeGenericParameters = new Class>[1][0];
}
returnTypes = new Class>[]{ methodReturn };
}
//2 or more return arguments
else
{
if(genericReturn instanceof ParameterizedType)
{
Type[] parameterizedTypes = ((ParameterizedType) genericReturn).getActualTypeArguments();
returnTypes = new Class>[parameterizedTypes.length];
returnTypeGenericParameters = new Class>[parameterizedTypes.length][];
for(int i = 0; i < parameterizedTypes.length; i++)
{
Type type = parameterizedTypes[i];
if(type instanceof Class)
{
Class returnType = (Class) type;
if(MatlabNumberArray.class.isAssignableFrom(returnType))
{
throw new LinkingException(method + " must parameterize " + returnType.getCanonicalName());
}
returnTypes[i] = returnType;
returnTypeGenericParameters[i] = new Class>[0];
}
else if(type instanceof GenericArrayType)
{
returnTypes[i] = getClassOfArrayType((GenericArrayType) type, method);
returnTypeGenericParameters[i] = new Class>[0];
}
else if(type instanceof ParameterizedType)
{
//MatlabNumberArray subclasses are allowed to be parameterized
ParameterizedType parameterizedType = (ParameterizedType) type;
Type rawType = parameterizedType.getRawType();
if(rawType instanceof Class && MatlabNumberArray.class.isAssignableFrom((Class>) rawType))
{
Class> returnType = (Class>) rawType;
returnTypes[i] = returnType;
Class> parameter = getMatlabNumberArrayParameter(
parameterizedType.getActualTypeArguments()[0], returnType, method);
returnTypeGenericParameters[i] = new Class>[] { parameter };
}
else
{
throw new LinkingException(method + " may not parameterize " +
methodReturn.getCanonicalName() + " with a parameterized type");
}
}
else if(type instanceof WildcardType)
{
throw new LinkingException(method + " may not parameterize " + methodReturn.getCanonicalName() +
" with a wild card type");
}
else if(type instanceof TypeVariable)
{
throw new LinkingException(method + " may not parameterize " + methodReturn.getCanonicalName() +
" with a generic type");
}
else
{
throw new LinkingException(method + " may not parameterize " + methodReturn.getCanonicalName() +
" with " + type);
}
}
}
else
{
throw new LinkingException(method + " must parameterize " + methodReturn.getCanonicalName());
}
}
return new ReturnTypeInfo(returnTypes, returnTypeGenericParameters);
}
private static Class> getMatlabNumberArrayParameter(Type type, Class> matlabArrayClass, Method method)
{
Class> parameter;
if(type instanceof GenericArrayType)
{
parameter = getClassOfArrayType((GenericArrayType) type, method);
ClassInfo paramInfo = ClassInfo.getInfo(parameter);
boolean validParameter =
((matlabArrayClass.equals(MatlabInt8Array.class) && byte.class.equals(paramInfo.baseComponentType)) ||
(matlabArrayClass.equals(MatlabInt16Array.class) && short.class.equals(paramInfo.baseComponentType)) ||
(matlabArrayClass.equals(MatlabInt32Array.class) && int.class.equals(paramInfo.baseComponentType)) ||
(matlabArrayClass.equals(MatlabInt64Array.class) && long.class.equals(paramInfo.baseComponentType)) ||
(matlabArrayClass.equals(MatlabSingleArray.class) && float.class.equals(paramInfo.baseComponentType)) ||
(matlabArrayClass.equals(MatlabDoubleArray.class) && double.class.equals(paramInfo.baseComponentType)));
if(!validParameter)
{
throw new LinkingException(method + " may not parameterize " + matlabArrayClass.getCanonicalName() +
" with " + parameter.getCanonicalName());
}
}
else
{
throw new LinkingException(method + " may not parameterize " + matlabArrayClass.getCanonicalName() +
" with " + type);
}
return parameter;
}
/**
*
* @param type
* @param method used for exception message only
* @return
*/
private static Class> getClassOfArrayType(GenericArrayType type, Method method)
{
int dimensions = 1;
Type componentType = type.getGenericComponentType();
while(!(componentType instanceof Class))
{
dimensions++;
if(componentType instanceof GenericArrayType)
{
componentType = ((GenericArrayType) componentType).getGenericComponentType();
}
else if(componentType instanceof TypeVariable)
{
throw new LinkingException(method + " may not parameterize " +
method.getReturnType().getCanonicalName() + " with a generic array");
}
else
{
throw new LinkingException(method + " may not parameterize " +
method.getReturnType().getCanonicalName() + " with an array of type " + type);
}
}
return ArrayUtils.getArrayClass((Class>) componentType, dimensions);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy