org.nuxeo.ecm.platform.ui.web.model.impl.EditableModelImpl.java Source code

Java tutorial

Introduction

Here is the source code for org.nuxeo.ecm.platform.ui.web.model.impl.EditableModelImpl.java

Source

/*
 * (C) Copyright 2007 Nuxeo SA (http://nuxeo.com/) and others.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * Contributors:
 *     Nuxeo - initial API and implementation
 *
 * $Id: EditableModelImpl.java 25559 2007-10-01 12:48:23Z atchertchian $
 */

package org.nuxeo.ecm.platform.ui.web.model.impl;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.faces.model.DataModel;
import javax.faces.model.DataModelEvent;
import javax.faces.model.DataModelListener;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.nuxeo.ecm.core.api.ListDiff;
import org.nuxeo.ecm.platform.ui.web.model.EditableModel;
import org.nuxeo.ecm.platform.ui.web.util.DeepCopy;

/**
 * Editable data model that handles value changes.
 * <p>
 * Only accepts lists or arrays of Serializable objects for now.
 *
 * @author <a href="mailto:at@nuxeo.com">Anahide Tchertchian</a>
 */
@SuppressWarnings({ "unchecked", "rawtypes" })
public class EditableModelImpl extends DataModel implements EditableModel, Serializable {

    private static final long serialVersionUID = 2550850486035521538L;

    private static final Log log = LogFactory.getLog(EditableModelImpl.class);

    // use this key to indicate unset values
    private static final Object _NULL = new Object();

    protected final Object originalData;

    // current data list
    protected List data;

    // current row index (zero relative)
    protected int index = -1;

    // XXX AT: not thread safe (?)
    protected Map<Integer, Integer> keyMap;

    protected ListDiff listDiff;

    protected Object template;

    public EditableModelImpl(Object value, Object template) {
        if (value != null) {
            if (!(value instanceof List) && !(value instanceof Object[])) {
                log.error("Cannot build editable model from " + value + ", list or array needed");
                value = null;
            }
        }
        originalData = value;
        listDiff = new ListDiff();
        keyMap = new HashMap<Integer, Integer>();
        initializeData(value);
        this.template = template;
    }

    protected void initializeData(Object originalData) {
        List data = null;
        if (originalData == null) {
            data = new ArrayList<Object>();
        } else if (originalData instanceof Object[]) {
            data = new ArrayList<Object>();
            for (Object item : (Object[]) originalData) {
                data.add(DeepCopy.deepCopy(item));
            }
        } else if (originalData instanceof List) {
            data = new ArrayList<Object>();
            data.addAll((List) DeepCopy.deepCopy(originalData));
        }
        setWrappedData(data);
    }

    @Override
    public Object getUnreferencedTemplate() {
        if (template == null) {
            return null;
        }
        if (template instanceof Serializable) {
            try {
                Serializable serializableTemplate = (Serializable) template;
                ByteArrayOutputStream out = new ByteArrayOutputStream();
                ObjectOutputStream oos = new ObjectOutputStream(out);
                oos.writeObject(serializableTemplate);
                oos.close();
                // deserialize to make sure it is not the same instance
                byte[] pickled = out.toByteArray();
                InputStream in = new ByteArrayInputStream(pickled);
                ObjectInputStream ois = new ObjectInputStream(in);
                Object newTemplate = ois.readObject();
                return newTemplate;
            } catch (IOException | ClassNotFoundException e) {
                throw new RuntimeException(e);
            }
        } else {
            log.warn("Template is not serializable, cannot clone " + "to add unreferenced value into model.");
            return template;
        }
    }

    @Override
    public Object getOriginalData() {
        return originalData;
    }

    @Override
    public Object getWrappedData() {
        return data;
    }

    @Override
    public void setWrappedData(Object data) {
        index = -1;
        if (data == null) {
            this.data = null;
        } else {
            this.data = (List) data;
            for (int i = 0; i < this.data.size(); i++) {
                keyMap.put(i, i);
            }
        }
    }

    // row data methods

