*** empty log message ***
[phpeclipse.git] / net.sourceforge.phpeclipse / src / net / sourceforge / phpdt / internal / corext / template / TemplateTranslator.java
1 /*
2  * (c) Copyright IBM Corp. 2000, 2001.
3  * All Rights Reserved.
4  */
5 package net.sourceforge.phpdt.internal.corext.template;
6
7 import java.util.HashMap;
8 import java.util.Iterator;
9 import java.util.Map;
10 import java.util.Set;
11 import java.util.Vector;
12
13 import org.eclipse.core.runtime.CoreException;
14
15 /**
16  * The template translator translates a string into a template buffer.
17  * The EBNF grammer of a valid string is as follows:
18  * 
19  * <p>
20  * template := (text | escape)*.<br />
21  * text := character - dollar.<br />
22  * escape := dollar ('{' identifier '}' | dollar).<br />
23  * dollar := '$'.<br />
24  * </p>
25  */
26 public class TemplateTranslator {
27
28         // states
29         private static final int TEXT= 0;
30         private static final int ESCAPE= 1;
31         private static final int IDENTIFIER= 2;
32
33         // tokens
34         private static final char ESCAPE_CHARACTER= '$';
35         private static final char IDENTIFIER_BEGIN= '{';
36         private static final char IDENTIFIER_END= '}';
37
38         /** a buffer for the translation result string */
39     private final StringBuffer fBuffer= new StringBuffer();    
40     /** position offsets of variables */
41     private final Vector fOffsets= new Vector();
42     /** position lengths of variables */
43     private final Vector fLengths= new Vector();
44
45         /** the current parsing state */
46     private int fState;    
47     /** the last translation error */
48     private String fErrorMessage;
49
50         /**
51          * Returns an error message if an error occured for the last translation, <code>null</code>
52          * otherwise.
53          */
54         public String getErrorMessage() {
55             return fErrorMessage;
56         }
57
58         /**
59          * Translates a template string to <code>TemplateBuffer</code>. <code>null</code>
60          * is returned if there was an error. <code>getErrorMessage()</code> retrieves the
61          * associated error message.
62          * 
63          * @param string the string to translate.
64          * @return returns the template buffer corresponding to the string, <code>null</code>
65          *         if there was an error.
66          * @see #getErrorMessage()
67          */
68         public TemplateBuffer translate(String string) throws CoreException {
69
70             fBuffer.setLength(0);
71             fOffsets.clear();
72             fLengths.clear();
73             fState= TEXT;
74             fErrorMessage= null;
75             
76                 if (!parse(string))
77                         return null;
78                         
79                 switch (fState) {
80                 case TEXT:
81                         break;
82                 
83                 // illegal, but be tolerant
84                 case ESCAPE:
85                         fErrorMessage= TemplateMessages.getString("TemplateTranslator.error.incomplete.variable"); //$NON-NLS-1$
86                         fBuffer.append(ESCAPE_CHARACTER);
87                         return null;
88                                 
89                 // illegal, but be tolerant
90                 case IDENTIFIER:
91                         fErrorMessage= TemplateMessages.getString("TemplateTranslator.error.incomplete.variable"); //$NON-NLS-1$
92                         fBuffer.append(ESCAPE_CHARACTER);
93                         return null;            
94                 }                       
95                 
96                 int[] offsets= new int[fOffsets.size()];
97                 int[] lengths= new int[fLengths.size()];
98                 
99                 for (int i= 0; i < fOffsets.size(); i++) {
100                         offsets[i]= ((Integer) fOffsets.get(i)).intValue();
101                         lengths[i]= ((Integer) fLengths.get(i)).intValue();
102                 }
103
104                 String translatedString= fBuffer.toString();
105                 TemplatePosition[] variables= findVariables(translatedString, offsets, lengths);
106
107                 return new TemplateBuffer(translatedString, variables);
108         }
109         
110         private static TemplatePosition[] findVariables(String string, int[] offsets, int[] lengths) {
111
112                 Map map= new HashMap();
113                 
114                 for (int i= 0; i != offsets.length; i++) {
115                     int offset= offsets[i];
116                     int length= lengths[i];
117                     
118                     String content= string.substring(offset, offset + length);
119                     Vector vector= (Vector) map.get(content);
120                     if (vector == null) {
121                         vector= new Vector();
122                         map.put(content, vector);
123                     }               
124                     vector.add(new Integer(offset));
125                 }
126                 
127                 TemplatePosition[] variables= new TemplatePosition[map.size()];
128                 int k= 0;
129                 
130                 Set keys= map.keySet();
131                 for (Iterator i= keys.iterator(); i.hasNext(); ) {
132                         String name= (String) i.next();                 
133                         Vector vector= (Vector) map.get(name);
134                         
135                         int[] offsets_= new int[vector.size()];
136                         for (int j= 0; j != offsets_.length; j++)
137                                 offsets_[j]= ((Integer) vector.get(j)).intValue();
138                                 
139                         variables[k]= new TemplatePosition(name, name, offsets_, name.length());
140                         k++;
141                 }
142                 
143                 return variables;
144         }
145
146         /** internal parser */
147         private boolean parse(String string) {
148
149                 for (int i= 0; i != string.length(); i++) {
150                     char ch= string.charAt(i);
151                         
152                         switch (fState) {
153                         case TEXT:
154                                 switch (ch) {
155                                 case ESCAPE_CHARACTER:
156                                         fState= ESCAPE;
157                                         break;
158                                         
159                                 default:
160                                         fBuffer.append(ch);
161                                         break;
162                                 }
163                                 break;
164                                 
165                         case ESCAPE:
166                                 switch (ch) {
167                                 case ESCAPE_CHARACTER:
168                                         fBuffer.append(ch);
169                                         fState= TEXT;
170                                         break;
171                                 
172                                 case IDENTIFIER_BEGIN:
173                                         fOffsets.add(new Integer(fBuffer.length()));
174                                         fState= IDENTIFIER;
175                                         break;
176                                         
177                                 default:
178                                         // illegal single escape character, but be tolerant
179                                         fErrorMessage= TemplateMessages.getString("TemplateTranslator.error.incomplete.variable"); //$NON-NLS-1$
180                                         fBuffer.append(ESCAPE_CHARACTER);
181                                         fBuffer.append(ch);
182                                         fState= TEXT;
183                                         return false;
184                                 }
185                                 break;
186
187                         case IDENTIFIER:
188                                 switch (ch) {
189                                 case IDENTIFIER_END:
190                                         int offset = ((Integer) fOffsets.get(fOffsets.size() - 1)).intValue();
191                                         fLengths.add(new Integer(fBuffer.length() - offset));
192                                         fState= TEXT;
193                                         break;
194                                 
195                                 default:
196                                         if (!Character.isUnicodeIdentifierStart((char) ch) &&
197                                                 !Character.isUnicodeIdentifierPart((char) ch))
198                                         {
199                                                 // illegal identifier character
200                                                 fErrorMessage= TemplateMessages.getString("TemplateTranslator.error.invalid.identifier"); //$NON-NLS-1$
201                                                 return false;
202                                         }
203                                 
204                                         fBuffer.append(ch);
205                                         break;
206                                 }
207                                 break;
208                         }
209                 }
210                 
211                 return true;
212         }
213
214 }