/**
* Copyright 2012 52°North Initiative for Geospatial Open Source Software GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.n52.geoar.newdata;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import org.n52.geoar.newdata.Annotations;
import org.n52.geoar.newdata.DataSource;
import org.n52.geoar.newdata.Filter;
import org.n52.geoar.GeoARApplication;
import org.n52.geoar.newdata.CheckList.CheckManager;
import org.n52.geoar.newdata.PluginLoader.PluginInfo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Parcel;
import dalvik.system.DexClassLoader;
import dalvik.system.DexFile;
public class InstalledPluginHolder extends PluginHolder {
private List<DataSourceHolder> mDataSources = new ArrayList<DataSourceHolder>();
private File pluginFile;
private Long version;
private String identifier;
private boolean loaded = false;
private String description;
private String name;
private Context mPluginContext;
private Bitmap pluginIcon;
private String publisher;
@CheckManager
private CheckList<InstalledPluginHolder>.Checker mChecker;
private DexClassLoader mPluginDexClassLoader;
private boolean iconLoaded;
private static final Logger LOG = LoggerFactory
.getLogger(InstalledPluginHolder.class);
public InstalledPluginHolder(PluginInfo pluginInfo) {
this.version = pluginInfo.version;
this.identifier = pluginInfo.identifier;
this.description = pluginInfo.description;
this.name = pluginInfo.name;
this.pluginFile = pluginInfo.pluginFile;
this.publisher = pluginInfo.publisher;
}
@Override
public String getIdentifier() {
return identifier;
}
@Override
public String getName() {
return name;
}
@Override
public String getPublisher() {
return publisher;
}
@Override
public Long getVersion() {
return version;
}
public String getDescription() {
return description;
};
/**
* Changes to the returned list do not affect loaded data sources
*
* @return
*/
public List<DataSourceHolder> getDataSources() {
if (!loaded) {
try {
loadPlugin();
} catch (IOException e) {
e.printStackTrace();
}
}
return mDataSources;
}
public File getPluginFile() {
return pluginFile;
}
public void setChecked(boolean state) {
mChecker.setChecked(state);
}
public boolean isChecked() {
return mChecker.isChecked();
}
@SuppressLint("NewApi")
private void loadPlugin() throws IOException {
mDataSources.clear();
String pluginBaseFileName = getPluginFile().getName().substring(0,
getPluginFile().getName().lastIndexOf("."));
// Check if the Plugin exists
if (!getPluginFile().exists())
throw new FileNotFoundException("Not found: " + getPluginFile());
File tmpDir = GeoARApplication.applicationContext.getDir(
pluginBaseFileName, 0);
Enumeration<String> entries = DexFile
.loadDex(
getPluginFile().getAbsolutePath(),
tmpDir.getAbsolutePath() + "/" + pluginBaseFileName
+ ".dex", 0).entries();
// Path for optimized dex equals path which will be used by
// dexClassLoader
// create separate ClassLoader for each plugin
mPluginDexClassLoader = new DexClassLoader(getPluginFile()
.getAbsolutePath(), tmpDir.getAbsolutePath(), null,
GeoARApplication.applicationContext.getClassLoader());
try {
while (entries.hasMoreElements()) {
// Check each classname for annotations
String entry = entries.nextElement();
Class<?> entryClass = mPluginDexClassLoader.loadClass(entry);
if (entryClass
.isAnnotationPresent(Annotations.DataSource.class)) {
// Class is a annotated as datasource
if (org.n52.geoar.newdata.DataSource.class
.isAssignableFrom(entryClass)) {
// Class is a datasource
@SuppressWarnings("unchecked")
DataSourceHolder dataSourceHolder = new DataSourceHolder(
(Class<? extends DataSource<? super Filter>>) entryClass,
this);
mDataSources.add(dataSourceHolder);
} else {
LOG.error("Datasource " + entryClass.getSimpleName()
+ " is not implementing DataSource interface");
// TODO handle error, somehow propagate back to user
}
}
}
} catch (ClassNotFoundException e) {
throw new RuntimeException("Dex changed");
} catch (LinkageError e) {
LOG.error("Data source " + getName() + " uses invalid class, "
+ e.getMessage());
}
loaded = true;
}
/**
* Returns a {@link Context} wrapping the
* {@link GeoARApplication#applicationContext}, but returning the value of
* {@link InstalledPluginHolder#getPluginResources()} as
* {@link Context#getResources()} return value.
*
* @return
*/
public Context getPluginContext() {
if (mPluginContext == null) {
mPluginContext = new PluginContext(
GeoARApplication.applicationContext, this);
}
return mPluginContext;
}
public ClassLoader getPluginClassLoader() {
return mPluginDexClassLoader;
}
@Override
public Bitmap getPluginIcon() {
if (!iconLoaded) {
try {
iconLoaded = true;
ZipFile zipFile = new ZipFile(pluginFile);
ZipEntry pluginIconEntry = zipFile.getEntry("icon.png");
if (pluginIconEntry != null) {
pluginIcon = BitmapFactory.decodeStream(zipFile
.getInputStream(pluginIconEntry));
} else {
LOG.info("Plugin " + getName() + " has no icon");
}
} catch (IOException e) {
LOG.error("Plugin " + getName() + " has an invalid icon");
}
}
return pluginIcon;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(getClass().getName());
super.writeToParcel(dest, flags);
}
public void saveState(ObjectOutputStream objectOutputStream)
throws IOException {
objectOutputStream.writeBoolean(isChecked());
for (DataSourceHolder dataSource : mDataSources) {
dataSource.saveState(objectOutputStream);
}
}
public void restoreState(PluginStateInputStream objectInputStream)
throws IOException {
setChecked(objectInputStream.readBoolean());
for (DataSourceHolder dataSource : mDataSources) {
dataSource.restoreState(objectInputStream);
}
}
/**
* Should be called after all state initialization took place, e.g. after
* {@link InstalledPluginHolder#restoreState(PluginStateInputStream)}
*/
public void postConstruct() {
for (DataSourceHolder dataSource : mDataSources) {
dataSource.postConstruct();
}
}
}