/**
*
* Funf: Open Sensing Framework
* Copyright (C) 2010-2011 Nadav Aharony, Wei Pan, Alex Pentland.
* Acknowledgments: Alan Gardner
* Contact: nadav@media.mit.edu
*
* This file is part of Funf.
*
* Funf is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation, either version 3 of
* the License, or (at your option) any later version.
*
* Funf 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with Funf. If not, see <http://www.gnu.org/licenses/>.
*
*/
package edu.mit.media.funf.pipeline;
import java.io.IOException;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import android.content.Context;
import android.util.Log;
import com.google.gson.Gson;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.TypeAdapter;
import com.google.gson.internal.Streams;
import com.google.gson.internal.bind.JsonTreeReader;
import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;
import edu.mit.media.funf.Schedule;
import edu.mit.media.funf.Schedule.DefaultSchedule;
import edu.mit.media.funf.config.ConfigurableTypeAdapterFactory;
import edu.mit.media.funf.config.DefaultRuntimeTypeAdapterFactory;
import edu.mit.media.funf.config.RuntimeTypeAdapterFactory;
import edu.mit.media.funf.json.JsonUtils;
import edu.mit.media.funf.util.AnnotationUtil;
import edu.mit.media.funf.util.LogUtil;
public class PipelineFactory implements RuntimeTypeAdapterFactory {
public static final String SCHEDULES_FIELD_NAME = "schedules";
public static final TypeToken<Map<String,Schedule>> SCHEDULES_FIELD_TYPE_TOKEN = new TypeToken<Map<String,Schedule>>(){};
public static final String SCHEDULE = "@schedule";
private RuntimeTypeAdapterFactory delegate;
/**
* Use the base class as the default class.
* @param context
* @param baseClass
*/
public PipelineFactory(Context context) {
this(context, BasicPipeline.class);
}
/**
* @param context
* @param baseClass
* @param defaultClass Setting this to null will cause a ParseException if the runtime type information is incorrect or unavailable.
*/
public PipelineFactory(Context context, Class<? extends Pipeline> defaultClass) {
assert context != null;
this.delegate = new DefaultRuntimeTypeAdapterFactory<Pipeline>(context, Pipeline.class, defaultClass, new ConfigurableTypeAdapterFactory());
}
@Override
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
TypeAdapter<T> delegateAdapter = delegate.create(gson, type);
if (delegateAdapter != null) {
delegateAdapter = new ScheduleAnnotatedTypeAdapter<T>(gson, type, delegateAdapter);
}
return delegateAdapter;
}
private static class ScheduleAnnotatedTypeAdapter<T> extends TypeAdapter<T> {
private Gson gson;
private TypeToken<T> type;
private TypeAdapter<T> delegateAdapter;
private ScheduleAnnotatedTypeAdapter(Gson gson, TypeToken<T> type, TypeAdapter<T> delegateAdapter) {
this.gson = gson;
this.type = type;
this.delegateAdapter = delegateAdapter;
}
@Override
public void write(JsonWriter out, T value) throws IOException {
// Cannot determine what was annotated, and what was in schedules
delegateAdapter.write(out, value);
}
@Override
public T read(JsonReader in) throws IOException {
JsonObject el = Streams.parse(in).getAsJsonObject();
// Strip existing json schedules
JsonObject directSchedules = el.has(SCHEDULES_FIELD_NAME) ? el.remove(SCHEDULES_FIELD_NAME).getAsJsonObject() : new JsonObject();
// Strip off @schedule annotations to update schedules attribute
// Load annotated schedules
JsonObject annotatedSchedules = new JsonObject();
// TODO: make this recursive to have nested schedules with dot notation
for (Map.Entry<String,JsonElement> entry : el.entrySet()) {
JsonElement entryEl = entry.getValue();
if (entryEl.isJsonObject()) {
JsonObject subConfig = entryEl.getAsJsonObject();
if (subConfig.has(SCHEDULE)) {
JsonElement scheduleConfig = subConfig.remove(SCHEDULE);
annotatedSchedules.add(entry.getKey(), scheduleConfig);
}
}
}
T result = delegateAdapter.read(new JsonTreeReader(el));
// If there is a 'schedules' field, inject schedules
Field schedulesField = AnnotationUtil.getField(SCHEDULES_FIELD_NAME, result.getClass());
if (schedulesField != null) {
///// Default schedules for every schedulable top level object
JsonObject defaultSchedules = new JsonObject();
List<Field> fields = new ArrayList<Field>();
for (Field field : AnnotationUtil.getAllFields(fields, result.getClass())) {
DefaultSchedule defaultSchedule = field.getAnnotation(DefaultSchedule.class);
if (defaultSchedule == null) {
boolean currentAccessibility = field.isAccessible();
try {
field.setAccessible(true);
Object fieldValue = field.get(result);
if (fieldValue != null) {
Class<?> fieldRuntimeClass = field.get(result).getClass();
defaultSchedule = fieldRuntimeClass.getAnnotation(DefaultSchedule.class);
}
} catch (IllegalArgumentException e) {
Log.e(LogUtil.TAG, "Bad access of configurable fields!!", e);
} catch (IllegalAccessException e) {
Log.e(LogUtil.TAG, "Bad access of configurable fields!!", e);
} finally {
field.setAccessible(currentAccessibility);
}
}
if (defaultSchedule != null) {
defaultSchedules.add(field.getName(), gson.toJsonTree(defaultSchedule, DefaultSchedule.class));
}
}
JsonObject schedulesJson = directSchedules;
JsonUtils.deepCopyOnto(defaultSchedules, schedulesJson, false); // Copy in default schedules, but do not replace
JsonUtils.deepCopyOnto(annotatedSchedules, schedulesJson, true); // Override with annotations
// For each schedule find default schedule, fill in remainder
Map<String,Schedule> schedules = gson.fromJson(schedulesJson, new TypeToken<Map<String,Schedule>>(){}.getType());
boolean currentAccessibility = schedulesField.isAccessible();
try {
schedulesField.setAccessible(true);
schedulesField.set(result, schedules);
} catch (IllegalArgumentException e) {
Log.e(LogUtil.TAG, "Bad access of configurable fields!!", e);
} catch (IllegalAccessException e) {
Log.e(LogUtil.TAG, "Bad access of configurable fields!!", e);
} finally {
schedulesField.setAccessible(currentAccessibility);
}
}
return result;
}
}
@Override
public <T> Class<? extends T> getRuntimeType(JsonElement el, TypeToken<T> type) {
return delegate.getRuntimeType(el, type);
}
}