/* * Copyright 2015 Nokia Solutions and Networks * Licensed under the Apache License, Version 2.0, * see license.txt file for details. */ package org.robotframework.ide.eclipse.main.plugin.model; import static com.google.common.collect.Lists.newArrayList; import java.io.ObjectStreamException; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.EnumSet; import java.util.List; import java.util.regex.Pattern; import org.eclipse.jface.resource.ImageDescriptor; import org.rf.ide.core.testdata.model.AModelElement; import org.rf.ide.core.testdata.model.ModelType; import org.rf.ide.core.testdata.model.presenter.DocumentationServiceHandler; import org.rf.ide.core.testdata.model.presenter.update.IExecutablesTableModelUpdater; import org.rf.ide.core.testdata.model.presenter.update.KeywordTableModelUpdater; import org.rf.ide.core.testdata.model.table.RobotExecutableRow; import org.rf.ide.core.testdata.model.table.exec.descs.VariableExtractor; import org.rf.ide.core.testdata.model.table.exec.descs.ast.mapping.MappingResult; import org.rf.ide.core.testdata.model.table.exec.descs.ast.mapping.VariableDeclaration; import org.rf.ide.core.testdata.model.table.keywords.KeywordArguments; import org.rf.ide.core.testdata.model.table.keywords.KeywordDocumentation; import org.rf.ide.core.testdata.model.table.keywords.UserKeyword; import org.rf.ide.core.testdata.model.table.keywords.names.EmbeddedKeywordNamesSupport; import org.rf.ide.core.testdata.text.read.IRobotTokenType; import org.rf.ide.core.testdata.text.read.recognizer.RobotToken; import org.rf.ide.core.testdata.text.read.recognizer.RobotTokenType; import org.robotframework.ide.eclipse.main.plugin.RedImages; import org.robotframework.ide.eclipse.main.plugin.project.library.ArgumentsDescriptor; import org.robotframework.ide.eclipse.main.plugin.project.library.KeywordSpecification; import com.google.common.base.Splitter; public class RobotKeywordDefinition extends RobotCodeHoldingElement<UserKeyword> { private static final long serialVersionUID = 1L; public RobotKeywordDefinition(final RobotKeywordsSection parent, final UserKeyword keyword) { super(parent, keyword); } @Override public IExecutablesTableModelUpdater<UserKeyword> getModelUpdater() { return new KeywordTableModelUpdater(); } @Override protected ModelType getExecutableRowModelType() { return ModelType.USER_KEYWORD_EXECUTABLE_ROW; } @Override public RobotTokenType getSettingDeclarationTokenTypeFor(final String name) { return RobotTokenType.findTypeOfDeclarationForKeywordSettingTable(name); } public void link() { final UserKeyword keyword = getLinkedElement(); final List<PrioriterizedKeywordsSettings> settings = newArrayList( EnumSet.allOf(PrioriterizedKeywordsSettings.class)); Collections.sort(settings); // settings for (final PrioriterizedKeywordsSettings setting : settings) { for (final AModelElement<UserKeyword> element : setting.getModelElements(keyword)) { getChildren().add(new RobotDefinitionSetting(this, element)); } } // body for (final RobotExecutableRow<UserKeyword> execRow : keyword.getKeywordExecutionRows()) { getChildren().add(new RobotKeywordCall(this, execRow)); } } @Override public void fixChildrenOrder() { Collections.sort(getChildren(), new Comparator<RobotKeywordCall>() { @Override public int compare(final RobotKeywordCall call1, final RobotKeywordCall call2) { if (call1 instanceof RobotDefinitionSetting && call2 instanceof RobotDefinitionSetting) { final ModelType modelType1 = call1.getLinkedElement().getModelType(); final ModelType modelType2 = call2.getLinkedElement().getModelType(); return PrioriterizedKeywordsSettings.from(modelType1) .compareTo(PrioriterizedKeywordsSettings.from(modelType2)); } else if (call1 instanceof RobotDefinitionSetting && !(call2 instanceof RobotDefinitionSetting)) { return -1; } else if (!(call1 instanceof RobotDefinitionSetting) && call2 instanceof RobotDefinitionSetting) { return 1; } else { // sort is stable so we don't care about those cases return 0; } } }); } @Override public RobotKeywordsSection getParent() { return (RobotKeywordsSection) super.getParent(); } @Override public ImageDescriptor getImage() { return RedImages.getUserKeywordImage(); } public RobotDefinitionSetting getArgumentsSetting() { return findSetting(ModelType.USER_KEYWORD_ARGUMENTS); } public String getDocumentation() { final RobotDefinitionSetting documentationSetting = findSetting(ModelType.USER_KEYWORD_DOCUMENTATION); if (documentationSetting != null) { final KeywordDocumentation documentation = (KeywordDocumentation) documentationSetting.getLinkedElement(); return DocumentationServiceHandler.toShowConsolidated(documentation); } return "<not documented>"; } public ArgumentsDescriptor createArgumentsDescriptor() { return ArgumentsDescriptor.createDescriptor(getArguments()); } private List<String> getArguments() { // embedded arguments are not provided for descriptor or documentation final List<String> args = newArrayList(); final RobotDefinitionSetting argumentsSetting = getArgumentsSetting(); if (argumentsSetting != null) { final KeywordArguments arguments = (KeywordArguments) argumentsSetting.getLinkedElement(); for (final RobotToken token : arguments.getArguments()) { args.add(toPythonicNotation(token)); } } return args; } private String toPythonicNotation(final RobotToken token) { final List<IRobotTokenType> types = token.getTypes(); String text = EmbeddedKeywordNamesSupport.removeRegex(token.getText().toString()); String defaultValue = null; if (text.contains("=")) { final List<String> splitted = Splitter.on('=').limit(2).splitToList(text); text = splitted.get(0); defaultValue = splitted.get(1); } if (types.contains(RobotTokenType.VARIABLES_DICTIONARY_DECLARATION) && text.startsWith("&{") && text.endsWith("}")) { return "**" + text.substring(2, text.length() - 1); } else if (types.contains(RobotTokenType.VARIABLES_LIST_DECLARATION) && text.startsWith("@{") && text.endsWith("}")) { return "*" + text.substring(2, text.length() - 1); } else if (types.contains(RobotTokenType.VARIABLES_SCALAR_DECLARATION) && text.startsWith("${") && text.endsWith("}")) { defaultValue = defaultValue == null ? "" : "=" + defaultValue; return text.substring(2, text.length() - 1) + defaultValue; } else { return text; } } public boolean isDeprecated() { return Pattern.compile("^\\*deprecated[^\\n\\r]*\\*.*").matcher(getDocumentation().toLowerCase()).find(); } public List<VariableDeclaration> getEmbeddedArguments() { final VariableExtractor extractor = new VariableExtractor(); final MappingResult extractedVars = extractor.extract(getLinkedElement().getDeclaration(), getSuiteFile().getName()); return extractedVars.getCorrectVariables(); } public KeywordSpecification createSpecification() { final KeywordSpecification keywordSpecification = new KeywordSpecification(); keywordSpecification.setName(getName()); keywordSpecification.setArguments(getArguments()); keywordSpecification.setFormat("ROBOT"); keywordSpecification.setDocumentation(getDocumentation()); return keywordSpecification; } @Override public void moveChildDown(final RobotKeywordCall keywordCall) { final int index = keywordCall.getIndex(); Collections.swap(getChildren(), index, index + 1); @SuppressWarnings("unchecked") final RobotExecutableRow<UserKeyword> linkedCall = (RobotExecutableRow<UserKeyword>) keywordCall .getLinkedElement(); getLinkedElement().moveDownExecutableRow(linkedCall); } @Override public void moveChildUp(final RobotKeywordCall keywordCall) { final int index = keywordCall.getIndex(); Collections.swap(getChildren(), index, index - 1); @SuppressWarnings("unchecked") final RobotExecutableRow<UserKeyword> linkedCall = (RobotExecutableRow<UserKeyword>) keywordCall .getLinkedElement(); getLinkedElement().moveUpExecutableRow(linkedCall); } @SuppressWarnings("unchecked") private Object readResolve() throws ObjectStreamException { // after deserialization we fix parent relationship in direct children for (final RobotKeywordCall call : getChildren()) { call.setParent(this); ((AModelElement<UserKeyword>) call.getLinkedElement()).setParent(getLinkedElement()); } return this; } @Override public String toString() { // for debugging purposes only return getName(); } static enum PrioriterizedKeywordsSettings { // the order is defined by enums definitions order (see Enum.compareTo() method javadoc) ARGUMENTS, DOCUMENTATION, TAGS, TIMEOUT, TEARDOWN, RETURN, UNKNOWN; public List<? extends AModelElement<UserKeyword>> getModelElements(final UserKeyword keyword) { switch (this) { case ARGUMENTS: return keyword.getArguments(); case DOCUMENTATION: return keyword.getDocumentation(); case TAGS: return keyword.getTags(); case TIMEOUT: return keyword.getTimeouts(); case TEARDOWN: return keyword.getTeardowns(); case RETURN: return keyword.getReturns(); case UNKNOWN: return keyword.getUnknownSettings(); default: return new ArrayList<>(); } } public static PrioriterizedKeywordsSettings from(final ModelType modelType) { switch (modelType) { case USER_KEYWORD_ARGUMENTS: return ARGUMENTS; case USER_KEYWORD_DOCUMENTATION: return DOCUMENTATION; case USER_KEYWORD_TAGS: return TAGS; case USER_KEYWORD_TIMEOUT: return TIMEOUT; case USER_KEYWORD_TEARDOWN: return TEARDOWN; case USER_KEYWORD_RETURN: return RETURN; case USER_KEYWORD_SETTING_UNKNOWN: return UNKNOWN; default: throw new IllegalArgumentException("No setting defined for " + modelType.name()); } } } }