blob: f228193646890bd31f6d28896f01b676518c7967 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2012 Oracle. 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:
* Oracle - initial API and implementation
******************************************************************************/
package org.eclipse.jpt.jpa.ui.internal;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import org.eclipse.core.filebuffers.FileBuffers;
import org.eclipse.core.filebuffers.ITextFileBuffer;
import org.eclipse.core.filebuffers.ITextFileBufferManager;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.jface.resource.ResourceManager;
import org.eclipse.jface.text.contentassist.CompletionProposal;
import org.eclipse.jface.text.contentassist.ICompletionProposal;
import org.eclipse.jpt.common.ui.internal.WorkbenchTools;
import org.eclipse.jpt.common.utility.internal.StringTools;
import org.eclipse.jpt.common.utility.internal.collection.CollectionTools;
import org.eclipse.jpt.jpa.core.JpaFile;
import org.eclipse.jpt.jpa.core.JpaStructureNode;
import org.eclipse.jpt.jpa.core.context.XmlFile;
import org.eclipse.jpt.jpa.ui.JpaWorkbench;
import org.eclipse.jpt.jpa.ui.JptJpaUiImages;
import org.eclipse.jpt.jpa.ui.JptJpaUiMessages;
import org.eclipse.jpt.jpa.ui.internal.plugin.JptJpaUiPlugin;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.widgets.Control;
import org.eclipse.wst.sse.ui.contentassist.CompletionProposalInvocationContext;
import org.eclipse.wst.xml.core.internal.provisional.document.IDOMDocument;
import org.eclipse.wst.xml.core.internal.provisional.document.IDOMElement;
import org.eclipse.wst.xml.core.internal.provisional.document.IDOMNode;
import org.eclipse.wst.xml.ui.internal.XMLUIMessages;
import org.eclipse.wst.xml.ui.internal.contentassist.ContentAssistRequest;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
/**
* This computer adds content assist support for a mapping file (ORM Configuration).
*
* @version 2.3
* @since 2.3
*/
public class JpaXmlCompletionProposalComputer extends DefaultJpaXmlCompletionProposalComputer {
public JpaXmlCompletionProposalComputer() {
}
@Override
public List<ICompletionProposal> computeCompletionProposals(
CompletionProposalInvocationContext context,
IProgressMonitor monitor) {
try {
return super.computeCompletionProposals(context, monitor);
} catch (RuntimeException ex) {
// When we run into any unexpected exception, we will log the exception
// and then return an empty list to prevent code completion process from
// crashing. We need to determine if runtime exceptions should be
// expected. If so, we could remove the log(ex) in the future.
JptJpaUiPlugin.instance().logError(ex);
return Collections.emptyList();
}
}
@Override
protected void addAttributeValueProposals(ContentAssistRequest contentAssistRequest,
CompletionProposalInvocationContext context) {
List<String> proposedValues = getProposedValues(context);
if (proposedValues.size() > 0 ) {
String matchString = contentAssistRequest.getMatchString();
if (matchString == null) {
matchString = ""; //$NON-NLS-1$
}
// initialize newMatchingString to an empty string to handle the case when user invokes code assist without giving delimiter
String newMatchString = ""; //$NON-NLS-1$
if ((matchString.length() > 0) && (matchString.startsWith("\"") || matchString.startsWith("'"))) { //$NON-NLS-1$ //$NON-NLS-2$
newMatchString = matchString.substring(1);
}
//create completion proposals for this attribute value declaration
int rOffset = contentAssistRequest.getReplacementBeginPosition();
int rLength = contentAssistRequest.getReplacementLength();
for (String possibleValue : proposedValues) {
if ((newMatchString.length() == 0) || StringTools.startsWithIgnoreCase(possibleValue, newMatchString)) {
// handle values that include special characters like double-quote or apostrophe
String convertedPossibleValue = null;
if (matchString.startsWith("\"")) { //$NON-NLS-1$
convertedPossibleValue = StringTools.convertToDoubleQuotedXmlAttributeValue(possibleValue);
} else if (matchString.startsWith("'")) { //$NON-NLS-1$
convertedPossibleValue = StringTools.convertToSingleQuotedXmlAttributeValue(possibleValue);
} else {
convertedPossibleValue = StringTools.convertToXmlAttributeValue(possibleValue);
}
CompletionProposal proposal = null;
// give users an additional message if value includes special characters since what is written
// to XML is most likely different from what is shown in the proposal list with this case
if (possibleValue.startsWith("\"")) { //$NON-NLS-1$
// handle the case when user does ""<invoke code assist here>" trying to get these special values
// User cannot do ""F<invoke code assist here>" or ""F<invoke code assist here>"" trying to
// get these values that are special and start with F because XML would regard the F as the
// start of another attribute since there are two double-quotes exist before F.
if (matchString.startsWith("\"") && newMatchString.startsWith("\"")) { //$NON-NLS-1$ //$NON-NLS-2$
proposal = new CompletionProposal(
convertedPossibleValue, rOffset, rLength + 1, convertedPossibleValue.length(), null,
possibleValue, null, JptJpaUiMessages.JPA_XML_COMPLETION_PROPOSAL_COMPUTER_SPECIAL_NAME_MSG);
} else {
proposal = new CompletionProposal(
convertedPossibleValue, rOffset, rLength, convertedPossibleValue.length(), null,
possibleValue, null, JptJpaUiMessages.JPA_XML_COMPLETION_PROPOSAL_COMPUTER_SPECIAL_NAME_MSG);
}
} else {
// do not give the addition message with normal values
proposal = new CompletionProposal(
convertedPossibleValue, rOffset, rLength, convertedPossibleValue.length(), null,
possibleValue, null, null );
}
contentAssistRequest.addProposal(proposal);
}
}
}
else {
setErrorMessage(XMLUIMessages.Content_Assist_not_availab_UI_);
}
}
@SuppressWarnings("deprecation")
@Override
protected void addTagInsertionProposals(
ContentAssistRequest contentAssistRequest, int childPosition,
CompletionProposalInvocationContext context) {
Node parent = contentAssistRequest.getParent();
// This is only valid at the document element level
// and only valid if it's a XML file
if ((parent != null) && (parent.getNodeType() == Node.DOCUMENT_NODE) &&
((IDOMDocument) parent).isXMLType() && !isCursorAfterXMLPI(contentAssistRequest)) {
return;
}
// only want completion proposals if cursor is after doctype...
if (!isCursorAfterDoctype(contentAssistRequest)) {
return;
}
if ((parent != null) && (parent instanceof IDOMNode) && isCommentNode((IDOMNode) parent)) {
// loop and find non comment node?
while ((parent != null) && isCommentNode((IDOMNode) parent)) {
parent = parent.getParentNode();
}
}
List<String> proposedValues = getProposedValues(context);
if (proposedValues.size() > 0 ) {
String matchString = contentAssistRequest.getMatchString();
if (matchString == null) {
matchString = ""; //$NON-NLS-1$
}
int begin = contentAssistRequest.getReplacementBeginPosition();
int length = contentAssistRequest.getReplacementLength();
if ((parent instanceof IDOMNode) && (parent.getNodeType() == Node.ELEMENT_NODE)) {
if (((IDOMNode) parent).getLastStructuredDocumentRegion() != ((IDOMNode) parent).getFirstStructuredDocumentRegion()) {
begin = ((IDOMNode) parent).getFirstStructuredDocumentRegion().getEndOffset();
length = ((IDOMNode) parent).getLastStructuredDocumentRegion().getStartOffset() - begin;
}
}
for (String proposedValue : proposedValues) {
if ((matchString.length() == 0) || StringTools.startsWithIgnoreCase(proposedValue, matchString)) {
String convertedProposedValue = StringTools.convertToXmlElementText(proposedValue);
CompletionProposal proposal = null;
if (proposedValue.startsWith("\"")) { //$NON-NLS-1$
proposal = new CompletionProposal(
convertedProposedValue, begin, length, convertedProposedValue.length(),
this.getImage(context, JptJpaUiImages.JPA_CONTENT), proposedValue, null,
JptJpaUiMessages.JPA_XML_COMPLETION_PROPOSAL_COMPUTER_SPECIAL_NAME_MSG);
} else {
proposal = new CompletionProposal(
convertedProposedValue, begin, length, convertedProposedValue.length(),
this.getImage(context, JptJpaUiImages.JPA_CONTENT), proposedValue, null, null);
}
contentAssistRequest.addProposal(proposal);
}
}
}
else {
setErrorMessage(XMLUIMessages.Content_Assist_not_availab_UI_);
}
}
/**
* Retrieves all of the possible valid values for this attribute/element declaration
*/
private List<String> getProposedValues(CompletionProposalInvocationContext context) {
int documentPosition = context.getInvocationOffset();
if (documentPosition == -1) return Collections.emptyList();
ITextFileBufferManager manager = FileBuffers.getTextFileBufferManager();
ITextFileBuffer buffer = manager.getTextFileBuffer(context.getDocument());
if (buffer == null) return Collections.emptyList();
IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot();
IFile file = root.getFile(buffer.getLocation());
JpaFile jpaFile = (JpaFile) file.getAdapter(JpaFile.class);
if (jpaFile == null) return Collections.emptyList();
Collection<JpaStructureNode> rootStructureNodes = CollectionTools.collection(jpaFile.getRootStructureNodes());
if (rootStructureNodes.isEmpty()) {
return Collections.emptyList();
}
List<String> list = new ArrayList<String>();
for (JpaStructureNode node : rootStructureNodes) {
Iterable<String> proposals = ((XmlFile.Root) node).getCompletionProposals(documentPosition);
if (proposals != null) {
CollectionTools.addAll(list, proposals);
}
}
return list;
}
/**
* This method can check if the cursor is after the XMLPI
*/
protected boolean isCursorAfterXMLPI(ContentAssistRequest car) {
Node aNode = car.getNode();
boolean xmlpiFound = false;
Document parent = aNode.getOwnerDocument();
int xmlpiNodePosition = -1;
boolean isAfterXMLPI = false;
if (parent == null) {
return true; // blank document case
}
for (Node child = parent.getFirstChild(); child != null; child = child.getNextSibling()) {
boolean xmlpi = ((child.getNodeType() == Node.PROCESSING_INSTRUCTION_NODE) && child.getNodeName().equals("xml")); //$NON-NLS-1$
xmlpiFound = xmlpiFound || xmlpi;
if (xmlpiFound) {
if (child instanceof IDOMNode) {
xmlpiNodePosition = ((IDOMNode) child).getEndOffset();
isAfterXMLPI = (car.getReplacementBeginPosition() >= xmlpiNodePosition);
}
break;
}
}
return isAfterXMLPI;
}
private boolean isCursorAfterDoctype(ContentAssistRequest car) {
Node aNode = car.getNode();
Document parent = aNode.getOwnerDocument();
int xmldoctypeNodePosition = -1;
boolean isAfterDoctype = true;
if (parent == null) {
return true; // blank document case
}
for (Node child = parent.getFirstChild(); child != null; child = child.getNextSibling()) {
if (child instanceof IDOMNode) {
if (child.getNodeType() == Node.DOCUMENT_TYPE_NODE) {
xmldoctypeNodePosition = ((IDOMNode) child).getEndOffset();
isAfterDoctype = (car.getReplacementBeginPosition() >= xmldoctypeNodePosition);
break;
}
}
}
return isAfterDoctype;
}
/**
* This is to determine if a tag is a special meta-info comment tag that
* shows up as an ELEMENT
*/
private boolean isCommentNode(IDOMNode node) {
return ((node != null) && (node instanceof IDOMElement) && ((IDOMElement) node).isCommentTag());
}
private Image getImage(CompletionProposalInvocationContext context, ImageDescriptor descriptor) {
return this.getImage(context.getViewer().getTextWidget(), descriptor);
}
private Image getImage(Control control, ImageDescriptor descriptor) {
return this.getResourceManager(control).createImage(descriptor);
}
private ResourceManager getResourceManager(Control control) {
return this.getJpaWorkbench().getResourceManager(control);
}
private JpaWorkbench getJpaWorkbench() {
return WorkbenchTools.getAdapter(JpaWorkbench.class);
}
}