package com.laytonsmith.persistence;
import com.laytonsmith.PureUtilities.Common.StringUtils;
import com.laytonsmith.PureUtilities.DaemonManager;
import com.laytonsmith.persistence.io.ConnectionMixinFactory;
import java.io.IOException;
import java.net.URI;
import java.util.HashSet;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* For data sources that can input and output strings as the complete
* data model, this class should be extended. The data source may not
* be written to file, but it is for sure going to be stored (or at least retrievable)
* from a UTF-8 encoded string.
*
*/
public abstract class StringSerializableDataSource extends AbstractDataSource {
/**
* A reference to the DataSourceModel used by the set and get methods.
*/
protected DataSourceModel model;
/**
* When a transaction starts or stops, this is set to true. If this
* is true, populate will run, then set this to false, and if this is false,
* populate will simply return.
*/
private boolean doPopulate = true;
/**
* If in a transaction, and we made a change, we know we need to write it out
* when the transaction finishes.
*/
private boolean hasChanges = false;
protected StringSerializableDataSource(){
}
protected StringSerializableDataSource(URI uri, ConnectionMixinFactory.ConnectionMixinOptions options) throws DataSourceException {
super(uri, options);
}
/**
* Writes the stringified data to whatever output is associated with
* this data source.
*
* @throws IOException
*/
protected void writeData(DaemonManager dm, String data) throws IOException, ReadOnlyException, DataSourceException {
getConnectionMixin().writeData(dm, data);
}
@Override
protected void clearKey0(DaemonManager dm, String[] key) throws DataSourceException, ReadOnlyException, IOException {
model.clearKey(key);
writeData(dm, serializeModel());
}
@Override
protected final void startTransaction0(DaemonManager dm) {
doPopulate = true;
}
/**
* {@inheritDoc}
*
* When we rollback, we simply re-populate the data, instead of tracking
* changes that happened since the transaction started.
* @param rollback
* @throws DataSourceException
* @throws IOException
*/
@Override
protected final void stopTransaction0(DaemonManager dm, boolean rollback) throws DataSourceException, IOException {
doPopulate = true;
if(hasChanges){
hasChanges = false;
if(rollback){
populate();
} else {
try {
writeData(dm, serializeModel());
} catch (ReadOnlyException ex) {
//This shouldn't happen, because we won't have been allowed to set any
Logger.getLogger(StringSerializableDataSource.class.getName()).log(Level.SEVERE, null, ex);
}
}
}
}
@Override
public void populate() throws DataSourceException {
if(inTransaction()){
if(doPopulate){
doPopulate = false;
} else {
return;
}
}
String data;
try {
data = getConnectionMixin().getData();
} catch (DataSourceException | IOException e) {
throw new DataSourceException("Could not populate the data source (" + uri + ") with data: " + e.getMessage(), e);
}
populateModel(data);
}
@Override
public Set<String[]> keySet(String[] keyBase) {
Set<String[]> keys = new HashSet<>();
String kb = StringUtils.Join(keyBase, ".");
for(String[] key : model.keySet()){
if(StringUtils.Join(key, ".").startsWith(kb)){
keys.add(key);
}
}
return keys;
}
@Override
protected final String get0(String[] key) throws DataSourceException {
return model.get(key);
}
@Override
protected final boolean set0(DaemonManager dm, String[] key, String value) throws ReadOnlyException, IOException, DataSourceException {
if (modifiers.contains(DataSourceModifier.READONLY)) {
throw new ReadOnlyException();
}
String old = get(key);
if ((old == null && value == null) || (old != null && old.equals(value))) {
return false;
}
model.set(key, value);
if(!inTransaction()){
//We need to output the model now
writeData(dm, serializeModel());
} else {
hasChanges = true;
}
return true;
}
/**
* Given some data retrieved from who knows where, populate the model.
*
* @param data
* @throws Exception
*/
protected abstract void populateModel(String data) throws DataSourceException;
/**
* Serializes the underlying model to a string, which can be written out
* to disk/network
*
* @return
*/
protected abstract String serializeModel();
/**
* Subclasses that need a certain type of file to be the "blank" version
* of a data model can override this. By default, an empty string is
* returned.
*
* @return
*/
@Override
protected String getBlankDataModel() {
return "";
}
@Override
public void disconnect() {
// By default, we assume that string based data sources don't need disconnecting.
// If this assumption is bad, the subclass can override this method.
}
}