1 52 53 package freemarker.core; 54 55 import java.text.Collator ; 56 import java.util.ArrayList ; 57 import java.util.Collections ; 58 import java.util.Comparator ; 59 import java.util.Date ; 60 import java.util.List ; 61 62 import freemarker.template.SimpleNumber; 63 import freemarker.template.TemplateBooleanModel; 64 import freemarker.template.TemplateDateModel; 65 import freemarker.template.TemplateException; 66 import freemarker.template.TemplateHashModel; 67 import freemarker.template.TemplateMethodModelEx; 68 import freemarker.template.TemplateModel; 69 import freemarker.template.TemplateModelException; 70 import freemarker.template.TemplateModelListSequence; 71 import freemarker.template.TemplateNumberModel; 72 import freemarker.template.TemplateScalarModel; 73 import freemarker.template.TemplateSequenceModel; 74 import freemarker.template.utility.Constants; 75 import freemarker.template.utility.StringUtil; 76 77 80 81 abstract class SequenceBuiltins { 82 abstract static class SequenceBuiltIn extends BuiltIn { 83 TemplateModel _getAsTemplateModel(Environment env) 84 throws TemplateException 85 { 86 TemplateModel model = target.getAsTemplateModel(env); 87 if (!(model instanceof TemplateSequenceModel)) { 88 throw invalidTypeException(model, target, env, "sequence"); 89 } 90 return calculateResult((TemplateSequenceModel) model); 91 } 92 abstract TemplateModel calculateResult(TemplateSequenceModel tsm) 93 throws 94 TemplateModelException; 95 } 96 97 static class firstBI extends SequenceBuiltIn { 98 TemplateModel calculateResult(TemplateSequenceModel tsm) 99 throws 100 TemplateModelException 101 { 102 if (tsm.size() == 0) { 103 return null; 104 } 105 return tsm.get(0); 106 } 107 } 108 109 static class lastBI extends SequenceBuiltIn { 110 TemplateModel calculateResult(TemplateSequenceModel tsm) 111 throws 112 TemplateModelException 113 { 114 if (tsm.size() == 0) { 115 return null; 116 } 117 return tsm.get(tsm.size() -1); 118 } 119 } 120 121 static class reverseBI extends SequenceBuiltIn { 122 TemplateModel calculateResult(TemplateSequenceModel tsm) { 123 if (tsm instanceof ReverseSequence) { 124 return ((ReverseSequence) tsm).seq; 125 } else { 126 return new ReverseSequence(tsm); 127 } 128 } 129 130 private static class ReverseSequence implements TemplateSequenceModel 131 { 132 private final TemplateSequenceModel seq; 133 134 ReverseSequence(TemplateSequenceModel seq) 135 { 136 this.seq = seq; 137 } 138 139 public int size() throws TemplateModelException 140 { 141 return seq.size(); 142 } 143 144 public TemplateModel get(int index) throws TemplateModelException 145 { 146 return seq.get(seq.size() - 1 - index); 147 } 148 } 149 } 150 151 static class sortBI extends SequenceBuiltIn { 152 153 static final int KEY_TYPE_STRING = 1; 154 static final int KEY_TYPE_NUMBER = 2; 155 static final int KEY_TYPE_DATE = 3; 156 157 TemplateModel calculateResult(TemplateSequenceModel seq) 158 throws TemplateModelException { 159 return sort(seq, null); 160 } 161 162 static String startErrorMessage(Object keys) { 163 return (keys == null ? "?sort" : "?sort_by(...)") + " failed: "; 164 } 165 166 179 static TemplateSequenceModel sort(TemplateSequenceModel seq, String [] keys) 180 throws TemplateModelException { 181 int i; 182 int keyCnt; 183 184 int ln = seq.size(); 185 if (ln == 0) { 186 return seq; 187 } 188 189 List res = new ArrayList (ln); 190 Object item; 191 item = seq.get(0); 192 if (keys != null) { 193 keyCnt = keys.length; 194 if (keyCnt == 0) { 195 keys = null; 196 } else { 197 for (i = 0; i < keyCnt; i++) { 198 if (!(item instanceof TemplateHashModel)) { 199 throw new TemplateModelException( 200 startErrorMessage(keys) 201 + (i == 0 202 ? "You can't use ?sort_by when the " 203 + "sequence items are not hashes." 204 : "The subvariable " 205 + StringUtil.jQuote(keys[i - 1]) 206 + " is not a hash, so ?sort_by " 207 + "can't proceed by getting the " 208 + StringUtil.jQuote(keys[i]) 209 + " subvariable.")); 210 } 211 212 item = ((TemplateHashModel) item).get(keys[i]); 213 if (item == null) { 214 throw new TemplateModelException( 215 startErrorMessage(keys) 216 + "The " + StringUtil.jQuote(keys[i]) 217 + " subvariable " 218 + (keyCnt == 1 219 ? "was not found." 220 : "(specified by ?sort_by argument number " 221 + (i + 1) + ") was not found.")); 222 } 223 } 224 } 225 } else { 226 keyCnt = 0; 227 } 228 229 int keyType; 230 if (item instanceof TemplateScalarModel) { 231 keyType = KEY_TYPE_STRING; 232 } else if (item instanceof TemplateNumberModel) { 233 keyType = KEY_TYPE_NUMBER; 234 } else if (item instanceof TemplateDateModel) { 235 keyType = KEY_TYPE_DATE; 236 } else { 237 throw new TemplateModelException( 238 startErrorMessage(keys) 239 + "Values used for sorting must be numbers, strings, or date/time values."); 240 } 241 242 if (keys == null) { 243 if (keyType == KEY_TYPE_STRING) { 244 for (i = 0; i < ln; i++) { 245 item = seq.get(i); 246 try { 247 res.add(new KVP( 248 ((TemplateScalarModel) item).getAsString(), 249 item)); 250 } catch (ClassCastException e) { 251 if (!(item instanceof TemplateScalarModel)) { 252 throw new TemplateModelException( 253 startErrorMessage(keys) 254 + "All values in the sequence must be " 255 + "strings, because the first value " 256 + "was a string. " 257 + "The value at index " + i 258 + " is not string."); 259 } else { 260 throw e; 261 } 262 } 263 } 264 } else if (keyType == KEY_TYPE_NUMBER) { 265 for (i = 0; i < ln; i++) { 266 item = seq.get(i); 267 try { 268 res.add(new KVP( 269 ((TemplateNumberModel) item).getAsNumber(), 270 item)); 271 } catch (ClassCastException e) { 272 if (!(item instanceof TemplateNumberModel)) { 273 throw new TemplateModelException( 274 startErrorMessage(keys) 275 + "All values in the sequence must be " 276 + "numbers, because the first value " 277 + "was a number. " 278 + "The value at index " + i 279 + " is not number."); 280 } else { 281 throw e; 282 } 283 } 284 } 285 } else if (keyType == KEY_TYPE_DATE) { 286 for (i = 0; i < ln; i++) { 287 item = seq.get(i); 288 try { 289 res.add(new KVP( 290 ((TemplateDateModel) item).getAsDate(), 291 item)); 292 } catch (ClassCastException e) { 293 if (!(item instanceof TemplateNumberModel)) { 294 throw new TemplateModelException( 295 startErrorMessage(keys) 296 + "All values in the sequence must be " 297 + "date/time values, because the first " 298 + "value was a date/time. " 299 + "The value at index " + i 300 + " is not date/time."); 301 } else { 302 throw e; 303 } 304 } 305 } 306 } else { 307 throw new RuntimeException ("FreeMarker bug: Bad key type"); 308 } 309 } else { 310 for (i = 0; i < ln; i++) { 311 item = seq.get(i); 312 Object key = item; 313 for (int j = 0; j < keyCnt; j++) { 314 try { 315 key = ((TemplateHashModel) key).get(keys[j]); 316 } catch (ClassCastException e) { 317 if (!(key instanceof TemplateHashModel)) { 318 throw new TemplateModelException( 319 startErrorMessage(keys) 320 + "Problem with the sequence item at index " + i + ": " 321 + "Can't get the " + StringUtil.jQuote(keys[j]) 322 + " subvariable, because the value is not a hash."); 323 } else { 324 throw e; 325 } 326 } 327 if (key == null) { 328 throw new TemplateModelException( 329 startErrorMessage(keys) 330 + "Problem with the sequence item at index " + i + ": " 331 + "The " + StringUtil.jQuote(keys[j]) 332 + " subvariable was not found."); 333 } 334 } 335 if (keyType == KEY_TYPE_STRING) { 336 try { 337 res.add(new KVP( 338 ((TemplateScalarModel) key).getAsString(), 339 item)); 340 } catch (ClassCastException e) { 341 if (!(key instanceof TemplateScalarModel)) { 342 throw new TemplateModelException( 343 startErrorMessage(keys) 344 + "All key values in the sequence must be " 345 + "date/time values, because the first key " 346 + "value was a date/time. The key value at " 347 + "index " + i + " is not a date/time."); 348 } else { 349 throw e; 350 } 351 } 352 } else if (keyType == KEY_TYPE_NUMBER) { 353 try { 354 res.add(new KVP( 355 ((TemplateNumberModel) key).getAsNumber(), 356 item)); 357 } catch (ClassCastException e) { 358 if (!(key instanceof TemplateNumberModel)) { 359 throw new TemplateModelException( 360 startErrorMessage(keys) 361 + "All key values in the sequence must be " 362 + "numbers, because the first key " 363 + "value was a number. The key value at " 364 + "index " + i + " is not a number."); 365 } 366 } 367 } else if (keyType == KEY_TYPE_DATE) { 368 try { 369 res.add(new KVP( 370 ((TemplateDateModel) key).getAsDate(), 371 item)); 372 } catch (ClassCastException e) { 373 if (!(key instanceof TemplateDateModel)) { 374 throw new TemplateModelException( 375 startErrorMessage(keys) 376 + "All key values in the sequence must be " 377 + "dates, because the first key " 378 + "value was a date. The key value at " 379 + "index " + i + " is not a date."); 380 } 381 } 382 } else { 383 throw new RuntimeException ("FreeMarker bug: Bad key type"); 384 } 385 } 386 } 387 388 Comparator cmprtr; 389 if (keyType == KEY_TYPE_STRING) { 390 cmprtr = new LexicalKVPComparator( 391 Environment.getCurrentEnvironment().getCollator()); 392 } else if (keyType == KEY_TYPE_NUMBER) { 393 cmprtr = new NumericalKVPComparator( 394 Environment.getCurrentEnvironment() 395 .getArithmeticEngine()); 396 } else if (keyType == KEY_TYPE_DATE) { 397 cmprtr = new DateKVPComparator(); 398 } else { 399 throw new RuntimeException ("FreeMarker bug: Bad key type"); 400 } 401 402 try { 403 Collections.sort(res, cmprtr); 404 } catch (ClassCastException exc) { 405 throw new TemplateModelException( 406 startErrorMessage(keys) 407 + "Unexpected error while sorting:" + exc, exc); 408 } 409 410 for (i = 0; i < ln; i++) { 411 res.set(i, ((KVP) res.get(i)).value); 412 } 413 414 return new TemplateModelListSequence(res); 415 } 416 417 private static class KVP { 418 private KVP(Object key, Object value) { 419 this.key = key; 420 this.value = value; 421 } 422 423 private Object key; 424 private Object value; 425 } 426 427 private static class NumericalKVPComparator implements Comparator { 428 private ArithmeticEngine ae; 429 430 private NumericalKVPComparator(ArithmeticEngine ae) { 431 this.ae = ae; 432 } 433 434 public int compare(Object arg0, Object arg1) { 435 try { 436 return ae.compareNumbers( 437 (Number ) ((KVP) arg0).key, 438 (Number ) ((KVP) arg1).key); 439 } catch (TemplateException e) { 440 throw new ClassCastException ( 441 "Failed to compare numbers: " + e); 442 } 443 } 444 } 445 446 private static class LexicalKVPComparator implements Comparator { 447 private Collator collator; 448 449 LexicalKVPComparator(Collator collator) { 450 this.collator = collator; 451 } 452 453 public int compare(Object arg0, Object arg1) { 454 return collator.compare( 455 ((KVP) arg0).key, ((KVP) arg1).key); 456 } 457 } 458 459 private static class DateKVPComparator implements Comparator { 460 461 public int compare(Object arg0, Object arg1) { 462 return ((Date ) ((KVP) arg0).key).compareTo( 463 (Date ) ((KVP) arg1).key); 464 } 465 } 466 467 } 468 469 static class sort_byBI extends sortBI { 470 TemplateModel calculateResult(TemplateSequenceModel seq) 471 { 472 return new BIMethod(seq); 473 } 474 475 static class BIMethod implements TemplateMethodModelEx { 476 TemplateSequenceModel seq; 477 478 BIMethod(TemplateSequenceModel seq) { 479 this.seq = seq; 480 } 481 482 public Object exec(List params) 483 throws TemplateModelException { 484 if (params.size() == 0) { 485 throw new TemplateModelException( 486 "?sort_by(key) needs exactly 1 argument."); 487 } 488 String [] subvars; 489 Object obj = params.get(0); 490 if (obj instanceof TemplateScalarModel) { 491 subvars = new String []{((TemplateScalarModel) obj).getAsString()}; 492 } else if (obj instanceof TemplateSequenceModel) { 493 TemplateSequenceModel seq = (TemplateSequenceModel) obj; 494 int ln = seq.size(); 495 subvars = new String [ln]; 496 for (int i = 0; i < ln; i++) { 497 Object item = seq.get(i); 498 try { 499 subvars[i] = ((TemplateScalarModel) item) 500 .getAsString(); 501 } catch (ClassCastException e) { 502 if (!(item instanceof TemplateScalarModel)) { 503 throw new TemplateModelException( 504 "The argument to ?sort_by(key), when it " 505 + "is a sequence, must be a sequence of " 506 + "strings, but the item at index " + i 507 + " is not a string." ); 508 } 509 } 510 } 511 } else { 512 throw new TemplateModelException( 513 "The argument to ?sort_by(key) must be a string " 514 + "(the name of the subvariable), or a sequence of " 515 + "strings (the \"path\" to the subvariable)."); 516 } 517 return sort(seq, subvars); 518 } 519 } 520 } 521 522 static class seq_containsBI extends BuiltIn { 523 TemplateModel _getAsTemplateModel(Environment env) 524 throws TemplateException { 525 TemplateModel model = target.getAsTemplateModel(env); 526 if (!(model instanceof TemplateSequenceModel)) 527 throw invalidTypeException(model, target, env, "sequence"); 528 return new BIMethod((TemplateSequenceModel) model, env); 529 } 530 531 private class BIMethod implements TemplateMethodModelEx { 532 private TemplateSequenceModel m_seq; 533 private Environment m_env; 534 535 private BIMethod(TemplateSequenceModel seq, Environment env) { 536 m_seq = seq; 537 m_env = env; 538 } 539 540 public Object exec(List args) 541 throws TemplateModelException { 542 if (args.size() != 1) 543 throw new TemplateModelException("?seq_contains(...) expects one argument."); 544 TemplateModel arg = (TemplateModel) args.get(0); 545 int size = m_seq.size(); 546 for (int i = 0; i < size; i++) { 547 if (modelsEqual(m_seq.get(i), arg, m_env)) 548 return TemplateBooleanModel.TRUE; 549 } 550 return TemplateBooleanModel.FALSE; 551 } 552 553 } 554 } 555 556 static class seq_index_ofBI extends BuiltIn { 557 private int m_dir; 558 559 public seq_index_ofBI(int dir) { 560 m_dir = dir; 561 } 562 563 TemplateModel _getAsTemplateModel(Environment env) 564 throws TemplateException { 565 TemplateModel model = target.getAsTemplateModel(env); 566 if (!(model instanceof TemplateSequenceModel)) 567 throw invalidTypeException(model, target, env, "sequence"); 568 return new BIMethod((TemplateSequenceModel) model, env); 569 } 570 571 private class BIMethod implements TemplateMethodModelEx { 572 private TemplateSequenceModel m_seq; 573 private Environment m_env; 574 575 private BIMethod(TemplateSequenceModel seq, Environment env) { 576 m_seq = seq; 577 m_env = env; 578 } 579 580 public Object exec(List args) 581 throws TemplateModelException { 582 int argcnt = args.size(); 583 if (argcnt != 1 && argcnt != 2) { 584 throw new TemplateModelException( 585 getBuiltinTemplate() + " expects 1 or 2 arguments."); 586 } 587 588 int startIndex; 589 int seqSize = m_seq.size(); 590 TemplateModel arg = (TemplateModel) args.get(0); 591 if (argcnt > 1) { 592 Object obj = args.get(1); 593 if (!(obj instanceof TemplateNumberModel)) { 594 throw new TemplateModelException( 595 getBuiltinTemplate() 596 + "expects a number as its second argument."); 597 } 598 startIndex = ((TemplateNumberModel) obj).getAsNumber().intValue(); 599 if (m_dir == 1) { 600 if (startIndex >= seqSize) { 601 return Constants.MINUS_ONE; 602 } 603 if (startIndex < 0) { 604 startIndex = 0; 605 } 606 } else { 607 if (startIndex >= seqSize) { 608 startIndex = seqSize - 1; 609 } 610 if (startIndex < 0) { 611 return Constants.MINUS_ONE; 612 } 613 } 614 } else { 615 if (m_dir == 1) { 616 startIndex = 0; 617 } else { 618 startIndex = seqSize - 1; 619 } 620 } 621 622 if (m_dir == 1) { 623 for (int i = startIndex; i < seqSize; i++) { 624 if (modelsEqual(m_seq.get(i), arg, m_env)) 625 return new SimpleNumber(i); 626 } 627 } else { 628 for (int i = startIndex; i >= 0; i--) { 629 if (modelsEqual(m_seq.get(i), arg, m_env)) 630 return new SimpleNumber(i); 631 } 632 } 633 return Constants.MINUS_ONE; 634 } 635 636 private String getBuiltinTemplate() { 637 if (m_dir == 1) 638 return "?seq_indexOf(...)"; 639 else 640 return "?seq_lastIndexOf(...)"; 641 } 642 } 643 } 644 645 static class chunkBI extends SequenceBuiltIn { 646 647 TemplateModel calculateResult(TemplateSequenceModel tsm) throws TemplateModelException { 648 return new BIMethod(tsm); 649 } 650 651 private static class BIMethod implements TemplateMethodModelEx { 652 653 private final TemplateSequenceModel tsm; 654 655 private BIMethod(TemplateSequenceModel tsm) { 656 this.tsm = tsm; 657 } 658 659 public Object exec(List args) throws TemplateModelException { 660 int numArgs = args.size(); 661 if (numArgs != 1 && numArgs !=2) { 662 throw new TemplateModelException( 663 "?chunk(...) expects 1 or 2 arguments."); 664 } 665 666 Object chunkSize = args.get(0); 667 if (!(chunkSize instanceof TemplateNumberModel)) { 668 throw new TemplateModelException( 669 "?chunk(...) expects a number as " 670 + "its 1st argument."); 671 } 672 673 return new ChunkedSequence( 674 tsm, 675 ((TemplateNumberModel) chunkSize).getAsNumber().intValue(), 676 numArgs > 1 ? (TemplateModel) args.get(1) : null); 677 } 678 } 679 680 private static class ChunkedSequence implements TemplateSequenceModel { 681 682 private final TemplateSequenceModel wrappedTsm; 683 684 private final int chunkSize; 685 686 private final TemplateModel fillerItem; 687 688 private final int numberOfChunks; 689 690 private ChunkedSequence( 691 TemplateSequenceModel wrappedTsm, int chunkSize, TemplateModel fillerItem) 692 throws TemplateModelException { 693 if (chunkSize < 1) { 694 throw new TemplateModelException( 695 "The 1st argument to ?chunk(...) must be at least 1."); 696 } 697 this.wrappedTsm = wrappedTsm; 698 this.chunkSize = chunkSize; 699 this.fillerItem = fillerItem; 700 numberOfChunks = (wrappedTsm.size() + chunkSize - 1) / chunkSize; 701 } 702 703 public TemplateModel get(final int chunkIndex) 704 throws TemplateModelException { 705 if (chunkIndex >= numberOfChunks) { 706 return null; 707 } 708 709 return new TemplateSequenceModel() { 710 711 private final int baseIndex = chunkIndex * chunkSize; 712 713 public TemplateModel get(int relIndex) 714 throws TemplateModelException { 715 int absIndex = baseIndex + relIndex; 716 if (absIndex < wrappedTsm.size()) { 717 return wrappedTsm.get(absIndex); 718 } else { 719 return absIndex < numberOfChunks * chunkSize 720 ? fillerItem 721 : null; 722 } 723 } 724 725 public int size() throws TemplateModelException { 726 return fillerItem != null || chunkIndex + 1 < numberOfChunks 727 ? chunkSize 728 : wrappedTsm.size() - baseIndex; 729 } 730 731 }; 732 } 733 734 public int size() throws TemplateModelException { 735 return numberOfChunks; 736 } 737 738 } 739 740 } 741 742 746 public static boolean modelsEqual(TemplateModel model1, TemplateModel model2, 747 Environment env) 748 throws TemplateModelException { 749 if (env.isClassicCompatible()) { 750 if (model1 == null) { 751 model1 = TemplateScalarModel.EMPTY_STRING; 752 } 753 if (model2 == null) { 754 model2 = TemplateScalarModel.EMPTY_STRING; 755 } 756 } 757 758 int comp = -1; 759 if(model1 instanceof TemplateNumberModel && model2 instanceof TemplateNumberModel) { 760 Number first = ((TemplateNumberModel) model1).getAsNumber(); 761 Number second = ((TemplateNumberModel) model2).getAsNumber(); 762 ArithmeticEngine ae = 763 env != null 764 ? env.getArithmeticEngine() 765 : env.getTemplate().getArithmeticEngine(); 766 try { 767 comp = ae.compareNumbers(first, second); 768 } catch (TemplateException ex) { 769 throw new TemplateModelException(ex); 770 } 771 } 772 else if(model1 instanceof TemplateDateModel && model2 instanceof TemplateDateModel) { 773 TemplateDateModel ltdm = (TemplateDateModel)model1; 774 TemplateDateModel rtdm = (TemplateDateModel)model2; 775 int ltype = ltdm.getDateType(); 776 int rtype = rtdm.getDateType(); 777 if(ltype != rtype) { 778 throw new TemplateModelException( 779 "Can not compare dates of different type. Left date is of " 780 + TemplateDateModel.TYPE_NAMES.get(ltype) 781 + " type, right date is of " 782 + TemplateDateModel.TYPE_NAMES.get(rtype) + " type."); 783 } 784 if(ltype == TemplateDateModel.UNKNOWN) { 785 throw new TemplateModelException( 786 "Left date is of UNKNOWN type, and can not be compared."); 787 } 788 if(rtype == TemplateDateModel.UNKNOWN) { 789 throw new TemplateModelException( 790 "Right date is of UNKNOWN type, and can not be compared."); 791 } 792 Date first = ltdm.getAsDate(); 793 Date second = rtdm.getAsDate(); 794 comp = first.compareTo(second); 795 } 796 else if(model1 instanceof TemplateScalarModel && model2 instanceof TemplateScalarModel) { 797 String first = ((TemplateScalarModel) model1).getAsString(); 798 String second = ((TemplateScalarModel) model2).getAsString(); 799 comp = env.getCollator().compare(first, second); 800 } 801 else if(model1 instanceof TemplateBooleanModel && model2 instanceof TemplateBooleanModel) { 802 boolean first = ((TemplateBooleanModel)model1).getAsBoolean(); 803 boolean second = ((TemplateBooleanModel)model2).getAsBoolean(); 804 comp = (first ? 1 : 0) - (second ? 1 : 0); 805 } 806 807 return (comp == 0); 808 } 809 810 } | Popular Tags |