blob: 78c3c6ca858cefd26dc7f665b680de9eb2b907e1 [file] [log] [blame]
cbateman6d3359f2006-11-28 20:23:25 +00001/*******************************************************************************
2 * Copyright (c) 2006 Oracle Corporation.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the Eclipse Public License v1.0
5 * which accompanies this distribution, and is available at
6 * http://www.eclipse.org/legal/epl-v10.html
7 *
8 * Contributors:
9 * Cameron Bateman/Oracle - initial API and implementation
10 *
11 ********************************************************************************/
12
13package org.eclipse.jst.jsf.core.internal.contentassist.el;
14
cbateman6b737e82007-12-13 00:38:26 +000015import org.eclipse.jface.text.Region;
16import org.eclipse.jst.jsf.context.structureddocument.IStructuredDocumentContext;
17import org.eclipse.jst.jsf.context.symbol.ISymbol;
cbateman6d3359f2006-11-28 20:23:25 +000018import org.eclipse.jst.jsp.core.internal.java.jspel.ASTAddExpression;
19import org.eclipse.jst.jsp.core.internal.java.jspel.ASTAndExpression;
20import org.eclipse.jst.jsp.core.internal.java.jspel.ASTChoiceExpression;
21import org.eclipse.jst.jsp.core.internal.java.jspel.ASTEqualityExpression;
22import org.eclipse.jst.jsp.core.internal.java.jspel.ASTExpression;
23import org.eclipse.jst.jsp.core.internal.java.jspel.ASTFunctionInvocation;
24import org.eclipse.jst.jsp.core.internal.java.jspel.ASTLiteral;
25import org.eclipse.jst.jsp.core.internal.java.jspel.ASTMultiplyExpression;
26import org.eclipse.jst.jsp.core.internal.java.jspel.ASTOrExpression;
27import org.eclipse.jst.jsp.core.internal.java.jspel.ASTRelationalExpression;
28import org.eclipse.jst.jsp.core.internal.java.jspel.ASTUnaryExpression;
29import org.eclipse.jst.jsp.core.internal.java.jspel.ASTValue;
30import org.eclipse.jst.jsp.core.internal.java.jspel.ASTValuePrefix;
31import org.eclipse.jst.jsp.core.internal.java.jspel.ASTValueSuffix;
32import org.eclipse.jst.jsp.core.internal.java.jspel.JSPELParser;
33import org.eclipse.jst.jsp.core.internal.java.jspel.JSPELParserConstants;
34import org.eclipse.jst.jsp.core.internal.java.jspel.JSPELParserVisitor;
35import org.eclipse.jst.jsp.core.internal.java.jspel.ParseException;
36import org.eclipse.jst.jsp.core.internal.java.jspel.SimpleNode;
37import org.eclipse.jst.jsp.core.internal.java.jspel.Token;
itrimbleef2843e2011-04-13 22:21:31 +000038import org.eclipse.jst.jsp.core.internal.java.jspel.TokenMgrError;
cbateman6d3359f2006-11-28 20:23:25 +000039
40/**
41 * Consumes an EL expression and converts into a completion prefix
42 *
43 * @author cbateman
44 *
45 */
46public final class ContentAssistParser
47{
48 /**
49 * @param relativePosition -- 1-based position in elText (first position is 1)
50 * @param elText
51 * @return a content assist strategy for the given position and el expression
52 * or null if one cannot be determined
53 */
54 public static ContentAssistStrategy getPrefix(final int relativePosition, final String elText)
55 {
56 if (elText == null)
57 {
58 return null;
59 }
cbateman6b737e82007-12-13 00:38:26 +000060 else if ("".equals(elText.trim())) //$NON-NLS-1$
cbateman6d3359f2006-11-28 20:23:25 +000061 {
cbateman6b737e82007-12-13 00:38:26 +000062 return new IdCompletionStrategy("", ""); //$NON-NLS-1$//$NON-NLS-2$
cbateman6d3359f2006-11-28 20:23:25 +000063 }
64
cbateman6b737e82007-12-13 00:38:26 +000065 PrefixVisitor visitor = getVisitorForPosition(relativePosition, elText);
66 return visitor != null? visitor.getPrefix() : null;
67 }
68
69 /**
70 * Get symbol and symbol region at given position in el string
71 * @param context - IStructuredDocumentContext
72 * @param relativePosition - position in el string
73 * @param elText - el string
74 * @return SymbolInfo. May be null.
75 */
76 public static SymbolInfo getSymbolInfo(IStructuredDocumentContext context, final int relativePosition, final String elText) {
77 if (elText == null || "".equals(elText.trim())) //$NON-NLS-1$
78 {
79 return null;
80 }
81 PrefixVisitor visitor = getVisitorForPosition(relativePosition, elText);
82 if (visitor != null) {
83 SymbolInfo symbolInfo = visitor.getSymbolInfo(context);
84 if (symbolInfo != null) {
85 Region r = symbolInfo.getRelativeRegion();
86 if (relativePosition > r.getOffset() && relativePosition <= r.getOffset() + r.getLength()) {
87 return symbolInfo;
88 }
89 }
90 }
91 return null;
92 }
93
94 private static PrefixVisitor getVisitorForPosition(final int relativePosition,
95 final String elText) {
96 final java.io.StringReader reader = new java.io.StringReader(elText);
cbateman6d3359f2006-11-28 20:23:25 +000097 final JSPELParser parser = new JSPELParser(reader);
98
99 try
100 {
101 final ASTExpression expr = parser.Expression();
102 final PrefixVisitor visitor = new PrefixVisitor(relativePosition, elText);
103 expr.jjtAccept(visitor, null);
cbateman6b737e82007-12-13 00:38:26 +0000104 return visitor;
cbateman6d3359f2006-11-28 20:23:25 +0000105 }
106 catch (ParseException pe)
107 {
108 // TODO: handle parser by using current and expected tokens
cbateman6b737e82007-12-13 00:38:26 +0000109 return null;
cbateman6d3359f2006-11-28 20:23:25 +0000110 }
itrimbleef2843e2011-04-13 22:21:31 +0000111 catch (TokenMgrError tme)
112 {
113 // TODO: handle parser by using current and expected tokens
114 return null;
115 }
cbateman6b737e82007-12-13 00:38:26 +0000116 }
117
118 private static String substring(String s, Region r) {
119 return s.substring(r.getOffset(), r.getOffset() + r.getLength());
cbateman6d3359f2006-11-28 20:23:25 +0000120 }
121
122 private static class PrefixVisitor implements JSPELParserVisitor
123 {
124 private final int _relativePos;
125 private final String _fullText;
126
cbateman6b737e82007-12-13 00:38:26 +0000127 private String _symbolPrefix; // = null; initialized as tree is visited
cbateman6d3359f2006-11-28 20:23:25 +0000128 private int _prefixType;
129 private boolean _prefixResolved; // = false; set to true when the prefix is resolved
cbateman6b737e82007-12-13 00:38:26 +0000130 private int _symbolStartPos = 1; // first char has position 1
131 private int _symbolEndPos = 0;
cbateman6d3359f2006-11-28 20:23:25 +0000132
133 PrefixVisitor(final int relativePos, final String fullText)
134 {
135 _relativePos = relativePos;
136 _fullText = fullText;
137 }
138
139 /**
140 * @return the prefix if resolved or null if not resolved
141 */
142 public ContentAssistStrategy getPrefix()
143 {
144 if (_prefixResolved)
145 {
146 switch(_prefixType)
147 {
148 case ContentAssistStrategy.PREFIX_TYPE_DOT_COMPLETION:
cbateman6b737e82007-12-13 00:38:26 +0000149 return new FunctionCompletionStrategy(_symbolPrefix, getProposalStart());
cbateman6d3359f2006-11-28 20:23:25 +0000150
151 case ContentAssistStrategy.PREFIX_TYPE_ID_COMPLETION:
cbateman6b737e82007-12-13 00:38:26 +0000152 return new IdCompletionStrategy(_symbolPrefix, getProposalStart());
cbateman6d3359f2006-11-28 20:23:25 +0000153
154 case ContentAssistStrategy.PREFIX_TYPE_EMPTY_EXPRESSION:
cbateman6b737e82007-12-13 00:38:26 +0000155 return new IdCompletionStrategy("", getProposalStart()); //$NON-NLS-1$
cbateman6d3359f2006-11-28 20:23:25 +0000156
157 default:
158 // do nothing; fall-through to return null
159 }
160 }
161
162 return null;
163 }
164
cbateman6b737e82007-12-13 00:38:26 +0000165 /**
166 * @param context - IStructuredDocumentContext
167 * @return symbol and symbol region if resolved, null otherwise
168 */
169 public SymbolInfo getSymbolInfo(IStructuredDocumentContext context) {
170 if (_prefixResolved && _symbolStartPos < _symbolEndPos) {
171 Region region = new Region(_symbolStartPos - 1, _symbolEndPos - _symbolStartPos + 1);
172 ISymbol symbol = null;
173 switch (_prefixType) {
174 case ContentAssistStrategy.PREFIX_TYPE_ID_COMPLETION:
175 symbol = SymbolResolveUtil.getSymbolForVariable(context, substring(_fullText, region));
176 break;
177 case ContentAssistStrategy.PREFIX_TYPE_DOT_COMPLETION:
178 symbol = SymbolResolveUtil.getSymbolForVariableSuffixExpr(context, _symbolPrefix + "." + substring(_fullText, region), _symbolEndPos == _fullText.length()); //$NON-NLS-1$
179 break;
180 }
181 if (symbol != null) {
182 return new SymbolInfo(symbol, region);
183 }
184 }
185 return null;
186 }
187
188 private String getProposalStart() {
189 if (_symbolStartPos <= _relativePos) {
190 return _fullText.substring(_symbolStartPos - 1, _relativePos - 1);
191 }
192 return ""; //$NON-NLS-1$
193 }
194
cbateman6d3359f2006-11-28 20:23:25 +0000195 public Object visit(ASTAddExpression node, Object data)
196 {
197 return node.childrenAccept(this, data);
198 }
199
cbateman6b737e82007-12-13 00:38:26 +0000200 public Object visit(ASTAndExpression node, Object data)
cbateman6d3359f2006-11-28 20:23:25 +0000201 {
202 return node.childrenAccept(this, data);
203 }
204
205 public Object visit(ASTChoiceExpression node, Object data)
206 {
207 return node.childrenAccept(this, data);
208 }
209
210 public Object visit(ASTEqualityExpression node, Object data)
211 {
212 return node.childrenAccept(this, data);
213 }
214
215 public Object visit(ASTExpression node, Object data)
216 {
217 return node.childrenAccept(this, data);
218 }
219
220 public Object visit(ASTFunctionInvocation node, Object data)
221 {
222 return node.childrenAccept(this, data);
223 }
224
225 public Object visit(ASTLiteral node, Object data)
226 {
227 return node.childrenAccept(this, data);
228 }
229
230 public Object visit(ASTMultiplyExpression node, Object data)
231 {
232 return node.childrenAccept(this, data);
233 }
234
235 public Object visit(ASTOrExpression node, Object data)
236 {
237 return node.childrenAccept(this, data);
238 }
239
240 public Object visit(ASTRelationalExpression node, Object data)
241 {
242 return node.childrenAccept(this, data);
243 }
244
245 public Object visit(ASTUnaryExpression node, Object data)
246 {
247 return node.childrenAccept(this, data);
248 }
249
250 public Object visit(ASTValue node, Object data)
251 {
252 // we're only in this value expr if it contains the cursor
253 if (testContainsCursor(node))
254 {
255 return node.childrenAccept(this, data);
256 }
257
258 return null;
259 }
260
261 public Object visit(ASTValuePrefix node, Object data)
262 {
263 // for now, only concern ourselves with simple (identifier) prefixes
264 if (!_prefixResolved
265 && node.jjtGetNumChildren() == 0
266 && node.getFirstToken().kind == JSPELParserConstants.IDENTIFIER)
267 {
cbateman6b737e82007-12-13 00:38:26 +0000268 _symbolPrefix = node.getFirstToken().image;
cbateman6d3359f2006-11-28 20:23:25 +0000269
270 if (testContainsCursor(node))
271 {
272 // if the cursor is on this id, we don't need to visit
273 // further since we know both the prefix -- the id -- and
274 // the type -- it's an id completion
275 _prefixType = ContentAssistStrategy.PREFIX_TYPE_ID_COMPLETION;
cbateman6b737e82007-12-13 00:38:26 +0000276 _symbolStartPos = node.getFirstToken().beginColumn;
277 _symbolEndPos = node.getFirstToken().endColumn;
cbateman6d3359f2006-11-28 20:23:25 +0000278 _prefixResolved = true;
279 }
280 }
281 return node.childrenAccept(this, data);
282 }
283
284 public Object visit(ASTValueSuffix node, Object data)
285 {
286 // for now, only deal with the simple .id suffix
287 Token lastToken = node.getLastToken();
288 if (node.jjtGetNumChildren() == 0)
289 {
290 if (!_prefixResolved
291 && node.getFirstToken().kind == JSPELParserConstants.DOT)
292 {
293 if (lastToken.kind == JSPELParserConstants.IDENTIFIER)
294 {
295 if (testContainsCursor(node))
296 {
297 _prefixType = ContentAssistStrategy.PREFIX_TYPE_DOT_COMPLETION;
298 int proposalStartLength = _relativePos - lastToken.beginColumn;
299 if (proposalStartLength < 0) { // Cursor after firstToken start but before lastToken start?
300 proposalStartLength = 0;
301 }
cbateman6b737e82007-12-13 00:38:26 +0000302 _symbolStartPos = lastToken.beginColumn;
303 _symbolEndPos = lastToken.endColumn;
cbateman6d3359f2006-11-28 20:23:25 +0000304 _prefixResolved = true;
305 }
306 // only include this suffix on the path if the cursor is
307 // further to the right. Thus for x.^y we get a prefix "x"
308 // and for x.y.^z we get "x.y" since this the part we must
309 // resolve the prefix for
310 else
311 {
cbateman6b737e82007-12-13 00:38:26 +0000312 _symbolPrefix += node.getFirstToken().image + lastToken.image;
cbateman6d3359f2006-11-28 20:23:25 +0000313 }
314 }
315 else if (lastToken == node.getFirstToken())
316 {
317 if (testCursorImmediatelyAfter(node))
318 {
319 _prefixType = ContentAssistStrategy.PREFIX_TYPE_DOT_COMPLETION;
cbateman6b737e82007-12-13 00:38:26 +0000320 _symbolStartPos = lastToken.endColumn + 1;
321 _symbolEndPos = lastToken.endColumn;
cbateman6d3359f2006-11-28 20:23:25 +0000322 _prefixResolved = true;
323 }
324 }
325 }
326
327 return null;
328 }
329
330 if (node.getFirstToken().kind == JSPELParserConstants.LBRACKET)
331 {
332 // try to support ca inside the brackets
333 node.childrenAccept(this, data);
334 }
335
336 Object retValue = node.childrenAccept(this, data);
337
338 if (!_prefixResolved)
339 {
340 // if we haven't resolved the prefix yet, then we need
341 // to append this suffix value
cbateman6b737e82007-12-13 00:38:26 +0000342 _symbolPrefix += _fullText.substring(node.getFirstToken().beginColumn-1, node.getLastToken().endColumn);
cbateman6d3359f2006-11-28 20:23:25 +0000343 }
344
345 return retValue;
346 }
347
348 public Object visit(SimpleNode node, Object data)
349 {
350 return node.childrenAccept(this, data);
351 }
352
353 private boolean testCursorImmediatelyAfter(SimpleNode node)
354 {
355 return node.getLastToken().endColumn == _relativePos-1;
356 }
357
358 /**
359 * "Containing a cursor" here is deemed to mean that current cursor
360 * position as indicated by _relativePos, is either directly before, on or
361 * directly after an expression. For example, in a Value expression like
362 *
363 * x x x . y y y . z z z
364 * ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^
365 * 1 2 3 4 5 6 7 8 9 0 1 2
366 *
367 * Position's 1-4 are on xxx, 5-8 are on yyy and 9-12 are on zzz
368 *
369 * @param node
370 * @return true if the node "contains the cursor" (see above)
371 */
372 private boolean testContainsCursor(SimpleNode node)
373 {
374 return (node.getFirstToken().beginColumn <= _relativePos
375 && node.getLastToken().endColumn+1 >= _relativePos);
376
377 }
378 }
379
380 private ContentAssistParser()
381 {
382 // utility class; not instantiable
383 }
384}