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 if (data == null) { 150 this.data = null; 151 setRowIndex(-1); 152 } else { 153 this.data = (List) data; 154 index = -1; 155 setRowIndex(0); 156 for (int i = 0; i < this.data.size(); i++) { 157 keyMap.put(i, i); 158 } 159 } 160 } 161 162 // row data methods 163 164 /** 165 * Returns the initial data for the given key. 166 * <p> 167 * Returns null marker if key is invalid or data did not exist for given key in the original data. 168 */ 169 protected Object getOriginalRowDataForKey(int key) { 170 if (originalData instanceof List) { 171 List list = (List) originalData; 172 if (key < 0 || key >= list.size()) { 173 return _NULL; 174 } else { 175 // if key exists in original data, then it's equal to the 176 // index. 177 return list.get(key); 178 } 179 } else if (originalData instanceof Object[]) { 180 Object[] array = (Object[]) originalData; 181 if (key < 0 || key >= array.length) { 182 return _NULL; 183 } else { 184 // if key exists in original data, then it's equal to the 185 // index. 186 return array[key]; 187 } 188 } else { 189 return _NULL; 190 } 191 } 192 193 /** 194 * Returns a new row key that is not already used. 195 */ 196 protected int getNewRowKey() { 197 Collection<Integer> keys = keyMap.values(); 198 if (keys.isEmpty()) { 199 return 0; 200 } else { 201 List<Integer> lkeys = Arrays.asList(keys.toArray(new Integer[] {})); 202 Comparator<Integer> comp = Collections.reverseOrder(); 203 Collections.sort(lkeys, comp); 204 Integer max = lkeys.get(0); 205 return max + 1; 206 } 207 } 208 209 @Override 210 public boolean isRowAvailable() { 211 if (data == null) { 212 return false; 213 } 214 return (index == -2) || (index >= 0 && index < data.size()); 215 } 216 217 @Override 218 public boolean isRowModified() { 219 if (!isRowAvailable()) { 220 return false; 221 } else { 222 Integer rowKey = getRowKey(); 223 if (rowKey == null) { 224 return false; 225 } else { 226 Object oldData = getOriginalRowDataForKey(rowKey); 227 if (oldData == _NULL) { 228 return false; 229 } 230 Object newData = getRowData(); 231 if (newData == null && oldData == null) { 232 return false; 233 } else { 234 if (newData != null) { 235 return !newData.equals(oldData); 236 } else { 237 return !oldData.equals(newData); 238 } 239 } 240 } 241 } 242 } 243 244 @Override 245 public boolean isRowNew() { 246 if (!isRowAvailable()) { 247 return false; 248 } else { 249 Integer rowKey = getRowKey(); 250 if (rowKey == null) { 251 return false; 252 } else { 253 Object oldData = getOriginalRowDataForKey(rowKey); 254 return oldData == _NULL; 255 } 256 } 257 } 258 259 @Override 260 public void recordValueModified(int index, Object newValue) { 261 listDiff.modify(index, newValue); 262 } 263 264 @Override 265 public int getRowCount() { 266 if (data == null) { 267 return -1; 268 } 269 return data.size(); 270 } 271 272 @Override 273 public Object getRowData() { 274 if (data == null) { 275 return null; 276 } else if (!isRowAvailable()) { 277 throw new IllegalArgumentException("No row available on " + this); 278 } else { 279 if (index == -2) { 280 // XXX return template instead (?) 281 return null; 282 } 283 return data.get(index); 284 } 285 } 286 287 @Override 288 public void setRowData(Object rowData) { 289 if (isRowAvailable()) { 290 data.set(index, rowData); 291 } 292 } 293 294 @Override 295 public int getRowIndex() { 296 return index; 297 } 298 299 @Override 300 public void setRowIndex(int rowIndex) { 301 if (rowIndex < -2) { 302 throw new IllegalArgumentException(); 303 } 304 int old = index; 305 index = rowIndex; 306 if (data == null) { 307 return; 308 } 309 DataModelListener[] listeners = getDataModelListeners(); 310 if (old != index && listeners != null) { 311 Object rowData = null; 312 if (isRowAvailable()) { 313 rowData = getRowData(); 314 } 315 DataModelEvent event = new DataModelEvent(this, index, rowData); 316 int n = listeners.length; 317 for (int i = 0; i < n; i++) { 318 if (null != listeners[i]) { 319 listeners[i].rowSelected(event); 320 } 321 } 322 } 323 } 324 325 @Override 326 public Integer getRowKey() { 327 if (index == -2) { 328 return index; 329 } 330 if (index < 0) { 331 return null; 332 } 333 return keyMap.get(index); 334 } 335 336 @Override 337 public void setRowKey(Integer key) { 338 // find index for that key 339 if (key != null) { 340 for (Integer i : keyMap.keySet()) { 341 Integer k = keyMap.get(i); 342 if (key.equals(k)) { 343 setRowIndex(i); 344 break; 345 } 346 } 347 } else { 348 setRowIndex(-1); 349 } 350 } 351 352 @Override 353 public ListDiff getListDiff() { 354 return listDiff; 355 } 356 357 @Override 358 public void setListDiff(ListDiff listDiff) { 359 this.listDiff = new ListDiff(listDiff); 360 } 361 362 @Override 363 public boolean isDirty() { 364 return listDiff != null && listDiff.isDirty(); 365 } 366 367 @Override 368 public void addTemplateValue() { 369 addValue(getUnreferencedTemplate()); 370 } 371 372 @Override 373 public boolean addValue(Object value) { 374 int position = data.size(); 375 boolean res = data.add(value); 376 listDiff.add(value); 377 int newRowKey = getNewRowKey(); 378 keyMap.put(position, newRowKey); 379 return res; 380 } 381 382 @Override 383 public void insertTemplateValue(int index) { 384 insertValue(index, getUnreferencedTemplate()); 385 } 386 387 @Override 388 public void insertValue(int index, Object value) { 389 if (index > data.size()) { 390 // make sure enough rows are made available 391 for (int i = data.size(); i < index; i++) { 392 addTemplateValue(); 393 } 394 } 395 data.add(index, value); 396 listDiff.insert(index, value); 397 // update key map to reflect new structure 398 Map<Integer, Integer> newKeyMap = new HashMap<Integer, Integer>(); 399 for (Integer i : keyMap.keySet()) { 400 Integer key = keyMap.get(i); 401 if (i >= index) { 402 newKeyMap.put(i + 1, key); 403 } else { 404 newKeyMap.put(i, key); 405 } 406 } 407 keyMap = newKeyMap; 408 // insert new key 409 int newRowKey = getNewRowKey(); 410 keyMap.put(index, newRowKey); 411 } 412 413 @Override 414 public Object moveValue(int fromIndex, int toIndex) { 415 Object old = data.remove(fromIndex); 416 data.add(toIndex, old); 417 listDiff.move(fromIndex, toIndex); 418 // update key map to reflect new structure 419 Map<Integer, Integer> newKeyMap = new HashMap<Integer, Integer>(); 420 if (fromIndex < toIndex) { 421 for (Integer i : keyMap.keySet()) { 422 Integer key = keyMap.get(i); 423 if (i < fromIndex) { 424 newKeyMap.put(i, key); 425 } else if (i > fromIndex && i <= toIndex) { 426 newKeyMap.put(i - 1, key); 427 } else if (i > toIndex) { 428 newKeyMap.put(i, key); 429 } 430 } 431 } else if (fromIndex > toIndex) { 432 for (Integer i : keyMap.keySet()) { 433 Integer key = keyMap.get(i); 434 if (i < toIndex) { 435 newKeyMap.put(i, key); 436 } else if (i >= toIndex && i < fromIndex) { 437 newKeyMap.put(i + 1, key); 438 } else if (i > fromIndex) { 439 newKeyMap.put(i, key); 440 } 441 } 442 } 443 newKeyMap.put(toIndex, keyMap.get(fromIndex)); 444 keyMap = newKeyMap; 445 return old; 446 } 447 448 @Override 449 public Object removeValue(int index) { 450 Object old = data.remove(index); 451 listDiff.remove(index); 452 // update key map to reflect new structure 453 Map<Integer, Integer> newKeyMap = new HashMap<Integer, Integer>(); 454 for (Integer i : keyMap.keySet()) { 455 Integer key = keyMap.get(i); 456 if (i > index) { 457 newKeyMap.put(i - 1, key); 458 } else if (i < index) { 459 newKeyMap.put(i, key); 460 } 461 } 462 keyMap = newKeyMap; 463 return old; 464 } 465 466 @Override 467 public int size() { 468 if (data != null) { 469 return data.size(); 470 } 471 return 0; 472 } 473 474 @Override 475 public String toString() { 476 final StringBuilder buf = new StringBuilder(); 477 buf.append(EditableModelImpl.class.getSimpleName()); 478 buf.append(" {"); 479 buf.append("originalData: "); 480 buf.append(originalData); 481 buf.append(", data: "); 482 buf.append(data); 483 buf.append(", index: "); 484 buf.append(index); 485 buf.append(", keyMap: "); 486 buf.append(keyMap); 487 buf.append(", dirty: "); 488 buf.append(isDirty()); 489 buf.append('}'); 490 return buf.toString(); 491 } 492 493}