Adding in the development version of the new data store system.

This commit is contained in:
Olof Larsson 2012-08-31 11:07:13 +02:00
parent 541c6d2e0a
commit 2bca9a6572
46 changed files with 2853 additions and 0 deletions

View File

@ -0,0 +1,530 @@
package com.massivecraft.mcore4.store;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import com.massivecraft.mcore4.MPlugin;
import com.massivecraft.mcore4.Predictate;
import com.massivecraft.mcore4.store.idstrategy.IdStrategy;
import com.massivecraft.mcore4.store.storeadapter.StoreAdapter;
public class Coll<E, L> implements CollInterface<E, L>
{
// -------------------------------------------- //
// WHAT DO WE HANDLE?
// -------------------------------------------- //
protected String name;
@Override public String name() { return this.name; }
protected Class<E> entityClass;
@Override public Class<E> entityClass() { return this.entityClass; }
protected Class<L> idClass;
@Override public Class<L> idClass() { return this.idClass; }
// -------------------------------------------- //
// SUPPORTING SYSTEM
// -------------------------------------------- //
protected MPlugin mplugin;
@Override public MPlugin mplugin() { return this.mplugin; }
protected Db<?> db;
@Override public Db<?> db() { return this.db; }
@Override public Driver<?> driver() { return this.db.driver(); }
protected IdStrategy<L, ?> idStrategy;
@Override public IdStrategy<L, ?> idStrategy() { return this.idStrategy; }
protected StoreAdapter storeAdapter;
@Override public StoreAdapter storeAdapter() { return this.storeAdapter; }
protected Object collDriverObject;
@Override public Object collDriverObject() { return this.collDriverObject; }
// -------------------------------------------- //
// STORAGE
// -------------------------------------------- //
protected Set<L> ids = Collections.newSetFromMap(new ConcurrentHashMap<L, Boolean>());
@Override public Collection<L> ids() { return Collections.unmodifiableCollection(this.ids); }
@Override public Collection<L> idsRemote() { return this.db().driver().ids(this); }
protected Set<E> entities = Collections.newSetFromMap(new ConcurrentHashMap<E, Boolean>());
@Override public Collection<E> getAll() { return Collections.unmodifiableCollection(this.entities); }
@Override public Collection<E> getAll(Predictate<E> where) { return MStoreUtil.uglySQL(this.getAll(), where, null, null, null); }
@Override public Collection<E> getAll(Predictate<E> where, Comparator<E> orderby) { return MStoreUtil.uglySQL(this.getAll(), where, orderby, null, null); }
@Override public Collection<E> getAll(Predictate<E> where, Comparator<E> orderby, Integer limit) { return MStoreUtil.uglySQL(this.getAll(), where, orderby, limit, null); }
@Override public Collection<E> getAll(Predictate<E> where, Comparator<E> orderby, Integer limit, Integer offset) { return MStoreUtil.uglySQL(this.getAll(), where, orderby, limit, offset); }
protected Map<L, E> id2entity = new ConcurrentHashMap<L, E>();
@Override public Map<L, E> id2entity() { return Collections.unmodifiableMap(this.id2entity); }
@Override
public E get(Object oid)
{
return this.get(oid, this.creative());
}
@Override
public E get(Object oid, boolean creative)
{
return this.get(oid, creative, true);
}
protected E get(Object oid, boolean creative, boolean noteChange)
{
L id = this.idFix(oid);
if (id == null) return null;
E ret = this.id2entity.get(id);
if (ret != null) return ret;
if ( ! creative) return null;
return this.create(id, noteChange);
}
@Override
public E getBestMatch(Object oid)
{
// TODO Auto-generated method stub
return null;
}
// Get the id for this entity.
protected Map<E, L> entity2id = new ConcurrentHashMap<E, L>();
@Override public Map<E, L> entity2id() { return Collections.unmodifiableMap(this.entity2id); }
@Override public L id(E entity) { return this.entity2id.get(entity); }
@Override
public L idFix(Object oid)
{
if (oid == null) return null;
if (oid.getClass() == this.idClass) return this.idClass.cast(oid);
return null;
}
// -------------------------------------------- //
// BAHAVIOR
// -------------------------------------------- //
protected boolean creative;
@Override public boolean creative() { return this.creative; }
@Override public void creative(boolean val) { this.creative = val; }
// Should that instance be saved or not?
// If it is default it should not be saved.
@Override public boolean isDefault(E entity) { return false; }
// -------------------------------------------- //
// CREATE
// -------------------------------------------- //
// This simply creates and returns a new instance
// It does not detach/attach or anything. Just creates a new instance.
@Override
public E createNewInstance()
{
try
{
return this.entityClass.newInstance();
}
catch (Exception e)
{
return null;
}
}
// Create new instance with automatic id
@Override
public E create()
{
return this.create(null);
}
// Create new instance with the requested id
@Override
public synchronized E create(Object oid)
{
return this.create(oid, true);
}
public synchronized E create(Object oid, boolean noteChange)
{
E entity = this.createNewInstance();
if (this.attach(entity, oid, noteChange) == null) return null;
return entity;
}
// -------------------------------------------- //
// ATTACH AND DETACH
// -------------------------------------------- //
@Override
public L attach(E entity)
{
return this.attach(entity, null);
}
@Override
public synchronized L attach(E entity, Object oid)
{
return this.attach(entity, oid, true);
}
protected synchronized L attach(E entity, Object oid, boolean noteChange)
{
// Check entity
if (entity == null) return null;
L id = this.id(entity);
if (id != null) return id;
// Check/Fix id
if (oid == null)
{
id = this.idStrategy().generate(this);
}
else
{
id = this.idFix(oid);
if (id == null) return null;
if (this.ids.contains(id)) return null;
}
// Attach
this.ids.add(id);
this.entities.add(entity);
this.id2entity.put(id, entity);
this.entity2id.put(entity, id);
// Make note of the change
if (noteChange)
{
this.localAttachIds.add(id);
this.changedIds.add(id);
}
return id;
}
@SuppressWarnings("unchecked")
@Override
public synchronized E detach(Object o)
{
// What id is this?
L id = null;
if (this.idClass.isInstance(o))
{
id = (L)o;
}
else if (this.entityClass.isInstance(o))
{
id = this.entity2id.get(o);
}
else
{
id = this.idFix(o);
}
if (id == null)
{
return null;
}
// Remove @ local
E ret = this.removeAtLocal(id);
// Identify the change
this.localDetachIds.add(id);
this.changedIds.add(id);
return ret;
}
// -------------------------------------------- //
// IDENTIFIED CHANGES
// -------------------------------------------- //
protected Set<L> localAttachIds = Collections.newSetFromMap(new ConcurrentHashMap<L, Boolean>());
protected Set<L> localDetachIds = Collections.newSetFromMap(new ConcurrentHashMap<L, Boolean>());
protected Set<L> changedIds = Collections.newSetFromMap(new ConcurrentHashMap<L, Boolean>());
protected synchronized void clearIdentifiedChanges(L id)
{
this.localAttachIds.remove(id);
this.localDetachIds.remove(id);
this.changedIds.remove(id);
}
// -------------------------------------------- //
// SYNCLOG
// -------------------------------------------- //
protected Map<L, Long> lastMtime = new ConcurrentHashMap<L, Long>();
protected Map<L, Object> lastRaw = new ConcurrentHashMap<L, Object>();
protected Set<L> lastDefault = Collections.newSetFromMap(new ConcurrentHashMap<L, Boolean>());
protected synchronized void clearSynclog(L id)
{
this.lastMtime.remove(id);
this.lastRaw.remove(id);
this.lastDefault.remove(id);
}
// -------------------------------------------- //
// SYNC LOWLEVEL IO ACTIONS
// -------------------------------------------- //
@Override
public synchronized E removeAtLocal(L id)
{
this.clearIdentifiedChanges(id);
this.clearSynclog(id);
this.ids.remove(id);
E entity = this.id2entity.remove(id);
if (entity == null) return null;
this.entity2id.remove(entity);
this.entities.remove(entity);
return entity;
}
@Override
public synchronized void removeAtRemote(L id)
{
this.clearIdentifiedChanges(id);
this.clearSynclog(id);
this.db().driver().delete(this, id);
}
@Override
public synchronized void saveToRemote(L id)
{
this.clearIdentifiedChanges(id);
this.clearSynclog(id);
E entity = this.id2entity.get(id);
if (entity == null) return;
Object raw = this.storeAdapter().read(this, entity);
this.lastRaw.put(id, raw);
if (this.isDefault(entity))
{
this.db.driver().delete(this, id);
this.lastDefault.add(id);
}
else
{
Long mtime = this.db.driver().save(this, id, raw);
if (mtime == null) return; // This fail should not happen often. We could handle it better though.
this.lastMtime.put(id, mtime);
}
}
@Override
public synchronized void loadFromRemote(L id)
{
this.clearIdentifiedChanges(id);
Entry<?, Long> entry = this.db().driver().load(this, id);
if (entry == null) return;
Object raw = entry.getKey();
if (raw == null) return;
Long mtime = entry.getValue();
if (mtime == null) return;
E entity = this.get(id, true, false);
this.storeAdapter().write(this, raw, entity);
// Store adapter again since result of a database read may be "different" from entity read.
this.lastRaw.put(id, this.storeAdapter().read(this, entity));
this.lastMtime.put(id, mtime);
this.lastDefault.remove(id);
}
// -------------------------------------------- //
// SYNC DECIDE AND BASIC DO
// -------------------------------------------- //
@Override
public ModificationState examineId(L id)
{
return this.examineId(id, null, false);
}
@Override
public ModificationState examineId(L id, Long remoteMtime)
{
return this.examineId(id, remoteMtime, true);
}
protected ModificationState examineId(L id, Long remoteMtime, boolean remoteMtimeSupplied)
{
if (this.localDetachIds.contains(id)) return ModificationState.LOCAL_DETACH;
if (this.localAttachIds.contains(id)) return ModificationState.LOCAL_ATTACH;
E localEntity = this.id2entity.get(id);
if ( ! remoteMtimeSupplied)
{
remoteMtime = this.driver().mtime(this, id);
}
boolean existsLocal = (localEntity != null);
boolean existsRemote = (remoteMtime != null);
if (existsLocal && existsRemote)
{
Long lastMtime = this.lastMtime.get(id);
if (remoteMtime.equals(lastMtime) == false) return ModificationState.REMOTE_ALTER;
Object lastRaw = this.lastRaw.get(id);
Object currentRaw = this.storeAdapter.read(this, localEntity);
if (currentRaw.equals(lastRaw) == false) return ModificationState.LOCAL_ALTER;
}
else if (existsLocal)
{
if ( ! this.lastDefault.contains(id)) return ModificationState.REMOTE_DETACH;
}
else if (existsRemote)
{
return ModificationState.REMOTE_ATTACH;
}
else if ( ! existsLocal && ! existsRemote)
{
return ModificationState.UNKNOWN;
}
return ModificationState.NONE;
}
@Override
public void syncId(L id)
{
ModificationState mstate = this.examineId(id);
//System.out.println("syncId \""+id+"\" "+mstate);
switch (mstate)
{
case LOCAL_ALTER:
case LOCAL_ATTACH:
this.saveToRemote(id);
break;
case LOCAL_DETACH:
this.removeAtRemote(id);
break;
case REMOTE_ALTER:
case REMOTE_ATTACH:
this.loadFromRemote(id);
break;
case REMOTE_DETACH:
this.removeAtLocal(id);
break;
default:
this.clearIdentifiedChanges(id);
break;
}
}
@Override
public void syncSuspects()
{
for (L id : this.changedIds)
{
this.syncId(id);
}
}
@Override
public void syncAll()
{
// Find all ids
Set<L> allids = new HashSet<L>(this.ids);
allids.addAll(this.driver().ids(this));
for (L id : allids)
{
this.syncId(id);
}
}
@Override
public void findSuspects()
{
// Get remote id and mtime snapshot
Map<L, Long> id2RemoteMtime = this.db().driver().id2mtime(this);
// Compile a list of all ids (both remote and local)
Set<L> allids = new HashSet<L>();
allids.addAll(id2RemoteMtime.keySet());
allids.addAll(this.ids);
// Check for modifications
for (L id : allids)
{
Long remoteMtime = id2RemoteMtime.get(id);
ModificationState state = this.examineId(id, remoteMtime);
if (state.modified())
{
//System.out.println("It seems "+id+" has state "+state);
this.changedIds.add(id);
}
}
}
// -------------------------------------------- //
// SYNC RUNNABLES / SCHEDULING
// -------------------------------------------- //
protected Runnable tickTask;
@Override public Runnable tickTask() { return this.tickTask; }
@Override
public void onTick()
{
this.syncSuspects();
}
protected ExamineThread<E, L> examineThread;
@Override public Thread examineThread() { return this.examineThread; }
// -------------------------------------------- //
// CONSTRUCTORS
// -------------------------------------------- //
public Coll(MPlugin mplugin, Db<?> db, String idStrategyName, String name, Class<E> entityClass, Class<L> idClass, boolean creative)
{
this.name = name;
this.entityClass = entityClass;
this.idClass = idClass;
this.creative = creative;
this.mplugin = mplugin;
this.db = db;
this.storeAdapter = this.db.driver().getStoreAdapter();
this.idStrategy = this.db.driver().getIdStrategy(idStrategyName);
if (this.idStrategy == null)
{
throw new IllegalArgumentException("UNKNOWN: The id stragegy \""+idStrategyName+"\" is unknown to the driver \""+db.driver().name()+"\".");
}
else if (this.idStrategy.getLocalClass() != idClass)
{
throw new IllegalArgumentException("MISSMATCH: The id stragegy \""+idStrategyName+"\" for the driver \""+db.driver().name()+"\" uses \""+this.idStrategy.getLocalClass().getSimpleName()+"\" but the collection "+this.name+"/"+this.getClass().getSimpleName()+" uses \""+idClass.getSimpleName()+"\".");
}
this.collDriverObject = db.getCollDriverObject(this);
final Coll<E, L> me = this;
this.tickTask = new Runnable()
{
@Override public void run() { me.onTick(); }
};
this.examineThread = new ExamineThread<E, L>(this);
}
}