    /**
     * Returns the initial data for the given key.
     * <p>
     * Returns null marker if key is invalid or data did not exist for given key in the original data.
     */
    protected Object getOriginalRowDataForKey(int key) {
        if (originalData instanceof List) {
            List list = (List) originalData;
            if (key < 0 || key >= list.size()) {
                return _NULL;
            } else {
                // if key exists in original data, then it's equal to the
                // index.
                return list.get(key);
            }
        } else if (originalData instanceof Object[]) {
            Object[] array = (Object[]) originalData;
            if (key < 0 || key >= array.length) {
                return _NULL;
            } else {
                // if key exists in original data, then it's equal to the
                // index.
                return array[key];
            }
        } else {
            return _NULL;
        }
    }

    /**
     * Returns a new row key that is not already used.
     */
    protected int getNewRowKey() {
        Collection<Integer> keys = keyMap.values();
        if (keys.isEmpty()) {
            return 0;
        } else {
            List<Integer> lkeys = Arrays.asList(keys.toArray(new Integer[] {}));
            Comparator<Integer> comp = Collections.reverseOrder();
            Collections.sort(lkeys, comp);
            Integer max = lkeys.get(0);
            return max + 1;
        }
    }

    @Override
    public boolean isRowAvailable() {
        if (data == null) {
            return false;
        }
        return (index == -2) || (index >= 0 && index < data.size());
    }

    @Override
    public boolean isRowModified() {
        if (!isRowAvailable()) {
            return false;
        } else {
            Integer rowKey = getRowKey();
            if (rowKey == null) {
                return false;
            } else {
                Object oldData = getOriginalRowDataForKey(rowKey);
                if (oldData == _NULL) {
                    return false;
                }
                Object newData = getRowData();
                if (newData == null && oldData == null) {
                    return false;
                } else {
                    if (newData != null) {
                        return !newData.equals(oldData);
                    } else {
                        return !oldData.equals(newData);
                    }
                }
            }
        }
    }

    @Override
    public boolean isRowNew() {
        if (!isRowAvailable()) {
            return false;
        } else {
            Integer rowKey = getRowKey();
            if (rowKey == null) {
                return false;
            } else {
                Object oldData = getOriginalRowDataForKey(rowKey);
                return oldData == _NULL;
            }
        }
    }

    @Override
    public void recordValueModified(int index, Object newValue) {
        listDiff.modify(index, newValue);
    }

    @Override
    public int getRowCount() {
        if (data == null) {
            return -1;
        }
        return data.size();
    }

    @Override
    public Object getRowData() {
        if (data == null) {
            return null;
        } else if (!isRowAvailable()) {
            throw new IllegalArgumentException("No row available on " + this);
        } else {
            if (index == -2) {
                // XXX return template instead (?)
                return null;
            }
            return data.get(index);
        }
    }

    @Override
    public void setRowData(Object rowData) {
        if (isRowAvailable()) {
            data.set(index, rowData);
        }
    }

    @Override
    public int getRowIndex() {
        return index;
    }

    @Override
    public void setRowIndex(int rowIndex) {
        if (rowIndex < -2) {
            throw new IllegalArgumentException();
        }
        int old = index;
        index = rowIndex;
        if (data == null) {
            return;
        }
        DataModelListener[] listeners = getDataModelListeners();
        if (old != index && listeners != null) {
            Object rowData = null;
            if (isRowAvailable()) {
                rowData = getRowData();
            }
            DataModelEvent event = new DataModelEvent(this, index, rowData);
            int n = listeners.length;
            for (int i = 0; i < n; i++) {
                if (null != listeners[i]) {
                    listeners[i].rowSelected(event);
                }
            }
        }
    }

    @Override
    public Integer getRowKey() {
        if (index == -2) {
            return index;
        }
        if (index < 0) {
            return null;
        }
        return keyMap.get(index);
    }

    @Override
    public void setRowKey(Integer key) {
        // find index for that key
        if (key != null) {
            for (Integer i : keyMap.keySet()) {
                Integer k = keyMap.get(i);
                if (key.equals(k)) {
                    setRowIndex(i);
                    break;
                }
            }
        } else {
            setRowIndex(-1);
        }
    }

