Package com.massivecraft.mcore.store

Source Code of com.massivecraft.mcore.store.Coll

package com.massivecraft.mcore.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.TreeMap;
import java.util.concurrent.ConcurrentSkipListMap;
import java.util.concurrent.ConcurrentSkipListSet;

import org.bukkit.plugin.Plugin;

import com.massivecraft.mcore.MCore;
import com.massivecraft.mcore.MPlugin;
import com.massivecraft.mcore.NaturalOrderComparator;
import com.massivecraft.mcore.Predictate;
import com.massivecraft.mcore.store.accessor.Accessor;
import com.massivecraft.mcore.xlib.gson.Gson;
import com.massivecraft.mcore.xlib.gson.JsonElement;

public class Coll<E> implements CollInterface<E>
{
  // -------------------------------------------- //
  // GLOBAL REGISTRY
  // -------------------------------------------- //
 
  public final static String TOTAL = "*total*";
 
  // All instances registered here are considered inited.
  private static Map<String, Coll<?>> name2instance = new ConcurrentSkipListMap<String, Coll<?>>(NaturalOrderComparator.get());
 
  private static Map<String, Coll<?>> umap = Collections.unmodifiableMap(name2instance);
  private static Set<String> unames = Collections.unmodifiableSet(name2instance.keySet());
  private static Collection<Coll<?>> uinstances = Collections.unmodifiableCollection(name2instance.values());
 
  public static Map<String, Coll<?>> getMap() { return umap; }
  public static Set<String> getNames() { return unames; }
  public static Collection<Coll<?>> getInstances() { return uinstances; }
 
  // -------------------------------------------- //
  // WHAT DO WE HANDLE?
  // -------------------------------------------- //
 
  protected final String name;
  @Override public String getName() { return this.name; }
 
  protected final String basename;
  @Override public String getBasename() { return this.basename; }
 
  protected final String universe;
  @Override public String getUniverse() { return this.universe; }
 
  protected final Class<E> entityClass;
  @Override public Class<E> getEntityClass() { return this.entityClass; }
 
  // -------------------------------------------- //
  // SUPPORTING SYSTEM
  // -------------------------------------------- //
 
  protected Plugin plugin;
  @Override public Plugin getPlugin() { return this.plugin; }
  public Gson getGson()
  {
    if (this.getPlugin() instanceof MPlugin)
    {
      return ((MPlugin)this.getPlugin()).gson;
    }
    else
    {
      return MCore.gson;
    }
  }
 
  protected Db db;
  @Override public Db getDb() { return this.db; }
  @Override public Driver getDriver() { return this.db.getDriver(); }
 
  protected Object collDriverObject;
  @Override public Object getCollDriverObject() { return this.collDriverObject; }
 
  // -------------------------------------------- //
  // STORAGE
  // -------------------------------------------- //
 