View File

@ -0,0 +1,137 @@
package com.massivecraft.mcore4.store;
import java.util.Collection;
import java.util.Comparator;
import java.util.Map;
import com.massivecraft.mcore4.MPlugin;
import com.massivecraft.mcore4.Predictate;
import com.massivecraft.mcore4.store.idstrategy.IdStrategy;
import com.massivecraft.mcore4.store.storeadapter.StoreAdapter;
public interface CollInterface<E, L>
{
// -------------------------------------------- //
// WHAT DO WE HANDLE?
// -------------------------------------------- //
public String name();
public Class<E> entityClass();
public Class<L> idClass();
// -------------------------------------------- //
// SUPPORTING SYSTEM
// -------------------------------------------- //
public MPlugin mplugin();
public Db<?> db();
public Driver<?> driver();
public StoreAdapter storeAdapter();
public IdStrategy<L, ?> idStrategy();
public Object collDriverObject();
// -------------------------------------------- //
// STORAGE
// -------------------------------------------- //
public Collection<L> ids();
public Collection<L> idsRemote();
public Collection<E> getAll();
public Collection<E> getAll(Predictate<E> where);
public Collection<E> getAll(Predictate<E> where, Comparator<E> orderby);
public Collection<E> getAll(Predictate<E> where, Comparator<E> orderby, Integer limit);
public Collection<E> getAll(Predictate<E> where, Comparator<E> orderby, Integer limit, Integer offset);
public Map<L, E> id2entity();
public E get(Object oid);
public E get(Object oid, boolean creative);
public E getBestMatch(Object oid);
public Map<E, L> entity2id();
public L id(E entity);
public L idFix(Object oid);
// -------------------------------------------- //
// BAHAVIOR
// -------------------------------------------- //
public boolean creative();
public void creative(boolean val);
// A default entity will not be saved.
// This is often used together with creative collections to save disc space.
public boolean isDefault(E entity);
// -------------------------------------------- //
// CREATE
// -------------------------------------------- //
// This simply creates and returns a new instance
// It does not detach/attach or anything. Just creates a new instance.
public E createNewInstance();
// Create new instance with automatic id
public E create();
// Create new instance with the requested id
public E create(Object oid);
// -------------------------------------------- //
// ATTACH AND DETACH
// -------------------------------------------- //
public L attach(E entity);
public L attach(E entity, Object oid);
public E detach(Object o);
// -------------------------------------------- //
// IDENTIFIED CHANGES
// -------------------------------------------- //
/*
public Set<L> localAttachIds();
public Set<L> localDetachIds();
public Set<L> changedIds();
public void clearIdentifiedChanges(L id);
*/
// -------------------------------------------- //
// SYNC LOG
// -------------------------------------------- //
/*
public Map<L, Long> lastMtime();
public Map<L, Object> lastRaw();
public Set<L> lastDefault();
public void clearSynclog(L id);
*/
// -------------------------------------------- //
// SYNC LOWLEVEL IO ACTIONS
// -------------------------------------------- //
public E removeAtLocal(L id);
public void removeAtRemote(L id);
public void saveToRemote(L id);
public void loadFromRemote(L id);
// -------------------------------------------- //
// SYNC EXAMINE AND DO
// -------------------------------------------- //
public ModificationState examineId(L id);
public ModificationState examineId(L id, Long remoteMtime);
public void syncId(L id);
public void syncSuspects();
public void syncAll();
public void findSuspects();
// -------------------------------------------- //
// SYNC RUNNABLES / SCHEDULING
// -------------------------------------------- //
// The tickTask simply runs the onTick method.
public Runnable tickTask();
public void onTick();
public Thread examineThread();
}

View File

@ -0,0 +1,16 @@
package com.massivecraft.mcore4.store;
import java.util.Set;
public interface Db<R>
{
public String name();
public boolean drop();
public Set<String> collnames();
public Driver<R> driver();
public Object getCollDriverObject(Coll<?, ?> coll);
}

View File

