package org.flowerplatform.editor.remote;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.flowerplatform.communication.command.DisplaySimpleMessageClientCommand;
import org.flowerplatform.communication.service.ServiceInvocationContext;
import org.flowerplatform.communication.stateful_service.RemoteInvocation;
import org.flowerplatform.editor.EditorPlugin;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class EditorOperationsService {
private static final Logger logger = LoggerFactory.getLogger(EditorOperationsService.class);
/**
* An <em>external editableResourcePath</em> is either a <em>canonical editableResourcePath</em or a <em>friendly editableResourcePath</em>.
* It's format is <b>editor_name :/ openable_resource # fragment </b>
* <p>
* A <b>friendly editableResourcePath</b> is human readable while a <b>canonical editableResourcePath</b> is the internal representation
* of the Project Explorer Path Fragments.
*
* @see EditorStatefulService#getCanonicalEditableResourcePath()
* @see EditorStatefulService#getFriendlierEditableResourcePath()
* @see #navigateFriendlyEditableResourcePathList()
*
* @author Sorin
* @author Cristi
* @flowerModelElementId _TeUxAEhHEeKn-dlTSOkszw
*/
@RemoteInvocation
public List<String> getFriendlyEditableResourcePathList(List<String> canonicalEditableResourcePathList) {
List<String> friendlyEditableResourcePathList = new ArrayList<String>();
for (String canonicalEditableResourcePath : canonicalEditableResourcePathList) {
DecodedLink decodedLink = new DecodedLink(canonicalEditableResourcePath);
EditorStatefulService editorStatefulService = null;
if (decodedLink.editorName != null) {
editorStatefulService = EditorPlugin.getInstance().getEditorStatefulServiceByEditorName(decodedLink.editorName);
}
if (editorStatefulService == null) {
logger.error("Could not obtain EditorStatefulService with editorName = " + decodedLink.editorName);
continue;
}
friendlyEditableResourcePathList.add(editorStatefulService.getFriendlyEditableResourcePath(canonicalEditableResourcePath));
}
return friendlyEditableResourcePathList;
}
/**
* Called from client side with openResources and selectResourceAtIndex parameters to open editors for the user.
* If problems happen (like incorrect format of the paths) then a message is presented for each path with the source of error.
* <p>
* This tries to speak the language of EditorStatefulService by transforming from friendly to canonical editableResourcePath.
*
* @see EditorStatefulService#getCanonicalEditableResourcePath(String)
*
* @param openResources comma separated friendly editableResourcePaths
* @param selectResourceAtIndex optional, which resource's editor to reveal at the end.
*
* @return <code>true</code> if the resource was opened successfully, <code>false</code> otherwise
*
* @author Sorin
* @author Mariana
* @author Cristi
*
* @flowerModelElementId _TeVYEkhHEeKn-dlTSOkszw
*/
@RemoteInvocation
public boolean navigateFriendlyEditableResourcePathList(ServiceInvocationContext context, String openResources, int selectResourceAtIndex) {
List<String> friendlyEditableResourcePathList = parseFriendlyEditableResourcePathList(openResources);
// It keeps computed info about the "friendly editableResourcePath" to be selected (if found and if demanded by selectResourceAtIndex method parameter).
boolean selectedResourceAtIndexIsValid = false;
String selectedCanonicalOpenableResourcePath = null;
EditorStatefulService selectedEditorStatefulService = null;
StringBuffer validationProblems = new StringBuffer();
for (String friendlyEditableResourcePath : friendlyEditableResourcePathList) {
try { // Run navigation logic independent one another path
DecodedLink decodedLink = new DecodedLink(friendlyEditableResourcePath);
// String editorName = URLGenerateNavigateUtilities.getEditorNameFromExternalEditableResourcePath(friendlyEditableResourcePath);
if (decodedLink.editorName == null) {
// the link doesn't contain the editor name; so we try to find the default
// editor, based on its extension
String contentType = EditorPlugin.getInstance().getContentTypeFromFileName(decodedLink.resourcePath);
ContentTypeDescriptor descriptor = EditorPlugin.getInstance().getContentTypeDescriptorsMap().get(contentType);
if (descriptor == null) {
validationProblems.append("Could not determine content type for path '" + friendlyEditableResourcePath + "' .").append("\n");
continue;
} else {
if (!descriptor.getCompatibleEditors().isEmpty()) {
decodedLink.editorName = descriptor.getCompatibleEditors().get(0);
}
}
}
EditorStatefulService editorStatefulService = EditorPlugin.getInstance().getEditorStatefulServiceByEditorName(decodedLink.editorName);
if (editorStatefulService == null) { // Validation
validationProblems.append("Could not find editor '" + decodedLink.editorName + "' for path '" + friendlyEditableResourcePath + "' .").append("\n");
continue;
}
// Determine the canonical editableResourcePath to speak the language of EditorStatefulService.
String canonicalEditableResourcePath = editorStatefulService.getCanonicalEditableResourcePath(friendlyEditableResourcePath, context.getCommunicationChannel(), validationProblems);
if (canonicalEditableResourcePath == null) {
continue;
}
EditableResource editableResource = editorStatefulService.subscribeClientForcefully(context.getCommunicationChannel(), getResourcePath(decodedLink.resourcePath), true);
if (editableResource == null) { // Validation
validationProblems.append("Could not find '" + decodedLink.resourcePath + "' . Either it doesn't exist, or it failed during load.").append("\n");
continue;
}
if (friendlyEditableResourcePathList.indexOf(friendlyEditableResourcePath) == selectResourceAtIndex) { // Caches info about the friendly editableResourcePath that must be revealed in an editor.
selectedResourceAtIndexIsValid = true;
selectedCanonicalOpenableResourcePath = decodedLink.resourcePath;
selectedEditorStatefulService = editorStatefulService;
}
if (decodedLink.fragment != null) // No need to navigate if there is no fragment.
editorStatefulService.navigateToFragment(context.getCommunicationChannel(), decodedLink.resourcePath, decodedLink.fragment);
} catch (Throwable t) {
logger.error("Error happened while performing logic to navigate path : " + friendlyEditableResourcePath, t);
validationProblems.append("Internal error happened while trying to navigate to path '" + friendlyEditableResourcePath + "'.").append("\n");
}
}
if (selectedResourceAtIndexIsValid) // Makes sense to select tab only when multiple paths are opened and the selected path is valid.
selectedEditorStatefulService.revealEditor(context.getCommunicationChannel(), selectedCanonicalOpenableResourcePath);
if (logger.isTraceEnabled() && selectResourceAtIndex >= 0 && !selectedResourceAtIndexIsValid)
logger.trace("Could not reveal the " + selectResourceAtIndex + "-th resource because it could not be opened from openResources = " + openResources);
if (validationProblems.toString().length() != 0) { // Show to user the collected problems with a single popup message.
context.getCommunicationChannel().appendOrSendCommand(
new DisplaySimpleMessageClientCommand("Warning",
"There were some problems while opening resources!",
validationProblems.toString(),
DisplaySimpleMessageClientCommand.ICON_WARNING));
return false;
}
return true;
}
/**
* @flowerModelElementId _TeV_I0hHEeKn-dlTSOkszw
*/
protected List<String> parseFriendlyEditableResourcePathList(String openResources) {
openResources = openResources.replace("\r\n", ",").replace("\n", ",").replace("\r", ",").trim(); // make it easier to process when it is multiline
List<String> friendlyEditableResourcePathList = new ArrayList<String>();
for (String openResourceItem : openResources.split(",")) {
openResourceItem = openResourceItem.trim(); // make it easier to process
if (openResourceItem.length() > 0)
friendlyEditableResourcePathList.add(openResourceItem);
}
return friendlyEditableResourcePathList;
}
protected String getResourcePath(String path) {
return EditorPlugin.getInstance().getFriendlyNameDecoded(path);
}
/**
* @author Cristi
*/
private static class DecodedLink {
public String editorName;
public String resourcePath;
public String fragment;
public DecodedLink(String externalEditableResourcePath) {
// e.g. editor:/path/to/url#fragment
// editor and fragment are optional
final String regex = "(?:(.*?):/)?([^#]*)(?:#(.+))?";
Matcher matcher = Pattern.compile(regex).matcher(externalEditableResourcePath);
if (!matcher.find()) {
// this shouldn't happen, as the expression matches any string
}
editorName = getNullIfStringEmpty(matcher.group(1));
resourcePath = getNullIfStringEmpty(matcher.group(2));
fragment = getNullIfStringEmpty(matcher.group(3));
}
private String getNullIfStringEmpty(String s) {
if (s != null && s.trim().length() == 0) {
return null;
} else {
return s;
}
}
}
}