001/* 
002 * JKNIV, whinstone one contract to access your database.
003 * 
004 * Copyright (C) 2017, the original author or authors.
005 *
006 * This library is free software; you can redistribute it and/or
007 * modify it under the terms of the GNU Lesser General Public
008 * License as published by the Free Software Foundation; either
009 * version 2.1 of the License.
010 * 
011 * This library is distributed in the hope that it will be useful,
012 * but WITHOUT ANY WARRANTY; without even the implied warranty of
013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014 * Lesser General Public License for more details.
015 * 
016 * You should have received a copy of the GNU Lesser General Public
017 * License along with this library; if not, write to the Free Software Foundation, Inc., 
018 * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
019 */
020package net.sf.jkniv.whinstone.types;
021
022import java.lang.reflect.Field;
023import java.lang.reflect.Method;
024import java.util.Collection;
025import java.util.HashMap;
026import java.util.Map;
027
028import org.slf4j.Logger;
029import org.slf4j.LoggerFactory;
030
031import net.sf.jkniv.reflect.beans.ObjectProxy;
032import net.sf.jkniv.reflect.beans.ObjectProxyFactory;
033import net.sf.jkniv.reflect.beans.PropertyAccess;
034import net.sf.jkniv.whinstone.JdbcColumn;
035import net.sf.jkniv.whinstone.types.Converter.EnumType;
036
037/**
038 * A register type of {@link Convertible} data
039 * 
040 * @author Alisson Gomes
041 * @since 0.6.0
042 */
043@SuppressWarnings({ "unchecked", "rawtypes" })
044public class RegisterType
045{
046    private final static Logger                    LOG = LoggerFactory.getLogger(RegisterType.class);
047    private final Map<TypeMap, Convertible<Object, Object>> registry = new HashMap<TypeMap, Convertible<Object, Object>>();
048    
049    public void register(Convertible convertible)
050    {
051        LOG.info("Registering converter {} {}", convertible);
052        Convertible<Object, Object> c = registry.put(new TypeMap(convertible.getType(), convertible.getColumnType()), convertible);
053        if (c != null && c != convertible)
054            LOG.warn("The converter {} was replaced by {}", c, convertible);
055        
056        TypeMap typeMapByClass = new TypeMap(convertible.getType(), UnknowType.getInstance());
057        c = registry.put(typeMapByClass, convertible);
058        if (c != null && c != convertible)
059            LOG.warn("Default converter for {} was replaced by {}", c, convertible);
060    }
061    
062    /**
063     * Retrieve a {@link Convertible} instance to customize the
064     * value of database to class field.
065     * @param <T> type of object into proxy reference
066     * @param access for property
067     * @param proxy of return type from query
068     * @return A convertible instance if found into class proxy or {@link NoConverterType}
069     * instance when the field or method is not annotated.
070     */
071    public <T> Convertible<Object, Object> toJdbc(PropertyAccess access, ObjectProxy<T> proxy)
072    {
073        Convertible<Object, Object> convertible = getConverterByAnnotation(access.getField(), access.getReadMethod(), proxy);
074        if (convertible == null)
075        {
076            if(access.hasField())
077                convertible = registry.get(new TypeMap(access.getField().getType(), UnknowType.getInstance()));
078            if (convertible == null)
079                convertible = NoConverterType.getInstance();
080        }
081        return convertible;
082    }
083
084    public Convertible<Object, Object> getConverter(Class classType)
085    {
086        Convertible<Object, Object> convertible = registry.get(new TypeMap(classType, UnknowType.getInstance()));
087        if (convertible == null)
088            convertible = NoConverterType.getInstance();
089
090        return convertible;
091    }
092    
093    /**
094     * Retrieve a {@link Convertible} instance to customize the
095     * value of database to class field.
096     * @param <T> type of object into proxy reference
097     * @param <R> the result of a query (like a {@link java.sql.ResultSet}
098     * @param column metadata of it
099     * @param proxy of return type from query
100     * @return A convertible instance if found into class proxy or {@link NoConverterType}
101     * instance when the field or method is not annotated.
102     */
103    public <T,R> Convertible<Object, Object> toAttribute(JdbcColumn<R> column, ObjectProxy<T> proxy)
104    {
105        PropertyAccess access = column.getPropertyAccess();
106        Convertible<Object, Object> convertible = getConverterByAnnotation(access.getField(), access.getWriterMethod(), proxy);
107        if (convertible == null)
108        {
109            if (column.getPropertyAccess().hasField())
110                convertible = registry.get(new TypeMap(column.getPropertyAccess().getField().getType(), column.getType()));
111            else if (column.getPropertyAccess().hasWriterMethod())
112                convertible = registry.get(new TypeMap(column.getPropertyAccess().getWriterMethod().getParameterTypes()[0], column.getType()));
113            
114            if (convertible == null)
115                convertible = NoConverterType.getInstance();
116        }
117        return convertible;
118    }
119    
120    /**
121     * Retrieve a {@link Convertible} instance to customize the
122     * value of database to class field.
123     * @param <T> type of proxy instance
124     * @param field attribute name from class
125     * @param method getter or setter method
126     * @param proxy of return type from query
127     * @return A convertible instance if found into class proxy or {@link NoConverterType}
128     * instance when the field or method is not annotated.
129     */
130    private <T> Convertible<Object, Object> getConverterByAnnotation(Field field, Method method,
131            ObjectProxy<T> proxy)
132    {
133        Convertible convertible = null;
134        Converter converter = null;
135        
136        if (field == null || method == null || Map.class.isAssignableFrom(proxy.getTargetClass())
137                || Collection.class.isAssignableFrom(proxy.getTargetClass()) || proxy.getTargetClass().isArray())
138            return convertible;
139        
140        converter = (Converter) method.getAnnotation(Converter.class);
141        if (converter == null)
142            converter = (Converter) field.getAnnotation(Converter.class);
143        
144        if (converter != null)
145        {
146            ObjectProxy proxyConvertible = null;
147            if (converter.converter().isEnum())
148            {
149                if (converter.isEnum() == EnumType.ORDINAL)
150                    convertible = new EnumOrdinalType(converter.converter());
151                else
152                    convertible = new EnumNameType(converter.converter());
153            }
154            else
155            {
156                proxyConvertible = ObjectProxyFactory.of(converter.converter());
157                if (converter.pattern() != null)
158                    proxyConvertible.setConstructorArgs(converter.pattern());
159                convertible = (Convertible) proxyConvertible.newInstance();
160            }
161        }
162        else if(field.getType().isEnum()) // doesn't use @Converter default is persist as String
163        {
164            convertible = new EnumNameType(field.getType());
165        }
166        return convertible;
167    }
168    
169    public boolean isEmpty()
170    {
171        return registry.isEmpty();
172    }
173}