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}