/*******************************************************************************
* Copyright (c) 2012-2015 Codenvy, S.A.
* 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:
* Codenvy, S.A. - initial API and implementation
*******************************************************************************/
package org.eclipse.che.ide.ext.java.client.editor;
import org.eclipse.che.ide.api.event.ProjectActionEvent;
import org.eclipse.che.ide.api.event.ProjectActionHandler;
import org.eclipse.che.ide.collections.Array;
import org.eclipse.che.ide.collections.Collections;
import org.eclipse.che.ide.collections.Jso;
import org.eclipse.che.ide.collections.StringMap;
import org.eclipse.che.ide.collections.js.JsoArray;
import org.eclipse.che.ide.collections.js.JsoStringMap;
import org.eclipse.che.ide.ext.java.jdt.core.compiler.IProblem;
import org.eclipse.che.ide.ext.java.jdt.internal.compiler.problem.DefaultProblem;
import org.eclipse.che.ide.ext.java.messages.CAProposalsComputedMessage;
import org.eclipse.che.ide.ext.java.messages.ComputeJavadocHandle;
import org.eclipse.che.ide.ext.java.messages.FileClosedMessage;
import org.eclipse.che.ide.ext.java.messages.FormatResultMessage;
import org.eclipse.che.ide.ext.java.messages.JavadocHandleComputed;
import org.eclipse.che.ide.ext.java.messages.Problem;
import org.eclipse.che.ide.ext.java.messages.ProblemLocationMessage;
import org.eclipse.che.ide.ext.java.messages.ProblemsMessage;
import org.eclipse.che.ide.ext.java.messages.ProposalAppliedMessage;
import org.eclipse.che.ide.ext.java.messages.RoutingTypes;
import org.eclipse.che.ide.ext.java.messages.WorkerProposal;
import org.eclipse.che.ide.ext.java.messages.impl.MessagesImpls;
import org.eclipse.che.ide.ext.java.messages.impl.OutlineUpdateMessage;
import org.eclipse.che.ide.ext.java.messages.impl.WorkerCodeBlock;
import org.eclipse.che.ide.ext.java.jdt.text.edits.CopySourceEdit;
import org.eclipse.che.ide.ext.java.jdt.text.edits.CopyTargetEdit;
import org.eclipse.che.ide.ext.java.jdt.text.edits.CopyingRangeMarker;
import org.eclipse.che.ide.ext.java.jdt.text.edits.DeleteEdit;
import org.eclipse.che.ide.ext.java.jdt.text.edits.InsertEdit;
import org.eclipse.che.ide.ext.java.jdt.text.edits.MoveSourceEdit;
import org.eclipse.che.ide.ext.java.jdt.text.edits.MoveTargetEdit;
import org.eclipse.che.ide.ext.java.jdt.text.edits.MultiTextEdit;
import org.eclipse.che.ide.ext.java.jdt.text.edits.RangeMarker;
import org.eclipse.che.ide.ext.java.jdt.text.edits.ReplaceEdit;
import org.eclipse.che.ide.ext.java.jdt.text.edits.TextEdit;
import org.eclipse.che.ide.util.UUID;
import org.eclipse.che.ide.util.loging.Log;
import com.google.gwt.core.client.GWT;
import com.google.gwt.webworker.client.ErrorEvent;
import com.google.gwt.webworker.client.ErrorHandler;
import com.google.gwt.webworker.client.MessageEvent;
import com.google.gwt.webworker.client.MessageHandler;
import com.google.gwt.webworker.client.Worker;
import com.google.gwt.webworker.client.messages.MessageFilter;
import com.google.gwt.webworker.client.messages.MessageImpl;
import com.google.inject.Inject;
import com.google.inject.name.Named;
import com.google.web.bindery.event.shared.EventBus;
/**
* The type Java parser worker impl.
*
* @author <a href="mailto:evidolob@codenvy.com">Evgen Vidolob</a>
*/
public class JavaParserWorkerImpl implements JavaParserWorker, ProjectActionHandler, MessageFilter.MessageRecipient<ProblemsMessage> {
private final MessageFilter messageFilter;
private final String workspaceId;
private String javaCAPath;
private Worker worker;
private String restContext;
private StringMap<WorkerCallback<?>> arrayCallbacks;
private StringMap<WorkerCallback<WorkerCodeBlock>> outlineCallbacks = Collections.createStringMap();
private StringMap<Callback<?>> callbacks = Collections.createStringMap();
@Inject
public JavaParserWorkerImpl(EventBus eventBus, @Named("restContext") String restContext,
@Named("workspaceId") String workspaceId, @Named("javaCA") String javaCAPath) {
this.restContext = restContext;
this.workspaceId = workspaceId;
this.javaCAPath = javaCAPath;
eventBus.addHandler(ProjectActionEvent.TYPE, this);
messageFilter = new MessageFilter();
arrayCallbacks = Collections.createStringMap();
messageFilter.registerMessageRecipient(RoutingTypes.PROBLEMS, this);
messageFilter.registerMessageRecipient(RoutingTypes.CA_PROPOSALS_COMPUTED,
new MessageFilter.MessageRecipient<CAProposalsComputedMessage>() {
@Override
public void onMessageReceived(CAProposalsComputedMessage message) {
handleCAComputed(message);
}
});
messageFilter
.registerMessageRecipient(RoutingTypes.CA_PROPOSAL_APPLIED, new MessageFilter.MessageRecipient<ProposalAppliedMessage>() {
@Override
public void onMessageReceived(ProposalAppliedMessage message) {
handleProposalApplied(message);
}
});
messageFilter
.registerMessageRecipient(RoutingTypes.OUTLINE_CODE_BLOCKS, new MessageFilter.MessageRecipient<OutlineUpdateMessage>() {
@Override
public void onMessageReceived(OutlineUpdateMessage message) {
handleUpdateOutline(message);
}
});
messageFilter
.registerMessageRecipient(RoutingTypes.FORMAT_RESULT, new MessageFilter.MessageRecipient<FormatResultMessage>() {
@Override
public void onMessageReceived(FormatResultMessage message) {
handleFormatApplied(message);
}
});
messageFilter.registerMessageRecipient(RoutingTypes.JAVADOC_HANDLE_COMPUTED,
new MessageFilter.MessageRecipient<JavadocHandleComputed>() {
@Override
public void onMessageReceived(JavadocHandleComputed message) {
handleJavadocMessage(message);
}
});
}
@SuppressWarnings("unchecked")
private void handleJavadocMessage(JavadocHandleComputed message) {
if (callbacks.containsKey(message.getId())) {
Callback<JavadocHandleComputed> callback = (Callback<JavadocHandleComputed>)callbacks.remove(message.getId());
callback.onCallback(message);
}
}
@SuppressWarnings("unchecked")
private void handleFormatApplied(FormatResultMessage message) {
Callback<TextEdit> callback = (Callback<TextEdit>)callbacks.remove(message.id());
Jso textEditJso = message.textEdit();
TextEdit textEdit = convertTextEditFromJso(textEditJso);
callback.onCallback(textEdit);
}
private TextEdit convertTextEditFromJso(Jso editJso) {
TextEdit textEdit = null;
String text;
int length;
int offSet = editJso.getFieldCastedToInteger("offSet");
String type = editJso.getStringField("type");
switch (type) {
case "ReplaceEdit":
text = editJso.getStringField("text");
length = editJso.getFieldCastedToInteger("length");
textEdit = new ReplaceEdit(offSet, length, text);
break;
case "DeleteEdit":
length = editJso.getFieldCastedToInteger("length");
textEdit = new DeleteEdit(offSet, length);
break;
case "InsertEdit":
text = editJso.getStringField("text");
textEdit = new InsertEdit(offSet, text);
break;
case "CopyingRangeMarker":
length = editJso.getFieldCastedToInteger("length");
textEdit = new CopyingRangeMarker(offSet, length);
break;
case "CopySourceEdit":
length = editJso.getFieldCastedToInteger("length");
Jso copyTargetEditJso = (Jso)editJso.getObjectField("CopyTargetEdit");
if (copyTargetEditJso != null) {
int offSetCopyTargetEdit = copyTargetEditJso.getFieldCastedToInteger("offSet");
CopyTargetEdit copyTargetEdit = new CopyTargetEdit(offSetCopyTargetEdit);
textEdit = new CopySourceEdit(offSet, length, copyTargetEdit);
} else textEdit = new CopySourceEdit(offSet, length);
break;
case "MoveSourceEdit":
length = editJso.getFieldCastedToInteger("length");
Jso moveTargetEditJso = (Jso)editJso.getObjectField("MoveTargetEdit");
if (moveTargetEditJso != null) {
int offSetMoveTargetEdit = moveTargetEditJso.getFieldCastedToInteger("offSet");
MoveTargetEdit moveTargetEdit = new MoveTargetEdit(offSetMoveTargetEdit);
textEdit = new MoveSourceEdit(offSet, length, moveTargetEdit);
} else textEdit = new MoveSourceEdit(offSet, length);
break;
case "MoveTargetEdit":
Jso moveSourceEditJso = (Jso)editJso.getObjectField("MoveSourceEdit");
if (moveSourceEditJso != null) {
int offSetMoveSourceEdit = moveSourceEditJso.getFieldCastedToInteger("offSet");
int lengthMoveSourceEdit = moveSourceEditJso.getFieldCastedToInteger("length");
MoveSourceEdit moveSourceEdit = new MoveSourceEdit(offSetMoveSourceEdit, lengthMoveSourceEdit);
textEdit = new MoveTargetEdit(offSet, moveSourceEdit);
} else textEdit = new MoveTargetEdit(offSet);
break;
case "MultiTextEdit":
length = editJso.getFieldCastedToInteger("length");
textEdit = new MultiTextEdit(offSet, length);
break;
case "RangeMarker":
length = editJso.getFieldCastedToInteger("length");
textEdit = new RangeMarker(offSet, length);
break;
case "CopyTargetEdit":
Jso copySourceEditJso = (Jso)editJso.getObjectField("CopySourceEdit");
if (copySourceEditJso != null) {
int offSetCopySourceEdit = copySourceEditJso.getFieldCastedToInteger("offSet");
int lengthCopySourceEdit = copySourceEditJso.getFieldCastedToInteger("length");
CopySourceEdit copySourceEdit = new CopySourceEdit(offSetCopySourceEdit, lengthCopySourceEdit);
textEdit = new CopyTargetEdit(offSet, copySourceEdit);
} else textEdit = new CopyTargetEdit(offSet);
break;
}
JsoArray children = editJso.getArrayField("children");
if (textEdit != null && children != null) {
textEdit.addChildren(convertChildrenTextEditFromJso(children));
}
return textEdit;
}
private TextEdit[] convertChildrenTextEditFromJso(JsoArray childrenJso) {
TextEdit[] edits = new TextEdit[childrenJso.size()];
for (int i = 0; i < childrenJso.size(); i++) {
Jso child = (Jso)childrenJso.get(i);
edits[i] = convertTextEditFromJso(child);
}
return edits;
}
private void handleUpdateOutline(OutlineUpdateMessage message) {
if (outlineCallbacks.containsKey(message.getFilePath())) {
WorkerCallback<WorkerCodeBlock> callback = outlineCallbacks.get(message.getFilePath());
callback.onResult(message.getBlocks());
}
}
@SuppressWarnings("unchecked")
private void handleProposalApplied(ProposalAppliedMessage message) {
if (callbacks.containsKey(message.id())) {
Callback<ProposalAppliedMessage> callback = (Callback<ProposalAppliedMessage>)callbacks.remove(message.id());
callback.onCallback(message);
}
}
@SuppressWarnings("unchecked")
private void handleCAComputed(CAProposalsComputedMessage message) {
if (!arrayCallbacks.containsKey(message.id())) {
return;
}
WorkerCallback<WorkerProposal> callback = (WorkerCallback<WorkerProposal>)arrayCallbacks.remove(message.id());
callback.onResult(message.proposals());
}
@Override
public void removeFqnFromCache(String fqn) {
if (worker == null) {
return;
}
MessagesImpls.RemoveFqnMessageImpl message = MessagesImpls.RemoveFqnMessageImpl.make();
message.setFqn(fqn);
worker.postMessage(message.serialize());
}
@Override
public void format(int offset, int length, String content, Callback<TextEdit> callback) {
String uuid = UUID.uuid();
callbacks.put(uuid, callback);
MessagesImpls.FormatMessageImpl message = MessagesImpls.FormatMessageImpl.make();
message.setId(uuid).setOffset(offset).setLength(length).setContent(content);
worker.postMessage(message.serialize());
}
@Override
public void preferenceFormatSettings(JsoStringMap<String> settings) {
MessagesImpls.PreferenceFormatSetMessageImpl message = MessagesImpls.PreferenceFormatSetMessageImpl.make();
message.setSettings(settings);
worker.postMessage(message.serialize());
}
@Override
public void computeJavadocHandle(int offset, String filePath, Callback<JavadocHandleComputed> callback) {
String uuid = UUID.uuid();
ComputeJavadocHandle message = ComputeJavadocHandle.make();
message.setOffset(offset).setId(uuid).setFilePath(filePath);
callbacks.put(uuid, callback);
worker.postMessage(message.serialize());
}
@Override
public void fileClosed(String path) {
FileClosedMessage message = FileClosedMessage.make();
message.setFilePath(path);
if (worker != null) {
worker.postMessage(message.serialize());
} else {
Log.error(getClass(), "worker is null");
}
}
@Override
public void dependenciesUpdated() {
MessagesImpls.DependenciesUpdatedMessageImpl message = MessagesImpls.DependenciesUpdatedMessageImpl.make();
worker.postMessage(message.serialize());
}
/** {@inheritDoc} */
@Override
public void parse(String content, String fileName, String filePath, String packageName, String projectPath,
boolean ignoreMethodBody, WorkerCallback<IProblem> callback) {
if (worker == null) {
return;
}
MessagesImpls.ParseMessageImpl parseMessage = MessagesImpls.ParseMessageImpl.make();
String uuid = UUID.uuid();
arrayCallbacks.put(uuid, callback);
parseMessage.setSource(content).setFileName(fileName).setFilePath(filePath).setId(uuid).setPackageName(packageName)
.setProjectPath(projectPath).setIgnoreMethodBodiess(ignoreMethodBody);
worker.postMessage(parseMessage.serialize());
}
/** {@inheritDoc} */
@Override
public void computeCAProposals(String content, int offset, String fileName, String projectPath,
String filePath, WorkerCallback<WorkerProposal> callback) {
if (worker == null) {
return;
}
MessagesImpls.ComputeCAProposalsMessageImpl computeMessage = MessagesImpls.ComputeCAProposalsMessageImpl.make();
String uuid = UUID.uuid();
arrayCallbacks.put(uuid, callback);
computeMessage.setDocContent(content).setOffset(offset).setFileName(fileName).setId(uuid).setProjectPath(projectPath)
.setFilePath(filePath);
worker.postMessage(computeMessage.serialize());
}
/** {@inheritDoc} */
@Override
public void applyCAProposal(String id, Callback<ProposalAppliedMessage> callback) {
if (worker == null) {
return;
}
MessagesImpls.ApplyProposalMessageImpl message = MessagesImpls.ApplyProposalMessageImpl.make();
message.setId(id);
callbacks.put(id, callback);
worker.postMessage(message.serialize());
}
@Override
public void addOutlineUpdateHandler(String filePath, WorkerCallback<WorkerCodeBlock> callback) {
outlineCallbacks.put(filePath, callback);
}
@Override
public void computeQAProposals(String content, int offset, int selectionLength, boolean updatedContent,
JsoArray<ProblemLocationMessage> problems, String filePath, WorkerCallback<WorkerProposal> callback) {
MessagesImpls.ComputeCorrMessageImpl corrMessage = MessagesImpls.ComputeCorrMessageImpl.make();
corrMessage.setDocumentContent(content)
.setDocumentOffset(offset)
.setDocumentSelectionLength(selectionLength)
.setUpdatedOffset(updatedContent)
.setProblemLocations(problems)
.setFilePath(filePath);
String uuid = UUID.uuid();
arrayCallbacks.put(uuid, callback);
corrMessage.setId(uuid);
worker.postMessage(corrMessage.serialize());
}
@Override
public void onProjectOpened(ProjectActionEvent event) {
if (worker != null) {
worker.terminate();
}
try {
//TODO check project type, create worker only if project is Java
// worker = Worker.create("http://localhost:8080/ide/_app/javaParserWorker/javaParserWorker.nocache.js");
worker = Worker.create(GWT.getModuleBaseURL() + "javaParserWorker/javaParserWorker.nocache.js");
worker.setOnMessage(new MessageHandler() {
@Override
public void onMessage(MessageEvent event) {
MessageImpl message = event.getDataAsJSO().cast();
messageFilter.dispatchMessage(message);
}
});
worker.setOnError(new ErrorHandler() {
@Override
public void onError(ErrorEvent event) {
Log.error(JavaParserWorkerImpl.class, event.getMessage(), event.getFilename(), event.getLineNumber());
}
});
MessagesImpls.ConfigMessageImpl config = MessagesImpls.ConfigMessageImpl.make();
config.setRestContext(restContext);
config.setWsId("/" + workspaceId);
config.setCaPath(javaCAPath);
config.setProjectName(event.getProject().getName());
config.setJavaDocContext(""); //TODO configure doc context
worker.postMessage(config.serialize());
} catch (Exception e) {
Log.error(getClass(), e);
}
}
@Override
public void onProjectClosed(ProjectActionEvent event) {
if (worker != null) {
worker.terminate();
worker = null;
}
}
@Override
@SuppressWarnings("unchecked")
public void onMessageReceived(ProblemsMessage message) {
if (!arrayCallbacks.containsKey(message.id())) {
return;
}
Array<Problem> problems = message.problems();
Array<IProblem> iProblems = Collections.createArray();
for (Problem p : problems.asIterable()) {
String[] arg = new String[p.stringArguments().size()];
for (int i = 0; i < p.stringArguments().size(); i++) {
arg[i] = p.stringArguments().get(i);
}
iProblems.add(new DefaultProblem(p.originatingFileName().toCharArray(), p.message(), p.id(), arg, p.severity(),
p.startPosition(),
p.endPosition(), p.line(), p.column()));
}
WorkerCallback<IProblem> callback = (WorkerCallback<IProblem>)arrayCallbacks.remove(message.id());
callback.onResult(iProblems);
}
}