package org.rubypeople.rdt.internal.core;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.text.edits.TextEdit;
import org.jruby.ast.DefnNode;
import org.jruby.ast.DefsNode;
import org.jruby.ast.NewlineNode;
import org.jruby.ast.Node;
import org.jruby.ast.RootNode;
import org.rubypeople.rdt.core.IRubyElement;
import org.rubypeople.rdt.core.IRubyModelStatus;
import org.rubypeople.rdt.core.IRubyModelStatusConstants;
import org.rubypeople.rdt.core.IRubyScript;
import org.rubypeople.rdt.core.IType;
import org.rubypeople.rdt.core.RubyModelException;
import org.rubypeople.rdt.internal.core.parser.RubyParser;
import org.rubypeople.rdt.internal.core.util.ASTRewrite;
import org.rubypeople.rdt.internal.core.util.ASTUtil;
import org.rubypeople.rdt.internal.core.util.Messages;
public class CreateMethodOperation extends RubyModelOperation {
/**
* A constant meaning to position the new element
* as the last child of its parent element.
*/
protected static final int INSERT_LAST = 1;
/**
* A constant meaning to position the new element
* after the element defined by <code>fAnchorElement</code>.
*/
protected static final int INSERT_AFTER = 2;
/**
* A constant meaning to position the new element
* before the element defined by <code>fAnchorElement</code>.
*/
protected static final int INSERT_BEFORE = 3;
/**
* One of the position constants, describing where
* to position the newly created element.
*/
protected int insertionPolicy = INSERT_LAST;
/**
* The element that the newly created element is
* positioned relative to, as described by
* <code>fInsertPosition</code>, or <code>null</code>
* if the newly created element will be positioned
* last.
*/
protected IRubyElement anchorElement = null;
/**
* A flag indicating whether creation of a new element occurred.
* A request for creating a duplicate element would request in this
* flag being set to <code>false</code>. Ensures that no deltas are generated
* when creation does not occur.
*/
protected boolean creationOccurred = true;
private String source;
private Node cuAST;
private Node createdNode;
private String[] parameters;
public CreateMethodOperation(IType parentElement, String source, boolean force) {
super(null, new IRubyElement[]{parentElement}, force);
this.source = source;
}
@Override
protected void executeOperation() throws RubyModelException {
try {
beginTask(getMainTaskName(), getMainAmountOfWork());
RubyElementDelta delta = newRubyElementDelta();
IRubyScript unit = getRubyScript();
generateNewRubyScriptAST(unit);
if (this.creationOccurred) {
//a change has really occurred
unit.save(null, false);
boolean isWorkingCopy = unit.isWorkingCopy();
if (!isWorkingCopy)
this.setAttribute(HAS_MODIFIED_RESOURCE_ATTR, TRUE);
worked(1);
resultElements = generateResultHandles();
if (!isWorkingCopy // if unit is working copy, then save will have already fired the delta
&& unit.getParent().exists()) {
for (int i = 0; i < resultElements.length; i++) {
delta.added(resultElements[i]);
}
addDelta(delta);
} // else unit is created outside classpath
// non-ruby resource delta will be notified by delta processor
}
} finally {
done();
}
}
/**
* Returns the IType the member is to be created in.
*/
protected IType getType() {
return (IType)getParentElement();
}
/**
* @see CreateElementInCUOperation#generateResultHandle
*/
protected IRubyElement generateResultHandle() {
String[] types = convertASTMethodTypesToSignatures();
String name = getASTNodeName();
return getType().getMethod(name, types);
}
private String getASTNodeName() {
if (this.createdNode instanceof DefsNode)
return ((DefsNode) this.createdNode).getName();
return ((DefnNode) this.createdNode).getName();
}
/**
* Returns the type signatures of the parameter types of the
* current <code>MethodDeclaration</code>
*/
protected String[] convertASTMethodTypesToSignatures() {
if (this.parameters == null) {
if (this.createdNode != null) {
DefnNode methodDeclaration = (DefnNode) this.createdNode;
this.parameters = ASTUtil.getArgs(methodDeclaration.getArgsNode(), methodDeclaration.getScope());
}
}
return this.parameters;
}
/**
* Creates and returns the handles for the elements this operation created.
*/
protected IRubyElement[] generateResultHandles() {
return new IRubyElement[]{generateResultHandle()};
}
/**
* Returns the ruby script in which the new element is being created.
*/
protected IRubyScript getRubyScript() {
return getRubyScriptFor(getParentElement());
}
/**
* @see CreateElementInCUOperation#getMainTaskName()
*/
public String getMainTaskName(){
return Messages.operation_createMethodProgress;
}
/**
* Returns the amount of work for the main task of this operation for
* progress reporting.
*/
protected int getMainAmountOfWork(){
return 2;
}
/**
* Instructs this operation to position the new element before
* the given sibling, or to add the new element as the last child
* of its parent if <code>null</code>.
*/
public void createBefore(IRubyElement sibling) {
setRelativePosition(sibling, INSERT_BEFORE);
}
/**
* Instructs this operation to position the new element relative
* to the given sibling, or to add the new element as the last child
* of its parent if <code>null</code>. The <code>position</code>
* must be one of the position constants.
*/
protected void setRelativePosition(IRubyElement sibling, int policy) throws IllegalArgumentException {
if (sibling == null) {
this.anchorElement = null;
this.insertionPolicy = INSERT_LAST;
} else {
this.anchorElement = sibling;
this.insertionPolicy = policy;
}
}
/*
* Generates a new AST for this operation and applies it to the given cu
*/
protected void generateNewRubyScriptAST(IRubyScript cu) throws RubyModelException {
this.cuAST = parse(cu);
IDocument document = getDocument(cu);
ASTRewrite rewriter = ASTRewrite.create(this.cuAST, document);
Node child = generateElementAST(document, cu);
if (child != null) {
Node parent = ((RubyElement) getParentElement()).findNode(this.cuAST);
if (parent == null)
parent = this.cuAST;
insertASTNode(rewriter, parent, child);
apply(rewriter, document);
}
worked(1);
}
private void insertASTNode(ASTRewrite rewriter, Node parent, Node child) {
switch (this.insertionPolicy) {
case INSERT_BEFORE:
Node element = ((RubyElement) this.anchorElement).findNode(this.cuAST);
rewriter.insertBefore(source, child, element, null);
case INSERT_AFTER:
element = ((RubyElement) this.anchorElement).findNode(this.cuAST);
rewriter.insertAfter(source, child, element, null);
case INSERT_LAST:
rewriter.insertLast(source, child, null);
break;
}
}
private Node generateElementAST(IDocument document, IRubyScript cu) {
RubyParser parser = new RubyParser();
Node root = parser.parse(this.source).getAST();
this.createdNode = ((NewlineNode) ((RootNode) root).getBodyNode()).getNextNode(); // Grab node from our parsed source!
return this.createdNode;
}
protected Node parse(IRubyScript cu) throws RubyModelException {
// ensure cu is consistent (noop if already consistent)
cu.makeConsistent(this.progressMonitor);
// create an AST for the ruby script
RubyParser parser = new RubyParser();
return parser.parse(cu.getSource()).getAST();
}
protected void apply(ASTRewrite rewriter, IDocument document) throws RubyModelException {
TextEdit edits = rewriter.rewriteAST(document, null);
try {
edits.apply(document);
} catch (BadLocationException e) {
throw new RubyModelException(e, IRubyModelStatusConstants.INVALID_CONTENTS);
}
}
/**
* Possible failures: <ul>
* <li>NO_ELEMENTS_TO_PROCESS - the compilation unit supplied to the operation is
* <code>null</code>.
* <li>INVALID_NAME - no name, a name was null or not a valid
* import declaration name.
* <li>INVALID_SIBLING - the sibling provided for positioning is not valid.
* </ul>
* @see IRubyModelStatus
*/
public IRubyModelStatus verify() {
if (getParentElement() == null) {
return new RubyModelStatus(IRubyModelStatusConstants.NO_ELEMENTS_TO_PROCESS);
}
if (this.anchorElement != null) {
IRubyElement domPresentParent = this.anchorElement.getParent();
if (!domPresentParent.equals(getParentElement())) {
return new RubyModelStatus(IRubyModelStatusConstants.INVALID_SIBLING, this.anchorElement);
}
}
return RubyModelStatus.VERIFIED_OK;
}
}