KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > eclipse > emf > codegen > jet > JETReader


1 /**
2  * <copyright>
3  *
4  * Copyright (c) 2002-2005 IBM Corporation and others.
5  * All rights reserved. This program and the accompanying materials
6  * are made available under the terms of the Eclipse Public License v1.0
7  * which accompanies this distribution, and is available at
8  * http://www.eclipse.org/legal/epl-v10.html
9  *
10  * Contributors:
11  * IBM - Initial API and implementation
12  *
13  * </copyright>
14  *
15  * $Id: JETReader.java,v 1.7 2005/06/12 13:19:04 emerks Exp $
16  */

17 package org.eclipse.emf.codegen.jet;
18
19
20 import java.io.CharArrayWriter JavaDoc;
21 import java.io.IOException JavaDoc;
22 import java.io.InputStream JavaDoc;
23 import java.io.InputStreamReader JavaDoc;
24 import java.io.UnsupportedEncodingException JavaDoc;
25 import java.util.ArrayList JavaDoc;
26 import java.util.HashMap JavaDoc;
27 import java.util.List JavaDoc;
28
29 import org.eclipse.emf.codegen.CodeGenPlugin;
30
31
32 /**
33  * JETReader is an input buffer for the JSP parser. It should allow
34  * unlimited lookahead and pushback. It also has a bunch of parsing
35  * utility methods for understanding htmlesque thingies.
36  */

