1 package org.apache.torque.om;
2
3 /*
4 * Licensed to the Apache Software Foundation (ASF) under one
5 * or more contributor license agreements. See the NOTICE file
6 * distributed with this work for additional information
7 * regarding copyright ownership. The ASF licenses this file
8 * to you under the Apache License, Version 2.0 (the
9 * "License"); you may not use this file except in compliance
10 * with the License. You may obtain a copy of the License at
11 *
12 * http://www.apache.org/licenses/LICENSE-2.0
13 *
14 * Unless required by applicable law or agreed to in writing,
15 * software distributed under the License is distributed on an
16 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17 * KIND, either express or implied. See the License for the
18 * specific language governing permissions and limitations
19 * under the License.
20 */
21
22 import java.sql.Types;
23 import java.util.ArrayList;
24 import java.util.List;
25
26 import org.apache.commons.lang.ObjectUtils;
27
28 /**
29 * This class can be used as an ObjectKey to uniquely identify an
30 * object within an application where the key consists of multiple
31 * entities (such a String[] representing a multi-column primary key).
32 *
33 * @author <a href="mailto:jmcnally@collab.net">John McNally</a>
34 * @author <a href="mailto:dlr@collab.net">Daniel Rall</a>
35 * @author <a href="mailto:drfish@cox.net">J. Russell Smyth</a>
36 * @version $Id: ComboKey.java 1351125 2012-06-17 16:51:03Z tv $
37 */
38 public class ComboKey extends ObjectKey
39 {
40 /**
41 * Serial version
42 */
43 private static final long serialVersionUID = -264927663211141894L;
44
45 // might want to shift these to TR.props
46
47 /** The single character used to separate key values in a string. */
48 public static final char SEPARATOR = ':';
49
50 /** The single character used to separate key values in a string. */
51 public static final String SEPARATOR_STRING = ":";
52
53 /** The array of the keys */
54 private SimpleKey[] key;
55
56 /**
57 * Creates an ComboKey whose internal representation will be
58 * set later, through a set method
59 */
60 public ComboKey()
61 {
62 // empty
63 }
64
65 /**
66 * Creates a ComboKey whose internal representation is an
67 * array of SimpleKeys.
68 *
69 * @param keys the key values
70 */
71 public ComboKey(SimpleKey[] keys)
72 {
73 setValue(keys);
74 }
75
76 /**
77 * Sets the internal representation to a String array.
78 *
79 * @param keys the key values
80 * @see #toString()
81 */
82 public ComboKey(String keys)
83 {
84 setValue(keys);
85 }
86
87 /**
88 * Sets the internal representation using a SimpleKey array.
89 *
90 * @param keys the key values
91 */
92 public void setValue(SimpleKey[] keys)
93 {
94 this.key = keys;
95 }
96
97 /**
98 * Sets the internal representation using a String of the
99 * form produced by the toString method.
100 *
101 * @param keys the key values
102 */
103 public void setValue(String keys)
104 {
105 int startPtr = 0;
106 int indexOfSep = keys.indexOf(SEPARATOR);
107 List<SimpleKey> tmpKeys = new ArrayList<SimpleKey>();
108 while (indexOfSep != -1)
109 {
110 if (indexOfSep == startPtr)
111 {
112 tmpKeys.add(null);
113 }
114 else
115 {
116 char keyType = keys.charAt(startPtr);
117 String keyString = keys.substring(startPtr + 1, indexOfSep);
118
119 SimpleKey newKey = null;
120 switch(keyType)
121 {
122 case 'N':
123 newKey = new NumberKey(keyString);
124 break;
125 case 'S':
126 newKey = new StringKey(keyString);
127 break;
128 case 'D':
129 try
130 {
131 newKey = new DateKey(keyString);
132 }
133 catch (NumberFormatException nfe)
134 {
135 newKey = new DateKey();
136 }
137 break;
138 default:
139 // unextepcted key type
140 }
141 tmpKeys.add(newKey);
142 }
143 startPtr = indexOfSep + 1;
144 indexOfSep = keys.indexOf(SEPARATOR, startPtr);
145 }
146
147 this.key = new SimpleKey[tmpKeys.size()];
148 for (int i = 0; i < this.key.length; i++)
149 {
150 this.key[i] = tmpKeys.get(i);
151 }
152 }
153
154 /**
155 * Sets the internal representation using a ComboKey.
156 *
157 * @param keys the key values
158 */
159 public void setValue(ComboKey keys)
160 {
161 setValue((SimpleKey[]) keys.getValue());
162 }
163
164 /**
165 * Get the underlying object.
166 *
167 * @return the underlying object
168 */
169 public Object getValue()
170 {
171 return key;
172 }
173
174 /**
175 * Returns the JDBC type of the key
176 * as defined in <code>java.sql.Types</code>.
177 *
178 * @return <code>Types.ARRAY</code>.
179 */
180 public int getJdbcType()
181 {
182 return Types.ARRAY;
183 }
184
185 /**
186 * This method will return true if the conditions for a looseEquals
187 * are met and in addition no parts of the keys are null.
188 *
189 * @param keyObj the comparison value
190 * @return whether the two objects are equal
191 */
192 public boolean equals(Object keyObj)
193 {
194 boolean isEqual = false;
195
196 if (key != null)
197 {
198 // check that all keys are not null
199 isEqual = true;
200 SimpleKey[] keys = key;
201 for (int i = 0; i < keys.length && isEqual; i++)
202 {
203 isEqual &= keys[i] != null && keys[i].getValue() != null;
204 }
205
206 isEqual &= looseEquals(keyObj);
207 }
208
209 return isEqual;
210 }
211
212 /**
213 * keyObj is equal to this ComboKey if keyObj is a ComboKey, String,
214 * ObjectKey[], or String[] that contains the same information this key
215 * contains.
216 * For example A String[] might be equal to this key, if this key was
217 * instantiated with a String[] and the arrays contain equal Strings.
218 * Another example, would be if keyObj is an ComboKey that was
219 * instantiated with a ObjectKey[] and this ComboKey was instantiated with
220 * a String[], but the ObjectKeys in the ObjectKey[] were instantiated
221 * with Strings that equal the Strings in this KeyObject's String[]
222 * This method is not as strict as the equals method which does not
223 * allow any null keys parts, while the internal key may not be null
224 * portions may be, and the two object will be considered equal if
225 * their null portions match.
226 *
227 * @param keyObj the comparison value
228 * @return whether the two objects are equal
229 */
230 public boolean looseEquals(Object keyObj)
231 {
232 boolean isEqual = false;
233
234 if (key != null)
235 {
236 // Checks a compound key (ObjectKey[] or String[]
237 // based) with the delimited String created by the
238 // toString() method. Slightly expensive, but should be less
239 // than parsing the String into its constituents.
240 if (keyObj instanceof String)
241 {
242 isEqual = toString().equals(keyObj);
243 }
244 // check against a ObjectKey. Two keys are equal, if their
245 // internal keys equivalent.
246 else if (keyObj instanceof ComboKey)
247 {
248 SimpleKey[] obj = (SimpleKey[])
249 ((ComboKey) keyObj).getValue();
250
251 SimpleKey[] keys1 = key;
252 SimpleKey[] keys2 = obj;
253 isEqual = keys1.length == keys2.length;
254 for (int i = 0; i < keys1.length && isEqual; i++)
255 {
256 isEqual &= ObjectUtils.equals(keys1[i], keys2[i]);
257 }
258 }
259 else if (keyObj instanceof SimpleKey[])
260 {
261 SimpleKey[] keys1 = key;
262 SimpleKey[] keys2 = (SimpleKey[]) keyObj;
263 isEqual = keys1.length == keys2.length;
264 for (int i = 0; i < keys1.length && isEqual; i++)
265 {
266 isEqual &= ObjectUtils.equals(keys1[i], keys2[i]);
267 }
268 }
269 }
270 return isEqual;
271 }
272
273 /**
274 *
275 * @param sb the StringBuffer to append
276 * @see #toString()
277 */
278 public void appendTo(StringBuffer sb)
279 {
280 if (key != null)
281 {
282 SimpleKey[] keys = key;
283 for (int i = 0; i < keys.length; i++)
284 {
285 if (keys[i] != null)
286 {
287 if (keys[i] instanceof StringKey)
288 {
289 sb.append("S");
290 }
291 else if (keys[i] instanceof NumberKey)
292 {
293 sb.append("N");
294 }
295 else if (keys[i] instanceof DateKey)
296 {
297 sb.append("D");
298 }
299 else
300 {
301 // unknown type
302 sb.append("U");
303 }
304 keys[i].appendTo(sb);
305 }
306 // MUST BE ADDED AFTER EACH KEY, IN CASE OF NULL KEY!
307 sb.append(SEPARATOR);
308 }
309 }
310 }
311
312 /**
313 * if the underlying key array is not null and the first element is
314 * not null this method returns the hashcode of the first element
315 * in the key. Otherwise calls ObjectKey.hashCode()
316 *
317 * @return an <code>int</code> value
318 */
319 public int hashCode()
320 {
321 if (key == null)
322 {
323 return super.hashCode();
324 }
325
326 SimpleKey sk = key[0];
327 if (sk == null)
328 {
329 return super.hashCode();
330 }
331
332 return sk.hashCode();
333 }
334
335 /**
336 * A String that may consist of one section or multiple sections
337 * separated by a colon. <br/>
338 * Each Key is represented by <code>[type N|S|D][value][:]</code>. <p/>
339 * Example: <br/>
340 * the ComboKey(StringKey("key1"), NumberKey(2)) is represented as
341 * <code><b>Skey1:N2:</b></code>
342 *
343 * @return a String representation
344 */
345 public String toString()
346 {
347 StringBuffer sbuf = new StringBuffer();
348 appendTo(sbuf);
349 return sbuf.toString();
350 }
351 }