API  1.0.0
CPComparisonPredicate.j
Go to the documentation of this file.
1 /*
2  * CPComparisonPredicate.j
3  *
4  * Portions based on NSComparisonPredicate.m in Cocotron (http://www.cocotron.org/)
5  * Copyright (c) 2006-2007 Christopher J. W. Lloyd
6  *
7  * Created by cacaodev.
8  * Copyright 2010.
9  *
10  * This library is free software; you can redistribute it and/or
11  * modify it under the terms of the GNU Lesser General Public
12  * License as published by the Free Software Foundation; either
13  * version 2.1 of the License, or (at your option) any later version.
14  *
15  * This library is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18  * Lesser General Public License for more details.
19  *
20  * You should have received a copy of the GNU Lesser General Public
21  * License along with this library; if not, write to the Free Software
22  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
23  */
24 
25 
26 
27 
28 @typedef CPComparisonPredicateModifier
29 @typedef CPPredicateOperatorType
30 
37 @implementation CPComparisonPredicate : CPPredicate
38 {
39  CPExpression _left;
40  CPExpression _right;
41 
42  CPComparisonPredicateModifier _modifier;
43  CPPredicateOperatorType _type;
44  unsigned int _options;
45  SEL _customSelector;
46 }
47 
48 // Constructors
56 + (CPPredicate)predicateWithLeftExpression:(CPExpression)left rightExpression:(CPExpression)right customSelector:(SEL)selector
57 {
58  return [[self alloc] initWithLeftExpression:left rightExpression:right customSelector:selector];
59 }
60 
70 + (CPPredicate)predicateWithLeftExpression:(CPExpression)left rightExpression:(CPExpression)right modifier:(CPComparisonPredicateModifier)modifier type:(int)type options:(unsigned)options
71 {
72  return [[self alloc] initWithLeftExpression:left rightExpression:right modifier:modifier type:type options:options];
73 }
74 
82 - (id)initWithLeftExpression:(CPExpression)left rightExpression:(CPExpression)right customSelector:(SEL)selector
83 {
84  self = [super init];
85 
86  if (self)
87  {
88  _left = left;
89  _right = right;
90  _modifier = CPDirectPredicateModifier;
92  _options = 0;
93  _customSelector = selector;
94  }
95 
96  return self;
97 }
98 
108 - (id)initWithLeftExpression:(CPExpression)left rightExpression:(CPExpression)right modifier:(CPComparisonPredicateModifier)modifier type:(CPPredicateOperatorType)type options:(unsigned)options
109 {
110  self = [super init];
111 
112  if (self)
113  {
114  _left = left;
115  _right = right;
116  _modifier = modifier;
117  _type = type;
118  _options = (type != CPMatchesPredicateOperatorType &&
119  type != CPLikePredicateOperatorType &&
122  type != CPInPredicateOperatorType &&
123  type != CPContainsPredicateOperatorType) ? 0 : options;
124 
125  _customSelector = NULL;
126  }
127 
128  return self;
129 }
130 
131 // Getting Information About a Comparison Predicate
136 - (CPComparisonPredicateModifier)comparisonPredicateModifier
137 {
138  return _modifier;
139 }
140 
145 - (SEL)customSelector
146 {
147  return _customSelector;
148 }
149 
154 - (CPExpression)leftExpression
155 {
156  return _left;
157 }
158 
163 - (unsigned)options
164 {
165  return _options;
166 }
167 
172 - (CPPredicateOperatorType)predicateOperatorType
173 {
174  return _type;
175 }
176 
181 - (CPExpression)rightExpression
182 {
183  return _right;
184 }
185 
186 - (CPString)predicateFormat
187 {
188  var modifier;
189 
190  switch (_modifier)
191  {
192  case CPDirectPredicateModifier: modifier = "";
193  break;
194  case CPAllPredicateModifier: modifier = "ALL ";
195  break;
196  case CPAnyPredicateModifier: modifier = "ANY ";
197  break;
198 
199  default: modifier = "";
200  break;
201  }
202 
203  var options;
204 
205  switch (_options)
206  {
207  case CPCaseInsensitivePredicateOption: options = "[c]";
208  break;
209  case CPDiacriticInsensitivePredicateOption: options = "[d]";
210  break;
212  options = "[cd]";
213  break;
214 
215  default: options = "";
216  break;
217  }
218 
219  var operator;
220 
221  switch (_type)
222  {
223  case CPLessThanPredicateOperatorType: operator = "<";
224  break;
225  case CPLessThanOrEqualToPredicateOperatorType: operator = "<=";
226  break;
227  case CPGreaterThanPredicateOperatorType: operator = ">";
228  break;
229  case CPGreaterThanOrEqualToPredicateOperatorType: operator = ">=";
230  break;
231  case CPEqualToPredicateOperatorType: operator = "==";
232  break;
233  case CPNotEqualToPredicateOperatorType: operator = "!=";
234  break;
235  case CPMatchesPredicateOperatorType: operator = "MATCHES";
236  break;
237  case CPLikePredicateOperatorType: operator = "LIKE";
238  break;
239  case CPBeginsWithPredicateOperatorType: operator = "BEGINSWITH";
240  break;
241  case CPEndsWithPredicateOperatorType: operator = "ENDSWITH";
242  break;
243  case CPInPredicateOperatorType: operator = "IN";
244  break;
245  case CPContainsPredicateOperatorType: operator = "CONTAINS";
246  break;
247  case CPCustomSelectorPredicateOperatorType: operator = CPStringFromSelector(_customSelector);
248  break;
249  }
250 
251  return [CPString stringWithFormat:@"%s%s %s%s %s",modifier,[_left description],operator,options,[_right description]];
252 }
253 
254 - (CPPredicate)predicateWithSubstitutionVariables:(CPDictionary)variables
255 {
256  var left = [_left _expressionWithSubstitutionVariables:variables],
257  right = [_right _expressionWithSubstitutionVariables:variables];
258 
260  return [CPComparisonPredicate predicateWithLeftExpression:left rightExpression:right modifier:_modifier type:_type options:_options];
261  else
263 }
264 
265 - (BOOL)isEqual:(id)anObject
266 {
267  if (self === anObject)
268  return YES;
269 
270  if (anObject === nil || anObject.isa !== self.isa || _modifier !== [anObject comparisonPredicateModifier] || _type !== [anObject predicateOperatorType] || _options !== [anObject options] || _customSelector !== [anObject customSelector] || ![_left isEqual:[anObject leftExpression]] || ![_right isEqual:[anObject rightExpression]])
271  return NO;
272 
273  return YES;
274 }
275 
276 - (BOOL)_evaluateValue:lhs rightValue:rhs
277 {
278  var leftIsNil = (lhs == nil || [lhs isEqual:[CPNull null]]),
279  rightIsNil = (rhs == nil || [rhs isEqual:[CPNull null]]);
280 
281  if ((leftIsNil || rightIsNil) && _type != CPCustomSelectorPredicateOperatorType)
282  return (leftIsNil == rightIsNil &&
283  (_type == CPEqualToPredicateOperatorType ||
286 
287  var string_compare_options = 0;
288 
289  // left and right should be casted first [CAST()] following 10.5 rules.
290  switch (_type)
291  {
292  case CPLessThanPredicateOperatorType: return ([lhs compare:rhs] == CPOrderedAscending);
293  case CPLessThanOrEqualToPredicateOperatorType: return ([lhs compare:rhs] != CPOrderedDescending);
294  case CPGreaterThanPredicateOperatorType: return ([lhs compare:rhs] == CPOrderedDescending);
295  case CPGreaterThanOrEqualToPredicateOperatorType: return ([lhs compare:rhs] != CPOrderedAscending);
296  case CPEqualToPredicateOperatorType: return [lhs isEqual:rhs];
297  case CPNotEqualToPredicateOperatorType: return (![lhs isEqual:rhs]);
298 
299  case CPMatchesPredicateOperatorType: var commut = (_options & CPCaseInsensitivePredicateOption) ? "gi":"g";
301  {
302  lhs = lhs.stripDiacritics();
303  rhs = rhs.stripDiacritics();
304  }
305  return (new RegExp(rhs,commut)).test(lhs);
306 
308  {
309  lhs = lhs.stripDiacritics();
310  rhs = rhs.stripDiacritics();
311  }
312  var commut = (_options & CPCaseInsensitivePredicateOption) ? "gi":"g",
313  reg = new RegExp(rhs.escapeForRegExp(),commut);
314  return reg.test(lhs);
315 
316  case CPBeginsWithPredicateOperatorType: var range = CPMakeRange(0, MIN([lhs length], [rhs length]));
317  if (_options & CPCaseInsensitivePredicateOption)
318  string_compare_options |= CPCaseInsensitiveSearch;
320  string_compare_options |= CPDiacriticInsensitiveSearch;
321  return ([lhs compare:rhs options:string_compare_options range:range] == CPOrderedSame);
322 
323  case CPEndsWithPredicateOperatorType: var range = CPMakeRange(MAX([lhs length] - [rhs length], 0), MIN([lhs length], [rhs length]));
324  if (_options & CPCaseInsensitivePredicateOption)
325  string_compare_options |= CPCaseInsensitiveSearch;
327  string_compare_options |= CPDiacriticInsensitiveSearch;
328  return ([lhs compare:rhs options:string_compare_options range:range] == CPOrderedSame);
329 
330  case CPCustomSelectorPredicateOperatorType: return [lhs performSelector:_customSelector withObject:rhs];
331 
332  case CPInPredicateOperatorType: var a = lhs; // swap
333  lhs = rhs;
334  rhs = a;
335  case CPContainsPredicateOperatorType: if (![lhs isKindOfClass:[CPString class]])
336  {
337  if (![lhs respondsToSelector: @selector(objectEnumerator)])
338  [CPException raise:CPInvalidArgumentException reason:@"The left/right hand side for a CONTAINS/IN operator must be a collection or a string"];
339 
340  return [lhs containsObject:rhs];
341  }
342 
343  if (_options & CPCaseInsensitivePredicateOption)
344  string_compare_options |= CPCaseInsensitiveSearch;
346  string_compare_options |= CPDiacriticInsensitiveSearch;
347 
348  return ([lhs rangeOfString:rhs options:string_compare_options].location != CPNotFound);
349 
350  case CPBetweenPredicateOperatorType: if ([rhs count] < 2)
351  [CPException raise:CPInvalidArgumentException reason:@"The right hand side for a BETWEEN operator must contain 2 objects"];
352 
353  return ([lhs compare:rhs[0]] == CPOrderedDescending && [lhs compare:rhs[1]] == CPOrderedAscending);
354 
355  default: return NO;
356  }
357 }
358 
359 - (BOOL)evaluateWithObject:(id)object
360 {
361  return [self evaluateWithObject:object substitutionVariables:nil];
362 }
363 
364 - (BOOL)evaluateWithObject:(id)object substitutionVariables:(CPDictionary)variables
365 {
366  var leftValue = [_left expressionValueWithObject:object context:variables],
367  rightValue = [_right expressionValueWithObject:object context:variables];
368 
369  leftValue = (typeof leftValue == "boolean") ? [CPNumber numberWithBool:leftValue] : leftValue;
370  rightValue = (typeof rightValue == "boolean") ? [CPNumber numberWithBool:rightValue] : rightValue;
371 
372  if (_modifier == CPDirectPredicateModifier)
373  return [self _evaluateValue:leftValue rightValue:rightValue];
374  else
375  {
376  if (![leftValue respondsToSelector:@selector(objectEnumerator)])
377  [CPException raise:CPInvalidArgumentException reason:@"The left hand side for an ALL or ANY operator must be either a CPArray or a CPSet"];
378 
379  var e = [leftValue objectEnumerator],
380  result = (_modifier == CPAllPredicateModifier),
381  value;
382 
383  while ((value = [e nextObject]) !== nil)
384  {
385  var eval = [self _evaluateValue:value rightValue:rightValue];
386 
387  if (eval != result)
388  return eval;
389  }
390 
391  return result;
392  }
393 }
394 
395 @end
396 
398 
399 - (id)initWithCoder:(CPCoder)coder
400 {
401  self = [super init];
402  if (self != nil)
403  {
404  _left = [coder decodeObjectForKey:@"CPComparisonPredicateLeftExpression"];
405  _right = [coder decodeObjectForKey:@"CPComparisonPredicateRightExpression"];
406  _modifier = [coder decodeIntForKey:@"CPComparisonPredicateModifier"];
407  _type = [coder decodeIntForKey:@"CPComparisonPredicateType"];
408  _options = [coder decodeIntForKey:@"CPComparisonPredicateOptions"];
409  _customSelector = [coder decodeObjectForKey:@"CPComparisonPredicateCustomSelector"];
410  }
411 
412  return self;
413 }
414 
415 - (void)encodeWithCoder:(CPCoder)coder
416 {
417  [coder encodeObject:_left forKey:@"CPComparisonPredicateLeftExpression"];
418  [coder encodeObject:_right forKey:@"CPComparisonPredicateRightExpression"];
419  [coder encodeInt:_modifier forKey:@"CPComparisonPredicateModifier"];
420  [coder encodeInt:_type forKey:@"CPComparisonPredicateType"];
421  [coder encodeInt:_options forKey:@"CPComparisonPredicateOptions"];
422  [coder encodeObject:_customSelector forKey:@"CPComparisonPredicateCustomSelector"];
423 }
424 
425 @end
426 
427 var source = ['*','?','(',')','{','}','.','+','|','/',','^'],
428  dest = ['.*','.?','\\(','\\)','\\{','\\}','\\.','\\+','\\|','\\/','\\$','\\^'];
429 
430 String.prototype.escapeForRegExp = function()
431 {
432  var foundChar = false,
433  i = 0;
434 
435  for (; i < source.length; ++i)
436  {
437  if (this.indexOf(source[i]) !== -1)
438  {
439  foundChar = true;
440  break;
441  }
442  }
443 
444  if (!foundChar)
445  return this;
446 
447  var result = "";
448 
449  for (i = 0; i < this.length; ++i)
450  {
451  var sourceIndex = source.indexOf(this.charAt(i));
452  if (sourceIndex !== -1)
453  result += dest[sourceIndex];
454  else
455  result += this.charAt(i);
456  }
457 
458  return result;
459 };
Used to implement exception handling (creating & raising).
Definition: CPException.h:2
CPOrderedAscending
Definition: CPObjJRuntime.j:48
var isEqual
An object representation of nil.
Definition: CPNull.h:2
CPOrderedSame
Definition: CPObjJRuntime.j:54
void raise:reason:(CPString aName, [reason] CPString aReason)
Definition: CPException.j:66
BOOL evaluateWithObject:substitutionVariables:(id object, [substitutionVariables] CPDictionary variables)
CPInvalidArgumentException
Definition: CPException.j:25
A mutable key-value pair collection.
Definition: CPDictionary.h:2
An immutable string (collection of characters).
Definition: CPString.h:2
CPNull null()
Definition: CPNull.j:51
CPOrderedDescending
Definition: CPObjJRuntime.j:60
CPPredicate predicateWithLeftExpression:rightExpression:customSelector:(CPExpression left, [rightExpression] CPExpression right, [customSelector] SEL selector)
CPDiacriticInsensitivePredicateOption
Defines methods for use when archiving & restoring (enc/decoding).
Definition: CPCoder.h:2
CPNotFound
Definition: CPObjJRuntime.j:62
CPComparisonPredicate is a subclass of CPPredicate used to compare expressions.
global CPInvalidArgumentException global CPRangeException CPCaseInsensitiveSearch
Definition: CPString.j:32
CPPredicate predicateWithLeftExpression:rightExpression:modifier:type:options:(CPExpression left, [rightExpression] CPExpression right, [modifier] CPComparisonPredicateModifier modifier, [type] int type, [options] unsigned options)
CPLessThanOrEqualToPredicateOperatorType
function CPStringFromSelector(aSelector)
Definition: CPObjJRuntime.j:23
var source
CPGreaterThanOrEqualToPredicateOperatorType
A bridged object to native Javascript numbers.
Definition: CPNumber.h:2
CPRange function CPMakeRange(location, length)
Definition: CPRange.j:37
id stringWithFormat:(CPString format, [,] ...)
Definition: CPString.j:166