net.tbnr.gearz.activerecord.GModel.java Source code

Java tutorial

Introduction

Here is the source code for net.tbnr.gearz.activerecord.GModel.java

Source

/*
 * Copyright (c) 2014.
 * CogzMC LLC USA
 * All Right reserved
 *
 * This software is the confidential and proprietary information of Cogz Development, LLC.
 * ("Confidential Information").
 * You shall not disclose such Confidential Information and shall use it only in accordance
 * with the terms of the license agreement you entered into with Cogz LLC.
 */

package net.tbnr.gearz.activerecord;

import com.mongodb.*;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import org.bson.types.ObjectId;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.*;

/**
 * Models you create should extend this class
 * All fields annotated with {@link BasicField}
 *
 * @author Joey Sacchini
 * @version 1.0
 */
@SuppressWarnings({ "UnusedDeclaration", "unchecked" })
@EqualsAndHashCode(of = { "objectId" }, doNotUseGetters = true)
@ToString(includeFieldNames = false)
public abstract class GModel {
    /**
     * This is the database that the GearzModel will operate in
     */
    @Getter
    @Setter
    private static DB defaultDatabase;

    /**
     * The ID of this object. Is null by default
     */
    @Setter
    @Getter
    private ObjectId objectId;

    /**
     * Database
     */
    private DB database;

    /**
     * The collection where this data will be saved
     */
    private DBCollection collection;

    /**
     * Used for building an object.
     */
    private BasicDBObjectBuilder basicDBObjectBuilder;

    /**
     * Creates a {@link GModel} with default values
     */
    public GModel() {
        this.database = GModel.defaultDatabase;
        this.objectId = null;
        setupAllEmptys();
        loadCollection();
    }

    /**
     * new GModel with overridden database
     *
     * @param database database
     */
    public GModel(DB database) {
        this.objectId = null;
        this.database = database;
        setupAllEmptys();
        loadCollection();
    }

    /**
     * Loads a GModel from an existing {@link DBObject} in a Database
     *
     * @param database The database to reference for linked objects.
     * @param dBobject The {@link DBObject} to load data from.
     */
    public GModel(DB database, DBObject dBobject) {
        this.database = database;
        this.objectId = (ObjectId) dBobject.get("_id");
        loadCollection();
        List<BasicAnalyzedField> allFields = getAllFields();
        for (BasicAnalyzedField analyzedField : allFields) {
            if (!dBobject.containsField(analyzedField.getKey())) {
                setupEmptyField(analyzedField.getField());
            }
            Object o = dBobject.get(analyzedField.getKey());
            o = readObjectFromDB(o);
            try {
                analyzedField.getField().set(this, o);
            } catch (IllegalAccessException e) {
                e.printStackTrace(); //TODO remove this
            }
        }
    }

