se.toxbee.sleepfighter.persist.PersistenceManager.java Source code

Java tutorial

Introduction

Here is the source code for se.toxbee.sleepfighter.persist.PersistenceManager.java

Source

/*******************************************************************************
 * Copyright (c) 2013 See AUTHORS file.
 * 
 * This file is part of SleepFighter.
 * 
 * SleepFighter 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.
 * 
 * SleepFighter 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 SleepFighter. If not, see <http://www.gnu.org/licenses/>.
 ******************************************************************************/
package se.toxbee.sleepfighter.persist;

import java.sql.SQLException;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import net.engio.mbassy.listener.Handler;
import se.toxbee.sleepfighter.model.Alarm;
import se.toxbee.sleepfighter.model.Alarm.AlarmEvent;
import se.toxbee.sleepfighter.model.AlarmList;
import se.toxbee.sleepfighter.model.SnoozeConfig;
import se.toxbee.sleepfighter.model.audio.AudioConfig;
import se.toxbee.sleepfighter.model.audio.AudioSource;
import se.toxbee.sleepfighter.model.challenge.ChallengeConfig;
import se.toxbee.sleepfighter.model.challenge.ChallengeConfigSet;
import se.toxbee.sleepfighter.model.challenge.ChallengeConfigSet.ChallengeParamEvent;
import se.toxbee.sleepfighter.model.challenge.ChallengeConfigSet.Event;
import se.toxbee.sleepfighter.model.challenge.ChallengeParam;
import se.toxbee.sleepfighter.model.challenge.ChallengeType;
import se.toxbee.sleepfighter.model.gps.GPSFilterArea;
import se.toxbee.sleepfighter.model.gps.GPSFilterAreaSet;
import se.toxbee.sleepfighter.utils.debug.Debug;
import se.toxbee.sleepfighter.utils.model.IdProvider;
import android.content.Context;
import android.util.Log;

import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.j256.ormlite.android.apptools.OpenHelperManager;
import com.j256.ormlite.dao.Dao;
import com.j256.ormlite.field.DataPersisterManager;
import com.j256.ormlite.stmt.QueryBuilder;
import com.j256.ormlite.table.TableUtils;

/**
 * Handles all reads and writes to persistence.<br/>
 * There should be no reason to keep more than 1 instance of this object.
 * 
 * @author Centril<twingoow@gmail.com> / Mazdak Farrokhzad.
 * @version 1.0
 * @since Sep 21, 2013
 */
public class PersistenceManager {
    private static final String TAG = PersistenceManager.class.getSimpleName();

    private volatile OrmHelper ormHelper = null;

    private Context context;

    private static boolean init = false;

    /**
     * Handles changes in alarm-list (the list itself, additions, deletions, etc).
     *
     * @param evt the event.
     */
    @Handler
    public void handleListChange(AlarmList.Event evt) {
        if (!(evt.source() instanceof AlarmList)) {
            return;
        }

        switch (evt.operation()) {
        case CLEAR:
            this.clearAlarms();
            break;

        case ADD:
            for (Object elem : evt.elements()) {
                Debug.d("added alarm to database");
                this.addAlarm((Alarm) elem);
            }
            break;

        case REMOVE:
            for (Object elem : evt.elements()) {
                this.removeAlarm((Alarm) elem);
            }
            break;

        case UPDATE:
            Alarm old = (Alarm) evt.elements().iterator().next();
            this.removeAlarm(old);
            this.addAlarm(evt.source().get(evt.index()));
            break;
        }
    }

    /**
     * Handles a change in an alarm.
     *
     * @param evt the event.
     */
    @Handler
    public void handleAlarmChange(AlarmEvent evt) {
        this.updateAlarm(evt.getAlarm(), evt);
    }

    /**
     * Handles a change in {@link ChallengeConfigSet}
     *
     * @param evt the event.
     */
    @Handler
    public void handleChallengeChange(ChallengeConfigSet.Event evt) {
        this.updateChallenges(evt);
    }

    /**
     * Handles a change in {@link AudioConfig}
     *
     * @param evt the event.
     */
    @Handler
    public void handleAudioConfigChange(AudioConfig.ChangeEvent evt) {
        Log.d(TAG, "handleAudioConfigChange #1");
        this.updateAudioConfig(evt);
    }