  protected Map<String, E> id2entity;
  @Override public Map<String, E> getId2entity() { return Collections.unmodifiableMap(this.id2entity); }
  @Override
  public E get(Object oid)
  {
    return this.get(oid, this.isCreative());
  }
  @Override
  public E get(Object oid, boolean creative)
  {
    return this.get(oid, creative, true);
  }
  protected E get(Object oid, boolean creative, boolean noteChange)
  {
    String id = this.fixId(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 Collection<String> getIds() { return Collections.unmodifiableCollection(this.id2entity.keySet()); }
  @Override public Collection<String> getIdsRemote() { return this.getDb().getDriver().getIds(this); }
  @Override
  public boolean containsId(Object oid)
  {
    String id = this.fixId(oid);
    if (id == null) return false;
    return this.id2entity.containsKey(id);
  }
 
  // Get the id for this entity.
  protected Map<E, String> entity2id;
  @Override public Map<E, String> getEntity2id() { return Collections.unmodifiableMap(this.entity2id); }
  @Override public String getId(Object entity) { return this.entity2id.get(entity); }
  @Override public boolean containsEntity(Object entity) { return this.entity2id.containsKey(entity); };
 
  @Override public Collection<E> getAll() { return Collections.unmodifiableCollection(this.entity2id.keySet()); }
  @Override public Collection<E> getAll(Predictate<? super E> where) { return MStoreUtil.uglySQL(this.getAll(), where, null, null, null); }
  @Override public Collection<E> getAll(Predictate<? super E> where, Comparator<? super E> orderby) { return MStoreUtil.uglySQL(this.getAll(), where, orderby, null, null); }
  @Override public Collection<E> getAll(Predictate<? super E> where, Comparator<? super E> orderby, Integer limit) { return MStoreUtil.uglySQL(this.getAll(), where, orderby, limit, null); }
  @Override public Collection<E> getAll(Predictate<? super E> where, Comparator<? super E> orderby, Integer limit, Integer offset) { return MStoreUtil.uglySQL(this.getAll(), where, orderby, limit, offset); }
 
  @Override
  public String fixId(Object oid)
  {
    if (oid == null) return null;
   
    String ret = null;
    if (oid instanceof String)
    {
      ret = (String)oid;
    }
    else if (oid.getClass() == this.entityClass)
    {
      ret = this.entity2id.get(oid);
    }
    if (ret == null) return null;
   
    return this.isLowercasing() ? ret.toLowerCase() : ret;
  }
 
  // -------------------------------------------- //
  // BEHAVIOR
  // -------------------------------------------- //

  protected boolean creative;
  @Override public boolean isCreative() { return this.creative; }
  @Override public void setCreative(boolean creative) { this.creative = creative; }
 
  // "Lowercasing" means that the ids are always converted to lower case when fixed.
  // This is highly recommended for sender colls.
  // The senderIds are case insensitive by nature and some times you simply can't know the correct casing.
  protected boolean lowercasing;
  @Override public boolean isLowercasing() { return this.lowercasing; }
  @Override public void setLowercasing(boolean lowercasing) { this.lowercasing = lowercasing; }
 
  // Should that instance be saved or not?
  // If it is default it should not be saved.
  @SuppressWarnings("rawtypes")
  @Override public boolean isDefault(E entity)
  {
    if (entity instanceof Entity)
    {
      return ((Entity)entity).isDefault();
    }
    else
    {
      return false;
    }
  }
 
  // -------------------------------------------- //
  // COPY AND CREATE
  // -------------------------------------------- //
 
  @SuppressWarnings({ "rawtypes", "unchecked" })
  public void copy(Object ofrom, Object oto)
  {
    if (ofrom == null) throw new NullPointerException("ofrom");
    if (oto == null) throw new NullPointerException("oto");
     
    if (ofrom instanceof Entity)
    {
      Entity efrom = (Entity)ofrom;
      Entity eto = (Entity)oto;
     
      eto.load(efrom);
    }
    else
    {
      Accessor.get(this.getEntityClass()).copy(ofrom, oto);
    }
  }
 
  // 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 String attach(E entity)
  {
    return this.attach(entity, null);
  }
 
  @Override
  public synchronized String attach(E entity, Object oid)
  {
    return this.attach(entity, oid, true);
  }

  @SuppressWarnings({ "unchecked", "rawtypes" })
  protected synchronized String attach(E entity, Object oid, boolean noteChange)
  {
    // Check entity
    if (entity == null) return null;
    String id = this.getId(entity);
    if (id != null) return id;
   
    // Check/Fix id
    if (oid == null)
    {
      id = MStore.createId();
    }
    else
    {
      id = this.fixId(oid);
      if (id == null) return null;
      if (this.id2entity.containsKey(id)) return null;
    }
   
    // PRE
    this.preAttach(entity, id);
   
    // Add entity reference info
    if (entity instanceof Entity)
    {
      ((Entity)entity).setColl(this);
      ((Entity)entity).setid(id);
    }
   
    // Attach
    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);
    }
   
    // POST
    this.postAttach(entity, id);
   
    return id;
  }
   
  @SuppressWarnings("unchecked")
  @Override
  public E detachEntity(Object entity)
  {
    E e = (E)entity;
    String id = this.getId(e);
    this.detach(e, id);
    return e;
  }
 
  @Override
  public E detachId(Object oid)
  {
    String id = this.fixId(oid);
    E e = this.get(id);
    this.detach(e, id);
    return e;
  }
 
