001/*
002 * Copyright (c) 2006-2011 Nuxeo SA (http://nuxeo.com/) and others.
003 *
004 * All rights reserved. This program and the accompanying materials
005 * are made available under the terms of the Eclipse Public License v1.0
006 * which accompanies this distribution, and is available at
007 * http://www.eclipse.org/legal/epl-v10.html
008 *
009 * Contributors:
010 *     Florent Guillaume
011 */
012package org.nuxeo.ecm.core.storage.sql;
013
014import java.io.Serializable;
015import java.util.ArrayList;
016import java.util.Calendar;
017import java.util.List;
018import java.util.Map;
019import java.util.Map.Entry;
020
021import org.nuxeo.ecm.core.api.model.Delta;
022
023/**
024 * The data of a single row in a table (keys/values form a map), or of multiple rows with the same id (values is an
025 * array of Serializable).
026 * <p>
027 * The id of the row is distinguished internally from other columns. For fragments corresponding to created data, the
028 * initial id is a temporary one, and it will be changed after database insert.
029 */
030public final class Row extends RowId implements Serializable, Cloneable {
031
032    private static final long serialVersionUID = 1L;
033
034    private static final int DEFAULT = 5;
035
036    private enum OpaqueValue {
037        OPAQUE_VALUE
038    }
039
040    /**
041     * A database value we don't care about reading. When present in a fragment, it won't be written, but any other
042     * value will be.
043     */
044    public static final Serializable OPAQUE = OpaqueValue.OPAQUE_VALUE;
045
046    /**
047     * The row keys, for single row.
048     */
049    protected String[] keys;
050
051    /**
052     * The row values.
053     */
054    public Serializable[] values;
055
056    /**
057     * The size of the allocated part of {@link #values}, for single rows.
058     */
059    protected int size;
060
061    /** Copy constructor. */
062    private Row(Row row) {
063        super(row);
064        keys = row.keys == null ? null : row.keys.clone();
065        values = row.values == null ? null : row.values.clone();
066        size = row.size;
067    }
068
069    @Override
070    public Row clone() {
071        return new Row(this);
072    }
073
074    /**
075     * Constructs an empty {@link Row} for the given table with the given id (may be {@code null}).
076     */
077    public Row(String tableName, Serializable id) {
078        super(tableName, id);
079        keys = new String[DEFAULT];
080        values = new Serializable[DEFAULT];
081        // size = 0;
082    }
083
084    /**
085     * Constructs a new {@link Row} from a map.
086     *
087     * @param map the initial data to use
088     */
089    public Row(String tableName, Map<String, Serializable> map) {
090        super(tableName, null); // id set through map
091        keys = new String[map.size()];
092        values = new Serializable[map.size()];
093        // size = 0;
094        for (Entry<String, Serializable> entry : map.entrySet()) {
095            putNew(entry.getKey(), entry.getValue());
096        }
097    }
098
099    /**
100     * Constructs a new {@link Row} from an array of values.
101     *
102     * @param array the initial data to use
103     */
104    public Row(String tableName, Serializable id, Serializable[] array) {
105        super(tableName, id);
106        values = array.clone();
107        keys = null;
108        size = -1;
109    }
110
111    public boolean isCollection() {
112        return size == -1;
113    }
114
115    private void ensureCapacity(int minCapacity) {
116        if (minCapacity > values.length) {
117            Serializable[] k = keys;
118            Serializable[] d = values;
119            int newCapacity = (values.length * 3) / 2 + 1;
120            if (newCapacity < minCapacity) {
121                newCapacity = minCapacity;
122            }
123            keys = new String[newCapacity];
124            values = new Serializable[newCapacity];
125            System.arraycopy(d, 0, values, 0, size);
126            System.arraycopy(k, 0, keys, 0, size);
127        }
128    }
129
130    /**
131     * Puts a key/value.
132     *
133     * @param key the key
134     * @param value the value
135     */
136    public void put(String key, Serializable value) {
137        if (key.equals(Model.MAIN_KEY)) {
138            id = value;
139            return;
140        }
141        // linear search but the array is small
142        for (int i = 0; i < size; i++) {
143            if (key.equals(keys[i])) {
144                Serializable oldValue = values[i];
145                if (oldValue instanceof Delta) {
146                    Delta oldDelta = (Delta) oldValue;
147                    if (value instanceof Delta) {
148                        if (value != oldDelta) {
149                            // add a delta to another delta
150                            value = oldDelta.add((Delta) value);
151                        }
152                    } else if (oldDelta.getFullValue().equals(value)) {
153                        // don't overwrite a delta with the full value
154                        // that actually comes from it
155                        return;
156                    }
157                }
158                values[i] = value;
159                return;
160            }
161        }
162        ensureCapacity(size + 1);
163        keys[size] = key == null ? null: key.intern();
164        values[size++] = value;
165    }
166
167    /**
168     * Puts a key/value, assuming the key is not already there.
169     *
170     * @param key the key
171     * @param value the value
172     */
173    public void putNew(String key, Serializable value) {
174        if (key.equals(Model.MAIN_KEY)) {
175            id = value;
176            return;
177        }
178        ensureCapacity(size + 1);
179        keys[size] = key == null ? null : key.intern();
180        values[size++] = value;
181    }
182
183    /**
184     * Gets a value from a key.
185     *
186     * @param key the key
187     * @return the value
188     */
189    public Serializable get(String key) {
190        if (key.equals(Model.MAIN_KEY)) {
191            return id;
192        }
193        // linear search but the array is small
194        for (int i = 0; i < size; i++) {
195            if (key.equals(keys[i])) {
196                return values[i];
197            }
198        }
199        return null;
200    }
201
202    /**
203     * Gets the list of keys. The id is not included.
204     */
205    public List<String> getKeys() {
206        List<String> list = new ArrayList<String>(size);
207        for (int i = 0; i < size; i++) {
208            list.add(keys[i]);
209        }
210        return list;
211    }
212
213    /**
214     * Gets the list of values. The id is not included.
215     */
216    public List<Serializable> getValues() {
217        List<Serializable> list = new ArrayList<Serializable>(size);
218        for (int i = 0; i < size; i++) {
219            list.add(values[i]);
220        }
221        return list;
222    }
223
224    @Override
225    public String toString() {
226        StringBuilder buf = new StringBuilder();
227        buf.append(getClass().getSimpleName());
228        buf.append('(');
229        buf.append(tableName);
230        buf.append(", ");
231        buf.append(id);
232        if (size != -1) {
233            // single row
234            buf.append(", {");
235            for (int i = 0; i < size; i++) {
236                if (i > 0) {
237                    buf.append(", ");
238                }
239                buf.append(keys[i]);
240                buf.append('=');
241                printValue(values[i], buf);
242            }
243            buf.append('}');
244        } else {
245            // multiple rows
246            buf.append(", [");
247            for (int i = 0; i < values.length; i++) {
248                if (i > 0) {
249                    buf.append(", ");
250                }
251                printValue(values[i], buf);
252            }
253            buf.append(']');
254        }
255        buf.append(')');
256        return buf.toString();
257    }
258
259    public static final int MAX_STRING = 100;
260
261    public static final int MAX_ARRAY = 10;
262
263    @SuppressWarnings("boxing")
264    public static void printValue(Serializable value, StringBuilder buf) {
265        if (value == null) {
266            buf.append("NULL");
267        } else if (value instanceof String) {
268            String v = (String) value;
269            if (v.length() > MAX_STRING) {
270                v = v.substring(0, MAX_STRING) + "...(" + v.length() + " chars)...";
271            }
272            buf.append('"');
273            buf.append(v);
274            buf.append('"');
275        } else if (value instanceof Calendar) {
276            Calendar cal = (Calendar) value;
277            char sign;
278            int offset = cal.getTimeZone().getOffset(cal.getTimeInMillis()) / 60000;
279            if (offset < 0) {
280                offset = -offset;
281                sign = '-';
282            } else {
283                sign = '+';
284            }
285            buf.append(String.format("Calendar(%04d-%02d-%02dT%02d:%02d:%02d.%03d%c%02d:%02d)", cal.get(Calendar.YEAR), //
286                    cal.get(Calendar.MONTH) + 1, //
287                    cal.get(Calendar.DAY_OF_MONTH), //
288                    cal.get(Calendar.HOUR_OF_DAY), //
289                    cal.get(Calendar.MINUTE), //
290                    cal.get(Calendar.SECOND), //
291                    cal.get(Calendar.MILLISECOND), //
292                    sign, offset / 60, offset % 60));
293        } else if (value.getClass().isArray()) {
294            Serializable[] v = (Serializable[]) value;
295            buf.append('[');
296            for (int i = 0; i < v.length; i++) {
297                if (i > 0) {
298                    buf.append(',');
299                    if (i > MAX_ARRAY) {
300                        buf.append("...(");
301                        buf.append(v.length);
302                        buf.append(" items)...");
303                        break;
304                    }
305                }
306                printValue(v[i], buf);
307            }
308            buf.append(']');
309        } else {
310            buf.append(value.toString());
311        }
312    }
313
314}