@ -0,0 +1,12 @@
package com.massivecraft.mcore4.store;
import java.util.Set;
public abstract class DbAbstract<R> implements Db<R>
{
@Override
public Set<String> collnames()
{
return this.driver().collnames(this);
}
}

View File

@ -0,0 +1,57 @@
package com.massivecraft.mcore4.store;
import java.io.File;
import com.massivecraft.mcore4.lib.gson.JsonElement;
import com.massivecraft.mcore4.util.DiscUtil;
public class DbGson extends DbAbstract<JsonElement>
{
// -------------------------------------------- //
// FIELDS
// -------------------------------------------- //
public File dir;
protected DriverGson driver;
@Override public DriverGson driver() { return driver; }
// -------------------------------------------- //
// CONSTRUCTORS
// -------------------------------------------- //
public DbGson(DriverGson driver, File folder)
{
this.driver = driver;
this.dir = folder;
}
// -------------------------------------------- //
// IMPLEMENTATION
// -------------------------------------------- //
@Override
public String name()
{
return dir.getAbsolutePath();
}
@Override
public boolean drop()
{
try
{
return DiscUtil.deleteRecursive(this.dir);
}
catch (Exception e)
{
return false;
}
}
@Override
public Object getCollDriverObject(Coll<?, ?> coll)
{
return new File(dir, coll.name());
}
}

View File

@ -0,0 +1,56 @@
package com.massivecraft.mcore4.store;
import com.massivecraft.mcore4.lib.mongodb.BasicDBObject;
import com.massivecraft.mcore4.lib.mongodb.DB;
public class DbMongo extends DbAbstract<BasicDBObject>
{
// -------------------------------------------- //
// FIELDS
// -------------------------------------------- //
public DB db;
protected DriverMongo driver;
@Override public DriverMongo driver() { return driver; }
// -------------------------------------------- //
// CONSTRUCTORS
// -------------------------------------------- //
public DbMongo(DriverMongo driver, DB db)
{
this.driver = driver;
this.db = db;
}
// -------------------------------------------- //
// IMPLEMENTATION
// -------------------------------------------- //
@Override
public String name()
{
return db.getName();
}
@Override
public boolean drop()
{
try
{
this.db.dropDatabase();
return true;
}
catch (Exception e)
{
return false;
}
}
@Override
public Object getCollDriverObject(Coll<?, ?> coll)
{
return db.getCollection(coll.name());
}
}

View File

@ -0,0 +1,55 @@
package com.massivecraft.mcore4.store;
import java.util.Collection;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import com.massivecraft.mcore4.store.idstrategy.IdStrategy;
import com.massivecraft.mcore4.store.storeadapter.StoreAdapter;
public interface Driver<R>
{
// Returns the name of the driver.
public String name();
// This is the rawdata format this driver works with.
// Could for example be JsonElement or DBObject
public Class<R> getRawdataClass();
// This is some sort of database specific id strategy with built in adapter
public boolean registerIdStrategy(IdStrategy<?, ?> idStrategy);
public <L> IdStrategy<L, ?> getIdStrategy(String idStrategyName);
// Get the default store adapter for the driver.
public StoreAdapter getStoreAdapter();
// Get a database instance from the driver
public Db<R> db(String uri);
// What collections are in the database?
public Set<String> collnames(Db<?> db);
// Is id X in the collection?
public <L> boolean containsId(Coll<?, L> coll, L id);
// When was X last altered?
public <L> Long mtime(Coll<?, L> coll, L id);
// What ids are in the collection?
public <L> Collection<L> ids(Coll<?, L> coll);
// Return a map of all ids with their corresponding mtimes
public <L> Map<L, Long> id2mtime(Coll<?, L> coll);
// Load the raw data for X. The second part of the entry is the remote mtime at the load.
public <L> Entry<R, Long> load(Coll<?, L> coll, L id);
// Save raw data as X
// Return value is the new mtime (we caused the change).
// If the mtime is null something failed.
public <L> Long save(Coll<?, L> coll, L id, final Object rawData);
// Delete X
public <L> void delete(Coll<?, L> coll, L id);
}

View File

@ -0,0 +1,34 @@
package com.massivecraft.mcore4.store;
import java.util.HashMap;
import java.util.Map;
import com.massivecraft.mcore4.store.idstrategy.IdStrategy;
public abstract class DriverAbstract<R> implements Driver<R>
{
public DriverAbstract(String name)
{
this.name = name;
}
protected String name;
@Override public String name() { return this.name; }
protected Map<String, IdStrategy<?, ?>> idStrategies = new HashMap<String, IdStrategy<?, ?>>();
@Override
public boolean registerIdStrategy(IdStrategy<?, ?> idStrategy)
{
if (idStrategies.containsKey(idStrategy.getName())) return false;
idStrategies.put(idStrategy.getName(), idStrategy);
return true;
}
@SuppressWarnings("unchecked")
@Override
public <L> IdStrategy<L, ?> getIdStrategy(String idStrategyName)
{
IdStrategy<?, ?> idStrategy = idStrategies.get(idStrategyName);
return (IdStrategy<L, ?>) idStrategy;
}
}

View File

@ -0,0 +1,196 @@
package com.massivecraft.mcore4.store;
import java.io.File;
import java.util.AbstractMap.SimpleEntry;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;
import com.massivecraft.mcore4.lib.gson.JsonElement;
import com.massivecraft.mcore4.lib.gson.JsonParser;
import com.massivecraft.mcore4.store.idstrategy.IdStrategyAiGson;
import com.massivecraft.mcore4.store.idstrategy.IdStrategyOidGson;
import com.massivecraft.mcore4.store.idstrategy.IdStrategyUuidMongoAndGson;
import com.massivecraft.mcore4.store.storeadapter.StoreAdapter;
import com.massivecraft.mcore4.store.storeadapter.StoreAdapterGson;
import com.massivecraft.mcore4.util.DiscUtil;
public class DriverGson extends DriverAbstract<JsonElement>
{
protected final static String DOTJSON = ".json";
// -------------------------------------------- //
// IMPLEMENTATION
// -------------------------------------------- //
@Override public Class<JsonElement> getRawdataClass() { return JsonElement.class; }
@Override
public StoreAdapter getStoreAdapter()
{
return StoreAdapterGson.get();
}
@Override
public Db<JsonElement> db(String uri)
{
// "gson://" is 7 chars
File folder = new File(uri.substring(7));
folder.mkdirs();
return new DbGson(this, folder);
}
@Override
public Set<String> collnames(Db<?> db)
{
Set<String> ret = new LinkedHashSet<String>();
for (File f : ((DbGson)db).dir.listFiles())
{
if ( ! f.isDirectory()) continue;
ret.add(f.getName());
}
return ret;
}
@Override
public <L> boolean containsId(Coll<?, L> coll, L id)
{
return fileFromId(coll, id).isFile();
}
@Override
public <L> Long mtime(Coll<?, L> coll, L id)
{
File file = fileFromId(coll, id);
if ( ! file.isFile()) return null;
return file.lastModified();
}
@Override
public <L> Collection<L> ids(Coll<?, L> coll)
{
List<L> ret = new ArrayList<L>();
// Scan the collection folder for .json files
File collDir = getCollDir(coll);
if ( ! collDir.isDirectory()) return ret;
for(File file : collDir.listFiles(JsonFileFilter.get()))
{
// Then convert them to what they should be
String remoteId = idFromFile(file);
L localId = coll.idStrategy().remoteToLocal(remoteId);
ret.add(localId);
}
return ret;
}
@Override
public <L> Map<L, Long> id2mtime(Coll<?, L> coll)
{
Map<L, Long> ret = new HashMap<L, Long>();
// Scan the collection folder for .json files
File collDir = getCollDir(coll);
if ( ! collDir.isDirectory()) return ret;
for(File file : collDir.listFiles(JsonFileFilter.get()))
{
// Then convert them to what they should be
String remoteId = idFromFile(file);
L localId = coll.idStrategy().remoteToLocal(remoteId);
ret.put(localId, file.lastModified());
}
return ret;
}
@Override
public <L> Entry<JsonElement, Long> load(Coll<?, L> coll, L id)
{
File file = fileFromId(coll, id);
Long mtime = file.lastModified();
if (mtime == 0) return null;
String content = DiscUtil.readCatch(file);
if (content == null) return null;
JsonElement raw = new JsonParser().parse(content);
return new SimpleEntry<JsonElement, Long>(raw, mtime);
}
@Override
public <L> Long save(Coll<?, L> coll, L id, Object rawData)
{
File file = fileFromId(coll, id);
getCollDir(coll).mkdirs();
String content = coll.mplugin().gson.toJson((JsonElement)rawData);
if (DiscUtil.writeCatch(file, content) == false) return null;
return file.lastModified();
}
@Override
public <L> void delete(Coll<?, L> coll, L id)
{
File file = fileFromId(coll, id);
file.delete();
}
// -------------------------------------------- //
// UTIL
// -------------------------------------------- //
protected static File getCollDir(Coll<?, ?> coll)
{
return (File) coll.collDriverObject();
}
protected static String idFromFile(File file)
{
if (file == null) return null;
String name = file.getName();
return name.substring(0, name.length()-5);
}
protected static <L> File fileFromId(Coll<?, L> coll, L id)
{
File collDir = getCollDir(coll);
String idString = (String)coll.idStrategy().localToRemote(id);
File idFile = new File(collDir, idString+DOTJSON);
return idFile;
}
//----------------------------------------------//
// CONSTRUCTORS
//----------------------------------------------//
public static String NAME = "gson";
private DriverGson()
{
super(NAME);
}
// -------------------------------------------- //
// INSTANCE
// -------------------------------------------- //
protected static DriverGson instance;
public static DriverGson get()
{
return instance;
}
static
{
instance = new DriverGson();
instance.registerIdStrategy(IdStrategyAiGson.get());
instance.registerIdStrategy(IdStrategyOidGson.get());
instance.registerIdStrategy(IdStrategyUuidMongoAndGson.get());
}
}

