Back to project page geoar-app.
The source code is released under:
Apache License
If you think the Android project geoar-app listed in this page is inappropriate, such as containing malicious code/tools or violating the copyright, please email info at java2s dot com, thanks.
/** * Copyright 2012 52North Initiative for Geospatial Open Source Software GmbH */*from w ww .j a v a 2 s. co m*/ * 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.IOException; import java.io.ObjectOutputStream; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.util.ArrayList; import java.util.HashMap; import java.util.Map; import org.n52.geoar.newdata.Annotations; import org.n52.geoar.newdata.DataSource; import org.n52.geoar.newdata.Filter; import org.n52.geoar.newdata.Visualization; import org.n52.geoar.newdata.Annotations.DefaultInstances; import org.n52.geoar.newdata.Annotations.DefaultSettingsSet; import org.n52.geoar.newdata.Annotations.PostConstruct; import org.n52.geoar.newdata.Annotations.SharedHttpClient; import org.n52.geoar.newdata.Annotations.SupportedVisualization; import org.n52.geoar.newdata.Annotations.SystemService; import org.n52.geoar.newdata.DataSourceInstanceSettingsDialogActivity.SettingsResultListener; import org.n52.geoar.settings.SettingsHelper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import android.app.Activity; import android.content.Context; import android.content.Intent; import android.os.Handler; import android.os.Message; import android.os.Parcel; import android.os.Parcelable; public class DataSourceHolder implements Parcelable { public static final Parcelable.Creator<DataSourceHolder> CREATOR = new Parcelable.Creator<DataSourceHolder>() { @Override public DataSourceHolder createFromParcel(Parcel in) { int id = in.readInt(); // Find DataSourceHolder with provided id for (DataSourceHolder holder : PluginLoader.getDataSources()) { if (holder.id == id) { return holder; } } return null; } @Override public DataSourceHolder[] newArray(int size) { return new DataSourceHolder[size]; } }; private static final int CLEAR_CACHE = 1; private static final int CLEAR_CACHE_AFTER_DEACTIVATION_DELAY = 10000; private static final Logger LOG = LoggerFactory .getLogger(DataSourceHolder.class); private static int nextId = 0; private static Map<Class<? extends Visualization>, Visualization> visualizationMap = new HashMap<Class<? extends Visualization>, Visualization>(); private byte cacheZoomLevel; private Class<? extends DataSource<? super Filter>> dataSourceClass; private Handler dataSourceHandler = new Handler(new Handler.Callback() { @Override public boolean handleMessage(Message msg) { if (msg.what == CLEAR_CACHE) { // Delayed clearing of cache after datasource has been // deactivated for (DataSourceInstanceHolder instance : mDataSourceInstances) { instance.deactivate(); } return true; } return false; } }); private String description; private Class<? extends Filter> filterClass; private final int id = nextId++; private byte maxZoomLevel; private CheckList<DataSourceInstanceHolder> mDataSourceInstances; private long minReloadInterval; private boolean mInstanceable; private byte minZoomLevel; private InstalledPluginHolder mPluginHolder; private String name; private CheckList<Visualization> visualizations; private Method nameCallbackMethod; /** * Wrapper for a {@link DataSource}. Provides access to general settings of * a data source, as well as its {@link Filter} implementation and supported * {@link Visualization}s. This holder also maintains the state of a * {@link DataSource} as it automatically activates and deactivates a data * source and clears its {@link DataCache} if required. * * @param dataSourceClass * @param pluginHolder * {@link InstalledPluginHolder} containing this data source */ @SuppressWarnings("unchecked") public DataSourceHolder( Class<? extends DataSource<? super Filter>> dataSourceClass, InstalledPluginHolder pluginHolder) { this.mPluginHolder = pluginHolder; this.dataSourceClass = dataSourceClass; Annotations.DataSource dataSourceAnnotation = dataSourceClass .getAnnotation(Annotations.DataSource.class); if (dataSourceAnnotation == null) { throw new RuntimeException("Class not annotated as datasource"); } mInstanceable = SettingsHelper.hasSettings(dataSourceClass); if (dataSourceAnnotation.name().resId() >= 0) { name = pluginHolder.getPluginContext().getString( dataSourceAnnotation.name().resId()); } else { name = dataSourceAnnotation.name().value(); } description = dataSourceAnnotation.description(); minReloadInterval = dataSourceAnnotation.minReloadInterval(); cacheZoomLevel = dataSourceAnnotation.cacheZoomLevel(); minZoomLevel = dataSourceAnnotation.minZoomLevel(); maxZoomLevel = dataSourceAnnotation.maxZoomLevel(); // Find name callback for (Method method : dataSourceClass.getMethods()) { if (method.isAnnotationPresent(Annotations.NameCallback.class)) { if (String.class.isAssignableFrom(method.getReturnType())) { nameCallbackMethod = method; } else { LOG.error("Data source " + name + " has an invalid NameCallback"); } } } // Find filter by getting the actual generic parameter type of the // implemented DataSource interface Class<?> currentClass = dataSourceClass; while (currentClass != null) { Type[] interfaces = currentClass.getGenericInterfaces(); for (Type interfaceType : interfaces) { ParameterizedType type = (ParameterizedType) interfaceType; if (!type.getRawType().equals(DataSource.class)) { continue; } filterClass = (Class<? extends Filter>) type .getActualTypeArguments()[0]; } if (filterClass == null) { currentClass = currentClass.getSuperclass(); } else { break; } } if (filterClass == null) { throw new RuntimeException( "Data source does not specify a filter class"); } } private void createDefaultInstances() { if (!instanceable()) { return; } DefaultInstances defaultInstances = dataSourceClass .getAnnotation(DefaultInstances.class); if (defaultInstances == null) { return; } settingsSets: for (DefaultSettingsSet defaultSettingsSet : defaultInstances .value()) { for (DataSourceInstanceHolder instance : getInstances()) { if (SettingsHelper.isEqualSettings(defaultSettingsSet, instance.getDataSource())) { continue settingsSets; } } DataSourceInstanceHolder instance = addInstance(); SettingsHelper.applyDefaultSettings(defaultSettingsSet, instance.getDataSource()); } } /** * Prevents datasource from getting unloaded. Should be called when * datasource is added to map/ar. */ @Deprecated public void activate() { LOG.info("Activating data source " + getName()); // prevents clearing of cache by removing messages dataSourceHandler.removeMessages(CLEAR_CACHE); } public boolean areAllChecked() { return mDataSourceInstances.allChecked(); } /** * Queues unloading of datasource and cached data */ @Deprecated public void deactivate() { LOG.info("Deactivating data source " + getName()); dataSourceHandler.sendMessageDelayed( dataSourceHandler.obtainMessage(CLEAR_CACHE), CLEAR_CACHE_AFTER_DEACTIVATION_DELAY); } @Override public int describeContents() { return 0; } public byte getCacheZoomLevel() { return cacheZoomLevel; } public String getDescription() { return description; } public Class<? extends Filter> getFilterClass() { return filterClass; } public String getIdentifier() { return dataSourceClass.getSimpleName(); } public CheckList<DataSourceInstanceHolder> getInstances() { if (mDataSourceInstances == null) { initializeInstances(); } return mDataSourceInstances; } public InstalledPluginHolder getPluginHolder() { return mPluginHolder; } /** * Should be called after all state initialization took place, e.g. after * {@link DataSourceHolder#restoreState(PluginStateInputStream)} */ void postConstruct() { createDefaultInstances(); } public byte getMaxZoomLevel() { return maxZoomLevel; } public long getMinReloadInterval() { return minReloadInterval; } public byte getMinZoomLevel() { return minZoomLevel; } public String getName() { return name; } public Method getNameCallbackMethod() { return nameCallbackMethod; } /** * Returns {@link Visualization}s supported by this data source. * * As {@link DataSource}s get lazily initialized, this method will not * return any {@link Visualization}s until the underlying {@link DataSource} * is accessed, e.g. by {@link DataSourceHolder#getDataSource()}. * * @return */ public CheckList<Visualization> getVisualizations() { if (visualizations == null) { initializeVisualizations(); } return visualizations; } /** * Indicates whether the represented data source can handle multiple * instances * * @return */ public boolean instanceable() { return mInstanceable; } /** * Method which injects all fields with GeoAR-{@link Annotations} and * finally calls the {@link PostConstruct} method (if available) for any * object. * * @param target * Any object */ public void perfomInjection(Object target) { // Field injection try { Class<? extends Object> currentClass = target.getClass(); while (currentClass != null) { for (Field f : currentClass.getDeclaredFields()) { if (f.isAnnotationPresent(SystemService.class)) { String serviceName = f.getAnnotation( SystemService.class).value(); f.setAccessible(true); f.set(target, mPluginHolder.getPluginContext() .getSystemService(serviceName)); } if (f.isAnnotationPresent(SharedHttpClient.class)) { f.setAccessible(true); f.set(target, PluginLoader.getSharedHttpClient()); } if (f.isAnnotationPresent(Annotations.PluginContext.class)) { f.setAccessible(true); f.set(target, mPluginHolder.getPluginContext()); } if (f.isAnnotationPresent(Annotations.SharedGeometryFactory.class)) { f.setAccessible(true); f.set(target, PluginLoader.getGeometryFactory()); } } currentClass = currentClass.getSuperclass(); } } catch (IllegalArgumentException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IllegalAccessException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (LinkageError e) { LOG.error("Data source " + getName() + " uses invalid class, " + e.getMessage()); } // Post construct try { Class<? extends Object> currentClass = target.getClass(); while (currentClass != null) { for (Method m : currentClass.getDeclaredMethods()) { if (m.isAnnotationPresent(PostConstruct.class)) { m.setAccessible(true); m.invoke(target); } } currentClass = currentClass.getSuperclass(); } } catch (InvocationTargetException e) { LOG.error("Data source " + getName() + " has an error in its PostConstruct method", e); } catch (IllegalArgumentException e) { LOG.error("Data source " + getName() + " has invalid PostConstruct arguments", e); } catch (IllegalAccessException e) { LOG.error("Data source " + getName() + " has an invalid PostConstruct method", e); } catch (RuntimeException e) { LOG.error("Data source " + getName() + " raised an exception in its PostConstruct method", e); e.printStackTrace(); } } public void restoreState(PluginStateInputStream objectInputStream) throws IOException { objectInputStream.setPluginClassLoader(mPluginHolder .getPluginClassLoader()); if (!mInstanceable) { boolean checked = objectInputStream.readBoolean(); if (checked || mDataSourceInstances != null) { // Set saved state if instance was checked or if it is already // initialized if (!getInstances().isEmpty()) { getInstances().get(0).setChecked(checked); getInstances().get(0).restoreState(objectInputStream); } } } else { int instancesCount = objectInputStream.readInt(); for (int i = 0; i < instancesCount; i++) { DataSourceInstanceHolder dataSourceInstance = addInstance(); dataSourceInstance.restoreState(objectInputStream); } } } public void saveState(ObjectOutputStream objectOutputStream) throws IOException { if (!mInstanceable) { // TODO ensure size == 1 objectOutputStream.writeBoolean(mDataSourceInstances != null && mDataSourceInstances.get(0).isChecked()); mDataSourceInstances.get(0).saveState(objectOutputStream); } else { objectOutputStream.writeInt(mDataSourceInstances.size()); for (DataSourceInstanceHolder dataSourceInstance : mDataSourceInstances) { dataSourceInstance.saveState(objectOutputStream); } } } /** * Calling this method affects all {@link DataSourceInstanceHolder} of this * object * * @param state */ public void setChecked(boolean state) { if (mDataSourceInstances != null) { mDataSourceInstances.checkAll(state); } } @Override public void writeToParcel(Parcel dest, int flags) { // Parcel based on unique internal DataSourceHolder id dest.writeInt(id); } /** * Lazily loads the instances of this data source. If it is not * {@link DataSourceHolder#instanceable()}, a default single instance will * be created. */ private void initializeInstances() { mDataSourceInstances = new CheckList<DataSourceInstanceHolder>( DataSourceInstanceHolder.class); if (!mInstanceable) { // data source has no instance-specific settings, create the single // instance LOG.info("Creating single-instance data source instance " + getName()); addInstance(); } } /** * Lazily loads all supported {@link Visualization}s of this data source. * {@link Visualization} are shared for all data sources. */ private void initializeVisualizations() { visualizations = new CheckList<Visualization>(); SupportedVisualization supportedVisualization = dataSourceClass .getAnnotation(SupportedVisualization.class); if (supportedVisualization != null) { Class<? extends Visualization>[] visualizationClasses = supportedVisualization .visualizationClasses(); try { for (int i = 0; i < visualizationClasses.length; i++) { // Find cached instance or create new one Visualization v = visualizationMap .get(visualizationClasses[i]); if (v == null) { // New instance needed v = visualizationClasses[i].newInstance(); perfomInjection(v); visualizationMap.put(visualizationClasses[i], v); } visualizations.add(v); visualizations.checkItem(v); // TODO } } catch (InstantiationException e) { throw new RuntimeException( "Referenced visualization has no appropriate constructor"); } catch (IllegalAccessException e) { throw new RuntimeException( "Referenced visualization has no appropriate constructor"); } } } private DataSourceInstanceHolder addInstance() { if (mDataSourceInstances == null) { initializeInstances(); } try { DataSource<? super Filter> dataSource = dataSourceClass .newInstance(); // perfomInjection(dataSource); final DataSourceInstanceHolder instance = new DataSourceInstanceHolder( this, dataSource); mDataSourceInstances.add(instance); return instance; } catch (InstantiationException e) { throw new RuntimeException("No default constructor for datasource"); } catch (IllegalAccessException e) { throw new RuntimeException("No valid constructor for datasource"); } } public void addInstance(Context context) { final DataSourceInstanceHolder instance = addInstance(); SettingsResultListener resultListener = new SettingsResultListener() { @Override void onSettingsResult(int resultCode) { if (resultCode == Activity.RESULT_OK) { instance.notifySettingsChanged(); instance.setChecked(true); } } }; Intent intent = new Intent(context, DataSourceInstanceSettingsDialogActivity.class); intent.putExtra("dataSourceInstance", instance); intent.putExtra("resultListener", resultListener); context.startActivity(intent); } public void removeUncheckedInstances() { if (!mInstanceable) { return; } ArrayList<DataSourceInstanceHolder> uncheckedInstances = new ArrayList<DataSourceInstanceHolder>( mDataSourceInstances.getUncheckedItems()); for (DataSourceInstanceHolder dataSourceInstance : uncheckedInstances) { mDataSourceInstances.remove(dataSourceInstance); } } }