/*
* Licensed to Luca Cavanna (the "Author") under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. Elastic Search licenses this
* file to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.shell.console.completer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* @author Luca Cavanna
*
* Helper that extracts the name of the objects that are useful to provide suggestions
* from the current user input, given also the cursor position
*
*/
public class IdentifierTokenizer {
/**
* Extracts the name of the objects that are useful in order to provide suggestions
* given the current user input and the cursor position.
* Strips out the dots and the arguments from any function call
* e.g. Given Requests.indexRequests.index('index_name').ty will return [Requests,indexRequest,index,ty]
* @param buffer the user input
* @param cursor the cursor position
* @return the list of the identifiers useful to provide suggestions
*/
public List<Identifier> tokenize(String buffer, int cursor) {
//Looks backward and collects a list of identifiers
//We stop as soon as we find something that is not a java identifier part or . or ).
//We also strip out the arguments from any function call
List<Identifier> identifiers = new ArrayList<Identifier>();
Identifier identifier = new Identifier(cursor);
int m = cursor - 1;
while (m >= 0) {
//identifier = new Identifier(m);
char c = buffer.charAt(m--);
//we keep adding chars to the same name till the identifier is finished
//using isJavaIdentifierPart even though it isn't always correct. Javascript identifiers have different rules,
//and the first character is quite different too.
if (Character.isJavaIdentifierPart(c)) {
if (identifier.getLastPosition() == cursor) {
identifier = new Identifier(cursor - 1);
}
identifier.append(c);
continue;
}
if (c == ']' && mightBeIdentifier(buffer, m)) {
//TODO only support for identifiers among square brackets and quotation marks, no support for arrays
int m2 = m - 1;
char c2 = buffer.charAt(m2);
boolean foundIdentifier = false;
Identifier id = new Identifier(m+1).incrementOffset(2);
while (m2 > 0) {
if (c2=='"' || c2 == '\'') {
if (buffer.charAt(--m2)=='[') {
id.incrementOffset(2);
foundIdentifier = true;
}
break;
}
id.append(c2);
c2 = buffer.charAt(--m2);
}
if (foundIdentifier) {
identifiers.add(id.reverseName());
m = m2 - 1;
identifier = new Identifier(m);
continue;
} else {
break;
}
}
//when the identifier is finished we add it to the list of identifiers found and we start with a new identifier
identifiers.add(identifier.reverseName());
identifier = new Identifier(m);
if (c == ' ') {
Identifier newKeyword = extractNewKeyword(buffer, m);
if (newKeyword != null) {
identifiers.add(newKeyword.reverseName());
break;
}
}
if (c != '.') {
break;
}
//if we've found ). we ignore all the arguments from the function call, till the previous open bracket
if (c == '.' && m > 1 && buffer.charAt(m) == ')') {
int m2 = stripArguments(buffer, m - 1);
if (m2 < 0) {
break;
}
identifier.incrementOffset(m - m2);
m = m2;
}
}
if (identifier.getOffset() > 0 || identifiers.isEmpty()) {
//adding the last identifier
identifiers.add(identifier.reverseName());
}
//no need to reverseName if empty or only one element
if (identifiers.size() > 1) {
Collections.reverse(identifiers);
}
return identifiers;
}
private int stripArguments(String buffer, int cursor) {
int innerBrackets = 0;
while (cursor >= 0) {
char c2 = buffer.charAt(cursor);
if (c2 == ')') {
innerBrackets++;
}
if (c2 == '(' && --innerBrackets == -1) {
return --cursor;
}
cursor--;
}
return cursor;
}
private boolean mightBeIdentifier(String buffer, int cursor) {
//not enough characters for an identifier among square brackets e.g. a['b']
if (cursor < 4) {
return false;
}
char c = buffer.charAt(cursor);
return c == '"' | c == '\'';
}
private Identifier extractNewKeyword(String buffer, int cursor) {
int m = cursor;
char c = buffer.charAt(m);
//ignores any additional whitespace e.g. new Test().
while (c == ' ' && m > 0) {
m--;
c = buffer.charAt(m);
}
char[] newOp = new char[]{'n', 'e', 'w'};
int pos = newOp.length - 1;
while (m > 0 && pos >= 0) {
if (c != newOp[pos--]) {
return null;
}
c = buffer.charAt(--m);
}
//new can be the first word, otherwise it must not appear after a java identifier part (e.g. abcnew )
if ( (m == 0 && pos == 0)
|| !Character.isJavaIdentifierPart(buffer.charAt(m)) ) {
Identifier id = new Identifier("wen", cursor);
int diff = cursor - m;
if (diff>3) {
id.incrementOffset(diff - 3);
}
return id;
}
return null;
}
}