    /**
     * Handles a change in {@link SnoozeConfig}
     *
     * @param evt the event.
     */
    @Handler
    public void handleSnoozeConfigChange(SnoozeConfig.ChangeEvent evt) {
        this.updateSnoozeConfig(evt);
    }

    /**
     * Handles a change in {@link GPSFilterArea}.
     *
     * @param evt the event.
     */
    @Handler
    public void handleGPSFilterChange(GPSFilterArea.ChangeEvent evt) {
        this.setGPSFilterArea(evt.getArea());
    }

    /**
     * Handles changes in GPSFilterAreaSet (the set itself, additions, deletions, etc).
     *
     * @param evt the event.
     */
    @Handler
    public void handleGPSFilterSetChange(GPSFilterAreaSet.Event evt) {
        if (!(evt.source() instanceof GPSFilterAreaSet)) {
            return;
        }

        switch (evt.operation()) {
        case CLEAR:
            this.clearGPSFilterAreas();
            break;

        case ADD:
            for (Object elem : evt.elements()) {
                this.setGPSFilterArea((GPSFilterArea) elem);
            }
            break;

        case REMOVE:
            for (Object elem : evt.elements()) {
                this.deleteGPSFilterArea((GPSFilterArea) elem);
            }
            break;

        case UPDATE:
            GPSFilterArea old = (GPSFilterArea) evt.elements().iterator().next();
            this.deleteGPSFilterArea(old);
            this.setGPSFilterArea(evt.source().get(evt.index()));
            break;
        }
    }

    /**
     * Constructs the PersistenceManager.
     *
     * @param context android context.
     */
    public PersistenceManager(Context context) {
        this.setContext(context);
    }

    /**
     * Sets the context to use.
     *
     * @param context android context.
     */
    public void setContext(Context context) {
        this.context = context;
    }

    /**
     * Rebuilds all data-structures. Any data is lost.
     */
    public void cleanStart() {
        this.getHelper().rebuild();
    }

    /**
     * Clears the list of all alarms.
     *
     * @throws PersistenceException if some SQL error happens.
     */
    public void clearAlarms() throws PersistenceException {
        this.clearTable(Alarm.class);

        this.clearTable(AudioSource.class);
        this.clearTable(AudioConfig.class);

        this.clearTable(ChallengeConfigSet.class);
        this.clearTable(ChallengeConfig.class);
        this.clearTable(ChallengeParam.class);

        this.clearTable(SnoozeConfig.class);
    }

    /**
     * Clears a DB table for given class.
     *
     * @param clazz the class to clear table for.
     */
    private void clearTable(Class<?> clazz) {
        try {
            TableUtils.clearTable(this.getHelper().getConnectionSource(), clazz);
        } catch (SQLException e) {
            throw new PersistenceException(e);
        }
    }

    /**
     * Fetches an AlarmsManager from database, it is sorted on ID.
     *
     * @return the fetched AlarmsManager.
     * @throws PersistenceException if some SQL error happens.
     */
    public List<Alarm> fetchAlarms() throws PersistenceException {
        try {
            Debug.d("fetching alarms");
            return this.joinFetched(this.makeAlarmQB().query());
        } catch (SQLException e) {
            throw new PersistenceException(e);
        }
    }

    /**
     * Fetches an AlarmsManager from database, it is sorted on names.
     *
     * @return the fetched AlarmsManager.
     * @throws PersistenceException if some SQL error happens.
     */
    public List<Alarm> fetchAlarmsSortedNames() throws PersistenceException {
        try {
            return this.joinFetched(this.makeAlarmQB().orderBy("name", true).query());
        } catch (SQLException e) {
            throw new PersistenceException(e);
        }
    }

    /**
     * Fetches a single alarm from database by its id.
     *
     * @param id the ID of the alarm to fetch.
     * @return the fetched Alarm.
     * @throws PersistenceException if some SQL error happens.
     */
    public Alarm fetchAlarmById(int id) throws PersistenceException {
        try {
            List<Alarm> alarms = this.joinFetched(this.makeAlarmQB().where().idEq(id).query());
            return alarms == null || alarms.size() == 0 ? null : alarms.get(0);
        } catch (SQLException e) {
            throw new PersistenceException(e);
        }
    }

