Docjar: A Java Source and Docuemnt Enginecom.*    java.*    javax.*    org.*    all    new    plug-in

Quick Search    Search Deep

Source code: jsd/ftp/tpl/Template.java


1   /*
2    * ----------------------------------------------------------------------------
3    * JStrangeDownloader and all accompanying source code files are
4    * Copyright (C) 2002 Dusty Davidson (dustyd@iastate.edu)
5    * 
6    * This program is free software; you can redistribute it and/or
7    * modify it under the terms of the GNU General Public License
8    * as published by the Free Software Foundation; either version 2
9    * of the License, or (at your option) any later version.
10   * 
11   * This program is distributed in the hope that it will be useful,
12   * but WITHOUT ANY WARRANTY; without even the implied warranty of
13   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14   * GNU General Public License for more details.
15   * 
16   * You should have received a copy of the GNU General Public License
17   * along with this program; if not, write to the Free Software
18   * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
19   * ----------------------------------------------------------------------------
20   *  
21   * Please see gpl.txt for the full text of the GNU General Public
22   * License.
23   */
24  
25  package jsd.ftp.tpl;
26  
27  import java.io.*;
28  import java.util.*;
29  
30  
31  /** 
32   * This class is used to load files into <code>OutputStream</code> 
33   * after parsing the template file. It supports variables, 
34   * for block, if-then-else block and iterator block. It has
35   * only one public method - <code>loadFile(OutputStream, Map)</code>.
36   * It also supports vector indexing. It stores the file data in a 
37   * byte array. We may face some problems later due to character 
38   * encoding. But for the time being it works fine.
39   * <a href="syntax.html">Template syntax.</a>
40   *
41   * @author <a href="mailto:rana_b@yahoo.com">Rana Bhattacharyya</a>
42   */
43  public 
44  class Template {
45  
46      // whitespaces
47      private static final String mstWhitespaces = " \t\r\n\f";
48  
49      // keywords
50      private static final String[] mstKeywords = { "IF",
51          "FOR",
52          "ITR",
53          "VARKEY",
54          "NULL"
55      };
56  
57      // keyword index
58      private static final int IF     = 0;
59      private static final int FOR    = 1;
60      private static final int ITR    = 2;
61      private static final int VARKEY = 3;
62      private static final int NULL   = 4;
63      private static final int VAR    = 5;
64  
65      private static final String SIZE = ".size"; 
66      private static final String LAST = ".last";
67      private static final String THRU = "THRU";
68      private static final String EQ   = "==";
69      private static final String NE   = "!=";
70      private static final String IN   = "IN";
71  
72      private File   mFile;
73      private long   mModifiedTime = 0;
74      private byte[] mbyContent;
75  
76  
77      /**
78       * Constructor.
79       */
80      public Template(File file) {
81          mFile = file;
82      }
83  
84  
85      /**
86       * Read file (if modified) into a byte array.
87       * This function is <code>Synchronized</code> to make
88       * this class thread safe. 
89       */
90      private synchronized void readFile() throws IOException {
91  
92          // file check
93          if (!mFile.exists())
94              throw new IOException(toString() + " : does not exist.");
95          if (!mFile.canRead())
96              throw new IOException(toString() + " : no read permission.");
97          if (!mFile.isFile())
98              throw new IOException(toString() + " : not a file.");
99  
100         // file modified
101         boolean bNeeded = false;
102         long modTime = mFile.lastModified();
103         if (modTime > mModifiedTime) {
104             mModifiedTime = modTime;
105             bNeeded = true;
106         }
107 
108         if (bNeeded) {
109             mbyContent = new byte[(int)mFile.length()];
110             FileInputStream fis = new FileInputStream(mFile);
111             fis.read(mbyContent);
112             fis.close();
113         }
114     }
115 
116 
117     /**
118      * Process block. First find the block type and then process.
119      */
120     private int processBlock(OutputStream out, Map hash, Block block) 
121     throws IOException {
122 
123         // get keyword type or var
124         int type = VAR; 
125         for (int i=0; i<mstKeywords.length; i++) {
126             if ( block.equals(mstKeywords[i]) ) {
127                 type = i;
128                 break;
129             }
130         }
131 
132         return processBlock(out, hash, block, type);
133     }
134 
135 
136     /**
137      * Process block - call respective functions. Each of these 
138      * functions returns the next index from where the next
139      * processing starts.
140      */
141     private int processBlock(OutputStream out, Map hash,
142                              Block block, int type)
143     throws IOException {
144 
145         int index;
146 
147         switch (type) {
148         
149         case VAR:
150             index = processVarBlock(out, hash, block);
151             break;
152 
153         case IF:
154             index = processIfBlock(out, hash, block.nextIndex());
155             break;
156 
157         case FOR:
158             index = processForBlock(out, hash, block.nextIndex());
159             break;
160 
161         case VARKEY:
162             index = processVarkeyBlock(out, block);
163             break;
164 
165         case NULL:
166             index = processNullBlock(out, block);
167             break;
168 
169         case ITR:
170             index = processItrBlock(out, hash, block.nextIndex());
171             break;
172 
173         default:
174             throw new IOException("Unknown keyword index=" + type);
175         }   
176 
177         return index;
178     }
179 
180 
181     /**
182      * Process null block - returns nothing
183      */
184     private int processNullBlock(OutputStream out, Block block) {
185         return block.nextIndex();
186     }
187 
188 
189     /**
190      * Process varkey.
191      * It is used to write ${ in the <code>OutputStream</code>.
192      */
193     private int processVarkeyBlock(OutputStream out, Block block) 
194     throws IOException {
195 
196         out.write('$');
197         out.write('{');
198         return block.nextIndex();
199     }
200 
201 
202     /**
203      * Process normal block. Normal block may contain other variables,
204      * keywords and other normal blocks. Search for "${". If found,
205      * get the block and process it.
206      */
207     private int processNormalBlock(OutputStream out, Map hash, Block block)
208     throws IOException {
209 
210         int ind = block.miStart;
211         int finalIndex = ind + block.miLength;
212         char c, c1;
213         while (ind < finalIndex) {
214             c = (char)mbyContent[ind++];
215 
216             if (c == '$') {
217                 c1 = (char)mbyContent[ind++];
218 
219                 // not a variable
220                 if (c1 != '{') {
221                     out.write(c);
222                     out.write(c1);
223                     continue;
224                 }
225 
226                 // variable and/or keyword
227                 Block nextBlock = getBlock(ind);
228                 ind = processBlock(out, hash, nextBlock);   
229             } else {
230                 out.write(c);   
231             }
232         }
233 
234         return block.nextIndex();
235     }
236 
237 
238     /**
239      * Process variable block. Get variable object from the 
240      * <code>Map</code> and write the string representation 
241      * of the object. 
242      */
243     private int processVarBlock(OutputStream out, Map hash, Block block)
244     throws IOException {
245 
246         // get variable value and send it
247         String sb = block.toString();
248         Object val = getVarObject(hash, sb);
249 
250         if (val != null)
251             out.write(val.toString().getBytes());
252 
253         return block.nextIndex();
254     }
255 
256 
257     /**
258      * Get variable object. 
259      * Returns null if not available in the Map
260      */
261     private Object getVarObject(Map hash, String var) 
262     throws IOException {
263 
264         // get actual variable string
265         String sb = getActualVarString(hash, var);
266 
267         // get vector size
268         if (sb.endsWith(SIZE)) {
269             return getVarSize(hash, var);
270         }
271 
272         // get last element of a vector
273         if (sb.endsWith(LAST)) {
274             return getLastVar(hash, var);
275         }
276 
277         // get vector element at a particular index
278         int startIndex = sb.indexOf('[');
279         int endIndex = sb.indexOf(']');
280         if (startIndex != -1 && endIndex != -1) {
281             if (endIndex < endIndex)
282                 throw new IOException("List indexing error");
283 
284             String indexBlock = sb.substring(startIndex+1, endIndex);       
285             String vectVar = sb.substring(0, startIndex);
286             Object obj = getVarObject(hash, vectVar);
287             return getIndexedObject(obj, indexBlock);
288         }
289 
290         // null variable
291         if (sb.equals(mstKeywords[NULL])) {
292             return null;
293         }
294 
295         // return other values
296         return hash.get(sb);
297     }
298 
299 
300     /**
301      * Get conditional variable object. If not a variable returns 
302      * the string itself. Else returns the equivalent object from
303      * the hashtable. If it is not available in the hashtable
304      * returns null.
305      */
306     private Object getCondVarObject(Map hash, String str) 
307     throws IOException{
308 
309         Object obj = null;
310 
311         // variable
312         if (str.charAt(0) == '$' && str.charAt(1) == '{') {
313             String var1 = str.substring(2, str.length() - 1);
314             String var2 = getBlock(str, 2);
315 
316             if (var1.equals(var2))
317                 obj = getVarObject(hash, var1);
318             else
319                 obj = getActualVarString(hash, str);
320         } else
321             obj = getActualVarString(hash, str);
322 
323         return obj;
324     }
325 
326 
327     /**
328      * Returns the size of a variable as an <code>Integer</code> 
329      * object. In case of Scalar returns 1. If object is null,
330      * returns 0 and in case of <code>List</code>
331      * returns the list size.
332      */
333     private Integer getVarSize(Map hash, String sb) 
334     throws IOException {
335 
336         // get object form hashtable
337         String vectName = sb.substring(0, sb.length()-SIZE.length());
338         Object obj = getVarObject(hash, vectName);
339 
340         if (obj == null)
341             return new Integer(0);
342 
343         if (obj instanceof java.util.List)
344             return new Integer(((List)obj).size());
345 
346         return new Integer(1);
347     }
348 
349 
350     /**
351      * Returns the last element of a vector. In case of scalar,
352      * returns the object itself. In case of null returns null
353      * and in case of vector returns null if size is zero or the 
354      * last element. 
355      */
356     private Object getLastVar(Map hash, String sb) 
357     throws IOException {
358 
359         String vectName = sb.substring(0, sb.length()-LAST.length());
360         Object obj = getVarObject(hash, vectName);
361 
362         if (obj == null)
363             return null;
364 
365         if (obj instanceof java.util.List) {
366             List v = (List)obj;
367             if (v.size() == 0)
368                 return null;
369             else
370                 return v.get(v.size() - 1);
371         }
372 
373         return obj;
374     }
375 
376 
377     /**
378      * Get vector element at a particular index. Tokenize  
379      * <code>indexBlock</code> string and returns the element.
380      */
381     private Object getIndexedObject(Object obj, String indexBlock) {
382 
383         if (obj == null)
384             return null;
385 
386         StringTokenizer st = new StringTokenizer(indexBlock, ",");
387         while (st.hasMoreTokens()) {
388             String initVar = st.nextToken().trim();
389             int ind = Integer.parseInt(initVar);
390             obj = elementAt(obj, ind); 
391         }
392 
393         st = null;
394         return obj;
395     }
396 
397 
398     /**
399      * Returns an element at a particular element. If the object
400      * is null, returns null. If the object is a <code>List</code>
401      * returns null if the index is out of range or the element at
402      * that index. 
403      */
404     private Object elementAt(Object obj, int index) {
405 
406         if (obj == null)
407             return null;
408 
409         if (obj instanceof java.util.List) {
410 
411             int size = ((List)obj).size();
412             if (index >= size || index < 0)
413                 return null;
414 
415             return((List)obj).get(index);
416         }
417 
418         if (index == 0)
419             return obj;
420 
421         return null;
422     }
423 
424 
425     /**
426      * Variable name can be formed dynamically. This function is
427      * used to get the actual variable name - by resolving inner
428      * variable name. Keywords will be treated as variables. The
429      * variable within '{' and '}' will be replaced by its string
430      * representation.
431      */
432     private String getActualVarString(Map hash, String initVar) 
433     throws IOException {
434 
435         int ind = 0;
436         char c, c1;
437         int size = initVar.length();
438         StringBuffer sb = new StringBuffer(64);
439 
440         while (ind < size) {
441             c = initVar.charAt(ind);
442             ++ind;
443 
444             if (c == '$') {
445                 c1 = initVar.charAt(ind);
446                 ind++;
447 
448                 // not a variable
449                 if (c1 != '{') {
450                     sb.append(c);
451                     sb.append(c1);
452                     continue;
453                 }
454 
455                 // variable and/or keyword
456                 String var = getBlock(initVar, ind);
457                 ind += var.length() + 1;
458                 Object obj = getVarObject(hash, var);
459                 if (obj != null)
460                     sb.append(obj);
461             } else {
462                 sb.append(c);   
463             }
464         }
465 
466         return sb.toString();
467     }
468 
469 
470     /**
471      * Process IF block. Returns the index of the next char. 
472      */
473     private int processIfBlock(OutputStream out, Map hash, int start)
474     throws IOException {
475 
476         // condition block starts - evaluate condition
477         int index = ignoreWhitespace(start);
478         if (mbyContent[index] != '{')
479             throw new IOException("IF condition block not found.");
480 
481         Block block = getBlock(index + 1);
482         boolean bTrue = isTrueCondition(hash, block);
483 
484         // get IF action block
485         index = block.nextIndex();
486         index = ignoreWhitespace(index);
487         if (mbyContent[index] != '{')
488             throw new IOException("Invalid char after IF");
489         block = getBlock(index + 1);
490         if (bTrue)
491             processNormalBlock(out, hash, block);
492 
493 
494         // ignore ELSE block if available and condition is TRUE.
495         index = block.nextIndex();
496         char c = (char)mbyContent[index];
497         if (bTrue) {
498             if (c == '{') {
499                 block = getBlock(index + 1);
500                 return block.nextIndex();  
501             }
502         }
503 
504         // else block starts
505         if (!bTrue) {
506 
507             // check ELSE block
508             if (c == '{') {
509                 block = getBlock(index + 1);
510                 processNormalBlock(out, hash, block);
511                 return block.nextIndex();
512             }
513         }
514 
515         return index;
516     }
517 
518 
519     /**
520      * Evaluate condition block in IF statement.
521      */
522     private boolean isTrueCondition(Map hash, Block block) 
523     throws IOException {
524 
525         String sb = block.toString();   
526         StringTokenizer st = new StringTokenizer(sb, mstWhitespaces);
527 
528         // get left side
529         if (!st.hasMoreTokens())
530             throw new IOException("First token not found in IF");
531         Object leftObj = getCondVarObject(hash, st.nextToken());
532 
533         // get condition operator
534         if (!st.hasMoreTokens())
535             throw new IOException("IF condition operator not found");
536         String condition = st.nextToken();
537 
538         // get rightside
539         if (!st.hasMoreTokens())
540             throw new IOException("Last token not found in IF");
541         Object rightObj = getCondVarObject(hash, st.nextToken());   
542 
543         // now evaluate
544         if (leftObj == null && rightObj == null) return condition.equals(EQ);
545         if (leftObj == null || rightObj == null) return condition.equals(NE);
546         if (condition.equals(EQ)) return rightObj.toString().equals(leftObj.toString());
547         if (condition.equals(NE)) return !rightObj.toString().equals(leftObj.toString());
548         if (condition.equals(IN)) return ifObjExists(leftObj, rightObj);
549 
550         throw new IOException("Invalid IF condition operator: " + condition);   
551     }
552 
553 
554     /**
555      * Check object existance. If any vector element is null, 
556      * that element is ignored.
557      */
558     private boolean ifObjExists(Object left, Object right) {
559 
560         // vector
561         if (right instanceof java.util.List) {
562             int sz = ((List)right).size();
563             for (int i=0; i<sz; i++) {
564                 Object obj = ((List)right).get(i);
565                 if (obj == null)
566                     continue;
567                 if (obj.toString().equals(left.toString()))
568                     return true;
569 
570             }
571             return false;
572         }
573 
574         // not a vector
575         return right.toString().equals(left.toString());
576     }
577 
578 
579     /**
580      * Process iterator block. Here <code>index</code> is the 
581      * starting index of the iterator initialization block.
582      */
583     private int processItrBlock(OutputStream out, Map hash, int index) 
584     throws IOException {
585 
586         int ind = ignoreWhitespace(index);
587         if (mbyContent[ind] != '{')
588             throw new IOException("ITR initialization block not found.");
589 
590         // get ITR init block  
591         Block sb = getBlock(ind + 1);
592         String str = sb.toString();
593         StringTokenizer st = new StringTokenizer(str, mstWhitespaces);
594 
595         // get ITR main block
596         ind = ignoreWhitespace(sb.nextIndex());
597         if (mbyContent[ind] != '{')
598             throw new IOException("ITR main block not found.");
599         sb = getBlock(ind + 1);
600         int retValue = sb.nextIndex();
601 
602 
603         // get init variable name
604         if (!st.hasMoreTokens())
605             throw new IOException("Variable name not found in ITR block");
606         String var = st.nextToken();
607 
608         // get start point
609         int start = getIntegerValue(hash, st);
610         if (start == -1)
611             return retValue;
612 
613         // strip "THRU"
614         if (!st.hasMoreTokens())
615             throw new IOException("Invalid ITR block - string THRU not found");
616         if (!st.nextToken().equals(THRU))
617             throw new IOException("Expecting THRU in ITR block");
618 
619         // get length
620         int len = getIntegerValue(hash, st);
621         if (len == -1)
622             return retValue;
623 
624         // process ITR main loop
625         for (int i=0; i<len; i++) {
626             hash.put(var, new Integer(i+start));
627             processNormalBlock(out, hash, sb);
628         }
629 
630         return retValue;
631     }
632 
633 
634     /**
635      * Get the integer value from <code>StringTokenizer</code>. 
636      * This function is used to get the start index and iteration
637      * count in ITR block. It returns -1 in case of error 
638      * (<code>NumberFormatException</code>).
639      */
640     private int getIntegerValue(Map hash, StringTokenizer st) 
641     throws IOException {
642 
643         // no tokens available
644         if (!st.hasMoreTokens())
645             throw new IOException("Start index not found in ITR block");
646 
647         String initVar = st.nextToken();
648 
649         // Get string representation of the number
650         String initVal = getActualVarString(hash, initVar);
651         try {
652             return Integer.parseInt(initVal);
653         } catch (NumberFormatException ex) {
654             return -1;
655         }
656     }
657 
658 
659     /**
660      * Process FOR block. Here <code>index</code> is the
661      * start index of the FOR initialization block.
662      */
663     private int processForBlock(OutputStream out, Map hash, int index)
664     throws IOException {
665 
666         // read initialization block
667         int ind = ignoreWhitespace(index);
668         char c = (char)mbyContent[ind];
669         if (c != '{')
670             throw new IOException("FOR initialization block not found.");
671 
672         Block sb = getBlock(ind + 1);
673         StringBuffer varNameSb = new StringBuffer();
674         List vec = getForList(hash, varNameSb, sb);
675         String varName = varNameSb.toString();
676 
677 
678         // read for main loop
679         ind = ignoreWhitespace(sb.nextIndex());
680         c = (char)mbyContent[ind];
681         if (c != '{')
682             throw new IOException("FOR main loop not found.");
683 
684         sb = getBlock(ind + 1);
685         int sz = vec.size();
686         for (int i=0; i<sz; i++) {
687             Object ob = vec.get(i);
688             hash.put(varName, ob);
689             processNormalBlock(out, hash, sb);
690         }
691 
692         return sb.nextIndex();
693     }
694 
695 
696     /**
697      * Get for vector and init variable name. It passes the
698      * name of the FOR temporary variable name in 
699      * <code>StringBuffer sb</code>
700      */
701     private List getForList(Map hash, 
702                             StringBuffer sb,
703                             Block block) throws IOException {
704 
705         String initBlock = block.toString();
706         StringTokenizer st = new StringTokenizer(initBlock, mstWhitespaces);
707 
708         // get init variable
709         if (!st.hasMoreTokens())
710             throw new IOException("Invalid FOR block");
711         String initVar = st.nextToken();
712         sb.append(initVar);
713 
714 
715         // ignore string "IN"
716         if (!st.hasMoreTokens())
717             throw new IOException("Invalid FOR block - string IN not found");
718         initVar = st.nextToken();
719         if (!initVar.equals(IN))
720             throw new IOException("Expecting IN, found " + initVar);
721 
722 
723         // get vector variable
724         if (!st.hasMoreTokens())
725             throw new IOException("Invalid FOR block - vector not found");
726         initVar = st.nextToken();
727 
728 
729         // get object form Map if variable
730         Object obj = getCondVarObject(hash, initVar);
731 
732         if (obj instanceof java.util.List)
733             return(List)obj;
734 
735         List vec = new Vector(1);
736         if (obj != null)
737             vec.add(obj);
738 
739         return vec;
740     }
741 
742 
743     /**
744      * Get block from index <code>start</code>.
745      * It starts just after the '{' character.
746      * It reads till it reaches the corresponding '}'. 
747      */
748     private Block getBlock(int start) {
749 
750         char c;     
751         int braceCount = 0;
752         int index = start;
753 
754         while (braceCount != 1) {
755             c = (char)mbyContent[index++];
756 
757             if (c == '{')
758                 --braceCount;
759             else if (c == '}')
760                 ++braceCount;
761         }
762 
763         return new Block(mbyContent, start, index - start - 1);
764     }
765 
766 
767     /**
768      * Get block - this function is used to form the 
769      * variable name dynamically.
770      */
771     private String getBlock(String str, int start) {
772 
773         char c;     
774         int braceCount = 0;
775         int index = start;
776 
777         while (braceCount != 1) {
778             c = str.charAt(index++);
779 
780             if (c == '{')
781                 --braceCount;
782             else if (c == '}')
783                 ++braceCount;
784         }
785 
786         return str.substring(start, index-1);
787     }
788 
789 
790     /**
791      * Ignore whitespace. Returns the first 
792      * non-whitespace char index.
793      */
794     private int ignoreWhitespace(int start) {
795 
796         char c;
797         while (true) {
798             c = (char)mbyContent[start];
799 
800             if (mstWhitespaces.indexOf(c) != -1) {
801                 ++start;
802                 continue;
803             } else
804                 break;
805         }
806         return start;
807     } 
808 
809 
810     /**
811      * Load file - it reads the file and process the block as 
812      * normal block.
813      */
814     public void loadFile(OutputStream out, Map hash) 
815     throws IOException {
816 
817         try {
818             readFile();
819             Block block = new Block(mbyContent);    
820             processNormalBlock(out, hash, block);
821         } catch (ArrayIndexOutOfBoundsException ex) {
822             throw new IOException("Unexpected end of file - bracket mismatch.");
823         } catch (Throwable th) {
824             throw new IOException(th.getLocalizedMessage());
825         }
826     }
827 
828     /**
829      * Get template file name
830      */
831     public String toString() {
832         return mFile.getAbsolutePath();
833     }
834 
835 }