View File

@ -0,0 +1,228 @@
package com.massivecraft.mcore4.store;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.AbstractMap.SimpleEntry;
import java.util.Map.Entry;
import com.massivecraft.mcore4.lib.mongodb.BasicDBObject;
import com.massivecraft.mcore4.lib.mongodb.DB;
import com.massivecraft.mcore4.lib.mongodb.DBCollection;
import com.massivecraft.mcore4.lib.mongodb.DBCursor;
import com.massivecraft.mcore4.lib.mongodb.MongoURI;
import com.massivecraft.mcore4.store.idstrategy.IdStrategyAiMongo;
import com.massivecraft.mcore4.store.idstrategy.IdStrategyOidMongo;
import com.massivecraft.mcore4.store.idstrategy.IdStrategyUuidMongoAndGson;
import com.massivecraft.mcore4.store.storeadapter.StoreAdapter;
import com.massivecraft.mcore4.store.storeadapter.StoreAdapterMongo;
public class DriverMongo extends DriverAbstract<BasicDBObject>
{
// -------------------------------------------- //
// STATIC
// -------------------------------------------- //
public final static String ID_FIELD = "_id";
public final static String MTIME_FIELD = "_mtime";
public final static BasicDBObject dboEmpty = new BasicDBObject();
public final static BasicDBObject dboKeysId = new BasicDBObject().append(ID_FIELD, 1);
public final static BasicDBObject dboKeysMtime = new BasicDBObject().append(MTIME_FIELD, 1);
public final static BasicDBObject dboKeysIdandMtime = new BasicDBObject().append(ID_FIELD, 1).append(MTIME_FIELD, 1);
// -------------------------------------------- //
// IMPLEMENTATION
// -------------------------------------------- //
@Override public Class<BasicDBObject> getRawdataClass() { return BasicDBObject.class; }
@Override
public StoreAdapter getStoreAdapter()
{
return StoreAdapterMongo.get();
}
@Override
public Db<BasicDBObject> db(String uri)
{
DB db = this.getDbInner(uri);
return new DbMongo(this, db);
}
@Override
public Set<String> collnames(Db<?> db)
{
return ((DbMongo)db).db.getCollectionNames();
}
@Override
public <L> boolean containsId(Coll<?, L> coll, L id)
{
DBCollection dbcoll = fixColl(coll);
DBCursor cursor = dbcoll.find(new BasicDBObject(ID_FIELD, coll.idStrategy().localToRemote(id)));
return cursor.count() != 0;
}
@Override
public <L> Long mtime(Coll<?, L> coll, L id)
{
DBCollection dbcoll = fixColl(coll);
BasicDBObject found = (BasicDBObject)dbcoll.findOne(new BasicDBObject(ID_FIELD, coll.idStrategy().localToRemote(id)), dboKeysMtime);
if (found == null) return null;
if ( ! found.containsField(MTIME_FIELD)) return null; // This should not happen! But better to ignore than crash?
return found.getLong(MTIME_FIELD);
}
@Override
public <L> Collection<L> ids(Coll<?, L> coll)
{
List<L> ret = null;
DBCollection dbcoll = fixColl(coll);
DBCursor cursor = dbcoll.find(dboEmpty, dboKeysId);
try
{
ret = new ArrayList<L>(cursor.count());
while(cursor.hasNext())
{
Object remoteId = cursor.next().get(ID_FIELD);
L localId = coll.idStrategy().remoteToLocal(remoteId);
ret.add(localId);
}
}
finally
{
cursor.close();
}
return ret;
}
@Override
public <L> Map<L, Long> id2mtime(Coll<?, L> coll)
{
Map<L, Long> ret = null;
DBCollection dbcoll = fixColl(coll);
DBCursor cursor = dbcoll.find(dboEmpty, dboKeysIdandMtime);
try
{
ret = new HashMap<L, Long>(cursor.count());
while(cursor.hasNext())
{
BasicDBObject raw = (BasicDBObject)cursor.next();
Object remoteId = raw.get(ID_FIELD);
L localId = coll.idStrategy().remoteToLocal(remoteId);
if ( ! raw.containsField(MTIME_FIELD)) continue; // This should not happen! But better to ignore than crash?
Long mtime = raw.getLong(MTIME_FIELD);
ret.put(localId, mtime);
}
}
finally
{
cursor.close();
}
return ret;
}
@Override
public <L> Entry<BasicDBObject, Long> load(Coll<?, L> coll, L id)
{
DBCollection dbcoll = fixColl(coll);
BasicDBObject raw = (BasicDBObject)dbcoll.findOne(new BasicDBObject(ID_FIELD, coll.idStrategy().localToRemote(id)));
if (raw == null) return null;
Long mtime = (Long) raw.removeField(MTIME_FIELD);
return new SimpleEntry<BasicDBObject, Long>(raw, mtime);
}
@Override
public <L> Long save(Coll<?, L> coll, L id, Object rawData)
{
DBCollection dbcoll = fixColl(coll);
// We shallow copy here in order to stop the extra "_mtime" field from messing up the lastRaw.
BasicDBObject data = (BasicDBObject)rawData;
data = (BasicDBObject)data.clone();
Long mtime = System.currentTimeMillis();
data.put(MTIME_FIELD, mtime);
Object remoteId = coll.idStrategy().localToRemote(id);
dbcoll.update(new BasicDBObject(ID_FIELD, remoteId), data, true, false);
return mtime;
}
@Override
public <L> void delete(Coll<?, L> coll, L id)
{
fixColl(coll).remove(new BasicDBObject(ID_FIELD, coll.idStrategy().localToRemote(id)));
}
//----------------------------------------------//
// UTIL
//----------------------------------------------//
protected static DBCollection fixColl(Coll<?, ?> coll)
{
return (DBCollection) coll.collDriverObject();
}
protected DB getDbInner(String uri)
{
MongoURI muri = new MongoURI(uri);
try
{
DB db = muri.connectDB();
if (muri.getUsername() == null) return db;
if ( ! db.authenticate(muri.getUsername(), muri.getPassword()))
{
//log(Level.SEVERE, "... db authentication failed.");
return null;
}
return db;
}
catch (Exception e)
{
//log(Level.SEVERE, "... mongo connection failed.");
e.printStackTrace();
return null;
}
}
//----------------------------------------------//
// CONSTRUCTORS
//----------------------------------------------//
private DriverMongo()
{
super("mongodb");
}
// -------------------------------------------- //
// INSTANCE
// -------------------------------------------- //
protected static DriverMongo instance;
public static DriverMongo get()
{
return instance;
}
static
{
instance = new DriverMongo();
instance.registerIdStrategy(IdStrategyAiMongo.get());
instance.registerIdStrategy(IdStrategyOidMongo.get());
instance.registerIdStrategy(IdStrategyUuidMongoAndGson.get());
}
}

View File

@ -0,0 +1,74 @@
package com.massivecraft.mcore4.store;
import com.massivecraft.mcore4.store.accessor.Accessor;
/**
* Usage of this class is highly optional. You may persist anything. If you are
* creating the class to be persisted yourself, it might be handy to extend this
* Entity class. It just contains a set of shortcut methods.
*/
// Self referencing generic using the "getThis trick".
// http://www.angelikalanger.com/GenericsFAQ/FAQSections/ProgrammingIdioms.html#FAQ206
public abstract class Entity<E extends Entity<E, L>, L>
{
public abstract Coll<E, L> getColl();
protected abstract E getThis();
public L attach()
{
return this.getColl().attach(getThis());
}
public E detach()
{
return this.getColl().detach(getThis());
}
public boolean attached()
{
return this.getColl().getAll().contains(getThis());
}
public boolean detached()
{
return ! this.attached();
}
// TODO: Mark as dirty.
// TODO: Perhaps even brute force methods to save or load from remote.
/*public boolean save()
{
return this.getColl().saveEntity(getThis());
}*/
public L getId()
{
return this.getColl().id(getThis());
}
@Override
public String toString()
{
return this.getClass().getSimpleName()+this.getColl().mplugin().gson.toJson(this, this.getColl().entityClass());
}
public E getDefaultInstance()
{
return this.getColl().createNewInstance();
}
public E loadDefaults()
{
Accessor.get(this.getColl().entityClass()).copy(this.getDefaultInstance(), this.getThis());
return this.getThis();
}
public E load(E entity)
{
Accessor.get(this.getColl().entityClass()).copy(entity, this.getThis());
return this.getThis();
}
}

