content: /**
   * Copyright (C) 2012 the original author or authors.
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *     http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  package co.jirm.mapper.definition;
  
  import static com.google.common.collect.Maps.newLinkedHashMap;
  import static java.util.Collections.emptyList;
  
  import java.util.Iterator;
  import java.util.LinkedHashMap;
  import java.util.List;
  import java.util.Map;
  import java.util.Map.Entry;
  import java.util.concurrent.Callable;
  import java.util.concurrent.ExecutionException;
  
  import javax.persistence.Table;
  
  import static co.jirm.core.util.JirmPrecondition.check;
  import co.jirm.mapper.SqlObjectConfig;
  
  import com.google.common.base.Objects;
  import com.google.common.base.Optional;
  import com.google.common.base.Splitter;
  import com.google.common.base.Throwables;
  import com.google.common.collect.ImmutableMap;
  import com.google.common.collect.Iterators;
  import com.google.common.collect.Lists;
  import com.google.common.util.concurrent.UncheckedExecutionException;
  
  
  public class SqlObjectDefinition<T> {
  	private final Class<T> objectType;
  	private final Map<String, SqlParameterDefinition> parameters;
  	private final Map<String, SqlParameterDefinition> sqlNameToParameter;
  	private final Map<String, SqlParameterDefinition> idParameters;
  	private final Map<String, SqlParameterDefinition> manyToOneParameters;
  	private final Map<String, SqlParameterDefinition> simpleParameters;
  	
  	private final String sqlName;
  	
  	private SqlObjectDefinition(SqlObjectConfig config, Class<T> objectType, Map<String, SqlParameterDefinition> parameters) {
  		super();
  		this.objectType = objectType;
  		this.parameters = parameters;
  		LinkedHashMap<String, SqlParameterDefinition> n = newLinkedHashMap();
  		LinkedHashMap<String, SqlParameterDefinition> id = newLinkedHashMap();
  		LinkedHashMap<String, SqlParameterDefinition> m = newLinkedHashMap();
  		LinkedHashMap<String, SqlParameterDefinition> s = newLinkedHashMap();
  		
  		for(Entry<String, SqlParameterDefinition> e : parameters.entrySet()) {
  			n.put(e.getValue().sqlName(), e.getValue());
  			if (e.getValue().isId()) {
  				id.put(e.getKey(), e.getValue());
  			}
  			if (e.getValue().getObjectDefinition().isPresent()) {
  				m.put(e.getKey(), e.getValue());
  			}
  			else {
  				s.put(e.getKey(), e.getValue());
  			}
  		}
  		sqlNameToParameter = ImmutableMap.copyOf(n);
  		idParameters = ImmutableMap.copyOf(id);
  		manyToOneParameters = ImmutableMap.copyOf(m);
  		simpleParameters = ImmutableMap.copyOf(s);
  		
  		Table t = objectType.getAnnotation(Table.class);
  		if (t != null && t.name() != null) {
  			sqlName = t.name();
  		}
  		else {
  			sqlName = config.getNamingStrategy().classToTableName(objectType.getSimpleName());
  		}
  	}
  	
  	@SuppressWarnings({ "rawtypes", "unchecked" })
  	private static <T> SqlObjectDefinition<T> _fromClass(Class<T> c, SqlObjectConfig config) {
  		return new SqlObjectDefinition(config, c, ImmutableMap.copyOf(SqlParameterDefinition.getSqlBeanParameters(c, config)));
  	}
  	
  //	public static <T> SqlObjectDefinition<T> fromClass(Class<T> c) {
  //		return fromClass(c, SqlObjectConfig.DEFAULT);
  //	}
  	
  	@SuppressWarnings("unchecked")
  	public static <T> SqlObjectDefinition<T> 
  		fromClass(final Class<T> objectType, final SqlObjectConfig config) {
  		try {
  			return (SqlObjectDefinition<T>) config.getCache().get(objectType, new Callable<SqlObjectDefinition<?>>() {
  				@Override
  				public SqlObjectDefinition<?> call() throws Exception {
  					return SqlObjectDefinition._fromClass(objectType, config);
  				}
  			});
  		} catch (ExecutionException e) {
  			throw Throwables.propagate(e);
  		} catch (UncheckedExecutionException e) {
  			throw Throwables.propagate(Objects.firstNonNull(e.getCause(), e));
  		}
  	}
  	
  	public Class<T> getObjectType() {
  		return objectType;
  	}
  	
  	public Map<String,SqlParameterDefinition> getParameters() {
  		return parameters;
  	}
  	
  	
  	public Map<String, SqlParameterDefinition> getManyToOneParameters() {
  		return manyToOneParameters;
  	}
  	
  	public Map<String, SqlParameterDefinition> getSqlNameToParameter() {
  		return sqlNameToParameter;
  	}
  	
  	public Optional<SqlParameterDefinition> resolveParameter(String parameter) {
  		if (getSqlNameToParameter().containsKey(parameter))
  			return Optional.of(getSqlNameToParameter().get(parameter));
  		if (getParameters().containsKey(parameter))
  			return Optional.of(getParameters().get(parameter));
  		return Optional.absent();
  	}
  	
  	public Optional<String> parameterNameToSql(String parameter) {
  		SqlParameterDefinition d = getParameters().get(parameter);
  		if (d == null) return Optional.absent();
  		return Optional.of(d.sqlName());
  	}
  	
  
  	
  	private final static Splitter dotSplitter = Splitter.on(".");
  	
  	
  	public List<SqlParameterDefinition> resolveParameterPath(String parameterDotPath) {
  		Iterator<String> it = dotSplitter.split(parameterDotPath).iterator();
  		List<SqlParameterDefinition> r = Lists.newArrayList();
  		SqlObjectDefinition<?> currentObject = this;
  		while(it.hasNext()) {
  			String pathPart = it.next();
  			Optional<SqlParameterDefinition> p = currentObject.resolveParameter(pathPart);
  			if ( ! p.isPresent() ) return emptyList();
  			check.state( ! ( it.hasNext() && ! p.get().isComplex()), "Bad path parts references a simple parameter: {}", parameterDotPath);
  			r.add(p.get());
  			if (it.hasNext())
  				currentObject = p.get().getObjectDefinition().get().getObjectDefintion();
  		}
  		return r;
  	}
  	
  	public Map<String, SqlParameterDefinition> getIdParameters() {
  		return idParameters;
  	}
  	
  	public Map<String, SqlParameterDefinition> getSimpleParameters() {
  		return simpleParameters;
  	}
  	
  	public Optional<SqlParameterDefinition> idParameter() {
  		return Optional.fromNullable(Iterators.get(this.getIdParameters().values().iterator(), 0, null));
  	}
  	
  	public String getSqlName() {
  		return sqlName;
  	}
  }
  