    /**
     * Constructs a QueryBuilder (QB) for querying 0-many Alarm(s).
     *
     * @return the query builder.
     */
    private QueryBuilder<Alarm, Integer> makeAlarmQB() {
        return this.getHelper().getAlarmDao().queryBuilder();
    }

    /**
     * Performs "Joins" and fetches all to Alarm associated objects and sets to respective Alarm.
     *
     * @param alarms the list of alarms to fill in blanks for.
     * @return the passed argument, for fluid interface.
     */
    private List<Alarm> joinFetched(final List<Alarm> alarms) {
        if (alarms.size() == 0) {
            return alarms;
        }

        OrmHelper helper = this.getHelper();

        /*
         * Make lookup tables.
         * -------------------
         * Find all AudioSource:s present and make AudioSource.id -> index(Alarm) lookup table.
         * Make a AudioConfig.id -> index(Alarm) lookup table.
         */
        Map<Integer, Integer> audioSourceLookup = Maps.newHashMap();
        Map<Integer, Integer> audioConfigLookup = Maps.newHashMap();
        Map<Integer, Integer> snoozeConfigLookup = Maps.newHashMap();
        BiMap<Integer, Integer> challengeSetLookup = HashBiMap.create();

        for (int i = 0; i < alarms.size(); ++i) {
            Alarm alarm = alarms.get(i);

            // Audio Source.
            AudioSource source = alarm.getAudioSource();
            if (source != null) {
                audioSourceLookup.put(source.getId(), i);
            }

            // Audio Config.
            audioConfigLookup.put(alarm.getAudioConfig().getId(), i);

            // Snooze config
            snoozeConfigLookup.put(alarm.getSnoozeConfig().getId(), i);

            // Challenge related.
            challengeSetLookup.put(alarm.getChallengeSet().getId(), i);
        }

        /*
         * Query for all tables.
         */
        List<AudioSource> audioSourceList = this.queryInIds(helper.getAudioSourceDao(), AudioSource.ID_COLUMN,
                audioSourceLookup);
        List<AudioConfig> audioConfigSetList = this.queryInIds(helper.getAudioConfigDao(), AudioConfig.ID_COLUMN,
                audioConfigLookup);
        List<SnoozeConfig> snoozeConfigSetList = this.queryInIds(helper.getSnoozeConfigDao(),
                SnoozeConfig.ID_COLUMN, snoozeConfigLookup);

        /*
         * Set all to respective Alarm object.
         */

        // Set AudioSource to each alarm.
        for (AudioSource source : audioSourceList) {
            int alarmIndex = audioSourceLookup.get(source.getId());
            alarms.get(alarmIndex).setFetched(source);
        }

        // Set AudioConfig to each alarm.
        for (AudioConfig config : audioConfigSetList) {
            int alarmIndex = audioConfigLookup.get(config.getId());
            Alarm alarm = alarms.get(alarmIndex);
            config.setMessageBus(alarm.getMessageBus());
            alarms.get(alarmIndex).setFetched(config);
        }

        for (SnoozeConfig config : snoozeConfigSetList) {
            int alarmIndex = snoozeConfigLookup.get(config.getId());
            Alarm alarm = alarms.get(alarmIndex);
            config.setMessageBus(alarm.getMessageBus());
            alarm.setFetched(config);
        }

        /*
         * Challenges...
         */
        this.fetchJoinChallenge(alarms, challengeSetLookup);

        return alarms;
    }