View File

@ -0,0 +1,43 @@
package com.massivecraft.mcore4.store;
public class ExamineThread<E, L> extends Thread
{
protected Coll<E, L> coll;
public ExamineThread(Coll<E, L> coll)
{
this.coll = coll;
this.setName("ExamineThread for "+coll.name());
}
// TODO: Implement logging and/or auto adjusting system for how long the sleep should be?
@Override
public void run()
{
while(true)
{
try
{
//long before = System.currentTimeMillis();
coll.findSuspects();
//long after = System.currentTimeMillis();
//XCore.p.log(this.getName()+ " complete. Took "+ (after-before) +"ms.");
Thread.sleep(5000);
}
catch (InterruptedException e)
{
// We've been interrupted. Lets bail.
return;
}
catch (Exception e)
{
e.printStackTrace();
}
}
}
}

View File

@ -0,0 +1,25 @@
package com.massivecraft.mcore4.store;
import java.io.File;
import java.io.FileFilter;
public class JsonFileFilter implements FileFilter
{
private final static String DOTJSON = ".json";
@Override
public boolean accept(File pathname)
{
return pathname.getName().toLowerCase().endsWith(DOTJSON);
}
// -------------------------------------------- //
// INSTANCE
// -------------------------------------------- //
protected static JsonFileFilter instance = new JsonFileFilter();
public static JsonFileFilter get()
{
return instance;
}
}

View File

@ -0,0 +1,49 @@
package com.massivecraft.mcore4.store;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.HashMap;
import java.util.Map;
public class MStore
{
protected static Map<String, Driver<?>> drivers = new HashMap<String, Driver<?>>();
public static boolean registerDriver(Driver<?> driver)
{
if (drivers.containsKey(driver.name())) return false;
drivers.put(driver.name(), driver);
return true;
}
public static Driver<?> getDriver(String id)
{
return drivers.get(id);
}
public static Db<?> getDb(String uri)
{
try
{
return getDb(new URI(uri));
}
catch (URISyntaxException e)
{
e.printStackTrace();
return null;
}
}
public static Db<?> getDb(URI uri)
{
String scheme = uri.getScheme();
Driver<?> driver = getDriver(scheme);
if (driver == null) return null;
return driver.db(uri.toString());
}
static
{
registerDriver(DriverMongo.get());
registerDriver(DriverGson.get());
}
}

View File

@ -0,0 +1,75 @@
package com.massivecraft.mcore4.store;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import com.massivecraft.mcore4.Predictate;
public class MStoreUtil
{
public static <T> ArrayList<T> uglySQL(Collection<T> items, Predictate<T> where, Comparator<T> orderby, Integer limit, Integer offset)
{
ArrayList<T> ret = new ArrayList<T>(items.size());
// WHERE
if (where == null)
{
ret.addAll(items);
}
else
{
for (T item : items)
{
if (where.apply(item))
{
ret.add(item);
}
}
}
// ORDERBY
if (orderby != null)
{
Collections.sort(ret, orderby);
}
// LIMIT AND OFFSET
// Parse args
int fromIndex = 0;
if (offset != null)
{
fromIndex = offset;
}
int toIndex = ret.size()-1;
if (limit != null)
{
toIndex = offset+limit;
}
// Clean args
if (fromIndex <= 0)
{
fromIndex = 0;
}
else if (fromIndex > ret.size()-1)
{
fromIndex = ret.size()-1;
}
if (toIndex < fromIndex)
{
toIndex = fromIndex;
}
else if (toIndex > ret.size()-1)
{
toIndex = ret.size()-1;
}
// No limit?
if (fromIndex == 0 && toIndex == ret.size()-1) return ret;
return new ArrayList<T>(ret.subList(fromIndex, toIndex));
}
}

View File

@ -0,0 +1,28 @@
package com.massivecraft.mcore4.store;
public enum ModificationState
{
LOCAL_ALTER (true, true),
LOCAL_ATTACH (true, true),
LOCAL_DETACH (true, true),
REMOTE_ALTER (true, false),
REMOTE_ATTACH (true, false),
REMOTE_DETACH (true, false),
NONE (false, false),
UNKNOWN (false, false),
;
private final boolean modified;
public boolean modified() { return this.modified; }
private final boolean local;
public boolean local() { return this.local; }
public boolean remote() { return this.local == false; }
private ModificationState(boolean modified, boolean local)
{
this.modified = modified;
this.local = local;
}
}

View File

@ -0,0 +1,51 @@
package com.massivecraft.mcore4.store;
import java.util.Collection;
import org.bukkit.entity.Player;
import org.bukkit.event.player.PlayerEvent;
import com.massivecraft.mcore4.MPlugin;
import com.massivecraft.mcore4.Predictate;
public class PlayerColl<E extends PlayerEntity<E>> extends Coll<E, String>
{
public PlayerColl(MPlugin mplugin, Db<?> db, String name, Class<E> entityClass)
{
super(mplugin, db, "ai", name, entityClass, String.class, true);
}
@Override
public String idFix(Object oid)
{
if (oid == null) return null;
if (oid instanceof String) return (String) oid;
if (oid instanceof Player) return ((Player)oid).getName();
if (oid instanceof PlayerEvent) return ((PlayerEvent)oid).getPlayer().getName();
return null;
}
public Collection<E> getAllOnline()
{
// TODO: Reverse the order since we have fewer players online than offline?
return this.getAll(new Predictate<E>()
{
public boolean apply(E entity)
{
return entity.isOnline();
}
});
}
public Collection<E> getAllOffline()
{
return this.getAll(new Predictate<E>()
{
public boolean apply(E entity)
{
return entity.isOffline();
}
});
}
}

View File

@ -0,0 +1,79 @@
package com.massivecraft.mcore4.store;
import java.util.Collection;
import org.bukkit.GameMode;
import org.bukkit.entity.Player;
import com.massivecraft.mcore4.util.PlayerUtil;
import com.massivecraft.mcore4.util.Txt;
public abstract class PlayerEntity<E extends PlayerEntity<E>> extends Entity<E, String>
{
public Player getPlayer()
{
return PlayerUtil.getPlayerExact(this.getId());
}
public boolean isOnline()
{
return this.getPlayer() != null;
}
public boolean isOffline()
{
return ! isOnline();
}
// -------------------------------------------- //
// CHECKER UTILS
// -------------------------------------------- //
public boolean isGameMode(GameMode gm, boolean defaultIfOffline)
{
Player player = this.getPlayer();
if (player == null || ! player.isOnline()) return defaultIfOffline;
return player.getGameMode() == gm;
}
// -------------------------------------------- //
// Message Sending Helpers
// -------------------------------------------- //
public void sendMessage(String msg)
{
Player player = this.getPlayer();
if (player == null) return;
player.sendMessage(msg);
}
public void sendMessage(Collection<String> msgs)
{
Player player = this.getPlayer();
if (player == null) return;
for(String msg : msgs)
{
player.sendMessage(msg);
}
}
public void msg(String msg)
{
this.sendMessage(Txt.parse(msg));
}
public void msg(String msg, Object... args)
{
this.sendMessage(Txt.parse(msg, args));
}
public void msg(Collection<String> msgs)
{
Player player = this.getPlayer();
if (player == null) return;
for(String msg : msgs)
{
player.sendMessage(Txt.parse(msg));
}
}
}

View File

@ -0,0 +1,20 @@
package com.massivecraft.mcore4.store.accessor;
import java.util.HashMap;
import java.util.Map;
public final class Accessor
{
private static Map<Class<?>, EntityAccessor> class2EntityAccessor = new HashMap<Class<?>, EntityAccessor>();
public static EntityAccessor get(Class<?> clazz)
{
EntityAccessor ret = class2EntityAccessor.get(clazz);
if (ret == null)
{
ret = new EntityAccessorPerProperty(clazz);
class2EntityAccessor.put(clazz, ret);
}
return ret;
}
}

View File

