blob: f6d877ac242c82fc7f25bfc32be5ca6e18f9d00b [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2004 IBM Corporation and others.
* 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:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.jst.jsp.ui.internal.contentassist;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.ITextViewer;
import org.eclipse.jst.jsp.core.internal.provisional.JSP11Namespace;
import org.eclipse.jst.jsp.core.internal.provisional.JSP12Namespace;
import org.eclipse.jst.jsp.core.internal.regions.DOMJSPRegionContexts;
import org.eclipse.swt.graphics.Image;
import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocument;
import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocumentRegion;
import org.eclipse.wst.sse.core.internal.provisional.text.ITextRegion;
import org.eclipse.wst.sse.core.internal.provisional.text.ITextRegionList;
import org.eclipse.wst.sse.core.internal.util.StringUtils;
import org.eclipse.wst.sse.ui.internal.contentassist.CustomCompletionProposal;
import org.eclipse.wst.sse.ui.internal.contentassist.IRelevanceCompletionProposal;
import org.eclipse.wst.xml.core.internal.regions.DOMRegionContext;
/**
* An implementation of ICompletionProposal whose values can be
* read after creation.
*
* @plannedfor 1.0
*/
public class JavaTypeCompletionProposal extends CustomCompletionProposal implements IRelevanceCompletionProposal {
private int fCursorPosition = 0;
private String fLocalDisplayString;
private String fShortName;
private String fQualifiedName;
private String fContainerName;
public JavaTypeCompletionProposal(String replacementString, int replacementOffset, int replacementLength, String qualifiedName, Image image, String typeName, String containerName, int relevence, boolean updateReplacementLengthOnValidate) {
super(replacementString, replacementOffset, replacementLength, qualifiedName.length() + 2, image,
(containerName != null && containerName.length() > 0) ? typeName + " - " + containerName : typeName, null, null, relevence, true); //$NON-NLS-1$
// CMVC 243817, superclass was comparing incorrect display string in validate method...
//super(replacementString, replacementOffset, replacementLength, image, (containerName != null && containerName.length() > 0)? typeName + " - " + containerName:typeName/*qualifiedName*/, relevence);
fShortName = typeName;
fQualifiedName = qualifiedName;
fContainerName = containerName;
fCursorPosition = fQualifiedName.length() + 2;
//fProposalInfo = proposalInfo;
if (containerName != null && containerName.length() > 0)
fLocalDisplayString = typeName + " - " + containerName; //$NON-NLS-1$
else
fLocalDisplayString = typeName;
}
public String getDisplayString() {
return fLocalDisplayString;
}
public int getCursorPosition() {
return fCursorPosition;
}
public void setCursorPosition(int cursorPosition) {
super.setCursorPosition(cursorPosition);
fCursorPosition = cursorPosition;
}
public String getQualifiedName() {
return fQualifiedName;
}
public String getAdditionalProposalInfo() {
// String info = super.getAdditionalProposalInfo();
// if (info == null || info.length() == 0 && fProposalInfo != null)
// return fProposalInfo.getInfo();
// return info;
return null; // unexplained NPE
}
public String getShortName() {
return fShortName;
}
protected String getImport(IStructuredDocumentRegion flatNode) {
ITextRegionList regions = flatNode.getRegions();
String importSpec = null;
boolean isImport = false;
for (int i = 0; i < regions.size(); i++) {
ITextRegion region = regions.get(i);
if (region.getType() == DOMRegionContext.XML_TAG_ATTRIBUTE_NAME) {
if (flatNode.getText(region).equals(JSP11Namespace.ATTR_NAME_IMPORT)) {
isImport = true;
}
else {
isImport = false;
}
}
else if (isImport && region.getType() == DOMRegionContext.XML_TAG_ATTRIBUTE_VALUE) {
importSpec = flatNode.getText(region);
}
}
return importSpec;
}
/**
* Add an import page directive for the current type name
*/
protected int applyImport(IStructuredDocument model) {
// if the type is in the default package or java.lang, skip it
if (fContainerName == null || fContainerName.length() == 0 || fContainerName.equals("java.lang")) //$NON-NLS-1$
return 0;
// collect page directives and store their import values
List imports = new ArrayList();
IStructuredDocumentRegion node = model.getFirstStructuredDocumentRegion();
// use the last position of a page directive as a hint as to where to add
// a new one
int hint = 0;
// watch for jsp:root so that we use the right XML/JSP format for the directive
boolean useXML = false;
while (node != null) {
// Can't just look for all StructuredDocumentRegions starting with JSP_DIRECTIVE_OPEN
// since the XML form is required, too
ITextRegionList regions = node.getRegions();
if (regions.size() > 1) {
ITextRegion name = regions.get(1);
// verify that this is a JSP directive
if (name.getType() == DOMJSPRegionContexts.JSP_DIRECTIVE_NAME) {
// verify that this is a *page* directive
if (node.getText(name).equals(JSP11Namespace.ATTR_NAME_PAGE) || node.getText(name).equals(JSP12Namespace.ElementName.DIRECTIVE_PAGE)) {
if (node.getEndOffset() < getReplacementOffset())
hint = node.getEndOffset();
String importSpec = getImport(node);
if (importSpec != null) {
imports.add(importSpec);
}
}
}
else {
// if this is a jsp:root tag, use the XML form
useXML = useXML || name.getType() == DOMJSPRegionContexts.JSP_ROOT_TAG_NAME;
}
}
node = node.getNext();
}
// evaluate requirements for a "new" import directive
boolean needsImport = !importHandles(fQualifiedName, imports);
int adjustmentLength = 0;
// insert "new" import directive
if (needsImport) {
String directive = null;
// vary the XML behavior
if (useXML) {
directive = "<jsp:directive.page import=\"" + fQualifiedName + "\"/>"; //$NON-NLS-1$ //$NON-NLS-2$
}
else {
directive = "<%@ page import=\"" + fQualifiedName + "\" %>"; //$NON-NLS-1$ //$NON-NLS-2$
}
try {
IRegion line = model.getLineInformationOfOffset(hint);
boolean prependNewLine = line.getOffset() + line.getLength() == hint;
boolean appendNewLine = hint == 0;
if (prependNewLine)
directive = model.getLineDelimiter() + directive;
if (appendNewLine)
directive = directive + model.getLineDelimiter();
adjustmentLength = directive.length();
}
catch (BadLocationException e) {
// ignore
}
try {
model.replace(hint, 0, directive);
}
catch (BadLocationException e) {
// Not that we should ever get a BLE, but if so, our
// replacement offset from the Content Assist call should
// work
try {
model.replace(getReplacementOffset(), 0, directive);
adjustmentLength = directive.length();
}
catch (BadLocationException e2) {
// now what?
}
}
}
return adjustmentLength;
}
/**
* See if the import specification is a wildcard import, and if so, that
* it applies to the given type.
*/
protected boolean isWildcardMatch(String importSpec, String type) {
int specLength = importSpec.length();
if (importSpec.endsWith("*") && specLength > 2 && type.length() >= specLength) { //$NON-NLS-1$
// pull out the package name including the final '.'
String container = importSpec.substring(0, specLength - 1);
// verify that the type is in the container's hierarchy and that
// there are no other package separators afterwards
if (type.startsWith(container) && type.indexOf('.', specLength - 1) < 0) {
// container matches
return true;
}
}
return false;
}
protected boolean importHandles(String type, List listOfImports) {
Iterator imports = listOfImports.iterator();
while (imports.hasNext()) {
String importSpec = StringUtils.strip(imports.next().toString());
if (importSpec.equals(type) || isWildcardMatch(importSpec, type))
return true;
}
return false;
}
public void apply(IDocument document, char trigger, int offset) {
// If we have a parsed IStructuredDocument, insert the short name instead of the
// fully qualified name and a import page directive if
// needed. Do the import first so the cursor goes to the right location
// and we don't surprise the user.
boolean addShortForm = false; //document instanceof IStructuredDocument && fContainerName != null && fContainerName.length() > 0;
if (addShortForm) {
setReplacementString('"' + fShortName + '"');
int importLength = applyImport((IStructuredDocument) document);
setReplacementOffset(getReplacementOffset() + importLength);
}
setCursorPosition(getReplacementString().length());
super.apply(document, trigger, offset);
}
/*
* @see org.eclipse.jface.text.contentassist.ICompletionProposalExtension1#apply(org.eclipse.jface.text.ITextViewer, char, int, int)
*/
public void apply(ITextViewer viewer, char trigger, int stateMask, int offset) {
// CMVC 243815
// (pa) this is overridden to get around replacement length modification
// which is done in the super (since eclipse 2.1)
apply(viewer.getDocument(), trigger, offset);
}
// code is borrowed from JavaCompletionProposal
protected boolean startsWith(IDocument document, int offset, String word) {
int wordLength = word == null ? 0 : word.length();
if (offset > getReplacementOffset() + wordLength)
return false;
try {
int length = offset - getReplacementOffset();
// CMVC 243817
// slightly modified to be a little more flexible for attribute value string matching..
String start = StringUtils.stripQuotes(document.get(getReplacementOffset(), length));
return word.toLowerCase().startsWith(start.toLowerCase()) || fQualifiedName.toLowerCase().startsWith(start.toLowerCase());
//return word.substring(0, length).equalsIgnoreCase(start);
}
catch (BadLocationException x) {
// ignore
}
return false;
}
/* (non-Javadoc)
* @see org.eclipse.jdt.internal.ui.text.java.JavaCompletionProposal#validate(org.eclipse.jface.text.IDocument, int, org.eclipse.jface.text.DocumentEvent)
// */
// public boolean validate(IDocument document, int offset, org.eclipse.jface.text.DocumentEvent event) {
// return super.validate(document, offset, event);
// }
/* (non-Javadoc)
* @see org.eclipse.jdt.internal.ui.text.java.JavaCompletionProposal#selected(org.eclipse.jface.text.ITextViewer, boolean)
*/
public void selected(ITextViewer viewer, boolean smartToggle) {
// (pa) we currently don't use smart toggle...
super.selected(viewer, false);
}
}