    /**
     * "Joins" in challenge set into list of alarms.
     *
     * @param alarms list of alarms.
     * @param challengeSetLookup the lookup challenge set id -> index in list of alarms.
     */
    private void fetchJoinChallenge(List<Alarm> alarms, Map<Integer, Integer> challengeSetLookup) {
        OrmHelper helper = this.getHelper();

        // 1) Read all sets and set them.
        List<ChallengeConfigSet> challengeSetList = this.queryInIds(helper.getChallengeConfigSetDao(),
                ChallengeConfigSet.ID_COLUMN, challengeSetLookup);
        for (ChallengeConfigSet challengeSet : challengeSetList) {
            // Bind challenge config set to alarm.
            int alarmIndex = challengeSetLookup.get(challengeSet.getId());
            Alarm alarm = alarms.get(alarmIndex);
            challengeSet.setMessageBus(alarm.getMessageBus());
            alarm.setChallenges(challengeSet);
        }

        // 2) Read all challenge config:s and set to each set.
        Map<Integer, ChallengeConfig> challengeConfigLookup = Maps.newHashMap();
        PersistenceExceptionDao<ChallengeConfig, Integer> configDao = helper.getChallengeConfigDao();
        List<ChallengeConfig> challengeConfigList = this.queryInIds(configDao, ChallengeConfig.SET_FOREIGN_COLUMN,
                challengeSetLookup);
        for (ChallengeConfig challengeConfig : challengeConfigList) {
            // Bind challenge config to set.
            int alarmIndex = challengeSetLookup.get(challengeConfig.getSetId());
            alarms.get(alarmIndex).getChallengeSet().putChallenge(challengeConfig);

            // Add to challenge config lookup.
            challengeConfigLookup.put(challengeConfig.getId(), challengeConfig);
        }

        // 3) Sanity fix. Find any missing ChallengeType:s and add them.
        for (ChallengeConfigSet challengeSet : challengeSetList) {
            Set<ChallengeType> missingTypes = Sets.complementOf(challengeSet.getDefinedTypes(),
                    ChallengeType.class);

            for (ChallengeType type : missingTypes) {
                ChallengeConfig config = new ChallengeConfig(type, false);
                config.setFetchedSetId(challengeSet.getId());

                configDao.create(config);

                challengeSet.putChallenge(config);
            }
        }

        // 3) Finally read all parameters and set to each config.
        List<ChallengeParam> challengeParamList = this.queryInIds(helper.getChallengeParamDao(),
                ChallengeParam.CHALLENGE_ID_COLUMN, challengeConfigLookup);
        for (ChallengeParam param : challengeParamList) {
            challengeConfigLookup.get(param.getId()).setFetched(param);
        }
    }

    /**
     * Helper for {@link #joinFetched(List)}, returns a list of items given a lookup table.
     *
     * @param dao the Domain Access Object for item type.
     * @param lookup the lookup table to get IDs from.
     * @return the list of items.
     */
    private <T extends IdProvider> List<T> queryInIds(Dao<T, Integer> dao, String idColumn,
            Map<Integer, ?> lookup) {
        try {
            return dao.queryBuilder().where().in(idColumn, lookup.keySet().toArray()).query();
        } catch (SQLException e) {
            throw new PersistenceException(e);
        }
    }

    /**
     * Updates an alarm to database.
     *
     * @param alarm the alarm to update.
     * @param evt AlarmEvent that occurred, required to update foreign fields.
     * @throws PersistenceException if some SQL error happens.
     */
    public void updateAlarm(Alarm alarm, AlarmEvent evt) throws PersistenceException {
        OrmHelper helper = this.getHelper();

        boolean updateAlarmTable = false;

        // First handle any updates to foreign fields that are set directly in Alarm.
        switch (evt.getModifiedField()) {
        case AUDIO_SOURCE:
            updateAlarmTable = this.updateAudioSource(alarm.getAudioSource(), (AudioSource) evt.getOldValue());
            break;

        default:
            updateAlarmTable = true;
            break;
        }

        if (updateAlarmTable) {
            helper.getAlarmDao().update(alarm);
        }
    }

    /**
     * Updates an Audio Config to database.
     *
     * @param ac the AudioConfig to update
     * @param evt ChangeEvent that occurred, required to update foreign fields.
     * @throws PersistenceException if some SQL error happens.
     */
    public void updateAudioConfig(AudioConfig.ChangeEvent evt) throws PersistenceException {
        Log.d(TAG, "updateAudioConfig #1");
        OrmHelper helper = this.getHelper();

        Log.d(TAG, "updateAudioConfig #2");
        helper.getAudioConfigDao().update(evt.getAudioConfig());
        Log.d(TAG, "updateAudioConfig #3");
    }

