blob: 11e7247eef68ada35e2e23c8440c82818c9e2375 [file] [log] [blame]
/*=============================================================================#
# Copyright (c) 2005, 2021 Stephan Wahlbrink and others.
#
# This program and the accompanying materials are made available under the
# terms of the Eclipse Public License 2.0 which is available at
# https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
# which is available at https://www.apache.org/licenses/LICENSE-2.0.
#
# SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
#
# Contributors:
# Stephan Wahlbrink <sw@wahlbrink.eu> - initial API and implementation
#=============================================================================*/
package org.eclipse.statet.r.codegeneration;
import java.util.List;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.jface.text.AbstractDocument;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.Position;
import org.eclipse.jface.text.templates.Template;
import org.eclipse.jface.text.templates.TemplateBuffer;
import org.eclipse.jface.text.templates.TemplateVariable;
import org.eclipse.osgi.util.NLS;
import org.eclipse.statet.ecommons.templates.TemplateMessages;
import org.eclipse.statet.internal.r.ui.RUIPlugin;
import org.eclipse.statet.ltk.model.core.element.LtkModelElement;
import org.eclipse.statet.ltk.model.core.element.SourceUnit;
import org.eclipse.statet.ltk.ui.templates.TemplateUtils;
import org.eclipse.statet.ltk.ui.templates.TemplateUtils.EvaluatedTemplate;
import org.eclipse.statet.r.core.BasicRResourceSourceUnit;
import org.eclipse.statet.r.core.rmodel.RElement;
import org.eclipse.statet.r.core.rmodel.RLangClass;
import org.eclipse.statet.r.core.rmodel.RLangMethod;
import org.eclipse.statet.r.core.rmodel.RLangSlot;
import org.eclipse.statet.r.core.rmodel.RSourceUnit;
import org.eclipse.statet.r.ui.RUI;
/**
* Class that offers access to the code templates contained.
*/
public class CodeGeneration {
/**
* Generates initial content for a new R script file.
*
* @param su the R source unit to create the source for. The unit does not need to exist
* @param lineDelimiter the line delimiter to be used
* @return the new content or <code>null</code> if the template is undefined or empty
* @throws CoreException thrown when the evaluation of the code template fails
*/
public static EvaluatedTemplate getNewRFileContent(final RSourceUnit su, final String lineDelimiter) throws CoreException {
final Template template = RUIPlugin.getInstance().getRCodeGenerationTemplateStore().findTemplate(RCodeTemplateContextType.NEW_RSCRIPTFILE);
if (template == null) {
return null;
}
final RCodeTemplateContext context = new RCodeTemplateContext(
RCodeTemplateContextType.NEW_RSCRIPTFILE_CONTEXTTYPE, su, lineDelimiter);
try {
final TemplateBuffer buffer = context.evaluate(template);
if (buffer == null) {
return null;
}
return new TemplateUtils.EvaluatedTemplate(buffer, lineDelimiter);
}
catch (final Exception e) {
throw new CoreException(new Status(IStatus.ERROR, RUI.BUNDLE_ID, NLS.bind(
TemplateMessages.TemplateEvaluation_error_description, template.getDescription()), e));
}
}
/**
* Generates content for the Roxygen comment for the given function definition
* @param rMethod function element
* @param lineDelimiter the line delimiter to be used
* @return
* @throws CoreException thrown when the evaluation of the code template fails
*/
public static EvaluatedTemplate getCommonFunctionRoxygenComment(final RLangMethod rMethod, final String lineDelimiter) throws CoreException {
final Template template = RUIPlugin.getInstance().getRCodeGenerationTemplateStore()
.findTemplate(RCodeTemplateContextType.ROXYGEN_COMMONFUNCTION_TEMPLATE_ID);
if (template == null) {
return null;
}
final SourceUnit su = rMethod.getSourceUnit();
final RCodeTemplateContext context = new RCodeTemplateContext(
RCodeTemplateContextType.ROXYGEN_COMMONFUNCTION_CONTEXTTYPE, su, lineDelimiter);
context.setRElement(rMethod);
try {
final TemplateBuffer buffer = context.evaluate(template);
if (buffer == null) {
return null;
}
final EvaluatedTemplate data = new EvaluatedTemplate(buffer, lineDelimiter);
final AbstractDocument content = data.startPostEdit();
final StringBuilder tagBuffer = new StringBuilder(64);
final TemplateVariable paramVariable = TemplateUtils.findVariable(buffer,
RCodeTemplateContextType.ROXYGEN_PARAM_TAGS_VAR_NAME );
final Position[] paramPositions = new Position[(paramVariable != null) ? paramVariable.getOffsets().length : 0];
for (int i = 0; i < paramPositions.length; i++) {
paramPositions[i] = new Position(paramVariable.getOffsets()[i], paramVariable.getLength());
content.addPosition(paramPositions[i]);
}
if (paramPositions.length > 0) {
String[] tags = null;
final var parameters= rMethod.getParameters();
if (parameters != null) {
final int count = parameters.size();
tags = new String[count];
for (int i = 0; i < count; i++) {
final String paramName= parameters.get(i).getName();
tagBuffer.append("@param "); //$NON-NLS-1$
if (paramName != null) {
tagBuffer.append(paramName);
}
tagBuffer.append(" "); //$NON-NLS-1$
tags[i] = tagBuffer.toString();
tagBuffer.setLength(0);
}
}
for (final Position pos : paramPositions) {
insertRoxygen(content, pos, tags);
}
}
data.finishPostEdit();
return data;
}
catch (final Exception e) {
throw new CoreException(new Status(IStatus.ERROR, RUI.BUNDLE_ID, NLS.bind(
TemplateMessages.TemplateEvaluation_error_description, template.getDescription()), e));
}
}
/**
* Generates content for the Roxygen comment for the given class definition
* @param rClass class element
* @param lineDelimiter the line delimiter to be used
* @return
* @throws CoreException thrown when the evaluation of the code template fails
*/
public static EvaluatedTemplate getClassRoxygenComment(final RLangClass rClass, final String lineDelimiter) throws CoreException {
final Template template = RUIPlugin.getInstance().getRCodeGenerationTemplateStore()
.findTemplate(RCodeTemplateContextType.ROXYGEN_S4CLASS_TEMPLATE_ID);
if (template == null) {
return null;
}
final SourceUnit su = rClass.getSourceUnit();
final RCodeTemplateContext context = new RCodeTemplateContext(
RCodeTemplateContextType.ROXYGEN_CLASS_CONTEXTTYPE, su, lineDelimiter);
context.setRElement(rClass);
try {
final TemplateBuffer buffer = context.evaluate(template);
if (buffer == null) {
return null;
}
final EvaluatedTemplate data = new EvaluatedTemplate(buffer, lineDelimiter);
final AbstractDocument content = data.startPostEdit();
final StringBuilder tagBuffer = new StringBuilder(64);
final TemplateVariable slotVariable = TemplateUtils.findVariable(buffer,
RCodeTemplateContextType.ROXYGEN_SLOT_TAGS_VAR_NAME );
final Position[] slotPositions = new Position[(slotVariable != null) ? slotVariable.getOffsets().length : 0];
for (int i = 0; i < slotPositions.length; i++) {
slotPositions[i] = new Position(slotVariable.getOffsets()[i], slotVariable.getLength());
content.addPosition(slotPositions[i]);
}
if (slotPositions.length > 0) {
final List<? extends LtkModelElement> slots = rClass.getModelChildren(RElement.R_S4SLOT_FILTER);
final int count = slots.size();
final var tags= new String[count];
for (int i = 0; i < count; i++) {
final RLangSlot slot = (RLangSlot)slots.get(i);
tagBuffer.append("@slot "); //$NON-NLS-1$
tagBuffer.append(slot.getElementName().getDisplayName());
tagBuffer.append(" "); //$NON-NLS-1$
tags[i] = tagBuffer.toString();
tagBuffer.setLength(0);
}
for (final Position pos : slotPositions) {
insertRoxygen(content, pos, tags);
}
}
data.finishPostEdit();
return data;
}
catch (final Exception e) {
throw new CoreException(new Status(IStatus.ERROR, RUI.BUNDLE_ID, NLS.bind(
TemplateMessages.TemplateEvaluation_error_description, template.getDescription()), e));
}
}
/**
* Generates content for the Roxygen comment for the given method definition
* @param rMethod function element
* @param lineDelimiter the line delimiter to be used
* @return
* @throws CoreException thrown when the evaluation of the code template fails
*/
public static EvaluatedTemplate getMethodRoxygenComment(final RLangMethod rMethod, final String lineDelimiter) throws CoreException {
final Template template = RUIPlugin.getInstance().getRCodeGenerationTemplateStore()
.findTemplate(RCodeTemplateContextType.ROXYGEN_S4METHOD_TEMPLATE_ID);
if (template == null) {
return null;
}
final SourceUnit su = rMethod.getSourceUnit();
final RCodeTemplateContext context = new RCodeTemplateContext(
RCodeTemplateContextType.ROXYGEN_METHOD_CONTEXTTYPE, su, lineDelimiter);
context.setRElement(rMethod);
try {
final TemplateBuffer buffer = context.evaluate(template);
if (buffer == null) {
return null;
}
final EvaluatedTemplate data = new EvaluatedTemplate(buffer, lineDelimiter);
final AbstractDocument content = data.startPostEdit();
final StringBuilder sb = new StringBuilder(64);
final Position[] sigPositions;
String sigText = null;
{ final TemplateVariable variable = TemplateUtils.findVariable(buffer,
RCodeTemplateContextType.ROXYGEN_SIG_LIST_VAR_NAME );
sigPositions = new Position[(variable != null) ? variable.getOffsets().length : 0];
for (int i = 0; i < sigPositions.length; i++) {
sigPositions[i] = new Position(variable.getOffsets()[i], variable.getLength());
content.addPosition(sigPositions[i]);
}
if (sigPositions.length > 0) {
final var parameters= rMethod.getParameters();
if (parameters != null) {
final int count = parameters.size();
for (int i = 0; i < count; i++) {
final var className= parameters.get(i).getClassName();
if (className == null || className.equals("ANY")) { //$NON-NLS-1$
break;
}
sb.append(className);
sb.append(","); //$NON-NLS-1$
}
if (sb.length() > 0) {
sigText = sb.substring(0, sb.length()-1);
sb.setLength(0);
}
}
}
}
final Position[] paramPositions;
String[] paramTags = null;
{ final TemplateVariable variable = TemplateUtils.findVariable(buffer,
RCodeTemplateContextType.ROXYGEN_PARAM_TAGS_VAR_NAME );
paramPositions = new Position[(variable != null) ? variable.getOffsets().length : 0];
for (int i = 0; i < paramPositions.length; i++) {
paramPositions[i] = new Position(variable.getOffsets()[i], variable.getLength());
content.addPosition(paramPositions[i]);
}
if (paramPositions.length > 0) {
final var parameters= rMethod.getParameters();
if (parameters != null) {
final int count = parameters.size();
paramTags = new String[count];
for (int i = 0; i < count; i++) {
final String paramName= parameters.get(i).getName();
sb.append("@param "); //$NON-NLS-1$
if (paramName != null) {
sb.append(paramName);
}
sb.append(" "); //$NON-NLS-1$
paramTags[i] = sb.toString();
sb.setLength(0);
}
}
}
}
for (final Position pos : sigPositions) {
insertRoxygen(content, pos, sigText);
}
for (final Position pos : paramPositions) {
insertRoxygen(content, pos, paramTags);
}
data.finishPostEdit();
return data;
}
catch (final Exception e) {
throw new CoreException(new Status(IStatus.ERROR, RUI.BUNDLE_ID, NLS.bind(
TemplateMessages.TemplateEvaluation_error_description, template.getDescription()), e));
}
}
/**
* Generates initial content for a new Rd file.
*
* @param su the Rd source unit to create the source for. The unit does not need to exist
* @param lineDelimiter the line delimiter to be used
* @return the new content or <code>null</code> if the template is undefined or empty
* @throws CoreException thrown when the evaluation of the code template fails
*/
public static EvaluatedTemplate getNewRdFileContent(final BasicRResourceSourceUnit su, final String lineDelimiter) throws CoreException {
final Template template = RUIPlugin.getInstance().getRdCodeGenerationTemplateStore().findTemplate(RdCodeTemplateContextType.NEW_RDOCFILE);
if (template == null) {
return null;
}
final RdCodeTemplateContext context = new RdCodeTemplateContext(
RdCodeTemplateContextType.NEW_RDOCFILE_CONTEXTTYPE, su, lineDelimiter);
try {
final TemplateBuffer buffer = context.evaluate(template);
if (buffer == null) {
return null;
}
return new TemplateUtils.EvaluatedTemplate(buffer, lineDelimiter);
}
catch (final Exception e) {
throw new CoreException(new Status(IStatus.ERROR, RUI.BUNDLE_ID, NLS.bind(
TemplateMessages.TemplateEvaluation_error_description, template.getDescription()), e));
}
}
private static void insertRoxygen(final AbstractDocument doc, final Position pos, final String[] tags) throws BadLocationException {
final int line = doc.getLineOfOffset(pos.getOffset());
final int lineOffset = doc.getLineOffset(line);
final int lineLength = doc.getLineLength(line);
final String orgLine = doc.get(lineOffset, lineLength);
final String prefix = orgLine.substring(0, pos.getOffset()-lineOffset); // can be replaced by more intelligent search
if (tags == null) {
return;
}
if (tags.length == 0) {
if (onlyWhitespace(orgLine.substring(prefix.length(), pos.getOffset()-lineOffset))
&& onlyWhitespace(orgLine.substring(pos.getOffset()-lineOffset+pos.getLength()))) {
doc.replace(lineOffset, lineLength, ""); //$NON-NLS-1$
return;
}
else {
doc.replace(pos.getOffset(), pos.getLength(), ""); //$NON-NLS-1$
return;
}
}
final StringBuilder sb = new StringBuilder(tags.length * 16);
sb.append(tags[0]);
for (int i = 1; i < tags.length; i++) {
sb.append(doc.getDefaultLineDelimiter());
sb.append(prefix);
sb.append(tags[i]);
}
doc.replace(pos.getOffset(), pos.getLength(), sb.toString());
}
private static void insertRoxygen(final AbstractDocument doc, final Position pos, final String s) throws BadLocationException {
doc.replace(pos.getOffset(), pos.getLength(), (s != null) ? s : ""); //$NON-NLS-1$
}
private static boolean onlyWhitespace(final String s) {
for (int i = 0; i < s.length(); i++) {
final char c = s.charAt(0);
if (c != ' ' && c != '\t' && c != '\n' && c != '\r') {
return false;
}
}
return true;
}
private CodeGeneration() {}
}