/*
* Copyright 2010 Red Hat, Inc. and/or its affiliates.
*
* Licensed 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.drools.eclipse.editors.completion;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.StringReader;
import java.util.StringTokenizer;
import java.util.regex.Pattern;
import org.eclipse.jdt.core.Signature;
public class CompletionUtil {
protected static final Pattern INCOMPLETED_MVEL_EXPRESSION = Pattern.compile( "[\\.\\(\\{\\[]\\Z",
Pattern.DOTALL );
protected static final Pattern COMPLETED_MVEL_EXPRESSION = Pattern.compile( "]\\)\\}\\]\\Z",
Pattern.DOTALL );
protected static final Pattern MODIFY_PATTERN = Pattern.compile( ".*modify\\s*\\(\\s*(.*)\\s*\\)(\\s*\\{(.*)\\s*\\}?)?",
Pattern.DOTALL );
protected static final Pattern START_OF_NEW_JAVA_STATEMENT = Pattern.compile( ".*[;{}]\\s*",
Pattern.DOTALL );
protected static final Pattern START_OF_METHOD_ARGS = Pattern.compile( ".*[\\(]\\s*",
Pattern.DOTALL );
private CompletionUtil() {
}
/**
* Looks behind, gets stuff after the white space. Basically ripping out the
* last word.
*/
public static String stripLastWord(String prefix) {
if ( "".equals( prefix ) ) {
return prefix;
}
if ( prefix.charAt( prefix.length() - 1 ) == ' ' ) {
return "";
} else {
char[] c = prefix.toCharArray();
int start = 0;
for ( int i = c.length - 1; i >= 0; i-- ) {
if ( Character.isWhitespace( c[i] ) || c[i] == '(' || c[i] == ':' || c[i] == ';' || c[i] == '=' || c[i] == '<' || c[i] == '>' || c[i] == '.' || c[i] == '{' || c[i] == '}' ) {
start = i + 1;
break;
}
}
prefix = prefix.substring( start,
prefix.length() );
return prefix;
}
}
public static String getPreviousExpression(String backText) {
int separator = backText.lastIndexOf( ';' );
if ( separator < 0 ) {
return backText;
}
return backText.substring( 0,
separator + 1 );
}
public static String getLastExpression(String backText) {
StringTokenizer st = new StringTokenizer( backText,
";" );
String last = "";
while ( st.hasMoreTokens() ) {
last = st.nextToken();
}
if ( last.trim().length() == 0 ) {
return backText;
}
return last;
}
public static String getInnerExpression(String backText) {
String last = getLastExpression( backText ).trim();
char[] c = last.toCharArray();
int start = 0;
for ( int i = c.length - 1; i >= 0; i-- ) {
if ( Character.isWhitespace( c[i] ) || c[i] == '(' || c[i] == '+' || c[i] == ')' || c[i] == '[' || c[i] == ']' || c[i] == ':' || c[i] == '=' || c[i] == '<' || c[i] == '>' || c[i] == ',' || c[i] == '{' || c[i] == '}' ) {
start = i + 1;
break;
}
}
last = last.substring( start );
return last;
}
public static int nestedExpressionIndex(char[] chars,
int start,
char type) {
int depth = 1;
char term = type;
switch ( type ) {
case '[' :
term = ']';
break;
case '{' :
term = '}';
break;
case '(' :
term = ')';
break;
}
if ( type == term ) {
for ( start++; start < chars.length; start++ ) {
if ( chars[start] == type ) {
return start;
}
}
} else {
for ( start++; start < chars.length; start++ ) {
if ( chars[start] == '\'' || chars[start] == '"' ) {
//start = captureStringLiteral(chars[start], chars, start, chars.length);
} else if ( chars[start] == type ) {
depth++;
} else if ( chars[start] == term && --depth == 0 ) {
return start;
}
}
}
return -1;
}
public static String stripWhiteSpace(String prefix) {
if ( "".equals( prefix ) ) {
return prefix;
}
if ( prefix.charAt( prefix.length() - 1 ) == ' ' ) {
return "";
} else {
char[] c = prefix.toCharArray();
int start = 0;
for ( int i = c.length - 1; i >= 0; i-- ) {
if ( Character.isWhitespace( c[i] ) ) {
start = i + 1;
break;
}
}
prefix = prefix.substring( start,
prefix.length() );
return prefix;
}
}
/**
* Attempt to enhance a consequence backtext such that it should compile in MVEL
* @param backText
* @return a substring of the back text, that should be compilable without
* syntax errors by the mvel compiler
*
* TODO: add tests and more use
* cases
*/
public static String getCompilableText(String backText) {
String trimed = backText.trim();
if ( trimed.endsWith( ";" ) ) {
// RHS expression should compile if it ends with ; but to get the last object,
// we do no want it, to simulate a return statement
return backText.substring( 0,
backText.length() - 1 );
} else if ( trimed.endsWith( "." ) || trimed.endsWith( "," ) ) {
// RHS expression should compile if it ends with no dot or comma
return backText.substring( 0,
backText.length() - 1 );
} else if ( CompletionUtil.COMPLETED_MVEL_EXPRESSION.matcher( backText ).matches() ) {
// RHS expression should compile if closed. just need to close the
// statement
return backText + ";";
// } else if ( INCOMPLETED_MVEL_EXPRESSION.matcher( backText ).matches() ) {
// // remove the last char and close the statement
// return backText.substring( 0,
// backText.length() - 1 );
} else {
//TODO: support completion within with {} blocks
//TODO: support completion within nested expression.
return backText;
}
}
/*
* propertyname extraction and bean convention methods names checks
*/
public static boolean isGetter(String methodName,
int argCount,
String returnedType) {
return isAccessor( methodName,
argCount,
0,
"get",
returnedType,
Signature.SIG_VOID,
false );
}
public static boolean isSetter(String methodName,
int argCount,
String returnedType) {
return isAccessor( methodName,
argCount,
1,
"set",
returnedType,
Signature.SIG_VOID,
true );
}
public static boolean isIsGetter(String methodName,
int argCount,
String returnedType) {
return isAccessor( methodName,
argCount,
0,
"is",
returnedType,
Signature.SIG_BOOLEAN,
true );
}
/**
* Given a data depicting a method (name, # or params/args, returned type key), tries to return a bean property name derived from that method.
* If a bean property name is not found, the initial method name is returned
* @param methodName
* @param parameterCount
* @param returnType
* @return a bean property name
*/
public static String getPropertyName(String methodName,
int parameterCount,
String returnType) {
if ( methodName == null ) {
return null;
}
String simpleName = methodName.replaceAll( "\\(\\)",
"" );
int prefixLength = 0;
if ( isIsGetter( simpleName,
parameterCount,
returnType ) ) {
prefixLength = 2;
} else if ( isGetter( simpleName,
parameterCount,
returnType ) //
|| isSetter( simpleName,
parameterCount,
returnType ) ) {
prefixLength = 3;
} else {
return methodName;
}
char firstChar = Character.toLowerCase( simpleName.charAt( prefixLength ) );
String propertyName = firstChar + simpleName.substring( prefixLength + 1 );
return propertyName;
}
public static String getPropertyName(String methodName,
char[] signature) {
if ( signature == null || methodName == null ) {
return methodName;
}
int parameterCount = Signature.getParameterCount( signature );
String returnType = new String( Signature.getReturnType( signature ) );
return getPropertyName( methodName,
parameterCount,
returnType );
}
/**
* Given a data depicting a method (name, # or params/args, returned type key), tries to return a writable bean property name derived from that method.
* If a writable (ie setter) bean property name is not found, the initial method name is returned
* @param methodName
* @param parameterCount
* @param returnType
* @return a bean property name
*/
public static String getWritablePropertyName(String methodName,
int parameterCount,
String returnType) {
if ( methodName == null ) {
return null;
}
String simpleName = methodName.replaceAll( "\\(\\)",
"" );
if ( !isSetter( simpleName,
parameterCount,
returnType ) ) {
return methodName;
}
int prefixLength = 3;
char firstChar = Character.toLowerCase( simpleName.charAt( prefixLength ) );
String propertyName = firstChar + simpleName.substring( prefixLength + 1 );
return propertyName;
}
public static String getWritablePropertyName(String methodName,
char[] signature) {
if ( signature == null || methodName == null ) {
return methodName;
}
int parameterCount = Signature.getParameterCount( signature );
String returnType = new String( Signature.getReturnType( signature ) );
return getWritablePropertyName( methodName,
parameterCount,
returnType );
}
/**
* Determine if the given method is a bean accessor (ie getter/setter)
* @param methodName
* @param actualParameterCount
* @param requiredParameterCount
* @param prefix
* @param returnType
* @param requiredReturnType
* @param includeType
* @return true if the method is a bean accessor, false otherwise
*/
private static boolean isAccessor(String methodName,
int actualParameterCount,
int requiredParameterCount,
String prefix,
String returnType,
String requiredReturnType,
boolean includeType) {
//must be longer than the accessor prefix
if ( methodName.length() < prefix.length() + 1 ) {
return false;
}
//start with get, set or is
if ( !methodName.startsWith( prefix ) ) {
return false;
}
if ( actualParameterCount != requiredParameterCount ) {
return false;
}
//if we check for the returned type, verify that the returned type is of the cirrect type signature
if ( includeType ) {
if ( !requiredReturnType.equals( returnType ) ) {
return false;
}
} else {
if ( requiredReturnType.equals( returnType ) ) {
return false;
}
}
return true;
}
public static boolean isStartOfNewStatement(String text,
String prefix) {
String javaTextWithoutPrefix = text.substring( 0,
text.length() - prefix.length() );
if ( "".equals( javaTextWithoutPrefix.trim() ) || CompletionUtil.START_OF_NEW_JAVA_STATEMENT.matcher( javaTextWithoutPrefix ).matches() ) {
return true;
}
return false;
}
public static String getLastLine(String text) {
final BufferedReader reader = new BufferedReader( new StringReader( text ) );
String line = null;
String lastLine = null;
try {
while ( (line = reader.readLine()) != null ) {
if ( line.trim().length() > 0 ) {
lastLine = line;
}
}
} catch ( final IOException e ) {
// should never happen, it's just reading over a string.
}
return lastLine;
}
/**
* COMPENSATES FOR LACK OF getSimpleName IN java.lang.Class
* Borrowed and adpated from MVEL's org.mvel.util.ParseTools.getSimpleClassName(Class)
* @param cls -- class reference
* @return Simple name of class
*/
public static String getSimpleClassName(Class<?> cls) {
int lastIndex = cls.getName().lastIndexOf( '$' );
if ( lastIndex < 0 ) {
lastIndex = cls.getName().lastIndexOf( '.' );
}
if ( cls.isArray() ) {
return cls.getName().substring( lastIndex + 1 ) + "[]";
} else {
return cls.getName().substring( lastIndex + 1 );
}
}
public static String getTextWithoutPrefix(final String javaText,
final String prefix) {
int endIndex = javaText.length() - prefix.length();
String javaTextWithoutPrefix = javaText;
//javaText can be an empty string.
if ( endIndex >= 0 ) {
javaTextWithoutPrefix = javaText.substring( 0,
endIndex );
}
return javaTextWithoutPrefix;
}
public static boolean isStartOfDialectExpression(String text) {
return "".equals( text.trim() ) || CompletionUtil.START_OF_NEW_JAVA_STATEMENT.matcher( text ).matches();
}
public static boolean isStartOfMethodArgsExpression(String text) {
return CompletionUtil.START_OF_NEW_JAVA_STATEMENT.matcher( text ).matches();
}
}