content: /**
   * 	Copyright (c) 2010 Philipp Giese
   * 
   * Permission is hereby granted, free of charge, to any person obtaining
   * a copy of this software and associated documentation files (the
   * "Software"), to deal in the Software without restriction, including
   * without limitation the rights to use, copy, modify, merge, publish,
   * distribute, sublicense, and/or sell copies of the Software, and to
   * permit persons to whom the Software is furnished to do so, subject to
   * the following conditions:
   * 
   * The above copyright notice and this permission notice shall be
   * included in all copies or substantial portions of the Software.
   * 
   * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
   * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
   * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
   * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
   * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
   * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
   * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
   */
  package com.orm.androrm;
  
  import java.lang.reflect.Field;
  import java.lang.reflect.Modifier;
  import java.util.ArrayList;
  import java.util.List;
  
  import com.orm.androrm.field.DataField;
  import com.orm.androrm.field.ForeignKeyField;
  import com.orm.androrm.field.ManyToManyField;
  import com.orm.androrm.field.OneToManyField;
  
  import android.util.Log;
  
  /**
   * @author Philipp Giese
   *
   */
  public class DatabaseBuilder {
  
  	private static final String TAG = "ANDORM:DATABASE:BUILDER";
  	
  	public static final String getTableName(Class<?> clazz) {
  		return clazz.getSimpleName().toLowerCase();
  	}
  	
  	protected static final<T extends Model> List<TableDefinition> getTableDefinitions(Class<T> clazz) {
  		List<TableDefinition> definitions = new ArrayList<TableDefinition>();
  		
  		if(!Modifier.isAbstract(clazz.getModifiers())) {
  			try {
  				if(ModelCache.knowsModel(clazz)) {
  					return ModelCache.getTableDefinitions(clazz);
  				}
  
  				T object = Model.getInstace(clazz);
  				TableDefinition definition = new TableDefinition(getTableName(clazz));
  				
  				getFieldDefinitions(object, clazz, definition);
  				
  				definitions.add(definition);
  				
  				for(Class<? extends Model> c: definition.getRelationalClasses()) {
  					definitions.addAll(getRelationDefinitions(c));
  				}
  				
  				ModelCache.setTableDefinitions(clazz, definitions);
  				
  				return definitions;
  			} catch(IllegalAccessException e) {
  				Log.e(TAG, "an exception has been thrown while gathering the database structure information.", e);
  			}
  		}
  		
  		return null;
  	}
  	
  	private static final<T extends Model> void getFieldDefinitions(
  			
  			T 				instance, 
  			Class<T> 		clazz, 
  			TableDefinition modelTable
  			
  	) throws IllegalArgumentException, IllegalAccessException {
  		
  		if(clazz != null && clazz.isInstance(instance)) {
  			ModelCache.addModel(clazz);
  			
  			for(Field field: getFields(clazz, instance)) {
  				String name = field.getName();
  
  				Object o = field.get(instance);
  				
  				if(o instanceof DataField) {
  					DataField<?> fieldObject = (DataField<?>) o;
  					modelTable.addField(name, fieldObject);
  				}
  				
  				if(o instanceof ManyToManyField) {
  					modelTable.addRelationalClass(clazz);
  				}
  			}
  			
  			getFieldDefinitions(instance, Model.getSuperclass(clazz), modelTable);
  		}
  	}
  	
  	/**
  	 * Retrieves all fields of a given class, that are
  	 * <ol>
  	 * 	<li><b>NOT</b> private</li>
  	 * 	<li>Database fields</li>
  	 * </ol>
  	 * In addition these fields are set to be accessible, so
  	 * that they can then be further processed. 
  	 * 
  	 * @param clazz		Class to extract the fields from. 
  	 * @param instance	Instance of that class. 
  	 * 
  	 * @return {@link List} of all fields, that are database fields, 
  	 * 		   and that are <b>NOT</b> private. 
  	 */
  	protected static final List<Field> getFields(
  			
  			Class<? extends Model> 	clazz, 
  			Model 					instance
  			
  	) {
  		
  		if(ModelCache.knowsFields(clazz)) {
  			return ModelCache.fieldsForModel(clazz);
  		}
  		
  		Field[] declaredFields = clazz.getDeclaredFields();
  		List<Field> fields = new ArrayList<Field>();
  		
  		try {
  			for(int i = 0, length = declaredFields.length; i < length; i++) {
  				Field field = declaredFields[i];
  				
  				if(!Modifier.isPrivate(field.getModifiers())) {
  					field.setAccessible(true);
  					Object f = field.get(instance);
  					
  					if(isDatabaseField(f)) {
  						fields.add(field);
  					}
  				}
  			}
  		} catch (IllegalAccessException e) {
  			Log.e(TAG, "exception thrown while trying to gain access to fields of class " 
  					+ clazz.getSimpleName(), e);
  		}
  		
  		ModelCache.setModelFields(clazz, fields);
  		
  		return fields;
  	}
  	
  	private static final boolean isDatabaseField(Object field) {
  		if(field != null) {
  			if(field instanceof DataField
  					|| isRelationalField(field)) {
  				
  				return true;
  			}
  		}
  		
  		return false;
  	}
  	
  	protected static final boolean isRelationalField(Object field) {
  		if(field != null) {
  			if(field instanceof ForeignKeyField
  					|| field instanceof OneToManyField
  					|| field instanceof ManyToManyField) {
  				
  				return true;
  			}
  		}
  		
  		return false;
  	}
  	
  	private static final<T extends Model> List<TableDefinition> getRelationDefinitions(Class<T> clazz) {
  		List<TableDefinition> definitions = new ArrayList<TableDefinition>();
  		
  		T object = Model.getInstace(clazz);
  		getRelationDefinitions(object, clazz, definitions);
  		
  		return definitions;
  	}
  	
  	@SuppressWarnings("unchecked")
  	private static final <T extends Model> void getRelationDefinitions(
  			
  			T 						instance, 
  			Class<T> 				clazz, 
  			List<TableDefinition> 	definitions
  			
  	) {
  		
  		if(clazz != null && clazz.isInstance(instance) ) {
  			for(Field field: getFields(clazz, instance)) {
  				try {
  					Object o = field.get(instance);
  					
  					if(o instanceof ManyToManyField) {
  						ManyToManyField<T, ?> m = (ManyToManyField<T, ?>) o;
  
  						String leftHand = getTableName(clazz);
  						String rightHand = getTableName(m.getTarget());
  						
  						TableDefinition definition = new TableDefinition(m.getRelationTableName());
  						
  						ForeignKeyField<T> leftLink = m.getLeftLinkDescriptor();
  						ForeignKeyField<?> rightLink = m.getRightHandDescriptor();
  						
  						definition.addField(leftHand, leftLink);
  						definition.addField(rightHand, rightLink);
  						
  						definitions.add(definition);
  					}
  				} catch(IllegalAccessException e) {
  					Log.e(TAG, "could not gather relation definitions for class " 
  							+ clazz.getSimpleName(), e);
  				}
  			}
  			
  			getRelationDefinitions(instance, Model.getSuperclass(clazz), definitions);
  		}
  	}
  }
  