@ -0,0 +1,175 @@
package com.massivecraft.mcore4.store.accessor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class AccessorUtil
{
//----------------------------------------------//
// MAKE ACCESSIBLE
//----------------------------------------------//
public static void makeAccessible(Field field)
{
if ((!Modifier.isPublic(field.getModifiers()) || !Modifier.isPublic(field.getDeclaringClass().getModifiers()) || Modifier.isFinal(field.getModifiers())) && !field.isAccessible())
{
field.setAccessible(true);
}
}
public static void makeAccessible(Method method)
{
if ((!Modifier.isPublic(method.getModifiers()) || !Modifier.isPublic(method.getDeclaringClass().getModifiers())) && !method.isAccessible())
{
method.setAccessible(true);
}
}
//----------------------------------------------//
// FIND
//----------------------------------------------//
public static List<Method> findMethod(Class<?> clazz, String name)
{
List<Method> ret = new ArrayList<Method>();
for (Class<?> c = clazz; c != null; c = c.getSuperclass())
{
Method[] methods = (c.isInterface() ? c.getMethods() : c.getDeclaredMethods());
for (Method method : methods)
{
if (name.equals(method.getName())) ret.add(method);
}
}
return ret;
}
public static Field findField(Class<?> clazz, String name)
{
for (Class<?> c = clazz; c != null && !Object.class.equals(c); c = c.getSuperclass())
{
Field[] fields = c.getDeclaredFields();
for (Field field : fields)
{
if (name.equals(field.getName())) return field;
}
}
return null;
}
public static List<Field> findAllFields(Class<?> clazz)
{
List<Field> fields = new ArrayList<Field>();
for (Class<?> c = clazz; c != null; c = c.getSuperclass())
{
fields.addAll(Arrays.asList(c.getDeclaredFields()));
}
return fields;
}
/**
* Must be non-transient.
* Must be non-final.
* Will be set accessible if possible.
*/
public static Map<String, Field> getFieldMap(Class<?> clazz)
{
Map<String, Field> ret = new HashMap<String, Field>();
for (Field field : findAllFields(clazz))
{
if (Modifier.isTransient(field.getModifiers())) continue;
if (Modifier.isFinal(field.getModifiers())) continue;
makeAccessible(field);
ret.put(field.getName(), field);
}
return ret;
}
//----------------------------------------------//
// FIND GETTERS AND SETTERS
//----------------------------------------------//
public static String ucfirst(String str)
{
return Character.toUpperCase(str.charAt(0))+str.substring(1);
}
public static String calcGetterNameBool(String fieldName) { return "is"+ucfirst(fieldName); }
public static String calcGetterName(String fieldName) { return "get"+ucfirst(fieldName); }
public static String calcSetterName(String fieldName) { return "set"+ucfirst(fieldName); }
// TODO: Use a predictate?
public static Method findGetter(Class<?> clazz, String fieldName)
{
for (Method method : findMethod(clazz, calcGetterName(fieldName)))
{
if (method.getParameterTypes().length == 0 && method.getReturnType() != null) return method;
}
for (Method method : findMethod(clazz, calcGetterNameBool(fieldName)))
{
if (method.getParameterTypes().length == 0 && method.getReturnType() == Boolean.class) return method;
}
return null;
}
public static Method findSetter(Class<?> clazz, String fieldName)
{
List<Method> methods = findMethod(clazz, calcSetterName(fieldName));
methods.addAll(findMethod(clazz, fieldName));
for (Method method : methods)
{
if (method != null && method.getParameterTypes().length == 1) return method;
}
return null;
}
//----------------------------------------------//
// CREATE PROPERTY ACCESS
//----------------------------------------------//
public static PropertyGetter createPropertyGetter(Class<?> clazz, String name)
{
Method method = findGetter(clazz, name);
if (method != null) return new PropertyGetterMethodReflection(method);
Field field = findField(clazz, name);
if (field != null) return new PropertyGetterFieldReflection(field);
return null;
}
public static PropertySetter createPropertySetter(Class<?> clazz, String name)
{
Method method = findSetter(clazz, name);
if (method != null) return new PropertySetterMethodReflection(method);
Field field = findField(clazz, name);
if (field != null) return new PropertySetterFieldReflection(field);
return null;
}
public static PropertyAccessor createPropertyAccessor(Class<?> clazz, String name)
{
PropertyGetter getter = createPropertyGetter(clazz, name);
if (getter == null) return null;
PropertySetter setter = createPropertySetter(clazz, name);
if (setter == null) return null;
return new PropertyAccessorComposite(getter, setter);
}
}

View File

@ -0,0 +1,6 @@
package com.massivecraft.mcore4.store.accessor;
public interface EntityAccessor extends EntitySetter, EntityGetter, EntityGlue
{
}

View File

@ -0,0 +1,51 @@
package com.massivecraft.mcore4.store.accessor;
import java.util.Collection;
public abstract class EntityAccessorAbstract implements EntityAccessor
{
protected final Class<?> clazz;
public Class<?> clazz() { return this.clazz; }
public EntityAccessorAbstract(Class<?> clazz)
{
this.clazz = clazz;
}
private static final boolean DEFAULT_TRANSPARENT = false;
@Override
public void copy(Object from, Object to, String property, boolean transparent)
{
Object val = this.get(from, property);
if (transparent && val == null) return;
this.set(to, property, val);
}
@Override
public void copy(Object from, Object to, String property)
{
this.copy(from, to, property, DEFAULT_TRANSPARENT);
}
@Override
public void copy(Object from, Object to, Collection<String> properties, boolean transparent)
{
for (String property : properties)
{
this.copy(from, to, property, transparent);
}
}
@Override
public void copy(Object from, Object to, Collection<String> properties)
{
this.copy(from, to, properties, DEFAULT_TRANSPARENT);
}
@Override
public void copy(Object from, Object to, boolean transparent)
{
this.copy(from, to, this.getPropertyNames(), transparent);
}
@Override
public void copy(Object from, Object to)
{
this.copy(from, to, DEFAULT_TRANSPARENT);
}
}

View File

@ -0,0 +1,85 @@
package com.massivecraft.mcore4.store.accessor;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
public class EntityAccessorPerProperty extends EntityAccessorAbstract
{
protected Map<String, PropertyAccessor> propertyAccessors;
public Map<String, PropertyAccessor> propertyAccessors() { return this.propertyAccessors; }
public PropertyAccessor propertyAccessor(String name)
{
PropertyAccessor ret = this.propertyAccessors.get(name);
if (ret == null)
{
throw new IllegalArgumentException("The property \""+name+"\" is not supported.");
}
return ret;
}
public void propertyAccessor(String name, PropertyAccessor val)
{
this.propertyAccessors.put(name, val);
}
//----------------------------------------------//
// CONSTRUCTORS
//----------------------------------------------//
public EntityAccessorPerProperty(Class<?> clazz)
{
super(clazz);
this.propertyAccessors = new HashMap<String, PropertyAccessor>();
this.populateAI1();
}
//----------------------------------------------//
// AI
//----------------------------------------------//
public void populateAI1()
{
this.propertyAccessorAI(AccessorUtil.getFieldMap(this.clazz).keySet());
}
public void propertyAccessorAI(String name)
{
this.propertyAccessors.put(name, AccessorUtil.createPropertyAccessor(this.clazz, name));
}
public void propertyAccessorAI(String... names)
{
for (String name : names)
{
this.propertyAccessorAI(name);
}
}
public void propertyAccessorAI(Collection<? extends String> names)
{
this.propertyAccessorAI(names.toArray(new String[0]));
}
//----------------------------------------------//
// IMPLEMENTATION
//----------------------------------------------//
@Override
public void set(Object entity, String property, Object val)
{
PropertyAccessor pa = this.propertyAccessor(property);
pa.set(entity, val);
}
@Override
public Object get(Object entity, String property)
{
PropertyAccessor pa = this.propertyAccessor(property);
return pa.get(entity);
}
@Override
public Collection<String> getPropertyNames()
{
return this.propertyAccessors.keySet();
}
}

View File

@ -0,0 +1,6 @@
package com.massivecraft.mcore4.store.accessor;
public interface EntityGetter
{
public Object get(Object entity, String property);
}

View File

@ -0,0 +1,17 @@
package com.massivecraft.mcore4.store.accessor;
import java.util.Collection;
public interface EntityGlue
{
public void copy(Object from, Object to, String property, boolean transparent);
public void copy(Object from, Object to, String property);
public void copy(Object from, Object to, Collection<String> properties, boolean transparent);
public void copy(Object from, Object to, Collection<String> properties);
public void copy(Object from, Object to, boolean transparent);
public void copy(Object from, Object to);
public Collection<String> getPropertyNames();
}

View File

@ -0,0 +1,6 @@
package com.massivecraft.mcore4.store.accessor;
public interface EntitySetter
{
public void set(Object entity, String property, Object val);
}

View File

@ -0,0 +1,6 @@
package com.massivecraft.mcore4.store.accessor;
public interface PropertyAccessor extends PropertySetter, PropertyGetter
{
}

View File

@ -0,0 +1,25 @@
package com.massivecraft.mcore4.store.accessor;
public class PropertyAccessorComposite implements PropertyAccessor
{
private final PropertyGetter getter;
private final PropertySetter setter;
public PropertyAccessorComposite(PropertyGetter getter, PropertySetter setter)
{
this.getter = getter;
this.setter = setter;
}
@Override
public void set(Object entity, Object val)
{
this.setter.set(entity, val);
}
@Override
public Object get(Object entity)
{
return this.getter.get(entity);
}
}

View File

@ -0,0 +1,6 @@
package com.massivecraft.mcore4.store.accessor;
public interface PropertyGetter
{
public Object get(Object entity);
}

View File

@ -0,0 +1,29 @@
package com.massivecraft.mcore4.store.accessor;
import java.lang.reflect.Field;
public class PropertyGetterFieldReflection implements PropertyGetter
{
private final Field field;
public PropertyGetterFieldReflection(Field field)
{
AccessorUtil.makeAccessible(field);
this.field = field;
}
@Override
public Object get(Object entity)
{
try
{
return this.field.get(entity);
}
catch (Exception e)
{
e.printStackTrace();
return null;
}
}
}

View File