  private void detach(E entity, String id)
  {
    // PRE
    this.preDetach(entity, id);
   
    // Remove @ local
    this.removeAtLocal(id);
   
    // Identify the change
    this.localDetachIds.add(id);
    this.changedIds.add(id);
   
    // POST
    this.postDetach(entity, id);
  }
 
  @Override
  public void preAttach(E entity, String id)
  {
    if (entity instanceof Entity)
    {
      ((Entity<?>)entity).preAttach(id);
    }
  }
 
  @Override
  public void postAttach(E entity, String id)
  {
    if (entity instanceof Entity)
    {
      ((Entity<?>)entity).postAttach(id);
    }
  }
 
  @Override
  public void preDetach(E entity, String id)
  {
    if (entity instanceof Entity)
    {
      ((Entity<?>)entity).preDetach(id);
    }
  }
 
  @Override
  public void postDetach(E entity, String id)
  {
    if (entity instanceof Entity)
    {
      ((Entity<?>)entity).postDetach(id);
    }
  }
 
  // -------------------------------------------- //
  // IDENTIFIED CHANGES
  // -------------------------------------------- //
 
  protected Set<String> localAttachIds;
  protected Set<String> localDetachIds;
  protected Set<String> changedIds;
 
  protected synchronized void clearIdentifiedChanges(Object oid)
  {
    String id = this.fixId(oid);
    this.localAttachIds.remove(id);
    this.localDetachIds.remove(id);
    this.changedIds.remove(id);
  }
 
  // -------------------------------------------- //
  // SYNCLOG
  // -------------------------------------------- //

  protected Map<String, Long> lastMtime;
  protected Map<String, JsonElement> lastRaw;
  protected Set<String> lastDefault;
 
  protected synchronized void clearSynclog(Object oid)
  {
    String id = this.fixId(oid);
    this.lastMtime.remove(id);
    this.lastRaw.remove(id);
    this.lastDefault.remove(id);
  }
 
  // Log database syncronization for display in the "/mcore mstore stats" command.
  private Map<String, Long> id2out = new TreeMap<String, Long>();
  private Map<String, Long> id2in = new TreeMap<String, Long>();
 
  public Map<String, Long> getSyncMap(boolean in)
  {
    return in ? this.id2in : this.id2out;
  }
 
  public long getSyncCount(String id, boolean in)
  {
    Long count = this.getSyncMap(in).get(id);
    if (count == null) return 0;
    return count;
  }
 
  public void addSyncCount(String id, boolean in)
  {
    long count = this.getSyncCount(id, in);
    count++;
    this.getSyncMap(in).put(id, count);
  }
 
  // -------------------------------------------- //
  // SYNC LOWLEVEL IO ACTIONS
  // -------------------------------------------- //
 
  @SuppressWarnings({ "unchecked", "rawtypes" })
  @Override
  public synchronized E removeAtLocal(Object oid)
  {
    String id = this.fixId(oid);
    this.clearIdentifiedChanges(id);
    this.clearSynclog(id);
   
    E entity = this.id2entity.remove(id);
    if (entity == null) return null;
   
    this.entity2id.remove(entity);
   
    // Remove entity reference info
    if (entity instanceof Entity)
    {
      ((Entity)entity).setColl(null);
      ((Entity)entity).setid(null);
    }
   
    return entity;
  }
 
  @Override
  public synchronized void removeAtRemote(Object oid)
  {
    String id = this.fixId(oid);
   
    this.clearIdentifiedChanges(id);
    this.clearSynclog(id);
   
    this.getDb().getDriver().delete(this, id);
  }
 
