001/* 002 * (C) Copyright 2007 Nuxeo SA (http://nuxeo.com/) and others. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 * 016 * Contributors: 017 * Nuxeo - initial API and implementation 018 * 019 * $Id: EditableModelImpl.java 25559 2007-10-01 12:48:23Z atchertchian $ 020 */ 021 022package org.nuxeo.ecm.platform.ui.web.model.impl; 023 024import java.io.ByteArrayInputStream; 025import java.io.ByteArrayOutputStream; 026import java.io.IOException; 027import java.io.InputStream; 028import java.io.ObjectInputStream; 029import java.io.ObjectOutputStream; 030import java.io.Serializable; 031import java.util.ArrayList; 032import java.util.Arrays; 033import java.util.Collection; 034import java.util.Collections; 035import java.util.Comparator; 036import java.util.HashMap; 037import java.util.List; 038import java.util.Map; 039 040import javax.faces.model.DataModel; 041import javax.faces.model.DataModelEvent; 042import javax.faces.model.DataModelListener; 043 044import org.apache.commons.logging.Log; 045import org.apache.commons.logging.LogFactory; 046import org.nuxeo.ecm.core.api.ListDiff; 047import org.nuxeo.ecm.platform.ui.web.model.EditableModel; 048import org.nuxeo.ecm.platform.ui.web.util.DeepCopy; 049 050/** 051 * Editable data model that handles value changes. 052 * <p> 053 * Only accepts lists or arrays of Serializable objects for now. 054 * 055 * @author <a href="mailto:at@nuxeo.com">Anahide Tchertchian</a> 056 */ 057@SuppressWarnings({ "unchecked", "rawtypes" }) 058public class EditableModelImpl extends DataModel implements EditableModel, Serializable { 059 060 private static final long serialVersionUID = 2550850486035521538L; 061 062 private static final Log log = LogFactory.getLog(EditableModelImpl.class); 063 064 // use this key to indicate unset values 065 private static final Object _NULL = new Object(); 066 067 protected final Object originalData; 068 069 // current data list 070 protected List data; 071 072 // current row index (zero relative) 073 protected int index = -1; 074 075 // XXX AT: not thread safe (?) 076 protected Map<Integer, Integer> keyMap; 077 078 protected ListDiff listDiff; 079 080 protected Object template; 081 082 public EditableModelImpl(Object value, Object template) { 083 if (value != null) { 084 if (!(value instanceof List) && !(value instanceof Object[])) { 085 log.error("Cannot build editable model from " + value + ", list or array needed"); 086 value = null; 087 } 088 } 089 originalData = value; 090 listDiff = new ListDiff(); 091 keyMap = new HashMap<Integer, Integer>(); 092 initializeData(value); 093 this.template = template; 094 } 095 096 protected void initializeData(Object originalData) { 097 List data = null; 098 if (originalData == null) { 099 data = new ArrayList<Object>(); 100 } else if (originalData instanceof Object[]) { 101 data = new ArrayList<Object>(); 102 for (Object item : (Object[]) originalData) { 103 data.add(DeepCopy.deepCopy(item)); 104 } 105 } else if (originalData instanceof List) { 106 data = new ArrayList<Object>(); 107 data.addAll((List) DeepCopy.deepCopy(originalData)); 108 } 109 setWrappedData(data); 110 } 111 112 @Override 113 public Object getUnreferencedTemplate() { 114 if (template == null) { 115 return null; 116 } 117 if (template instanceof Serializable) { 118 try { 119 Serializable serializableTemplate = (Serializable) template; 120 ByteArrayOutputStream out = new ByteArrayOutputStream(); 121 ObjectOutputStream oos = new ObjectOutputStream(out); 122 oos.writeObject(serializableTemplate); 123 oos.close(); 124 // deserialize to make sure it is not the same instance 125 byte[] pickled = out.toByteArray(); 126 InputStream in = new ByteArrayInputStream(pickled); 127 ObjectInputStream ois = new ObjectInputStream(in); 128 Object newTemplate = ois.readObject(); 129 return newTemplate; 130 } catch (IOException | ClassNotFoundException e) { 131 throw new RuntimeException(e); 132 } 133 } else { 134 log.warn("Template is not serializable, cannot clone " + "to add unreferenced value into model."); 135 return template; 136 } 137 } 138 139 @Override 140 public Object getOriginalData() { 141 return originalData; 142 } 143 144 @Override 145 public Object getWrappedData() { 146 return data; 147 } 148 149 @Override 150 public void setWrappedData(Object data) { 151 index = -1; 152 if (data == null) { 153 this.data = null; 154 } else { 155 this.data = (List) data; 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}