/**
* Copyright (c) 2009-2011, The HATS Consortium. All rights reserved.
* This file is licensed under the terms of the Modified BSD License.
*/
package org.absmodels.abs.plugin.wizards;
import java.util.Arrays;
import java.util.List;
import org.absmodels.abs.plugin.editor.ABSEditor;
import org.absmodels.abs.plugin.util.InternalASTNode;
import org.absmodels.abs.plugin.util.UtilityFunctions;
import org.absmodels.abs.plugin.util.UtilityFunctions.EditorPosition;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.IPath;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.TextSelection;
import org.eclipse.ui.PlatformUI;
import abs.frontend.ast.ClassDecl;
import abs.frontend.ast.Decl;
import abs.frontend.ast.InterfaceDecl;
import abs.frontend.ast.MainBlock;
import abs.frontend.ast.ModuleDecl;
/**
* Utility class used by all ABS wizards
*
* @author cseise
*
*/
public class WizardUtil {
private WizardUtil() {
// no instantiations allowed
}
// Wizard Error Messages
/**
* Enumerations for describing wizard error conditions
*/
public static enum ErrorType {
NO_ERROR(null),
ERROR_NO_NAME("Empty name field."),
ERROR_DOT_END("The name must not end with a '.'!"),
ERROR_DUPLICATE_NAME("Duplicate name: "),
ERROR_KEYWORD("The name is a reserved keyword."),
ERROR_NO_VALID_FILE("Please select a valid ABS file."),
ERROR_NO_VALID_MODULE("Please select a valid ABS module."),
ERROR_NO_UPPER_CASE("The name must begin with an upper case character!"),
ERROR_INVALID_NAME("Please enter a valid name.");
private String description;
private String informationString = "";
ErrorType(String desc) {
description = desc;
}
/**
* @return returns a description of the error condition
*/
public String getDescription() {
return description;
}
/**
* Sets the new information String used for detailing the error message.
* Currently, the information String is only used to store the name of a
* duplicate module
*
* @param info
* The new information string
*/
public void setInformationString(String info) {
this.informationString = info;
}
/**
* Returns additional information to the error condition.
*
* Currently, the information String is only used to store the name of a
* duplicate module
*
* @return Additional information to the error condition or empty String
* if there are no additional information
*
*/
public String getInformationString() {
return informationString;
}
}
/**
* Enumeration for determining the strings that have to be inserted on the creation of new
* <ul>
* <li>Modules</li>
* <li>Classes</li>
* <li>Interfaces</li>
* </ul>
*
*/
public static enum InsertType{
INSERT_MODULE("\nmodule ",";\n\t",11),
INSERT_CLASS("\n class ","\n { \n\t \n }\n",14),
INSERT_INTERFACE("\n interface ","\n { \n\t \n }\n",18);
String insertBN;
String insertAN;
private int insertOff;
InsertType(String insertBeforeName, String insertAfterName, int insertOffset){
insertBN = insertBeforeName;
insertAN = insertAfterName;
insertOff = insertOffset;
}
public int getInsertOffset() {
return insertOff;
}
public int getInsertOffset(String name) {
return (name == null) ? getInsertOffset() : insertOff + name.length();
}
public String getInsertBeforeName() {
return insertBN;
}
public String getInsertAfterName() {
return insertAN;
}
public String getInsertionString(String name){
return insertBN + name + insertAN;
}
}
private static final List<String> keywordList = Arrays.asList(abs.frontend.parser.Keywords.getKeywords());
private static final boolean isKeyword(String str){
return keywordList.contains(str);
}
/**
* Method for validating a module name.<p>
* <br/>
* A valid module name
* <ul>
* <li>does not contain invalid characters</li>
* <li>is not empty</li>
* <li>begins with a upper case letter</li>
* <li>is not an ABS keyword</li>
* <li>the name does not end with a '.'</li>
* </ul>
* @param name
* The module name that is to be validated
* @return ErrorType.NO_ERROR if the name is valid and other ErrorType enum
* members for the corresponding error condition.
*
* @see org.absmodels.abs.plugin.wizards.WizardUtil.ErrorType
*/
public static ErrorType validateModule(String name) {
if ("".equals(name)) {
return ErrorType.ERROR_NO_NAME;
}
boolean matches = name.matches("[A-Z a-z _ \\.]*");
if (WizardUtil.isKeyword(name)) {
return ErrorType.ERROR_KEYWORD;
}else if (name.length() > 1 && name.charAt(name.length() - 1) == '.') {
return ErrorType.ERROR_DOT_END;
}else if (matches && name.length() > 0 && Character.getType(name.charAt(0)) != Character.UPPERCASE_LETTER) {
return ErrorType.ERROR_NO_UPPER_CASE;
}else if (!matches) {
return ErrorType.ERROR_INVALID_NAME;
}
return ErrorType.NO_ERROR;
}
/**
* Method for validating a class or interface name.<p>
*
* A valid class or interface name
* <ul>
* <li>only contains letters or digits as defined by {@link java.lang.Character#isLetterOrDigit(char)}</li>
* </ul>
* @param name
* The class or interface name that is to be validated
* @return ErrorType.NO_ERROR if the name is valid and the ErrorType
* ERROR_INVALID_NAME if the name is invalid.
*
* @see org.absmodels.abs.plugin.wizards.WizardUtil.ErrorType
*/
public static ErrorType validate(String name) {
if ("".equals(name))
return ErrorType.ERROR_NO_NAME;
if (isKeyword(name)) {
return ErrorType.ERROR_KEYWORD;
}
char[] charName = name.toCharArray();
for (int i = 0; i < charName.length; i++) {
if (!Character.isLetterOrDigit(charName[i])) {
return ErrorType.ERROR_INVALID_NAME;
} else if (i == 0 && !Character.isUpperCase(charName[i])) {
return ErrorType.ERROR_NO_UPPER_CASE;
}
}
return ErrorType.NO_ERROR;
}
/**
* Same as {@link #validate(String)} but additionally checks, whether the specified
* class name already exists in the given module declaration
*
* @param text
* Name to be checked.
* @param decl
* Target ModuleDecl.
* @return In case of a duplicate name, this method returns
* ErrorType.ERROR_NO_DUPLICATE_NAME with the duplicate name set in
* its information string.
*/
public static ErrorType validateClass(String text, ModuleDecl decl) {
if (decl != null) {
for (Decl d : decl.getDecls()) {
if (d instanceof ClassDecl && d.getName().equals(text)) {
ErrorType type = ErrorType.ERROR_DUPLICATE_NAME;
type.setInformationString(text);
return type;
}
}
}
return validate(text);
}
/**
* Same as {@link #validate(String)} but additionally checks, whether the specified
* interface name already exists in the given module declaration
*
* @param text
* Name to be checked.
* @param decl
* Target ModuleDecl.
* @return In case of a duplicate name, this method returns
* ErrorType.ERROR_NO_DUPLICATE_NAME with the duplicate name set in
* its information string.
*/
public static ErrorType validateInterface(String text, ModuleDecl decl){
if (decl != null) {
for (Decl d : decl.getDecls()) {
if (d instanceof InterfaceDecl && d.getName().equals(text)) {
ErrorType type = ErrorType.ERROR_DUPLICATE_NAME;
type.setInformationString(text);
return type;
}
}
}
return validate(text);
}
/**
* Determines the insertion position for a new interface/class declaration.
* If the given ModuleDecl has a MainBlock the insertion point is right
* before the main block. Otherwise, the insertion point is at the end of
* the ModuleDecl.
*
* @param d
* The target document
* @param astNode
* The target ModuleDecl
* @return the insertion position of the new class or interface.
* @throws BadLocationException
* if the location of the module declaration or the respective
* main block could not be found
*/
public static int getInsertionPosition(IDocument d, InternalASTNode<ModuleDecl> astNode) throws BadLocationException {
Assert.isNotNull(d);
Assert.isNotNull(astNode);
final ModuleDecl m = astNode.getASTNode();
Assert.isNotNull(m,"ModuleDecl argument is null!");
final EditorPosition position;
/* Classes and interfaces go before product lines / products in the grammar.
* We don't want to generate invalid input for the user.
*/
if (m.hasBlock()) {
MainBlock block = m.getBlock();
position = UtilityFunctions.getPosition(block);
return d.getLineOffset(position.getLinestart()) + position.getColstart();
} else {
position = UtilityFunctions.getPosition(m);
return d.getLineOffset(position.getLineend()) + position.getColend();
}
}
/**
* Helper method for formatting the error message of an ErrorType
* @param err
* @return String representation of the given ErrorType
*/
public static String getErrorDescription(ErrorType err){
String errorMessage = err.getDescription();
if (err == ErrorType.ERROR_DUPLICATE_NAME){
errorMessage += err.getInformationString();
}
return errorMessage;
}
/**
* Saves the editor and sets the selection to the offset insertOffset
* @param editor
* @param insertOffset
*/
protected static void saveEditorAndGotoOffset(ABSEditor editor, int insertOffset) {
PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage().saveEditor(editor, false);
editor.getSelectionProvider().setSelection(new TextSelection(insertOffset, 0));
}
/**
* Gets the editor for a specific ModuleDecl which is wrapped in an InternalASTNode
* @param m The target ModuleDecl
* @return The according {@link ABSEditor}
*/
protected static ABSEditor getEditorForModuleDecl(InternalASTNode<ModuleDecl> m) {
if (m != null) {
IPath path = UtilityFunctions.getPathOfModuleDecl(m);
return UtilityFunctions.openABSEditorForFile(path, m.getProject());
}
return null;
}
static class EditorData {
ABSEditor editor;
IDocument document;
}
/**
* Returns the current IDocument. ONLY WORKS BECAUSE WE JUST OPENED IT!
*/
static EditorData getDocumentForModuleDecl(InternalASTNode<ModuleDecl> m) {
assert m != null;
EditorData data = new EditorData();
data.editor = getEditorForModuleDecl(m);
if (data.editor != null)
data.document = data.editor.getDocumentProvider().getDocument(data.editor.getEditorInput());
return data;
}
}