37 public class JETReader
38 {
39   protected char startTagInitialChar = '<';
40   protected char endTagInitialChar = '%';
41   protected char endTagFinalChar = '>';
42   protected JETMark current = null;
43   protected String JavaDoc master = null;
44   protected List JavaDoc sourceFiles = new ArrayList JavaDoc();
45   protected List JavaDoc baseURIs = new ArrayList JavaDoc();
46   protected int size = 0;
47   protected boolean trimExtraNewLine = true;
48
49   public JETReader(String JavaDoc baseURI, String JavaDoc locationURI, InputStream JavaDoc inputStream, String JavaDoc encoding) throws JETException
50   {
51     stackStream(baseURI, locationURI, inputStream, encoding);
52   }
53   
54   public JETReader(String JavaDoc locationURI, InputStream JavaDoc inputStream, String JavaDoc encoding) throws JETException
55   {
56     this(null, locationURI, inputStream, encoding);
57   }
58
59   public String JavaDoc getFile(int fileid)
60   {
61     return (String JavaDoc) sourceFiles.get(fileid);
62   }
63   
64   public String JavaDoc getBaseURI(int fileid)
65   {
66     return (String JavaDoc) baseURIs.get(fileid);
67   }
68
69   public void stackStream(String JavaDoc locationURI, InputStream JavaDoc iStream, String JavaDoc encoding) throws JETException
70   {
71     stackStream(null, locationURI, iStream, encoding);
72   }
73   
74   /**
75    * Stack a stream for parsing
76    * @param iStream Stream ready to parse
77    * @param encoding Optional encoding to read the file.
78    */

79   public void stackStream(String JavaDoc baseURI, String JavaDoc locationURI, InputStream JavaDoc iStream, String JavaDoc encoding) throws JETException
80   {
81     InputStreamReader JavaDoc reader = null;
82     try
83     {
84       // Until the encoding can be specified within the template
85
// we need to assume an encoding capable of working with any character set.
86
if (encoding == null)
87       {
88         encoding = "UTF8";
89       }
90
91       // Register the file, and read its content:
92
//
93
int fileid = registerSourceFile(locationURI);
94       registerBaseURI(baseURI);
95       reader = new InputStreamReader JavaDoc(iStream, encoding);
96       CharArrayWriter JavaDoc writer = new CharArrayWriter JavaDoc();
97       char buf[] = new char[1024];
98       for (int i = 0; (i = reader.read(buf)) != -1; )
99       {
100         // Remove zero width non-breaking space, which may be used as a byte order marker,
101
// and may be ignored according to the Unicode FAQ: http://www.unicode.org/unicode/faq/utf_bom.html#38
102
//
103
if (buf[0] == '\uFEFF')
104         {
105           writer.write(buf, 1, i - 1);
106         }
107         else
108         {
109           writer.write(buf, 0, i);
110         }
111       }
112       writer.close();
113       if (current == null)
114       {
115         current = new JETMark(this, writer.toCharArray(), fileid, locationURI, encoding);
116       }
117       else
118       {
119         current.pushStream(writer.toCharArray(), fileid, locationURI, encoding);
120       }
121     }
122     catch (UnsupportedEncodingException JavaDoc exception)
123     {
124       throw new JETException(exception);
125     }
126     catch (IOException JavaDoc exception)
127     {
128       throw new JETException(exception);
129     }
130     finally
131     {
132       if (reader != null)
133       {
134         try
135         {
136           reader.close();
137         }
138         catch (Exception JavaDoc exception)
139         {
140           throw new JETException(exception);
141         }
142       }
143     }
144   }
145
146   public boolean popFile()
147   {
148     // Is stack created ? (will happen if the JET file we're looking at is missing.
149
//
150
if (current == null)
151     {
152       return false;
153     }
154
155     // Restore parser state:
156
//
157
size--;
158
159     return current.popStream();
160   }
161
162   /**
163    * Register a new source file.
164    * This method is used to implement file inclusion. Each included file
165    * gets a uniq identifier (which is the index in the array of source files).
166    * @return The index of the now registered file.
167    */

168   protected int registerSourceFile(String JavaDoc file)
169   {
170     sourceFiles.add(file);
171     ++this.size;
172     return sourceFiles.size() - 1;
173   }
174   
175   /**
176    * Register a new baseURI.
177    * This method is used to implement file inclusion. Each included file
178    * gets a uniq identifier (which is the index in the array of base URIs).
179    * @return The index of the now registered file.
180    */

181   protected void registerBaseURI(String JavaDoc baseURI)
182   {
183     baseURIs.add(baseURI);
184   }
185
186   /**
187    * Returns whether more input is available. If the end of the buffer for an included file is reached, it will return
188    * to the context of the previous file, and return whether more input is available from there. In this case, if
189    * trimExtraNewLine is true, then an unwanted extra newline character will be suppressed. We consider the first
190    * newline in the buffer we are returning to be unwanted if the ending buffer already has at least one trailing
191    * newline.
192   */

193   public boolean hasMoreInput()
194   {
195     if (current.cursor < current.stream.length)
196     {
197       return true;
198     }
199     
200     boolean nl = hasTrailingNewLine();
201     while (popFile())
202     {
203       if (current.cursor < current.stream.length)
204       {
205         if (trimExtraNewLine && nl)
206         {
207           skipNewLine();
208         }
209         return true;
210       }
211     }
212     return false;
213   }
214
215   /**
216    * Tests whether the current stream has at least one trailing newline, optionally followed by spaces.
217    */

218   protected boolean hasTrailingNewLine()
219   {
220     char[] stream = current.stream;
221
222     for (int i = stream.length - 1; i >= 0; i--)
223     {
224       if (stream[i] == '\n' || stream[i] == '\r')
225       {
226         return true;
227       }
228       else if (stream[i] != ' ')
229       {
230         return false;
231       }
232     }
233     return false;
234   }
235
236   /**
237    * If the next character would be a line break, moves the cursor past it.
238    */

239   protected void skipNewLine()
240   {
241     char[] stream = current.stream;
242     int c = current.cursor;
243
244     if (stream.length > c + 1 && (stream[c] == '\n' && stream[c + 1] == '\r' || stream[c] == '\r' && stream[c + 1] == '\n'))
245     {
246       current.cursor += 2;
247       current.line++;
248       current.col = stream[0] == '\n' ? 1 : 0;
249     }
250     else if (stream.length > c && (stream[c] == '\n' || stream[c] == '\r'))
251     {
252       current.cursor++;
253       current.line++;
254       current.col = 0;
255     }
256   }
257
258   public int nextChar()
259   {
260     if (!hasMoreInput())
261     {
262       return -1;
263     }
264
265     int ch = current.stream[current.cursor];
266
267     ++current.cursor;
268
269     if (ch == '\n')
270     {
271       ++current.line;
272       current.col = 0;
273     }
274     else
275     {
276       ++current.col;
277     }
278     return ch;
279   }
280
281   /**
282    * Gets Content until the next potential JSP element. Because all elements
283    * begin with a '&lt;' we can just move until we see the next one.
284    */

285   public String JavaDoc nextContent()
286   {
287     int cur_cursor = current.cursor;
288     int len = current.stream.length;
289     if (cur_cursor == len) return "";
290     char ch;
291
292     // pure obsfuscated genius!
293
while (++current.cursor < len && (ch = current.stream[current.cursor]) != startTagInitialChar)
294     {
295       if (ch == '\n')
296       {
297         ++current.line;
298         current.col = 0;
299       }
300       else
301       {
302         ++current.col;
303       }
304     }
305
306     return new String JavaDoc(current.stream, cur_cursor, current.cursor-cur_cursor);
307   }
308
309   public char[] getChars(JETMark start, JETMark stop)
310   {
311     JETMark oldstart = mark();
312     reset(start);
313     CharArrayWriter JavaDoc writer = new CharArrayWriter JavaDoc();
314     while (!stop.equals(mark()))
315     {
316           writer.write(nextChar());
317     }
318     writer.close();
319     reset(oldstart);
320     return writer.toCharArray();
321   }
322
323   public int peekChar()
324   {
325     return current.stream[current.cursor];
326   }
327
328   public JETMark mark()
329   {
330     return new JETMark(current);
331   }
332
333   public void reset(JETMark mark)
334   {
335     current = new JETMark(mark);
336   }
337
338   public boolean matchesIgnoreCase(String JavaDoc string)
339   {
340     JETMark mark = mark();
341     int ch = 0;
342     int i = 0;
343     do
344     {
345       ch = nextChar();
346       if (Character.toLowerCase((char) ch) != string.charAt(i++))
347       {
348         reset(mark);
349         return false;
350       }
351     } while (i < string.length());
352     reset(mark);
353     return true;
354   }
355
356   public boolean matches(String JavaDoc string)
357   {
358     JETMark mark = mark();
359     int ch = 0;
360     int i = 0;
361     do
362     {
363       ch = nextChar();
364       if (((char) ch) != string.charAt(i++))
365       {
366         reset(mark);
367         return false;
368       }
369     } while (i < string.length());
370     reset(mark);
371     return true;
372   }
373
374   public void advance(int n)
375   {
376     while (--n >= 0)
377     nextChar();
378   }
379
380   public int skipSpaces()
381   {
382     int i = 0;
383     while (isSpace())
384     {
385       ++i;
386       nextChar();
387     }
388     return i;
389   }
390
391   /**
392    * Skip until the given string is matched in the stream.
393    * When returned, the context is positioned past the end of the match.
394    * @param limit The String to match.
395    * @return A non-null <code>JETMark</code> instance if found,
396    * <strong>null</strong> otherwise.
397    */

398   public JETMark skipUntil(String JavaDoc limit)
399   {
400     JETMark ret = null;
401     int limlen = limit.length();
402     int ch;
403
404     skip:
405     for (ret = mark(), ch = nextChar(); ch != -1; ret = mark(), ch = nextChar())
406     {
407       if (ch == limit.charAt(0))
408       {
409         for (int i = 1; i < limlen; i++)
410         {
411           if (Character.toLowerCase((char) nextChar()) != limit.charAt(i))
412           {
413             continue skip;
414           }
415         }
416         return ret;
417       }
418     }
419     return null;
420   }
421
422   protected boolean isSpace()
423   {
424     return peekChar() <= ' ';
425   }
426
427   /**
428    * Parse a space delimited token.
429    * If quoted the token will consume all characters up to a matching quote,
430    * otherwise, it consumes up to the first delimiter character.
431    * @param quoted If <strong>true</strong> accept quoted strings.
432    */

433   public String JavaDoc parseToken(boolean quoted) throws JETException
434   {
435     StringBuffer JavaDoc stringBuffer = new StringBuffer JavaDoc();
436     skipSpaces();
437     stringBuffer.setLength(0);
438
439     int ch = peekChar();
440
441     if (quoted)
442     {
443       if (ch == '"' || ch == '\'')
444       {
445         char endQuote = ch == '"' ? '"' : '\'';
446
447         // Consume the open quote:
448
//
449
ch = nextChar();
450         for (ch = nextChar(); ch != -1 && ch != endQuote; ch = nextChar())
451         {
452           if (ch == '\\')
453           {
454             ch = nextChar();
455           }
456           stringBuffer.append((char) ch);
457         }
458
459         // Check end of quote, skip closing quote:
460
//
461
if (ch == -1)
462         {
463           throw new JETException(CodeGenPlugin.getPlugin().getString("jet.error.quotes.unterminated", new Object JavaDoc [] { mark().toString()}));
464         }
465       }
466       else
467       {
468         throw new JETException(CodeGenPlugin.getPlugin().getString("jet.error.attr.quoted", new Object JavaDoc [] { mark().toString() }));
469       }
470     }
471     else
472     {
473       if (!isDelimiter())
474       {
475         // Read value until delimiter is found:
476
do
477         {
478           ch = nextChar();
479           // Take care of the quoting here.
480
if (ch == '\\')
481           {
482             if (peekChar() == '"' || peekChar() == '\'' || peekChar() == endTagFinalChar || peekChar() == endTagInitialChar)
483             {
484               ch = nextChar();
485             }
486           }
487           stringBuffer.append((char) ch);
488         }
489         while (!isDelimiter());
490       }
491     }
492     return stringBuffer.toString();
493   }
494
495   /**
496    * Parse an attribute/value pair, and store it in provided hash table.
497    * The attribute/value pair is defined by:
498    * <pre>
499    * av := spaces token spaces '=' spaces token spaces
500    * </pre>
501    * Where <em>token</em> is defined by <code>parseToken</code> and
502    * <em>spaces</em> is defined by <code>skipSpaces</code>.
503    * The name is always considered case insensitive, hence stored in its
504    * lower case version.
505    * @param into The HashMap instance to save the result to.
506    */

507   protected void parseAttributeValue(HashMap JavaDoc into) throws JETException
508   {
509     // Get the attribute name:
510
//
511
skipSpaces();
512     String JavaDoc name = parseToken(false);
513
514     // Check for an equal sign:
515
//
516
skipSpaces();
517     if (peekChar() != '=')
518     {
519       throw new JETException(CodeGenPlugin.getPlugin().getString("jet.error.attr.novalue", new Object JavaDoc[] { name, mark().toString() }));
520     }
521     nextChar();
522
523     // Get the attribute value:
524
//
525
skipSpaces();
526     String JavaDoc value = parseToken(true);
527     skipSpaces();
528
529     // Add the binding to the provided hashtable:
530
//
531
into.put(name, value);
532   }
533
534   /**
535    * Parse some tag attributes for Beans.
536    * The stream is assumed to be positioned right after the tag name. The
537    * syntax recognized is:
538    * <pre>
539    * tag-attrs := empty | attr-list ("&gt;" | "--&gt;" | %&gt;)
540    * attr-list := empty | av spaces attr-list
541    * empty := spaces
542    * </pre>
543    * Where <em>av</em> is defined by <code>parseAttributeValue</code>.
544    * @return A HashMap mapping String instances (variable names) into
545    * String instances (variable values).
546    */

547   public HashMap JavaDoc parseTagAttributesBean() throws JETException
548   {
549     HashMap JavaDoc values = new HashMap JavaDoc(11);
550     while (true)
551     {
552       skipSpaces();
553       int ch = peekChar();
554       if (ch == endTagFinalChar)
555       {
556         // End of the useBean tag.
557
//
558
return values;
559       }
560       else if (ch == '/')
561       {
562         JETMark mark = mark();
563         nextChar();
564
565         // XMLesque Close tags
566
//
567
try
568         {
569           if (nextChar() == endTagFinalChar)
570           {
571             return values;
572           }
573         }
574         finally
575         {
576           reset(mark);
577         }
578       }
579       if (ch == -1)
580       {
581         break;
582       }
583
584       // Parse as an attribute=value:
585
//
586
parseAttributeValue(values);
587     }
588
589     // Reached EOF:
590
//
591
throw new JETException(CodeGenPlugin.getPlugin().getString("jet.error.tag.attr.unterminated", new Object JavaDoc [] { mark().toString() }));
592   }
593
594   /**
595    * Parse some tag attributes.
596    * The stream is assumed to be positioned right after the tag name. The
597    * syntax recognized is:
598    * <pre>
599    * tag-attrs := empty | attr-list ("&gt;" | "--&gt;" | %&gt;)
600    * attr-list := empty | av spaces attr-list
601    * empty := spaces
602    * </pre>
603    * Where <em>av</em> is defined by <code>parseAttributeValue</code>.
604    * @return A HashMap mapping String instances (variable names) into
605    * String instances (variable values).
606    */

607   public HashMap JavaDoc parseTagAttributes()
608       throws JETException
609   {
610     HashMap JavaDoc values = new HashMap JavaDoc(11);
611     while (true)
612     {
613       skipSpaces();
614       int ch = peekChar();
615       if (ch == endTagFinalChar)
616       {
617         return values;
618       }
619
620       if (ch == '-')
621       {
622         JETMark mark = mark();
623         nextChar();
624         // Close NCSA like attributes "->"
625
try
626         {
627           if (nextChar() == '-' && nextChar() == endTagFinalChar)
628           {
629             return values;
630           }
631         }
632         finally
633         {
634           reset(mark);
635         }
636       }
637       else if (ch == endTagInitialChar)
638       {
639         JETMark mark = mark();
640         nextChar();
641         // Close variable like attributes "%>"
642
try
643         {
644           if (nextChar() == endTagFinalChar)
645           {
646             return values;
647           }
648         }
649         finally
650         {
651           reset(mark);
652         }
653       }
654       else if (ch == '/')
655       {
656         JETMark mark = mark();
657         nextChar();
658         // XMLesque Close tags
659
try
660         {
661           if (nextChar() == endTagFinalChar)
662           {
663             return values;
664           }
665         }
666         finally
667         {
668           reset(mark);
669         }
670       }
671       if (ch == -1)
672       {
673         break;
674       }
675       // Parse as an attribute=value:
676
parseAttributeValue(values);
677     }
678     // Reached EOF:
679
throw new JETException(CodeGenPlugin.getPlugin().getString("jet.error.tag.attr.unterminated", new Object JavaDoc [] { mark().toString() }));
680   }
681
682   /**
683    * Parse utils - Is current character a token delimiter ?
684    * Delimiters are currently defined to be =, &gt;, &lt;, ", and ' or any
685    * any space character as defined by <code>isSpace</code>.
686    * @return A boolean.
687    */

688   protected boolean isDelimiter()
689   {
690     if (! isSpace())
691     {
692       int ch = peekChar();
693
694       // Look for a single-char work delimiter:
695
//
696
if (ch == '=' || ch == endTagFinalChar || ch == '"' || ch == '\'' || ch == '/')
697       {
698         return true;
699       }
700
701       // Look for an end-of-comment or end-of-tag:
702
//
703
if (ch == '-')
704       {
705         JETMark mark = mark();
706         if (((ch = nextChar()) == endTagFinalChar) || ((ch == '-') && (nextChar() == endTagFinalChar)))
707         {
708           reset(mark);
709           return true;
710         }
711         else
712         {
713           reset(mark);
714           return false;
715         }
716       }
717       return false;
718     }
719     else
720     {
721       return true;
722     }
723   }
724
725   public void setStartTag(String JavaDoc startTag)
726   {
727     startTagInitialChar = startTag.charAt(0);
728   }
729   
730   public void setEndTag(String JavaDoc endTag)
731   {
732     endTagFinalChar = endTag.charAt(endTag.length() - 1);
733     endTagInitialChar = endTag.charAt(0);
734   }
735 }
736
Popular Tags