package org.raidenjpa.db;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import javax.persistence.ElementCollection;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.Predicate;
import org.raidenjpa.reflection.Cloner;
import org.raidenjpa.reflection.ReflectionUtil;
import org.raidenjpa.util.BadSmell;
import org.raidenjpa.util.FixMe;
@BadSmell("1) Singleton dont allow multi thread - 2) Is it really necessary?")
public class InMemoryDB {
private static InMemoryDB me;
private static final Object MUTEX = new Object();
private Long sequence = 1L;
private Map<String, List<Object>> data = new HashMap<String, List<Object>>();
private InMemoryDB() {
}
@SuppressWarnings("unchecked")
public <T> List<T> getAll(String table) {
return (List<T>) rows(table);
}
@FixMe("Get the @Id property instead of id attribute")
@SuppressWarnings("unchecked")
public <T> List<T> get(Class<T> table, Collection<Object> ids) {
List<Object> rows = rows(table);
List<T> result = new ArrayList<T>();
for (Object entidade : rows) {
Object beanId = ReflectionUtil.getBeanField(entidade, "id");
if (ids.contains(beanId)) {
result.add((T) entidade);
}
}
return result;
}
private <T> List<Object> rows(Class<T> table) {
return rows(table.getSimpleName());
}
@BadSmell("Primitive obssession, nao deveria ser um map, deveria ser uma classe tabelas")
private <T> List<Object> rows(String table) {
List<Object> rows = data.get(table);
if (rows == null) {
rows = new ArrayList<Object>();
data.put(table, rows);
}
return rows;
}
public <T> T put(T original) {
T entidade = this.cloneObject(original);
if (ReflectionUtil.getBeanField(entidade, "id") == null) {
ReflectionUtil.setBeanField(entidade, "id", nextSequence());
rows(entidade.getClass()).add(entidade);
} else {
replace(rows(entidade.getClass()), entidade);
}
return entidade;
}
@SuppressWarnings("unchecked")
private <T> T cloneObject(T original) {
if (original instanceof Cloneable) {
Method cloneMethod = ReflectionUtil.getMethod(original, "clone");
if (cloneMethod != null) {
return (T) ReflectionUtil.invoke(original, cloneMethod);
}
}
return Cloner.shallowCopy(original);
}
public void replace(List<Object> rows, Object newObject) {
Iterator<Object> it = rows.iterator();
while (it.hasNext()) {
Object entidade = it.next();
if (ReflectionUtil.getBeanField(entidade, "id").equals(ReflectionUtil.getBeanField(newObject, "id"))) {
rows.remove(entidade);
rows.add(newObject);
return;
}
}
rows.add(newObject);
}
public Long nextSequence() {
synchronized (sequence) {
return sequence++;
}
}
public <T> T get(Class<T> table, Object id) {
List<T> entidades = get(table, Arrays.asList(id));
if (entidades.isEmpty()) {
return null;
}
return entidades.get(0);
}
public <T> Resultado<T> query(final Consulta<T> consulta) {
List<Object> rows = new ArrayList<Object>(rows(consulta.getType()));
filter(rows, new ArrayList<Filtro>(consulta.getFiltros()));
order(rows, new ArrayList<Ordem>(consulta.getOrdems()));
rows = limit(rows, consulta.getLimit());
return new ResultadoInMemory<T>(rows);
}
private List<Object> limit(List<Object> rows, Long limit) {
if (limit == null || limit < 0 || limit >= rows.size()) {
return rows;
}
return rows.subList(0, limit.intValue());
}
private void order(List<Object> rows, final List<Ordem> ordems) {
if (ordems.isEmpty()) {
return;
}
Collections.sort(rows, new Comparator<Object>() {
@SuppressWarnings({ "unchecked" })
public int compare(Object o1, Object o2) {
for (Ordem ordem : ordems) {
Comparable<Object> value1 = (Comparable<Object>) ReflectionUtil.getBeanField(o1, ordem.getAtributo());
Comparable<Object> value2 = (Comparable<Object>) ReflectionUtil.getBeanField(o2, ordem.getAtributo());
if (value1.equals(value2)) {
continue;
}
if (ordem.getOrientacao() == Orientacao.ASC) {
return value1.compareTo(value2);
} else {
return value2.compareTo(value1);
}
}
return 0;
}
});
}
public void remove(Class<?> table, Object id) {
List<Object> rows = rows(table);
Iterator<Object> it = rows.iterator();
while (it.hasNext()) {
Object entidade = it.next();
Object entidadeId = ReflectionUtil.getBeanField(entidade, "id");
if (entidadeId.equals(id)) {
it.remove();
return;
}
}
throw new IllegalArgumentException("Cannot delete: Table " + table.getSimpleName() + " nao contem o id " + id);
}
private <T> void filter(final List<Object> rows, final List<Filtro> filtros) {
if (filtros.isEmpty()) {
return;
}
final Filtro filtro = filtros.get(0);
CollectionUtils.filter(rows, new Predicate() {
@SuppressWarnings({ "unchecked" })
public boolean evaluate(Object obj) {
String atributo = filtro.getAtributo();
Operador operador = filtro.getOperador();
if (ReflectionUtil.isBeanFieldAnnotated(obj, atributo, ElementCollection.class)) {
Collection<Object> collection = (Collection<Object>) ReflectionUtil.getBeanField(obj, atributo);
for (Object itemCollection : collection) {
if (isTrue((Comparable<Object>) itemCollection, operador, (Comparable<Object>) filtro.getValor())) {
return true;
}
}
return false;
} else {
Comparable<Object> valorFiltro = (Comparable<Object>) filtro.getValor();
Comparable<Object> valorObj = (Comparable<Object>) ReflectionUtil.getBeanField(obj, atributo);
return isTrue(valorObj, operador, valorFiltro);
}
}
private boolean isTrue(Comparable<Object> valorObj, Operador operador, Comparable<Object> valorFiltro) {
if (operador == Operador.IGUAL) {
if (valorFiltro.equals(valorObj)) {
return true;
}
} else if (operador == Operador.MAIOR_IGUAL_QUE) {
if (valorObj.compareTo(valorFiltro) >= 0) {
return true;
}
} else if (operador == Operador.MAIOR_QUE) {
if (valorObj.compareTo(valorFiltro) > 0) {
return true;
}
} else if (operador == Operador.MENOR_IGUAL_QUE) {
if (valorObj.compareTo(valorFiltro) <= 0) {
return true;
}
} else if (operador == Operador.MENOR_QUE) {
if (valorObj.compareTo(valorFiltro) < 0) {
return true;
}
} else {
throw new RuntimeException("Operador " + operador + " not implemented yet");
}
return false;
}
});
filtros.remove(filtro);
filter(rows, filtros);
}
public static InMemoryDB me() {
if (me == null) {
synchronized (MUTEX) {
if (me == null) {
me = new InMemoryDB();
}
}
}
return me;
}
public int count() {
Collection<List<Object>> tabelas = data.values();
int count = 0;
for (List<Object> rows : tabelas) {
count += rows.size();
}
return count;
}
public void truncate() {
data = new HashMap<String, List<Object>>();
}
}