@ -0,0 +1,29 @@
package com.massivecraft.mcore4.store.accessor;
import java.lang.reflect.Method;
public class PropertyGetterMethodReflection implements PropertyGetter
{
private final Method method;
public PropertyGetterMethodReflection(Method method)
{
AccessorUtil.makeAccessible(method);
this.method = method;
}
@Override
public Object get(Object entity)
{
try
{
return this.method.invoke(entity, new Object[0]);
}
catch (Exception e)
{
e.printStackTrace();
return null;
}
}
}

View File

@ -0,0 +1,6 @@
package com.massivecraft.mcore4.store.accessor;
public interface PropertySetter
{
public void set(Object entity, Object val);
}

View File

@ -0,0 +1,28 @@
package com.massivecraft.mcore4.store.accessor;
import java.lang.reflect.Field;
public class PropertySetterFieldReflection implements PropertySetter
{
private final Field field;
public PropertySetterFieldReflection(Field field)
{
AccessorUtil.makeAccessible(field);
this.field = field;
}
@Override
public void set(Object entity, Object val)
{
try
{
this.field.set(entity, val);
}
catch (Exception e)
{
e.printStackTrace();
}
}
}

View File

@ -0,0 +1,28 @@
package com.massivecraft.mcore4.store.accessor;
import java.lang.reflect.Method;
public class PropertySetterMethodReflection implements PropertySetter
{
private final Method method;
public PropertySetterMethodReflection(Method method)
{
AccessorUtil.makeAccessible(method);
this.method = method;
}
@Override
public void set(Object entity, Object val)
{
try
{
this.method.invoke(entity, val);
}
catch (Exception e)
{
e.printStackTrace();
}
}
}

View File

@ -0,0 +1,29 @@
package com.massivecraft.mcore4.store.idstrategy;
import com.massivecraft.mcore4.store.CollInterface;
/**
* The tasks of the IdStrategy:
* 1. Generate a new free id that has not yet been used.
* 2. Convert the id between java object and raw database data.
*
* To complete the tasks the IdStrategy must be tailored for a specific database-driver.
* Thus you will find multiple implementations with the name "ai" (auto increment).
* There must be one implementation per driver.
*/
public interface IdStrategy<L, R>
{
// The name of the strategy (such as "auto_increment")
public String getName();
// The id classes
public Class<L> getLocalClass();
public Class<R> getRemoteClass();
// Convert
public R localToRemote(Object local);
public L remoteToLocal(Object remote);
// Generate
public L generate(CollInterface<?, L> coll);
}

View File

@ -0,0 +1,41 @@
package com.massivecraft.mcore4.store.idstrategy;
import java.util.Collection;
import com.massivecraft.mcore4.store.CollInterface;
public abstract class IdStrategyAbstract<L, R> implements IdStrategy<L, R>
{
public IdStrategyAbstract(String name, Class<L> localClass, Class<R> remoteClass)
{
this.name = name;
this.localClass = localClass;
this.remoteClass = remoteClass;
}
protected String name;
@Override public String getName() { return this.name; }
protected Class<L> localClass;
@Override public Class<L> getLocalClass() { return this.localClass; }
protected Class<R> remoteClass;
@Override public Class<R> getRemoteClass() { return this.remoteClass; }
@Override
public L generate(CollInterface<?, L> coll)
{
Collection<L> alreadyInUse = coll.ids();
L ret = null;
do
{
ret = this.generateAttempt(coll);
if (ret == null) return null;
}
while (alreadyInUse.contains(ret));
return ret;
}
public abstract L generateAttempt(CollInterface<?, L> coll);
}

View File

@ -0,0 +1,91 @@
package com.massivecraft.mcore4.store.idstrategy;
import java.io.File;
import java.io.IOException;
import com.massivecraft.mcore4.store.CollInterface;
import com.massivecraft.mcore4.store.DbGson;
import com.massivecraft.mcore4.util.DiscUtil;
public class IdStrategyAiGson extends IdStrategyAbstract<String, String>
{
//----------------------------------------------//
// CONSTRUCTORS
//----------------------------------------------//
private IdStrategyAiGson()
{
super("ai", String.class, String.class);
}
// -------------------------------------------- //
// IMPLEMENTATION
// -------------------------------------------- //
@Override public String localToRemote(Object local) { return (String)local; }
@Override public String remoteToLocal(Object remote) { return (String)remote; }
@Override
public String generateAttempt(CollInterface<?, String> coll)
{
File file = getAiFile(coll);
// Ensure the file exists
if (this.ensureFileExists(file) == false)
{
return null;
}
String content = DiscUtil.readCatch(file);
if (content == null)
{
return null;
}
Integer current = 0;
if (content.length() > 0)
{
current = Integer.valueOf(content);
}
Integer next = current + 1;
if (DiscUtil.writeCatch(file, next.toString()) == false)
{
return null;
}
return current.toString();
}
protected File getAiFile(CollInterface<?, String> coll)
{
DbGson cdb = (DbGson)coll.db();
return new File(cdb.dir, coll.name() + "_ai.txt");
}
protected boolean ensureFileExists(File file)
{
if (file.isFile()) return true;
if (file.isDirectory()) return false;
try
{
return file.createNewFile();
}
catch (IOException e)
{
return false;
}
}
// -------------------------------------------- //
// INSTANCE
// -------------------------------------------- //
protected static IdStrategyAiGson instance = new IdStrategyAiGson();
public static IdStrategyAiGson get()
{
return instance;
}
}

View File

@ -0,0 +1,67 @@
package com.massivecraft.mcore4.store.idstrategy;
import com.massivecraft.mcore4.lib.mongodb.BasicDBObject;
import com.massivecraft.mcore4.lib.mongodb.DBCollection;
import com.massivecraft.mcore4.lib.mongodb.DBObject;
import com.massivecraft.mcore4.store.CollInterface;
import com.massivecraft.mcore4.store.DbMongo;
public class IdStrategyAiMongo extends IdStrategyAbstract<String, String>
{
private static String SEC = "seq";
// -------------------------------------------- //
// FIELDS
// -------------------------------------------- //
// String sequenseName;
// DBCollection sequenseCollection;
String sequenseField;
//----------------------------------------------//
// CONSTRUCTORS
//----------------------------------------------//
private IdStrategyAiMongo()
{
super("ai", String.class, String.class);
this.sequenseField = SEC;
}
// -------------------------------------------- //
// IMPLEMENTATION
// -------------------------------------------- //
@Override public String localToRemote(Object local) { return (String)local; }
@Override public String remoteToLocal(Object remote) { return (String)remote; }
// http://dev.bubblemix.net/blog/2011/04/auto-increment-for-mongodb-with-the-java-driver/
@Override
public String generateAttempt(CollInterface<?, String> coll)
{
String sequenseName = coll.name();
DBCollection sequenseCollection = ((DbMongo)coll.db()).db.getCollection(SEC);
// this object represents your "query", its analogous to a WHERE clause in SQL
DBObject query = new BasicDBObject();
query.put("_id", sequenseName); // where _id = the input sequence name
// this object represents the "update" or the SET blah=blah in SQL
DBObject change = new BasicDBObject(this.sequenseField, 1);
DBObject update = new BasicDBObject("$inc", change); // the $inc here is a mongodb command for increment
// Atomically updates the sequence field and returns the value for you
DBObject res = sequenseCollection.findAndModify(query, new BasicDBObject(), new BasicDBObject(), false, update, true, true);
return res.get(this.sequenseField).toString();
}
// -------------------------------------------- //
// INSTANCE
// -------------------------------------------- //
protected static IdStrategyAiMongo instance = new IdStrategyAiMongo();
public static IdStrategyAiMongo get()
{
return instance;
}
}

View File

@ -0,0 +1,39 @@
package com.massivecraft.mcore4.store.idstrategy;
import com.massivecraft.mcore4.lib.bson.types.ObjectId;
import com.massivecraft.mcore4.store.CollInterface;
public class IdStrategyOidGson extends IdStrategyAbstract<ObjectId, String>
{
// -------------------------------------------- //
// IMPLEMENTATION
// -------------------------------------------- //
@Override public String localToRemote(Object local) { return ((ObjectId)local).toStringBabble(); }
@Override public ObjectId remoteToLocal(Object remote) { return ObjectId.massageToObjectId((String)remote); }
@Override
public ObjectId generateAttempt(CollInterface<?, ObjectId> coll)
{
return ObjectId.get();
}
//----------------------------------------------//
// CONSTRUCTORS
//----------------------------------------------//
private IdStrategyOidGson()
{
super("oid", ObjectId.class, String.class);
}
// -------------------------------------------- //
// INSTANCE
// -------------------------------------------- //
protected static IdStrategyOidGson instance = new IdStrategyOidGson();
public static IdStrategyOidGson get()
{
return instance;
}
}

View File

