From 2bca9a6572ecc6034727be6c82baded3ac1e3729 Mon Sep 17 00:00:00 2001 From: Olof Larsson Date: Fri, 31 Aug 2012 11:07:13 +0200 Subject: [PATCH] Adding in the development version of the new data store system. --- src/com/massivecraft/mcore4/store/Coll.java | 530 ++++++++++++++++++ .../mcore4/store/CollInterface.java | 137 +++++ src/com/massivecraft/mcore4/store/Db.java | 16 + .../massivecraft/mcore4/store/DbAbstract.java | 12 + src/com/massivecraft/mcore4/store/DbGson.java | 57 ++ .../massivecraft/mcore4/store/DbMongo.java | 56 ++ src/com/massivecraft/mcore4/store/Driver.java | 55 ++ .../mcore4/store/DriverAbstract.java | 34 ++ .../massivecraft/mcore4/store/DriverGson.java | 196 +++++++ .../mcore4/store/DriverMongo.java | 228 ++++++++ src/com/massivecraft/mcore4/store/Entity.java | 74 +++ .../mcore4/store/ExamineThread.java | 43 ++ .../mcore4/store/JsonFileFilter.java | 25 + src/com/massivecraft/mcore4/store/MStore.java | 49 ++ .../massivecraft/mcore4/store/MStoreUtil.java | 75 +++ .../mcore4/store/ModificationState.java | 28 + .../massivecraft/mcore4/store/PlayerColl.java | 51 ++ .../mcore4/store/PlayerEntity.java | 79 +++ .../mcore4/store/accessor/Accessor.java | 20 + .../mcore4/store/accessor/AccessorUtil.java | 175 ++++++ .../mcore4/store/accessor/EntityAccessor.java | 6 + .../accessor/EntityAccessorAbstract.java | 51 ++ .../accessor/EntityAccessorPerProperty.java | 85 +++ .../mcore4/store/accessor/EntityGetter.java | 6 + .../mcore4/store/accessor/EntityGlue.java | 17 + .../mcore4/store/accessor/EntitySetter.java | 6 + .../store/accessor/PropertyAccessor.java | 6 + .../accessor/PropertyAccessorComposite.java | 25 + .../mcore4/store/accessor/PropertyGetter.java | 6 + .../PropertyGetterFieldReflection.java | 29 + .../PropertyGetterMethodReflection.java | 29 + .../mcore4/store/accessor/PropertySetter.java | 6 + .../PropertySetterFieldReflection.java | 28 + .../PropertySetterMethodReflection.java | 28 + .../mcore4/store/idstrategy/IdStrategy.java | 29 + .../store/idstrategy/IdStrategyAbstract.java | 41 ++ .../store/idstrategy/IdStrategyAiGson.java | 91 +++ .../store/idstrategy/IdStrategyAiMongo.java | 67 +++ .../store/idstrategy/IdStrategyOidGson.java | 39 ++ .../store/idstrategy/IdStrategyOidMongo.java | 40 ++ .../IdStrategyUuidMongoAndGson.java | 40 ++ .../storeadapter/MongoGsonConverter.java | 142 +++++ .../store/storeadapter/StoreAdapter.java | 13 + .../storeadapter/StoreAdapterAbstract.java | 12 + .../store/storeadapter/StoreAdapterGson.java | 36 ++ .../store/storeadapter/StoreAdapterMongo.java | 35 ++ 46 files changed, 2853 insertions(+) create mode 100644 src/com/massivecraft/mcore4/store/Coll.java create mode 100644 src/com/massivecraft/mcore4/store/CollInterface.java create mode 100644 src/com/massivecraft/mcore4/store/Db.java create mode 100644 src/com/massivecraft/mcore4/store/DbAbstract.java create mode 100644 src/com/massivecraft/mcore4/store/DbGson.java create mode 100644 src/com/massivecraft/mcore4/store/DbMongo.java create mode 100644 src/com/massivecraft/mcore4/store/Driver.java create mode 100644 src/com/massivecraft/mcore4/store/DriverAbstract.java create mode 100644 src/com/massivecraft/mcore4/store/DriverGson.java create mode 100644 src/com/massivecraft/mcore4/store/DriverMongo.java create mode 100644 src/com/massivecraft/mcore4/store/Entity.java create mode 100644 src/com/massivecraft/mcore4/store/ExamineThread.java create mode 100644 src/com/massivecraft/mcore4/store/JsonFileFilter.java create mode 100644 src/com/massivecraft/mcore4/store/MStore.java create mode 100644 src/com/massivecraft/mcore4/store/MStoreUtil.java create mode 100644 src/com/massivecraft/mcore4/store/ModificationState.java create mode 100644 src/com/massivecraft/mcore4/store/PlayerColl.java create mode 100644 src/com/massivecraft/mcore4/store/PlayerEntity.java create mode 100644 src/com/massivecraft/mcore4/store/accessor/Accessor.java create mode 100644 src/com/massivecraft/mcore4/store/accessor/AccessorUtil.java create mode 100644 src/com/massivecraft/mcore4/store/accessor/EntityAccessor.java create mode 100644 src/com/massivecraft/mcore4/store/accessor/EntityAccessorAbstract.java create mode 100644 src/com/massivecraft/mcore4/store/accessor/EntityAccessorPerProperty.java create mode 100644 src/com/massivecraft/mcore4/store/accessor/EntityGetter.java create mode 100644 src/com/massivecraft/mcore4/store/accessor/EntityGlue.java create mode 100644 src/com/massivecraft/mcore4/store/accessor/EntitySetter.java create mode 100644 src/com/massivecraft/mcore4/store/accessor/PropertyAccessor.java create mode 100644 src/com/massivecraft/mcore4/store/accessor/PropertyAccessorComposite.java create mode 100644 src/com/massivecraft/mcore4/store/accessor/PropertyGetter.java create mode 100644 src/com/massivecraft/mcore4/store/accessor/PropertyGetterFieldReflection.java create mode 100644 src/com/massivecraft/mcore4/store/accessor/PropertyGetterMethodReflection.java create mode 100644 src/com/massivecraft/mcore4/store/accessor/PropertySetter.java create mode 100644 src/com/massivecraft/mcore4/store/accessor/PropertySetterFieldReflection.java create mode 100644 src/com/massivecraft/mcore4/store/accessor/PropertySetterMethodReflection.java create mode 100644 src/com/massivecraft/mcore4/store/idstrategy/IdStrategy.java create mode 100644 src/com/massivecraft/mcore4/store/idstrategy/IdStrategyAbstract.java create mode 100644 src/com/massivecraft/mcore4/store/idstrategy/IdStrategyAiGson.java create mode 100644 src/com/massivecraft/mcore4/store/idstrategy/IdStrategyAiMongo.java create mode 100644 src/com/massivecraft/mcore4/store/idstrategy/IdStrategyOidGson.java create mode 100644 src/com/massivecraft/mcore4/store/idstrategy/IdStrategyOidMongo.java create mode 100644 src/com/massivecraft/mcore4/store/idstrategy/IdStrategyUuidMongoAndGson.java create mode 100644 src/com/massivecraft/mcore4/store/storeadapter/MongoGsonConverter.java create mode 100644 src/com/massivecraft/mcore4/store/storeadapter/StoreAdapter.java create mode 100644 src/com/massivecraft/mcore4/store/storeadapter/StoreAdapterAbstract.java create mode 100644 src/com/massivecraft/mcore4/store/storeadapter/StoreAdapterGson.java create mode 100644 src/com/massivecraft/mcore4/store/storeadapter/StoreAdapterMongo.java diff --git a/src/com/massivecraft/mcore4/store/Coll.java b/src/com/massivecraft/mcore4/store/Coll.java new file mode 100644 index 00000000..d63a30f8 --- /dev/null +++ b/src/com/massivecraft/mcore4/store/Coll.java @@ -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 implements CollInterface +{ + // -------------------------------------------- // + // WHAT DO WE HANDLE? + // -------------------------------------------- // + + protected String name; + @Override public String name() { return this.name; } + + protected Class entityClass; + @Override public Class entityClass() { return this.entityClass; } + + protected Class idClass; + @Override public Class 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 idStrategy; + @Override public IdStrategy 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 ids = Collections.newSetFromMap(new ConcurrentHashMap()); + @Override public Collection ids() { return Collections.unmodifiableCollection(this.ids); } + @Override public Collection idsRemote() { return this.db().driver().ids(this); } + + protected Set entities = Collections.newSetFromMap(new ConcurrentHashMap()); + @Override public Collection getAll() { return Collections.unmodifiableCollection(this.entities); } + @Override public Collection getAll(Predictate where) { return MStoreUtil.uglySQL(this.getAll(), where, null, null, null); } + @Override public Collection getAll(Predictate where, Comparator orderby) { return MStoreUtil.uglySQL(this.getAll(), where, orderby, null, null); } + @Override public Collection getAll(Predictate where, Comparator orderby, Integer limit) { return MStoreUtil.uglySQL(this.getAll(), where, orderby, limit, null); } + @Override public Collection getAll(Predictate where, Comparator orderby, Integer limit, Integer offset) { return MStoreUtil.uglySQL(this.getAll(), where, orderby, limit, offset); } + + protected Map id2entity = new ConcurrentHashMap(); + @Override public Map 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 entity2id = new ConcurrentHashMap(); + @Override public Map 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 localAttachIds = Collections.newSetFromMap(new ConcurrentHashMap()); + protected Set localDetachIds = Collections.newSetFromMap(new ConcurrentHashMap()); + protected Set changedIds = Collections.newSetFromMap(new ConcurrentHashMap()); + + protected synchronized void clearIdentifiedChanges(L id) + { + this.localAttachIds.remove(id); + this.localDetachIds.remove(id); + this.changedIds.remove(id); + } + + // -------------------------------------------- // + // SYNCLOG + // -------------------------------------------- // + + protected Map lastMtime = new ConcurrentHashMap(); + protected Map lastRaw = new ConcurrentHashMap(); + protected Set lastDefault = Collections.newSetFromMap(new ConcurrentHashMap()); + + 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 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 allids = new HashSet(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 id2RemoteMtime = this.db().driver().id2mtime(this); + + // Compile a list of all ids (both remote and local) + Set allids = new HashSet(); + 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 examineThread; + @Override public Thread examineThread() { return this.examineThread; } + + // -------------------------------------------- // + // CONSTRUCTORS + // -------------------------------------------- // + + public Coll(MPlugin mplugin, Db db, String idStrategyName, String name, Class entityClass, Class 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 me = this; + this.tickTask = new Runnable() + { + @Override public void run() { me.onTick(); } + }; + + this.examineThread = new ExamineThread(this); + } +} diff --git a/src/com/massivecraft/mcore4/store/CollInterface.java b/src/com/massivecraft/mcore4/store/CollInterface.java new file mode 100644 index 00000000..49536206 --- /dev/null +++ b/src/com/massivecraft/mcore4/store/CollInterface.java @@ -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 +{ + // -------------------------------------------- // + // WHAT DO WE HANDLE? + // -------------------------------------------- // + public String name(); + public Class entityClass(); + public Class idClass(); + + // -------------------------------------------- // + // SUPPORTING SYSTEM + // -------------------------------------------- // + public MPlugin mplugin(); + + public Db db(); + public Driver driver(); + public StoreAdapter storeAdapter(); + public IdStrategy idStrategy(); + public Object collDriverObject(); + + // -------------------------------------------- // + // STORAGE + // -------------------------------------------- // + public Collection ids(); + public Collection idsRemote(); + + public Collection getAll(); + public Collection getAll(Predictate where); + public Collection getAll(Predictate where, Comparator orderby); + public Collection getAll(Predictate where, Comparator orderby, Integer limit); + public Collection getAll(Predictate where, Comparator orderby, Integer limit, Integer offset); + + public Map id2entity(); + public E get(Object oid); + public E get(Object oid, boolean creative); + public E getBestMatch(Object oid); + + public Map 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 localAttachIds(); + public Set localDetachIds(); + public Set changedIds(); + public void clearIdentifiedChanges(L id); + */ + + // -------------------------------------------- // + // SYNC LOG + // -------------------------------------------- // + + /* + public Map lastMtime(); + public Map lastRaw(); + public Set 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(); + + +} \ No newline at end of file diff --git a/src/com/massivecraft/mcore4/store/Db.java b/src/com/massivecraft/mcore4/store/Db.java new file mode 100644 index 00000000..5b3f2c4a --- /dev/null +++ b/src/com/massivecraft/mcore4/store/Db.java @@ -0,0 +1,16 @@ +package com.massivecraft.mcore4.store; + +import java.util.Set; + +public interface Db +{ + public String name(); + + public boolean drop(); + + public Set collnames(); + + public Driver driver(); + + public Object getCollDriverObject(Coll coll); +} diff --git a/src/com/massivecraft/mcore4/store/DbAbstract.java b/src/com/massivecraft/mcore4/store/DbAbstract.java new file mode 100644 index 00000000..bdb3156d --- /dev/null +++ b/src/com/massivecraft/mcore4/store/DbAbstract.java @@ -0,0 +1,12 @@ +package com.massivecraft.mcore4.store; + +import java.util.Set; + +public abstract class DbAbstract implements Db +{ + @Override + public Set collnames() + { + return this.driver().collnames(this); + } +} diff --git a/src/com/massivecraft/mcore4/store/DbGson.java b/src/com/massivecraft/mcore4/store/DbGson.java new file mode 100644 index 00000000..7e7e696e --- /dev/null +++ b/src/com/massivecraft/mcore4/store/DbGson.java @@ -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 +{ + // -------------------------------------------- // + // 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()); + } +} diff --git a/src/com/massivecraft/mcore4/store/DbMongo.java b/src/com/massivecraft/mcore4/store/DbMongo.java new file mode 100644 index 00000000..1472ebc4 --- /dev/null +++ b/src/com/massivecraft/mcore4/store/DbMongo.java @@ -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 +{ + // -------------------------------------------- // + // 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()); + } +} diff --git a/src/com/massivecraft/mcore4/store/Driver.java b/src/com/massivecraft/mcore4/store/Driver.java new file mode 100644 index 00000000..701eee60 --- /dev/null +++ b/src/com/massivecraft/mcore4/store/Driver.java @@ -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 +{ + // 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 getRawdataClass(); + + // This is some sort of database specific id strategy with built in adapter + public boolean registerIdStrategy(IdStrategy idStrategy); + public IdStrategy getIdStrategy(String idStrategyName); + + // Get the default store adapter for the driver. + public StoreAdapter getStoreAdapter(); + + // Get a database instance from the driver + public Db db(String uri); + + // What collections are in the database? + public Set collnames(Db db); + + // Is id X in the collection? + public boolean containsId(Coll coll, L id); + + // When was X last altered? + public Long mtime(Coll coll, L id); + + // What ids are in the collection? + public Collection ids(Coll coll); + + // Return a map of all ids with their corresponding mtimes + public Map id2mtime(Coll coll); + + // Load the raw data for X. The second part of the entry is the remote mtime at the load. + public Entry load(Coll 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 Long save(Coll coll, L id, final Object rawData); + + // Delete X + public void delete(Coll coll, L id); +} diff --git a/src/com/massivecraft/mcore4/store/DriverAbstract.java b/src/com/massivecraft/mcore4/store/DriverAbstract.java new file mode 100644 index 00000000..b3ac3059 --- /dev/null +++ b/src/com/massivecraft/mcore4/store/DriverAbstract.java @@ -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 implements Driver +{ + public DriverAbstract(String name) + { + this.name = name; + } + + protected String name; + @Override public String name() { return this.name; } + + protected Map> idStrategies = new HashMap>(); + @Override + public boolean registerIdStrategy(IdStrategy idStrategy) + { + if (idStrategies.containsKey(idStrategy.getName())) return false; + idStrategies.put(idStrategy.getName(), idStrategy); + return true; + } + + @SuppressWarnings("unchecked") + @Override + public IdStrategy getIdStrategy(String idStrategyName) + { + IdStrategy idStrategy = idStrategies.get(idStrategyName); + return (IdStrategy) idStrategy; + } +} diff --git a/src/com/massivecraft/mcore4/store/DriverGson.java b/src/com/massivecraft/mcore4/store/DriverGson.java new file mode 100644 index 00000000..64fefc79 --- /dev/null +++ b/src/com/massivecraft/mcore4/store/DriverGson.java @@ -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 +{ + protected final static String DOTJSON = ".json"; + + // -------------------------------------------- // + // IMPLEMENTATION + // -------------------------------------------- // + + @Override public Class getRawdataClass() { return JsonElement.class; } + + @Override + public StoreAdapter getStoreAdapter() + { + return StoreAdapterGson.get(); + } + + @Override + public Db db(String uri) + { + // "gson://" is 7 chars + File folder = new File(uri.substring(7)); + folder.mkdirs(); + return new DbGson(this, folder); + } + + @Override + public Set collnames(Db db) + { + Set ret = new LinkedHashSet(); + + for (File f : ((DbGson)db).dir.listFiles()) + { + if ( ! f.isDirectory()) continue; + ret.add(f.getName()); + } + + return ret; + } + + @Override + public boolean containsId(Coll coll, L id) + { + return fileFromId(coll, id).isFile(); + } + + @Override + public Long mtime(Coll coll, L id) + { + File file = fileFromId(coll, id); + if ( ! file.isFile()) return null; + return file.lastModified(); + } + + @Override + public Collection ids(Coll coll) + { + List ret = new ArrayList(); + + // 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 Map id2mtime(Coll coll) + { + Map ret = new HashMap(); + + // 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 Entry load(Coll 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(raw, mtime); + } + + @Override + public Long save(Coll 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 void delete(Coll 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 File fileFromId(Coll 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()); + } + +} diff --git a/src/com/massivecraft/mcore4/store/DriverMongo.java b/src/com/massivecraft/mcore4/store/DriverMongo.java new file mode 100644 index 00000000..6f35725b --- /dev/null +++ b/src/com/massivecraft/mcore4/store/DriverMongo.java @@ -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 +{ + // -------------------------------------------- // + // 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 getRawdataClass() { return BasicDBObject.class; } + + @Override + public StoreAdapter getStoreAdapter() + { + return StoreAdapterMongo.get(); + } + + @Override + public Db db(String uri) + { + DB db = this.getDbInner(uri); + return new DbMongo(this, db); + } + + @Override + public Set collnames(Db db) + { + return ((DbMongo)db).db.getCollectionNames(); + } + + @Override + public boolean containsId(Coll 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 Long mtime(Coll 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 Collection ids(Coll coll) + { + List ret = null; + + DBCollection dbcoll = fixColl(coll); + + DBCursor cursor = dbcoll.find(dboEmpty, dboKeysId); + try + { + ret = new ArrayList(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 Map id2mtime(Coll coll) + { + Map ret = null; + + DBCollection dbcoll = fixColl(coll); + + DBCursor cursor = dbcoll.find(dboEmpty, dboKeysIdandMtime); + try + { + ret = new HashMap(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 Entry load(Coll 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(raw, mtime); + } + + @Override + public Long save(Coll 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 void delete(Coll 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()); + } +} diff --git a/src/com/massivecraft/mcore4/store/Entity.java b/src/com/massivecraft/mcore4/store/Entity.java new file mode 100644 index 00000000..0ccfbb7a --- /dev/null +++ b/src/com/massivecraft/mcore4/store/Entity.java @@ -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, L> +{ + public abstract Coll 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(); + } +} diff --git a/src/com/massivecraft/mcore4/store/ExamineThread.java b/src/com/massivecraft/mcore4/store/ExamineThread.java new file mode 100644 index 00000000..a3049308 --- /dev/null +++ b/src/com/massivecraft/mcore4/store/ExamineThread.java @@ -0,0 +1,43 @@ +package com.massivecraft.mcore4.store; + +public class ExamineThread extends Thread +{ + protected Coll coll; + + public ExamineThread(Coll 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(); + } + } + } +} diff --git a/src/com/massivecraft/mcore4/store/JsonFileFilter.java b/src/com/massivecraft/mcore4/store/JsonFileFilter.java new file mode 100644 index 00000000..9973240c --- /dev/null +++ b/src/com/massivecraft/mcore4/store/JsonFileFilter.java @@ -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; + } +} diff --git a/src/com/massivecraft/mcore4/store/MStore.java b/src/com/massivecraft/mcore4/store/MStore.java new file mode 100644 index 00000000..4a1d8088 --- /dev/null +++ b/src/com/massivecraft/mcore4/store/MStore.java @@ -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> drivers = new HashMap>(); + 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()); + } +} \ No newline at end of file diff --git a/src/com/massivecraft/mcore4/store/MStoreUtil.java b/src/com/massivecraft/mcore4/store/MStoreUtil.java new file mode 100644 index 00000000..5b4da69a --- /dev/null +++ b/src/com/massivecraft/mcore4/store/MStoreUtil.java @@ -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 ArrayList uglySQL(Collection items, Predictate where, Comparator orderby, Integer limit, Integer offset) + { + ArrayList ret = new ArrayList(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(ret.subList(fromIndex, toIndex)); + } +} diff --git a/src/com/massivecraft/mcore4/store/ModificationState.java b/src/com/massivecraft/mcore4/store/ModificationState.java new file mode 100644 index 00000000..ec93510b --- /dev/null +++ b/src/com/massivecraft/mcore4/store/ModificationState.java @@ -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; + } + +} diff --git a/src/com/massivecraft/mcore4/store/PlayerColl.java b/src/com/massivecraft/mcore4/store/PlayerColl.java new file mode 100644 index 00000000..7e469c11 --- /dev/null +++ b/src/com/massivecraft/mcore4/store/PlayerColl.java @@ -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> extends Coll +{ + public PlayerColl(MPlugin mplugin, Db db, String name, Class 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 getAllOnline() + { + // TODO: Reverse the order since we have fewer players online than offline? + return this.getAll(new Predictate() + { + public boolean apply(E entity) + { + return entity.isOnline(); + } + }); + } + + public Collection getAllOffline() + { + return this.getAll(new Predictate() + { + public boolean apply(E entity) + { + return entity.isOffline(); + } + }); + } + +} diff --git a/src/com/massivecraft/mcore4/store/PlayerEntity.java b/src/com/massivecraft/mcore4/store/PlayerEntity.java new file mode 100644 index 00000000..1676d1ff --- /dev/null +++ b/src/com/massivecraft/mcore4/store/PlayerEntity.java @@ -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> extends Entity +{ + 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 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 msgs) + { + Player player = this.getPlayer(); + if (player == null) return; + for(String msg : msgs) + { + player.sendMessage(Txt.parse(msg)); + } + } +} diff --git a/src/com/massivecraft/mcore4/store/accessor/Accessor.java b/src/com/massivecraft/mcore4/store/accessor/Accessor.java new file mode 100644 index 00000000..2d34dcd5 --- /dev/null +++ b/src/com/massivecraft/mcore4/store/accessor/Accessor.java @@ -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, EntityAccessor> class2EntityAccessor = new HashMap, 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; + } +} diff --git a/src/com/massivecraft/mcore4/store/accessor/AccessorUtil.java b/src/com/massivecraft/mcore4/store/accessor/AccessorUtil.java new file mode 100644 index 00000000..0b21bc96 --- /dev/null +++ b/src/com/massivecraft/mcore4/store/accessor/AccessorUtil.java @@ -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 findMethod(Class clazz, String name) + { + List ret = new ArrayList(); + 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 findAllFields(Class clazz) + { + List fields = new ArrayList(); + 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 getFieldMap(Class clazz) + { + Map ret = new HashMap(); + + 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 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); + } + + +} diff --git a/src/com/massivecraft/mcore4/store/accessor/EntityAccessor.java b/src/com/massivecraft/mcore4/store/accessor/EntityAccessor.java new file mode 100644 index 00000000..f88b3b6b --- /dev/null +++ b/src/com/massivecraft/mcore4/store/accessor/EntityAccessor.java @@ -0,0 +1,6 @@ +package com.massivecraft.mcore4.store.accessor; + +public interface EntityAccessor extends EntitySetter, EntityGetter, EntityGlue +{ + +} diff --git a/src/com/massivecraft/mcore4/store/accessor/EntityAccessorAbstract.java b/src/com/massivecraft/mcore4/store/accessor/EntityAccessorAbstract.java new file mode 100644 index 00000000..45cefa72 --- /dev/null +++ b/src/com/massivecraft/mcore4/store/accessor/EntityAccessorAbstract.java @@ -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 properties, boolean transparent) + { + for (String property : properties) + { + this.copy(from, to, property, transparent); + } + } + @Override + public void copy(Object from, Object to, Collection 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); + } +} diff --git a/src/com/massivecraft/mcore4/store/accessor/EntityAccessorPerProperty.java b/src/com/massivecraft/mcore4/store/accessor/EntityAccessorPerProperty.java new file mode 100644 index 00000000..321d99b0 --- /dev/null +++ b/src/com/massivecraft/mcore4/store/accessor/EntityAccessorPerProperty.java @@ -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 propertyAccessors; + public Map 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(); + 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 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 getPropertyNames() + { + return this.propertyAccessors.keySet(); + } +} diff --git a/src/com/massivecraft/mcore4/store/accessor/EntityGetter.java b/src/com/massivecraft/mcore4/store/accessor/EntityGetter.java new file mode 100644 index 00000000..b82568e2 --- /dev/null +++ b/src/com/massivecraft/mcore4/store/accessor/EntityGetter.java @@ -0,0 +1,6 @@ +package com.massivecraft.mcore4.store.accessor; + +public interface EntityGetter +{ + public Object get(Object entity, String property); +} diff --git a/src/com/massivecraft/mcore4/store/accessor/EntityGlue.java b/src/com/massivecraft/mcore4/store/accessor/EntityGlue.java new file mode 100644 index 00000000..27de4e1b --- /dev/null +++ b/src/com/massivecraft/mcore4/store/accessor/EntityGlue.java @@ -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 properties, boolean transparent); + public void copy(Object from, Object to, Collection properties); + + public void copy(Object from, Object to, boolean transparent); + public void copy(Object from, Object to); + + public Collection getPropertyNames(); +} \ No newline at end of file diff --git a/src/com/massivecraft/mcore4/store/accessor/EntitySetter.java b/src/com/massivecraft/mcore4/store/accessor/EntitySetter.java new file mode 100644 index 00000000..16a241f5 --- /dev/null +++ b/src/com/massivecraft/mcore4/store/accessor/EntitySetter.java @@ -0,0 +1,6 @@ +package com.massivecraft.mcore4.store.accessor; + +public interface EntitySetter +{ + public void set(Object entity, String property, Object val); +} diff --git a/src/com/massivecraft/mcore4/store/accessor/PropertyAccessor.java b/src/com/massivecraft/mcore4/store/accessor/PropertyAccessor.java new file mode 100644 index 00000000..6e9d6d54 --- /dev/null +++ b/src/com/massivecraft/mcore4/store/accessor/PropertyAccessor.java @@ -0,0 +1,6 @@ +package com.massivecraft.mcore4.store.accessor; + +public interface PropertyAccessor extends PropertySetter, PropertyGetter +{ + +} diff --git a/src/com/massivecraft/mcore4/store/accessor/PropertyAccessorComposite.java b/src/com/massivecraft/mcore4/store/accessor/PropertyAccessorComposite.java new file mode 100644 index 00000000..4ecf1423 --- /dev/null +++ b/src/com/massivecraft/mcore4/store/accessor/PropertyAccessorComposite.java @@ -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); + } +} diff --git a/src/com/massivecraft/mcore4/store/accessor/PropertyGetter.java b/src/com/massivecraft/mcore4/store/accessor/PropertyGetter.java new file mode 100644 index 00000000..3fd8a147 --- /dev/null +++ b/src/com/massivecraft/mcore4/store/accessor/PropertyGetter.java @@ -0,0 +1,6 @@ +package com.massivecraft.mcore4.store.accessor; + +public interface PropertyGetter +{ + public Object get(Object entity); +} diff --git a/src/com/massivecraft/mcore4/store/accessor/PropertyGetterFieldReflection.java b/src/com/massivecraft/mcore4/store/accessor/PropertyGetterFieldReflection.java new file mode 100644 index 00000000..6439ce99 --- /dev/null +++ b/src/com/massivecraft/mcore4/store/accessor/PropertyGetterFieldReflection.java @@ -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; + } + } + +} diff --git a/src/com/massivecraft/mcore4/store/accessor/PropertyGetterMethodReflection.java b/src/com/massivecraft/mcore4/store/accessor/PropertyGetterMethodReflection.java new file mode 100644 index 00000000..17755979 --- /dev/null +++ b/src/com/massivecraft/mcore4/store/accessor/PropertyGetterMethodReflection.java @@ -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; + } + } + +} diff --git a/src/com/massivecraft/mcore4/store/accessor/PropertySetter.java b/src/com/massivecraft/mcore4/store/accessor/PropertySetter.java new file mode 100644 index 00000000..3e9d5b44 --- /dev/null +++ b/src/com/massivecraft/mcore4/store/accessor/PropertySetter.java @@ -0,0 +1,6 @@ +package com.massivecraft.mcore4.store.accessor; + +public interface PropertySetter +{ + public void set(Object entity, Object val); +} diff --git a/src/com/massivecraft/mcore4/store/accessor/PropertySetterFieldReflection.java b/src/com/massivecraft/mcore4/store/accessor/PropertySetterFieldReflection.java new file mode 100644 index 00000000..b2230c1c --- /dev/null +++ b/src/com/massivecraft/mcore4/store/accessor/PropertySetterFieldReflection.java @@ -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(); + } + } + +} diff --git a/src/com/massivecraft/mcore4/store/accessor/PropertySetterMethodReflection.java b/src/com/massivecraft/mcore4/store/accessor/PropertySetterMethodReflection.java new file mode 100644 index 00000000..d69f4c68 --- /dev/null +++ b/src/com/massivecraft/mcore4/store/accessor/PropertySetterMethodReflection.java @@ -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(); + } + } + +} diff --git a/src/com/massivecraft/mcore4/store/idstrategy/IdStrategy.java b/src/com/massivecraft/mcore4/store/idstrategy/IdStrategy.java new file mode 100644 index 00000000..53bffc38 --- /dev/null +++ b/src/com/massivecraft/mcore4/store/idstrategy/IdStrategy.java @@ -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 +{ + // The name of the strategy (such as "auto_increment") + public String getName(); + + // The id classes + public Class getLocalClass(); + public Class getRemoteClass(); + + // Convert + public R localToRemote(Object local); + public L remoteToLocal(Object remote); + + // Generate + public L generate(CollInterface coll); +} diff --git a/src/com/massivecraft/mcore4/store/idstrategy/IdStrategyAbstract.java b/src/com/massivecraft/mcore4/store/idstrategy/IdStrategyAbstract.java new file mode 100644 index 00000000..ec536346 --- /dev/null +++ b/src/com/massivecraft/mcore4/store/idstrategy/IdStrategyAbstract.java @@ -0,0 +1,41 @@ +package com.massivecraft.mcore4.store.idstrategy; + +import java.util.Collection; + +import com.massivecraft.mcore4.store.CollInterface; + +public abstract class IdStrategyAbstract implements IdStrategy +{ + public IdStrategyAbstract(String name, Class localClass, Class remoteClass) + { + this.name = name; + this.localClass = localClass; + this.remoteClass = remoteClass; + } + + protected String name; + @Override public String getName() { return this.name; } + + protected Class localClass; + @Override public Class getLocalClass() { return this.localClass; } + + protected Class remoteClass; + @Override public Class getRemoteClass() { return this.remoteClass; } + + + @Override + public L generate(CollInterface coll) + { + Collection 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 coll); +} diff --git a/src/com/massivecraft/mcore4/store/idstrategy/IdStrategyAiGson.java b/src/com/massivecraft/mcore4/store/idstrategy/IdStrategyAiGson.java new file mode 100644 index 00000000..013521dd --- /dev/null +++ b/src/com/massivecraft/mcore4/store/idstrategy/IdStrategyAiGson.java @@ -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 +{ + + //----------------------------------------------// + // 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 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 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; + } +} diff --git a/src/com/massivecraft/mcore4/store/idstrategy/IdStrategyAiMongo.java b/src/com/massivecraft/mcore4/store/idstrategy/IdStrategyAiMongo.java new file mode 100644 index 00000000..dd5514da --- /dev/null +++ b/src/com/massivecraft/mcore4/store/idstrategy/IdStrategyAiMongo.java @@ -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 +{ + 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 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; + } +} diff --git a/src/com/massivecraft/mcore4/store/idstrategy/IdStrategyOidGson.java b/src/com/massivecraft/mcore4/store/idstrategy/IdStrategyOidGson.java new file mode 100644 index 00000000..22d9d63b --- /dev/null +++ b/src/com/massivecraft/mcore4/store/idstrategy/IdStrategyOidGson.java @@ -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 +{ + // -------------------------------------------- // + // 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 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; + } +} diff --git a/src/com/massivecraft/mcore4/store/idstrategy/IdStrategyOidMongo.java b/src/com/massivecraft/mcore4/store/idstrategy/IdStrategyOidMongo.java new file mode 100644 index 00000000..fa7854f0 --- /dev/null +++ b/src/com/massivecraft/mcore4/store/idstrategy/IdStrategyOidMongo.java @@ -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 +{ + // -------------------------------------------- // + // IMPLEMENTATION + // -------------------------------------------- // + + @Override public ObjectId localToRemote(Object local) { return (ObjectId)local; } + @Override public ObjectId remoteToLocal(Object remote) { return (ObjectId)remote; } + + @Override + public ObjectId generateAttempt(CollInterface 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; + } + +} diff --git a/src/com/massivecraft/mcore4/store/idstrategy/IdStrategyUuidMongoAndGson.java b/src/com/massivecraft/mcore4/store/idstrategy/IdStrategyUuidMongoAndGson.java new file mode 100644 index 00000000..c791e3d0 --- /dev/null +++ b/src/com/massivecraft/mcore4/store/idstrategy/IdStrategyUuidMongoAndGson.java @@ -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 +{ + // -------------------------------------------- // + // 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 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; + } +} diff --git a/src/com/massivecraft/mcore4/store/storeadapter/MongoGsonConverter.java b/src/com/massivecraft/mcore4/store/storeadapter/MongoGsonConverter.java new file mode 100644 index 00000000..a0a898d6 --- /dev/null +++ b/src/com/massivecraft/mcore4/store/storeadapter/MongoGsonConverter.java @@ -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 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 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); + } +} diff --git a/src/com/massivecraft/mcore4/store/storeadapter/StoreAdapter.java b/src/com/massivecraft/mcore4/store/storeadapter/StoreAdapter.java new file mode 100644 index 00000000..f5f7c495 --- /dev/null +++ b/src/com/massivecraft/mcore4/store/storeadapter/StoreAdapter.java @@ -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) +} diff --git a/src/com/massivecraft/mcore4/store/storeadapter/StoreAdapterAbstract.java b/src/com/massivecraft/mcore4/store/storeadapter/StoreAdapterAbstract.java new file mode 100644 index 00000000..d592814f --- /dev/null +++ b/src/com/massivecraft/mcore4/store/storeadapter/StoreAdapterAbstract.java @@ -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; + } +} diff --git a/src/com/massivecraft/mcore4/store/storeadapter/StoreAdapterGson.java b/src/com/massivecraft/mcore4/store/storeadapter/StoreAdapterGson.java new file mode 100644 index 00000000..6ac46d6b --- /dev/null +++ b/src/com/massivecraft/mcore4/store/storeadapter/StoreAdapterGson.java @@ -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; + } +} diff --git a/src/com/massivecraft/mcore4/store/storeadapter/StoreAdapterMongo.java b/src/com/massivecraft/mcore4/store/storeadapter/StoreAdapterMongo.java new file mode 100644 index 00000000..069816b1 --- /dev/null +++ b/src/com/massivecraft/mcore4/store/storeadapter/StoreAdapterMongo.java @@ -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; + } +}