  @Override
  public synchronized void saveToRemote(Object oid)
  {
    String id = this.fixId(oid);
   
    this.clearIdentifiedChanges(id);
    this.clearSynclog(id);
   
    E entity = this.id2entity.get(id);
    if (entity == null) return;
   
    JsonElement raw = this.getGson().toJsonTree(entity, this.getEntityClass());
    this.lastRaw.put(id, raw);
   
    if (this.isDefault(entity))
    {
      this.db.getDriver().delete(this, id);
      this.lastDefault.add(id);
    }
    else
    {
      Long mtime = this.db.getDriver().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(Object oid)
  {
    String id = this.fixId(oid);
   
    this.clearIdentifiedChanges(id);
   
    Entry<JsonElement, Long> entry = this.getDriver().load(this, id);
    if (entry == null) return;
   
    JsonElement raw = entry.getKey();
    if (raw == null) return;
   
    Long mtime = entry.getValue();
    if (mtime == null) return;
   
    E entity = this.get(id, false);
    if (entity != null)
    {
      // It did already exist
      this.copy(this.getGson().fromJson(raw, this.getEntityClass()), entity);
    }
    else
    {
      // Create first
      entity = this.createNewInstance();
     
      // Copy over data first
      this.copy(this.getGson().fromJson(raw, this.getEntityClass()), entity);
     
      // Then attach!
      this.attach(entity, oid, false);
    }
   
    this.lastRaw.put(id, raw);   
    this.lastMtime.put(id, mtime);
    this.lastDefault.remove(id);
  }
 
  // -------------------------------------------- //
  // SYNC DECIDE AND BASIC DO
  // -------------------------------------------- //
 
  @Override
  public ModificationState examineId(Object oid)
  {
    String id = this.fixId(oid);
    return this.examineId(id, null, false);
  }
 
  @Override
  public ModificationState examineId(Object oid, Long remoteMtime)
  {
    String id = this.fixId(oid);
    return this.examineId(id, remoteMtime, true);
  }
 
  protected ModificationState examineId(Object oid, Long remoteMtime, boolean remoteMtimeSupplied)
  {
    String id = this.fixId(oid);
   
    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.getDriver().getMtime(this, id);
    }
   
    boolean existsLocal = (localEntity != null);
    boolean existsRemote = (remoteMtime != null);
   
    if ( ! existsLocal && ! existsRemote) return ModificationState.UNKNOWN;
   
    if (existsLocal && existsRemote)
    {
      Long lastMtime = this.lastMtime.get(id);
      if (remoteMtime.equals(lastMtime) == false) return ModificationState.REMOTE_ALTER;
     
      if (this.examineHasLocalAlter(id, localEntity)) return ModificationState.LOCAL_ALTER;
    }
    else if (existsLocal)
    {
      if (this.lastDefault.contains(id))
      {
        if (this.examineHasLocalAlter(id, localEntity)) return ModificationState.LOCAL_ALTER;
      }
      else
      {
        return ModificationState.REMOTE_DETACH;
      }
    }
    else if (existsRemote)
    {
      return ModificationState.REMOTE_ATTACH;
    }
   
    return ModificationState.NONE;
  }
 
  protected boolean examineHasLocalAlter(String id, E entity)
  {
    JsonElement lastRaw = this.lastRaw.get(id);
    JsonElement currentRaw = this.getGson().toJsonTree(entity, this.getEntityClass());
   
    return !MStore.equal(lastRaw, currentRaw);
  }
 
  @Override
  public ModificationState syncId(Object oid)
  {
    String id = this.fixId(oid);
   
    ModificationState mstate = this.examineId(id);
   
    //mplugin.log("syncId: It seems", id, "has state", mstate);
   
    switch (mstate)
    {
      case LOCAL_ALTER:
      case LOCAL_ATTACH:
        this.saveToRemote(id);
        if (this.inited())
        {
          this.addSyncCount(TOTAL, false);
          this.addSyncCount(id, false);
        }
      break;
      case LOCAL_DETACH:
        this.removeAtRemote(id);
        if (this.inited())
        {
          this.addSyncCount(TOTAL, false);
          this.addSyncCount(id, false);
        }
      break;
      case REMOTE_ALTER:
      case REMOTE_ATTACH:
        this.loadFromRemote(id);
        if (this.inited())
        {
          this.addSyncCount(TOTAL, true);
          this.addSyncCount(id, true);
        }
      break;
      case REMOTE_DETACH:
        this.removeAtLocal(id);
        if (this.inited())
        {
          this.addSyncCount(TOTAL, true);
          this.addSyncCount(id, true);
        }
      break;
      default:
        this.clearIdentifiedChanges(id);
      break;
    }
   
    return mstate;
  }
 
  @Override
  public void syncSuspects()
  {
    for (String id : this.changedIds)
    {
      this.syncId(id);
    }
  }
 
  @Override
  public void syncAll()
  {
    // Find all ids
    Set<String> allids = new HashSet<String>(this.id2entity.keySet());
    allids.addAll(this.getDriver().getIds(this));
    for (String id : allids)
    {
      this.syncId(id);
    }
  }
 
  @Override
  public void findSuspects()
  {
    // Get remote id and mtime snapshot
    Map<String, Long> id2RemoteMtime = this.getDb().getDriver().getId2mtime(this);
   
    // Compile a list of all ids (both remote and local)
    Set<String> allids = new HashSet<String>();
    allids.addAll(id2RemoteMtime.keySet());
    allids.addAll(this.id2entity.keySet());
   
    // Check for modifications
    for (String id : allids)
    {
      Long remoteMtime = id2RemoteMtime.get(id);
      ModificationState state = this.examineId(id, remoteMtime);
      //mplugin.log("findSuspects: It seems", id, "has state", state);
      if (state.isModified())
      {
        //System.out.println("It seems "+id+" has state "+state);
        this.changedIds.add(id);
      }
    }
  }
 
  // -------------------------------------------- //
  // SYNC RUNNABLES / SCHEDULING
  // -------------------------------------------- //
 
  protected Runnable tickTask;
  @Override public Runnable getTickTask() { return this.tickTask; }
  @Override
  public void onTick()
  {
    this.syncSuspects();
  }
 
  // -------------------------------------------- //
  // CONSTRUCT
  // -------------------------------------------- //
 
  public Coll(String name, Class<E> entityClass, Db db, Plugin plugin, boolean creative, boolean lowercasing, String idStrategyName, Comparator<? super String> idComparator, Comparator<? super E> entityComparator)
  {
    // Setup the name and the parsed parts
    this.name = name;
    String[] nameParts = this.name.split("\\@");
    this.basename = nameParts[0];
    if (nameParts.length > 1)
    {
      this.universe = nameParts[1];
    }
    else
    {
      this.universe = null;
    }
   
    // WHAT DO WE HANDLE?
    this.entityClass = entityClass;
    this.creative = creative;
    this.lowercasing = lowercasing;
   
    // SUPPORTING SYSTEM
    this.plugin = plugin;
    this.db = db;
    this.collDriverObject = db.getCollDriverObject(this);
   
    // STORAGE
    this.id2entity = new ConcurrentSkipListMap<String, E>(idComparator);
    this.entity2id = new ConcurrentSkipListMap<E, String>(entityComparator);
   
    // IDENTIFIED CHANGES
    this.localAttachIds = new ConcurrentSkipListSet<String>(idComparator);
    this.localDetachIds = new ConcurrentSkipListSet<String>(idComparator);
    this.changedIds = new ConcurrentSkipListSet<String>(idComparator);
   
    // SYNCLOG
    this.lastMtime = new ConcurrentSkipListMap<String, Long>(idComparator);
    this.lastRaw = new ConcurrentSkipListMap<String, JsonElement>(idComparator);
    this.lastDefault = new ConcurrentSkipListSet<String>(idComparator);
   
    final Coll<E> me = this;
    this.tickTask = new Runnable()
    {
      @Override public void run() { me.onTick(); }
    };
  }
 
  public Coll(String name, Class<E> entityClass, Db db, Plugin plugin, boolean creative, boolean lowercasing)
  {
    this(name, entityClass, db, plugin, creative, lowercasing, "uuid", null, null);
  }
 
  public Coll(String name, Class<E> entityClass, Db db, Plugin plugin)
  {
    this(name, entityClass, db, plugin, false, false);
  }
 
  @Override
  public void init()
  {
    if (this.inited()) return;
   
    // TODO: Could this be more efficient by considering it's the first sync?
    this.syncAll();
   
    name2instance.put(this.getName(), this);
  }
 
  @Override
  public void deinit()
  {
    if (!this.inited()) return;
   
    // TODO: Save outwards only? We may want to avoid loads at this stage...
    this.syncAll();
   
    name2instance.remove(this.getName());
  }
 
  @Override
  public boolean inited()
  {
    return name2instance.containsKey(this.getName());
  }
}
TOP

Related Classes of com.massivecraft.mcore.store.Coll

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.