Skip to main content
aboutsummaryrefslogtreecommitdiffstats
blob: 2703b767102c127a4d53570d7d7a0c82bc9456b9 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
/*******************************************************************************
 * Copyright (c) 2000, 2004 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials 
 * are made available under the terms of the Common Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/cpl-v10.html
 * 
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package org.eclipse.jface.text.templates;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.Vector;

/**
 * The template translator translates a string into a template buffer. Regions
 * marked as variables are translated into <code>TemplateVariable</code>s.
 * <p>
 * The EBNF grammar of a valid string is as follows:</p>
 * <p>
 * template := (text | escape)*. <br />
 * text := character - dollar. <br />
 * escape := dollar ('{' identifier '}' | dollar). <br />
 * dollar := '$'. <br />
 * </p>
 * <p>
 * Clients may extend the <code>createVariable</code> method of this class.
 * </p>
 * 
 * @since 3.0
 */
public class TemplateTranslator {

	// states
	private static final int TEXT= 0;
	private static final int ESCAPE= 1;
	private static final int IDENTIFIER= 2;

	// tokens
	private static final char ESCAPE_CHARACTER= '$';
	private static final char IDENTIFIER_BEGIN= '{';
	private static final char IDENTIFIER_END= '}';

	/** a buffer for the translation result string */
    private final StringBuffer fBuffer= new StringBuffer();    
    /** position offsets of variables */
    private final Vector fOffsets= new Vector();
    /** position lengths of variables */
    private final Vector fLengths= new Vector();

	/** the current parsing state */
    private int fState;    
    /** the last translation error */
    private String fErrorMessage;

	/**
	 * Returns an error message if an error occurred for the last translation,
	 * <code>null</code> otherwise.
	 * 
	 * @return the error message if an error occurred during the most recent
	 *         translation, <code>null</code> otherwise
	 */
	public String getErrorMessage() {
	    return fErrorMessage;
	}
	
	/**
	 * Translates a template to a <code>TemplateBuffer</code>. <code>null</code>
	 * is returned if there was an error. <code>getErrorMessage()</code> retrieves the
	 * associated error message.
	 * 
	 * @param template the template to translate.
	 * @return returns the template buffer corresponding to the string, <code>null</code>
	 *         if there was an error.
	 * @see #getErrorMessage()
	 * @throws TemplateException if translation failed
	 */
	public TemplateBuffer translate(Template template) throws TemplateException {
		return translate(template.getPattern());
	}

	/**
	 * Translates a template string to <code>TemplateBuffer</code>. <code>null</code>
	 * is returned if there was an error. <code>getErrorMessage()</code> retrieves the
	 * associated error message.
	 * 
	 * @param string the string to translate.
	 * @return returns the template buffer corresponding to the string, <code>null</code>
	 *         if there was an error.
	 * @see #getErrorMessage()
	 * @throws TemplateException if translation failed
	 */
	public TemplateBuffer translate(String string) throws TemplateException {

	    fBuffer.setLength(0);
	    fOffsets.clear();
	    fLengths.clear();
	    fState= TEXT;
	    fErrorMessage= null;
	    
		if (!parse(string))
			throw new TemplateException(fErrorMessage);
			
		switch (fState) {
		case TEXT:
			break;
		
		// illegal
		case ESCAPE:
			throw new TemplateException(TextTemplateMessages.getString("TemplateTranslator.error.incomplete.variable")); //$NON-NLS-1$
				
		// illegal
		case IDENTIFIER:
			throw new TemplateException(TextTemplateMessages.getString("TemplateTranslator.error.incomplete.variable")); //$NON-NLS-1$
		}			
		
		int[] offsets= new int[fOffsets.size()];
		int[] lengths= new int[fLengths.size()];
		
		for (int i= 0; i < fOffsets.size(); i++) {
			offsets[i]= ((Integer) fOffsets.get(i)).intValue();
			lengths[i]= ((Integer) fLengths.get(i)).intValue();
		}

		String translatedString= fBuffer.toString();
		TemplateVariable[] variables= findVariables(translatedString, offsets, lengths);

		return new TemplateBuffer(translatedString, variables);
	}
	
	private TemplateVariable[] findVariables(String string, int[] offsets, int[] lengths) {

		Map map= new HashMap();
		
		for (int i= 0; i != offsets.length; i++) {
		    int offset= offsets[i];
		    int length= lengths[i];
		    
		    String content= string.substring(offset, offset + length);
		    Vector vector= (Vector) map.get(content);
		    if (vector == null) {
		    	vector= new Vector();
		    	map.put(content, vector);
		    }		    
		    vector.add(new Integer(offset));
		}
		
		TemplateVariable[] variables= new TemplateVariable[map.size()];
		int k= 0;
		
		Set keys= map.keySet();
		for (Iterator i= keys.iterator(); i.hasNext(); ) {
			String name= (String) i.next();			
			Vector vector= (Vector) map.get(name);
			
			int[] offsets_= new int[vector.size()];
			for (int j= 0; j != offsets_.length; j++)
				offsets_[j]= ((Integer) vector.get(j)).intValue();
				
			variables[k]= createVariable(name, name, offsets_);
			k++;
		}
		
		return variables;
	}

	/**
	 * Hook method to create new variables. Subclasses may override to supply their
	 * custom variable type.
	 * <p>
	 * Clients may replace this method.
	 * </p>
	 * 
	 * @param type the type of the new variable.
	 * @param name the name of the new variable.
	 * @param offsets the offsets where the variable occurs in the template
	 * @return a new instance of <code>TemplateVariable</code>
	 */
	protected TemplateVariable createVariable(String type, String name, int[] offsets) {
		return new TemplateVariable(type, name, offsets);
	}

	/**
	 * Internal parser.
	 * 
	 * @param string the string to parse
	 * @return <code>true</code> if parsing was successful
	 */
	private boolean parse(String string) {

		for (int i= 0; i != string.length(); i++) {
		    char ch= string.charAt(i);
			
			switch (fState) {
			case TEXT:
				switch (ch) {
				case ESCAPE_CHARACTER:
					fState= ESCAPE;
					break;
					
				default:
					fBuffer.append(ch);
					break;
				}
				break;
				
			case ESCAPE:
				switch (ch) {
				case ESCAPE_CHARACTER:
					fBuffer.append(ch);
					fState= TEXT;
					break;
				
				case IDENTIFIER_BEGIN:
					fOffsets.add(new Integer(fBuffer.length()));
					fState= IDENTIFIER;
					break;
					
				default:
					// illegal single escape character, but be tolerant
					fErrorMessage= TextTemplateMessages.getString("TemplateTranslator.error.incomplete.variable"); //$NON-NLS-1$
					fBuffer.append(ESCAPE_CHARACTER);
					fBuffer.append(ch);
					fState= TEXT;
					return false;
				}
				break;

			case IDENTIFIER:
				switch (ch) {
				case IDENTIFIER_END:
					int offset = ((Integer) fOffsets.get(fOffsets.size() - 1)).intValue();
					fLengths.add(new Integer(fBuffer.length() - offset));
					fState= TEXT;
					break;
				
				default:
					if (!Character.isUnicodeIdentifierStart(ch) &&
						!Character.isUnicodeIdentifierPart(ch))
					{
						// illegal identifier character
						fErrorMessage= TextTemplateMessages.getString("TemplateTranslator.error.invalid.identifier"); //$NON-NLS-1$
						return false;
					}
				
					fBuffer.append(ch);
					break;
				}
				break;
			}
		}
		
		return true;
	}

}

Back to the top