    private void setupEmptyField(Field f) {
        //Type type = f.getGenericType(); never used
        Object setTo = null;
        if (List.class.isAssignableFrom(f.getType())) {
            setTo = new ArrayList<>();
        }
        if (Map.class.isAssignableFrom(f.getType())) {
            setTo = new HashMap<>();
        }
        if (setTo == null)
            return;
        try {
            f.set(this, setTo);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }

    private void setupAllEmptys() {
        for (Field f : this.getClass().getDeclaredFields()) {
            f.setAccessible(true);
            if (!f.isAnnotationPresent(BasicField.class))
                continue;
            try {
                if (f.get(this) != null)
                    continue;
            } catch (IllegalAccessException e) {
                e.printStackTrace(); //TODO remove
                continue;
            }
            setupEmptyField(f);
        }
    }

    /**
     * Updates the internal variables to be consistent.
     */
    private void updateObjects() {
        this.basicDBObjectBuilder = new BasicDBObjectBuilder();
        for (BasicAnalyzedField analyzedField : getAllFields()) {
            if (!isValidValue(analyzedField.getValue()))
                continue;
            this.basicDBObjectBuilder.append(analyzedField.getKey(), analyzedField.getValue());
        }
        if (this.objectId != null) {
            this.basicDBObjectBuilder.append("_id", this.objectId);
        }
        this.basicDBObjectBuilder.append("_class", this.getClass().getName());
        this.basicDBObjectBuilder.append("_schema_v", "1.0");
    }

    /**
     * Saves the data to the database. Possibly updates it.
     */
    public void save() {
        DBObject objectValue = this.getObjectValue();
        this.collection.save(objectValue);
        this.objectId = (ObjectId) objectValue.get("_id");
    }

    /**
     * Process each field
     *
     * @return All processed fields.
     */
    private List<BasicAnalyzedField> getAllFields() {
        ArrayList<BasicAnalyzedField> fields = new ArrayList<>();
        for (Field f : this.getClass().getDeclaredFields()) {
            if (!f.isAnnotationPresent(BasicField.class))
                continue;
            BasicField annotation = f.getAnnotation(BasicField.class);
            BasicAnalyzedField analyzedField = new BasicAnalyzedField(
                    (annotation.key().equals("") ? f.getName().toLowerCase() : annotation.key()), f);
            if (analyzedField.getKey().equals("_id") || analyzedField.getKey().equals("_link_flag")
                    || analyzedField.getKey().equals("_class") || analyzedField.getKey().equals("_schema_v"))
                continue;
            f.setAccessible(true);
            Object o;
            try {
                o = f.get(this);
            } catch (IllegalAccessException e) {
                e.printStackTrace(); //TODO remove this
                return null;
            }
            o = processField(o, f, analyzedField.getKey());
            analyzedField.setValue(o);
            fields.add(analyzedField);
        }
        return fields;
    }

    /**
     * Will turn an object within the code into something for the database (that can be read by method below)
     *
     * @param f     The field
     * @param dbKey The key for the field in the database (used for auto-increments on fields)
     * @return The processed field value.
     */
    private Object processField(Object o, Field f, String dbKey) {
        if (o == null) {
            if (!f.isAnnotationPresent(AutoIncrement.class))
                return null;
            if (!f.getType().equals(Integer.class))
                return null;
            Integer i = 1;
            DBObject obj = null;
            while (obj == null || this.collection.findOne(obj) == null) {
                i++;
                obj = new BasicDBObject(dbKey, i);
            }
            o = i;
            try {
                f.set(this, o);
            } catch (IllegalAccessException e) {
                e.printStackTrace(); //TODO remove this
                return null;
            }
        }
        if (o instanceof GModel) {
            if (f.isAnnotationPresent(LinkedObject.class)) {
                ObjectId objectId1 = ((GModel) o).getObjectId();
                if (objectId1 == null) {
                    ((GModel) o).save();
                    objectId1 = ((GModel) o).getObjectId();
                }
                BasicDBObject object = new BasicDBObject();
                object.put("_class", o.getClass().getName());
                object.put("_id", objectId1);
                object.put("_link_flag", true);
                o = object;
            } else if (f.isAnnotationPresent(EmbeddedObject.class)) {
                o = ((GModel) o).getObjectValue();
            } else {
                return null;
            }
        }
        if (o instanceof Map) {
            o = processMap(f, dbKey, (Map) o);
        }
        if (o instanceof List) {
            o = processList(f, dbKey, (List) o);
        }
        if (o instanceof Enum) {
            o = "_ENUM:" + ((Enum) o).getDeclaringClass().getName() + "_" + ((Enum) o).name();
        }
        return o;
    }

    private DBObject processMap(Field f, String dbKey, Map o) {
        DBObject object = new BasicDBObject();
        for (Object o1 : o.keySet()) {
            if (!(o1 instanceof String))
                continue;
            String key = (String) o1;
            Object value = o.get(key);
            value = processField(value, f, dbKey);
            object.put(key, value);
        }
        return object;
    }

    /**
     * This is used to take a DB object, and read it. It will convert linked objets, embedded objects, and lists of anything.
     *
     * @param o The object from the database
     * @return Processed data.
     */
    private Object readObjectFromDB(Object o) {
        if (o == null)
            return null;
        if (o instanceof String) {
            String o1 = (String) o;
            if (o1.startsWith("_ENUM")) {
                String s;
                try {
                    s = o1.split(":")[1];
                } catch (IndexOutOfBoundsException ex) {
                    return null;
                }
                String[] split = s.split("_");
                if (split.length < 2)
                    return null;
                Class<Enum> aClass;
                try {
                    aClass = (Class<Enum>) Class.forName(split[0]);
                } catch (ClassNotFoundException e) {
                    return null;
                } catch (ClassCastException e) {
                    e.printStackTrace(); //TODO Remove
                    return null;
                }
                String[] strings = Arrays.copyOfRange(split, 1, split.length);
                StringBuilder className = new StringBuilder();
                for (String string : strings) {
                    className.append(string).append("_");
                }
                String s1 = className.toString();
                if (s1.length() == 0)
                    return null;
                String cName = s1.substring(0, s1.length() - 1);
                return Enum.valueOf(aClass, cName);
            }
        }
        if (o instanceof DBObject) {
            if (o instanceof BasicDBList) {
                BasicDBList l = (BasicDBList) o;
                List list = new ArrayList();
                for (Object next : l) {
                    list.add(readObjectFromDB(next));
                }
                o = list;
            } else {
                DBObject o1 = (DBObject) o;
                Object aClass = o1.get("_class");
                if (aClass == null) {
                    HashMap<String, Object> m = new HashMap<>();
                    for (String s : o1.keySet()) {
                        m.put(s, o1.get(s));
                    }
                    return m;
                }
                if (!(aClass instanceof String))
                    return null;
                Class c;
                try {
                    c = Class.forName((String) aClass);
                } catch (ClassNotFoundException e) {
                    e.printStackTrace(); //TODO remove this
                    return null;
                }
                if (!c.isAssignableFrom(GModel.class))
                    return null;
                ObjectId objectId = (ObjectId) o1.get("_id");
                String collectionName = getCollectionName(c);
                DBCollection collection1 = this.database.getCollection(collectionName);
                DBObject id = collection1.findOne(new BasicDBObject("_id", objectId));
                if (id == null)
                    return null;
                GModel m = modelFromOne(c, id, this.database);
                if (o1.containsField("_link_flag")) {
                    m = m.findOne();
                }
                o = m;
            }
        }
        return o;
    }

    DBObject getObjectValue() {
        return getObjectValue(true);
    }

    /**
     * Turns this into a DBObject
     *
     * @return DBObject
     */
    DBObject getObjectValue(boolean includeNulls) {
        updateObjects();
        DBObject dbObject = basicDBObjectBuilder.get();
        if (includeNulls)
            return dbObject;
        DBObject object2 = cloneObject(dbObject);
        for (String s : dbObject.keySet()) {
            Object o = dbObject.get(s);
            if (o == null) {
                object2.removeField(s);
                continue;
            }
            if (o instanceof BasicDBObject && ((BasicDBObject) o).size() == 0) {
                object2.removeField(s);
                continue;
            }
            if (o instanceof BasicDBList && ((BasicDBList) o).size() == 0) {
                object2.removeField(s);
            }
        }
        return object2;
    }

    private BasicDBObject cloneObject(DBObject object) {
        BasicDBObject basicDBObject = new BasicDBObject();
        for (String s : object.keySet()) {
            basicDBObject.put(s, object.get(s));
        }
        return basicDBObject;

    }

    /**
     * Validates the type.
     *
     * @param o Object to test.
     * @return If we can store this object.
     */
    static boolean isValidValue(Object o) {
        return (o instanceof String) || (o instanceof Integer) || (o instanceof Float) || (o instanceof Long)
                || (o instanceof DBObject) || (o instanceof Boolean) || (o instanceof ObjectId)
                || (o instanceof Double) || (o instanceof Character) || (o instanceof Short) || (o instanceof Date);
    }

    /**
     *
     * Processes a list into a {@link BasicDBList}
     * @param l The {@link List} object
     * @return The {@link BasicDBList} object.
     */
    private BasicDBList processList(Field f, String dbKey, List l) {
        BasicDBList list = new BasicDBList();
        for (Object o : l) {
            Object o1 = processField(o, f, dbKey);
            if (!isValidValue(o1))
                continue;
            list.add(o1);
        }
        return list;
    }

    /**
     * Finds a single object from the database based upon the values of this object.
     *
     * @return one.
     */
    public GModel findOne() {
        DBObject objectValue = this.getObjectValue(false);
        DBObject one = this.collection.findOne(objectValue);
        if (one == null)
            return null;
        GModel gModel = modelFromOne(this.getClass(), one, this.database);
        gModel.database = this.database;
        gModel.updateObjects();
        return gModel;
    }

    /**
     * Finds many from the params supplied
     *
     * @return All objects.
     */
    public List<GModel> findMany() {
        ArrayList<GModel> models = new ArrayList<>();
        DBCursor dbObjects = this.collection.find(this.getObjectValue(false));
        for (DBObject o : dbObjects) {
            GModel m = modelFromOne(this.getClass(), o, this.database);
            models.add(m);
        }
        return models;
    }

    public List<GModel> findAll() {
        ArrayList<GModel> models = new ArrayList<>();
        DBCursor dbObjects = this.collection.find();
        for (DBObject o : dbObjects) {
            GModel m = modelFromOne(this.getClass(), o, this.database);
            models.add(m);
        }
        return models;
    }

    /**
     * Generates a GModel class from the database (read)
     *
     * @param clazz    GModel
     * @param one      The object
     * @param database The database
     * @return A {@link GModel} or null if there is an error getting the value from the database
     */
    static GModel modelFromOne(Class<? extends GModel> clazz, DBObject one, DB database) {
        Constructor<? extends GModel> constructor;
        try {
            constructor = clazz.getConstructor(DB.class, DBObject.class);
        } catch (NoSuchMethodException e) {
            e.printStackTrace(); //TODO remove this
            return null;
        }
        GModel gModel;
        try {
            gModel = constructor.newInstance(database, one);
        } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) {
            e.printStackTrace(); //TODO remove this
            return null;
        }
        return gModel;
    }

    /**
     * Loads the collection for the constructors.
     */
    private void loadCollection() {
        this.collection = this.database.getCollection(getCollectionName(this));
    }

    static String getCollectionName(GModel model) {
        return getCollectionName(model.getClass());
    }

    static String getCollectionName(Class<? extends GModel> clazz) {
        String name;
        if (clazz.isAnnotationPresent(Collection.class)) {
            Collection annotation = clazz.getAnnotation(Collection.class);
            name = annotation.value();
        } else {
            name = clazz.getSimpleName().toLowerCase();
            name = name + "s";
        }
        return name;
    }

    public void remove() {
        if (this.objectId == null)
            return;
        this.collection.remove(new BasicDBObject("_id", this.objectId));
    }
}