@ -0,0 +1,40 @@
package com.massivecraft.mcore4.store.idstrategy;
import com.massivecraft.mcore4.lib.bson.types.ObjectId;
import com.massivecraft.mcore4.store.CollInterface;
public class IdStrategyOidMongo extends IdStrategyAbstract<ObjectId, ObjectId>
{
// -------------------------------------------- //
// IMPLEMENTATION
// -------------------------------------------- //
@Override public ObjectId localToRemote(Object local) { return (ObjectId)local; }
@Override public ObjectId remoteToLocal(Object remote) { return (ObjectId)remote; }
@Override
public ObjectId generateAttempt(CollInterface<?, ObjectId> coll)
{
return ObjectId.get();
}
//----------------------------------------------//
// CONSTRUCTORS
//----------------------------------------------//
private IdStrategyOidMongo()
{
super("oid", ObjectId.class, ObjectId.class);
}
// -------------------------------------------- //
// INSTANCE
// -------------------------------------------- //
protected static IdStrategyOidMongo instance = new IdStrategyOidMongo();
public static IdStrategyOidMongo get()
{
return instance;
}
}

View File

@ -0,0 +1,40 @@
package com.massivecraft.mcore4.store.idstrategy;
import java.util.UUID;
import com.massivecraft.mcore4.store.CollInterface;
public class IdStrategyUuidMongoAndGson extends IdStrategyAbstract<UUID, String>
{
// -------------------------------------------- //
// IMPLEMENTATION
// -------------------------------------------- //
@Override public String localToRemote(Object local) { return ((UUID)local).toString(); }
@Override public UUID remoteToLocal(Object remote) { return UUID.fromString((String)remote); }
@Override
public UUID generateAttempt(CollInterface<?, UUID> coll)
{
return UUID.randomUUID();
}
//----------------------------------------------//
// CONSTRUCTORS
//----------------------------------------------//
private IdStrategyUuidMongoAndGson()
{
super("uuid", UUID.class, String.class);
}
// -------------------------------------------- //
// INSTANCE
// -------------------------------------------- //
protected static IdStrategyUuidMongoAndGson instance = new IdStrategyUuidMongoAndGson();
public static IdStrategyUuidMongoAndGson get()
{
return instance;
}
}

View File

@ -0,0 +1,142 @@
package com.massivecraft.mcore4.store.storeadapter;
import java.util.Map.Entry;
import com.massivecraft.mcore4.lib.bson.types.ObjectId;
import com.massivecraft.mcore4.lib.gson.JsonArray;
import com.massivecraft.mcore4.lib.gson.JsonElement;
import com.massivecraft.mcore4.lib.gson.JsonNull;
import com.massivecraft.mcore4.lib.gson.JsonObject;
import com.massivecraft.mcore4.lib.gson.JsonPrimitive;
import com.massivecraft.mcore4.lib.mongodb.BasicDBList;
import com.massivecraft.mcore4.lib.mongodb.BasicDBObject;
import com.massivecraft.mcore4.lib.mongodb.DBObject;
public final class MongoGsonConverter
{
// -------------------------------------------- //
// GSON 2 MONGO
// -------------------------------------------- //
public static BasicDBObject gson2MongoObject(JsonElement inElement)
{
JsonObject in = inElement.getAsJsonObject();
BasicDBObject out = new BasicDBObject();
for (Entry<String, JsonElement> entry : in.entrySet())
{
String key = entry.getKey();
JsonElement val = entry.getValue();
if (val.isJsonArray())
{
out.put(key, gson2MongoArray(val));
}
else if (val.isJsonObject())
{
out.put(key, gson2MongoObject(val));
}
else
{
out.put(key, gson2MongoPrimitive(val));
}
}
return out;
}
public static BasicDBList gson2MongoArray(JsonElement inElement)
{
JsonArray in = inElement.getAsJsonArray();
BasicDBList out = new BasicDBList();
for (int i = 0; i < in.size(); i++)
{
JsonElement element = in.get(i);
if (element.isJsonArray())
{
out.add(gson2MongoArray(element));
}
else if (element.isJsonObject())
{
out.add(gson2MongoObject(element));
}
else
{
out.add(gson2MongoPrimitive(element));
}
}
return out;
}
public static Object gson2MongoPrimitive(JsonElement inElement)
{
if (inElement.isJsonNull()) return null;
JsonPrimitive in = inElement.getAsJsonPrimitive();
if (in.isBoolean()) return in.getAsBoolean();
if (in.isNumber()) return in.getAsNumber();
if (in.isString()) return in.getAsString();
throw new IllegalArgumentException("Unsupported value type for: " + in);
}
// -------------------------------------------- //
// MONGO 2 GSON
// -------------------------------------------- //
public static JsonObject mongo2GsonObject(DBObject inObject)
{
if (!(inObject instanceof BasicDBObject)) throw new IllegalArgumentException("Expected BasicDBObject as argument type!");
BasicDBObject in = (BasicDBObject)inObject;
JsonObject jsonObject = new JsonObject();
for (Entry<String, Object> entry : in.entrySet())
{
String key = entry.getKey();
Object val = entry.getValue();
if (val instanceof BasicDBList)
{
jsonObject.add(key, mongo2GsonArray((BasicDBList)val));
}
else if (val instanceof BasicDBObject)
{
jsonObject.add(key, mongo2GsonObject((BasicDBObject)val));
}
else
{
jsonObject.add(key, mongo2GsonPrimitive(val));
}
}
return jsonObject;
}
public static JsonArray mongo2GsonArray(DBObject inObject)
{
if (!(inObject instanceof BasicDBList)) throw new IllegalArgumentException("Expected BasicDBList as argument type!");
BasicDBList in = (BasicDBList)inObject;
JsonArray jsonArray = new JsonArray();
for (int i = 0; i < in.size(); i++)
{
Object object = in.get(i);
if (object instanceof BasicDBList)
{
jsonArray.add(mongo2GsonArray((BasicDBList) object));
}
else if (object instanceof BasicDBObject)
{
jsonArray.add(mongo2GsonObject((BasicDBObject) object));
}
else
{
jsonArray.add(mongo2GsonPrimitive(object));
}
}
return jsonArray;
}
public static JsonElement mongo2GsonPrimitive(Object inObject)
{
if (inObject == null) return JsonNull.INSTANCE;
if (inObject instanceof Boolean) return new JsonPrimitive((Boolean) inObject);
if (inObject instanceof Number) return new JsonPrimitive((Number) inObject);
if (inObject instanceof String) return new JsonPrimitive((String) inObject);
if (inObject instanceof Character) return new JsonPrimitive((Character) inObject);
if (inObject instanceof ObjectId) return new JsonPrimitive(inObject.toString());
throw new IllegalArgumentException("Unsupported value type for: " + inObject);
}
}

View File

@ -0,0 +1,13 @@
package com.massivecraft.mcore4.store.storeadapter;
import com.massivecraft.mcore4.store.Coll;
public interface StoreAdapter
{
// A store adapter is supposed to be used with a certain driver.
// This method returns the name of that driver.
public String forDriverName();
public Object read(Coll<?, ?> coll, Object entity);
public void write(Coll<?, ?> coll, Object raw, Object entity); // (This is an opaque/complete write)
}

View File

@ -0,0 +1,12 @@
package com.massivecraft.mcore4.store.storeadapter;
public abstract class StoreAdapterAbstract implements StoreAdapter
{
protected String forDriverName;
@Override public String forDriverName() { return this.forDriverName; }
public StoreAdapterAbstract(String forDriverName)
{
this.forDriverName = forDriverName;
}
}

View File

@ -0,0 +1,36 @@
package com.massivecraft.mcore4.store.storeadapter;
import com.massivecraft.mcore4.lib.gson.JsonElement;
import com.massivecraft.mcore4.store.Coll;
import com.massivecraft.mcore4.store.accessor.Accessor;
public class StoreAdapterGson extends StoreAdapterAbstract
{
public StoreAdapterGson()
{
super("gson");
}
@Override
public Object read(Coll<?, ?> coll, Object entity)
{
return coll.mplugin().gson.toJsonTree(entity, coll.entityClass());
}
@Override
public void write(Coll<?, ?> coll, Object raw, Object entity)
{
Object temp = coll.mplugin().gson.fromJson((JsonElement)raw, coll.entityClass());
Accessor.get(coll.entityClass()).copy(temp, entity);
}
// -------------------------------------------- //
// INSTANCE
// -------------------------------------------- //
protected static StoreAdapterGson instance = new StoreAdapterGson();
public static StoreAdapterGson get()
{
return instance;
}
}

View File

@ -0,0 +1,35 @@
package com.massivecraft.mcore4.store.storeadapter;
import com.massivecraft.mcore4.lib.gson.JsonElement;
import com.massivecraft.mcore4.lib.mongodb.DBObject;
import com.massivecraft.mcore4.store.Coll;
public class StoreAdapterMongo extends StoreAdapterAbstract
{
public StoreAdapterMongo()
{
super("mongo");
}
@Override
public Object read(Coll<?, ?> coll, Object entity)
{
return MongoGsonConverter.gson2MongoObject((JsonElement)StoreAdapterGson.get().read(coll, entity));
}
@Override
public void write(Coll<?, ?> coll, Object raw, Object entity)
{
StoreAdapterGson.get().write(coll, MongoGsonConverter.mongo2GsonObject((DBObject) raw), entity);
}
// -------------------------------------------- //
// INSTANCE
// -------------------------------------------- //
protected static StoreAdapterMongo instance = new StoreAdapterMongo();
public static StoreAdapterMongo get()
{
return instance;
}
}