    /**
     * Updates a SnoozeConfig to database.
     *
     * @param ac the SnoozeConfig to update
     * @param evt SnoozeConfig that occurred, required to update foreign fields.
     * @throws PersistenceException if some SQL error happens.
     */
    public void updateSnoozeConfig(SnoozeConfig.ChangeEvent evt) throws PersistenceException {
        OrmHelper helper = this.getHelper();

        helper.getSnoozeConfigDao().update(evt.getSnoozeConfig());
    }

    /**
     * Updates the audio source.
     *
     * @param source the audio source.
     * @param old the old source if any.
     * @return true if the alarm table must be updated as a result.
     */
    private boolean updateAudioSource(AudioSource source, AudioSource old) {
        OrmHelper helper = this.getHelper();
        PersistenceExceptionDao<AudioSource, Integer> asDao = helper.getAudioSourceDao();

        if (old != null) {
            if (source == null) {
                asDao.delete(source);
            } else {
                source.setId(old.getId());
                asDao.update(source);
                return false;
            }
        } else {
            asDao.create(source);
        }
        return true;
    }

    /**
     * Updates the challenges.
     *
     * @param evt the event.
     */
    private void updateChallenges(Event evt) {
        Log.d(TAG, evt.toString());

        OrmHelper helper = this.getHelper();
        ChallengeConfigSet set = evt.getSet();

        if (evt instanceof ChallengeConfigSet.EnabledEvent) {
            // Handle change for isEnabled().
            helper.getChallengeConfigSetDao().update(set);
            return;
        } else if (evt instanceof ChallengeConfigSet.ChallengeEvent) {
            ChallengeConfig config = ((ChallengeConfigSet.ChallengeEvent) evt).getChallengeConfig();

            if (evt instanceof ChallengeConfigSet.ChallengeEnabledEvent) {
                // Handle change for isEnabled() for specific ChallengeConfig.
                helper.getChallengeConfigDao().update(config);
                return;
            } else if (evt instanceof ChallengeConfigSet.ChallengeParamEvent) {
                // Handle change for a specific challenge config parameter.
                ChallengeParamEvent event = (ChallengeParamEvent) evt;
                String key = event.getKey();

                ChallengeParam param = new ChallengeParam(config.getId(), key, config.getParam(key));
                helper.getChallengeParamDao().createOrUpdate(param);
                return;
            }
        }

        throw new IllegalArgumentException("Do you know something we don't?");
    }

    /**
     * Stores/adds an alarm to database.
     *
     * @param alarm the alarm to store.
     * @throws PersistenceException if some SQL error happens.
     */
    public void addAlarm(Alarm alarm) throws PersistenceException {
        OrmHelper helper = this.getHelper();

        // Handle audio source foreign object if present.
        AudioSource audioSource = alarm.getAudioSource();
        helper.getAudioSourceDao().create(audioSource);

        // Handle audio config foreign object.
        AudioConfig audioConfig = alarm.getAudioConfig();
        helper.getAudioConfigDao().create(audioConfig);

        // Handle snooze config foreign object.
        SnoozeConfig snoozeConfig = alarm.getSnoozeConfig();
        helper.getSnoozeConfigDao().create(snoozeConfig);

        this.addChallengeSet(alarm);

        // Finally persist alarm itself to DB.
        helper.getAlarmDao().create(alarm);
    }

    /**
     * Stores/adds the challenge set of alarm when adding alarm to database.
     *
     * @param alarm the alarm to save challenge set for.
     */
    private void addChallengeSet(Alarm alarm) {
        OrmHelper helper = this.getHelper();

        // Insert set.
        ChallengeConfigSet set = alarm.getChallengeSet();
        helper.getChallengeConfigSetDao().create(set);

        PersistenceExceptionDao<ChallengeConfig, Integer> challengeDao = helper.getChallengeConfigDao();
        PersistenceExceptionDao<ChallengeParam, Integer> paramDao = helper.getChallengeParamDao();

        for (ChallengeConfig challenge : set.getConfigs()) {
            // Insert challenge.
            challenge.setFetchedSetId(set.getId());
            challengeDao.create(challenge);

            // Insert each param.
            for (Entry<String, String> entry : challenge.getParams().entrySet()) {
                ChallengeParam param = new ChallengeParam(challenge.getId(), entry.getKey(), entry.getValue());
                paramDao.create(param);
            }
        }
    }

