001/* 002 * (C) Copyright 2006-2011 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 * Florent Guillaume 018 */ 019package org.nuxeo.ecm.core.storage.sql; 020 021import java.io.Serializable; 022import java.util.ArrayList; 023import java.util.Calendar; 024import java.util.List; 025import java.util.Map; 026import java.util.Map.Entry; 027 028import org.nuxeo.ecm.core.api.model.Delta; 029 030/** 031 * 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 032 * array of Serializable). 033 * <p> 034 * The id of the row is distinguished internally from other columns. For fragments corresponding to created data, the 035 * initial id is a temporary one, and it will be changed after database insert. 036 */ 037public final class Row extends RowId implements Serializable, Cloneable { 038 039 private static final long serialVersionUID = 1L; 040 041 private static final int DEFAULT = 5; 042 043 private enum OpaqueValue { 044 OPAQUE_VALUE 045 } 046 047 /** 048 * A database value we don't care about reading. When present in a fragment, it won't be written, but any other 049 * value will be. 050 */ 051 public static final Serializable OPAQUE = OpaqueValue.OPAQUE_VALUE; 052 053 /** 054 * The row keys, for single row. 055 */ 056 protected String[] keys; 057 058 /** 059 * The row values. 060 */ 061 public Serializable[] values; 062 063 /** 064 * The size of the allocated part of {@link #values}, for single rows. 065 */ 066 protected int size; 067 068 /** Copy constructor. */ 069 private Row(Row row) { 070 super(row); 071 keys = row.keys == null ? null : row.keys.clone(); 072 values = row.values == null ? null : row.values.clone(); 073 size = row.size; 074 } 075 076 @Override 077 public Row clone() { 078 return new Row(this); 079 } 080 081 /** 082 * Constructs an empty {@link Row} for the given table with the given id (may be {@code null}). 083 */ 084 public Row(String tableName, Serializable id) { 085 super(tableName, id); 086 keys = new String[DEFAULT]; 087 values = new Serializable[DEFAULT]; 088 // size = 0; 089 } 090 091 /** 092 * Constructs a new {@link Row} from a map. 093 * 094 * @param map the initial data to use 095 */ 096 public Row(String tableName, Map<String, Serializable> map) { 097 super(tableName, null); // id set through map 098 keys = new String[map.size()]; 099 values = new Serializable[map.size()]; 100 // size = 0; 101 for (Entry<String, Serializable> entry : map.entrySet()) { 102 putNew(entry.getKey(), entry.getValue()); 103 } 104 } 105 106 /** 107 * Constructs a new {@link Row} from an array of values. 108 * 109 * @param array the initial data to use 110 */ 111 public Row(String tableName, Serializable id, Serializable[] array) { 112 super(tableName, id); 113 values = array.clone(); 114 keys = null; 115 size = -1; 116 } 117 118 public boolean isCollection() { 119 return size == -1; 120 } 121 122 private void ensureCapacity(int minCapacity) { 123 if (minCapacity > values.length) { 124 Serializable[] k = keys; 125 Serializable[] d = values; 126 int newCapacity = (values.length * 3) / 2 + 1; 127 if (newCapacity < minCapacity) { 128 newCapacity = minCapacity; 129 } 130 keys = new String[newCapacity]; 131 values = new Serializable[newCapacity]; 132 System.arraycopy(d, 0, values, 0, size); 133 System.arraycopy(k, 0, keys, 0, size); 134 } 135 } 136 137 /** 138 * Puts a key/value. 139 * 140 * @param key the key 141 * @param value the value 142 */ 143 public void put(String key, Serializable value) { 144 if (key.equals(Model.MAIN_KEY)) { 145 id = value; 146 return; 147 } 148 // linear search but the array is small 149 for (int i = 0; i < size; i++) { 150 if (key.equals(keys[i])) { 151 Serializable oldValue = values[i]; 152 if (oldValue instanceof Delta) { 153 Delta oldDelta = (Delta) oldValue; 154 if (value instanceof Delta) { 155 if (value != oldDelta) { 156 // add a delta to another delta 157 value = oldDelta.add((Delta) value); 158 } 159 } else if (oldDelta.getFullValue().equals(value)) { 160 // don't overwrite a delta with the full value 161 // that actually comes from it 162 return; 163 } 164 } 165 values[i] = value; 166 return; 167 } 168 } 169 ensureCapacity(size + 1); 170 keys[size] = key == null ? null: key.intern(); 171 values[size++] = value; 172 } 173 174 /** 175 * Puts a key/value, assuming the key is not already there. 176 * 177 * @param key the key 178 * @param value the value 179 */ 180 public void putNew(String key, Serializable value) { 181 if (key.equals(Model.MAIN_KEY)) { 182 id = value; 183 return; 184 } 185 ensureCapacity(size + 1); 186 keys[size] = key == null ? null : key.intern(); 187 values[size++] = value; 188 } 189 190 /** 191 * Gets a value from a key. 192 * 193 * @param key the key 194 * @return the value 195 */ 196 public Serializable get(String key) { 197 if (key.equals(Model.MAIN_KEY)) { 198 return id; 199 } 200 // linear search but the array is small 201 for (int i = 0; i < size; i++) { 202 if (key.equals(keys[i])) { 203 return values[i]; 204 } 205 } 206 return null; 207 } 208 209 /** 210 * Gets the list of keys. The id is not included. 211 */ 212 public List<String> getKeys() { 213 List<String> list = new ArrayList<String>(size); 214 for (int i = 0; i < size; i++) { 215 list.add(keys[i]); 216 } 217 return list; 218 } 219 220 /** 221 * Gets the list of values. The id is not included. 222 */ 223 public List<Serializable> getValues() { 224 List<Serializable> list = new ArrayList<Serializable>(size); 225 for (int i = 0; i < size; i++) { 226 list.add(values[i]); 227 } 228 return list; 229 } 230 231 @Override 232 public String toString() { 233 StringBuilder buf = new StringBuilder(); 234 buf.append(getClass().getSimpleName()); 235 buf.append('('); 236 buf.append(tableName); 237 buf.append(", "); 238 buf.append(id); 239 if (size != -1) { 240 // single row 241 buf.append(", {"); 242 for (int i = 0; i < size; i++) { 243 if (i > 0) { 244 buf.append(", "); 245 } 246 buf.append(keys[i]); 247 buf.append('='); 248 printValue(values[i], buf); 249 } 250 buf.append('}'); 251 } else { 252 // multiple rows 253 buf.append(", ["); 254 for (int i = 0; i < values.length; i++) { 255 if (i > 0) { 256 buf.append(", "); 257 } 258 printValue(values[i], buf); 259 } 260 buf.append(']'); 261 } 262 buf.append(')'); 263 return buf.toString(); 264 } 265 266 public static final int MAX_STRING = 100; 267 268 public static final int MAX_ARRAY = 10; 269 270 @SuppressWarnings("boxing") 271 public static void printValue(Serializable value, StringBuilder buf) { 272 if (value == null) { 273 buf.append("NULL"); 274 } else if (value instanceof String) { 275 String v = (String) value; 276 if (v.length() > MAX_STRING) { 277 v = v.substring(0, MAX_STRING) + "...(" + v.length() + " chars)..."; 278 } 279 buf.append('"'); 280 buf.append(v); 281 buf.append('"'); 282 } else if (value instanceof Calendar) { 283 Calendar cal = (Calendar) value; 284 char sign; 285 int offset = cal.getTimeZone().getOffset(cal.getTimeInMillis()) / 60000; 286 if (offset < 0) { 287 offset = -offset; 288 sign = '-'; 289 } else { 290 sign = '+'; 291 } 292 buf.append(String.format("Calendar(%04d-%02d-%02dT%02d:%02d:%02d.%03d%c%02d:%02d)", cal.get(Calendar.YEAR), // 293 cal.get(Calendar.MONTH) + 1, // 294 cal.get(Calendar.DAY_OF_MONTH), // 295 cal.get(Calendar.HOUR_OF_DAY), // 296 cal.get(Calendar.MINUTE), // 297 cal.get(Calendar.SECOND), // 298 cal.get(Calendar.MILLISECOND), // 299 sign, offset / 60, offset % 60)); 300 } else if (value.getClass().isArray()) { 301 Serializable[] v = (Serializable[]) value; 302 buf.append('['); 303 for (int i = 0; i < v.length; i++) { 304 if (i > 0) { 305 buf.append(','); 306 if (i > MAX_ARRAY) { 307 buf.append("...("); 308 buf.append(v.length); 309 buf.append(" items)..."); 310 break; 311 } 312 } 313 printValue(v[i], buf); 314 } 315 buf.append(']'); 316 } else { 317 buf.append(value.toString()); 318 } 319 } 320 321}