/******************************************************************************* * Copyright (c) 2004, 2010 BREDEX GmbH. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * BREDEX GmbH - initial API and implementation and/or initial documentation *******************************************************************************/ package org.eclipse.jubula.client.core.utils; import java.io.IOException; import java.io.PushbackReader; import java.io.StringReader; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.Validate; import org.eclipse.jubula.client.core.gen.parser.parameter.lexer.LexerException; import org.eclipse.jubula.client.core.gen.parser.parameter.node.EOF; import org.eclipse.jubula.client.core.gen.parser.parameter.parser.Parser; import org.eclipse.jubula.client.core.gen.parser.parameter.parser.ParserException; import org.eclipse.jubula.client.core.i18n.Messages; import org.eclipse.jubula.client.core.model.IParamDescriptionPO; import org.eclipse.jubula.client.core.model.IParamNodePO; import org.eclipse.jubula.client.core.model.IParameterInterfacePO; import org.eclipse.jubula.client.core.parser.parameter.JubulaParameterLexer; import org.eclipse.jubula.tools.internal.exception.InvalidDataException; import org.eclipse.jubula.tools.internal.messagehandling.MessageIDs; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * @author BREDEX GmbH * @created 16.08.2007 */ public abstract class ParamValueConverter { /** the logger */ private static final Logger LOG = LoggerFactory.getLogger( ParamValueConverter.class); /** * All error codes that should be indicated to the user as "recoverable". * Essentially, this means that the parameter text is not currently valid, * but it may be made valid by appending the correct characters. * This is generally marked in the Jubula UI by highlighting the text field * in yellow. */ private static final Set<Integer> RECOVERABLE_PARSE_ERROR_CODES = new HashSet<Integer>(); static { RECOVERABLE_PARSE_ERROR_CODES.add(MessageIDs.E_ONE_CHAR_PARSE_ERROR); RECOVERABLE_PARSE_ERROR_CODES.add(MessageIDs.E_MISSING_CLOSING_BRACE); } /** string in gui representation */ private String m_guiString = null; /** * <code>m_modelString</code> string in model representation */ private String m_modelString = null; /** * list of tokens for current string * */ private List<IParamValueToken> m_tokens = new ArrayList<IParamValueToken>(); /** * <code>m_errors</code>errors, detected in tokens */ private List<TokenError> m_errors = new ArrayList<TokenError>(1); /** * <code>m_currentNode</code>node contains the parameter with this parameter value - can be null for global context */ private IParameterInterfacePO m_currentNode = null; /** param description associated with current gui- or model string */ private IParamDescriptionPO m_desc = null; /** validator for special validations */ private IParamValueValidator m_validator; /** * describes the state of a single token */ public enum ConvValidationState { /**token has syntax or semantical errors */ invalid, /** unknown state */ notSet, /** currently invalid, but could be valid later */ undecided, /** token is free of syntax or semantical errors */ valid } /** * @param currentNode node with parameter for this parameterValue - can be null for global context * @param desc param description associated with current string (parameter value) * @param validator to use for special validations */ public ParamValueConverter(IParameterInterfacePO currentNode, IParamDescriptionPO desc, IParamValueValidator validator) { if (!isGUI()) { Validate.notNull(currentNode, Messages.NodeForGivenParameterValueMustNotBeNull); } m_currentNode = currentNode; m_desc = desc; m_validator = validator; } /** * default constructor */ protected ParamValueConverter() { // do nothing } /** * @return list of reference names containing in s */ public List<String> getNamesForReferences() { List<String> paramNames = new ArrayList<String>(); for (IParamValueToken token : getAllTokens()) { if (token instanceof RefToken) { RefToken refToken = (RefToken)token; paramNames.add(RefToken.extractCore(refToken.getGuiString())); } } return paramNames; } /** * @return list of variables contained in current string */ public List<String> getVariables() { List<String> variables = new ArrayList<String>(); for (IParamValueToken token : getAllTokens()) { if (token instanceof VariableToken) { variables.add(token.getGuiString()); } } return variables; } /** * @return true, if string contains at least one reference */ public boolean containsReferences() { for (IParamValueToken token : getAllTokens()) { if (token instanceof RefToken) { return true; } } return false; } /** * @return true, if string contains only simple values */ public boolean containsOnlySimpleValues() { for (IParamValueToken token : getTokens()) { if (!(token instanceof SimpleValueToken)) { return false; } } return true; } /** * @param stack current execution stack * @return string for testexecution * @throws InvalidDataException in case of any problem to resolve the token */ public String getExecutionString(List<ExecObject> stack) throws InvalidDataException { StringBuilder builder = new StringBuilder(); for (IParamValueToken token : getTokens()) { builder.append(token.getExecutionString( new ArrayList<ExecObject>(stack))); } return builder.toString(); } /** * parses the provided string and separates it in single tokens */ void createTokens() { String toParse = isGUI() ? getGuiString() : getModelString(); Parser parser = new Parser(new JubulaParameterLexer(new PushbackReader( new StringReader(StringUtils.defaultString(toParse))))); ParsedParameter parsedParam = new ParsedParameter(isGUI(), getCurrentNode(), getDesc()); try { parser.parse().apply(parsedParam); List<IParamValueToken> liste = parsedParam.getTokens(); setTokens(liste); } catch (LexerException e) { createErrors(e, getGuiString()); } catch (ParserException e) { createErrors(e, getGuiString()); } catch (IOException e) { LOG.error(Messages.ParameterParsingErrorOccurred, e); createErrors(e, getGuiString()); } catch (SemanticParsingException e) { createErrors(e, getGuiString()); } } /** calls the validation for each token */ abstract void validateSingleTokens(); /** * @return Returns the tokens. */ public List<IParamValueToken> getTokens() { return m_tokens; } /** * * @return all tokens contained in this converter (including nested tokens). */ protected List<IParamValueToken> getAllTokens() { List<IParamValueToken> tokens = new ArrayList<IParamValueToken>(getTokens()); for (IParamValueToken token : getTokens()) { if (token instanceof INestableParamValueToken) { addAllSubTokens((INestableParamValueToken)token, tokens); } } return tokens; } /** * Recursive method for finding all (recursively) nested tokens. * * @param token The token from which to acquire nested tokens. * @param tokenList The list to which the nested tokens should be added. */ private void addAllSubTokens( INestableParamValueToken token, List<IParamValueToken> tokenList) { IParamValueToken[] nestedTokens = token.getNestedTokens(); tokenList.addAll(Arrays.asList(nestedTokens)); for (IParamValueToken subToken : nestedTokens) { if (subToken instanceof INestableParamValueToken) { addAllSubTokens((INestableParamValueToken)subToken, tokenList); } } } /** * @param tokens The tokens to set. */ protected void setTokens(List<IParamValueToken> tokens) { m_tokens = tokens; } /** * @return Returns the currentNode. */ public IParameterInterfacePO getCurrentNode() { return m_currentNode; } /** * @return Returns the guiString. */ public String getGuiString() { return m_guiString; } /** * @param guiString The guiString to set. */ protected void setGuiString(String guiString) { m_guiString = guiString; } /** * @return Returns the modelString. */ public String getModelString() { return m_modelString; } /** * @return an unmodifiable list of contained RefTokens */ public List<RefToken> getRefTokens() { List <RefToken> refTokens = new ArrayList<RefToken>(); for (IParamValueToken token : getAllTokens()) { if (token instanceof RefToken) { refTokens.add((RefToken)token); } } return Collections.unmodifiableList(refTokens); } /** * @param modelString The modelString to set. */ protected void setModelString(String modelString) { m_modelString = modelString; } /** * @return Returns the desc. */ public IParamDescriptionPO getDesc() { return m_desc; } /** * @param desc The desc to set. */ protected void setDesc(IParamDescriptionPO desc) { m_desc = desc; } /** * @return list of detected errors in tokens */ public List <TokenError> getErrors() { validateSingleTokens(); return m_errors; } /** * @return if currently converted and validated string contains errors */ public boolean containsErrors() { return !(m_errors.isEmpty()); } /** * @param error error to add to error list */ protected void addError(TokenError error) { m_errors.add(error); } /** * @param errors The errors to set. */ protected void setErrors(List<TokenError> errors) { m_errors = errors; } /** * @param currentNode The currentNode to set. */ void setCurrentNode(IParamNodePO currentNode) { m_currentNode = currentNode; } /** * Creates an error based on the provided information and appends that * error to the receiver. * * @param e The exception that caused the error. * @param input The input string for which the exception occurred. */ protected void createErrors(IOException e, String input) { addError(new TokenError(input, MessageIDs.E_GENERAL_PARSE_ERROR, ConvValidationState.invalid)); } /** * Creates an error based on the provided information and appends that * error to the receiver. * * @param e The exception that caused the error. * @param input The input string for which the exception occurred. */ protected void createErrors(LexerException e, String input) { addError(new TokenError(input, MessageIDs.E_GENERAL_PARSE_ERROR, ConvValidationState.invalid)); } /** * Creates an error based on the provided information and appends that * error to the receiver. * * @param e The exception that caused the error. * @param input The input string for which the exception occurred. */ protected void createErrors(ParserException e, String input) { ConvValidationState state = ConvValidationState.invalid; if (e.getToken() instanceof EOF) { // unexpected EOF token means that the error occurred at the // end of the parsed string, which implies that, although the // current string is invalid, it could be made valid by appending // the necessary text state = ConvValidationState.undecided; } addError(new TokenError(input, MessageIDs.E_GENERAL_PARSE_ERROR, state)); } /** * Creates an error based on the provided information and appends that * error to the receiver. * * @param e The exception that caused the error. * @param input The input string for which the exception occurred. */ protected void createErrors(SemanticParsingException e, String input) { if (RECOVERABLE_PARSE_ERROR_CODES.contains(e.getErrorId())) { addError(new TokenError(input, e.getErrorId(), ConvValidationState.undecided)); } else { addError(new TokenError(input, e.getErrorId(), ConvValidationState.invalid)); } } /** * @return Returns the validator. */ IParamValueValidator getValidator() { return m_validator; } /** * creates appropriate TokenError for given ConverterValidationState * @param state computed validation state * @param token validated token */ protected void createTokenError(ConvValidationState state, IParamValueToken token) { if (state == ConvValidationState.invalid || state == ConvValidationState.undecided) { TokenError tokenError = new TokenError(getGuiString(), token.getErrorKey(), state); addError(tokenError); } } /** * @return whether we are in GUI context */ abstract boolean isGUI(); }