    /**
     * Removes an alarm from database.
     *
     * @param alarm the alarm to remove.
     * @throws PersistenceException if some SQL error happens.
     */
    public void removeAlarm(Alarm alarm) throws PersistenceException {
        OrmHelper helper = this.getHelper();

        // Handle audio source foreign object if present.
        AudioSource audioSource = alarm.getAudioSource();
        if (audioSource != null) {
            helper.getAudioSourceDao().delete(audioSource);
        }

        // Handle audio config foreign object.
        AudioConfig audioConfig = alarm.getAudioConfig();
        helper.getAudioConfigDao().delete(audioConfig);

        // Handle snooze config foreign object.
        SnoozeConfig snoozeConfig = alarm.getSnoozeConfig();
        helper.getSnoozeConfigDao().delete(snoozeConfig);

        // Handle challenge config set foreign object.
        this.removeChallengeSet(alarm);

        // Finally delete alarm itself from DB.
        helper.getAlarmDao().delete(alarm);
    }

    /**
     * Removes the challenge set of alarm when removing alarm from database.
     *
     * @param alarm the alarm to remove challenge set for.
     */
    private void removeChallengeSet(Alarm alarm) {
        OrmHelper helper = this.getHelper();

        ChallengeConfigSet set = alarm.getChallengeSet();

        // Compute list of ids to remove.
        Collection<ChallengeConfig> challengeList = set.getConfigs();
        List<Integer> removeIds = Lists.newArrayListWithCapacity(challengeList.size());

        for (ChallengeConfig challenge : challengeList) {
            removeIds.add(challenge.getId());
        }

        // Remove all challenge configs & params.
        helper.getChallengeConfigDao().deleteIds(removeIds);
        helper.getChallengeParamDao().deleteIds(removeIds);

        // Finally remove set.
        helper.getChallengeConfigSetDao().delete(set);
    }

    /**
     * Fetches all available GPSFilterArea:s.
     *
     * @return the list of GPSFilterArea:s.
     */
    public GPSFilterAreaSet fetchGPSFilterAreas() {
        OrmHelper helper = this.getHelper();
        return new GPSFilterAreaSet(helper.getGPSFilterAreaDao().queryForAll());
    }

    /**
     * Stores/updates a GPSFilterArea in database.
     *
     * @param area the GPSFilterArea to store/update.
     */
    public void setGPSFilterArea(GPSFilterArea area) {
        Log.d(TAG, area.toString());

        OrmHelper helper = this.getHelper();
        helper.getGPSFilterAreaDao().createOrUpdate(area);
    }

    /**
     * Deletes an GPSFilterArea from database. 
     *
     * @param area the area.
     */
    public void deleteGPSFilterArea(GPSFilterArea area) {
        OrmHelper helper = this.getHelper();

        helper.getGPSFilterAreaDao().delete(area);
    }

    /**
     * Removes all gpsfilter areas.
     */
    public void clearGPSFilterAreas() {
        this.clearTable(GPSFilterArea.class);
    }

    /**
     * Releases any resources held such as the OrmHelper.
     */
    public void release() {
        if (this.ormHelper != null) {
            OpenHelperManager.releaseHelper();
            this.ormHelper = null;
        }
    }

    /**
     * Returns the OrmHelper.
     *
     * @return the helper.
     */
    public OrmHelper getHelper() {
        if (this.ormHelper == null) {
            if (this.context == null) {
                throw new PersistenceException("There is no helper set and context == null");
            }

            this.loadHelper();
        }

        return this.ormHelper;
    }

    /**
     * Loads the OrmHelper.
     */
    private void loadHelper() {
        this.ormHelper = OpenHelperManager.getHelper(this.context, OrmHelper.class);

        this.init();
    }

    /**
     * Initialization code goes here.
     */
    private void init() {
        // Run Once guard.
        if (init) {
            return;
        }
        init = true;

        // Initialization code goes here.
        DataPersisterManager.registerDataPersisters(BooleanArrayType.getSingleton());
    }
}