| /*=============================================================================# |
| # Copyright (c) 2005, 2019 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.elements.IModelElement; |
| import org.eclipse.statet.ltk.model.core.elements.ISourceUnit; |
| import org.eclipse.statet.ltk.ui.templates.TemplateUtils; |
| import org.eclipse.statet.ltk.ui.templates.TemplateUtils.EvaluatedTemplate; |
| import org.eclipse.statet.r.core.RResourceUnit; |
| import org.eclipse.statet.r.core.model.ArgsDefinition; |
| import org.eclipse.statet.r.core.model.ArgsDefinition.Arg; |
| import org.eclipse.statet.r.core.model.IRClass; |
| import org.eclipse.statet.r.core.model.IRElement; |
| import org.eclipse.statet.r.core.model.IRMethod; |
| import org.eclipse.statet.r.core.model.IRSlot; |
| import org.eclipse.statet.r.core.model.IRSourceUnit; |
| 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 IRSourceUnit 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 IRMethod rMethod, final String lineDelimiter) throws CoreException { |
| final Template template = RUIPlugin.getInstance().getRCodeGenerationTemplateStore() |
| .findTemplate(RCodeTemplateContextType.ROXYGEN_COMMONFUNCTION_TEMPLATE_ID); |
| if (template == null) { |
| return null; |
| } |
| |
| final ISourceUnit 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 ArgsDefinition args = rMethod.getArgsDefinition(); |
| if (args != null) { |
| final int count = args.size(); |
| tags = new String[count]; |
| for (int i = 0; i < count; i++) { |
| tagBuffer.append("@param "); //$NON-NLS-1$ |
| tagBuffer.append(args.get(i).name); |
| 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 IRClass rClass, final String lineDelimiter) throws CoreException { |
| final Template template = RUIPlugin.getInstance().getRCodeGenerationTemplateStore() |
| .findTemplate(RCodeTemplateContextType.ROXYGEN_S4CLASS_TEMPLATE_ID); |
| if (template == null) { |
| return null; |
| } |
| |
| final ISourceUnit 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) { |
| String[] tags = null; |
| final List<? extends IModelElement> slots = rClass.getModelChildren(IRElement.R_S4SLOT_FILTER); |
| final int count = slots.size(); |
| tags = new String[count]; |
| for (int i = 0; i < count; i++) { |
| final IRSlot slot = (IRSlot) 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 IRMethod rMethod, final String lineDelimiter) throws CoreException { |
| final Template template = RUIPlugin.getInstance().getRCodeGenerationTemplateStore() |
| .findTemplate(RCodeTemplateContextType.ROXYGEN_S4METHOD_TEMPLATE_ID); |
| if (template == null) { |
| return null; |
| } |
| |
| final ISourceUnit 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 ArgsDefinition args = rMethod.getArgsDefinition(); |
| if (args != null) { |
| final int count = args.size(); |
| for (int i = 0; i < count; i++) { |
| final Arg arg = args.get(i); |
| if (arg.className == null || arg.className.equals("ANY")) { //$NON-NLS-1$ |
| break; |
| } |
| sb.append(arg.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 ArgsDefinition args = rMethod.getArgsDefinition(); |
| if (args != null) { |
| final int count = args.size(); |
| paramTags = new String[count]; |
| for (int i = 0; i < count; i++) { |
| sb.append("@param "); //$NON-NLS-1$ |
| sb.append(args.get(i).name); |
| 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 RResourceUnit 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() {} |
| |
| } |