    @Override
    public ListDiff getListDiff() {
        return listDiff;
    }

    @Override
    public void setListDiff(ListDiff listDiff) {
        this.listDiff = new ListDiff(listDiff);
    }

    @Override
    public boolean isDirty() {
        return listDiff != null && listDiff.isDirty();
    }

    @Override
    public void addTemplateValue() {
        addValue(getUnreferencedTemplate());
    }

    @Override
    public boolean addValue(Object value) {
        int position = data.size();
        boolean res = data.add(value);
        listDiff.add(value);
        int newRowKey = getNewRowKey();
        keyMap.put(position, newRowKey);
        return res;
    }

    @Override
    public void insertTemplateValue(int index) {
        insertValue(index, getUnreferencedTemplate());
    }

    @Override
    public void insertValue(int index, Object value) {
        if (index > data.size()) {
            // make sure enough rows are made available
            for (int i = data.size(); i < index; i++) {
                addTemplateValue();
            }
        }
        data.add(index, value);
        listDiff.insert(index, value);
        // update key map to reflect new structure
        Map<Integer, Integer> newKeyMap = new HashMap<Integer, Integer>();
        for (Integer i : keyMap.keySet()) {
            Integer key = keyMap.get(i);
            if (i >= index) {
                newKeyMap.put(i + 1, key);
            } else {
                newKeyMap.put(i, key);
            }
        }
        keyMap = newKeyMap;
        // insert new key
        int newRowKey = getNewRowKey();
        keyMap.put(index, newRowKey);
    }

    @Override
    public Object moveValue(int fromIndex, int toIndex) {
        Object old = data.remove(fromIndex);
        data.add(toIndex, old);
        listDiff.move(fromIndex, toIndex);
        // update key map to reflect new structure
        Map<Integer, Integer> newKeyMap = new HashMap<Integer, Integer>();
        if (fromIndex < toIndex) {
            for (Integer i : keyMap.keySet()) {
                Integer key = keyMap.get(i);
                if (i < fromIndex) {
                    newKeyMap.put(i, key);
                } else if (i > fromIndex && i <= toIndex) {
                    newKeyMap.put(i - 1, key);
                } else if (i > toIndex) {
                    newKeyMap.put(i, key);
                }
            }
        } else if (fromIndex > toIndex) {
            for (Integer i : keyMap.keySet()) {
                Integer key = keyMap.get(i);
                if (i < toIndex) {
                    newKeyMap.put(i, key);
                } else if (i >= toIndex && i < fromIndex) {
                    newKeyMap.put(i + 1, key);
                } else if (i > fromIndex) {
                    newKeyMap.put(i, key);
                }
            }
        }
        newKeyMap.put(toIndex, keyMap.get(fromIndex));
        keyMap = newKeyMap;
        return old;
    }

    @Override
    public Object removeValue(int index) {
        Object old = data.remove(index);
        listDiff.remove(index);
        // update key map to reflect new structure
        Map<Integer, Integer> newKeyMap = new HashMap<Integer, Integer>();
        for (Integer i : keyMap.keySet()) {
            Integer key = keyMap.get(i);
            if (i > index) {
                newKeyMap.put(i - 1, key);
            } else if (i < index) {
                newKeyMap.put(i, key);
            }
        }
        keyMap = newKeyMap;
        return old;
    }

    @Override
    public int size() {
        if (data != null) {
            return data.size();
        }
        return 0;
    }

    @Override
    public String toString() {
        final StringBuilder buf = new StringBuilder();
        buf.append(EditableModelImpl.class.getSimpleName());
        buf.append(" {");
        buf.append("originalData: ");
        buf.append(originalData);
        buf.append(", data: ");
        buf.append(data);
        buf.append(", index: ");
        buf.append(index);
        buf.append(", keyMap: ");
        buf.append(keyMap);
        buf.append(", dirty: ");
        buf.append(isDirty());
        buf.append('}');
        return buf.toString();
    }

}