/*
Copyright 2012-2013, Polyvi Inc. (http://polyvi.github.io/openxface)
This program is distributed under the terms of the GNU General Public License.
This file is part of xFace.
xFace is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
xFace 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 for more details.
You should have received a copy of the GNU General Public License
along with xFace. If not, see <http://www.gnu.org/licenses/>.
*/
package com.polyvi.xface.plugin.api;
import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.jar.JarFile;
import java.util.jar.Manifest;
import com.polyvi.xface.core.XConfiguration;
import com.polyvi.xface.core.XISystemContext;
import com.polyvi.xface.extension.XExtensionContext;
import com.polyvi.xface.util.XLog;
import dalvik.system.DexClassLoader;
public class XPluginLoader {
private static final String CLASS_NAME = XPluginLoader.class
.getSimpleName();
private static final String PLUGIN_JAR_ENTRY_TAG = "Main-Class";
private static final String JAR_SUFFIX = ".jar";
/**
* 管理插件的map
*/
private ConcurrentHashMap<String, XPluginBase> mExtenalPlugins;
private XIWebContext mPluginExecuteContext;
/**
* jar包的loader map
*/
private static HashMap<String, XEntry> mJarLoaderMap = new HashMap<String, XEntry>();
/**
* jar库存放的目录
*/
private static String mJarFolder;
/**
* 可以目录 系统动态加载jar文件需要使用
*/
private static String mWriteableDir;
/**
* 构造一个键值对结构
*/
private static class XEntry {
public XEntry(ClassLoader loader, HashMap<String, String> map) {
mLoader = loader;
mPluginDescription = map;
}
public ClassLoader mLoader;
public HashMap<String, String> mPluginDescription;
}
public XPluginLoader(XIWebContext webCtx) {
mPluginExecuteContext = webCtx;
mExtenalPlugins = new ConcurrentHashMap<String, XPluginBase>();
}
/**
* 加载插件
*
* @param cordovaInterface
*/
public ConcurrentHashMap<String, XPluginBase> loadPlugins(
XISystemContext ctx, XExtensionContext extContext) {
// 目前有三种加载插件的方式
// 从xml中读取插件
loadPluginFromMap(extContext,
extContext.getSystemContext().getContext().getClassLoader(),
XConfiguration.getInstance().readPluginsConfig());
// 从jar包中读取插件
loadPluginFromJar(extContext);
// 从xml中读取插件描述 具体插件的配置通过描述返回
loadPluginByDescription(extContext,
extContext.getSystemContext().getContext().getClassLoader(),
XConfiguration.getInstance().readPluginDescriptions());
return mExtenalPlugins;
}
private void loadPluginFromMap(XExtensionContext extContext,
ClassLoader loader, HashMap<String, String> map) {
Iterator<Map.Entry<String, String>> iter = map.entrySet().iterator();
while (iter.hasNext()) {
Map.Entry<String, String> entry = iter.next();
XPluginBase p = createPlugin(extContext, entry.getValue(), loader);
if (null != p) {
registerPlugin(entry.getKey(), p);
}
}
}
/**
* 卸载插件 在app被卸载的时候需要清空插件
*/
public void unloadPlugins() {
// TODO 是否需要发送一个事件给每个插件
mExtenalPlugins.clear();
}
/**
* 创建插件
*
* @param className
* @param loader
* @return
*/
private XPluginBase createPlugin(XExtensionContext ectx, String className,
ClassLoader loader) {
try {
Class<?> cls = loader.loadClass(className);
Object obj = cls.newInstance();
if (!(obj instanceof XPluginBase)) {
XLog.e(CLASS_NAME, "Class (" + className
+ ") not a sub class of XExtension!");
return null;
}
XPluginBase plugin = (XPluginBase) obj;
plugin.initialize(ectx, mPluginExecuteContext);
return plugin;
} catch (ClassNotFoundException e) {
XLog.e(CLASS_NAME, "Class:" + className + " not found!");
} catch (InstantiationException e) {
XLog.e(CLASS_NAME, "Can't create object of class " + className);
} catch (IllegalAccessException e) {
XLog.e(CLASS_NAME, "Can't create object of class " + className);
}
return null;
}
/**
* 注册插件
*
* @param name
* @param plugin
*/
private void registerPlugin(String name, XPluginBase plugin) {
mExtenalPlugins.put(name, plugin);
}
/**
* 获取插件
*
* @return
*/
public ConcurrentHashMap<String, XPluginBase> getPlugins() {
return mExtenalPlugins;
}
/**
* 设置动态记载库的目录以及提供一个系统可写目录(系统需要将dex文件缓存到这个可写目录)
*
* @param jarDir
*/
public static void setJarDir(String jarDir, String writeableDir) {
mJarFolder = jarDir;
mWriteableDir = writeableDir;
}
/**
* 根据插件描述加载插件
*/
private void loadPluginByDescription(XExtensionContext ctx,
ClassLoader loader, Set<String> deses) {
try {
Iterator<String> iter = deses.iterator();
while (iter.hasNext()) {
String des = iter.next();
Class<?> libProviderClazz = loader.loadClass(des);
Object obj = libProviderClazz.newInstance();
if (obj instanceof XIPluginDescription) {
XIPluginDescription desObj = (XIPluginDescription) libProviderClazz
.newInstance();
HashMap<String, String> map = desObj.getPluginDesciption();
loadPluginFromMap(ctx, loader, map);
} else {
XLog.e(CLASS_NAME, "instance XPluginDescription error");
}
}
} catch (ClassNotFoundException e) {
XLog.e(CLASS_NAME, "ClassNotFoundException when load plugin description");
e.printStackTrace();
} catch (IllegalAccessException e) {
XLog.e(CLASS_NAME, "IllegalAccessException when load plugin description");
e.printStackTrace();
} catch (InstantiationException e) {
XLog.e(CLASS_NAME, "InstantiationException when load plugin description");
e.printStackTrace();
}
}
/**
* 动态从jar文件中读取插件配置
*
* @param ctx
* @param jarPath
* jar文件的文件夹
* @return
*/
private void loadPluginFromJar(XExtensionContext ctx) {
// 参数检查
if (null == mJarFolder || null == mWriteableDir
|| !new File(mWriteableDir).exists()
|| !new File(mWriteableDir).exists()) {
return;
}
// 读取思路:
// 1. 通过Manifest读取插件描述类 插件描述类必须实现接口XIPluginDescription
// 2. 通过描述类找到对应的插件类以及js上的访问名称
// 3. TODO 如何动态卸载?
// 4. FIXME:如果jar动态调用so文件,还需要测试
if (!mJarLoaderMap.isEmpty()) {
// 通过这里保证引擎启动 只加载一次
for (XEntry value : mJarLoaderMap.values()) {
loadPluginFromMap(ctx, value.mLoader, value.mPluginDescription);
}
return;
}
File[] files = new File(mJarFolder).listFiles(new ModuleFilter());
for (File jar : files) {
try {
DexClassLoader cl = new DexClassLoader(jar.getAbsolutePath(),
mWriteableDir, null, ctx.getSystemContext()
.getContext().getClassLoader());
JarFile jarFile = new JarFile(jar);
Manifest manifest = jarFile.getManifest();
String pluginDes = manifest.getMainAttributes().getValue(
PLUGIN_JAR_ENTRY_TAG);
if (null == pluginDes) {
continue;
}
Class<?> libProviderClazz = cl.loadClass(pluginDes);
XIPluginDescription obj = (XIPluginDescription) libProviderClazz
.newInstance();
HashMap<String, String> map = obj.getPluginDesciption();
loadPluginFromMap(ctx, cl, map);
mJarLoaderMap.put(jar.getAbsolutePath(), new XEntry(cl, map));
} catch (ClassNotFoundException e) {
handleLoadExceptionFromJar(e, jar,
" Load class dynamicly error.");
} catch (IllegalAccessException e) {
handleLoadExceptionFromJar(e, jar,
" lllegal access error when instance plugin.");
} catch (InstantiationException e) {
handleLoadExceptionFromJar(e, jar, " instance plugin error.");
} catch (IOException e) {
handleLoadExceptionFromJar(e, jar, "init jar package error.");
}
}
}
/**
* 处理从jar包中加载类的异常
*
* @param e
* 异常对象
* @param jar
* jar包文件
* @param tag
* log信息
*/
private void handleLoadExceptionFromJar(Exception e, File jar, String log) {
mJarLoaderMap.remove(jar.getAbsolutePath());
XLog.e(CLASS_NAME, log);
e.printStackTrace();
}
/**
* 过滤器 仅仅加载jar文件
*/
private static class ModuleFilter implements FileFilter {
@Override
public boolean accept(File file) {
return file.isFile()
&& file.getName().toLowerCase().endsWith(JAR_SUFFIX);
}
}
}