001: /*
002: * Copyright (c) 2003 The Visigoth Software Society. All rights
003: * reserved.
004: *
005: * Redistribution and use in source and binary forms, with or without
006: * modification, are permitted provided that the following conditions
007: * are met:
008: *
009: * 1. Redistributions of source code must retain the above copyright
010: * notice, this list of conditions and the following disclaimer.
011: *
012: * 2. Redistributions in binary form must reproduce the above copyright
013: * notice, this list of conditions and the following disclaimer in
014: * the documentation and/or other materials provided with the
015: * distribution.
016: *
017: * 3. The end-user documentation included with the redistribution, if
018: * any, must include the following acknowledgement:
019: * "This product includes software developed by the
020: * Visigoth Software Society (http://www.visigoths.org/)."
021: * Alternately, this acknowledgement may appear in the software itself,
022: * if and wherever such third-party acknowledgements normally appear.
023: *
024: * 4. Neither the name "FreeMarker", "Visigoth", nor any of the names of the
025: * project contributors may be used to endorse or promote products derived
026: * from this software without prior written permission. For written
027: * permission, please contact visigoths@visigoths.org.
028: *
029: * 5. Products derived from this software may not be called "FreeMarker" or "Visigoth"
030: * nor may "FreeMarker" or "Visigoth" appear in their names
031: * without prior written permission of the Visigoth Software Society.
032: *
033: * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
034: * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
035: * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
036: * DISCLAIMED. IN NO EVENT SHALL THE VISIGOTH SOFTWARE SOCIETY OR
037: * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
038: * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
039: * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
040: * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
041: * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
042: * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
043: * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
044: * SUCH DAMAGE.
045: * ====================================================================
046: *
047: * This software consists of voluntary contributions made by many
048: * individuals on behalf of the Visigoth Software Society. For more
049: * information on the Visigoth Software Society, please see
050: * http://www.visigoths.org/
051: */
052:
053: package freemarker.core;
054:
055: import java.text.Collator;
056: import java.util.ArrayList;
057: import java.util.Collections;
058: import java.util.Comparator;
059: import java.util.Date;
060: import java.util.List;
061:
062: import freemarker.template.SimpleNumber;
063: import freemarker.template.TemplateBooleanModel;
064: import freemarker.template.TemplateDateModel;
065: import freemarker.template.TemplateException;
066: import freemarker.template.TemplateHashModel;
067: import freemarker.template.TemplateMethodModelEx;
068: import freemarker.template.TemplateModel;
069: import freemarker.template.TemplateModelException;
070: import freemarker.template.TemplateModelListSequence;
071: import freemarker.template.TemplateNumberModel;
072: import freemarker.template.TemplateScalarModel;
073: import freemarker.template.TemplateSequenceModel;
074: import freemarker.template.TemplateCollectionModel;
075: import freemarker.template.TemplateModelIterator;
076: import freemarker.template.utility.Constants;
077: import freemarker.template.utility.StringUtil;
078:
079: /**
080: * A holder for builtins that operate exclusively on TemplateSequenceModels.
081: */
082:
083: abstract class SequenceBuiltins {
084: abstract static class SequenceBuiltIn extends BuiltIn {
085: TemplateModel _getAsTemplateModel(Environment env)
086: throws TemplateException {
087: TemplateModel model = target.getAsTemplateModel(env);
088: if (!(model instanceof TemplateSequenceModel)) {
089: throw invalidTypeException(model, target, env,
090: "sequence");
091: }
092: return calculateResult((TemplateSequenceModel) model);
093: }
094:
095: abstract TemplateModel calculateResult(TemplateSequenceModel tsm)
096: throws TemplateModelException;
097: }
098:
099: static class firstBI extends SequenceBuiltIn {
100: TemplateModel calculateResult(TemplateSequenceModel tsm)
101: throws TemplateModelException {
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 TemplateModelException {
112: if (tsm.size() == 0) {
113: return null;
114: }
115: return tsm.get(tsm.size() - 1);
116: }
117: }
118:
119: static class reverseBI extends SequenceBuiltIn {
120: TemplateModel calculateResult(TemplateSequenceModel tsm) {
121: if (tsm instanceof ReverseSequence) {
122: return ((ReverseSequence) tsm).seq;
123: } else {
124: return new ReverseSequence(tsm);
125: }
126: }
127:
128: private static class ReverseSequence implements
129: TemplateSequenceModel {
130: private final TemplateSequenceModel seq;
131:
132: ReverseSequence(TemplateSequenceModel seq) {
133: this .seq = seq;
134: }
135:
136: public int size() throws TemplateModelException {
137: return seq.size();
138: }
139:
140: public TemplateModel get(int index)
141: throws TemplateModelException {
142: return seq.get(seq.size() - 1 - index);
143: }
144: }
145: }
146:
147: static class sortBI extends SequenceBuiltIn {
148:
149: static final int KEY_TYPE_STRING = 1;
150: static final int KEY_TYPE_NUMBER = 2;
151: static final int KEY_TYPE_DATE = 3;
152:
153: TemplateModel calculateResult(TemplateSequenceModel seq)
154: throws TemplateModelException {
155: return sort(seq, null);
156: }
157:
158: static String startErrorMessage(Object keys) {
159: return (keys == null ? "?sort" : "?sort_by(...)")
160: + " failed: ";
161: }
162:
163: /**
164: * Sorts a sequence for the <tt>sort</tt> and <tt>sort_by</tt>
165: * built-ins.
166: *
167: * @param seq the sequence to sort.
168: * @param keys the name of the subvariable whose value is used for the
169: * sorting. If the sorting is done by a sub-subvaruable, then this
170: * will be of length 2, and so on. If the sorting is done by the
171: * sequene items directly, then this argument has to be 0 length
172: * array or <code>null</code>.
173: * @return a new sorted sequence, or the original sequence if the
174: * sequence length was 0.
175: */
176: static TemplateSequenceModel sort(TemplateSequenceModel seq,
177: String[] keys) throws TemplateModelException {
178: int i;
179: int keyCnt;
180:
181: int ln = seq.size();
182: if (ln == 0) {
183: return seq;
184: }
185:
186: List res = new ArrayList(ln);
187: Object item;
188: item = seq.get(0);
189: if (keys != null) {
190: keyCnt = keys.length;
191: if (keyCnt == 0) {
192: keys = null;
193: } else {
194: for (i = 0; i < keyCnt; i++) {
195: if (!(item instanceof TemplateHashModel)) {
196: throw new TemplateModelException(
197: startErrorMessage(keys)
198: + (i == 0 ? "You can't use ?sort_by when the "
199: + "sequence items are not hashes."
200: : "The subvariable "
201: + StringUtil
202: .jQuote(keys[i - 1])
203: + " is not a hash, so ?sort_by "
204: + "can't proceed by getting the "
205: + StringUtil
206: .jQuote(keys[i])
207: + " subvariable."));
208: }
209:
210: item = ((TemplateHashModel) item).get(keys[i]);
211: if (item == null) {
212: throw new TemplateModelException(
213: startErrorMessage(keys)
214: + "The "
215: + StringUtil
216: .jQuote(keys[i])
217: + " subvariable "
218: + (keyCnt == 1 ? "was not found."
219: : "(specified by ?sort_by argument number "
220: + (i + 1)
221: + ") 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)
249: .getAsString(), 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 "
258: + i + " 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)
270: .getAsNumber(), 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 "
279: + i + " 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(((TemplateDateModel) item)
290: .getAsDate(), item));
291: } catch (ClassCastException e) {
292: if (!(item instanceof TemplateNumberModel)) {
293: throw new TemplateModelException(
294: startErrorMessage(keys)
295: + "All values in the sequence must be "
296: + "date/time values, because the first "
297: + "value was a date/time. "
298: + "The value at index "
299: + i
300: + " is not date/time.");
301: } else {
302: throw e;
303: }
304: }
305: }
306: } else {
307: throw new RuntimeException(
308: "FreeMarker bug: Bad key type");
309: }
310: } else {
311: for (i = 0; i < ln; i++) {
312: item = seq.get(i);
313: Object key = item;
314: for (int j = 0; j < keyCnt; j++) {
315: try {
316: key = ((TemplateHashModel) key)
317: .get(keys[j]);
318: } catch (ClassCastException e) {
319: if (!(key instanceof TemplateHashModel)) {
320: throw new TemplateModelException(
321: startErrorMessage(keys)
322: + "Problem with the sequence item at index "
323: + i
324: + ": "
325: + "Can't get the "
326: + StringUtil
327: .jQuote(keys[j])
328: + " subvariable, because the value is not a hash.");
329: } else {
330: throw e;
331: }
332: }
333: if (key == null) {
334: throw new TemplateModelException(
335: startErrorMessage(keys)
336: + "Problem with the sequence item at index "
337: + i
338: + ": "
339: + "The "
340: + StringUtil
341: .jQuote(keys[j])
342: + " subvariable was not found.");
343: }
344: }
345: if (keyType == KEY_TYPE_STRING) {
346: try {
347: res.add(new KVP(((TemplateScalarModel) key)
348: .getAsString(), item));
349: } catch (ClassCastException e) {
350: if (!(key instanceof TemplateScalarModel)) {
351: throw new TemplateModelException(
352: startErrorMessage(keys)
353: + "All key values in the sequence must be "
354: + "date/time values, because the first key "
355: + "value was a date/time. The key value at "
356: + "index "
357: + i
358: + " is not a date/time.");
359: } else {
360: throw e;
361: }
362: }
363: } else if (keyType == KEY_TYPE_NUMBER) {
364: try {
365: res.add(new KVP(((TemplateNumberModel) key)
366: .getAsNumber(), item));
367: } catch (ClassCastException e) {
368: if (!(key instanceof TemplateNumberModel)) {
369: throw new TemplateModelException(
370: startErrorMessage(keys)
371: + "All key values in the sequence must be "
372: + "numbers, because the first key "
373: + "value was a number. The key value at "
374: + "index " + i
375: + " is not a number.");
376: }
377: }
378: } else if (keyType == KEY_TYPE_DATE) {
379: try {
380: res.add(new KVP(((TemplateDateModel) key)
381: .getAsDate(), item));
382: } catch (ClassCastException e) {
383: if (!(key instanceof TemplateDateModel)) {
384: throw new TemplateModelException(
385: startErrorMessage(keys)
386: + "All key values in the sequence must be "
387: + "dates, because the first key "
388: + "value was a date. The key value at "
389: + "index " + i
390: + " is not a date.");
391: }
392: }
393: } else {
394: throw new RuntimeException(
395: "FreeMarker bug: Bad key type");
396: }
397: }
398: }
399:
400: Comparator cmprtr;
401: if (keyType == KEY_TYPE_STRING) {
402: cmprtr = new LexicalKVPComparator(Environment
403: .getCurrentEnvironment().getCollator());
404: } else if (keyType == KEY_TYPE_NUMBER) {
405: cmprtr = new NumericalKVPComparator(Environment
406: .getCurrentEnvironment().getArithmeticEngine());
407: } else if (keyType == KEY_TYPE_DATE) {
408: cmprtr = new DateKVPComparator();
409: } else {
410: throw new RuntimeException(
411: "FreeMarker bug: Bad key type");
412: }
413:
414: try {
415: Collections.sort(res, cmprtr);
416: } catch (ClassCastException exc) {
417: throw new TemplateModelException(
418: startErrorMessage(keys)
419: + "Unexpected error while sorting:"
420: + exc, exc);
421: }
422:
423: for (i = 0; i < ln; i++) {
424: res.set(i, ((KVP) res.get(i)).value);
425: }
426:
427: return new TemplateModelListSequence(res);
428: }
429:
430: private static class KVP {
431: private KVP(Object key, Object value) {
432: this .key = key;
433: this .value = value;
434: }
435:
436: private Object key;
437: private Object value;
438: }
439:
440: private static class NumericalKVPComparator implements
441: Comparator {
442: private ArithmeticEngine ae;
443:
444: private NumericalKVPComparator(ArithmeticEngine ae) {
445: this .ae = ae;
446: }
447:
448: public int compare(Object arg0, Object arg1) {
449: try {
450: return ae.compareNumbers((Number) ((KVP) arg0).key,
451: (Number) ((KVP) arg1).key);
452: } catch (TemplateException e) {
453: throw new ClassCastException(
454: "Failed to compare numbers: " + e);
455: }
456: }
457: }
458:
459: private static class LexicalKVPComparator implements Comparator {
460: private Collator collator;
461:
462: LexicalKVPComparator(Collator collator) {
463: this .collator = collator;
464: }
465:
466: public int compare(Object arg0, Object arg1) {
467: return collator.compare(((KVP) arg0).key,
468: ((KVP) arg1).key);
469: }
470: }
471:
472: private static class DateKVPComparator implements Comparator {
473:
474: public int compare(Object arg0, Object arg1) {
475: return ((Date) ((KVP) arg0).key)
476: .compareTo((Date) ((KVP) arg1).key);
477: }
478: }
479:
480: }
481:
482: static class sort_byBI extends sortBI {
483: TemplateModel calculateResult(TemplateSequenceModel seq) {
484: return new BIMethod(seq);
485: }
486:
487: static class BIMethod implements TemplateMethodModelEx {
488: TemplateSequenceModel seq;
489:
490: BIMethod(TemplateSequenceModel seq) {
491: this .seq = seq;
492: }
493:
494: public Object exec(List params)
495: throws TemplateModelException {
496: if (params.size() == 0) {
497: throw new TemplateModelException(
498: "?sort_by(key) needs exactly 1 argument.");
499: }
500: String[] subvars;
501: Object obj = params.get(0);
502: if (obj instanceof TemplateScalarModel) {
503: subvars = new String[] { ((TemplateScalarModel) obj)
504: .getAsString() };
505: } else if (obj instanceof TemplateSequenceModel) {
506: TemplateSequenceModel seq = (TemplateSequenceModel) obj;
507: int ln = seq.size();
508: subvars = new String[ln];
509: for (int i = 0; i < ln; i++) {
510: Object item = seq.get(i);
511: try {
512: subvars[i] = ((TemplateScalarModel) item)
513: .getAsString();
514: } catch (ClassCastException e) {
515: if (!(item instanceof TemplateScalarModel)) {
516: throw new TemplateModelException(
517: "The argument to ?sort_by(key), when it "
518: + "is a sequence, must be a sequence of "
519: + "strings, but the item at index "
520: + i
521: + " is not a string.");
522: }
523: }
524: }
525: } else {
526: throw new TemplateModelException(
527: "The argument to ?sort_by(key) must be a string "
528: + "(the name of the subvariable), or a sequence of "
529: + "strings (the \"path\" to the subvariable).");
530: }
531: return sort(seq, subvars);
532: }
533: }
534: }
535:
536: static class seq_containsBI extends BuiltIn {
537: TemplateModel _getAsTemplateModel(Environment env)
538: throws TemplateException {
539: TemplateModel model = target.getAsTemplateModel(env);
540: if (model instanceof TemplateSequenceModel) {
541: return new BIMethodForSequence(
542: (TemplateSequenceModel) model, env);
543: } else if (model instanceof TemplateCollectionModel) {
544: return new BIMethodForCollection(
545: (TemplateCollectionModel) model, env);
546: } else {
547: throw invalidTypeException(model, target, env,
548: "sequence or collection");
549: }
550: }
551:
552: private class BIMethodForSequence implements
553: TemplateMethodModelEx {
554: private TemplateSequenceModel m_seq;
555: private Environment m_env;
556:
557: private BIMethodForSequence(TemplateSequenceModel seq,
558: Environment env) {
559: m_seq = seq;
560: m_env = env;
561: }
562:
563: public Object exec(List args) throws TemplateModelException {
564: if (args.size() != 1)
565: throw new TemplateModelException(
566: "?seq_contains(...) expects one argument.");
567: TemplateModel arg = (TemplateModel) args.get(0);
568: int size = m_seq.size();
569: for (int i = 0; i < size; i++) {
570: if (modelsEqual(m_seq.get(i), arg, m_env))
571: return TemplateBooleanModel.TRUE;
572: }
573: return TemplateBooleanModel.FALSE;
574: }
575:
576: }
577:
578: private class BIMethodForCollection implements
579: TemplateMethodModelEx {
580: private TemplateCollectionModel m_coll;
581: private Environment m_env;
582:
583: private BIMethodForCollection(TemplateCollectionModel coll,
584: Environment env) {
585: m_coll = coll;
586: m_env = env;
587: }
588:
589: public Object exec(List args) throws TemplateModelException {
590: if (args.size() != 1)
591: throw new TemplateModelException(
592: "?seq_contains(...) expects one argument.");
593: TemplateModel arg = (TemplateModel) args.get(0);
594: TemplateModelIterator it = m_coll.iterator();
595: while (it.hasNext()) {
596: if (modelsEqual(it.next(), arg, m_env))
597: return TemplateBooleanModel.TRUE;
598: }
599: return TemplateBooleanModel.FALSE;
600: }
601:
602: }
603:
604: }
605:
606: static class seq_index_ofBI extends BuiltIn {
607: private int m_dir;
608:
609: public seq_index_ofBI(int dir) {
610: m_dir = dir;
611: }
612:
613: TemplateModel _getAsTemplateModel(Environment env)
614: throws TemplateException {
615: TemplateModel model = target.getAsTemplateModel(env);
616: if (!(model instanceof TemplateSequenceModel))
617: throw invalidTypeException(model, target, env,
618: "sequence");
619: return new BIMethod((TemplateSequenceModel) model, env);
620: }
621:
622: private class BIMethod implements TemplateMethodModelEx {
623: private TemplateSequenceModel m_seq;
624: private Environment m_env;
625:
626: private BIMethod(TemplateSequenceModel seq, Environment env) {
627: m_seq = seq;
628: m_env = env;
629: }
630:
631: public Object exec(List args) throws TemplateModelException {
632: int argcnt = args.size();
633: if (argcnt != 1 && argcnt != 2) {
634: throw new TemplateModelException(
635: getBuiltinTemplate()
636: + " expects 1 or 2 arguments.");
637: }
638:
639: int startIndex;
640: int seqSize = m_seq.size();
641: TemplateModel arg = (TemplateModel) args.get(0);
642: if (argcnt > 1) {
643: Object obj = args.get(1);
644: if (!(obj instanceof TemplateNumberModel)) {
645: throw new TemplateModelException(
646: getBuiltinTemplate()
647: + "expects a number as its second argument.");
648: }
649: startIndex = ((TemplateNumberModel) obj)
650: .getAsNumber().intValue();
651: if (m_dir == 1) {
652: if (startIndex >= seqSize) {
653: return Constants.MINUS_ONE;
654: }
655: if (startIndex < 0) {
656: startIndex = 0;
657: }
658: } else {
659: if (startIndex >= seqSize) {
660: startIndex = seqSize - 1;
661: }
662: if (startIndex < 0) {
663: return Constants.MINUS_ONE;
664: }
665: }
666: } else {
667: if (m_dir == 1) {
668: startIndex = 0;
669: } else {
670: startIndex = seqSize - 1;
671: }
672: }
673:
674: if (m_dir == 1) {
675: for (int i = startIndex; i < seqSize; i++) {
676: if (modelsEqual(m_seq.get(i), arg, m_env))
677: return new SimpleNumber(i);
678: }
679: } else {
680: for (int i = startIndex; i >= 0; i--) {
681: if (modelsEqual(m_seq.get(i), arg, m_env))
682: return new SimpleNumber(i);
683: }
684: }
685: return Constants.MINUS_ONE;
686: }
687:
688: private String getBuiltinTemplate() {
689: if (m_dir == 1)
690: return "?seq_indexOf(...)";
691: else
692: return "?seq_lastIndexOf(...)";
693: }
694: }
695: }
696:
697: static class chunkBI extends SequenceBuiltIn {
698:
699: TemplateModel calculateResult(TemplateSequenceModel tsm)
700: throws TemplateModelException {
701: return new BIMethod(tsm);
702: }
703:
704: private static class BIMethod implements TemplateMethodModelEx {
705:
706: private final TemplateSequenceModel tsm;
707:
708: private BIMethod(TemplateSequenceModel tsm) {
709: this .tsm = tsm;
710: }
711:
712: public Object exec(List args) throws TemplateModelException {
713: int numArgs = args.size();
714: if (numArgs != 1 && numArgs != 2) {
715: throw new TemplateModelException(
716: "?chunk(...) expects 1 or 2 arguments.");
717: }
718:
719: Object chunkSize = args.get(0);
720: if (!(chunkSize instanceof TemplateNumberModel)) {
721: throw new TemplateModelException(
722: "?chunk(...) expects a number as "
723: + "its 1st argument.");
724: }
725:
726: return new ChunkedSequence(tsm,
727: ((TemplateNumberModel) chunkSize).getAsNumber()
728: .intValue(),
729: numArgs > 1 ? (TemplateModel) args.get(1)
730: : null);
731: }
732: }
733:
734: private static class ChunkedSequence implements
735: TemplateSequenceModel {
736:
737: private final TemplateSequenceModel wrappedTsm;
738:
739: private final int chunkSize;
740:
741: private final TemplateModel fillerItem;
742:
743: private final int numberOfChunks;
744:
745: private ChunkedSequence(TemplateSequenceModel wrappedTsm,
746: int chunkSize, TemplateModel fillerItem)
747: throws TemplateModelException {
748: if (chunkSize < 1) {
749: throw new TemplateModelException(
750: "The 1st argument to ?chunk(...) must be at least 1.");
751: }
752: this .wrappedTsm = wrappedTsm;
753: this .chunkSize = chunkSize;
754: this .fillerItem = fillerItem;
755: numberOfChunks = (wrappedTsm.size() + chunkSize - 1)
756: / chunkSize;
757: }
758:
759: public TemplateModel get(final int chunkIndex)
760: throws TemplateModelException {
761: if (chunkIndex >= numberOfChunks) {
762: return null;
763: }
764:
765: return new TemplateSequenceModel() {
766:
767: private final int baseIndex = chunkIndex
768: * chunkSize;
769:
770: public TemplateModel get(int relIndex)
771: throws TemplateModelException {
772: int absIndex = baseIndex + relIndex;
773: if (absIndex < wrappedTsm.size()) {
774: return wrappedTsm.get(absIndex);
775: } else {
776: return absIndex < numberOfChunks
777: * chunkSize ? fillerItem : null;
778: }
779: }
780:
781: public int size() throws TemplateModelException {
782: return fillerItem != null
783: || chunkIndex + 1 < numberOfChunks ? chunkSize
784: : wrappedTsm.size() - baseIndex;
785: }
786:
787: };
788: }
789:
790: public int size() throws TemplateModelException {
791: return numberOfChunks;
792: }
793:
794: }
795:
796: }
797:
798: /*
799: * WARNING! This algorithm is duplication of ComparisonExpression.isTrue(...).
800: * Thus, if you update this method, then you have to update that too!
801: */
802: public static boolean modelsEqual(TemplateModel model1,
803: TemplateModel model2, Environment env)
804: throws TemplateModelException {
805: if (env.isClassicCompatible()) {
806: if (model1 == null) {
807: model1 = TemplateScalarModel.EMPTY_STRING;
808: }
809: if (model2 == null) {
810: model2 = TemplateScalarModel.EMPTY_STRING;
811: }
812: }
813:
814: int comp = -1;
815: if (model1 instanceof TemplateNumberModel
816: && model2 instanceof TemplateNumberModel) {
817: Number first = ((TemplateNumberModel) model1).getAsNumber();
818: Number second = ((TemplateNumberModel) model2)
819: .getAsNumber();
820: ArithmeticEngine ae = env != null ? env
821: .getArithmeticEngine() : env.getTemplate()
822: .getArithmeticEngine();
823: try {
824: comp = ae.compareNumbers(first, second);
825: } catch (TemplateException ex) {
826: throw new TemplateModelException(ex);
827: }
828: } else if (model1 instanceof TemplateDateModel
829: && model2 instanceof TemplateDateModel) {
830: TemplateDateModel ltdm = (TemplateDateModel) model1;
831: TemplateDateModel rtdm = (TemplateDateModel) model2;
832: int ltype = ltdm.getDateType();
833: int rtype = rtdm.getDateType();
834: if (ltype != rtype) {
835: throw new TemplateModelException(
836: "Can not compare dates of different type. Left date is of "
837: + TemplateDateModel.TYPE_NAMES
838: .get(ltype)
839: + " type, right date is of "
840: + TemplateDateModel.TYPE_NAMES
841: .get(rtype) + " type.");
842: }
843: if (ltype == TemplateDateModel.UNKNOWN) {
844: throw new TemplateModelException(
845: "Left date is of UNKNOWN type, and can not be compared.");
846: }
847: if (rtype == TemplateDateModel.UNKNOWN) {
848: throw new TemplateModelException(
849: "Right date is of UNKNOWN type, and can not be compared.");
850: }
851: Date first = ltdm.getAsDate();
852: Date second = rtdm.getAsDate();
853: comp = first.compareTo(second);
854: } else if (model1 instanceof TemplateScalarModel
855: && model2 instanceof TemplateScalarModel) {
856: String first = ((TemplateScalarModel) model1).getAsString();
857: String second = ((TemplateScalarModel) model2)
858: .getAsString();
859: comp = env.getCollator().compare(first, second);
860: } else if (model1 instanceof TemplateBooleanModel
861: && model2 instanceof TemplateBooleanModel) {
862: boolean first = ((TemplateBooleanModel) model1)
863: .getAsBoolean();
864: boolean second = ((TemplateBooleanModel) model2)
865: .getAsBoolean();
866: comp = (first ? 1 : 0) - (second ? 1 : 0);
867: }
868:
869: return (comp == 0);
870: }
871:
872: }
|