001/*
002 * (C) Copyright 2007 Nuxeo SAS (http://nuxeo.com/) and contributors.
003 *
004 * All rights reserved. This program and the accompanying materials
005 * are made available under the terms of the GNU Lesser General Public License
006 * (LGPL) version 2.1 which accompanies this distribution, and is available at
007 * http://www.gnu.org/licenses/lgpl.html
008 *
009 * This library is distributed in the hope that it will be useful,
010 * but WITHOUT ANY WARRANTY; without even the implied warranty of
011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
012 * Lesser General Public License for more details.
013 *
014 * Contributors:
015 *     Nuxeo - initial API and implementation
016 *
017 * $Id: EditableModelImpl.java 25559 2007-10-01 12:48:23Z atchertchian $
018 */
019
020package org.nuxeo.ecm.platform.ui.web.model.impl;
021
022import java.io.ByteArrayInputStream;
023import java.io.ByteArrayOutputStream;
024import java.io.IOException;
025import java.io.InputStream;
026import java.io.ObjectInputStream;
027import java.io.ObjectOutputStream;
028import java.io.Serializable;
029import java.util.ArrayList;
030import java.util.Arrays;
031import java.util.Collection;
032import java.util.Collections;
033import java.util.Comparator;
034import java.util.HashMap;
035import java.util.List;
036import java.util.Map;
037
038import javax.faces.model.DataModel;
039import javax.faces.model.DataModelEvent;
040import javax.faces.model.DataModelListener;
041
042import org.apache.commons.logging.Log;
043import org.apache.commons.logging.LogFactory;
044import org.nuxeo.ecm.core.api.ListDiff;
045import org.nuxeo.ecm.platform.ui.web.model.EditableModel;
046import org.nuxeo.ecm.platform.ui.web.util.DeepCopy;
047
048/**
049 * Editable data model that handles value changes.
050 * <p>
051 * Only accepts lists or arrays of Serializable objects for now.
052 *
053 * @author <a href="mailto:at@nuxeo.com">Anahide Tchertchian</a>
054 */
055@SuppressWarnings({ "unchecked", "rawtypes" })
056public class EditableModelImpl extends DataModel implements EditableModel, Serializable {
057
058    private static final long serialVersionUID = 2550850486035521538L;
059
060    private static final Log log = LogFactory.getLog(EditableModelImpl.class);
061
062    // use this key to indicate unset values
063    private static final Object _NULL = new Object();
064
065    protected final Object originalData;
066
067    // current data list
068    protected List data;
069
070    // current row index (zero relative)
071    protected int index = -1;
072
073    // XXX AT: not thread safe (?)
074    protected Map<Integer, Integer> keyMap;
075
076    protected ListDiff listDiff;
077
078    protected Object template;
079
080    public EditableModelImpl(Object value, Object template) {
081        if (value != null) {
082            if (!(value instanceof List) && !(value instanceof Object[])) {
083                log.error("Cannot build editable model from " + value + ", list or array needed");
084                value = null;
085            }
086        }
087        originalData = value;
088        listDiff = new ListDiff();
089        keyMap = new HashMap<Integer, Integer>();
090        initializeData(value);
091        this.template = template;
092    }
093
094    protected void initializeData(Object originalData) {
095        List data = null;
096        if (originalData == null) {
097            data = new ArrayList<Object>();
098        } else if (originalData instanceof Object[]) {
099            data = new ArrayList<Object>();
100            for (Object item : (Object[]) originalData) {
101                data.add(DeepCopy.deepCopy(item));
102            }
103        } else if (originalData instanceof List) {
104            data = new ArrayList<Object>();
105            data.addAll((List) DeepCopy.deepCopy(originalData));
106        }
107        setWrappedData(data);
108    }
109
110    @Override
111    public Object getUnreferencedTemplate() {
112        if (template == null) {
113            return null;
114        }
115        if (template instanceof Serializable) {
116            try {
117                Serializable serializableTemplate = (Serializable) template;
118                ByteArrayOutputStream out = new ByteArrayOutputStream();
119                ObjectOutputStream oos = new ObjectOutputStream(out);
120                oos.writeObject(serializableTemplate);
121                oos.close();
122                // deserialize to make sure it is not the same instance
123                byte[] pickled = out.toByteArray();
124                InputStream in = new ByteArrayInputStream(pickled);
125                ObjectInputStream ois = new ObjectInputStream(in);
126                Object newTemplate = ois.readObject();
127                return newTemplate;
128            } catch (IOException | ClassNotFoundException e) {
129                throw new RuntimeException(e);
130            }
131        } else {
132            log.warn("Template is not serializable, cannot clone " + "to add unreferenced value into model.");
133            return template;
134        }
135    }
136
137    @Override
138    public Object getOriginalData() {
139        return originalData;
140    }
141
142    @Override
143    public Object getWrappedData() {
144        return data;
145    }
146
147    @Override
148    public void setWrappedData(Object data) {
149        index = -1;
150        if (data == null) {
151            this.data = null;
152        } else {
153            this.data = (List) data;
154            for (int i = 0; i < this.data.size(); i++) {
155                keyMap.put(i, i);
156            }
157        }
158    }
159
160    // row data methods
161
162    /**
163     * Returns the initial data for the given key.
164     * <p>
165     * Returns null marker if key is invalid or data did not exist for given key in the original data.
166     */
167    protected Object getOriginalRowDataForKey(int key) {
168        if (originalData instanceof List) {
169            List list = (List) originalData;
170            if (key < 0 || key >= list.size()) {
171                return _NULL;
172            } else {
173                // if key exists in original data, then it's equal to the
174                // index.
175                return list.get(key);
176            }
177        } else if (originalData instanceof Object[]) {
178            Object[] array = (Object[]) originalData;
179            if (key < 0 || key >= array.length) {
180                return _NULL;
181            } else {
182                // if key exists in original data, then it's equal to the
183                // index.
184                return array[key];
185            }
186        } else {
187            return _NULL;
188        }
189    }
190
191    /**
192     * Returns a new row key that is not already used.
193     */
194    protected int getNewRowKey() {
195        Collection<Integer> keys = keyMap.values();
196        if (keys.isEmpty()) {
197            return 0;
198        } else {
199            List<Integer> lkeys = Arrays.asList(keys.toArray(new Integer[] {}));
200            Comparator<Integer> comp = Collections.reverseOrder();
201            Collections.sort(lkeys, comp);
202            Integer max = lkeys.get(0);
203            return max + 1;
204        }
205    }
206
207    @Override
208    public boolean isRowAvailable() {
209        if (data == null) {
210            return false;
211        }
212        return (index == -2) || (index >= 0 && index < data.size());
213    }
214
215    @Override
216    public boolean isRowModified() {
217        if (!isRowAvailable()) {
218            return false;
219        } else {
220            Integer rowKey = getRowKey();
221            if (rowKey == null) {
222                return false;
223            } else {
224                Object oldData = getOriginalRowDataForKey(rowKey);
225                if (oldData == _NULL) {
226                    return false;
227                }
228                Object newData = getRowData();
229                if (newData == null && oldData == null) {
230                    return false;
231                } else {
232                    if (newData != null) {
233                        return !newData.equals(oldData);
234                    } else {
235                        return !oldData.equals(newData);
236                    }
237                }
238            }
239        }
240    }
241
242    @Override
243    public boolean isRowNew() {
244        if (!isRowAvailable()) {
245            return false;
246        } else {
247            Integer rowKey = getRowKey();
248            if (rowKey == null) {
249                return false;
250            } else {
251                Object oldData = getOriginalRowDataForKey(rowKey);
252                return oldData == _NULL;
253            }
254        }
255    }
256
257    @Override
258    public void recordValueModified(int index, Object newValue) {
259        listDiff.modify(index, newValue);
260    }
261
262    @Override
263    public int getRowCount() {
264        if (data == null) {
265            return -1;
266        }
267        return data.size();
268    }
269
270    @Override
271    public Object getRowData() {
272        if (data == null) {
273            return null;
274        } else if (!isRowAvailable()) {
275            throw new IllegalArgumentException("No row available on " + this);
276        } else {
277            if (index == -2) {
278                // XXX return template instead (?)
279                return null;
280            }
281            return data.get(index);
282        }
283    }
284
285    @Override
286    public void setRowData(Object rowData) {
287        if (isRowAvailable()) {
288            data.set(index, rowData);
289        }
290    }
291
292    @Override
293    public int getRowIndex() {
294        return index;
295    }
296
297    @Override
298    public void setRowIndex(int rowIndex) {
299        if (rowIndex < -2) {
300            throw new IllegalArgumentException();
301        }
302        int old = index;
303        index = rowIndex;
304        if (data == null) {
305            return;
306        }
307        DataModelListener[] listeners = getDataModelListeners();
308        if (old != index && listeners != null) {
309            Object rowData = null;
310            if (isRowAvailable()) {
311                rowData = getRowData();
312            }
313            DataModelEvent event = new DataModelEvent(this, index, rowData);
314            int n = listeners.length;
315            for (int i = 0; i < n; i++) {
316                if (null != listeners[i]) {
317                    listeners[i].rowSelected(event);
318                }
319            }
320        }
321    }
322
323    @Override
324    public Integer getRowKey() {
325        if (index == -2) {
326            return index;
327        }
328        if (index < 0) {
329            return null;
330        }
331        return keyMap.get(index);
332    }
333
334    @Override
335    public void setRowKey(Integer key) {
336        // find index for that key
337        if (key != null) {
338            for (Integer i : keyMap.keySet()) {
339                Integer k = keyMap.get(i);
340                if (key.equals(k)) {
341                    setRowIndex(i);
342                    break;
343                }
344            }
345        } else {
346            setRowIndex(-1);
347        }
348    }
349
350    @Override
351    public ListDiff getListDiff() {
352        return listDiff;
353    }
354
355    @Override
356    public void setListDiff(ListDiff listDiff) {
357        this.listDiff = new ListDiff(listDiff);
358    }
359
360    @Override
361    public boolean isDirty() {
362        return listDiff != null && listDiff.isDirty();
363    }
364
365    @Override
366    public void addTemplateValue() {
367        addValue(getUnreferencedTemplate());
368    }
369
370    @Override
371    public boolean addValue(Object value) {
372        int position = data.size();
373        boolean res = data.add(value);
374        listDiff.add(value);
375        int newRowKey = getNewRowKey();
376        keyMap.put(position, newRowKey);
377        return res;
378    }
379
380    @Override
381    public void insertTemplateValue(int index) {
382        insertValue(index, getUnreferencedTemplate());
383    }
384
385    @Override
386    public void insertValue(int index, Object value) {
387        if (index > data.size()) {
388            // make sure enough rows are made available
389            for (int i = data.size(); i < index; i++) {
390                addTemplateValue();
391            }
392        }
393        data.add(index, value);
394        listDiff.insert(index, value);
395        // update key map to reflect new structure
396        Map<Integer, Integer> newKeyMap = new HashMap<Integer, Integer>();
397        for (Integer i : keyMap.keySet()) {
398            Integer key = keyMap.get(i);
399            if (i >= index) {
400                newKeyMap.put(i + 1, key);
401            } else {
402                newKeyMap.put(i, key);
403            }
404        }
405        keyMap = newKeyMap;
406        // insert new key
407        int newRowKey = getNewRowKey();
408        keyMap.put(index, newRowKey);
409    }
410
411    @Override
412    public Object moveValue(int fromIndex, int toIndex) {
413        Object old = data.remove(fromIndex);
414        data.add(toIndex, old);
415        listDiff.move(fromIndex, toIndex);
416        // update key map to reflect new structure
417        Map<Integer, Integer> newKeyMap = new HashMap<Integer, Integer>();
418        if (fromIndex < toIndex) {
419            for (Integer i : keyMap.keySet()) {
420                Integer key = keyMap.get(i);
421                if (i < fromIndex) {
422                    newKeyMap.put(i, key);
423                } else if (i > fromIndex && i <= toIndex) {
424                    newKeyMap.put(i - 1, key);
425                } else if (i > toIndex) {
426                    newKeyMap.put(i, key);
427                }
428            }
429        } else if (fromIndex > toIndex) {
430            for (Integer i : keyMap.keySet()) {
431                Integer key = keyMap.get(i);
432                if (i < toIndex) {
433                    newKeyMap.put(i, key);
434                } else if (i >= toIndex && i < fromIndex) {
435                    newKeyMap.put(i + 1, key);
436                } else if (i > fromIndex) {
437                    newKeyMap.put(i, key);
438                }
439            }
440        }
441        newKeyMap.put(toIndex, keyMap.get(fromIndex));
442        keyMap = newKeyMap;
443        return old;
444    }
445
446    @Override
447    public Object removeValue(int index) {
448        Object old = data.remove(index);
449        listDiff.remove(index);
450        // update key map to reflect new structure
451        Map<Integer, Integer> newKeyMap = new HashMap<Integer, Integer>();
452        for (Integer i : keyMap.keySet()) {
453            Integer key = keyMap.get(i);
454            if (i > index) {
455                newKeyMap.put(i - 1, key);
456            } else if (i < index) {
457                newKeyMap.put(i, key);
458            }
459        }
460        keyMap = newKeyMap;
461        return old;
462    }
463
464    @Override
465    public int size() {
466        if (data != null) {
467            return data.size();
468        }
469        return 0;
470    }
471
472    @Override
473    public String toString() {
474        final StringBuilder buf = new StringBuilder();
475        buf.append(EditableModelImpl.class.getSimpleName());
476        buf.append(" {");
477        buf.append("originalData: ");
478        buf.append(originalData);
479        buf.append(", data: ");
480        buf.append(data);
481        buf.append(", index: ");
482        buf.append(index);
483        buf.append(", keyMap: ");
484        buf.append(keyMap);
485        buf.append(", dirty: ");
486        buf.append(isDirty());
487        buf.append('}');
488        return buf.toString();
489    }
490
491}