/* * Copyright 2009-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.codehaus.groovy.eclipse.codeassist; import static org.codehaus.groovy.eclipse.core.util.ListUtil.newEmptyList; import java.util.Collections; import java.util.List; import org.codehaus.groovy.ast.ClassNode; import org.codehaus.groovy.ast.MethodNode; import org.codehaus.groovy.ast.Parameter; import org.codehaus.groovy.eclipse.codeassist.completions.NamedArgsMethodNode; import org.codehaus.groovy.eclipse.codeassist.proposals.IGroovyProposal; import org.eclipse.jdt.core.CompletionProposal; import org.eclipse.jdt.core.IClasspathEntry; import org.eclipse.jdt.core.IJavaElement; import org.eclipse.jdt.core.IType; import org.eclipse.jdt.core.JavaModelException; import org.eclipse.jdt.core.Signature; import org.eclipse.jdt.core.compiler.CharOperation; import org.eclipse.jdt.groovy.search.VariableScope; import org.eclipse.jdt.internal.compiler.env.AccessRestriction; import org.eclipse.jdt.internal.compiler.env.AccessRuleSet; import org.eclipse.jdt.internal.core.ClasspathEntry; import org.eclipse.jdt.internal.core.PackageFragmentRoot; import org.eclipse.jdt.internal.ui.JavaPlugin; import org.eclipse.jdt.internal.ui.JavaPluginImages; import org.eclipse.jdt.internal.ui.viewsupport.ImageDescriptorRegistry; import org.eclipse.jdt.ui.text.java.CompletionProposalLabelProvider; import org.eclipse.jface.text.BadLocationException; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.contentassist.ICompletionProposal; import org.eclipse.jface.viewers.StyledString; import org.eclipse.swt.graphics.Image; public class ProposalUtils { private ProposalUtils() {} // Taken from org.eclipse.jdt.ui.text.java.CompletionProposalCollector /** Triggers for method proposals without parameters. Do not modify. */ public final static char[] METHOD_TRIGGERS = new char[] { ';', ',', '.', '\t', '[', ' ' }; /** Triggers for method proposals. Do not modify. */ public final static char[] METHOD_WITH_ARGUMENTS_TRIGGERS = new char[] { '(', '-', ' ' }; /** Triggers for types. Do not modify. */ // public final static char[] TYPE_TRIGGERS = new char[] { '.', '\t', '[', '(', ' ' }; // In groovy, types are valid expression, so add all the var triggers as well public final static char[] TYPE_TRIGGERS = new char[] { '.', '[', '(', ' ', '\t', '=', ';' }; /** Triggers for variables. Do not modify. */ public final static char[] VAR_TRIGGER = new char[] { '\t', ' ', '=', ';', '.' }; public static final List<IGroovyProposal> NO_PROPOSALS = Collections.emptyList(); public static final ICompletionProposal[] NO_COMPLETIONS = new ICompletionProposal[0]; private static ImageDescriptorRegistry registry; static { try { registry = JavaPlugin.getImageDescriptorRegistry(); } catch (Exception e) { // exception in initialization when testing e.printStackTrace(); registry = null; } } public static char[] createTypeSignature(ClassNode node) { return createTypeSignatureStr(node).toCharArray(); } public static String createTypeSignatureStr(ClassNode node) { if (node == null) { node = VariableScope.OBJECT_CLASS_NODE; } String name = node.getName(); if (name.startsWith("[")) { return name; } else { return Signature.createTypeSignature(name, true); } } public static String createUnresolvedTypeSignatureStr(ClassNode node) { String name = node.getNameWithoutPackage(); if (name.startsWith("[")) { return name; } else { return Signature.createTypeSignature(name, false); } } /** * Can be null if access restriction cannot be resolved for given type. */ public static AccessRestriction getTypeAccessibility(IType type) { PackageFragmentRoot root = (PackageFragmentRoot) type.getAncestor(IJavaElement.PACKAGE_FRAGMENT_ROOT); try { IClasspathEntry entry = root.getResolvedClasspathEntry(); // Alternative: // entry = ((JavaProject) typeProject).getClasspathEntryFor(root // .getPath()); if (entry instanceof ClasspathEntry) { AccessRuleSet accessRuleSet = ((ClasspathEntry) entry).getAccessRuleSet(); if (accessRuleSet != null) { char[] packageName = type.getPackageFragment().getElementName().toCharArray(); char[][] packageChars = CharOperation.splitOn('.', packageName); char[] fileWithoutExtension = type.getElementName().toCharArray(); return accessRuleSet.getViolatedRestriction(CharOperation.concatWith(packageChars, fileWithoutExtension, '/')); } } } catch (JavaModelException e) { } return null; } /** * Includes named params but not optional params. */ public static char[] createMethodSignature(MethodNode node) { return createMethodSignatureStr(node, 0).toCharArray(); } /** * Includes named params but not optional params. */ public static String createMethodSignatureStr(MethodNode node) { return createMethodSignatureStr(node, 0); } /** * Includes named params but not optional params. * * @param ignoreParameters number of parameters to ignore at the start */ public static char[] createMethodSignature(MethodNode node, int ignoreParameters) { return createMethodSignatureStr(node, ignoreParameters).toCharArray(); } /** * Includes named params but not optional params. * * @param ignoreParameters number of parameters to ignore at the start */ public static String createMethodSignatureStr(MethodNode node, int ignoreParameters) { String returnType = createTypeSignatureStr(node.getReturnType()); Parameter[] parameters; if (node instanceof NamedArgsMethodNode) { parameters = ((NamedArgsMethodNode) node).getVisibleParams(); } else { parameters = node.getParameters(); } String[] parameterTypes = new String[parameters.length - ignoreParameters]; for (int i = 0; i < parameterTypes.length; i++) { parameterTypes[i] = createTypeSignatureStr(parameters[i + ignoreParameters].getType()); } return Signature.createMethodSignature(parameterTypes, returnType); } public static char[] createSimpleTypeName(ClassNode node) { String name = node.getName(); if (name.startsWith("[")) { int arrayCount = Signature.getArrayCount(name); String noArrayName = Signature.getElementType(name); String simpleName = Signature.getSignatureSimpleName(noArrayName); StringBuilder sb = new StringBuilder(); sb.append(simpleName); for (int i = 0; i < arrayCount; i++) { sb.append("[]"); } return sb.toString().toCharArray(); } else { return node.getNameWithoutPackage().toCharArray(); } } private static final CompletionProposalLabelProvider labelProvider = new CompletionProposalLabelProvider(); public static Image getImage(CompletionProposal proposal) { return registry.get(labelProvider.createImageDescriptor(proposal)); } public static Image getParameterImage() { return registry.get(JavaPluginImages.DESC_OBJS_LOCAL_VARIABLE); } public static StyledString createDisplayString(CompletionProposal proposal) { return labelProvider.createStyledLabel(proposal); } /** * Match ignoring case and checking camel case. */ public static boolean looselyMatches(String prefix, String target) { if (target == null || prefix == null) { return false; } // Zero length string matches everything. if (prefix.length() == 0) { return true; } // Exclude a bunch right away if (prefix.charAt(0) != target.charAt(0)) { return false; } if (target.startsWith(prefix)) { return true; } String lowerCase = target.toLowerCase(); if (lowerCase.startsWith(prefix)) { return true; } // Test for camel characters in the prefix. if (prefix.equals(prefix.toLowerCase())) { return false; } String[] prefixParts = toCamelCaseParts(prefix); String[] targetParts = toCamelCaseParts(target); if (prefixParts.length > targetParts.length) { return false; } for (int i = 0; i < prefixParts.length; ++i) { if (!targetParts[i].startsWith(prefixParts[i])) { return false; } } return true; } /** * Converts an input string into parts delimited by upper case characters. Used for camel case matches. * e.g. GroClaL = ['Gro','Cla','L'] to match say 'GroovyClassLoader'. * e.g. mA = ['m','A'] */ private static String[] toCamelCaseParts(String str) { List<String> parts = newEmptyList(); for (int i = str.length() - 1; i >= 0; --i) { if (Character.isUpperCase(str.charAt(i))) { parts.add(str.substring(i)); str = str.substring(0, i); } } if (str.length() != 0) { parts.add(str); } Collections.reverse(parts); return parts.toArray(new String[parts.size()]); } /** * Creates a name for a field if this is a getter or a setter method name. */ public static String createMockFieldName(String methodName) { int prefix = methodName.startsWith("is") ? 2 : 3; if (methodName.length() > prefix) { /* * Check if second character of the field name is upper case and * then return the field name without converting first character to * lower case. */ if (methodName.length() > prefix + 1 && Character.isUpperCase(methodName.charAt(prefix + 1))) { return methodName.substring(prefix); } else { return Character.toLowerCase(methodName.charAt(prefix)) + methodName.substring(prefix + 1); } } else { return "$$$$$"; } } /** * Creates a name for a field if this is a getter or a setter method name. * The resulting name is capitalized. */ public static String createCapitalMockFieldName(String methodName) { return methodName.length() > 3 ? methodName.substring(3) : "$$$$$"; } public static boolean hasWhitespace(char[] chars) { for (char c : chars) { if (CharOperation.isWhitespace(c)) { return true; } } return false; } /** Checks '.&' operator before replacement offset. */ public static boolean isMethodPointerCompletion(IDocument document, int replacementOffset) { try { boolean seenAmpersand = false; while (--replacementOffset > 0) { char c = document.getChar(replacementOffset); if (Character.isJavaIdentifierPart(c) || (!Character.isWhitespace(c) && c != '&' && c != '.')) break; if (c == '&') { if (seenAmpersand) break; seenAmpersand = true; } else if (c == '.') { if (seenAmpersand) return true; break; } } } catch (BadLocationException e) { } return false; } }