001package gu.sql2java;
002
003import java.util.Arrays;
004import java.util.Map;
005import java.util.Objects;
006import java.util.Map.Entry;
007
008import com.google.common.base.Preconditions;
009import com.google.common.base.Throwables;
010import com.google.common.collect.Lists;
011import com.google.common.primitives.Ints;
012
013import static com.google.common.base.Preconditions.*;
014
015/**
016 * abstract implementation of {@link BaseBean}
017 * @author guyadong
018 *
019 */
020public abstract class BaseRow implements BaseBean,Comparable<BaseRow>,Cloneable {
021
022        protected final RowMetaData metaData;
023        private volatile Map<String, Object> mapView;
024        protected BaseRow(RowMetaData metaData) {
025                this.metaData = checkNotNull(metaData,"metaData is null");
026        }
027        protected BaseRow() {
028                this.metaData = checkNotNull(RowMetaData.getMetaData(getClass()),"metaData is null");
029        }
030        @Override
031        public final boolean isInitialized(String column) {
032                return isInitialized(metaData.columnIDOf(column));
033        }
034        
035        @Override
036        public boolean beModified() {
037                for(int i=0; i<metaData.columnCount; ++i){
038                        if(isModified(i)){
039                                return true;
040                        }
041                }
042                return false;
043        }
044        
045    @Override
046    public boolean isModified(String column){        
047        return isModified(metaData.columnIDOf(column));
048    }
049
050        @Override
051        public final <T> T getValue(String column) {
052                return getValue(metaData.columnIDOf(column));
053        }
054
055        @Override
056        public final <T> T getValueChecked(int columnID) {
057                T value = getValue(columnID);
058                return Preconditions.checkNotNull(value,"value of columnid %s IS NULL", columnID);
059        }
060        @Override
061        public final <T> T getValueChecked(String column) {
062                T value = getValue(column);
063                return Preconditions.checkNotNull(value,"value of column %s IS NULL", column);
064        }
065        @Override
066        public final <T> void setValue(String column, T value) {
067                setValue(metaData.columnIDOf(column),value);
068        }
069    @SuppressWarnings("unchecked")
070    @Override
071    public <T> T getValue(int columnID)
072    {
073        try {
074                        return (T) metaData.getterMethods.get(columnID).invoke(this);
075                } catch (IndexOutOfBoundsException e) {
076                        return null;
077                }catch (Exception e) {
078                        Throwables.throwIfUnchecked(e);
079                        throw new RuntimeException(e);
080                }
081    }
082
083    @Override
084    public <T> void setValue(int columnID,T value)
085    {
086        try {
087                        metaData.setterMethods.get(columnID).invoke(this, value);
088                } catch (IndexOutOfBoundsException e) {
089                        return ;
090                }catch (Exception e) {
091                        Throwables.throwIfUnchecked(e);
092                        throw new RuntimeException(e);
093                }
094    }
095    @Override
096    public Object[] primaryValues()
097    {
098        Object[] values = new Object[metaData.primaryKeyCount];
099        for(int i = 0; i < values.length; ++i) {
100            values[i] = getValue(metaData.primaryKeyIds[i]);
101        }
102        return values;
103    }
104
105    @Override
106    public <T> T primaryValue()
107    {
108        if(metaData.primaryKeyCount != 1) {
109            throw new UnsupportedOperationException();
110        }
111        return getValue(metaData.primaryKeyIds[0]);
112    }
113    
114    @Override
115    public Object[] asValueArray(int...columnIds)
116    {
117                if(null == columnIds || columnIds.length == 0){
118                        columnIds = metaData.defaultColumnIdList;
119                }
120        Object[] v = new Object[columnIds.length];
121        for(int i=0; i< v.length; ++i){
122                v[i] = getValue(columnIds[i]);
123        }
124        return v;
125    }
126        @Override
127        public Map<String, Object> asNameValueMap(){
128                // double check 
129                if(mapView == null){
130                        synchronized (this) {
131                                if(mapView == null){
132                                        mapView = new RowMapView(this);
133                                }
134                        }
135                }
136                return mapView;
137        }
138        
139        @Override
140    public <B extends BaseBean> B copy(B bean)
141    {
142        return copy(bean,new int[0]);
143    }
144
145    @SuppressWarnings("unchecked")
146    @Override
147    public <B extends BaseBean> B copy(B bean, int... fieldList)
148    {   
149        if(bean != null && bean != this){
150                if (null == fieldList || 0 == fieldList.length){
151                        fieldList = metaData.defaultColumnIdList;
152                }
153                for (int columnId:fieldList) {
154                        if( bean.isInitialized(columnId) && !Objects.deepEquals(bean.getValue(columnId), getValue(columnId))){
155                                setValue(columnId, bean.getValue(columnId));
156                        }
157                }
158        }
159        return (B)this;
160    }
161
162    @Override
163    public <B extends BaseBean> B copy(B bean, String... fieldList)
164    {
165        return copy(bean,
166                        (null == fieldList) 
167                                ? null 
168                                : Ints.toArray(Lists.transform(Arrays.asList(fieldList), metaData.COLUMNID_FUN)));
169    }
170
171    @Override
172        @SuppressWarnings("unchecked")
173        public <B extends BaseBean,F extends BaseBean> B copy(F from,Map<Integer,Integer> columnsMap){
174        for(Map.Entry<Integer, Integer> entry : checkNotNull(columnsMap,"columnsMap is null").entrySet()){
175                setValue(entry.getValue(), null == from ? null : from.getValue(entry.getKey()));
176        }
177        return (B) this;
178    }
179    
180    @SuppressWarnings("unchecked")
181        public <B extends BaseBean> B copy(Map<Integer, Object>values){
182        if(null != values){
183                for(Entry<Integer, Object> entry : values.entrySet()){
184                        setValue(entry.getKey().intValue(), entry.getValue());
185                }
186        }
187        return (B) this;
188    }
189    
190    @Override
191        public final String tableName() {
192                return metaData.tablename;
193        }
194
195        /**
196     * cast byte array to HEX string
197     * 
198     * @param input
199     * @return {@code null} if {@code input} is null
200     */
201    private static final String toHex(byte[] input) {
202        if (null == input){
203            return null;
204        }
205        StringBuffer sb = new StringBuffer(input.length * 2);
206        for (int i = 0; i < input.length; i++) {
207            sb.append(Character.forDigit((input[i] & 240) >> 4, 16));
208            sb.append(Character.forDigit(input[i] & 15, 16));
209        }
210        return sb.toString();
211    }
212    private static final StringBuilder append(StringBuilder buffer,boolean full,byte[] value){
213        if(full || null == value){
214            buffer.append(toHex(value));
215        }else{
216            buffer.append(value.length).append(" bytes");
217        }
218        return buffer;
219    }
220    private static int stringLimit = 64;
221    private static final int MINIMUM_LIMIT = 16;
222    private static final StringBuilder append(StringBuilder buffer,boolean full,String value){
223        if(full || null == value || value.length() <= stringLimit){
224            buffer.append(value);
225        }else{
226            buffer.append(value.substring(0,stringLimit - 8)).append(" ...").append(value.substring(stringLimit-4,stringLimit));
227        }
228        return buffer;
229    }
230    private static final StringBuilder append(StringBuilder buffer,boolean full,Object value){
231        if(value instanceof String){
232                return append(buffer, full, (String)value);
233        }
234        if(value instanceof byte[]){
235                return append(buffer, full, (byte[])value);
236        }
237        return buffer.append(value);
238    }
239    public static final void setStringLimit(int limit){
240        checkArgument(limit >= MINIMUM_LIMIT, "INVALID limit %s,minimum value %s",limit,MINIMUM_LIMIT);
241        stringLimit = limit;
242    }
243        @Override
244        public String toString(boolean notNull, boolean fullIfStringOrBytes) {
245        StringBuilder builder = new StringBuilder(this.getClass().getName()).append("@").append(Integer.toHexString(this.hashCode())).append("[");
246        int count = 0;
247        for(int i = 0; i < metaData.columnCount; ++i){
248                Object value = getValue(i);
249                if( !notNull || null != value){
250                if(count > 0){
251                        builder.append(",");
252                }
253                        builder.append(metaData.columnNameOf(i)).append("=");
254                        append(builder,fullIfStringOrBytes,value);
255                        ++count;
256                }
257        }
258        builder.append("]");        
259                return builder.toString();
260        }
261        @Override
262        public boolean equals(Object object) {
263                if(!metaData.beanType.isInstance(object)){
264                        return false;
265                }
266                BaseBean bean = (BaseBean)object;
267                EqualsBuilder equalsBuilder = new EqualsBuilder();
268                for(int i=0; i<metaData.columnCount; ++i){
269                        equalsBuilder.append((Object)getValue(i), (Object)bean.getValue(i));
270                }
271                return equalsBuilder.isEquals();
272    }
273        @Override
274    public int hashCode() {
275                HashCodeBuilder hashCodeBuilder = new HashCodeBuilder(-82280557, -700257973);
276                int[] hashIds = metaData.primaryKeyCount > 0 ? metaData.primaryKeyIds : metaData.defaultColumnIdList;
277                for(int i=0; i<hashIds.length; ++i){
278                        hashCodeBuilder.append((Object)getValue(hashIds[i]));
279                }
280                return hashCodeBuilder.toHashCode();
281    }
282        
283    @Override
284    public String toString() {
285        return toString(true,false);
286    }
287
288    @Override
289    public int compareTo(BaseRow object){
290        CompareToBuilder compareToBuilder = new CompareToBuilder();
291        for(int i=0; i<metaData.columnCount; ++i){
292                compareToBuilder.append((Object)getValue(i), (Object)object.getValue(i));
293        }
294        return compareToBuilder.toComparison();
295    }
296    @Override
297    public BaseRow clone(){
298                try {
299                        return (BaseRow) super.clone();
300                } catch (CloneNotSupportedException e) {
301                        throw new RuntimeException(e);
302                }
303        }
304
305}