| /******************************************************************************* |
| * Copyright (c) 2004, 2006 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.utils.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); |
| } |
| |
| } |