content: /**
   * 
   */
  package org.activejpa.entity;
  
  import java.io.Serializable;
  import java.lang.reflect.Field;
  import java.lang.reflect.Method;
  import java.util.Collection;
  import java.util.List;
  
  import javax.persistence.TypedQuery;
  import javax.persistence.criteria.CriteriaBuilder;
  import javax.persistence.criteria.CriteriaQuery;
  import javax.persistence.criteria.Join;
  import javax.persistence.criteria.Root;
  
  import org.activejpa.ActiveJpaException;
  import org.activejpa.util.PropertyUtil;
  import org.javalite.common.Inflector;
  import org.slf4j.Logger;
  import org.slf4j.LoggerFactory;
  
  import lombok.RequiredArgsConstructor;
  
  /**
   * @author ganeshs
   *
   */
  @RequiredArgsConstructor
  public class EntityCollection<T extends Model> extends BaseObject {
  	
  	private final Model parent;
  	
  	private final String name;
  	
  	private final Class<T> elementType;
  	
  	private static final Logger logger = LoggerFactory.getLogger(EntityCollection.class);
  	
  	/**
  	 * Adds the item to the underlying collection
  	 */
  	public T add(T item) {
  		logger.debug("Adding the item {} to the collection", item);
  		return addOrRemove(item, true);
  	}
  	
  	/**
  	 * Removes the item from the underlying collection
  	 */
  	public T remove(T item) {
  		logger.debug("Removing the item {} from the collection", item);
  		return addOrRemove(item, false);
  	}
  	
  	/**
  	 * Adds or Removes the item to/from the underlying collection. This method attempts in the following order to add/remove the item,
  	 * <ul>
  	 * <li> Checks if there's a method with the signature <code>addOrderItem(OrderItem orderItem)</code> in the parent model where orderItem is a collection.
  	 * <li> Retrieves the getter for the collection property and adds/removes the item to/from that
  	 * <li> Retrieves the collection property field and adds/removes the item to/from that
  	 * </ul>
  	 * 
  	 * Throws {@link ActiveJpaException} if none of the above ways adds/removes the item to/from the collection
  	 * @param item
  	 */
  	@SuppressWarnings("unchecked")
  	protected T addOrRemove(T item, boolean add) {
  		Method method = null;
  		String methodName = getMethodName(add ? "add" : "remove", name);
  		try {
  			logger.trace("Attempting to invoke the method {} on the parent {}", methodName, parent);
  			method = parent.getClass().getDeclaredMethod(methodName, elementType);
  			method.invoke(parent, item);
  			return item;
  		} catch (NoSuchMethodException e) {
  			logger.debug("Method {} doesn't exist in the class {}", methodName, parent.getClass());
  		} catch (Exception e) {
  			throw new ActiveJpaException("Failed while invoking the method " + method.getName(), e);
  		}
  		
  		// Try adding the item to the collection property returned by the getter
  		logger.trace("Attempting to invoke the getter for the property {} on the parent {}", name, parent);
  		Collection<T> collection = (Collection<T>) PropertyUtil.getProperty(parent, name);
  		if (collection != null) {
  			if (add) {
  				collection.add(item);
  			} else {
  				collection.remove(item);
  			}
  			return item;
  		}
  		
  		// Try to find out the field and add/remove it to/from that
  		try {
  			logger.trace("Attempting to invoke the the property {} on the parent {}", name, parent);
  			Field field = parent.getClass().getDeclaredField(name);
  			field.setAccessible(true);
  			collection = (Collection<T>) field.get(parent);
  			if (add) {
  				collection.add(item);
  			} else {
  				collection.remove(item);
  			}
  			return item;
  		} catch (Exception e) {
  			logger.error("Failed to {} the item {} to the collection of the parent {}", add ? "add" : "remove", item, parent, e);
  			throw new ActiveJpaException("Failed while adding/removing the item to the collection - " + name, e);
  		}
  	}
  
  	public T findById(Serializable id) {
  		return one(name + ".id", id);
  	}
  	
  	public List<T> all() {
  		return where(new Filter());
  	}
  	
  	public T first(Object... paramValues) {
  		List<T> list = where(createFilter(paramValues));
  		if (list.isEmpty()) {
  			return null;
  		}
  		return list.get(0);
  	}
  	
  	public T one(Object... paramValues) {
  		Filter filter = createFilter(paramValues);
  		return createCollectionQuery(filter).getSingleResult();
  	}
  	
  	public List<T> where(Filter filter) {
  		return createCollectionQuery(filter).getResultList();
  	}
  	
  	public List<T> where(Object... paramValues) {
  		return where(createFilter(paramValues));
  	}
  	
  	public long count(Filter filter) {
  		filter.condition("id", parent.getId());
  		CriteriaBuilder builder = getEntityManager().getCriteriaBuilder();
  		CriteriaQuery<Long> cQuery = builder.createQuery(Long.class);
  		Root<? extends Model> root = cQuery.from(parent.getClass());
  		Join<?,?> join = root.join(name);
  		cQuery.select(builder.count(join));
  		filter.constructQuery(builder, cQuery, root);
  		
  		TypedQuery<Long> query = createQuery(cQuery, filter);
  		return query.getSingleResult();
  	}
  	
  	private TypedQuery<T> createCollectionQuery(Filter filter) {
  		filter.condition("id", parent.getId());
  		TypedQuery<T> query = createQuery(parent.getClass(), name, elementType, filter);
  		return query;
  	}
  	
  	public void persist() {
  		parent.persist();
  	}
  	
  	/**
  	 * Constructs the name of the method from the collectionName 
  	 * 
  	 * @param prefix
  	 * @param collectionName
  	 * @return
  	 */
  	private String getMethodName(String prefix, String collectionName) {
  		collectionName = Inflector.singularize(collectionName);
  		return prefix + collectionName.substring(0, 1).toUpperCase() + collectionName.substring(1);
  	}
  }
  