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    @Override
070    public final <T> void setValueIfNonNull(String column, T value) 
071    {
072        if(value != null){
073                setValue(column,value);
074        }
075    }
076    @SuppressWarnings("unchecked")
077    @Override
078    public <T> T getValue(int columnID)
079    {
080        try {
081                        return (T) metaData.getterMethods.get(columnID).invoke(this);
082                } catch (IndexOutOfBoundsException e) {
083                        return null;
084                }catch (Exception e) {
085                        Throwables.throwIfUnchecked(e);
086                        throw new RuntimeException(e);
087                }
088    }
089
090    @Override
091    public <T> void setValue(int columnID,T value)
092    {
093        try {
094                        metaData.setterMethods.get(columnID).invoke(this, value);
095                } catch (IndexOutOfBoundsException e) {
096                        return ;
097                }catch (Exception e) {
098                        Throwables.throwIfUnchecked(e);
099                        throw new RuntimeException(e);
100                }
101    }
102    @Override
103    public final <T> void setValueIfNonNull(int columnID,T value)
104    {
105        if(value != null){
106            setValue(columnID,value);
107        }
108    }
109    
110    @Override
111    public Object[] primaryValues()
112    {
113        Object[] values = new Object[metaData.primaryKeyCount];
114        for(int i = 0; i < values.length; ++i) {
115            values[i] = getValue(metaData.primaryKeyIds[i]);
116        }
117        return values;
118    }
119
120    @Override
121    public <T> T primaryValue()
122    {
123        if(metaData.primaryKeyCount != 1) {
124            throw new UnsupportedOperationException();
125        }
126        return getValue(metaData.primaryKeyIds[0]);
127    }
128    
129    @Override
130    public Object[] asValueArray(int...columnIds)
131    {
132                if(null == columnIds || columnIds.length == 0){
133                        columnIds = metaData.defaultColumnIdList;
134                }
135        Object[] v = new Object[columnIds.length];
136        for(int i=0; i< v.length; ++i){
137                v[i] = getValue(columnIds[i]);
138        }
139        return v;
140    }
141        @Override
142        public Map<String, Object> asNameValueMap(){
143                // double check 
144                if(mapView == null){
145                        synchronized (this) {
146                                if(mapView == null){
147                                        mapView = new RowMapView(this);
148                                }
149                        }
150                }
151                return mapView;
152        }
153        
154        @Override
155    public <B extends BaseBean> B copy(B bean)
156    {
157        return copy(bean,new int[0]);
158    }
159
160    @SuppressWarnings("unchecked")
161    @Override
162    public <B extends BaseBean> B copy(B bean, int... fieldList)
163    {   
164        if(bean != null && bean != this){
165                if (null == fieldList || 0 == fieldList.length){
166                        fieldList = metaData.defaultColumnIdList;
167                }
168                for (int columnId:fieldList) {
169                        if( bean.isInitialized(columnId) && !Objects.deepEquals(bean.getValue(columnId), getValue(columnId))){
170                                setValue(columnId, bean.getValue(columnId));
171                        }
172                }
173        }
174        return (B)this;
175    }
176
177    @Override
178    public <B extends BaseBean> B copy(B bean, String... fieldList)
179    {
180        return copy(bean,
181                        (null == fieldList) 
182                                ? null 
183                                : Ints.toArray(Lists.transform(Arrays.asList(fieldList), metaData.COLUMNID_FUN)));
184    }
185
186    @Override
187        @SuppressWarnings("unchecked")
188        public <B extends BaseBean,F extends BaseBean> B copy(F from,Map<Integer,Integer> columnsMap){
189        for(Map.Entry<Integer, Integer> entry : checkNotNull(columnsMap,"columnsMap is null").entrySet()){
190                setValue(entry.getValue(), null == from ? null : from.getValue(entry.getKey()));
191        }
192        return (B) this;
193    }
194    
195    @SuppressWarnings("unchecked")
196        public <B extends BaseBean> B copy(Map<Integer, Object>values){
197        if(null != values){
198                for(Entry<Integer, Object> entry : values.entrySet()){
199                        setValue(entry.getKey().intValue(), entry.getValue());
200                }
201        }
202        return (B) this;
203    }
204    
205    @Override
206        public final String tableName() {
207                return metaData.tablename;
208        }
209
210        /**
211     * cast byte array to HEX string
212     * 
213     * @param input
214     * @return {@code null} if {@code input} is null
215     */
216    private static final String toHex(byte[] input) {
217        if (null == input){
218            return null;
219        }
220        StringBuffer sb = new StringBuffer(input.length * 2);
221        for (int i = 0; i < input.length; i++) {
222            sb.append(Character.forDigit((input[i] & 240) >> 4, 16));
223            sb.append(Character.forDigit(input[i] & 15, 16));
224        }
225        return sb.toString();
226    }
227    private static final StringBuilder append(StringBuilder buffer,boolean full,byte[] value){
228        if(full || null == value){
229            buffer.append(toHex(value));
230        }else{
231            buffer.append(value.length).append(" bytes");
232        }
233        return buffer;
234    }
235    private static int stringLimit = 64;
236    private static final int MINIMUM_LIMIT = 16;
237    private static final StringBuilder append(StringBuilder buffer,boolean full,String value){
238        if(full || null == value || value.length() <= stringLimit){
239            buffer.append(value);
240        }else{
241            buffer.append(value.substring(0,stringLimit - 8)).append(" ...").append(value.substring(stringLimit-4,stringLimit));
242        }
243        return buffer;
244    }
245    private static final StringBuilder append(StringBuilder buffer,boolean full,Object value){
246        if(value instanceof String){
247                return append(buffer, full, (String)value);
248        }
249        if(value instanceof byte[]){
250                return append(buffer, full, (byte[])value);
251        }
252        return buffer.append(value);
253    }
254    public static final void setStringLimit(int limit){
255        checkArgument(limit >= MINIMUM_LIMIT, "INVALID limit %s,minimum value %s",limit,MINIMUM_LIMIT);
256        stringLimit = limit;
257    }
258        @Override
259        public String toString(boolean notNull, boolean fullIfStringOrBytes) {
260        StringBuilder builder = new StringBuilder(this.getClass().getName()).append("@").append(Integer.toHexString(this.hashCode())).append("[");
261        int count = 0;
262        for(int i = 0; i < metaData.columnCount; ++i){
263                Object value = getValue(i);
264                if( !notNull || null != value){
265                if(count > 0){
266                        builder.append(",");
267                }
268                        builder.append(metaData.columnNameOf(i)).append("=");
269                        append(builder,fullIfStringOrBytes,value);
270                        ++count;
271                }
272        }
273        builder.append("]");        
274                return builder.toString();
275        }
276        @Override
277        public boolean equals(Object object) {
278                if(!metaData.beanType.isInstance(object)){
279                        return false;
280                }
281                BaseBean bean = (BaseBean)object;
282                EqualsBuilder equalsBuilder = new EqualsBuilder();
283                for(int i=0; i<metaData.columnCount; ++i){
284                        equalsBuilder.append((Object)getValue(i), (Object)bean.getValue(i));
285                }
286                return equalsBuilder.isEquals();
287    }
288        @Override
289    public int hashCode() {
290                HashCodeBuilder hashCodeBuilder = new HashCodeBuilder(-82280557, -700257973);
291                int[] hashIds = metaData.primaryKeyCount > 0 ? metaData.primaryKeyIds : metaData.defaultColumnIdList;
292                for(int i=0; i<hashIds.length; ++i){
293                        hashCodeBuilder.append((Object)getValue(hashIds[i]));
294                }
295                return hashCodeBuilder.toHashCode();
296    }
297        
298    @Override
299    public String toString() {
300        return toString(true,false);
301    }
302
303    @Override
304    public int compareTo(BaseRow object){
305        CompareToBuilder compareToBuilder = new CompareToBuilder();
306        for(int i=0; i<metaData.columnCount; ++i){
307                compareToBuilder.append((Object)getValue(i), (Object)object.getValue(i));
308        }
309        return compareToBuilder.toComparison();
310    }
311    @Override
312    public BaseRow clone(){
313                try {
314                        return (BaseRow) super.clone();
315                } catch (CloneNotSupportedException e) {
316                        throw new RuntimeException(e);
317                }
318        }
319
320}