/** * Copyright (c) 2005-2011 by Appcelerator, Inc. All Rights Reserved. * Licensed under the terms of the Eclipse Public License (EPL). * Please see the license.txt included with this distribution for details. * Any modifications to this file must keep this entire header intact. */ package org.python.pydev.editor.codecompletion.revisited.javaintegration; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map.Entry; import org.eclipse.jdt.core.CompletionProposal; import org.eclipse.jdt.core.ICompilationUnit; import org.eclipse.jdt.core.IJavaElement; import org.eclipse.jdt.core.IMethod; import org.eclipse.jdt.core.JavaModelException; import org.eclipse.jdt.internal.ui.JavaPlugin; import org.eclipse.jdt.internal.ui.text.java.AbstractJavaCompletionProposal; import org.eclipse.jdt.ui.text.java.CompletionProposalCollector; import org.eclipse.jdt.ui.text.java.IJavaCompletionProposal; import org.eclipse.swt.widgets.Display; import org.python.pydev.core.FullRepIterable; import org.python.pydev.core.ICodeCompletionASTManager; import org.python.pydev.core.ICompletionCache; import org.python.pydev.core.ICompletionState; import org.python.pydev.core.IModule; import org.python.pydev.core.IPythonNature; import org.python.pydev.core.IToken; import org.python.pydev.core.docutils.StringUtils; import org.python.pydev.core.log.Log; import org.python.pydev.editor.actions.PyAction; import org.python.pydev.editor.codecompletion.PyCodeCompletionImages; import org.python.pydev.editor.codecompletion.revisited.modules.AbstractModule; import org.python.pydev.editor.codecompletion.revisited.modules.CompiledToken; import org.python.pydev.editor.codecompletion.revisited.visitors.Definition; import com.aptana.shared_core.string.FastStringBuffer; import com.aptana.shared_core.structure.Tuple; /** * This is an abstract class for modules based on java classes. * * @author Fabio */ public abstract class AbstractJavaClassModule extends AbstractModule { public static final boolean DEBUG_JAVA_COMPLETIONS = false; protected static final CompiledToken[] EMPTY_ITOKEN = new CompiledToken[0]; protected CompiledToken[] tokens; public static HashMap<String, String> replacementMap = new HashMap<String, String>(); static { replacementMap.put("object", "obj"); replacementMap.put("class", "class_"); replacementMap.put("[QString", "str"); replacementMap.put("[I", "int"); } public boolean hasFutureImportAbsoluteImportDeclared() { return false; } protected AbstractJavaClassModule(String name) { super(name); checkJavaImageDescriptorCreated(); } /** * Variable just to see if the java image descriptor was created. */ private static boolean imageDescriptorCreated = false; /** * This method must be called to make sure that the java image descriptor is already initialized. */ @SuppressWarnings("restriction") protected void checkJavaImageDescriptorCreated() { if (imageDescriptorCreated) { return; } //that's because if the JavaPlugin is not initialized, we'll have errors because it will try to create the //image descriptor registry from a non-display owner when making the completions (and in this way, we'll //guarantee that its cache is already created). try { JavaPlugin.getImageDescriptorRegistry(); } catch (Throwable e) { Display.getDefault().syncExec(new Runnable() { public void run() { try { JavaPlugin.getImageDescriptorRegistry(); } catch (Throwable e) { //ignore it at this point } } }); } imageDescriptorCreated = true; } /** * This method will create the tokens for a given package. */ protected CompiledToken[] createTokens(String packagePlusactTok) { ArrayList<CompiledToken> lst = new ArrayList<CompiledToken>(); try { //TODO: if we don't want to depend on jdt inner classes, we should create a org.eclipse.jdt.core.CompletionRequestor //(it's not currently done because its API is not as easy to handle). //we should be able to check the CompletionProposalCollector to see how we can transform the info we want... //also, making that change, it should be faster, because we won't need to 1st create a java proposal to then //create a pydev token (it would be a single step to transform it from a Completion Proposal to an IToken). List<Tuple<IJavaElement, CompletionProposal>> elementsFound = getJavaCompletionProposals(packagePlusactTok, null); HashMap<String, IJavaElement> generatedProperties = new HashMap<String, IJavaElement>(); FastStringBuffer tempBuffer = new FastStringBuffer(128); for (Tuple<IJavaElement, CompletionProposal> element : elementsFound) { IJavaElement javaElement = element.o1; String args = ""; if (javaElement instanceof IMethod) { tempBuffer.clear(); tempBuffer.append("()"); IMethod method = (IMethod) javaElement; for (String param : method.getParameterTypes()) { if (tempBuffer.length() > 2) { tempBuffer.insert(1, ", "); } //now, let's make the parameter 'pretty' String lastPart = FullRepIterable.getLastPart(param); if (lastPart.length() > 0) { lastPart = PyAction.lowerChar(lastPart, 0); if (lastPart.charAt(lastPart.length() - 1) == ';') { lastPart = lastPart.substring(0, lastPart.length() - 1); } } //we may have to replace it for some other word String replacement = replacementMap.get(lastPart); if (replacement != null) { lastPart = replacement; } tempBuffer.insert(1, lastPart); } args = tempBuffer.toString(); String elementName = method.getElementName(); if (elementName.startsWith("get") || elementName.startsWith("set")) { //Create a property for it tempBuffer.clear(); elementName = elementName.substring(3); if (elementName.length() > 0) { tempBuffer.append(Character.toLowerCase(elementName.charAt(0))); tempBuffer.append(elementName.substring(1)); String propertyName = tempBuffer.toString(); IJavaElement existing = generatedProperties.get(propertyName); if (existing != null) { if (existing.getElementName().startsWith("set")) { //getXXX has precedence over the setXXX. generatedProperties.put(propertyName, javaElement); } } else { generatedProperties.put(propertyName, javaElement); } } } } if (DEBUG_JAVA_COMPLETIONS) { System.out.println("Element: " + javaElement); } lst.add(new JavaElementToken(javaElement.getElementName(), "", args, this.name, getType(javaElement .getElementType()), javaElement, element.o2)); } //Fill our generated properties. for (Entry<String, IJavaElement> entry : generatedProperties.entrySet()) { IJavaElement javaElement = entry.getValue(); lst.add(new JavaElementToken(entry.getKey(), "", "", this.name, IToken.TYPE_ATTR, javaElement, PyCodeCompletionImages.getImageForType(IToken.TYPE_ATTR))); } } catch (Exception e) { Log.log(e); } return lst.toArray(new CompiledToken[lst.size()]); } /** * Stores the mapping from the java type to the IToken type */ private static HashMap<Integer, Integer> typesMapping = new HashMap<Integer, Integer>(); static { typesMapping.put(IJavaElement.CLASS_FILE, IToken.TYPE_CLASS); typesMapping.put(IJavaElement.COMPILATION_UNIT, IToken.TYPE_CLASS); typesMapping.put(IJavaElement.PACKAGE_DECLARATION, IToken.TYPE_PACKAGE); typesMapping.put(IJavaElement.PACKAGE_FRAGMENT, IToken.TYPE_PACKAGE); typesMapping.put(IJavaElement.PACKAGE_FRAGMENT_ROOT, IToken.TYPE_PACKAGE); } /** * @param elementType the java element type we're interested in * @return the IToken type which is correspondent to the java type */ private int getType(int elementType) { Integer found = typesMapping.get(elementType); if (found != null) { return found; } return IToken.TYPE_ATTR; } /** * Compiled modules do not have imports to be seen * @see org.python.pydev.editor.javacodecompletion.AbstractModule#getWildImportedModules() */ public IToken[] getWildImportedModules() { return EMPTY_ITOKEN; } /** * Compiled modules do not have imports to be seen * @see org.python.pydev.editor.javacodecompletion.AbstractModule#getTokenImportedModules() */ public IToken[] getTokenImportedModules() { return EMPTY_ITOKEN; } /** * @see org.python.pydev.editor.javacodecompletion.AbstractModule#getGlobalTokens() */ public IToken[] getGlobalTokens() { return this.tokens; } /** * @see org.python.pydev.editor.javacodecompletion.AbstractModule#getDocString() */ public String getDocString() { return "Java class module extension"; } /** * @see org.python.pydev.editor.codecompletion.revisited.modules.AbstractModule#getGlobalTokens(java.lang.String) */ public IToken[] getGlobalTokens(ICompletionState state, ICodeCompletionASTManager manager) { String actTok = state.getFullActivationToken(); if (actTok == null) { actTok = state.getActivationToken(); } if (actTok == null) { return new IToken[0]; } String act = new FastStringBuffer(name, 2 + actTok.length()).append('.').append(actTok).toString(); return createTokens(act); } @Override public boolean isInDirectGlobalTokens(String tok, ICompletionCache completionCache) { if (this.tokens != null) { return binaryHasObject(this.tokens, new CompiledToken(tok, "", "", "", 0)); } return false; } @Override public boolean isInGlobalTokens(String tok, IPythonNature nature, ICompletionCache completionCache) { if (tok.indexOf('.') == -1) { return isInDirectGlobalTokens(tok, completionCache); } else { System.err.println("Still no treated isInDirectGlobalTokens with dotted string:" + tok); return false; } } /** * Gotten from Arrays.binarySearch (but returning boolean if key was found or not). * * It also works directly with CompiledToken because we want a custom compare (from the representation) */ private static boolean binaryHasObject(CompiledToken[] a, CompiledToken key) { int low = 0; int high = a.length - 1; while (low <= high) { int mid = (low + high) >> 1; CompiledToken midVal = (CompiledToken) a[mid]; int cmp = midVal.getRepresentation().compareTo(key.getRepresentation()); if (cmp < 0) low = mid + 1; else if (cmp > 0) high = mid - 1; else return true; // key found } return false; // key not found. } /** * @param findInfo * @see org.python.pydev.editor.codecompletion.revisited.modules.AbstractModule#findDefinition(java.lang.String, int, int) */ public Definition[] findDefinition(ICompletionState state, int line, int col, IPythonNature nature) throws Exception { //try to see if that's a java class from a package... to do that, we must go iterating through the name found //to check if we're able to find modules with that name. If a module with that name is found, that means that //we actually have a java class. List<String> splitted = StringUtils.dotSplit(state.getActivationToken()); FastStringBuffer modNameBuf = new FastStringBuffer(this.getName(), 128); IModule validModule = null; IModule module = null; int i = 0; //so that we know what will result in the tok for (String s : splitted) { modNameBuf.append("."); modNameBuf.append(s); module = nature.getAstManager().getModule(modNameBuf.toString(), nature, true, false); if (module != null) { validModule = module; } else { break; } } modNameBuf.clear(); FastStringBuffer pathInJavaClass = modNameBuf; //get it cleared if (validModule == null) { validModule = this; pathInJavaClass.clear(); pathInJavaClass.append(state.getActivationToken()); } else { //After having found a valid java class, we must also check which was the resulting token within that class //to check if it's some method or something alike (that should be easy after having the class and the path //to the method we want to find within it). if (!(validModule instanceof AbstractJavaClassModule)) { throw new RuntimeException("The module found from a java class module was found as another kind: " + validModule.getClass()); } for (int j = i; j < splitted.size(); j++) { if (j != i) { pathInJavaClass.append('.'); } pathInJavaClass.append(splitted.get(j)); } } AbstractJavaClassModule javaClassModule = (AbstractJavaClassModule) validModule; IJavaElement elementFound = null; String foundAs; if (pathInJavaClass.length() == 0) { //ok, now, if there is no path, the definition is the java class itself. foundAs = ""; elementFound = findJavaElement(javaClassModule.getName()); } else { //ok, it's not the class directly, so, we have to check what it actually is. foundAs = pathInJavaClass.toString(); List<Tuple<IJavaElement, CompletionProposal>> javaCompletionProposals = getJavaCompletionProposals( javaClassModule.getName(), foundAs); if (javaCompletionProposals.size() > 0) { elementFound = javaCompletionProposals.get(0).o1; } else if (javaClassModule.getName().endsWith("." + foundAs)) { //This is the following case: we have a reference to the constructor (e.g.: javax.swing.JFrame.JFrame) //So, we have to ignore the last JFrame part. foundAs = ""; elementFound = findJavaElement(javaClassModule.getName()); } } if (elementFound != null) { return new Definition[] { new JavaDefinition(foundAs, javaClassModule, elementFound) }; } //no definitions found return new Definition[0]; } /** * @return tuple with: * - a list of tuples corresponding to the element and the proposal for the gotten elements * */ protected abstract IJavaElement findJavaElement(String javaClassModuleName) throws Exception; /** * Gets tuples with the java element and the corresponding completion proposal for that element. * * @param completeClassDesc the name of the class from where we should get the tokens. E.g. java.lang.Class, javax.swing.JFrame * @param filterCompletionName if specified, only return matches from elements that have the name passed (otherwise it should be null) * @return a list of tuples corresponding to the element and the proposal for the gotten elements * @throws JavaModelException */ protected abstract List<Tuple<IJavaElement, CompletionProposal>> getJavaCompletionProposals( String completeClassDesc, String filterCompletionName) throws Exception; /** * Gets tuples with the java element and the corresponding completion proposal for that element. * * @param contents the contents that should be set for doing the code-completion * @param completionOffset the offset where the code completion should be requested * @param filterCompletionName if specified, only return matches from elements that have the name passed (otherwise it should be null) * @return a list of tuples corresponding to the element and the proposal for the gotten elements * @throws JavaModelException */ protected abstract List<Tuple<IJavaElement, CompletionProposal>> getJavaCompletionProposals(String contents, int completionOffset, final String filterCompletionName) throws Exception; /** * Create a proposal collector that's able to gather the passed completions/related java elements and adds * them to the passed 'ret' parameter * * @param filterCompletionName may be null or a name to which we want to match the java element name (so, only * a java element with an exact match of its name to filterCompletionName is added). * * @param ret the placeholder for the found java elements and completion proposals * @param unit the ICompilationUnit that's used (must be passed in the CompletionProposalCollector constructor). * @return the collector that will gather the completions (note that it'll keep the 'ret' placeholder alive unti it's * garbage-collected. */ protected CompletionProposalCollector createCollector(final String filterCompletionName, final List<Tuple<IJavaElement, CompletionProposal>> ret, ICompilationUnit unit) { CompletionProposalCollector collector = new CompletionProposalCollector(unit) { /** * Override the java proposal creation to always return null, as we'll keep just what we actually need. */ @SuppressWarnings("restriction") @Override public IJavaCompletionProposal createJavaCompletionProposal(CompletionProposal proposal) { IJavaCompletionProposal javaCompletionProposal = super.createJavaCompletionProposal(proposal); if (javaCompletionProposal instanceof AbstractJavaCompletionProposal) { AbstractJavaCompletionProposal prop = (AbstractJavaCompletionProposal) javaCompletionProposal; IJavaElement javaElement = prop.getJavaElement(); if (javaElement != null) { if (filterCompletionName == null) { ret.add(new Tuple<IJavaElement, CompletionProposal>(javaElement, proposal)); return null; } if (javaElement.getElementName().equals(filterCompletionName)) { ret.add(new Tuple<IJavaElement, CompletionProposal>(javaElement, proposal)); return null; } } } return null; } }; return collector; } /** * For java, as we don't have __init__.py, the package folder name is always the actual name of the module */ @Override public String getPackageFolderName() { return this.name; } }